--- a/.hgtags Tue Dec 12 11:17:25 2017 +0100
+++ b/.hgtags Fri Jan 12 11:02:52 2018 +0100
@@ -608,3 +608,9 @@
5010381099f1227724261665f0843a60447991b2 3.25.2
5010381099f1227724261665f0843a60447991b2 debian/3.25.2-1
5010381099f1227724261665f0843a60447991b2 centos/3.25.2-1
+d238badfc268ad4440b3238a24690858bad3fbdd 3.25.3
+d238badfc268ad4440b3238a24690858bad3fbdd centos/3.25.3-1
+d238badfc268ad4440b3238a24690858bad3fbdd debian/3.25.3-1
+b8567725c473b701fe9352e578ad6e05c523c1f2 3.25.4
+b8567725c473b701fe9352e578ad6e05c523c1f2 centos/3.25.4-1
+b8567725c473b701fe9352e578ad6e05c523c1f2 debian/3.25.4-1
--- a/cubicweb.spec Tue Dec 12 11:17:25 2017 +0100
+++ b/cubicweb.spec Fri Jan 12 11:02:52 2018 +0100
@@ -8,7 +8,7 @@
%{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}
Name: cubicweb
-Version: 3.25.2
+Version: 3.25.4
Release: logilab.1%{?dist}
Summary: CubicWeb is a semantic web application framework
Source0: https://pypi.python.org/packages/source/c/cubicweb/cubicweb-%{version}.tar.gz
@@ -29,6 +29,7 @@
Requires: %{python}-logilab-database >= 1.15.0
Requires: %{python}-passlib
Requires: %{python}-lxml
+Requires: %{python}-unittest2 >= 0.7.0
Requires: %{python}-twisted-web < 16.0.0
Requires: %{python}-markdown
Requires: pytz
--- a/cubicweb/entities/__init__.py Tue Dec 12 11:17:25 2017 +0100
+++ b/cubicweb/entities/__init__.py Fri Jan 12 11:02:52 2018 +0100
@@ -98,15 +98,29 @@
# meta data api ###########################################################
- def __getattr__(self, name):
- prefix = 'dc_'
- if name.startswith(prefix):
- # Proxy to IDublinCore adapter for bw compat.
- adapted = self.cw_adapt_to('IDublinCore')
- method = name[len(prefix):]
- if hasattr(adapted, method):
- return getattr(adapted, method)
- raise AttributeError(name)
+ def dc_title(self):
+ return self.cw_adapt_to('IDublinCore').title()
+
+ def dc_long_title(self):
+ return self.cw_adapt_to('IDublinCore').long_title()
+
+ def dc_description(self, *args, **kwargs):
+ return self.cw_adapt_to('IDublinCore').description(*args, **kwargs)
+
+ def dc_authors(self):
+ return self.cw_adapt_to('IDublinCore').authors()
+
+ def dc_creator(self):
+ return self.cw_adapt_to('IDublinCore').creator()
+
+ def dc_date(self, *args, **kwargs):
+ return self.cw_adapt_to('IDublinCore').date(*args, **kwargs)
+
+ def dc_type(self, *args, **kwargs):
+ return self.cw_adapt_to('IDublinCore').type(*args, **kwargs)
+
+ def dc_language(self):
+ return self.cw_adapt_to('IDublinCore').language()
@property
def creator(self):
--- a/cubicweb/pyramid/pyramidctl.py Tue Dec 12 11:17:25 2017 +0100
+++ b/cubicweb/pyramid/pyramidctl.py Fri Jan 12 11:02:52 2018 +0100
@@ -350,13 +350,15 @@
host = cwconfig['interface']
port = cwconfig['port'] or 8080
+ url_scheme = ('https' if cwconfig['base-url'].startswith('https')
+ else 'http')
repo = app.application.registry['cubicweb.repository']
warnings.warn(
'the "pyramid" command does not start repository "looping tasks" '
'anymore; use the standalone "scheduler" command if needed'
)
try:
- waitress.serve(app, host=host, port=port)
+ waitress.serve(app, host=host, port=port, url_scheme=url_scheme)
finally:
repo.shutdown()
if self._needreload:
--- a/cubicweb/rtags.py Tue Dec 12 11:17:25 2017 +0100
+++ b/cubicweb/rtags.py Fri Jan 12 11:02:52 2018 +0100
@@ -88,7 +88,14 @@
def __repr__(self):
# find a way to have more infos but keep it readable
# (in error messages in case of an ambiguity for instance)
- return '%s (%s): %s' % (id(self), self.__regid__, self.__class__)
+ return '<%s %s>' % (self.__regid__, self._short_repr())
+
+ def _short_repr(self):
+ # find a way to have more infos but keep it readable
+ # (in error messages in case of an ambiguity for instance)
+ return '%s@0x%x%s' % (
+ self.__module__, id(self),
+ ' derived from %s' % self._parent._short_repr() if self._parent else '')
# dict compat
def __getitem__(self, key):
--- a/cubicweb/schema.py Tue Dec 12 11:17:25 2017 +0100
+++ b/cubicweb/schema.py Fri Jan 12 11:02:52 2018 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2016 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -19,9 +19,8 @@
from __future__ import print_function
-import pkgutil
import re
-from os.path import join, basename
+from os.path import join
from hashlib import md5
from logging import getLogger
from warnings import warn
@@ -29,7 +28,6 @@
from six import PY2, text_type, string_types, add_metaclass
from six.moves import range
-from logilab.common import tempattr
from logilab.common.decorators import cached, clear_cache, monkeypatch, cachedproperty
from logilab.common.logging_ext import set_log_methods
from logilab.common.deprecation import deprecated
@@ -44,14 +42,15 @@
cstr_json_dumps, cstr_json_loads)
from yams.reader import (CONSTRAINTS, PyFileReader, SchemaLoader,
cleanup_sys_modules, fill_schema_from_namespace)
+from yams.buildobjs import _add_relation as yams_add_relation
-from rql import parse, nodes, RQLSyntaxError, TypeResolverException
+from rql import parse, nodes, stmts, RQLSyntaxError, TypeResolverException
from rql.analyze import ETypeResolver
import cubicweb
+from cubicweb import server
from cubicweb import ETYPE_NAME_MAP, ValidationError, Unauthorized, _
-from cubicweb import server
PURE_VIRTUAL_RTYPES = set(('identity', 'has_text',))
VIRTUAL_RTYPES = set(('eid', 'identity', 'has_text',))
@@ -602,6 +601,8 @@
a given form
"""
return display_name(req, self.type, form, context)
+
+
ERSchema.display_name = ERSchema_display_name
@@ -621,6 +622,8 @@
return frozenset(g for g in self.permissions[action] if isinstance(g, string_types))
except KeyError:
return ()
+
+
PermissionMixIn.get_groups = get_groups
@@ -640,6 +643,8 @@
return tuple(g for g in self.permissions[action] if not isinstance(g, string_types))
except KeyError:
return ()
+
+
PermissionMixIn.get_rqlexprs = get_rqlexprs
@@ -656,6 +661,8 @@
orig_set_action_permissions(self, action, tuple(permissions))
clear_cache(self, 'get_rqlexprs')
clear_cache(self, 'get_groups')
+
+
orig_set_action_permissions = PermissionMixIn.set_action_permissions
PermissionMixIn.set_action_permissions = set_action_permissions
@@ -673,6 +680,8 @@
if action in ('update', 'delete'):
return 'owners' in self.get_groups(action)
return False
+
+
PermissionMixIn.has_local_role = has_local_role
@@ -681,6 +690,8 @@
self.has_perm(req, 'read')):
return False
return self.has_local_role(action) or self.has_perm(req, action)
+
+
PermissionMixIn.may_have_permission = may_have_permission
@@ -691,6 +702,8 @@
return True
except Unauthorized:
return False
+
+
PermissionMixIn.has_perm = has_perm
@@ -734,6 +747,8 @@
for rqlexpr in self.get_rqlexprs(action)):
return
raise Unauthorized(action, str(self))
+
+
PermissionMixIn.check_perm = check_perm
@@ -818,7 +833,7 @@
"""convenience method that returns the *main* (i.e. the first non meta)
attribute defined in the entity schema
"""
- for rschema, _ in self.attribute_definitions():
+ for rschema, __ in self.attribute_definitions():
if not (rschema in META_RTYPES
or self.is_metadata(rschema)):
return rschema
@@ -1262,7 +1277,7 @@
#
# possible enhancement: check entity being created, it's probably
# the main eid unless this is a composite relation
- if eidto is None or 'S' in self.mainvars or not 'O' in self.mainvars:
+ if eidto is None or 'S' in self.mainvars or 'O' not in self.mainvars:
maineid = eidfrom
qname = role_name(rtype, 'subject')
else:
@@ -1272,7 +1287,7 @@
msg = session._(self.msg)
else:
msg = '%(constraint)s %(expression)s failed' % {
- 'constraint': session._(self.type()),
+ 'constraint': session._(self.type()),
'expression': self.expression}
raise ValidationError(maineid, {qname: msg})
@@ -1320,9 +1335,6 @@
# workflow extensions #########################################################
-from yams.buildobjs import _add_relation as yams_add_relation
-
-
class workflowable_definition(ybo.metadefinition):
"""extends default EntityType's metaclass to add workflow relations
(i.e. in_state, wf_info_for and custom_workflow). This is the default
@@ -1409,7 +1421,8 @@
"""
self.info('loading %s schemas', ', '.join(config.cubes()))
try:
- return super(CubicWebSchemaLoader, self).load(config, config.schema_modnames(), **kwargs)
+ return super(CubicWebSchemaLoader, self).load(
+ config, config.schema_modnames(), **kwargs)
finally:
# we've to cleanup modules imported from cubicweb.schemas as well
cleanup_sys_modules([join(cubicweb.CW_SOFTWARE_ROOT, 'schemas')])
@@ -1448,29 +1461,36 @@
return self.regular_formats + tuple(NEED_PERM_FORMATS)
return self.regular_formats
+
# XXX itou for some Statement methods
-from rql import stmts
-
def bw_get_etype(self, name):
return orig_get_etype(self, bw_normalize_etype(name))
+
+
orig_get_etype = stmts.ScopeNode.get_etype
stmts.ScopeNode.get_etype = bw_get_etype
def bw_add_main_variable_delete(self, etype, vref):
return orig_add_main_variable_delete(self, bw_normalize_etype(etype), vref)
+
+
orig_add_main_variable_delete = stmts.Delete.add_main_variable
stmts.Delete.add_main_variable = bw_add_main_variable_delete
def bw_add_main_variable_insert(self, etype, vref):
return orig_add_main_variable_insert(self, bw_normalize_etype(etype), vref)
+
+
orig_add_main_variable_insert = stmts.Insert.add_main_variable
stmts.Insert.add_main_variable = bw_add_main_variable_insert
def bw_set_statement_type(self, etype):
return orig_set_statement_type(self, bw_normalize_etype(etype))
+
+
orig_set_statement_type = stmts.Select.set_statement_type
stmts.Select.set_statement_type = bw_set_statement_type
--- a/cubicweb/server/ssplanner.py Tue Dec 12 11:17:25 2017 +0100
+++ b/cubicweb/server/ssplanner.py Fri Jan 12 11:02:52 2018 +0100
@@ -24,7 +24,7 @@
from cubicweb import QueryError
from cubicweb.schema import VIRTUAL_RTYPES
-from cubicweb.rqlrewrite import add_types_restriction
+from cubicweb.rqlrewrite import add_types_restriction, RQLRelationRewriter
from cubicweb.server.edition import EditedEntity
READ_ONLY_RTYPES = set(('eid', 'has_text', 'is', 'is_instance_of', 'identity'))
@@ -302,6 +302,9 @@
union.append(select)
select.clean_solutions(solutions)
add_types_restriction(self.schema, select)
+ # Rewrite computed relations
+ rewriter = RQLRelationRewriter(plan.cnx)
+ rewriter.rewrite(union, plan.args)
self.rqlhelper.annotate(union)
return self.build_select_plan(plan, union)
--- a/cubicweb/server/test/unittest_querier.py Tue Dec 12 11:17:25 2017 +0100
+++ b/cubicweb/server/test/unittest_querier.py Fri Jan 12 11:02:52 2018 +0100
@@ -1404,6 +1404,16 @@
self.assertEqual(len(rset.rows), 1)
self.assertEqual(rset.description, [('CWUser',)])
+ # computed relation tests ##################################################
+
+ def test_computed_relation_write_queries(self):
+ """Ensure we can use computed relation in WHERE clause of write queries"""
+ with self.admin_access.cnx() as cnx:
+ cnx.execute('INSERT Personne P: P nom "user", P login_user U WHERE NOT U user_login P')
+ cnx.execute('DELETE P login_user U WHERE U user_login P')
+ cnx.execute('DELETE Personne P WHERE U user_login P')
+ cnx.execute('SET U login "people" WHERE U user_login P')
+
# ZT datetime tests ########################################################
def test_tz_datetime(self):
--- a/cubicweb/web/application.py Tue Dec 12 11:17:25 2017 +0100
+++ b/cubicweb/web/application.py Fri Jan 12 11:02:52 2018 +0100
@@ -79,11 +79,11 @@
from cubicweb.web.views.authentication import Session
orig_cnx = req.cnx
- anon_cnx = anonymous_cnx(orig_cnx.session.repo)
+ anon_cnx = anonymous_cnx(orig_cnx.repo)
try:
with anon_cnx:
# web request expect a session attribute on cnx referencing the web session
- anon_cnx.session = Session(orig_cnx.session.repo, anon_cnx.user)
+ anon_cnx.session = Session(orig_cnx.repo, anon_cnx.user)
req.set_cnx(anon_cnx)
yield req
finally:
--- a/cubicweb/web/test/unittest_uicfg.py Tue Dec 12 11:17:25 2017 +0100
+++ b/cubicweb/web/test/unittest_uicfg.py Fri Jan 12 11:02:52 2018 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -15,23 +15,26 @@
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
+
import copy
import warnings
-from logilab.common.testlib import tag
-from cubicweb.devtools.testlib import CubicWebTC
+from yams.buildobjs import RelationDefinition, EntityType
+
+from cubicweb.devtools.testlib import CubicWebTC, BaseTestCase
+from cubicweb.schema import build_schema_from_namespace
from cubicweb.web import uihelper, formwidgets as fwdgs
from cubicweb.web.views import uicfg
abaa = uicfg.actionbox_appearsin_addmenu
+
class UICFGTC(CubicWebTC):
def test_default_actionbox_appearsin_addmenu_config(self):
self.assertFalse(abaa.etype_get('TrInfo', 'wf_info_for', 'object', 'CWUser'))
-
class DefinitionOrderTC(CubicWebTC):
"""This test check that when multiple definition could match a key, only
the more accurate apply"""
@@ -41,19 +44,19 @@
for rtag in (uicfg.autoform_section, uicfg.autoform_field_kwargs):
rtag._old_tagdefs = copy.deepcopy(rtag._tagdefs)
new_def = (
- (('*', 'login', '*'),
- {'formtype':'main', 'section':'hidden'}),
- (('*', 'login', '*'),
- {'formtype':'muledit', 'section':'hidden'}),
- (('CWUser', 'login', '*'),
- {'formtype':'main', 'section':'attributes'}),
- (('CWUser', 'login', '*'),
- {'formtype':'muledit', 'section':'attributes'}),
- (('CWUser', 'login', 'String'),
- {'formtype':'main', 'section':'inlined'}),
- (('CWUser', 'login', 'String'),
- {'formtype':'inlined', 'section':'attributes'}),
- )
+ (('*', 'login', '*'),
+ {'formtype': 'main', 'section': 'hidden'}),
+ (('*', 'login', '*'),
+ {'formtype': 'muledit', 'section': 'hidden'}),
+ (('CWUser', 'login', '*'),
+ {'formtype': 'main', 'section': 'attributes'}),
+ (('CWUser', 'login', '*'),
+ {'formtype': 'muledit', 'section': 'attributes'}),
+ (('CWUser', 'login', 'String'),
+ {'formtype': 'main', 'section': 'inlined'}),
+ (('CWUser', 'login', 'String'),
+ {'formtype': 'inlined', 'section': 'attributes'}),
+ )
for key, kwargs in new_def:
uicfg.autoform_section.tag_subject_of(key, **kwargs)
@@ -62,13 +65,11 @@
for rtag in (uicfg.autoform_section, uicfg.autoform_field_kwargs):
rtag._tagdefs = rtag._old_tagdefs
- @tag('uicfg')
def test_definition_order_hidden(self):
result = uicfg.autoform_section.get('CWUser', 'login', 'String', 'subject')
expected = set(['main_inlined', 'muledit_attributes', 'inlined_attributes'])
self.assertSetEqual(result, expected)
- @tag('uihelper', 'order', 'func')
def test_uihelper_set_fields_order(self):
afk_get = uicfg.autoform_field_kwargs.get
self.assertEqual(afk_get('CWUser', 'firstname', 'String', 'subject'), {})
@@ -78,7 +79,6 @@
self.assertTrue(issubclass(w[-1].category, DeprecationWarning))
self.assertEqual(afk_get('CWUser', 'firstname', 'String', 'subject'), {'order': 1})
- @tag('uicfg', 'order', 'func')
def test_uicfg_primaryview_set_fields_order(self):
pvdc = uicfg.primaryview_display_ctrl
pvdc.set_fields_order('CWUser', ('login', 'firstname', 'surname'))
@@ -86,7 +86,6 @@
self.assertEqual(pvdc.get('CWUser', 'firstname', 'String', 'subject'), {'order': 1})
self.assertEqual(pvdc.get('CWUser', 'surname', 'String', 'subject'), {'order': 2})
- @tag('uihelper', 'kwargs', 'func')
def test_uihelper_set_field_kwargs(self):
afk_get = uicfg.autoform_field_kwargs.get
self.assertEqual(afk_get('CWUser', 'firstname', 'String', 'subject'), {})
@@ -97,7 +96,6 @@
self.assertTrue(issubclass(w[-1].category, DeprecationWarning))
self.assertEqual(afk_get('CWUser', 'firstname', 'String', 'subject'), {'widget': wdg})
- @tag('uihelper', 'hidden', 'func')
def test_uihelper_hide_fields(self):
# original conf : in_group is edited in 'attributes' section everywhere
section_conf = uicfg.autoform_section.get('CWUser', 'in_group', '*', 'subject')
@@ -117,13 +115,14 @@
section_conf = uicfg.autoform_section.get('CWUser', 'in_group', '*', 'subject')
self.assertCountEqual(section_conf, ['main_hidden', 'muledit_hidden'])
- @tag('uihelper', 'hidden', 'formconfig')
def test_uihelper_formconfig(self):
afk_get = uicfg.autoform_field_kwargs.get
+
class CWUserFormConfig(uihelper.FormConfig):
etype = 'CWUser'
hidden = ('in_group',)
fields_order = ('login', 'firstname')
+
section_conf = uicfg.autoform_section.get('CWUser', 'in_group', '*', 'subject')
self.assertCountEqual(section_conf, ['main_hidden', 'muledit_attributes'])
self.assertEqual(afk_get('CWUser', 'firstname', 'String', 'subject'), {'order': 1})
@@ -148,6 +147,55 @@
self.assertTrue(obj is custom_afs)
+def _schema():
+
+ class Personne(EntityType):
+ pass
+
+ class Societe(EntityType):
+ pass
+
+ class Tag(EntityType):
+ pass
+
+ class travaille(RelationDefinition):
+ subject = 'Personne'
+ object = 'Societe'
+
+ class tags(RelationDefinition):
+ subject = 'Tag'
+ object = ('Personne', 'Societe', 'Tag')
+
+ return build_schema_from_namespace(locals().items())
+
+
+class AutoformSectionTC(BaseTestCase):
+
+ def test_derivation(self):
+ schema = _schema()
+ afs = uicfg.AutoformSectionRelationTags()
+ afs.tag_subject_of(('Personne', 'travaille', '*'), 'main', 'relations')
+ afs.tag_object_of(('*', 'travaille', 'Societe'), 'main', 'relations')
+ afs.tag_subject_of(('Tag', 'tags', '*'), 'main', 'relations')
+
+ afs2 = afs.derive(__name__, afs.__select__)
+ afs2.tag_subject_of(('Personne', 'travaille', '*'), 'main', 'attributes')
+ afs2.tag_object_of(('*', 'travaille', 'Societe'), 'main', 'attributes')
+ afs2.tag_subject_of(('Tag', 'tags', 'Societe'), 'main', 'attributes')
+
+ afs.init(schema)
+ afs2.init(schema)
+
+ self.assertEqual(afs2.etype_get('Tag', 'tags', 'subject', 'Personne'),
+ set(('main_relations', 'muledit_hidden', 'inlined_relations')))
+ self.assertEqual(afs2.etype_get('Tag', 'tags', 'subject', 'Societe'),
+ set(('main_attributes', 'muledit_hidden', 'inlined_attributes')))
+ self.assertEqual(afs2.etype_get('Personne', 'travaille', 'subject', 'Societe'),
+ set(('main_attributes', 'muledit_hidden', 'inlined_attributes')))
+ self.assertEqual(afs2.etype_get('Societe', 'travaille', 'object', 'Personne'),
+ set(('main_attributes', 'muledit_hidden', 'inlined_attributes')))
+
+
if __name__ == '__main__':
- from logilab.common.testlib import unittest_main
- unittest_main()
+ import unittest
+ unittest.main()
--- a/cubicweb/web/views/autoform.py Tue Dec 12 11:17:25 2017 +0100
+++ b/cubicweb/web/views/autoform.py Fri Jan 12 11:02:52 2018 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -118,16 +118,10 @@
.. Controlling the generic relation fields
"""
-
-from cubicweb import _
-
-from warnings import warn
-
from six.moves import range
from logilab.mtconverter import xml_escape
from logilab.common.decorators import iclassmethod, cached
-from logilab.common.deprecation import deprecated
from logilab.common.registry import NoSelectableObject
from cubicweb import neg_role, uilib
@@ -185,7 +179,7 @@
return False
def process_posted(self, form):
- pass # handled by the subform
+ pass # handled by the subform
class InlineEntityEditionFormView(f.FormViewMixIn, EntityView):
@@ -237,7 +231,7 @@
**self.cw_extra_kwargs)
if self.pform is None:
form.restore_previous_post(form.session_key())
- #assert form.parent_form
+ # assert form.parent_form
self.add_hiddens(form, entity)
return form
@@ -261,7 +255,7 @@
entity = self._entity()
rdef = entity.e_schema.rdef(self.rtype, neg_role(self.role), self.petype)
card = rdef.role_cardinality(self.role)
- if card == '1': # don't display remove link
+ if card == '1': # don't display remove link
return None
# if cardinality is 1..n (+), dont display link to remove an inlined form for the first form
# allowing to edit the relation. To detect so:
@@ -294,7 +288,7 @@
except KeyError:
self._cw.data[countkey] = 1
self.form.render(w=self.w, divid=divid, title=title, removejs=removejs,
- i18nctx=i18nctx, counter=self._cw.data[countkey] ,
+ i18nctx=i18nctx, counter=self._cw.data[countkey],
**kwargs)
def form_title(self, entity, i18nctx):
@@ -374,21 +368,21 @@
& specified_etype_implements('Any'))
_select_attrs = InlineEntityCreationFormView._select_attrs + ('card',)
- card = None # make pylint happy
- form = None # no actual form wrapped
+ card = None # make pylint happy
+ form = None # no actual form wrapped
def call(self, i18nctx, **kwargs):
self._cw.set_varmaker()
divid = "addNew%s%s%s:%s" % (self.etype, self.rtype, self.role, self.peid)
self.w(u'<div class="inlinedform" id="%s" cubicweb:limit="true">'
- % divid)
+ % divid)
js = "addInlineCreationForm('%s', '%s', '%s', '%s', '%s', '%s')" % (
self.peid, self.petype, self.etype, self.rtype, self.role, i18nctx)
if self.pform.should_hide_add_new_relation_link(self.rtype, self.card):
js = "toggleVisibility('%s'); %s" % (divid, js)
__ = self._cw.pgettext
self.w(u'<a class="addEntity" id="add%s:%slink" href="javascript: %s" >+ %s.</a>'
- % (self.rtype, self.peid, js, __(i18nctx, 'add a %s' % self.etype)))
+ % (self.rtype, self.peid, js, __(i18nctx, 'add a %s' % self.etype)))
self.w(u'</div>')
@@ -400,6 +394,7 @@
return u'%s:%s:%s' % (eid, rtype, reid)
return u'%s:%s:%s' % (reid, rtype, eid)
+
def toggleable_relation_link(eid, nodeid, label='x'):
"""return javascript snippet to delete/undelete a relation between two
entities
@@ -420,6 +415,7 @@
return ['%s:%s:%s' % (subj, rel, obj) for subj, rel, obj in pending
if eid is None or eid in (subj, obj)]
+
def get_pending_deletes(req, eid=None):
"""shortcut to access req's pending_delete entry
@@ -430,6 +426,7 @@
return ['%s:%s:%s' % (subj, rel, obj) for subj, rel, obj in pending
if eid is None or eid in (subj, obj)]
+
def parse_relations_descr(rdescr):
"""parse a string describing some relations, in the form
subjeids:rtype:objeids
@@ -443,6 +440,7 @@
for obj in objs.split('_'):
yield int(subj), rtype, int(obj)
+
def delete_relations(req, rdefs):
"""delete relations from the repository"""
# FIXME convert to using the syntax subject:relation:eids
@@ -452,6 +450,7 @@
execute(rql, {'x': subj, 'y': obj})
req.set_message(req._('relations deleted'))
+
def insert_relations(req, rdefs):
"""insert relations into the repository"""
execute = req.execute
@@ -468,10 +467,12 @@
peid=peid, petype=petype)
return self._call_view(view, i18nctx=i18nctx)
+
@ajaxfunc(output_type='json')
def validate_form(self, action, names, values):
return self.validate_form(action, names, values)
+
@ajaxfunc
def cancel_edition(self, errorurl):
"""cancelling edition from javascript
@@ -491,6 +492,7 @@
pendings.append(value)
req.session.data[key] = pendings
+
def _remove_pending(req, eidfrom, rel, eidto, kind):
key = 'pending_%s' % kind
pendings = req.session.data[key]
@@ -499,21 +501,25 @@
pendings.remove(value)
req.session.data[key] = pendings
+
@ajaxfunc(output_type='json')
def remove_pending_insert(self, args):
eidfrom, rel, eidto = args
_remove_pending(self._cw, eidfrom, rel, eidto, 'insert')
+
@ajaxfunc(output_type='json')
def add_pending_inserts(self, tripletlist):
for eidfrom, rel, eidto in tripletlist:
_add_pending(self._cw, eidfrom, rel, eidto, 'insert')
+
@ajaxfunc(output_type='json')
def remove_pending_delete(self, args):
eidfrom, rel, eidto = args
_remove_pending(self._cw, eidfrom, rel, eidto, 'delete')
+
@ajaxfunc(output_type='json')
def add_pending_delete(self, args):
eidfrom, rel, eidto = args
@@ -527,7 +533,6 @@
w = stream.append
req = form._cw
_ = req._
- __ = _
eid = form.edited_entity.eid
w(u'<table id="relatedEntities">')
for rschema, role, related in field.relations_table(form):
@@ -541,9 +546,9 @@
w(u'<li>%s<span id="span%s" class="%s">%s</span></li>'
% (viewparams[1], viewparams[0], viewparams[2], viewparams[3]))
if not form.force_display and form.maxrelitems < len(related):
- link = (u'<span>'
- '[<a href="javascript: window.location.href+=\'&__force_display=1\'">%s</a>]'
- '</span>' % _('view all'))
+ link = (u'<span>[<a '
+ 'href="javascript: window.location.href+=\'&__force_display=1\'"'
+ '>%s</a>]</span>' % _('view all'))
w(u'<li>%s</li>' % link)
w(u'</ul>')
w(u'</td>')
@@ -619,7 +624,8 @@
if rschema.has_perm(form._cw, 'delete', **haspermkwargs):
toggleable_rel_link_func = toggleable_relation_link
else:
- toggleable_rel_link_func = lambda x, y, z: u''
+ def toggleable_rel_link_func(x, y, z):
+ return u''
for row in range(rset.rowcount):
nodeid = relation_id(entity.eid, rschema, role,
rset[row][0])
@@ -641,7 +647,7 @@
for pendingid in pending_inserts:
eidfrom, rtype, eidto = pendingid.split(':')
pendingid = 'id' + pendingid
- if int(eidfrom) == entity.eid: # subject
+ if int(eidfrom) == entity.eid: # subject
label = display_name(form._cw, rtype, 'subject',
entity.cw_etype)
reid = eidto
@@ -687,13 +693,14 @@
relname, role = self._cw.form.get('relation').rsplit('_', 1)
return u"""\
<div class="%s" id="%s">
- <select id="%s" onchange="javascript: addPendingInsert(this.options[this.selectedIndex], %s, %s, '%s');">
+ <select id="%s"
+ onchange="javascript: addPendingInsert(this.options[this.selectedIndex], %s, %s, '%s');">
%s
</select>
</div>
""" % (hidden and 'hidden' or '', divid, selectid,
- xml_escape(json_dumps(entity.eid)), is_cell and 'true' or 'null', relname,
- '\n'.join(options))
+ xml_escape(json_dumps(entity.eid)), is_cell and 'true' or 'null',
+ relname, '\n'.join(options))
def _get_select_options(self, entity, rschema, role):
"""add options to search among all entities of each possible type"""
@@ -706,7 +713,7 @@
# NOTE: expect 'limit' arg on choices method of relation field
for eview, reid in field.vocabulary(form, limit=limit):
if reid is None:
- if eview: # skip blank value
+ if eview: # skip blank value
options.append('<option class="separator">-- %s --</option>'
% xml_escape(eview))
elif reid != ff.INTERNAL_FIELD_VALUE:
@@ -724,7 +731,7 @@
for eschema in targettypes:
mode = '%s:%s:%s:%s' % (role, entity.eid, rschema.type, eschema)
url = self._cw.build_url(entity.rest_path(), vid='search-associate',
- __mode=mode)
+ __mode=mode)
options.append((eschema.display_name(self._cw),
'<option value="%s">%s %s</option>' % (
xml_escape(url), _('Search for'), eschema.display_name(self._cw))))
@@ -784,7 +791,7 @@
for rtype, role in self.editable_attributes():
try:
self.field_by_name(str(rtype), role)
- continue # explicitly specified
+ continue # explicitly specified
except f.FieldNotFound:
# has to be guessed
try:
@@ -803,7 +810,7 @@
for formview in self.inlined_form_views():
field = self._inlined_form_view_field(formview)
self.fields.append(field)
- if not field.fieldset in fsio:
+ if field.fieldset not in fsio:
fsio.append(field.fieldset)
if self.formtype == 'main':
# add the generic relation field if necessary
@@ -817,7 +824,7 @@
pass
else:
self.fields.append(field)
- if not field.fieldset in fsio:
+ if field.fieldset not in fsio:
fsio.append(field.fieldset)
self.maxrelitems = self._cw.property_value('navigation.related-limit')
self.force_display = bool(self._cw.form.get('__force_display'))
@@ -949,7 +956,7 @@
relation.
"""
return (self.should_display_add_new_relation_link(
- rschema, existing, card) and
+ rschema, existing, card) and
self.check_inlined_rdef_permissions(
rschema, role, tschema, ttype))
@@ -968,7 +975,6 @@
return rdef.has_perm(self._cw, 'add', **rdefkwargs)
return rdef.may_have_permission('add', self._cw)
-
def should_hide_add_new_relation_link(self, rschema, card):
"""return true if once an inlined creation form is added, the 'add new'
link should be hidden
@@ -1009,7 +1015,7 @@
pass
-## default form ui configuration ##############################################
+# default form ui configuration ##############################################
_AFS = uicfg.autoform_section
# use primary and not generated for eid since it has to be an hidden
@@ -1049,6 +1055,7 @@
_AFFK.tag_subject_of(('TrInfo', 'wf_info_for', '*'),
{'widget': fw.HiddenInput})
+
def registration_callback(vreg):
global etype_relation_field
--- a/cubicweb/web/views/cwuser.py Tue Dec 12 11:17:25 2017 +0100
+++ b/cubicweb/web/views/cwuser.py Fri Jan 12 11:02:52 2018 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -20,7 +20,7 @@
from cubicweb import _
-from hashlib import sha1 # pylint: disable=E0611
+from hashlib import sha1 # pylint: disable=E0611
from six import text_type
from six.moves import range
@@ -28,7 +28,6 @@
from logilab.mtconverter import xml_escape
from cubicweb import tags
-from cubicweb.schema import display_name
from cubicweb.predicates import one_line_rset, is_instance, match_user_groups
from cubicweb.view import EntityView, StartupView
from cubicweb.web import action, formwidgets
@@ -39,7 +38,8 @@
_affk = uicfg.autoform_field_kwargs
_affk.tag_subject_of(('CWUser', 'in_group', 'CWGroup'),
- {'widget': formwidgets.InOutWidget})
+ {'widget': formwidgets.InOutWidget})
+
class UserPreferencesEntityAction(action.Action):
__regid__ = 'prefs'
@@ -66,7 +66,7 @@
self.w(u'''<?xml version="1.0" encoding="%s"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:rdfs="http://www.w3org/2000/01/rdf-schema#"
- xmlns:foaf="http://xmlns.com/foaf/0.1/"> '''% self._cw.encoding)
+ xmlns:foaf="http://xmlns.com/foaf/0.1/"> ''' % self._cw.encoding)
for i in range(self.cw_rset.rowcount):
self.cell_call(i, 0)
self.w(u'</rdf:RDF>\n')
@@ -121,6 +121,7 @@
'U last_login_time LL, G eid %(x)s', {'x': entity.eid})
self.wview('cwgroup.users', rset, 'null')
+
class CWGroupUsersTable(tableview.RsetTableView):
__regid__ = 'cwgroup.users'
__select__ = is_instance('CWUser')
@@ -135,9 +136,7 @@
__select__ = is_instance('CWGroup')
def entity_call(self, entity):
- self._cw.add_css(('cubicweb.schema.css','cubicweb.acl.css'))
- access_types = ('read', 'delete', 'add', 'update')
- w = self.w
+ self._cw.add_css(('cubicweb.schema.css', 'cubicweb.acl.css'))
objtype_access = {'CWEType': ('read', 'delete', 'add', 'update'),
'CWRelation': ('add', 'delete')}
rql_cwetype = 'DISTINCT Any X WHERE X %s_permission CWG, X is CWEType, ' \
@@ -170,7 +169,7 @@
# user / groups management views ###############################################
class ManageUsersAction(actions.ManagersAction):
- __regid__ = 'cwuser' # see rewrite rule /cwuser
+ __regid__ = 'cwuser' # see rewrite rule /cwuser
title = _('users and groups')
category = 'manage'
@@ -179,7 +178,7 @@
__regid__ = 'cw.users-and-groups-management'
__select__ = StartupView.__select__ & match_user_groups('managers')
title = _('Users and groups management')
- tabs = [_('cw.users-management'), _('cw.groups-management'),]
+ tabs = [_('cw.users-management'), _('cw.groups-management')]
default_tab = 'cw.users-management'
def call(self, **kwargs):
@@ -191,7 +190,7 @@
class CWUserManagementView(StartupView):
__regid__ = 'cw.users-management'
__select__ = StartupView.__select__ & match_user_groups('managers')
- cache_max_age = 0 # disable caching
+ cache_max_age = 0 # disable caching
# XXX one could wish to display for instance only user's firstname/surname
# for non managers but filtering out NULL caused crash with an ldapuser
# source. The ldapuser source has been dropped and this code can be updated.
@@ -217,24 +216,24 @@
column_renderers = {
'user': tableview.EntityTableColRenderer(
- renderfunc=lambda w,x: w(tags.a(x.login, href=x.absolute_url())),
+ renderfunc=lambda w, x: w(tags.a(x.login, href=x.absolute_url())),
sortfunc=lambda x: x.login),
'in_state': tableview.EntityTableColRenderer(
- renderfunc=lambda w,x: w(x.cw_adapt_to('IWorkflowable').printable_state),
+ renderfunc=lambda w, x: w(x.cw_adapt_to('IWorkflowable').printable_state),
sortfunc=lambda x: x.cw_adapt_to('IWorkflowable').printable_state),
'in_group': tableview.EntityTableColRenderer(
- renderfunc=lambda w,x: x.view('reledit', rtype='in_group', role='subject', w=w)),
+ renderfunc=lambda w, x: x.view('reledit', rtype='in_group', role='subject', w=w)),
'primary_email': tableview.RelatedEntityColRenderer(
- getrelated=lambda x:x.primary_email and x.primary_email[0] or None),
+ getrelated=lambda x: x.primary_email and x.primary_email[0] or None),
'cw_source': tableview.RelatedEntityColRenderer(
getrelated=lambda x: x.cw_source[0]),
- }
+ }
class CWGroupsManagementView(StartupView):
__regid__ = 'cw.groups-management'
__select__ = StartupView.__select__ & match_user_groups('managers')
- cache_max_age = 0 # disable caching
+ cache_max_age = 0 # disable caching
rql = ('Any G,GN ORDERBY GN WHERE G is CWGroup, G name GN, NOT G name "owners"')
def call(self, **kwargs):
@@ -253,6 +252,6 @@
'group': tableview.MainEntityColRenderer(),
'nb_users': tableview.EntityTableColRenderer(
header=_('num. users'),
- renderfunc=lambda w,x: w(text_type(x.num_users())),
+ renderfunc=lambda w, x: w(text_type(x.num_users())),
sortfunc=lambda x: x.num_users()),
- }
+ }
--- a/cubicweb/web/views/formrenderers.py Tue Dec 12 11:17:25 2017 +0100
+++ b/cubicweb/web/views/formrenderers.py Fri Jan 12 11:02:52 2018 +0100
@@ -172,7 +172,7 @@
else:
templstr = u' %s\n'
for field, err in errors:
- if field is None:
+ if not field:
errormsg += templstr % err
else:
errormsg += templstr % '%s: %s' % (req._(field), err)
--- a/cubicweb/web/views/uicfg.py Tue Dec 12 11:17:25 2017 +0100
+++ b/cubicweb/web/views/uicfg.py Fri Jan 12 11:02:52 2018 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -54,12 +54,14 @@
uicfg.actionbox_appearsin_addmenu.tag_object_of(('*', 'entry_of', 'Blog'), True)
"""
+from itertools import repeat
+
from six import string_types
from cubicweb import neg_role
from cubicweb.rtags import (RelationTags, RelationTagsBool, RelationTagsSet,
RelationTagsDict, NoTargetRelationTagsDict,
- _ensure_str_key)
+ rtags_chain, _ensure_str_key)
from cubicweb.schema import META_RTYPES, INTERNAL_TYPES, WORKFLOW_TYPES
@@ -203,7 +205,16 @@
class AutoformSectionRelationTags(RelationTagsSet):
- """autoform relations'section"""
+ """autoform relations'section
+
+ Notice that unlike other rtags where wildcard handling is done when
+ retrieving some value, all values are expanded here during initialization
+ step.
+
+ For derived rtags, values specified for the 'main' form type are propagated
+ to the 'inlined' form type if unspecified. Others are fetched back from the
+ parent.
+ """
__regid__ = 'autoform_section'
_allowed_form_types = ('main', 'inlined', 'muledit')
@@ -215,7 +226,34 @@
def init(self, schema, check=True):
super(AutoformSectionRelationTags, self).init(schema, check)
- self.apply(schema, self._initfunc_step2)
+ if self._parent is None:
+ self.apply(schema, self._initfunc_step2)
+ else:
+ # we still need to expand wildcard in defined keys
+ for key in list(self._tagdefs):
+ stype, rtype, otype, role = key
+ rschema = schema.rschema(rtype)
+ if stype == '*' and stype == '*':
+ concrete_rdefs = rschema.rdefs.keys()
+ elif stype == '*':
+ concrete_rdefs = zip(rschema.subjects(otype), repeat(otype))
+ elif otype == '*':
+ concrete_rdefs = zip(repeat(stype), rschema.objects(stype))
+ else:
+ concrete_rdefs = [(stype, otype)]
+ for sschema, oschema in concrete_rdefs:
+ self._init(sschema, rschema, oschema, role)
+ # also, we have to copy values from 'main' to 'inlined' and
+ # for other undefined sections from the parent's rtag
+ formsections = self.get(sschema, rschema, oschema, role)
+ sectdict = _formsections_as_dict(formsections)
+ parent_formsections = self._parent.get(sschema, rschema, oschema, role)
+ parent_sectdict = _formsections_as_dict(parent_formsections)
+ for formtype, section in parent_sectdict.items():
+ if formtype not in sectdict:
+ if formtype == 'inlined':
+ section = sectdict.get('main', section)
+ formsections.add('%s_%s' % (formtype, section))
def _init(self, sschema, rschema, oschema, role):
formsections = self.init_get(sschema, rschema, oschema, role)
@@ -300,7 +338,12 @@
def get(self, *key):
# overriden to avoid recomputing done in parent classes
- return self._tagdefs.get(key, ())
+ for rtag in rtags_chain(self):
+ try:
+ return rtag._tagdefs[key]
+ except KeyError:
+ continue
+ return ()
def relations_by_section(self, entity, formtype, section, permission,
strict=False):
--- a/cubicweb/web/views/workflow.py Tue Dec 12 11:17:25 2017 +0100
+++ b/cubicweb/web/views/workflow.py Fri Jan 12 11:02:52 2018 +0100
@@ -1,4 +1,4 @@
-# copyright 2003-2014 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# copyright 2003 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
@@ -25,24 +25,21 @@
from cubicweb import _
import os
-from warnings import warn
from six import add_metaclass
from logilab.mtconverter import xml_escape
-from logilab.common.graph import escape
from logilab.common.deprecation import class_deprecated
from cubicweb import Unauthorized
-from cubicweb.predicates import (has_related_entities, one_line_rset,
- relation_possible, match_form_params,
- score_entity, is_instance, adaptable)
+from cubicweb.predicates import (one_line_rset,
+ relation_possible, match_form_params,
+ score_entity, is_instance, adaptable)
from cubicweb.view import EntityView
-from cubicweb.schema import display_name
-from cubicweb.web import stdmsgs, action, component, form, action
-from cubicweb.web import formfields as ff, formwidgets as fwdgs
+from cubicweb.web import stdmsgs, action, component, form
+from cubicweb.web import formwidgets as fwdgs
from cubicweb.web.views import TmpFileViewMixin
-from cubicweb.web.views import uicfg, forms, primary, ibreadcrumbs
+from cubicweb.web.views import uicfg, forms, ibreadcrumbs
from cubicweb.web.views.tabs import TabbedPrimaryView, PrimaryTab
from cubicweb.web.views.dotgraphview import DotGraphView, DotPropsHandler
@@ -77,6 +74,7 @@
_afs = uicfg.autoform_section
_affk = uicfg.autoform_field_kwargs
+
# IWorkflowable views #########################################################
class ChangeStateForm(forms.CompositeEntityForm):
@@ -84,7 +82,7 @@
# session_key() implementation)
__regid__ = domid = 'changestate'
- form_renderer_id = 'base' # don't want EntityFormRenderer
+ form_renderer_id = 'base' # don't want EntityFormRenderer
form_buttons = [fwdgs.SubmitButton(),
fwdgs.Button(stdmsgs.BUTTON_CANCEL,
{'class': fwdgs.Button.css_class + ' cwjs-edition-cancel'})]
@@ -132,8 +130,8 @@
class WFHistoryView(EntityView):
__regid__ = 'wfhistory'
- __select__ = relation_possible('wf_info_for', role='object') & \
- score_entity(lambda x: x.cw_adapt_to('IWorkflowable').workflow_history)
+ __select__ = (relation_possible('wf_info_for', role='object')
+ & score_entity(lambda x: x.cw_adapt_to('IWorkflowable').workflow_history))
title = _('Workflow history')
@@ -180,6 +178,7 @@
"""display incontext view for an entity as well as its current state"""
__regid__ = 'incontext-state'
__select__ = adaptable('IWorkflowable')
+
def entity_call(self, entity):
iwf = entity.cw_adapt_to('IWorkflowable')
self.w(u'%s [%s]' % (entity.view('incontext'), iwf.printable_state))
@@ -238,9 +237,10 @@
_abaa.tag_object_of(('Transition', 'transition_of', 'Workflow'), True)
_abaa.tag_object_of(('WorkflowTransition', 'transition_of', 'Workflow'), True)
+
class WorkflowPrimaryView(TabbedPrimaryView):
__select__ = is_instance('Workflow')
- tabs = [ _('wf_tab_info'), _('wfgraph'),]
+ tabs = [_('wf_tab_info'), _('wfgraph')]
default_tab = 'wf_tab_info'
@@ -253,6 +253,7 @@
self.w(xml_escape(self._cw.view('textincontext', self.cw_rset,
row=row, col=col)))
+
class WorkflowTabTextView(PrimaryTab):
__regid__ = 'wf_tab_info'
__select__ = PrimaryTab.__select__ & one_line_rset() & is_instance('Workflow')
@@ -262,7 +263,7 @@
self.w(u'<div>%s</div>' % (entity.printable_value('description')))
self.w(u'<span>%s%s</span>' % (_("workflow_of").capitalize(), _(" :")))
html = []
- for e in entity.workflow_of:
+ for e in entity.workflow_of:
view = e.view('outofcontext')
if entity.eid == e.default_workflow[0].eid:
view += u' <span>[%s]</span>' % _('default_workflow')
@@ -273,10 +274,9 @@
'Any T,T,DS,T,TT ORDERBY TN WHERE T transition_of WF, WF eid %(x)s,'
'T type TT, T name TN, T destination_state DS?', {'x': entity.eid})
self.wview('table', rset, 'null',
- cellvids={ 1: 'trfromstates', 2: 'outofcontext', 3:'trsecurity',},
- headers = (_('Transition'), _('from_state'),
- _('to_state'), _('permissions'), _('type') ),
- )
+ cellvids={1: 'trfromstates', 2: 'outofcontext', 3: 'trsecurity'},
+ headers=(_('Transition'), _('from_state'),
+ _('to_state'), _('permissions'), _('type')))
class TransitionSecurityTextView(EntityView):
@@ -293,9 +293,9 @@
in entity.require_group))))
if entity.condition:
self.w(u'<div>%s%s %s</div>' %
- ( _('conditions'), _(" :"),
- u'<br/>'.join((e.dc_title() for e
- in entity.condition))))
+ (_('conditions'), _(" :"),
+ u'<br/>'.join((e.dc_title() for e in entity.condition))))
+
class TransitionAllowedTextView(EntityView):
__regid__ = 'trfromstates'
@@ -317,11 +317,13 @@
for e in getattr(wf, 'reverse_%s' % wfrelation)
if rschema.has_perm(req, 'add', **{param: e.eid}))
+
# TrInfo
_afs.tag_subject_of(('TrInfo', 'to_state', '*'), 'main', 'hidden')
_afs.tag_subject_of(('TrInfo', 'from_state', '*'), 'main', 'hidden')
_afs.tag_attribute(('TrInfo', 'tr_count'), 'main', 'hidden')
+
# BaseTransition
# XXX * allowed_transition BaseTransition
# XXX BaseTransition destination_state *
@@ -337,12 +339,14 @@
wfeid = eids[0]
return _wf_items_for_relation(form._cw, wfeid, 'state_of', field)
+
_afs.tag_subject_of(('*', 'destination_state', '*'), 'main', 'attributes')
_affk.tag_subject_of(('*', 'destination_state', '*'),
{'choices': transition_states_vocabulary})
_afs.tag_object_of(('*', 'allowed_transition', '*'), 'main', 'attributes')
_affk.tag_object_of(('*', 'allowed_transition', '*'),
- {'choices': transition_states_vocabulary})
+ {'choices': transition_states_vocabulary})
+
# State
@@ -350,13 +354,14 @@
entity = form.edited_entity
if entity.has_eid():
wfeid = entity.state_of[0].eid
- else :
+ else:
eids = form.linked_to.get(('state_of', 'subject'))
if not eids:
return []
wfeid = eids[0]
return _wf_items_for_relation(form._cw, wfeid, 'transition_of', field)
+
_afs.tag_subject_of(('State', 'allowed_transition', '*'), 'main', 'attributes')
_affk.tag_subject_of(('State', 'allowed_transition', '*'),
{'choices': state_transitions_vocabulary})
@@ -366,22 +371,29 @@
class WorkflowIBreadCrumbsAdapter(ibreadcrumbs.IBreadCrumbsAdapter):
__select__ = is_instance('Workflow')
+
# XXX what if workflow of multiple types?
def parent_entity(self):
return self.entity.workflow_of and self.entity.workflow_of[0] or None
+
class WorkflowItemIBreadCrumbsAdapter(ibreadcrumbs.IBreadCrumbsAdapter):
__select__ = is_instance('BaseTransition', 'State')
+
def parent_entity(self):
return self.entity.workflow
+
class TransitionItemIBreadCrumbsAdapter(ibreadcrumbs.IBreadCrumbsAdapter):
__select__ = is_instance('SubWorkflowExitPoint')
+
def parent_entity(self):
return self.entity.reverse_subworkflow_exit[0]
+
class TrInfoIBreadCrumbsAdapter(ibreadcrumbs.IBreadCrumbsAdapter):
__select__ = is_instance('TrInfo')
+
def parent_entity(self):
return self.entity.for_entity
@@ -422,6 +434,7 @@
for outgoingstate in transition.potential_destinations():
yield transition.eid, outgoingstate.eid, transition
+
class WorkflowGraphView(DotGraphView):
__regid__ = 'wfgraph'
__select__ = EntityView.__select__ & one_line_rset() & is_instance('Workflow')
--- a/debian/changelog Tue Dec 12 11:17:25 2017 +0100
+++ b/debian/changelog Fri Jan 12 11:02:52 2018 +0100
@@ -1,3 +1,15 @@
+cubicweb (3.25.4-1) unstable; urgency=medium
+
+ * New upstream release.
+
+ -- Denis Laxalde <denis.laxalde@logilab.fr> Fri, 12 Jan 2018 10:47:38 +0100
+
+cubicweb (3.25.3-1) unstable; urgency=medium
+
+ * New upstream release
+
+ -- Philippe Pepiot <philippe.pepiot@logilab.fr> Tue, 17 Oct 2017 11:58:05 +0200
+
cubicweb (3.25.2-1) unstable; urgency=medium
* New upstream release
--- a/flake8-ok-files.txt Tue Dec 12 11:17:25 2017 +0100
+++ b/flake8-ok-files.txt Fri Jan 12 11:02:52 2018 +0100
@@ -67,6 +67,7 @@
cubicweb/server/test/unittest_ssplanner.py
cubicweb/server/test/unittest_rqlannotation.py
cubicweb/server/test/unittest_utils.py
+cubicweb/schema.py
cubicweb/sobjects/notification.py
cubicweb/sobjects/services.py
cubicweb/sobjects/test/unittest_notification.py
@@ -109,14 +110,17 @@
cubicweb/web/test/data/entities.py
cubicweb/web/test/unittest_application.py
cubicweb/web/test/unittest_http_headers.py
+cubicweb/web/test/unittest_uicfg.py
cubicweb/web/test/unittest_views_basetemplates.py
cubicweb/web/test/unittest_views_cwsources.py
cubicweb/web/test/unittest_views_json.py
cubicweb/web/views/authentication.py
+cubicweb/web/views/cwuser.py
cubicweb/web/views/editcontroller.py
cubicweb/web/views/json.py
cubicweb/web/views/searchrestriction.py
cubicweb/web/views/staticcontrollers.py
+cubicweb/web/views/workflow.py
cubicweb/web/views/uicfg.py
cubicweb/xy.py
cubicweb/pyramid/auth.py