Merge with 3.25
authorDenis Laxalde <denis.laxalde@logilab.fr>
Fri, 12 Jan 2018 11:02:52 +0100
changeset 12248 6350e0a482d5
parent 12245 cd760c411242 (current diff)
parent 12247 4a619b42263d (diff)
child 12249 676c166bbd74
Merge with 3.25
cubicweb/__pkginfo__.py
cubicweb/server/ssplanner.py
cubicweb/server/test/unittest_querier.py
flake8-ok-files.txt
tox.ini
--- 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+=\'&amp;__force_display=1\'">%s</a>]'
-                            '</span>' % _('view all'))
+                    link = (u'<span>[<a '
+                            'href="javascript: window.location.href+=\'&amp;__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'&#160;%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