reorganize code:
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Wed, 27 Jan 2010 09:53:48 +0100
changeset 4387 4aacd6492ef4
parent 4386 cf8842b69379
child 4388 15c6607c4bda
reorganize code: move everything related to inlined forms and generic relation from editviews/editforms where there are used, eg in autoforms
web/views/autoform.py
web/views/editcontroller.py
web/views/editforms.py
web/views/editviews.py
--- a/web/views/autoform.py	Wed Jan 27 09:25:40 2010 +0100
+++ b/web/views/autoform.py	Wed Jan 27 09:53:48 2010 +0100
@@ -9,17 +9,538 @@
 __docformat__ = "restructuredtext en"
 _ = unicode
 
-from logilab.common.decorators import iclassmethod
+from simplejson import dumps
+
+from logilab.mtconverter import xml_escape
+from logilab.common.decorators import iclassmethod, cached
 
-from cubicweb import typed_eid
-from cubicweb.web import stdmsgs, uicfg, form, \
-     formwidgets as fw, formfields as ff
-from cubicweb.web.views import forms, editforms, editviews
+from cubicweb import typed_eid, neg_role, uilib
+from cubicweb.schema import display_name
+from cubicweb.view import EntityView
+from cubicweb.selectors import (
+    match_kwargs, match_form_params, non_final_entity,
+    specified_etype_implements)
+from cubicweb.web import stdmsgs, uicfg, eid_param, \
+     form as f, formwidgets as fw, formfields as ff
+from cubicweb.web.views import forms
 
 _AFS = uicfg.autoform_section
 _AFFK = uicfg.autoform_field_kwargs
 
 
+# inlined form handling ########################################################
+
+class InlinedFormField(ff.Field):
+    def __init__(self, view=None, **kwargs):
+        if view.role == 'object':
+            fieldset = u'%s_object%s' % view.rtype
+        else:
+            fieldset = view.rtype
+        #kwargs.setdefault('fieldset', fieldset)
+        kwargs.setdefault('label', None)
+        super(InlinedFormField, self).__init__(name=view.rtype, role=view.role,
+                                               eidparam=True, **kwargs)
+        self.view = view
+
+    def render(self, form, renderer):
+        """render this field, which is part of form, using the given form
+        renderer
+        """
+        view = self.view
+        i18nctx = 'inlined:%s.%s.%s' % (form.edited_entity.e_schema,
+                                        view.rtype, view.role)
+        return u'<div class="inline-%s-%s-slot">%s</div>' % (
+            view.rtype, view.role,
+            view.render(i18nctx=i18nctx, row=view.cw_row, col=view.cw_col))
+
+    def form_init(self, form):
+        """method called before by build_context to trigger potential field
+        initialization requiring the form instance
+        """
+        if self.view.form:
+            self.view.form.build_context(form.formvalues)
+
+    @property
+    def needs_multipart(self):
+        if self.view.form:
+            # take a look at inlined forms to check (recursively) if they need
+            # multipart handling.
+            return self.view.form.needs_multipart
+        return False
+
+    def has_been_modified(self, form):
+        return False
+
+    def process_posted(self, form):
+        pass # handled by the subform
+
+
+class InlineEntityEditionFormView(f.FormViewMixIn, EntityView):
+    """
+    :attr peid: the parent entity's eid hosting the inline form
+    :attr rtype: the relation bridging `etype` and `peid`
+    :attr role: the role played by the `peid` in the relation
+    :attr pform: the parent form where this inlined form is being displayed
+    """
+    __regid__ = 'inline-edition'
+    __select__ = non_final_entity() & match_kwargs('peid', 'rtype')
+
+    _select_attrs = ('peid', 'rtype', 'role', 'pform', 'etype')
+    removejs = "removeInlinedEntity('%s', '%s', '%s')"
+
+    def __init__(self, *args, **kwargs):
+        for attr in self._select_attrs:
+            setattr(self, attr, kwargs.pop(attr, None))
+        super(InlineEntityEditionFormView, self).__init__(*args, **kwargs)
+
+    def _entity(self):
+        assert self.cw_row is not None, self
+        return self.cw_rset.get_entity(self.cw_row, self.cw_col)
+
+    @property
+    @cached
+    def form(self):
+        entity = self._entity()
+        form = self._cw.vreg['forms'].select('edition', self._cw,
+                                             entity=entity,
+                                             formtype='inlined',
+                                             form_renderer_id='inline',
+                                             copy_nav_params=False,
+                                             mainform=False,
+                                             parent_form=self.pform,
+                                             **self.cw_extra_kwargs)
+        if self.pform is None:
+            form.restore_previous_post(form.session_key())
+        #assert form.parent_form
+        self.add_hiddens(form, entity)
+        return form
+
+    def cell_call(self, row, col, i18nctx, **kwargs):
+        """
+        :param peid: the parent entity's eid hosting the inline form
+        :param rtype: the relation bridging `etype` and `peid`
+        :param role: the role played by the `peid` in the relation
+        """
+        entity = self._entity()
+        divonclick = "restoreInlinedEntity('%s', '%s', '%s')" % (
+            self.peid, self.rtype, entity.eid)
+        self.render_form(i18nctx, divonclick=divonclick, **kwargs)
+
+    def render_form(self, i18nctx, **kwargs):
+        """fetch and render the form"""
+        entity = self._entity()
+        divid = '%s-%s-%s' % (self.peid, self.rtype, entity.eid)
+        title = self.form_title(entity, i18nctx)
+        removejs = self.removejs and self.removejs % (
+            self.peid, self.rtype, entity.eid)
+        countkey = '%s_count' % self.rtype
+        try:
+            self._cw.data[countkey] += 1
+        except KeyError:
+            self._cw.data[countkey] = 1
+        self.w(self.form.render(
+            divid=divid, title=title, removejs=removejs, i18nctx=i18nctx,
+            counter=self._cw.data[countkey] , **kwargs))
+
+    def form_title(self, entity, i18nctx):
+        return self._cw.pgettext(i18nctx, entity.__regid__)
+
+    def add_hiddens(self, form, entity):
+        """to ease overriding (see cubes.vcsfile.views.forms for instance)"""
+        iid = 'rel-%s-%s-%s' % (self.peid, self.rtype, entity.eid)
+        #  * str(self.rtype) in case it's a schema object
+        #  * neged_role() since role is the for parent entity, we want the role
+        #    of the inlined entity
+        form.add_hidden(name=str(self.rtype), value=self.peid,
+                        role=neg_role(self.role), eidparam=True, id=iid)
+
+    def keep_entity(self, form, entity):
+        if not entity.has_eid():
+            return True
+        # are we regenerating form because of a validation error ?
+        if form.form_previous_values:
+            cdvalues = self._cw.list_form_param(eid_param(self.rtype, self.peid),
+                                                form.form_previous_values)
+            if unicode(entity.eid) not in cdvalues:
+                return False
+        return True
+
+
+class InlineEntityCreationFormView(InlineEntityEditionFormView):
+    """
+    :attr etype: the entity type being created in the inline form
+    """
+    __regid__ = 'inline-creation'
+    __select__ = (match_kwargs('peid', 'rtype')
+                  & specified_etype_implements('Any'))
+
+    @property
+    def removejs(self):
+        entity = self._entity()
+        card = entity.e_schema.rdef(self.rtype, neg_role(self.role)).role_cardinality(self.role)
+        # when one is adding an inline entity for a relation of a single card,
+        # the 'add a new xxx' link disappears. If the user then cancel the addition,
+        # we have to make this link appears back. This is done by giving add new link
+        # id to removeInlineForm.
+        if card not in '?1':
+            return "removeInlineForm('%%s', '%%s', '%s', '%%s')" % self.role
+        divid = "addNew%s%s%s:%s" % (
+            self.etype, self.rtype, self.role, self.peid)
+        return "removeInlineForm('%%s', '%%s', '%s', '%%s', '%s')" % (
+            self.role, divid)
+
+    @cached
+    def _entity(self):
+        try:
+            cls = self._cw.vreg['etypes'].etype_class(self.etype)
+        except:
+            self.w(self._cw._('no such entity type %s') % etype)
+            return
+        entity = cls(self._cw)
+        entity.eid = self._cw.varmaker.next()
+        return entity
+
+    def call(self, i18nctx, **kwargs):
+        self.render_form(i18nctx, **kwargs)
+
+
+class InlineAddNewLinkView(InlineEntityCreationFormView):
+    """
+    :attr card: the cardinality of the relation according to role of `peid`
+    """
+    __regid__ = 'inline-addnew-link'
+    __select__ = (match_kwargs('peid', 'rtype')
+                  & specified_etype_implements('Any'))
+
+    _select_attrs = InlineEntityCreationFormView._select_attrs + ('card',)
+    form = None # no actual form wrapped
+
+    def call(self, i18nctx, **kwargs):
+        self._cw.set_varmaker()
+        divid = "addNew%s%s%s:%s" % (self.etype, self.rtype, self.role, self.peid)
+        self.w(u'<div class="inlinedform" id="%s" cubicweb:limit="true">'
+          % divid)
+        js = "addInlineCreationForm('%s', '%s', '%s', '%s', '%s')" % (
+            self.peid, self.etype, self.rtype, self.role, i18nctx)
+        if self.pform.should_hide_add_new_relation_link(self.rtype, self.card):
+            js = "toggleVisibility('%s'); %s" % (divid, js)
+        __ = self._cw.pgettext
+        self.w(u'<a class="addEntity" id="add%s:%slink" href="javascript: %s" >+ %s.</a>'
+          % (self.rtype, self.peid, js, __(i18nctx, 'add a %s' % self.etype)))
+        self.w(u'</div>')
+
+
+# generic relations handling ##################################################
+
+def relation_id(eid, rtype, role, reid):
+    """return an identifier for a relation between two entities"""
+    if role == 'subject':
+        return u'%s:%s:%s' % (eid, rtype, reid)
+    return u'%s:%s:%s' % (reid, rtype, eid)
+
+def toggleable_relation_link(eid, nodeid, label='x'):
+    """return javascript snippet to delete/undelete a relation between two
+    entities
+    """
+    js = u"javascript: togglePendingDelete('%s', %s);" % (
+        nodeid, xml_escape(dumps(eid)))
+    return u'[<a class="handle" href="%s" id="handle%s">%s</a>]' % (
+        js, nodeid, label)
+
+
+def get_pending_inserts(req, eid=None):
+    """shortcut to access req's pending_insert entry
+
+    This is where are stored relations being added while editing
+    an entity. This used to be stored in a temporary cookie.
+    """
+    pending = req.get_session_data('pending_insert') or ()
+    return ['%s:%s:%s' % (subj, rel, obj) for subj, rel, obj in pending
+            if eid is None or eid in (subj, obj)]
+
+def get_pending_deletes(req, eid=None):
+    """shortcut to access req's pending_delete entry
+
+    This is where are stored relations being removed while editing
+    an entity. This used to be stored in a temporary cookie.
+    """
+    pending = req.get_session_data('pending_delete') or ()
+    return ['%s:%s:%s' % (subj, rel, obj) for subj, rel, obj in pending
+            if eid is None or eid in (subj, obj)]
+
+def parse_relations_descr(rdescr):
+    """parse a string describing some relations, in the form
+    subjeids:rtype:objeids
+    where subjeids and objeids are eids separeted by a underscore
+
+    return an iterator on (subject eid, relation type, object eid) found
+    """
+    for rstr in rdescr:
+        subjs, rtype, objs = rstr.split(':')
+        for subj in subjs.split('_'):
+            for obj in objs.split('_'):
+                yield typed_eid(subj), rtype, typed_eid(obj)
+
+def delete_relations(req, rdefs):
+    """delete relations from the repository"""
+    # FIXME convert to using the syntax subject:relation:eids
+    execute = req.execute
+    for subj, rtype, obj in parse_relations_descr(rdefs):
+        rql = 'DELETE X %s Y where X eid %%(x)s, Y eid %%(y)s' % rtype
+        execute(rql, {'x': subj, 'y': obj}, ('x', 'y'))
+    req.set_message(req._('relations deleted'))
+
+def insert_relations(req, rdefs):
+    """insert relations into the repository"""
+    execute = req.execute
+    for subj, rtype, obj in parse_relations_descr(rdefs):
+        rql = 'SET X %s Y where X eid %%(x)s, Y eid %%(y)s' % rtype
+        execute(rql, {'x': subj, 'y': obj}, ('x', 'y'))
+
+
+class GenericRelationsWidget(fw.FieldWidget):
+
+    def render(self, form, field, renderer):
+        stream = []
+        w = stream.append
+        req = form._cw
+        _ = req._
+        __ = _
+        eid = form.edited_entity.eid
+        w(u'<table id="relatedEntities">')
+        for rschema, role, related in field.relations_table(form):
+            # already linked entities
+            if related:
+                w(u'<tr><th class="labelCol">%s</th>' % rschema.display_name(req, role))
+                w(u'<td>')
+                w(u'<ul>')
+                for viewparams in related:
+                    w(u'<li class="invisible">%s<div id="span%s" class="%s">%s</div></li>'
+                      % (viewparams[1], viewparams[0], viewparams[2], viewparams[3]))
+                if not form.force_display and form.maxrelitems < len(related):
+                    link = (u'<span class="invisible">'
+                            '[<a href="javascript: window.location.href+=\'&amp;__force_display=1\'">%s</a>]'
+                            '</span>' % _('view all'))
+                    w(u'<li class="invisible">%s</li>' % link)
+                w(u'</ul>')
+                w(u'</td>')
+                w(u'</tr>')
+        pendings = list(field.restore_pending_inserts(form))
+        if not pendings:
+            w(u'<tr><th>&#160;</th><td>&#160;</td></tr>')
+        else:
+            for row in pendings:
+                # soon to be linked to entities
+                w(u'<tr id="tr%s">' % row[1])
+                w(u'<th>%s</th>' % row[3])
+                w(u'<td>')
+                w(u'<a class="handle" title="%s" href="%s">[x]</a>' %
+                  (_('cancel this insert'), row[2]))
+                w(u'<a id="a%s" class="editionPending" href="%s">%s</a>'
+                  % (row[1], row[4], xml_escape(row[5])))
+                w(u'</td>')
+                w(u'</tr>')
+        w(u'<tr id="relationSelectorRow_%s" class="separator">' % eid)
+        w(u'<th class="labelCol">')
+        w(u'<select id="relationSelector_%s" tabindex="%s" '
+          'onchange="javascript:showMatchingSelect(this.options[this.selectedIndex].value,%s);">'
+          % (eid, req.next_tabindex(), xml_escape(dumps(eid))))
+        w(u'<option value="">%s</option>' % _('select a relation'))
+        for i18nrtype, rschema, role in field.relations:
+            # more entities to link to
+            w(u'<option value="%s_%s">%s</option>' % (rschema, role, i18nrtype))
+        w(u'</select>')
+        w(u'</th>')
+        w(u'<td id="unrelatedDivs_%s"></td>' % eid)
+        w(u'</tr>')
+        w(u'</table>')
+        return '\n'.join(stream)
+
+
+class GenericRelationsField(ff.Field):
+    widget = GenericRelationsWidget
+
+    def __init__(self, relations, name='_cw_generic_field', **kwargs):
+        assert relations
+        kwargs['eidparam'] = True
+        super(GenericRelationsField, self).__init__(name, **kwargs)
+        self.relations = relations
+
+    def process_posted(self, form):
+        todelete = get_pending_deletes(form._cw)
+        if todelete:
+            delete_relations(form._cw, todelete)
+        toinsert = get_pending_inserts(form._cw)
+        if toinsert:
+            insert_relations(form._cw, toinsert)
+        return ()
+
+    def relations_table(self, form):
+        """yiels 3-tuples (rtype, role, related_list)
+        where <related_list> itself a list of :
+          - node_id (will be the entity element's DOM id)
+          - appropriate javascript's togglePendingDelete() function call
+          - status 'pendingdelete' or ''
+          - oneline view of related entity
+        """
+        entity = form.edited_entity
+        pending_deletes = get_pending_deletes(form._cw, entity.eid)
+        for label, rschema, role in self.relations:
+            related = []
+            if entity.has_eid():
+                rset = entity.related(rschema, role, limit=form.related_limit)
+                if rschema.has_perm(form._cw, 'delete'):
+                    toggleable_rel_link_func = toggleable_relation_link
+                else:
+                    toggleable_rel_link_func = lambda x, y, z: u''
+                for row in xrange(rset.rowcount):
+                    nodeid = relation_id(entity.eid, rschema, role,
+                                         rset[row][0])
+                    if nodeid in pending_deletes:
+                        status, label = u'pendingDelete', '+'
+                    else:
+                        status, label = u'', 'x'
+                    dellink = toggleable_rel_link_func(entity.eid, nodeid, label)
+                    eview = form._cw.view('oneline', rset, row=row)
+                    related.append((nodeid, dellink, status, eview))
+            yield (rschema, role, related)
+
+    def restore_pending_inserts(self, form):
+        """used to restore edition page as it was before clicking on
+        'search for <some entity type>'
+        """
+        entity = form.edited_entity
+        pending_inserts = set(get_pending_inserts(form._cw, form.edited_entity.eid))
+        for pendingid in pending_inserts:
+            eidfrom, rtype, eidto = pendingid.split(':')
+            if typed_eid(eidfrom) == entity.eid: # subject
+                label = display_name(form._cw, rtype, 'subject',
+                                     entity.__regid__)
+                reid = eidto
+            else:
+                label = display_name(form._cw, rtype, 'object',
+                                     entity.__regid__)
+                reid = eidfrom
+            jscall = "javascript: cancelPendingInsert('%s', 'tr', null, %s);" \
+                     % (pendingid, entity.eid)
+            rset = form._cw.eid_rset(reid)
+            eview = form._cw.view('text', rset, row=0)
+            # XXX find a clean way to handle baskets
+            if rset.description[0][0] == 'Basket':
+                eview = '%s (%s)' % (eview, display_name(form._cw, 'Basket'))
+            yield rtype, pendingid, jscall, label, reid, eview
+
+
+class UnrelatedDivs(EntityView):
+    __regid__ = 'unrelateddivs'
+    __select__ = match_form_params('relation')
+
+    def cell_call(self, row, col):
+        entity = self.cw_rset.get_entity(row, col)
+        relname, role = self._cw.form.get('relation').rsplit('_', 1)
+        rschema = self._cw.vreg.schema.rschema(relname)
+        hidden = 'hidden' in self._cw.form
+        is_cell = 'is_cell' in self._cw.form
+        self.w(self.build_unrelated_select_div(entity, rschema, role,
+                                               is_cell=is_cell, hidden=hidden))
+
+    def build_unrelated_select_div(self, entity, rschema, role,
+                                   is_cell=False, hidden=True):
+        options = []
+        divid = 'div%s_%s_%s' % (rschema.type, role, entity.eid)
+        selectid = 'select%s_%s_%s' % (rschema.type, role, entity.eid)
+        if rschema.symetric or role == 'subject':
+            targettypes = rschema.objects(entity.e_schema)
+            etypes = '/'.join(sorted(etype.display_name(self._cw) for etype in targettypes))
+        else:
+            targettypes = rschema.subjects(entity.e_schema)
+            etypes = '/'.join(sorted(etype.display_name(self._cw) for etype in targettypes))
+        etypes = uilib.cut(etypes, self._cw.property_value('navigation.short-line-size'))
+        options.append('<option>%s %s</option>' % (self._cw._('select a'), etypes))
+        options += self._get_select_options(entity, rschema, role)
+        options += self._get_search_options(entity, rschema, role, targettypes)
+        if 'Basket' in self._cw.vreg.schema: # XXX
+            options += self._get_basket_options(entity, rschema, role, targettypes)
+        relname, role = self._cw.form.get('relation').rsplit('_', 1)
+        return u"""\
+<div class="%s" id="%s">
+  <select id="%s" onchange="javascript: addPendingInsert(this.options[this.selectedIndex], %s, %s, '%s');">
+    %s
+  </select>
+</div>
+""" % (hidden and 'hidden' or '', divid, selectid,
+       xml_escape(dumps(entity.eid)), is_cell and 'true' or 'null', relname,
+       '\n'.join(options))
+
+    def _get_select_options(self, entity, rschema, role):
+        """add options to search among all entities of each possible type"""
+        options = []
+        pending_inserts = get_pending_inserts(self._cw, entity.eid)
+        rtype = rschema.type
+        form = self._cw.vreg['forms'].select('edition', self._cw, entity=entity)
+        field = form.field_by_name(rschema, role, entity.e_schema)
+        limit = self._cw.property_value('navigation.combobox-limit')
+        # NOTE: expect 'limit' arg on choices method of relation field
+        for eview, reid in field.vocabulary(form, limit=limit):
+            if reid is None:
+                if eview: # skip blank value
+                    options.append('<option class="separator">-- %s --</option>'
+                                   % xml_escape(eview))
+            elif reid != ff.INTERNAL_FIELD_VALUE:
+                optionid = relation_id(entity.eid, rtype, role, reid)
+                if optionid not in pending_inserts:
+                    # prefix option's id with letters to make valid XHTML wise
+                    options.append('<option id="id%s" value="%s">%s</option>' %
+                                   (optionid, reid, xml_escape(eview)))
+        return options
+
+    def _get_search_options(self, entity, rschema, role, targettypes):
+        """add options to search among all entities of each possible type"""
+        options = []
+        _ = self._cw._
+        for eschema in targettypes:
+            mode = '%s:%s:%s:%s' % (role, entity.eid, rschema.type, eschema)
+            url = self._cw.build_url(entity.rest_path(), vid='search-associate',
+                                 __mode=mode)
+            options.append((eschema.display_name(self._cw),
+                            '<option value="%s">%s %s</option>' % (
+                xml_escape(url), _('Search for'), eschema.display_name(self._cw))))
+        return [o for l, o in sorted(options)]
+
+    # XXX move this out
+    def _get_basket_options(self, entity, rschema, role, targettypes):
+        options = []
+        rtype = rschema.type
+        _ = self._cw._
+        for basketeid, basketname in self._get_basket_links(self._cw.user.eid,
+                                                            role, targettypes):
+            optionid = relation_id(entity.eid, rtype, role, basketeid)
+            options.append('<option id="%s" value="%s">%s %s</option>' % (
+                optionid, basketeid, _('link to each item in'), xml_escape(basketname)))
+        return options
+
+    def _get_basket_links(self, ueid, role, targettypes):
+        targettypes = set(targettypes)
+        for basketeid, basketname, elements in self._get_basket_info(ueid):
+            baskettypes = elements.column_types(0)
+            # if every elements in the basket can be attached to the
+            # edited entity
+            if baskettypes & targettypes:
+                yield basketeid, basketname
+
+    def _get_basket_info(self, ueid):
+        basketref = []
+        basketrql = 'Any B,N WHERE B is Basket, B owned_by U, U eid %(x)s, B name N'
+        basketresultset = self._cw.execute(basketrql, {'x': ueid}, 'x')
+        for result in basketresultset:
+            basketitemsrql = 'Any X WHERE X in_basket B, B eid %(x)s'
+            rset = self._cw.execute(basketitemsrql, {'x': result[0]}, 'x')
+            basketref.append((result[0], result[1], rset))
+        return basketref
+
+
+# The automatic entity form ####################################################
+
 class AutomaticEntityForm(forms.EntityFieldsForm):
     """base automatic form to edit any entity.
 
@@ -54,7 +575,7 @@
         """
         try:
             return super(AutomaticEntityForm, cls_or_self).field_by_name(name, role, eschema)
-        except form.FieldNotFound:
+        except f.FieldNotFound:
             if name == '_cw_generic_field' and not isinstance(cls_or_self, type):
                 return cls_or_self._generic_relations_field()
             raise
@@ -70,13 +591,13 @@
             try:
                 self.field_by_name(str(rtype), role)
                 continue # explicitly specified
-            except form.FieldNotFound:
+            except f.FieldNotFound:
                 # has to be guessed
                 try:
                     field = self.field_by_name(str(rtype), role,
                                                eschema=entity.e_schema)
                     self.fields.append(field)
-                except form.FieldNotFound:
+                except f.FieldNotFound:
                     # meta attribute such as <attr>_format
                     continue
         if self.formtype == 'main':
@@ -97,7 +618,7 @@
                 '_cw_generic_field' in self.display_fields):
                 try:
                     field = self.field_by_name('_cw_generic_field')
-                except form.FieldNotFound:
+                except f.FieldNotFound:
                     # no editable relation
                     pass
                 else:
@@ -141,11 +662,11 @@
         except AttributeError:
             srels_by_cat = self.editable_relations()
         if not srels_by_cat:
-            raise form.FieldNotFound('_cw_generic_field')
+            raise f.FieldNotFound('_cw_generic_field')
         fieldset = u'%s :' % self._cw.__('This %s' % self.edited_entity.e_schema)
         fieldset = fieldset.capitalize()
-        return editviews.GenericRelationsField(self.editable_relations(),
-                                               fieldset=fieldset, label=None)
+        return GenericRelationsField(self.editable_relations(),
+                                     fieldset=fieldset, label=None)
 
     def _inlined_form_view_field(self, view):
         # XXX allow more customization
@@ -153,7 +674,7 @@
                                  view.role, view.etype)
         if kwargs is None:
             kwargs = {}
-        return editforms.InlinedFormField(view=view, **kwargs)
+        return InlinedFormField(view=view, **kwargs)
 
     # methods mapping edited entity relations to fields in the form ############
 
--- a/web/views/editcontroller.py	Wed Jan 27 09:25:40 2010 +0100
+++ b/web/views/editcontroller.py	Wed Jan 27 09:53:48 2010 +0100
@@ -13,8 +13,7 @@
 
 from cubicweb import Binary, ValidationError, typed_eid
 from cubicweb.web import INTERNAL_FIELD_VALUE, RequestError, NothingToEdit, ProcessFormError
-from cubicweb.web.views.editviews import delete_relations, insert_relations
-from cubicweb.web.views.basecontrollers import ViewController
+from cubicweb.web.views import basecontrollers, autoform
 
 
 class RqlQuery(object):
@@ -44,7 +43,7 @@
         return rql
 
 
-class EditController(ViewController):
+class EditController(basecontrollers.ViewController):
     __regid__ = 'edit'
 
     def publish(self, rset=None):
@@ -99,13 +98,13 @@
         if req.form.has_key('__delete'):
             todelete = req.list_form_param('__delete', req.form, pop=True)
             if todelete:
-                delete_relations(self._cw, todelete)
+                autoform.delete_relations(self._cw, todelete)
         if req.form.has_key('__insert'):
             warn('[3.6] stop using __insert, support will be removed',
                  DeprecationWarning)
             toinsert = req.list_form_param('__insert', req.form, pop=True)
             if toinsert:
-                insert_relations(self._cw, toinsert)
+                autoform.insert_relations(self._cw, toinsert)
         self._cw.remove_pending_operations()
         if self.errors:
             errors = dict((f.name, unicode(ex)) for f, ex in self.errors)
@@ -174,7 +173,7 @@
         if formparams.has_key('__delete'):
             # XXX deprecate?
             todelete = self._cw.list_form_param('__delete', formparams, pop=True)
-            delete_relations(self._cw, todelete)
+            autoform.delete_relations(self._cw, todelete)
         if formparams.has_key('__cloned_eid'):
             entity.copy_relations(typed_eid(formparams['__cloned_eid']))
         if is_main_entity: # only execute linkto for the main entity
--- a/web/views/editforms.py	Wed Jan 27 09:25:40 2010 +0100
+++ b/web/views/editforms.py	Wed Jan 27 09:53:48 2010 +0100
@@ -16,7 +16,6 @@
 from logilab.mtconverter import xml_escape
 from logilab.common.decorators import cached
 
-from cubicweb import neg_role
 from cubicweb.selectors import (match_kwargs, one_line_rset, non_final_entity,
                                 specified_etype_implements, yes)
 from cubicweb.view import EntityView
@@ -83,204 +82,6 @@
         w(form.render())
 
 
-class ClickAndEditFormView(FormViewMixIn, EntityView):
-    """form used to permit ajax edition of a relation or attribute of an entity
-    in a view, if logged user have the permission to edit it.
-
-    (double-click on the field to see an appropriate edition widget).
-    """
-    __regid__ = 'doreledit'
-    __select__ = non_final_entity() & match_kwargs('rtype')
-    # FIXME editableField class could be toggleable from userprefs
-
-    _onclick = u"showInlineEditionForm(%(eid)s, '%(rtype)s', '%(divid)s')"
-    _onsubmit = ("return inlineValidateRelationForm('%(rtype)s', '%(role)s', '%(eid)s', "
-                 "'%(divid)s', %(reload)s, '%(vid)s', '%(default)s', '%(lzone)s');")
-    _cancelclick = "hideInlineEdit(%s,\'%s\',\'%s\')"
-    _defaultlandingzone = (u'<img title="%(msg)s" src="data/pen_icon.png" '
-                           'alt="%(msg)s"/>')
-    _landingzonemsg = _('click to edit this field')
-    # default relation vids according to cardinality
-    _one_rvid = 'incontext'
-    _many_rvid = 'csv'
-
-
-    def cell_call(self, row, col, rtype=None, role='subject',
-                  reload=False,      # controls reloading the whole page after change
-                  rvid=None,         # vid to be applied to other side of rtype (non final relations only)
-                  default=None,      # default value
-                  landing_zone=None  # prepend value with a separate html element to click onto
-                                     # (esp. needed when values are links)
-                  ):
-        """display field to edit entity's `rtype` relation on click"""
-        assert rtype
-        assert role in ('subject', 'object'), '%s is not an acceptable role value' % role
-        self._cw.add_js('cubicweb.edition.js')
-        self._cw.add_css('cubicweb.form.css')
-        if default is None:
-            default = xml_escape(self._cw._('<no value>'))
-        schema = self._cw.vreg.schema
-        entity = self.cw_rset.get_entity(row, col)
-        rschema = schema.rschema(rtype)
-        lzone = self._build_landing_zone(landing_zone)
-        # compute value, checking perms, build form
-        if rschema.final:
-            form = self._build_form(entity, rtype, role, 'base', default, reload, lzone)
-            if not self.should_edit_attribute(entity, rschema, role, form):
-                self.w(entity.printable_value(rtype))
-                return
-            value = entity.printable_value(rtype) or default
-        else:
-            rvid = self._compute_best_vid(entity.e_schema, rschema, role)
-            rset = entity.related(rtype, role)
-            if rset:
-                value = self._cw.view(rvid, rset)
-            else:
-                value = default
-            if not self.should_edit_relation(entity, rschema, role, rvid):
-                if rset:
-                    self.w(value)
-                return
-            # XXX do we really have to give lzone twice?
-            form = self._build_form(entity, rtype, role, 'base', default, reload, lzone,
-                                    dict(vid=rvid, lzone=lzone))
-        field = form.field_by_name(rtype, role, entity.e_schema)
-        form.append_field(field)
-        self.relation_form(lzone, value, form,
-                           self._build_renderer(entity, rtype, role))
-
-    def should_edit_attribute(self, entity, rschema, role, form):
-        rtype = str(rschema)
-        ttype = rschema.targets(entity.__regid__, role)[0]
-        afs = uicfg.autoform_section.etype_get(entity.__regid__, rtype, role, ttype)
-        if 'main_hidden' in afs or not entity.has_perm('update'):
-            return False
-        try:
-            form.field_by_name(rtype, role, entity.e_schema)
-        except FieldNotFound:
-            return False
-        return True
-
-    def should_edit_relation(self, entity, rschema, role, rvid):
-        if ((role == 'subject' and not rschema.has_perm(self._cw, 'add',
-                                                        fromeid=entity.eid))
-            or
-            (role == 'object' and not rschema.has_perm(self._cw, 'add',
-                                                       toeid=entity.eid))):
-            return False
-        return True
-
-    def relation_form(self, lzone, value, form, renderer):
-        """xxx-reledit div (class=field)
-              +-xxx div (class="editableField")
-              |   +-landing zone
-              +-xxx-value div
-              +-xxx-form div
-        """
-        w = self.w
-        divid = form.event_args['divid']
-        w(u'<div id="%s-reledit" class="field" '
-          u'onmouseout="addElementClass(jQuery(\'#%s\'), \'hidden\')" '
-          u'onmouseover="removeElementClass(jQuery(\'#%s\'), \'hidden\')">'
-          % (divid, divid, divid))
-        w(u'<div id="%s-value" class="editableFieldValue">%s</div>' % (divid, value))
-        w(form.render(renderer=renderer))
-        w(u'<div id="%s" class="editableField hidden" onclick="%s" title="%s">' % (
-                divid, xml_escape(self._onclick % form.event_args),
-                self._cw._(self._landingzonemsg)))
-        w(lzone)
-        w(u'</div>')
-        w(u'</div>')
-
-    def _compute_best_vid(self, eschema, rschema, role):
-        dispctrl = _pvdc.etype_get(eschema, rschema, role)
-        if dispctrl.get('rvid'):
-            return dispctrl['rvid']
-        if eschema.rdef(rschema, role).role_cardinality(role) in '+*':
-            return self._many_rvid
-        return self._one_rvid
-
-    def _build_landing_zone(self, lzone):
-        return lzone or self._defaultlandingzone % {
-            'msg': xml_escape(self._cw._(self._landingzonemsg))}
-
-    def _build_renderer(self, entity, rtype, role):
-        return self._cw.vreg['formrenderers'].select(
-            'base', self._cw, entity=entity, display_label=False,
-            display_help=False, table_class='',
-            button_bar_class='buttonbar', display_progress_div=False)
-
-    def _build_args(self, entity, rtype, role, formid, default, reload, lzone,
-                    extradata=None):
-        divid = '%s-%s-%s' % (rtype, role, entity.eid)
-        event_args = {'divid' : divid, 'eid' : entity.eid, 'rtype' : rtype,
-                      'reload' : dumps(reload), 'default' : default, 'role' : role, 'vid' : u'',
-                      'lzone' : lzone}
-        if extradata:
-            event_args.update(extradata)
-        return divid, event_args
-
-    def _build_form(self, entity, rtype, role, formid, default, reload, lzone,
-                  extradata=None, **formargs):
-        divid, event_args = self._build_args(entity, rtype, role, formid, default,
-                                      reload, lzone, extradata)
-        onsubmit = self._onsubmit % event_args
-        cancelclick = self._cancelclick % (entity.eid, rtype, divid)
-        form = self._cw.vreg['forms'].select(
-            formid, self._cw, entity=entity, domid='%s-form' % divid,
-            cssstyle='display: none', onsubmit=onsubmit, action='#',
-            form_buttons=[fw.SubmitButton(),
-                          fw.Button(stdmsgs.BUTTON_CANCEL, onclick=cancelclick)],
-            **formargs)
-        form.event_args = event_args
-        return form
-
-
-class DummyForm(object):
-    __slots__ = ('event_args',)
-    def form_render(self, **_args):
-        return u''
-    def render(self, **_args):
-        return u''
-    def append_field(self, *args):
-        pass
-    def field_by_name(self, rtype, role, eschema=None):
-        return None
-
-
-class AutoClickAndEditFormView(ClickAndEditFormView):
-    """same as ClickAndEditFormView but checking if the view *should* be applied
-    by checking uicfg configuration and composite relation property.
-    """
-    __regid__ = 'reledit'
-    _onclick = (u"loadInlineEditionForm(%(eid)s, '%(rtype)s', '%(role)s', "
-                "'%(divid)s', %(reload)s, '%(vid)s', '%(default)s', '%(lzone)s');")
-
-    def should_edit_relation(self, entity, rschema, role, rvid):
-        eschema = entity.e_schema
-        rtype = str(rschema)
-        # XXX check autoform_section. what if 'generic'?
-        dispctrl = _pvdc.etype_get(eschema, rtype, role)
-        vid = dispctrl.get('vid', 'reledit')
-        if vid != 'reledit': # reledit explicitly disabled
-            return False
-        if eschema.rdef(rschema, role).composite == role:
-            return False
-        return super(AutoClickAndEditFormView, self).should_edit_relation(
-            entity, rschema, role, rvid)
-
-    def _build_form(self, entity, rtype, role, formid, default, reload, lzone,
-                  extradata=None, **formargs):
-        _divid, event_args = self._build_args(entity, rtype, role, formid, default,
-                                              reload, lzone, extradata)
-        form = DummyForm()
-        form.event_args = event_args
-        return form
-
-    def _build_renderer(self, entity, rtype, role):
-        pass
-
-
 class EditionFormView(FormViewMixIn, EntityView):
     """display primary entity edition form"""
     __regid__ = 'edition'
@@ -449,203 +250,202 @@
         self.w(form.render(formvid='edition'))
 
 
-# inlined form handling ########################################################
+# click and edit handling ('reledit') ##########################################
 
-class InlinedFormField(ff.Field):
-    def __init__(self, view=None, **kwargs):
-        if view.role == 'object':
-            fieldset = u'%s_object%s' % view.rtype
-        else:
-            fieldset = view.rtype
-        #kwargs.setdefault('fieldset', fieldset)
-        kwargs.setdefault('label', None)
-        super(InlinedFormField, self).__init__(name=view.rtype, role=view.role,
-                                               eidparam=True, **kwargs)
-        self.view = view
+class DummyForm(object):
+    __slots__ = ('event_args',)
+    def form_render(self, **_args):
+        return u''
+    def render(self, **_args):
+        return u''
+    def append_field(self, *args):
+        pass
+    def field_by_name(self, rtype, role, eschema=None):
+        return None
+
 
-    def render(self, form, renderer):
-        """render this field, which is part of form, using the given form
-        renderer
-        """
-        view = self.view
-        i18nctx = 'inlined:%s.%s.%s' % (form.edited_entity.e_schema,
-                                        view.rtype, view.role)
-        return u'<div class="inline-%s-%s-slot">%s</div>' % (
-            view.rtype, view.role,
-            view.render(i18nctx=i18nctx, row=view.cw_row, col=view.cw_col))
+class ClickAndEditFormView(FormViewMixIn, EntityView):
+    """form used to permit ajax edition of a relation or attribute of an entity
+    in a view, if logged user have the permission to edit it.
+
+    (double-click on the field to see an appropriate edition widget).
+    """
+    __regid__ = 'doreledit'
+    __select__ = non_final_entity() & match_kwargs('rtype')
+    # FIXME editableField class could be toggleable from userprefs
 
-    def form_init(self, form):
-        """method called before by build_context to trigger potential field
-        initialization requiring the form instance
-        """
-        if self.view.form:
-            self.view.form.build_context(form.formvalues)
-
-    @property
-    def needs_multipart(self):
-        if self.view.form:
-            # take a look at inlined forms to check (recursively) if they need
-            # multipart handling.
-            return self.view.form.needs_multipart
-        return False
-
-    def has_been_modified(self, form):
-        return False
-
-    def process_posted(self, form):
-        pass # handled by the subform
+    _onclick = u"showInlineEditionForm(%(eid)s, '%(rtype)s', '%(divid)s')"
+    _onsubmit = ("return inlineValidateRelationForm('%(rtype)s', '%(role)s', '%(eid)s', "
+                 "'%(divid)s', %(reload)s, '%(vid)s', '%(default)s', '%(lzone)s');")
+    _cancelclick = "hideInlineEdit(%s,\'%s\',\'%s\')"
+    _defaultlandingzone = (u'<img title="%(msg)s" src="data/pen_icon.png" '
+                           'alt="%(msg)s"/>')
+    _landingzonemsg = _('click to edit this field')
+    # default relation vids according to cardinality
+    _one_rvid = 'incontext'
+    _many_rvid = 'csv'
 
 
-class InlineEntityEditionFormView(FormViewMixIn, EntityView):
-    """
-    :attr peid: the parent entity's eid hosting the inline form
-    :attr rtype: the relation bridging `etype` and `peid`
-    :attr role: the role played by the `peid` in the relation
-    :attr pform: the parent form where this inlined form is being displayed
-    """
-    __regid__ = 'inline-edition'
-    __select__ = non_final_entity() & match_kwargs('peid', 'rtype')
+    def cell_call(self, row, col, rtype=None, role='subject',
+                  reload=False,      # controls reloading the whole page after change
+                  rvid=None,         # vid to be applied to other side of rtype (non final relations only)
+                  default=None,      # default value
+                  landing_zone=None  # prepend value with a separate html element to click onto
+                                     # (esp. needed when values are links)
+                  ):
+        """display field to edit entity's `rtype` relation on click"""
+        assert rtype
+        assert role in ('subject', 'object'), '%s is not an acceptable role value' % role
+        self._cw.add_js('cubicweb.edition.js')
+        self._cw.add_css('cubicweb.form.css')
+        if default is None:
+            default = xml_escape(self._cw._('<no value>'))
+        schema = self._cw.vreg.schema
+        entity = self.cw_rset.get_entity(row, col)
+        rschema = schema.rschema(rtype)
+        lzone = self._build_landing_zone(landing_zone)
+        # compute value, checking perms, build form
+        if rschema.final:
+            form = self._build_form(entity, rtype, role, 'base', default, reload, lzone)
+            if not self.should_edit_attribute(entity, rschema, role, form):
+                self.w(entity.printable_value(rtype))
+                return
+            value = entity.printable_value(rtype) or default
+        else:
+            rvid = self._compute_best_vid(entity.e_schema, rschema, role)
+            rset = entity.related(rtype, role)
+            if rset:
+                value = self._cw.view(rvid, rset)
+            else:
+                value = default
+            if not self.should_edit_relation(entity, rschema, role, rvid):
+                if rset:
+                    self.w(value)
+                return
+            # XXX do we really have to give lzone twice?
+            form = self._build_form(entity, rtype, role, 'base', default, reload, lzone,
+                                    dict(vid=rvid, lzone=lzone))
+        field = form.field_by_name(rtype, role, entity.e_schema)
+        form.append_field(field)
+        self.relation_form(lzone, value, form,
+                           self._build_renderer(entity, rtype, role))
 
-    _select_attrs = ('peid', 'rtype', 'role', 'pform', 'etype')
-    removejs = "removeInlinedEntity('%s', '%s', '%s')"
-
-    def __init__(self, *args, **kwargs):
-        for attr in self._select_attrs:
-            setattr(self, attr, kwargs.pop(attr, None))
-        super(InlineEntityEditionFormView, self).__init__(*args, **kwargs)
+    def should_edit_attribute(self, entity, rschema, role, form):
+        rtype = str(rschema)
+        ttype = rschema.targets(entity.__regid__, role)[0]
+        afs = uicfg.autoform_section.etype_get(entity.__regid__, rtype, role, ttype)
+        if 'main_hidden' in afs or not entity.has_perm('update'):
+            return False
+        try:
+            form.field_by_name(rtype, role, entity.e_schema)
+        except FieldNotFound:
+            return False
+        return True
 
-    def _entity(self):
-        assert self.cw_row is not None, self
-        return self.cw_rset.get_entity(self.cw_row, self.cw_col)
+    def should_edit_relation(self, entity, rschema, role, rvid):
+        if ((role == 'subject' and not rschema.has_perm(self._cw, 'add',
+                                                        fromeid=entity.eid))
+            or
+            (role == 'object' and not rschema.has_perm(self._cw, 'add',
+                                                       toeid=entity.eid))):
+            return False
+        return True
+
+    def relation_form(self, lzone, value, form, renderer):
+        """xxx-reledit div (class=field)
+              +-xxx div (class="editableField")
+              |   +-landing zone
+              +-xxx-value div
+              +-xxx-form div
+        """
+        w = self.w
+        divid = form.event_args['divid']
+        w(u'<div id="%s-reledit" class="field" '
+          u'onmouseout="addElementClass(jQuery(\'#%s\'), \'hidden\')" '
+          u'onmouseover="removeElementClass(jQuery(\'#%s\'), \'hidden\')">'
+          % (divid, divid, divid))
+        w(u'<div id="%s-value" class="editableFieldValue">%s</div>' % (divid, value))
+        w(form.render(renderer=renderer))
+        w(u'<div id="%s" class="editableField hidden" onclick="%s" title="%s">' % (
+                divid, xml_escape(self._onclick % form.event_args),
+                self._cw._(self._landingzonemsg)))
+        w(lzone)
+        w(u'</div>')
+        w(u'</div>')
 
-    @property
-    @cached
-    def form(self):
-        entity = self._entity()
-        form = self._cw.vreg['forms'].select('edition', self._cw,
-                                             entity=entity,
-                                             formtype='inlined',
-                                             form_renderer_id='inline',
-                                             copy_nav_params=False,
-                                             mainform=False,
-                                             parent_form=self.pform,
-                                             **self.cw_extra_kwargs)
-        if self.pform is None:
-            form.restore_previous_post(form.session_key())
-        #assert form.parent_form
-        self.add_hiddens(form, entity)
+    def _compute_best_vid(self, eschema, rschema, role):
+        dispctrl = _pvdc.etype_get(eschema, rschema, role)
+        if dispctrl.get('rvid'):
+            return dispctrl['rvid']
+        if eschema.rdef(rschema, role).role_cardinality(role) in '+*':
+            return self._many_rvid
+        return self._one_rvid
+
+    def _build_landing_zone(self, lzone):
+        return lzone or self._defaultlandingzone % {
+            'msg': xml_escape(self._cw._(self._landingzonemsg))}
+
+    def _build_renderer(self, entity, rtype, role):
+        return self._cw.vreg['formrenderers'].select(
+            'base', self._cw, entity=entity, display_label=False,
+            display_help=False, table_class='',
+            button_bar_class='buttonbar', display_progress_div=False)
+
+    def _build_args(self, entity, rtype, role, formid, default, reload, lzone,
+                    extradata=None):
+        divid = '%s-%s-%s' % (rtype, role, entity.eid)
+        event_args = {'divid' : divid, 'eid' : entity.eid, 'rtype' : rtype,
+                      'reload' : dumps(reload), 'default' : default, 'role' : role, 'vid' : u'',
+                      'lzone' : lzone}
+        if extradata:
+            event_args.update(extradata)
+        return divid, event_args
+
+    def _build_form(self, entity, rtype, role, formid, default, reload, lzone,
+                  extradata=None, **formargs):
+        divid, event_args = self._build_args(entity, rtype, role, formid, default,
+                                      reload, lzone, extradata)
+        onsubmit = self._onsubmit % event_args
+        cancelclick = self._cancelclick % (entity.eid, rtype, divid)
+        form = self._cw.vreg['forms'].select(
+            formid, self._cw, entity=entity, domid='%s-form' % divid,
+            cssstyle='display: none', onsubmit=onsubmit, action='#',
+            form_buttons=[fw.SubmitButton(),
+                          fw.Button(stdmsgs.BUTTON_CANCEL, onclick=cancelclick)],
+            **formargs)
+        form.event_args = event_args
         return form
 
-    def cell_call(self, row, col, i18nctx, **kwargs):
-        """
-        :param peid: the parent entity's eid hosting the inline form
-        :param rtype: the relation bridging `etype` and `peid`
-        :param role: the role played by the `peid` in the relation
-        """
-        entity = self._entity()
-        divonclick = "restoreInlinedEntity('%s', '%s', '%s')" % (
-            self.peid, self.rtype, entity.eid)
-        self.render_form(i18nctx, divonclick=divonclick, **kwargs)
 
-    def render_form(self, i18nctx, **kwargs):
-        """fetch and render the form"""
-        entity = self._entity()
-        divid = '%s-%s-%s' % (self.peid, self.rtype, entity.eid)
-        title = self.form_title(entity, i18nctx)
-        removejs = self.removejs and self.removejs % (
-            self.peid, self.rtype, entity.eid)
-        countkey = '%s_count' % self.rtype
-        try:
-            self._cw.data[countkey] += 1
-        except KeyError:
-            self._cw.data[countkey] = 1
-        self.w(self.form.render(
-            divid=divid, title=title, removejs=removejs, i18nctx=i18nctx,
-            counter=self._cw.data[countkey] , **kwargs))
-
-    def form_title(self, entity, i18nctx):
-        return self._cw.pgettext(i18nctx, entity.__regid__)
-
-    def add_hiddens(self, form, entity):
-        """to ease overriding (see cubes.vcsfile.views.forms for instance)"""
-        iid = 'rel-%s-%s-%s' % (self.peid, self.rtype, entity.eid)
-        #  * str(self.rtype) in case it's a schema object
-        #  * neged_role() since role is the for parent entity, we want the role
-        #    of the inlined entity
-        form.add_hidden(name=str(self.rtype), value=self.peid,
-                        role=neg_role(self.role), eidparam=True, id=iid)
-
-    def keep_entity(self, form, entity):
-        if not entity.has_eid():
-            return True
-        # are we regenerating form because of a validation error ?
-        if form.form_previous_values:
-            cdvalues = self._cw.list_form_param(eid_param(self.rtype, self.peid),
-                                                form.form_previous_values)
-            if unicode(entity.eid) not in cdvalues:
-                return False
-        return True
-
-
-class InlineEntityCreationFormView(InlineEntityEditionFormView):
-    """
-    :attr etype: the entity type being created in the inline form
+class AutoClickAndEditFormView(ClickAndEditFormView):
+    """same as ClickAndEditFormView but checking if the view *should* be applied
+    by checking uicfg configuration and composite relation property.
     """
-    __regid__ = 'inline-creation'
-    __select__ = (match_kwargs('peid', 'rtype')
-                  & specified_etype_implements('Any'))
-
-    @property
-    def removejs(self):
-        entity = self._entity()
-        card = entity.e_schema.rdef(self.rtype, neg_role(self.role)).role_cardinality(self.role)
-        # when one is adding an inline entity for a relation of a single card,
-        # the 'add a new xxx' link disappears. If the user then cancel the addition,
-        # we have to make this link appears back. This is done by giving add new link
-        # id to removeInlineForm.
-        if card not in '?1':
-            return "removeInlineForm('%%s', '%%s', '%s', '%%s')" % self.role
-        divid = "addNew%s%s%s:%s" % (
-            self.etype, self.rtype, self.role, self.peid)
-        return "removeInlineForm('%%s', '%%s', '%s', '%%s', '%s')" % (
-            self.role, divid)
+    __regid__ = 'reledit'
+    _onclick = (u"loadInlineEditionForm(%(eid)s, '%(rtype)s', '%(role)s', "
+                "'%(divid)s', %(reload)s, '%(vid)s', '%(default)s', '%(lzone)s');")
 
-    @cached
-    def _entity(self):
-        try:
-            cls = self._cw.vreg['etypes'].etype_class(self.etype)
-        except:
-            self.w(self._cw._('no such entity type %s') % etype)
-            return
-        entity = cls(self._cw)
-        entity.eid = self._cw.varmaker.next()
-        return entity
-
-    def call(self, i18nctx, **kwargs):
-        self.render_form(i18nctx, **kwargs)
-
+    def should_edit_relation(self, entity, rschema, role, rvid):
+        eschema = entity.e_schema
+        rtype = str(rschema)
+        # XXX check autoform_section. what if 'generic'?
+        dispctrl = _pvdc.etype_get(eschema, rtype, role)
+        vid = dispctrl.get('vid', 'reledit')
+        if vid != 'reledit': # reledit explicitly disabled
+            return False
+        if eschema.rdef(rschema, role).composite == role:
+            return False
+        return super(AutoClickAndEditFormView, self).should_edit_relation(
+            entity, rschema, role, rvid)
 
-class InlineAddNewLinkView(InlineEntityCreationFormView):
-    """
-    :attr card: the cardinality of the relation according to role of `peid`
-    """
-    __regid__ = 'inline-addnew-link'
-    __select__ = (match_kwargs('peid', 'rtype')
-                  & specified_etype_implements('Any'))
-
-    _select_attrs = InlineEntityCreationFormView._select_attrs + ('card',)
-    form = None # no actual form wrapped
+    def _build_form(self, entity, rtype, role, formid, default, reload, lzone,
+                  extradata=None, **formargs):
+        _divid, event_args = self._build_args(entity, rtype, role, formid, default,
+                                              reload, lzone, extradata)
+        form = DummyForm()
+        form.event_args = event_args
+        return form
 
-    def call(self, i18nctx, **kwargs):
-        self._cw.set_varmaker()
-        divid = "addNew%s%s%s:%s" % (self.etype, self.rtype, self.role, self.peid)
-        self.w(u'<div class="inlinedform" id="%s" cubicweb:limit="true">'
-          % divid)
-        js = "addInlineCreationForm('%s', '%s', '%s', '%s', '%s')" % (
-            self.peid, self.etype, self.rtype, self.role, i18nctx)
-        if self.pform.should_hide_add_new_relation_link(self.rtype, self.card):
-            js = "toggleVisibility('%s'); %s" % (divid, js)
-        __ = self._cw.pgettext
-        self.w(u'<a class="addEntity" id="add%s:%slink" href="javascript: %s" >+ %s.</a>'
-          % (self.rtype, self.peid, js, __(i18nctx, 'add a %s' % self.etype)))
-        self.w(u'</div>')
+    def _build_renderer(self, entity, rtype, role):
+        pass
+
--- a/web/views/editviews.py	Wed Jan 27 09:25:40 2010 +0100
+++ b/web/views/editviews.py	Wed Jan 27 09:53:48 2010 +0100
@@ -8,36 +8,16 @@
 __docformat__ = "restructuredtext en"
 _ = unicode
 
-from simplejson import dumps
-
 from logilab.common.decorators import cached
 from logilab.mtconverter import xml_escape
 
-from cubicweb import typed_eid, uilib
-from cubicweb.schema import display_name
+from cubicweb import typed_eid
 from cubicweb.view import EntityView
 from cubicweb.selectors import (one_line_rset, non_final_entity,
-                                match_search_state, match_form_params)
-from cubicweb.web import formwidgets as fw, formfields as ff
+                                match_search_state)
 from cubicweb.web.views import baseviews, linksearch_select_url
 
 
-def relation_id(eid, rtype, role, reid):
-    """return an identifier for a relation between two entities"""
-    if role == 'subject':
-        return u'%s:%s:%s' % (eid, rtype, reid)
-    return u'%s:%s:%s' % (reid, rtype, eid)
-
-def toggleable_relation_link(eid, nodeid, label='x'):
-    """return javascript snippet to delete/undelete a relation between two
-    entities
-    """
-    js = u"javascript: togglePendingDelete('%s', %s);" % (
-        nodeid, xml_escape(dumps(eid)))
-    return u'[<a class="handle" href="%s" id="handle%s">%s</a>]' % (
-        js, nodeid, label)
-
-
 class SearchForAssociationView(EntityView):
     """view called by the edition view when the user asks to search for
     something to link to the edited eid
@@ -88,296 +68,6 @@
             entity.view('outofcontext', w=self.w)
 
 
-def get_pending_inserts(req, eid=None):
-    """shortcut to access req's pending_insert entry
-
-    This is where are stored relations being added while editing
-    an entity. This used to be stored in a temporary cookie.
-    """
-    pending = req.get_session_data('pending_insert') or ()
-    return ['%s:%s:%s' % (subj, rel, obj) for subj, rel, obj in pending
-            if eid is None or eid in (subj, obj)]
-
-def get_pending_deletes(req, eid=None):
-    """shortcut to access req's pending_delete entry
-
-    This is where are stored relations being removed while editing
-    an entity. This used to be stored in a temporary cookie.
-    """
-    pending = req.get_session_data('pending_delete') or ()
-    return ['%s:%s:%s' % (subj, rel, obj) for subj, rel, obj in pending
-            if eid is None or eid in (subj, obj)]
-
-def parse_relations_descr(rdescr):
-    """parse a string describing some relations, in the form
-    subjeids:rtype:objeids
-    where subjeids and objeids are eids separeted by a underscore
-
-    return an iterator on (subject eid, relation type, object eid) found
-    """
-    for rstr in rdescr:
-        subjs, rtype, objs = rstr.split(':')
-        for subj in subjs.split('_'):
-            for obj in objs.split('_'):
-                yield typed_eid(subj), rtype, typed_eid(obj)
-
-def delete_relations(req, rdefs):
-    """delete relations from the repository"""
-    # FIXME convert to using the syntax subject:relation:eids
-    execute = req.execute
-    for subj, rtype, obj in parse_relations_descr(rdefs):
-        rql = 'DELETE X %s Y where X eid %%(x)s, Y eid %%(y)s' % rtype
-        execute(rql, {'x': subj, 'y': obj}, ('x', 'y'))
-    req.set_message(req._('relations deleted'))
-
-def insert_relations(req, rdefs):
-    """insert relations into the repository"""
-    execute = req.execute
-    for subj, rtype, obj in parse_relations_descr(rdefs):
-        rql = 'SET X %s Y where X eid %%(x)s, Y eid %%(y)s' % rtype
-        execute(rql, {'x': subj, 'y': obj}, ('x', 'y'))
-
-
-class GenericRelationsWidget(fw.FieldWidget):
-
-    def render(self, form, field, renderer):
-        stream = []
-        w = stream.append
-        req = form._cw
-        _ = req._
-        __ = _
-        eid = form.edited_entity.eid
-        w(u'<table id="relatedEntities">')
-        for rschema, role, related in field.relations_table(form):
-            # already linked entities
-            if related:
-                w(u'<tr><th class="labelCol">%s</th>' % rschema.display_name(req, role))
-                w(u'<td>')
-                w(u'<ul>')
-                for viewparams in related:
-                    w(u'<li class="invisible">%s<div id="span%s" class="%s">%s</div></li>'
-                      % (viewparams[1], viewparams[0], viewparams[2], viewparams[3]))
-                if not form.force_display and form.maxrelitems < len(related):
-                    link = (u'<span class="invisible">'
-                            '[<a href="javascript: window.location.href+=\'&amp;__force_display=1\'">%s</a>]'
-                            '</span>' % _('view all'))
-                    w(u'<li class="invisible">%s</li>' % link)
-                w(u'</ul>')
-                w(u'</td>')
-                w(u'</tr>')
-        pendings = list(field.restore_pending_inserts(form))
-        if not pendings:
-            w(u'<tr><th>&#160;</th><td>&#160;</td></tr>')
-        else:
-            for row in pendings:
-                # soon to be linked to entities
-                w(u'<tr id="tr%s">' % row[1])
-                w(u'<th>%s</th>' % row[3])
-                w(u'<td>')
-                w(u'<a class="handle" title="%s" href="%s">[x]</a>' %
-                  (_('cancel this insert'), row[2]))
-                w(u'<a id="a%s" class="editionPending" href="%s">%s</a>'
-                  % (row[1], row[4], xml_escape(row[5])))
-                w(u'</td>')
-                w(u'</tr>')
-        w(u'<tr id="relationSelectorRow_%s" class="separator">' % eid)
-        w(u'<th class="labelCol">')
-        w(u'<select id="relationSelector_%s" tabindex="%s" '
-          'onchange="javascript:showMatchingSelect(this.options[this.selectedIndex].value,%s);">'
-          % (eid, req.next_tabindex(), xml_escape(dumps(eid))))
-        w(u'<option value="">%s</option>' % _('select a relation'))
-        for i18nrtype, rschema, role in field.relations:
-            # more entities to link to
-            w(u'<option value="%s_%s">%s</option>' % (rschema, role, i18nrtype))
-        w(u'</select>')
-        w(u'</th>')
-        w(u'<td id="unrelatedDivs_%s"></td>' % eid)
-        w(u'</tr>')
-        w(u'</table>')
-        return '\n'.join(stream)
-
-
-class GenericRelationsField(ff.Field):
-    widget = GenericRelationsWidget
-
-    def __init__(self, relations, name='_cw_generic_field', **kwargs):
-        assert relations
-        kwargs['eidparam'] = True
-        super(GenericRelationsField, self).__init__(name, **kwargs)
-        self.relations = relations
-
-    def process_posted(self, form):
-        todelete = get_pending_deletes(form._cw)
-        if todelete:
-            delete_relations(form._cw, todelete)
-        toinsert = get_pending_inserts(form._cw)
-        if toinsert:
-            insert_relations(form._cw, toinsert)
-        return ()
-
-    def relations_table(self, form):
-        """yiels 3-tuples (rtype, role, related_list)
-        where <related_list> itself a list of :
-          - node_id (will be the entity element's DOM id)
-          - appropriate javascript's togglePendingDelete() function call
-          - status 'pendingdelete' or ''
-          - oneline view of related entity
-        """
-        entity = form.edited_entity
-        pending_deletes = get_pending_deletes(form._cw, entity.eid)
-        for label, rschema, role in self.relations:
-            related = []
-            if entity.has_eid():
-                rset = entity.related(rschema, role, limit=form.related_limit)
-                if rschema.has_perm(form._cw, 'delete'):
-                    toggleable_rel_link_func = toggleable_relation_link
-                else:
-                    toggleable_rel_link_func = lambda x, y, z: u''
-                for row in xrange(rset.rowcount):
-                    nodeid = relation_id(entity.eid, rschema, role,
-                                         rset[row][0])
-                    if nodeid in pending_deletes:
-                        status, label = u'pendingDelete', '+'
-                    else:
-                        status, label = u'', 'x'
-                    dellink = toggleable_rel_link_func(entity.eid, nodeid, label)
-                    eview = form._cw.view('oneline', rset, row=row)
-                    related.append((nodeid, dellink, status, eview))
-            yield (rschema, role, related)
-
-    def restore_pending_inserts(self, form):
-        """used to restore edition page as it was before clicking on
-        'search for <some entity type>'
-        """
-        entity = form.edited_entity
-        pending_inserts = set(get_pending_inserts(form._cw, form.edited_entity.eid))
-        for pendingid in pending_inserts:
-            eidfrom, rtype, eidto = pendingid.split(':')
-            if typed_eid(eidfrom) == entity.eid: # subject
-                label = display_name(form._cw, rtype, 'subject',
-                                     entity.__regid__)
-                reid = eidto
-            else:
-                label = display_name(form._cw, rtype, 'object',
-                                     entity.__regid__)
-                reid = eidfrom
-            jscall = "javascript: cancelPendingInsert('%s', 'tr', null, %s);" \
-                     % (pendingid, entity.eid)
-            rset = form._cw.eid_rset(reid)
-            eview = form._cw.view('text', rset, row=0)
-            # XXX find a clean way to handle baskets
-            if rset.description[0][0] == 'Basket':
-                eview = '%s (%s)' % (eview, display_name(form._cw, 'Basket'))
-            yield rtype, pendingid, jscall, label, reid, eview
-
-
-class UnrelatedDivs(EntityView):
-    __regid__ = 'unrelateddivs'
-    __select__ = match_form_params('relation')
-
-    def cell_call(self, row, col):
-        entity = self.cw_rset.get_entity(row, col)
-        relname, role = self._cw.form.get('relation').rsplit('_', 1)
-        rschema = self._cw.vreg.schema.rschema(relname)
-        hidden = 'hidden' in self._cw.form
-        is_cell = 'is_cell' in self._cw.form
-        self.w(self.build_unrelated_select_div(entity, rschema, role,
-                                               is_cell=is_cell, hidden=hidden))
-
-    def build_unrelated_select_div(self, entity, rschema, role,
-                                   is_cell=False, hidden=True):
-        options = []
-        divid = 'div%s_%s_%s' % (rschema.type, role, entity.eid)
-        selectid = 'select%s_%s_%s' % (rschema.type, role, entity.eid)
-        if rschema.symetric or role == 'subject':
-            targettypes = rschema.objects(entity.e_schema)
-            etypes = '/'.join(sorted(etype.display_name(self._cw) for etype in targettypes))
-        else:
-            targettypes = rschema.subjects(entity.e_schema)
-            etypes = '/'.join(sorted(etype.display_name(self._cw) for etype in targettypes))
-        etypes = uilib.cut(etypes, self._cw.property_value('navigation.short-line-size'))
-        options.append('<option>%s %s</option>' % (self._cw._('select a'), etypes))
-        options += self._get_select_options(entity, rschema, role)
-        options += self._get_search_options(entity, rschema, role, targettypes)
-        if 'Basket' in self._cw.vreg.schema: # XXX
-            options += self._get_basket_options(entity, rschema, role, targettypes)
-        relname, role = self._cw.form.get('relation').rsplit('_', 1)
-        return u"""\
-<div class="%s" id="%s">
-  <select id="%s" onchange="javascript: addPendingInsert(this.options[this.selectedIndex], %s, %s, '%s');">
-    %s
-  </select>
-</div>
-""" % (hidden and 'hidden' or '', divid, selectid,
-       xml_escape(dumps(entity.eid)), is_cell and 'true' or 'null', relname,
-       '\n'.join(options))
-
-    def _get_select_options(self, entity, rschema, role):
-        """add options to search among all entities of each possible type"""
-        options = []
-        pending_inserts = get_pending_inserts(self._cw, entity.eid)
-        rtype = rschema.type
-        form = self._cw.vreg['forms'].select('edition', self._cw, entity=entity)
-        field = form.field_by_name(rschema, role, entity.e_schema)
-        limit = self._cw.property_value('navigation.combobox-limit')
-        for eview, reid in field.choices(form, limit): # XXX expect 'limit' arg on choices
-            if reid is None:
-                if eview: # skip blank value
-                    options.append('<option class="separator">-- %s --</option>'
-                                   % xml_escape(eview))
-            elif reid != ff.INTERNAL_FIELD_VALUE:
-                optionid = relation_id(entity.eid, rtype, role, reid)
-                if optionid not in pending_inserts:
-                    # prefix option's id with letters to make valid XHTML wise
-                    options.append('<option id="id%s" value="%s">%s</option>' %
-                                   (optionid, reid, xml_escape(eview)))
-        return options
-
-    def _get_search_options(self, entity, rschema, role, targettypes):
-        """add options to search among all entities of each possible type"""
-        options = []
-        _ = self._cw._
-        for eschema in targettypes:
-            mode = '%s:%s:%s:%s' % (role, entity.eid, rschema.type, eschema)
-            url = self._cw.build_url(entity.rest_path(), vid='search-associate',
-                                 __mode=mode)
-            options.append((eschema.display_name(self._cw),
-                            '<option value="%s">%s %s</option>' % (
-                xml_escape(url), _('Search for'), eschema.display_name(self._cw))))
-        return [o for l, o in sorted(options)]
-
-    # XXX move this out
-    def _get_basket_options(self, entity, rschema, role, targettypes):
-        options = []
-        rtype = rschema.type
-        _ = self._cw._
-        for basketeid, basketname in self._get_basket_links(self._cw.user.eid,
-                                                            role, targettypes):
-            optionid = relation_id(entity.eid, rtype, role, basketeid)
-            options.append('<option id="%s" value="%s">%s %s</option>' % (
-                optionid, basketeid, _('link to each item in'), xml_escape(basketname)))
-        return options
-
-    def _get_basket_links(self, ueid, role, targettypes):
-        targettypes = set(targettypes)
-        for basketeid, basketname, elements in self._get_basket_info(ueid):
-            baskettypes = elements.column_types(0)
-            # if every elements in the basket can be attached to the
-            # edited entity
-            if baskettypes & targettypes:
-                yield basketeid, basketname
-
-    def _get_basket_info(self, ueid):
-        basketref = []
-        basketrql = 'Any B,N WHERE B is Basket, B owned_by U, U eid %(x)s, B name N'
-        basketresultset = self._cw.execute(basketrql, {'x': ueid}, 'x')
-        for result in basketresultset:
-            basketitemsrql = 'Any X WHERE X in_basket B, B eid %(x)s'
-            rset = self._cw.execute(basketitemsrql, {'x': result[0]}, 'x')
-            basketref.append((result[0], result[1], rset))
-        return basketref
-
-
 class ComboboxView(EntityView):
     """the view used in combobox (unrelated entities)