diff -r 058bb3dc685f -r 0b59724cb3f2 web/views/uicfg.py --- a/web/views/uicfg.py Mon Jan 04 18:40:30 2016 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,693 +0,0 @@ -# copyright 2003-2013 LOGILAB S.A. (Paris, FRANCE), all rights reserved. -# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr -# -# This file is part of CubicWeb. -# -# CubicWeb is free software: you can redistribute it and/or modify it under the -# terms of the GNU Lesser General Public License as published by the Free -# Software Foundation, either version 2.1 of the License, or (at your option) -# any later version. -# -# CubicWeb is distributed in the hope that it will be useful, but WITHOUT -# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more -# details. -# -# You should have received a copy of the GNU Lesser General Public License along -# with CubicWeb. If not, see . -"""This module (``cubicweb.web.views.uicfg``) regroups a set of structures that may be -used to configure various options of the generated web interface. - -To configure the interface generation, we use ``RelationTag`` objects. - -Index view configuration -```````````````````````` -:indexview_etype_section: - entity type category in the index/manage page. May be one of: - - * ``application`` - * ``system`` - * ``schema`` - * ``subobject`` (not displayed by default) - - By default only entities on the ``application`` category are shown. - -.. sourcecode:: python - - from cubicweb.web.views import uicfg - # force hiding - uicfg.indexview_etype_section['HideMe'] = 'subobject' - # force display - uicfg.indexview_etype_section['ShowMe'] = 'application' - - -Actions box configuration -````````````````````````` -:actionbox_appearsin_addmenu: - simple boolean relation tags used to control the "add entity" submenu. - Relations whose rtag is True will appears, other won't. - -.. sourcecode:: python - - # Adds all subjects of the entry_of relation in the add menu of the ``Blog`` - # primary view - uicfg.actionbox_appearsin_addmenu.tag_object_of(('*', 'entry_of', 'Blog'), True) -""" -__docformat__ = "restructuredtext en" - -from warnings import warn - -from six import string_types - -from cubicweb import neg_role -from cubicweb.rtags import (RelationTags, RelationTagsBool, RelationTagsSet, - RelationTagsDict, NoTargetRelationTagsDict, - _ensure_str_key) -from cubicweb.schema import META_RTYPES, INTERNAL_TYPES, WORKFLOW_TYPES - - -# primary view configuration ################################################## - -class PrimaryViewSectionRelationTags(RelationTags): - """primary view section configuration""" - __regid__ = 'primaryview_section' - - _allowed_values = frozenset(('attributes', 'relations', - 'sideboxes', 'hidden')) - - def _init(self, sschema, rschema, oschema, role): - if self.get(sschema, rschema, oschema, role) is None: - rdef = rschema.rdef(sschema, oschema) - if rschema.final: - if rschema.meta or sschema.is_metadata(rschema) \ - or oschema.type in ('Password', 'Bytes'): - section = 'hidden' - else: - section = 'attributes' - else: - if rdef.role_cardinality(role) in '1+': - section = 'attributes' - elif rdef.composite == neg_role(role): - section = 'relations' - else: - section = 'sideboxes' - self.tag_relation((sschema, rschema, oschema, role), section) - -primaryview_section = PrimaryViewSectionRelationTags() - - -class DisplayCtrlRelationTags(NoTargetRelationTagsDict): - """primary view display controller configuration""" - __regid__ = 'primaryview_display_ctrl' - - def __init__(self, *args, **kwargs): - super(DisplayCtrlRelationTags, self).__init__(*args, **kwargs) - self.counter = 0 - - def _init(self, sschema, rschema, oschema, role): - if role == 'subject': - oschema = '*' - else: - sschema = '*' - self.counter += 1 - self.setdefault((sschema, rschema, oschema, role), - 'order', - self.counter) - - def set_fields_order(self, etype, relations): - """specify the field order in `etype` primary view. - - :param etype: the entity type as a string - :param attrs: the ordered list of attribute names (or relations) - - `attrs` can be strings or 2-tuples (relname, role_of_etype_in_the_rel) - - Unspecified fields will be displayed after specified ones, their - order being consistent with the schema definition. - - Examples: - - .. sourcecode:: python - - from cubicweb.web.views.uicfg import primaryview_display_ctrl as pvdc - pvdc.set_fields_order('CWUser', ('firstname', ('in_group', 'subject'), - 'surname', 'login')) - - """ - for index, relation in enumerate(relations): - if not isinstance(relation, tuple): - relation = (relation, 'subject') - rtype, role = relation - if role == 'subject': - self.tag_subject_of((etype, rtype, '*'), {'order': index}) - else: - self.tag_object_of((etype, rtype, '*'), {'order': index}) - - -primaryview_display_ctrl = DisplayCtrlRelationTags() - - -# index view configuration #################################################### -# entity type section in the index/manage page. May be one of -# * 'application' -# * 'system' -# * 'schema' -# * 'hidden' -# * 'subobject' (not displayed by default) - -class InitializableDict(dict): # XXX not a rtag. Turn into an appobject? - def __init__(self, *args, **kwargs): - super(InitializableDict, self).__init__(*args, **kwargs) - self.__defaults = dict(self) - - def init(self, schema, check=True): - self.update(self.__defaults) - for eschema in schema.entities(): - if eschema.final: - continue - if eschema.schema_entity(): - self.setdefault(eschema, 'schema') - elif eschema in INTERNAL_TYPES or eschema in WORKFLOW_TYPES: - self.setdefault(eschema, 'system') - elif eschema.is_subobject(strict=True): - self.setdefault(eschema, 'subobject') - else: - self.setdefault(eschema, 'application') - -indexview_etype_section = InitializableDict( - EmailAddress='subobject', - Bookmark='system', - # entity types in the 'system' table by default (managers only) - CWUser='system', CWGroup='system', - ) - - -# autoform.AutomaticEntityForm configuration ################################## - -def _formsections_as_dict(formsections): - result = {} - for formsection in formsections: - formtype, section = formsection.split('_', 1) - result[formtype] = section - return result - -def _card_and_comp(sschema, rschema, oschema, role): - rdef = rschema.rdef(sschema, oschema) - if role == 'subject': - card = rdef.cardinality[0] - composed = not rschema.final and rdef.composite == 'object' - else: - card = rdef.cardinality[1] - composed = not rschema.final and rdef.composite == 'subject' - return card, composed - -class AutoformSectionRelationTags(RelationTagsSet): - """autoform relations'section""" - __regid__ = 'autoform_section' - - _allowed_form_types = ('main', 'inlined', 'muledit') - _allowed_values = {'main': ('attributes', 'inlined', 'relations', - 'metadata', 'hidden'), - 'inlined': ('attributes', 'inlined', 'hidden'), - 'muledit': ('attributes', 'hidden'), - } - - def init(self, schema, check=True): - super(AutoformSectionRelationTags, self).init(schema, check) - self.apply(schema, self._initfunc_step2) - - def _init(self, sschema, rschema, oschema, role): - formsections = self.init_get(sschema, rschema, oschema, role) - if formsections is None: - formsections = self.tag_container_cls() - if not any(tag.startswith('inlined') for tag in formsections): - if not rschema.final: - negsects = self.init_get(sschema, rschema, oschema, neg_role(role)) - if 'main_inlined' in negsects: - formsections.add('inlined_hidden') - key = _ensure_str_key( (sschema, rschema, oschema, role) ) - self._tagdefs[key] = formsections - - def _initfunc_step2(self, sschema, rschema, oschema, role): - formsections = self.get(sschema, rschema, oschema, role) - sectdict = _formsections_as_dict(formsections) - if rschema in META_RTYPES: - sectdict.setdefault('main', 'hidden') - sectdict.setdefault('muledit', 'hidden') - sectdict.setdefault('inlined', 'hidden') - elif role == 'subject' and rschema in sschema.meta_attributes(): - # meta attribute, usually embeded by the described attribute's field - # (eg RichTextField, FileField...) - sectdict.setdefault('main', 'hidden') - sectdict.setdefault('muledit', 'hidden') - sectdict.setdefault('inlined', 'hidden') - # ensure we have a tag for each form type - if not 'main' in sectdict: - if not rschema.final and ( - sectdict.get('inlined') == 'attributes' or - 'inlined_attributes' in self.init_get(sschema, rschema, oschema, - neg_role(role))): - sectdict['main'] = 'hidden' - elif sschema.is_metadata(rschema): - sectdict['main'] = 'metadata' - else: - card, composed = _card_and_comp(sschema, rschema, oschema, role) - if card in '1+': - sectdict['main'] = 'attributes' - if not 'muledit' in sectdict: - sectdict['muledit'] = 'attributes' - elif rschema.final: - sectdict['main'] = 'attributes' - else: - sectdict['main'] = 'relations' - if not 'muledit' in sectdict: - sectdict['muledit'] = 'hidden' - if sectdict['main'] == 'attributes': - card, composed = _card_and_comp(sschema, rschema, oschema, role) - if card in '1+' and not composed: - sectdict['muledit'] = 'attributes' - if not 'inlined' in sectdict: - sectdict['inlined'] = sectdict['main'] - # recompute formsections and set it to avoid recomputing - for formtype, section in sectdict.items(): - formsections.add('%s_%s' % (formtype, section)) - - def tag_relation(self, key, formtype, section): - if isinstance(formtype, tuple): - for ftype in formtype: - self.tag_relation(key, ftype, section) - return - assert formtype in self._allowed_form_types, \ - 'formtype should be in (%s), not %s' % ( - ','.join(self._allowed_form_types), formtype) - assert section in self._allowed_values[formtype], \ - 'section for %s should be in (%s), not %s' % ( - formtype, ','.join(self._allowed_values[formtype]), section) - rtags = self._tagdefs.setdefault(_ensure_str_key(key), - self.tag_container_cls()) - # remove previous section for this form type if any - if rtags: - for tag in rtags.copy(): - if tag.startswith(formtype): - rtags.remove(tag) - rtags.add('%s_%s' % (formtype, section)) - return rtags - - def init_get(self, stype, rtype, otype, tagged): - key = (stype, rtype, otype, tagged) - rtags = {} - for key in self._get_keys(stype, rtype, otype, tagged): - tags = self._tagdefs.get(key, ()) - for tag in tags: - assert '_' in tag, (tag, tags) - section, value = tag.split('_', 1) - rtags[section] = value - cls = self.tag_container_cls - rtags = cls('_'.join([section,value]) - for section,value in rtags.items()) - return rtags - - def get(self, *key): - # overriden to avoid recomputing done in parent classes - return self._tagdefs.get(key, ()) - - def relations_by_section(self, entity, formtype, section, permission, - strict=False): - """return a list of (relation schema, target schemas, role) for the - given entity matching categories and permission. - - `strict`: - bool telling if having local role is enough (strict = False) or not - """ - tag = '%s_%s' % (formtype, section) - eschema = entity.e_schema - cw = entity._cw - permsoverrides = cw.vreg['uicfg'].select('autoform_permissions_overrides', cw, entity=entity) - if entity.has_eid(): - eid = entity.eid - else: - eid = None - strict = False - if permission == 'update': - assert section in ('attributes', 'metadata', 'hidden') - relpermission = 'add' - else: - assert section not in ('metadata', 'hidden') - relpermission = permission - for rschema, targetschemas, role in eschema.relation_definitions(True): - _targetschemas = [] - for tschema in targetschemas: - # check section's tag first, potentially lower cost than - # checking permission which may imply rql queries - if not tag in self.etype_get(eschema, rschema, role, tschema): - continue - rdef = rschema.role_rdef(eschema, tschema, role) - if rschema.final: - if not rdef.has_perm(cw, permission, eid=eid, - creating=eid is None): - continue - elif strict or not rdef.has_local_role(relpermission): - if role == 'subject': - if not rdef.has_perm(cw, relpermission, fromeid=eid): - continue - elif role == 'object': - if not rdef.has_perm(cw, relpermission, toeid=eid): - continue - _targetschemas.append(tschema) - if not _targetschemas: - continue - targetschemas = _targetschemas - rdef = eschema.rdef(rschema, role=role, targettype=targetschemas[0]) - # XXX tag allowing to hijack the permission machinery when - # permission is not verifiable until the entity is actually - # created... - if eid is None and '%s_on_new' % permission in permsoverrides.etype_get(eschema, rschema, role): - yield (rschema, targetschemas, role) - continue - if not rschema.final and role == 'subject': - # on relation with cardinality 1 or ?, we need delete perm as well - # if the relation is already set - if (relpermission == 'add' - and rdef.role_cardinality(role) in '1?' - and eid and entity.related(rschema.type, role) - and not rdef.has_perm(cw, 'delete', fromeid=eid, - toeid=entity.related(rschema.type, role)[0][0])): - continue - elif role == 'object': - # on relation with cardinality 1 or ?, we need delete perm as well - # if the relation is already set - if (relpermission == 'add' - and rdef.role_cardinality(role) in '1?' - and eid and entity.related(rschema.type, role) - and not rdef.has_perm(cw, 'delete', toeid=eid, - fromeid=entity.related(rschema.type, role)[0][0])): - continue - yield (rschema, targetschemas, role) - - def hide_field(self, etype, attr, desttype='*', formtype='main'): - """hide `attr` in `etype` forms. - - :param etype: the entity type as a string - :param attr: the name of the attribute or relation to hide - :param formtype: which form will be affected ('main', 'inlined', etc.), - *main* by default. - - `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_rel) - - Examples: - - .. sourcecode:: python - - from cubicweb.web.views.uicfg import autoform_section as afs - afs.hide_field('CWUser', 'login') - afs.hide_field('*', 'name') - afs.hide_field('CWUser', 'use_email', formtype='inlined') - - """ - self._tag_etype_attr(etype, attr, desttype, - formtype=formtype, section='hidden') - - def hide_fields(self, etype, attrs, formtype='main'): - """simple for-loop wrapper around :func:`hide_field`. - - :param etype: the entity type as a string - :param attrs: the ordered list of attribute names (or relations) - :param formtype: which form will be affected ('main', 'inlined', etc.), - *main* by default. - - `attrs` can be strings or 2-tuples (relname, role_of_etype_in_the_rel) - - Examples: - - .. sourcecode:: python - - from cubicweb.web.views.uicfg import autoform_section as afs - afs.hide_fields('CWUser', ('login', ('use_email', 'subject')), - formtype='inlined') - """ - for attr in attrs: - self.hide_field(etype, attr, formtype=formtype) - - def edit_inline(self, etype, attr, desttype='*', formtype=('main', 'inlined')): - """edit `attr` with and inlined form. - - :param etype: the entity type as a string - :param attr: the name of the attribute or relation - :param desttype: the destination type(s) concerned, default is everything - :param formtype: which form will be affected ('main', 'inlined', etc.), - *main* and *inlined* by default. - - `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation) - - Examples: - - .. sourcecode:: python - - from cubicweb.web.views.uicfg import autoform_section as afs - - afs.edit_inline('*', 'use_email') - """ - self._tag_etype_attr(etype, attr, desttype, formtype=formtype, - section='inlined') - - def edit_as_attr(self, etype, attr, desttype='*', formtype=('main', 'muledit')): - """make `attr` appear in the *attributes* section of `etype` form. - - :param etype: the entity type as a string - :param attr: the name of the attribute or relation - :param desttype: the destination type(s) concerned, default is everything - :param formtype: which form will be affected ('main', 'inlined', etc.), - *main* and *muledit* by default. - - `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation) - - Examples: - - .. sourcecode:: python - - from cubicweb.web.views.uicfg import autoform_section as afs - - afs.edit_as_attr('CWUser', 'in_group') - """ - self._tag_etype_attr(etype, attr, desttype, - formtype=formtype, section='attributes') - - def set_muledit_editable(self, etype, attrs): - """make `attrs` appear in muledit form of `etype`. - - :param etype: the entity type as a string - :param attrs: the ordered list of attribute names (or relations) - - `attrs` can be strings or 2-tuples (relname, role_of_etype_in_the_relation) - - Examples: - - .. sourcecode:: python - - from cubicweb.web.views.uicfg import autoform_section as afs - - afs.set_muledit_editable('CWUser', ('firstname', 'surname', 'in_group')) - """ - for attr in attrs: - self.edit_as_attr(self, etype, attr, formtype='muledit') - -autoform_section = AutoformSectionRelationTags() - - -# relations'field class - -class AutoformFieldTags(RelationTags): - __regid__ = 'autoform_field' - - def set_field(self, etype, attr, field): - """sets the `attr` field of `etype`. - - :param etype: the entity type as a string - :param attr: the name of the attribute or relation - - `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation) - - """ - self._tag_etype_attr(etype, attr, '*', field) - -autoform_field = AutoformFieldTags() - - -# relations'field explicit kwargs (given to field's __init__) - -class AutoformFieldKwargsTags(RelationTagsDict): - __regid__ = 'autoform_field_kwargs' - - def set_fields_order(self, etype, attrs): - """specify the field order in `etype` main edition form. - - :param etype: the entity type as a string - :param attrs: the ordered list of attribute names (or relations) - - `attrs` can be strings or 2-tuples (relname, role_of_etype_in_the_rel) - - Unspecified fields will be displayed after specified ones, their - order being consistent with the schema definition. - - Examples: - - .. sourcecode:: python - - from cubicweb.web.views.uicfg import autoform_field_kwargs as affk - affk.set_fields_order('CWUser', ('firstname', 'surname', 'login')) - affk.set_fields_order('CWUser', ('firstname', ('in_group', 'subject'), - 'surname', 'login')) - - """ - for index, attr in enumerate(attrs): - self._tag_etype_attr(etype, attr, '*', {'order': index}) - - def set_field_kwargs(self, etype, attr, **kwargs): - """tag `attr` field of `etype` with additional named paremeters. - - :param etype: the entity type as a string - :param attr: the name of the attribute or relation - - `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation) - - Examples: - - .. sourcecode:: python - - from cubicweb.web.views.uicfg import autoform_field_kwargs as affk - affk.set_field_kwargs('Person', 'works_for', widget=fwdgs.AutoCompletionWidget()) - affk.set_field_kwargs('CWUser', 'login', label=_('login or email address'), - widget=fwdgs.TextInput(attrs={'size': 30})) - """ - self._tag_etype_attr(etype, attr, '*', kwargs) - - -autoform_field_kwargs = AutoformFieldKwargsTags() - - -# set of tags of the form _on_new on relations. is a -# schema action (add/update/delete/read), and when such a tag is found -# permissions checking is by-passed and supposed to be ok -class AutoFormPermissionsOverrides(RelationTagsSet): - __regid__ = 'autoform_permissions_overrides' - -autoform_permissions_overrides = AutoFormPermissionsOverrides() - - -class ReleditTags(NoTargetRelationTagsDict): - """Associate to relation a dictionary to control `reledit` (e.g. edition of - attributes / relations from within views). - - Possible keys and associated values are: - - * `novalue_label`, alternative default value (shown when there is no value). - - * `novalue_include_rtype`, when `novalue_label` is not specified, this boolean - flag control wether the generated default value should contains the - relation label or not. Will be the opposite of the `showlabel` value found - in the `primaryview_display_ctrl` rtag by default. - - * `reload`, boolean, eid (to reload to) or function taking subject and - returning bool/eid. This is useful when editing a relation (or attribute) - that impacts the url or another parts of the current displayed - page. Defaults to False. - - * `rvid`, alternative view id (as str) for relation or composite edition. - Default is 'autolimited'. - - * `edit_target`, may be either 'rtype' (to edit the relation) or 'related' - (to edit the related entity). This controls whether to edit the relation - or the target entity of the relation. Currently only one-to-one relations - support target entity edition. By default, the 'related' option is taken - whenever the relation is composite. - """ - __regid__ = 'reledit' - _keys = frozenset('novalue_label novalue_include_rtype reload rvid edit_target'.split()) - - def tag_relation(self, key, tag): - for tagkey in tag: - assert tagkey in self._keys, 'tag %r not in accepted tags: %r' % (tag, self._keys) - return super(ReleditTags, self).tag_relation(key, tag) - - def _init(self, sschema, rschema, oschema, role): - values = self.get(sschema, rschema, oschema, role) - if not rschema.final: - composite = rschema.rdef(sschema, oschema).composite == role - if role == 'subject': - oschema = '*' - else: - sschema = '*' - edittarget = values.get('edit_target') - if edittarget not in (None, 'rtype', 'related'): - self.warning('reledit: wrong value for edit_target on relation %s: %s', - rschema, edittarget) - edittarget = None - if not edittarget: - edittarget = 'related' if composite else 'rtype' - self.tag_relation((sschema, rschema, oschema, role), - {'edit_target': edittarget}) - if not 'novalue_include_rtype' in values: - showlabel = primaryview_display_ctrl.get( - sschema, rschema, oschema, role).get('showlabel', True) - self.tag_relation((sschema, rschema, oschema, role), - {'novalue_include_rtype': not showlabel}) - -reledit_ctrl = ReleditTags() - - -# boxes.EditBox configuration ################################################# - -# 'link' / 'create' relation tags, used to control the "add entity" submenu - -class ActionBoxUicfg(RelationTagsBool): - __regid__ = 'actionbox_appearsin_addmenu' - - def _init(self, sschema, rschema, oschema, role): - if self.get(sschema, rschema, oschema, role) is None: - if rschema in META_RTYPES: - self.tag_relation((sschema, rschema, oschema, role), False) - return - rdef = rschema.rdef(sschema, oschema) - if not rdef.role_cardinality(role) in '?1' and rdef.composite == role: - self.tag_relation((sschema, rschema, oschema, role), True) - - def _tag_etype_attr(self, etype, attr, desttype='*', *args, **kwargs): - if isinstance(attr, string_types): - attr, role = attr, 'subject' - else: - attr, role = attr - if role == 'subject': - self.tag_subject_of((etype, attr, desttype), *args, **kwargs) - else: - self.tag_object_of((desttype, attr, etype), *args, **kwargs) - - def append_to_addmenu(self, etype, attr, createdtype='*'): - """adds `attr` in the actions box *addrelated* submenu of `etype`. - - :param etype: the entity type as a string - :param attr: the name of the attribute or relation to hide - :param createdtype: the target type of the relation (optional, defaults to '*' (all possible types)) - - `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation) - - """ - self._tag_etype_attr(etype, attr, createdtype, True) - - def remove_from_addmenu(self, etype, attr, createdtype='*'): - """removes `attr` from the actions box *addrelated* submenu of `etype`. - - :param etype: the entity type as a string - :param attr: the name of the attribute or relation to hide - :param createdtype: the target type of the relation (optional, defaults to '*' (all possible types)) - - `attr` can be a string or 2-tuple (relname, role_of_etype_in_the_relation) - """ - self._tag_etype_attr(etype, attr, createdtype, False) - -actionbox_appearsin_addmenu = ActionBoxUicfg() - - - -def registration_callback(vreg): - vreg.register_all(globals().values(), __name__) - indexview_etype_section.init(vreg.schema)