# HG changeset patch # User Denis Laxalde # Date 1515751372 -3600 # Node ID 6350e0a482d515764d9b318e855c01fede0ddc85 # Parent cd760c411242c6c01d8ed2376d1cf0b8275e6af9# Parent 4a619b42263d09ca37dd81bd2304a851bc91f961 Merge with 3.25 diff -r cd760c411242 -r 6350e0a482d5 .hgtags --- 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 diff -r cd760c411242 -r 6350e0a482d5 cubicweb.spec --- 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 diff -r cd760c411242 -r 6350e0a482d5 cubicweb/__pkginfo__.py diff -r cd760c411242 -r 6350e0a482d5 cubicweb/entities/__init__.py --- 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): diff -r cd760c411242 -r 6350e0a482d5 cubicweb/pyramid/pyramidctl.py --- 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: diff -r cd760c411242 -r 6350e0a482d5 cubicweb/rtags.py --- 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): diff -r cd760c411242 -r 6350e0a482d5 cubicweb/schema.py --- 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 diff -r cd760c411242 -r 6350e0a482d5 cubicweb/server/ssplanner.py --- 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) diff -r cd760c411242 -r 6350e0a482d5 cubicweb/server/test/unittest_querier.py --- 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): diff -r cd760c411242 -r 6350e0a482d5 cubicweb/web/application.py --- 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: diff -r cd760c411242 -r 6350e0a482d5 cubicweb/web/test/unittest_uicfg.py --- 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 . + 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() diff -r cd760c411242 -r 6350e0a482d5 cubicweb/web/views/autoform.py --- 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'
' - % 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'+ %s.' - % (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'
') @@ -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'') for rschema, role, related in field.relations_table(form): @@ -541,9 +546,9 @@ w(u'
  • %s%s
  • ' % (viewparams[1], viewparams[0], viewparams[2], viewparams[3])) if not form.force_display and form.maxrelitems < len(related): - link = (u'' - '[%s]' - '' % _('view all')) + link = (u'[%s]' % _('view all')) w(u'
  • %s
  • ' % link) w(u'') w(u'') @@ -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"""\
    - %s
    """ % (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('' % 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), '' % ( 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 diff -r cd760c411242 -r 6350e0a482d5 cubicweb/web/views/cwuser.py --- 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''' '''% 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'\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()), - } + } diff -r cd760c411242 -r 6350e0a482d5 cubicweb/web/views/formrenderers.py --- 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) diff -r cd760c411242 -r 6350e0a482d5 cubicweb/web/views/uicfg.py --- 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): diff -r cd760c411242 -r 6350e0a482d5 cubicweb/web/views/workflow.py --- 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'
    %s
    ' % (entity.printable_value('description'))) self.w(u'%s%s' % (_("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' [%s]' % _('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'
    %s%s %s
    ' % - ( _('conditions'), _(" :"), - u'
    '.join((e.dc_title() for e - in entity.condition)))) + (_('conditions'), _(" :"), + u'
    '.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') diff -r cd760c411242 -r 6350e0a482d5 debian/changelog --- 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 Fri, 12 Jan 2018 10:47:38 +0100 + +cubicweb (3.25.3-1) unstable; urgency=medium + + * New upstream release + + -- Philippe Pepiot Tue, 17 Oct 2017 11:58:05 +0200 + cubicweb (3.25.2-1) unstable; urgency=medium * New upstream release diff -r cd760c411242 -r 6350e0a482d5 flake8-ok-files.txt --- 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 diff -r cd760c411242 -r 6350e0a482d5 tox.ini