[uicfg, autoform] more consistent/powerful autoform_section rtags by using formtype/section; deprecates autoform_is_inlined; refactor automatci form and renderer thanks to this
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Fri, 25 Sep 2009 11:49:47 +0200
changeset 3476 6e927b729ae1
parent 3475 9c07e6c48e35
child 3477 8a9e26b93bb7
[uicfg, autoform] more consistent/powerful autoform_section rtags by using formtype/section; deprecates autoform_is_inlined; refactor automatci form and renderer thanks to this
devtools/devctl.py
web/uicfg.py
web/views/actions.py
web/views/autoform.py
web/views/editforms.py
web/views/forms.py
--- a/devtools/devctl.py	Fri Sep 25 11:30:59 2009 +0200
+++ b/devtools/devctl.py	Fri Sep 25 11:49:47 2009 +0200
@@ -124,19 +124,19 @@
     if libconfig is not None:
         from cubicweb.cwvreg import CubicWebVRegistry, clear_rtag_objects
         libschema = libconfig.load_schema(remove_unused_rtypes=False)
-        rinlined = deepcopy(uicfg.autoform_is_inlined)
+        afs = deepcopy(uicfg.autoform_section)
         appearsin_addmenu = deepcopy(uicfg.actionbox_appearsin_addmenu)
         clear_rtag_objects()
         cleanup_sys_modules(libconfig)
         libvreg = CubicWebVRegistry(libconfig)
         libvreg.set_schema(libschema) # trigger objects registration
-        librinlined = uicfg.autoform_is_inlined
+        libafs = uicfg.autoform_section
         libappearsin_addmenu = uicfg.actionbox_appearsin_addmenu
         # prefill vregdone set
         list(_iter_vreg_objids(libvreg, vregdone))
     else:
         libschema = {}
-        rinlined = uicfg.autoform_is_inlined
+        afs = uicfg.autoform_section
         appearsin_addmenu = uicfg.actionbox_appearsin_addmenu
     done = set()
     for eschema in sorted(schema.entities()):
@@ -154,9 +154,11 @@
             continue
         for rschema, targetschemas, role in eschema.relation_definitions(True):
             for tschema in targetschemas:
-                if rinlined.etype_get(eschema, rschema, role, tschema) and \
+                fsections = afs.etype_get(eschema, rschema, role, tschema)
+                if 'inlined_attributes' in fsections and \
                        (libconfig is None or not
-                        librinlined.etype_get(eschema, rschema, role, tschema)):
+                        'inlined_attributes' in libafs.etype_get(
+                            eschema, rschema, role, tschema)):
                     add_msg(w, 'add a %s' % tschema,
                             'inlined:%s.%s.%s' % (etype, rschema, role))
                     add_msg(w, 'remove this %s' % tschema,
--- a/web/uicfg.py	Fri Sep 25 11:30:59 2009 +0200
+++ b/web/uicfg.py	Fri Sep 25 11:49:47 2009 +0200
@@ -57,8 +57,44 @@
   simple boolean relation tags used to control the "add entity" submenu.
   Relations whose rtag is True will appears, other won't.
 
+
 Automatic form configuration
 ````````````````````````````
+:autoform_section:
+   where to display a relation in entity form, according to form type.
+   `tag_attribute`, `tag_subject_of` and `tag_object_of` methods for this
+   relation tags expect two arguments additionaly to the relation key: a
+   `formtype` and a `section`.
+
+   formtype may be one of:
+   * 'main', the main entity form
+   * 'inlined', the form for an entity inlined into another's one
+   * 'muledit', the multiple entity (table) form
+
+   section may be one of:
+   * 'hidden', don't display
+   * 'attributes', display in the attributes section
+   * 'relations', display in the relations section, using the generic relation
+     selector combobox (available in main form only, and not for attribute
+     relation)
+   * 'metadata', display in a special metadata form (NOT YET IMPLEMENTED,
+     subject to changes)
+
+:autoform_field:
+  specify a custom field instance to use for a relation
+
+:autoform_field_kwargs:
+
+  specify a dictionnary of arguments to give to the field constructor for a
+  relation. You usually want to use either `autoform_field` or
+  `autoform_field_kwargs`, not both. The later won't have any effect if the
+  former is specified for a relation.
+
+:autoform_permissions_overrides:
+
+  provide a way to by-pass security checking for dark-corner case where it can't
+  be verified properly. XXX documents.
+
 
 :organization: Logilab
 :copyright: 2009 LOGILAB S.A. (Paris, FRANCE), license is LGPL v2.
@@ -67,9 +103,12 @@
 """
 __docformat__ = "restructuredtext en"
 
+from warnings import warn
+
 from cubicweb import neg_role
-from cubicweb.rtags import (RelationTags, RelationTagsBool,
-                            RelationTagsSet, RelationTagsDict, register_rtag)
+from cubicweb.rtags import (RelationTags, RelationTagsBool, RelationTagsSet,
+                            RelationTagsDict, register_rtag, _ensure_str_key)
+from cubicweb.schema import META_RTYPES
 from cubicweb.web import formwidgets
 
 
@@ -144,6 +183,7 @@
 primaryview_display_ctrl = DisplayCtrlRelationTags('primaryview_display_ctrl',
                                                    init_primaryview_display_ctrl)
 
+
 # index view configuration ####################################################
 # entity type section in the index/manage page. May be one of
 # * 'application'
@@ -176,38 +216,182 @@
 
 # autoform.AutomaticEntityForm configuration ##################################
 
-# relations'section (eg primary/secondary/generic/metadata/generated)
+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):
+    if role == 'subject':
+        card = rschema.rproperty(sschema, oschema, 'cardinality')[0]
+        composed = rschema.rproperty(sschema, oschema, 'composite') == 'object'
+    else:
+        card = rschema.rproperty(sschema, oschema, 'cardinality')[1]
+        composed = rschema.rproperty(sschema, oschema, 'composite') == 'subject'
+    return card, composed
+
+class AutoformSectionRelationTags(RelationTagsSet):
+    """autoform relations'section"""
 
-def init_autoform_section(rtag, sschema, rschema, oschema, role):
-    if rtag.get(sschema, rschema, oschema, role) is None:
-        if autoform_is_inlined.get(sschema, rschema, oschema, role) or \
-               autoform_is_inlined.get(sschema, rschema, oschema, neg_role(role)):
-            section = 'generated'
-        elif sschema.is_metadata(rschema):
-            section = 'metadata'
-        else:
-            if role == 'subject':
-                card = rschema.rproperty(sschema, oschema, 'cardinality')[0]
-                composed = rschema.rproperty(sschema, oschema, 'composite') == 'object'
+    bw_tag_map = {
+        'primary':   {'main': 'attributes', 'muledit': 'attributes'},
+        'secondary': {'main': 'attributes', 'muledit': 'hidden'},
+        'metadata':  {'main': 'metadata'},
+        'generic':   {'main': 'relations'},
+        'generated': {'main': 'hidden'},
+        }
+
+    _allowed_form_types = ('main', 'inlined', 'muledit')
+    _allowed_values = {'main': ('attributes', 'relations', 'metadata', 'hidden'),
+                       'inlined': ('attributes', 'hidden'),
+                       'muledit': ('attributes', 'hidden'),
+                       }
+
+    @staticmethod
+    def _initfunc(self, sschema, rschema, oschema, role):
+        formsections = self.init_get(sschema, rschema, oschema, role)
+        if formsections is None:
+            formsections = self.tag_container_cls()
+        sectdict = _formsections_as_dict(formsections)
+        if rschema in META_RTYPES:
+            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.is_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 = rschema.rproperty(sschema, oschema, 'cardinality')[1]
-                composed = rschema.rproperty(sschema, oschema, 'composite') == 'subject'
-            if card in '1+':
-                if not rschema.is_final() and composed:
-                    # XXX why? probably because we want it unlined, though this
-                    # is not the case by default
-                    section = 'generated'
+                card, composed = _card_and_comp(sschema, rschema, oschema, role)
+                if card in '1+':
+                    if not rschema.is_final() and composed:
+                        # XXX why? probably because we want it unlined, though
+                        # this is not the case by default
+                        sectdict['main'] = 'hidden'
+                    else:
+                        sectdict['main'] = 'attributes'
+                        if not 'muledit' in sectdict:
+                            sectdict['muledit'] = 'attributes'
+                elif rschema.is_final():
+                    sectdict['main'] = 'attributes'
                 else:
-                    section = 'primary'
-            elif rschema.is_final():
-                section = 'secondary'
-            else:
-                section = 'generic'
-        rtag.tag_relation((sschema, rschema, oschema, role), section)
+                    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'] = 'hidden'
+        # recompute formsections and set it to avoid recomputing
+        for formtype, section in sectdict.iteritems():
+            formsections.add('%s_%s' % (formtype, section))
+        key = _ensure_str_key( (sschema, rschema, oschema, role) )
+        self._tagdefs[key] = formsections
+
+    def tag_relation(self, key, formtype, section=None):
+        if section is None:
+            tag = formtype
+            for formtype, section in self.bw_tag_map[tag].iteritems():
+                warn('[3.6] add tag to autoform section by specifying form '
+                     'type and tag. Replace %s by formtype=%s, section=%s'
+                     % (tag, formtype, section), DeprecationWarning, stacklevel=2)
+                self.tag_relation(key, formtype, section)
+        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, *key):
+        return super(AutoformSectionRelationTags, self).get(*key)
+
+    def get(self, *key):
+        # overriden to avoid recomputing done in parent classes
+        return self._tagdefs[key]
+
+    def relations_by_section(self, entity, formtype, section,
+                             permission=None, strict=False):
+        """return a list of (relation schema, target schemas, role) for the
+        given entity matching categories and permission.
 
-autoform_section = RelationTags('autoform_section', init_autoform_section,
-                                set(('primary', 'secondary', 'generic',
-                                     'metadata', 'generated')))
+        `strict`:
+          bool telling if having local role is enough (strict = False) or not
+        """
+        tag = '%s_%s' % (formtype, section)
+        eschema  = entity.e_schema
+        permsoverrides = autoform_permissions_overrides
+        if entity.has_eid():
+            eid = entity.eid
+        else:
+            eid = None
+            strict = False
+        for rschema, targetschemas, role in eschema.relation_definitions(True):
+            # check category first, potentially lower cost than checking
+            # permission which may imply rql queries
+            if tag is not None:
+                targetschemas = [tschema for tschema in targetschemas
+                                 if tag in self.etype_get(eschema, rschema,
+                                                          role, tschema)]
+            if not targetschemas:
+                continue
+            if permission is not None:
+                # 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 rschema.is_final():
+                    if not rschema.has_perm(entity._cw, permission, eid):
+                        continue
+                elif role == 'subject':
+                    if not ((not strict and rschema.has_local_role(permission)) or
+                            rschema.has_perm(entity._cw, permission, fromeid=eid)):
+                        continue
+                    # on relation with cardinality 1 or ?, we need delete perm as well
+                    # if the relation is already set
+                    if (permission == 'add'
+                        and rschema.cardinality(eschema, targetschemas[0], role) in '1?'
+                        and eid and entity.related(rschema.type, role)
+                        and not rschema.has_perm(entity._cw, 'delete', fromeid=eid,
+                                                 toeid=entity.related(rschema.type, role)[0][0])):
+                        continue
+                elif role == 'object':
+                    if not ((not strict and rschema.has_local_role(permission)) or
+                            rschema.has_perm(entity._cw, permission, toeid=eid)):
+                        continue
+                    # on relation with cardinality 1 or ?, we need delete perm as well
+                    # if the relation is already set
+                    if (permission == 'add'
+                        and rschema.cardinality(targetschemas[0], eschema, role) in '1?'
+                        and eid and entity.related(rschema.type, role)
+                        and not rschema.has_perm(entity._cw, 'delete', toeid=eid,
+                                                 fromeid=entity.related(rschema.type, role)[0][0])):
+                        continue
+            yield (rschema, targetschemas, role)
+
+
+
+autoform_section = AutoformSectionRelationTags('autoform_section')
 
 # relations'field class
 autoform_field = RelationTags('autoform_field')
@@ -215,11 +399,6 @@
 # relations'field explicit kwargs (given to field's __init__)
 autoform_field_kwargs = RelationTagsDict()
 
-# inlined view flag for non final relations: when True for an entry, the
-# entity(ies) at the other end of the relation will be editable from the
-# form of the edited entity
-autoform_is_inlined = RelationTagsBool('autoform_is_inlined')
-
 
 # set of tags of the form <action>_on_new on relations. <action> is a
 # schema action (add/update/delete/read), and when such a tag is found
@@ -238,3 +417,20 @@
 
 actionbox_appearsin_addmenu = RelationTagsBool('actionbox_appearsin_addmenu',
                                                init_actionbox_appearsin_addmenu)
+
+
+# deprecated ###################################################################
+
+class AutoformIsInlined(RelationTags):
+    """XXX for < 3.6 bw compat"""
+    def tag_relation(self, key, tag):
+        warn('autoform_is_inlined rtag is deprecated, use autoform_section '
+             'with inlined formtype and "attributes" or "hidden" section',
+             DeprecationWarning, stacklevel=2)
+        section = tag and 'attributes' or 'hidden'
+        autoform_section.tag_relation(key, 'inlined', section)
+
+# inlined view flag for non final relations: when True for an entry, the
+# entity(ies) at the other end of the relation will be editable from the
+# form of the edited entity
+autoform_is_inlined = AutoformIsInlined('autoform_is_inlined')
--- a/web/views/actions.py	Fri Sep 25 11:30:59 2009 +0200
+++ b/web/views/actions.py	Fri Sep 25 11:49:47 2009 +0200
@@ -18,7 +18,6 @@
 from cubicweb.web import uicfg, controller
 from cubicweb.web.action import Action
 from cubicweb.web.views import linksearch_select_url, vid_from_rset
-from cubicweb.web.views.autoform import AutomaticEntityForm
 
 
 class has_editable_relation(EntitySelector):
@@ -31,11 +30,17 @@
     def score_entity(self, entity):
         # if user has no update right but it can modify some relation,
         # display action anyway
-        for dummy in AutomaticEntityForm.esrelations_by_category(
-            entity, 'generic', 'add', strict=True):
+        form = self._cw.vreg['forms'].select('edition', self._cw,
+                                             entity=entity)
+        for dummy in form.editable_relations():
             return 1
-        for rschema, targetschemas, role in AutomaticEntityForm.erelations_by_category(
-            entity, ('primary', 'secondary'), 'add', strict=True):
+        try:
+            editableattrs = form.editable_attributes(strict=True)
+        except TypeError:
+            warn('[3.6] %s: editable_attributes now take strict=False as '
+                 'optional argument', DeprecationWarning)
+            editableattrs = form.editable_attributes()
+        for rschema, targetschemas, role in editableattrs:
             if not rschema.is_final():
                 return 1
         return 0
--- a/web/views/autoform.py	Fri Sep 25 11:30:59 2009 +0200
+++ b/web/views/autoform.py	Fri Sep 25 11:49:47 2009 +0200
@@ -16,18 +16,19 @@
 from cubicweb.web.formfields import guess_field
 from cubicweb.web.views import forms, editforms
 
+_afs = uicfg.autoform_section
 
 class AutomaticEntityForm(forms.EntityFieldsForm):
     """base automatic form to edit any entity.
 
     Designed to be fully generated from schema but highly configurable through:
-    * rtags (rcategories, rfields, rwidgets, inlined, rpermissions)
+
+    * uicfg (autoform_* relation tags)
     * various standard form parameters
-
-    XXX s/rtags/uicfg/ ?
+    * overriding
 
     You can also easily customise it by adding/removing fields in
-    AutomaticEntityForm instances.
+    AutomaticEntityForm instances or by inheriting from it.
     """
     __regid__ = 'edition'
 
@@ -37,100 +38,18 @@
     form_buttons = [fwdgs.SubmitButton(),
                     fwdgs.Button(stdmsgs.BUTTON_APPLY, cwaction='apply'),
                     fwdgs.Button(stdmsgs.BUTTON_CANCEL, cwaction='cancel')]
-    attrcategories = ('primary', 'secondary')
+    # for attributes selection when searching in uicfg.autoform_section
+    formtype = 'main'
+    # set this to a list of [(relation, role)] if you want to explictily tell
+    # which relations should be edited
+    display_fields = None
     # class attributes below are actually stored in the uicfg module since we
     # don't want them to be reloaded
-    rcategories = uicfg.autoform_section
     rfields = uicfg.autoform_field
     rfields_kwargs = uicfg.autoform_field_kwargs
-    rinlined = uicfg.autoform_is_inlined
-    rpermissions_overrides = uicfg.autoform_permissions_overrides
 
     # class methods mapping schema relations to fields in the form ############
 
-    @classmethod
-    def erelations_by_category(cls, entity, categories=None, permission=None,
-                               rtags=None, strict=False):
-        """return a list of (relation schema, target schemas, role) matching
-        categories and permission
-
-        `strict`:
-          bool telling if having local role is enough (strict = False) or not
-        """
-        if categories is not None:
-            if not isinstance(categories, (list, tuple, set, frozenset)):
-                categories = (categories,)
-            if not isinstance(categories, (set, frozenset)):
-                categories = frozenset(categories)
-        eschema  = entity.e_schema
-        if rtags is None:
-            rtags = cls.rcategories
-        permsoverrides = cls.rpermissions_overrides
-        if entity.has_eid():
-            eid = entity.eid
-        else:
-            eid = None
-            strict = False
-        for rschema, targetschemas, role in eschema.relation_definitions(True):
-            # check category first, potentially lower cost than checking
-            # permission which may imply rql queries
-            if categories is not None:
-                targetschemas = [tschema for tschema in targetschemas
-                                 if rtags.etype_get(eschema, rschema, role, tschema) in categories]
-                if not targetschemas:
-                    continue
-            if permission is not None:
-                # 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 rschema.is_final():
-                    if not rschema.has_perm(entity._cw, permission, eid):
-                        continue
-                elif role == 'subject':
-                    if not ((not strict and rschema.has_local_role(permission)) or
-                            rschema.has_perm(entity._cw, permission, fromeid=eid)):
-                        continue
-                    # on relation with cardinality 1 or ?, we need delete perm as well
-                    # if the relation is already set
-                    if (permission == 'add'
-                        and rschema.cardinality(eschema, targetschemas[0], role) in '1?'
-                        and eid and entity.related(rschema.type, role)
-                        and not rschema.has_perm(entity._cw, 'delete', fromeid=eid,
-                                                 toeid=entity.related(rschema.type, role)[0][0])):
-                        continue
-                elif role == 'object':
-                    if not ((not strict and rschema.has_local_role(permission)) or
-                            rschema.has_perm(entity._cw, permission, toeid=eid)):
-                        continue
-                    # on relation with cardinality 1 or ?, we need delete perm as well
-                    # if the relation is already set
-                    if (permission == 'add'
-                        and rschema.cardinality(targetschemas[0], eschema, role) in '1?'
-                        and eid and entity.related(rschema.type, role)
-                        and not rschema.has_perm(entity._cw, 'delete', toeid=eid,
-                                                 fromeid=entity.related(rschema.type, role)[0][0])):
-                        continue
-            yield (rschema, targetschemas, role)
-
-    @classmethod
-    def esrelations_by_category(cls, entity, categories=None, permission=None,
-                                strict=False):
-        """filter out result of relations_by_category(categories, permission) by
-        removing final relations
-
-        return a sorted list of (relation's label, relation'schema, role)
-        """
-        result = []
-        for rschema, ttypes, role in cls.erelations_by_category(
-            entity, categories, permission, strict=strict):
-            if rschema.is_final():
-                continue
-            result.append((rschema.display_name(entity._cw, role), rschema, role))
-        return sorted(result)
-
     @iclassmethod
     def field_by_name(cls_or_self, name, role='subject', eschema=None):
         """return field with the given name and role. If field is not explicitly
@@ -167,14 +86,14 @@
         entity = self.edited_entity
         if entity.has_eid():
             entity.complete()
-        for rschema, role in self.editable_attributes():
+        for rtype, role in self.editable_attributes():
             try:
-                self.field_by_name(rschema.type, role)
+                self.field_by_name(str(rtype), role)
                 continue # explicitly specified
             except form.FieldNotFound:
                 # has to be guessed
                 try:
-                    field = self.field_by_name(rschema.type, role,
+                    field = self.field_by_name(str(rtype), role,
                                                eschema=entity.e_schema)
                     self.fields.append(field)
                 except form.FieldNotFound:
@@ -234,34 +153,38 @@
 
     # methods mapping edited entity relations to fields in the form ############
 
-    def relations_by_category(self, categories=None, permission=None):
+    def _relations_by_section(self, section, permission='add', strict=False):
         """return a list of (relation schema, target schemas, role) matching
         given category(ies) and permission
         """
-        return self.erelations_by_category(self.edited_entity, categories,
-                                           permission)
+        return _afs.relations_by_section(
+            self.edited_entity, self.formtype, section, permission, strict)
+
+    def editable_attributes(self, strict=False):
+        """return a list of (relation schema, role) to edit for the entity"""
+        if self.display_fields is not None:
+            return self.display_fields
+        # XXX we should simply put eid in the generated section, no?
+        return [(rtype, role) for rtype, _, role in self._relations_by_section(
+            'attributes', strict=strict) if rtype != 'eid']
+
+    def editable_relations(self):
+        """return a sorted list of (relation's label, relation'schema, role) for
+        relations in the 'relations' section
+        """
+        result = []
+        for rschema, _, role in self._relations_by_section('relations',
+                                                           strict=True):
+            result.append( (rschema.display_name(entity._cw, role,
+                                                 entity.__regid__),
+                            rschema, role) )
+        return sorted(result)
 
     def inlined_relations(self):
         """return a list of (relation schema, target schemas, role) matching
         given category(ies) and permission
         """
-        return self.erelations_by_category(self.edited_entity, True, 'add',
-                                           self.rinlined)
-
-    def srelations_by_category(self, categories=None, permission=None,
-                               strict=False):
-        """filter out result of relations_by_category(categories, permission) by
-        removing final relations
-
-        return a sorted list of (relation's label, relation'schema, role)
-        """
-        return self.esrelations_by_category(self.edited_entity, categories,
-                                           permission, strict=strict)
-
-    def editable_attributes(self):
-        """return a list of (relation schema, role) to edit for the entity"""
-        return [(rschema, role) for rschema, _, role in self.relations_by_category(
-                self.attrcategories, 'add') if rschema != 'eid']
+        return self._relations_by_section('inlined')
 
     # generic relations modifier ###############################################
 
@@ -275,8 +198,7 @@
         """
         entity = self.edited_entity
         pending_deletes = self._cw.get_pending_deletes(entity.eid)
-        for label, rschema, role in self.srelations_by_category('generic', 'add',
-                                                                strict=True):
+        for label, rschema, role in self.editable_relations():
             relatedrset = entity.related(rschema, role, limit=self.related_limit)
             if rschema.has_perm(self._cw, 'delete'):
                 toggleable_rel_link_func = editforms.toggleable_relation_link
--- a/web/views/editforms.py	Fri Sep 25 11:30:59 2009 +0200
+++ b/web/views/editforms.py	Fri Sep 25 11:49:47 2009 +0200
@@ -105,10 +105,6 @@
     __select__ = non_final_entity() & match_kwargs('rtype')
     # FIXME editableField class could be toggleable from userprefs
 
-    # add metadata to allow edition of metadata attributes (not considered by
-    # edition form by default)
-    attrcategories = ('primary', 'secondary', 'metadata')
-
     _onclick = u"showInlineEditionForm(%(eid)s, '%(rtype)s', '%(divid)s')"
     _defaultlandingzone = (u'<img title="%(msg)s" '
                            'src="data/accessories-text-editor.png" '
@@ -172,8 +168,7 @@
             onsubmit = ("return inlineValidateAttributeForm('%(rtype)s', '%(eid)s', '%(divid)s', "
                         "%(reload)s, '%(default)s');")
             form = self._build_form(
-                entity, rtype, role, 'edition', default, onsubmit, reload,
-                attrcategories=self.attrcategories)
+                entity, rtype, role, default, onsubmit, reload)
             if not self.should_edit_attribute(entity, rschema, role, form):
                 return
             value = entity.printable_value(rtype) or default
@@ -203,7 +198,7 @@
         rtype = str(rschema)
         ttype = rschema.targets(entity.__regid__, role)[0]
         afs = uicfg.autoform_section.etype_get(entity.__regid__, rtype, role, ttype)
-        if not (afs in self.attrcategories and entity.has_perm('update')):
+        if 'main_hidden' in afs or not entity.has_perm('update'):
             self.w(entity.printable_value(rtype))
             return False
         try:
@@ -392,8 +387,7 @@
         if entity.eid == self.newentity.eid:
             form.form_add_hidden(eid_param('__cloned_eid', entity.eid),
                                  self.copying.eid)
-        for rschema, _, role in form.relations_by_category(form.attrcategories,
-                                                           'add'):
+        for rschema, role in form.editable_attributes():
             if not rschema.is_final():
                 # ensure relation cache is filed
                 rset = self.copying.related(rschema, role)
@@ -416,9 +410,9 @@
         super(TableEditForm, self).__init__(req, rset=rset, **kwargs)
         for row in xrange(len(self.cw_rset)):
             form = self._cw.vreg['forms'].select('edition', self._cw,
-                                             rset=self.cw_rset, row=row,
-                                             attrcategories=('primary',),
-                                             mainform=False)
+                                                 rset=self.cw_rset, row=row,
+                                                 formtype='muledit',
+                                                 mainform=False)
             # XXX rely on the EntityCompositeFormRenderer to put the eid input
             form.remove_field(form.field_by_name('eid'))
             self.form_add_subform(form)
@@ -467,8 +461,10 @@
     def render_form(self, entity, peid, rtype, role, i18nctx, **kwargs):
         """fetch and render the form"""
         form = self._cw.vreg['forms'].select('edition', self._cw, entity=entity,
-                                         form_renderer_id='inline',
-                                         mainform=False, copy_nav_params=False)
+                                             form_renderer_id='inline',
+                                             formtype='inlined',
+                                             mainform=False,
+                                             copy_nav_params=False)
         self.add_hiddens(form, entity, peid, rtype, role)
         divid = '%s-%s-%s' % (peid, rtype, entity.eid)
         title = self._cw.pgettext(i18nctx, 'This %s' % entity.e_schema)
--- a/web/views/forms.py	Fri Sep 25 11:30:59 2009 +0200
+++ b/web/views/forms.py	Fri Sep 25 11:49:47 2009 +0200
@@ -547,8 +547,7 @@
                 break
         return result
 
-    def srelations_by_category(self, categories=None, permission=None,
-                               strict=False):
+    def editable_relations(self):
         return ()
 
     def should_display_add_new_relation_link(self, rschema, existant, card):