web/views/autoform.py
changeset 3476 6e927b729ae1
parent 3468 b02fa4db2868
child 3481 d614369df4b3
equal deleted inserted replaced
3475:9c07e6c48e35 3476:6e927b729ae1
    14 from cubicweb.web import stdmsgs, uicfg
    14 from cubicweb.web import stdmsgs, uicfg
    15 from cubicweb.web import form, formwidgets as fwdgs
    15 from cubicweb.web import form, formwidgets as fwdgs
    16 from cubicweb.web.formfields import guess_field
    16 from cubicweb.web.formfields import guess_field
    17 from cubicweb.web.views import forms, editforms
    17 from cubicweb.web.views import forms, editforms
    18 
    18 
       
    19 _afs = uicfg.autoform_section
    19 
    20 
    20 class AutomaticEntityForm(forms.EntityFieldsForm):
    21 class AutomaticEntityForm(forms.EntityFieldsForm):
    21     """base automatic form to edit any entity.
    22     """base automatic form to edit any entity.
    22 
    23 
    23     Designed to be fully generated from schema but highly configurable through:
    24     Designed to be fully generated from schema but highly configurable through:
    24     * rtags (rcategories, rfields, rwidgets, inlined, rpermissions)
    25 
       
    26     * uicfg (autoform_* relation tags)
    25     * various standard form parameters
    27     * various standard form parameters
    26 
    28     * overriding
    27     XXX s/rtags/uicfg/ ?
       
    28 
    29 
    29     You can also easily customise it by adding/removing fields in
    30     You can also easily customise it by adding/removing fields in
    30     AutomaticEntityForm instances.
    31     AutomaticEntityForm instances or by inheriting from it.
    31     """
    32     """
    32     __regid__ = 'edition'
    33     __regid__ = 'edition'
    33 
    34 
    34     cwtarget = 'eformframe'
    35     cwtarget = 'eformframe'
    35     cssclass = 'entityForm'
    36     cssclass = 'entityForm'
    36     copy_nav_params = True
    37     copy_nav_params = True
    37     form_buttons = [fwdgs.SubmitButton(),
    38     form_buttons = [fwdgs.SubmitButton(),
    38                     fwdgs.Button(stdmsgs.BUTTON_APPLY, cwaction='apply'),
    39                     fwdgs.Button(stdmsgs.BUTTON_APPLY, cwaction='apply'),
    39                     fwdgs.Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')]
    40                     fwdgs.Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')]
    40     attrcategories = ('primary', 'secondary')
    41     # for attributes selection when searching in uicfg.autoform_section
       
    42     formtype = 'main'
       
    43     # set this to a list of [(relation, role)] if you want to explictily tell
       
    44     # which relations should be edited
       
    45     display_fields = None
    41     # class attributes below are actually stored in the uicfg module since we
    46     # class attributes below are actually stored in the uicfg module since we
    42     # don't want them to be reloaded
    47     # don't want them to be reloaded
    43     rcategories = uicfg.autoform_section
       
    44     rfields = uicfg.autoform_field
    48     rfields = uicfg.autoform_field
    45     rfields_kwargs = uicfg.autoform_field_kwargs
    49     rfields_kwargs = uicfg.autoform_field_kwargs
    46     rinlined = uicfg.autoform_is_inlined
       
    47     rpermissions_overrides = uicfg.autoform_permissions_overrides
       
    48 
    50 
    49     # class methods mapping schema relations to fields in the form ############
    51     # class methods mapping schema relations to fields in the form ############
    50 
       
    51     @classmethod
       
    52     def erelations_by_category(cls, entity, categories=None, permission=None,
       
    53                                rtags=None, strict=False):
       
    54         """return a list of (relation schema, target schemas, role) matching
       
    55         categories and permission
       
    56 
       
    57         `strict`:
       
    58           bool telling if having local role is enough (strict = False) or not
       
    59         """
       
    60         if categories is not None:
       
    61             if not isinstance(categories, (list, tuple, set, frozenset)):
       
    62                 categories = (categories,)
       
    63             if not isinstance(categories, (set, frozenset)):
       
    64                 categories = frozenset(categories)
       
    65         eschema  = entity.e_schema
       
    66         if rtags is None:
       
    67             rtags = cls.rcategories
       
    68         permsoverrides = cls.rpermissions_overrides
       
    69         if entity.has_eid():
       
    70             eid = entity.eid
       
    71         else:
       
    72             eid = None
       
    73             strict = False
       
    74         for rschema, targetschemas, role in eschema.relation_definitions(True):
       
    75             # check category first, potentially lower cost than checking
       
    76             # permission which may imply rql queries
       
    77             if categories is not None:
       
    78                 targetschemas = [tschema for tschema in targetschemas
       
    79                                  if rtags.etype_get(eschema, rschema, role, tschema) in categories]
       
    80                 if not targetschemas:
       
    81                     continue
       
    82             if permission is not None:
       
    83                 # tag allowing to hijack the permission machinery when
       
    84                 # permission is not verifiable until the entity is actually
       
    85                 # created...
       
    86                 if eid is None and '%s_on_new' % permission in permsoverrides.etype_get(eschema, rschema, role):
       
    87                     yield (rschema, targetschemas, role)
       
    88                     continue
       
    89                 if rschema.is_final():
       
    90                     if not rschema.has_perm(entity._cw, permission, eid):
       
    91                         continue
       
    92                 elif role == 'subject':
       
    93                     if not ((not strict and rschema.has_local_role(permission)) or
       
    94                             rschema.has_perm(entity._cw, permission, fromeid=eid)):
       
    95                         continue
       
    96                     # on relation with cardinality 1 or ?, we need delete perm as well
       
    97                     # if the relation is already set
       
    98                     if (permission == 'add'
       
    99                         and rschema.cardinality(eschema, targetschemas[0], role) in '1?'
       
   100                         and eid and entity.related(rschema.type, role)
       
   101                         and not rschema.has_perm(entity._cw, 'delete', fromeid=eid,
       
   102                                                  toeid=entity.related(rschema.type, role)[0][0])):
       
   103                         continue
       
   104                 elif role == 'object':
       
   105                     if not ((not strict and rschema.has_local_role(permission)) or
       
   106                             rschema.has_perm(entity._cw, permission, toeid=eid)):
       
   107                         continue
       
   108                     # on relation with cardinality 1 or ?, we need delete perm as well
       
   109                     # if the relation is already set
       
   110                     if (permission == 'add'
       
   111                         and rschema.cardinality(targetschemas[0], eschema, role) in '1?'
       
   112                         and eid and entity.related(rschema.type, role)
       
   113                         and not rschema.has_perm(entity._cw, 'delete', toeid=eid,
       
   114                                                  fromeid=entity.related(rschema.type, role)[0][0])):
       
   115                         continue
       
   116             yield (rschema, targetschemas, role)
       
   117 
       
   118     @classmethod
       
   119     def esrelations_by_category(cls, entity, categories=None, permission=None,
       
   120                                 strict=False):
       
   121         """filter out result of relations_by_category(categories, permission) by
       
   122         removing final relations
       
   123 
       
   124         return a sorted list of (relation's label, relation'schema, role)
       
   125         """
       
   126         result = []
       
   127         for rschema, ttypes, role in cls.erelations_by_category(
       
   128             entity, categories, permission, strict=strict):
       
   129             if rschema.is_final():
       
   130                 continue
       
   131             result.append((rschema.display_name(entity._cw, role), rschema, role))
       
   132         return sorted(result)
       
   133 
    52 
   134     @iclassmethod
    53     @iclassmethod
   135     def field_by_name(cls_or_self, name, role='subject', eschema=None):
    54     def field_by_name(cls_or_self, name, role='subject', eschema=None):
   136         """return field with the given name and role. If field is not explicitly
    55         """return field with the given name and role. If field is not explicitly
   137         defined for the form but `eclass` is specified, guess_field will be
    56         defined for the form but `eclass` is specified, guess_field will be
   165     def __init__(self, *args, **kwargs):
    84     def __init__(self, *args, **kwargs):
   166         super(AutomaticEntityForm, self).__init__(*args, **kwargs)
    85         super(AutomaticEntityForm, self).__init__(*args, **kwargs)
   167         entity = self.edited_entity
    86         entity = self.edited_entity
   168         if entity.has_eid():
    87         if entity.has_eid():
   169             entity.complete()
    88             entity.complete()
   170         for rschema, role in self.editable_attributes():
    89         for rtype, role in self.editable_attributes():
   171             try:
    90             try:
   172                 self.field_by_name(rschema.type, role)
    91                 self.field_by_name(str(rtype), role)
   173                 continue # explicitly specified
    92                 continue # explicitly specified
   174             except form.FieldNotFound:
    93             except form.FieldNotFound:
   175                 # has to be guessed
    94                 # has to be guessed
   176                 try:
    95                 try:
   177                     field = self.field_by_name(rschema.type, role,
    96                     field = self.field_by_name(str(rtype), role,
   178                                                eschema=entity.e_schema)
    97                                                eschema=entity.e_schema)
   179                     self.fields.append(field)
    98                     self.fields.append(field)
   180                 except form.FieldNotFound:
    99                 except form.FieldNotFound:
   181                     # meta attribute such as <attr>_format
   100                     # meta attribute such as <attr>_format
   182                     continue
   101                     continue
   232 
   151 
   233     action = property(action, set_action)
   152     action = property(action, set_action)
   234 
   153 
   235     # methods mapping edited entity relations to fields in the form ############
   154     # methods mapping edited entity relations to fields in the form ############
   236 
   155 
   237     def relations_by_category(self, categories=None, permission=None):
   156     def _relations_by_section(self, section, permission='add', strict=False):
   238         """return a list of (relation schema, target schemas, role) matching
   157         """return a list of (relation schema, target schemas, role) matching
   239         given category(ies) and permission
   158         given category(ies) and permission
   240         """
   159         """
   241         return self.erelations_by_category(self.edited_entity, categories,
   160         return _afs.relations_by_section(
   242                                            permission)
   161             self.edited_entity, self.formtype, section, permission, strict)
       
   162 
       
   163     def editable_attributes(self, strict=False):
       
   164         """return a list of (relation schema, role) to edit for the entity"""
       
   165         if self.display_fields is not None:
       
   166             return self.display_fields
       
   167         # XXX we should simply put eid in the generated section, no?
       
   168         return [(rtype, role) for rtype, _, role in self._relations_by_section(
       
   169             'attributes', strict=strict) if rtype != 'eid']
       
   170 
       
   171     def editable_relations(self):
       
   172         """return a sorted list of (relation's label, relation'schema, role) for
       
   173         relations in the 'relations' section
       
   174         """
       
   175         result = []
       
   176         for rschema, _, role in self._relations_by_section('relations',
       
   177                                                            strict=True):
       
   178             result.append( (rschema.display_name(entity._cw, role,
       
   179                                                  entity.__regid__),
       
   180                             rschema, role) )
       
   181         return sorted(result)
   243 
   182 
   244     def inlined_relations(self):
   183     def inlined_relations(self):
   245         """return a list of (relation schema, target schemas, role) matching
   184         """return a list of (relation schema, target schemas, role) matching
   246         given category(ies) and permission
   185         given category(ies) and permission
   247         """
   186         """
   248         return self.erelations_by_category(self.edited_entity, True, 'add',
   187         return self._relations_by_section('inlined')
   249                                            self.rinlined)
       
   250 
       
   251     def srelations_by_category(self, categories=None, permission=None,
       
   252                                strict=False):
       
   253         """filter out result of relations_by_category(categories, permission) by
       
   254         removing final relations
       
   255 
       
   256         return a sorted list of (relation's label, relation'schema, role)
       
   257         """
       
   258         return self.esrelations_by_category(self.edited_entity, categories,
       
   259                                            permission, strict=strict)
       
   260 
       
   261     def editable_attributes(self):
       
   262         """return a list of (relation schema, role) to edit for the entity"""
       
   263         return [(rschema, role) for rschema, _, role in self.relations_by_category(
       
   264                 self.attrcategories, 'add') if rschema != 'eid']
       
   265 
   188 
   266     # generic relations modifier ###############################################
   189     # generic relations modifier ###############################################
   267 
   190 
   268     def relations_table(self):
   191     def relations_table(self):
   269         """yiels 3-tuples (rtype, target, related_list)
   192         """yiels 3-tuples (rtype, target, related_list)
   273           - status 'pendingdelete' or ''
   196           - status 'pendingdelete' or ''
   274           - oneline view of related entity
   197           - oneline view of related entity
   275         """
   198         """
   276         entity = self.edited_entity
   199         entity = self.edited_entity
   277         pending_deletes = self._cw.get_pending_deletes(entity.eid)
   200         pending_deletes = self._cw.get_pending_deletes(entity.eid)
   278         for label, rschema, role in self.srelations_by_category('generic', 'add',
   201         for label, rschema, role in self.editable_relations():
   279                                                                 strict=True):
       
   280             relatedrset = entity.related(rschema, role, limit=self.related_limit)
   202             relatedrset = entity.related(rschema, role, limit=self.related_limit)
   281             if rschema.has_perm(self._cw, 'delete'):
   203             if rschema.has_perm(self._cw, 'delete'):
   282                 toggleable_rel_link_func = editforms.toggleable_relation_link
   204                 toggleable_rel_link_func = editforms.toggleable_relation_link
   283             else:
   205             else:
   284                 toggleable_rel_link_func = lambda x, y, z: u''
   206                 toggleable_rel_link_func = lambda x, y, z: u''