web/uicfg.py
changeset 3476 6e927b729ae1
parent 3369 7b88d12b4ee2
child 3480 370d20fec445
equal deleted inserted replaced
3475:9c07e6c48e35 3476:6e927b729ae1
    55 `````````````````````````
    55 `````````````````````````
    56 :actionbox_appearsin_addmenu:
    56 :actionbox_appearsin_addmenu:
    57   simple boolean relation tags used to control the "add entity" submenu.
    57   simple boolean relation tags used to control the "add entity" submenu.
    58   Relations whose rtag is True will appears, other won't.
    58   Relations whose rtag is True will appears, other won't.
    59 
    59 
       
    60 
    60 Automatic form configuration
    61 Automatic form configuration
    61 ````````````````````````````
    62 ````````````````````````````
       
    63 :autoform_section:
       
    64    where to display a relation in entity form, according to form type.
       
    65    `tag_attribute`, `tag_subject_of` and `tag_object_of` methods for this
       
    66    relation tags expect two arguments additionaly to the relation key: a
       
    67    `formtype` and a `section`.
       
    68 
       
    69    formtype may be one of:
       
    70    * 'main', the main entity form
       
    71    * 'inlined', the form for an entity inlined into another's one
       
    72    * 'muledit', the multiple entity (table) form
       
    73 
       
    74    section may be one of:
       
    75    * 'hidden', don't display
       
    76    * 'attributes', display in the attributes section
       
    77    * 'relations', display in the relations section, using the generic relation
       
    78      selector combobox (available in main form only, and not for attribute
       
    79      relation)
       
    80    * 'metadata', display in a special metadata form (NOT YET IMPLEMENTED,
       
    81      subject to changes)
       
    82 
       
    83 :autoform_field:
       
    84   specify a custom field instance to use for a relation
       
    85 
       
    86 :autoform_field_kwargs:
       
    87 
       
    88   specify a dictionnary of arguments to give to the field constructor for a
       
    89   relation. You usually want to use either `autoform_field` or
       
    90   `autoform_field_kwargs`, not both. The later won't have any effect if the
       
    91   former is specified for a relation.
       
    92 
       
    93 :autoform_permissions_overrides:
       
    94 
       
    95   provide a way to by-pass security checking for dark-corner case where it can't
       
    96   be verified properly. XXX documents.
       
    97 
    62 
    98 
    63 :organization: Logilab
    99 :organization: Logilab
    64 :copyright: 2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
   100 :copyright: 2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
    65 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
   101 :contact: http://www.logilab.fr/ -- mailto:contact@logilab.fr
    66 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
   102 :license: GNU Lesser General Public License, v2.1 - http://www.gnu.org/licenses
    67 """
   103 """
    68 __docformat__ = "restructuredtext en"
   104 __docformat__ = "restructuredtext en"
    69 
   105 
       
   106 from warnings import warn
       
   107 
    70 from cubicweb import neg_role
   108 from cubicweb import neg_role
    71 from cubicweb.rtags import (RelationTags, RelationTagsBool,
   109 from cubicweb.rtags import (RelationTags, RelationTagsBool, RelationTagsSet,
    72                             RelationTagsSet, RelationTagsDict, register_rtag)
   110                             RelationTagsDict, register_rtag, _ensure_str_key)
       
   111 from cubicweb.schema import META_RTYPES
    73 from cubicweb.web import formwidgets
   112 from cubicweb.web import formwidgets
    74 
   113 
    75 
   114 
    76 def card_from_role(card, role):
   115 def card_from_role(card, role):
    77     if role == 'subject':
   116     if role == 'subject':
   142     rtag.setdefault((sschema, rschema, oschema, role), 'order', rtag._counter)
   181     rtag.setdefault((sschema, rschema, oschema, role), 'order', rtag._counter)
   143 
   182 
   144 primaryview_display_ctrl = DisplayCtrlRelationTags('primaryview_display_ctrl',
   183 primaryview_display_ctrl = DisplayCtrlRelationTags('primaryview_display_ctrl',
   145                                                    init_primaryview_display_ctrl)
   184                                                    init_primaryview_display_ctrl)
   146 
   185 
       
   186 
   147 # index view configuration ####################################################
   187 # index view configuration ####################################################
   148 # entity type section in the index/manage page. May be one of
   188 # entity type section in the index/manage page. May be one of
   149 # * 'application'
   189 # * 'application'
   150 # * 'system'
   190 # * 'system'
   151 # * 'schema'
   191 # * 'schema'
   174                                             BaseTransition='hidden',
   214                                             BaseTransition='hidden',
   175                                             )
   215                                             )
   176 
   216 
   177 # autoform.AutomaticEntityForm configuration ##################################
   217 # autoform.AutomaticEntityForm configuration ##################################
   178 
   218 
   179 # relations'section (eg primary/secondary/generic/metadata/generated)
   219 def _formsections_as_dict(formsections):
   180 
   220     result = {}
   181 def init_autoform_section(rtag, sschema, rschema, oschema, role):
   221     for formsection in formsections:
   182     if rtag.get(sschema, rschema, oschema, role) is None:
   222         formtype, section = formsection.split('_', 1)
   183         if autoform_is_inlined.get(sschema, rschema, oschema, role) or \
   223         result[formtype] = section
   184                autoform_is_inlined.get(sschema, rschema, oschema, neg_role(role)):
   224     return result
   185             section = 'generated'
   225 
   186         elif sschema.is_metadata(rschema):
   226 def _card_and_comp(sschema, rschema, oschema, role):
   187             section = 'metadata'
   227     if role == 'subject':
       
   228         card = rschema.rproperty(sschema, oschema, 'cardinality')[0]
       
   229         composed = rschema.rproperty(sschema, oschema, 'composite') == 'object'
       
   230     else:
       
   231         card = rschema.rproperty(sschema, oschema, 'cardinality')[1]
       
   232         composed = rschema.rproperty(sschema, oschema, 'composite') == 'subject'
       
   233     return card, composed
       
   234 
       
   235 class AutoformSectionRelationTags(RelationTagsSet):
       
   236     """autoform relations'section"""
       
   237 
       
   238     bw_tag_map = {
       
   239         'primary':   {'main': 'attributes', 'muledit': 'attributes'},
       
   240         'secondary': {'main': 'attributes', 'muledit': 'hidden'},
       
   241         'metadata':  {'main': 'metadata'},
       
   242         'generic':   {'main': 'relations'},
       
   243         'generated': {'main': 'hidden'},
       
   244         }
       
   245 
       
   246     _allowed_form_types = ('main', 'inlined', 'muledit')
       
   247     _allowed_values = {'main': ('attributes', 'relations', 'metadata', 'hidden'),
       
   248                        'inlined': ('attributes', 'hidden'),
       
   249                        'muledit': ('attributes', 'hidden'),
       
   250                        }
       
   251 
       
   252     @staticmethod
       
   253     def _initfunc(self, sschema, rschema, oschema, role):
       
   254         formsections = self.init_get(sschema, rschema, oschema, role)
       
   255         if formsections is None:
       
   256             formsections = self.tag_container_cls()
       
   257         sectdict = _formsections_as_dict(formsections)
       
   258         if rschema in META_RTYPES:
       
   259             sectdict.setdefault('main', 'hidden')
       
   260             sectdict.setdefault('muledit', 'hidden')
       
   261             sectdict.setdefault('inlined', 'hidden')
       
   262         # ensure we have a tag for each form type
       
   263         if not 'main' in sectdict:
       
   264             if not rschema.is_final() and (
       
   265                 sectdict.get('inlined') == 'attributes' or
       
   266                 'inlined_attributes' in self.init_get(sschema, rschema, oschema,
       
   267                                                       neg_role(role))):
       
   268                 sectdict['main'] = 'hidden'
       
   269             elif sschema.is_metadata(rschema):
       
   270                 sectdict['main'] = 'metadata'
       
   271             else:
       
   272                 card, composed = _card_and_comp(sschema, rschema, oschema, role)
       
   273                 if card in '1+':
       
   274                     if not rschema.is_final() and composed:
       
   275                         # XXX why? probably because we want it unlined, though
       
   276                         # this is not the case by default
       
   277                         sectdict['main'] = 'hidden'
       
   278                     else:
       
   279                         sectdict['main'] = 'attributes'
       
   280                         if not 'muledit' in sectdict:
       
   281                             sectdict['muledit'] = 'attributes'
       
   282                 elif rschema.is_final():
       
   283                     sectdict['main'] = 'attributes'
       
   284                 else:
       
   285                     sectdict['main'] = 'relations'
       
   286         if not 'muledit' in sectdict:
       
   287             sectdict['muledit'] = 'hidden'
       
   288             if sectdict['main'] == 'attributes':
       
   289                 card, composed = _card_and_comp(sschema, rschema, oschema, role)
       
   290                 if card in '1+' and not composed:
       
   291                     sectdict['muledit'] = 'attributes'
       
   292         if not 'inlined' in sectdict:
       
   293             sectdict['inlined'] = 'hidden'
       
   294         # recompute formsections and set it to avoid recomputing
       
   295         for formtype, section in sectdict.iteritems():
       
   296             formsections.add('%s_%s' % (formtype, section))
       
   297         key = _ensure_str_key( (sschema, rschema, oschema, role) )
       
   298         self._tagdefs[key] = formsections
       
   299 
       
   300     def tag_relation(self, key, formtype, section=None):
       
   301         if section is None:
       
   302             tag = formtype
       
   303             for formtype, section in self.bw_tag_map[tag].iteritems():
       
   304                 warn('[3.6] add tag to autoform section by specifying form '
       
   305                      'type and tag. Replace %s by formtype=%s, section=%s'
       
   306                      % (tag, formtype, section), DeprecationWarning, stacklevel=2)
       
   307                 self.tag_relation(key, formtype, section)
       
   308         assert formtype in self._allowed_form_types, \
       
   309                'formtype should be in (%s), not %s' % (
       
   310             ','.join(self._allowed_form_types), formtype)
       
   311         assert section in self._allowed_values[formtype], \
       
   312                'section for %s should be in (%s), not %s' % (
       
   313             formtype, ','.join(self._allowed_values[formtype]), section)
       
   314         rtags = self._tagdefs.setdefault(_ensure_str_key(key),
       
   315                                          self.tag_container_cls())
       
   316         # remove previous section for this form type if any
       
   317         if rtags:
       
   318             for tag in rtags.copy():
       
   319                 if tag.startswith(formtype):
       
   320                     rtags.remove(tag)
       
   321         rtags.add('%s_%s' % (formtype, section))
       
   322         return rtags
       
   323 
       
   324     def init_get(self, *key):
       
   325         return super(AutoformSectionRelationTags, self).get(*key)
       
   326 
       
   327     def get(self, *key):
       
   328         # overriden to avoid recomputing done in parent classes
       
   329         return self._tagdefs[key]
       
   330 
       
   331     def relations_by_section(self, entity, formtype, section,
       
   332                              permission=None, strict=False):
       
   333         """return a list of (relation schema, target schemas, role) for the
       
   334         given entity matching categories and permission.
       
   335 
       
   336         `strict`:
       
   337           bool telling if having local role is enough (strict = False) or not
       
   338         """
       
   339         tag = '%s_%s' % (formtype, section)
       
   340         eschema  = entity.e_schema
       
   341         permsoverrides = autoform_permissions_overrides
       
   342         if entity.has_eid():
       
   343             eid = entity.eid
   188         else:
   344         else:
   189             if role == 'subject':
   345             eid = None
   190                 card = rschema.rproperty(sschema, oschema, 'cardinality')[0]
   346             strict = False
   191                 composed = rschema.rproperty(sschema, oschema, 'composite') == 'object'
   347         for rschema, targetschemas, role in eschema.relation_definitions(True):
   192             else:
   348             # check category first, potentially lower cost than checking
   193                 card = rschema.rproperty(sschema, oschema, 'cardinality')[1]
   349             # permission which may imply rql queries
   194                 composed = rschema.rproperty(sschema, oschema, 'composite') == 'subject'
   350             if tag is not None:
   195             if card in '1+':
   351                 targetschemas = [tschema for tschema in targetschemas
   196                 if not rschema.is_final() and composed:
   352                                  if tag in self.etype_get(eschema, rschema,
   197                     # XXX why? probably because we want it unlined, though this
   353                                                           role, tschema)]
   198                     # is not the case by default
   354             if not targetschemas:
   199                     section = 'generated'
   355                 continue
   200                 else:
   356             if permission is not None:
   201                     section = 'primary'
   357                 # tag allowing to hijack the permission machinery when
   202             elif rschema.is_final():
   358                 # permission is not verifiable until the entity is actually
   203                 section = 'secondary'
   359                 # created...
   204             else:
   360                 if eid is None and '%s_on_new' % permission in permsoverrides.etype_get(eschema, rschema, role):
   205                 section = 'generic'
   361                     yield (rschema, targetschemas, role)
   206         rtag.tag_relation((sschema, rschema, oschema, role), section)
   362                     continue
   207 
   363                 if rschema.is_final():
   208 autoform_section = RelationTags('autoform_section', init_autoform_section,
   364                     if not rschema.has_perm(entity._cw, permission, eid):
   209                                 set(('primary', 'secondary', 'generic',
   365                         continue
   210                                      'metadata', 'generated')))
   366                 elif role == 'subject':
       
   367                     if not ((not strict and rschema.has_local_role(permission)) or
       
   368                             rschema.has_perm(entity._cw, permission, fromeid=eid)):
       
   369                         continue
       
   370                     # on relation with cardinality 1 or ?, we need delete perm as well
       
   371                     # if the relation is already set
       
   372                     if (permission == 'add'
       
   373                         and rschema.cardinality(eschema, targetschemas[0], role) in '1?'
       
   374                         and eid and entity.related(rschema.type, role)
       
   375                         and not rschema.has_perm(entity._cw, 'delete', fromeid=eid,
       
   376                                                  toeid=entity.related(rschema.type, role)[0][0])):
       
   377                         continue
       
   378                 elif role == 'object':
       
   379                     if not ((not strict and rschema.has_local_role(permission)) or
       
   380                             rschema.has_perm(entity._cw, permission, toeid=eid)):
       
   381                         continue
       
   382                     # on relation with cardinality 1 or ?, we need delete perm as well
       
   383                     # if the relation is already set
       
   384                     if (permission == 'add'
       
   385                         and rschema.cardinality(targetschemas[0], eschema, role) in '1?'
       
   386                         and eid and entity.related(rschema.type, role)
       
   387                         and not rschema.has_perm(entity._cw, 'delete', toeid=eid,
       
   388                                                  fromeid=entity.related(rschema.type, role)[0][0])):
       
   389                         continue
       
   390             yield (rschema, targetschemas, role)
       
   391 
       
   392 
       
   393 
       
   394 autoform_section = AutoformSectionRelationTags('autoform_section')
   211 
   395 
   212 # relations'field class
   396 # relations'field class
   213 autoform_field = RelationTags('autoform_field')
   397 autoform_field = RelationTags('autoform_field')
   214 
   398 
   215 # relations'field explicit kwargs (given to field's __init__)
   399 # relations'field explicit kwargs (given to field's __init__)
   216 autoform_field_kwargs = RelationTagsDict()
   400 autoform_field_kwargs = RelationTagsDict()
   217 
       
   218 # inlined view flag for non final relations: when True for an entry, the
       
   219 # entity(ies) at the other end of the relation will be editable from the
       
   220 # form of the edited entity
       
   221 autoform_is_inlined = RelationTagsBool('autoform_is_inlined')
       
   222 
   401 
   223 
   402 
   224 # set of tags of the form <action>_on_new on relations. <action> is a
   403 # set of tags of the form <action>_on_new on relations. <action> is a
   225 # schema action (add/update/delete/read), and when such a tag is found
   404 # schema action (add/update/delete/read), and when such a tag is found
   226 # permissions checking is by-passed and supposed to be ok
   405 # permissions checking is by-passed and supposed to be ok
   236                rschema.rproperty(sschema, oschema, 'composite') == role:
   415                rschema.rproperty(sschema, oschema, 'composite') == role:
   237             rtag.tag_relation((sschema, rschema, oschema, role), True)
   416             rtag.tag_relation((sschema, rschema, oschema, role), True)
   238 
   417 
   239 actionbox_appearsin_addmenu = RelationTagsBool('actionbox_appearsin_addmenu',
   418 actionbox_appearsin_addmenu = RelationTagsBool('actionbox_appearsin_addmenu',
   240                                                init_actionbox_appearsin_addmenu)
   419                                                init_actionbox_appearsin_addmenu)
       
   420 
       
   421 
       
   422 # deprecated ###################################################################
       
   423 
       
   424 class AutoformIsInlined(RelationTags):
       
   425     """XXX for < 3.6 bw compat"""
       
   426     def tag_relation(self, key, tag):
       
   427         warn('autoform_is_inlined rtag is deprecated, use autoform_section '
       
   428              'with inlined formtype and "attributes" or "hidden" section',
       
   429              DeprecationWarning, stacklevel=2)
       
   430         section = tag and 'attributes' or 'hidden'
       
   431         autoform_section.tag_relation(key, 'inlined', section)
       
   432 
       
   433 # inlined view flag for non final relations: when True for an entry, the
       
   434 # entity(ies) at the other end of the relation will be editable from the
       
   435 # form of the edited entity
       
   436 autoform_is_inlined = AutoformIsInlined('autoform_is_inlined')