[form] important refactoring of inlined forms to get proper separation of form object creation / rendering stable
authorSylvain Thénault <sylvain.thenault@logilab.fr>
Tue, 29 Sep 2009 12:44:50 +0200
branchstable
changeset 3518 11ce4682187d
parent 3517 8832e231fad7
child 3519 e3ce9db1133c
[form] important refactoring of inlined forms to get proper separation of form object creation / rendering
web/views/autoform.py
web/views/basecontrollers.py
web/views/editforms.py
web/views/formrenderers.py
--- a/web/views/autoform.py	Tue Sep 29 12:44:06 2009 +0200
+++ b/web/views/autoform.py	Tue Sep 29 12:44:50 2009 +0200
@@ -8,7 +8,7 @@
 __docformat__ = "restructuredtext en"
 _ = unicode
 
-from logilab.common.decorators import iclassmethod
+from logilab.common.decorators import iclassmethod, cached
 
 from cubicweb import typed_eid
 from cubicweb.web import stdmsgs, uicfg
@@ -194,6 +194,11 @@
         """true if the form needs enctype=multipart/form-data"""
         return self._subform_needs_multipart()
 
+    def build_context(self, rendervalues=None):
+        super(AutomaticEntityForm, self).build_context(rendervalues)
+        for form in self.inlined_forms():
+            form.build_context(rendervalues)
+
     def _subform_needs_multipart(self, _tested=None):
         if _tested is None:
             _tested = set()
@@ -337,6 +342,46 @@
 
     # inlined forms support ####################################################
 
+    @cached
+    def inlined_form_views(self):
+        """compute and return list of inlined form views (hosting the inlined form object)
+        """
+        formviews = []
+        entity = self.edited_entity
+        for rschema, ttypes, role in self.inlined_relations():
+            # show inline forms only if there's one possible target type
+            # for rschema
+            if len(ttypes) != 1:
+                self.warning('entity related by the %s relation should have '
+                             'inlined form but there is multiple target types, '
+                             'dunno what to do', rschema)
+                continue
+            ttype = ttypes[0].type
+            if self.should_inline_relation_form(rschema, ttype, role):
+                formviews += self.inline_edition_form_view(rschema, ttype, role)
+                if role == 'subject':
+                    card = rschema.rproperty(entity.e_schema, ttype, 'cardinality')[0]
+                else:
+                    card = rschema.rproperty(ttype, entity.e_schema, 'cardinality')[1]
+                # there is no related entity and we need at least one: we need to
+                # display one explicit inline-creation view
+                if self.should_display_inline_creation_form(rschema, formviews, card):
+                    formviews += self.inline_creation_form_view(rschema, ttype, role)
+                # we can create more than one related entity, we thus display a link
+                # to add new related entities
+                if self.should_display_add_new_relation_link(rschema, formviews, card):
+                    addnewlink = self.vreg['views'].select(
+                        'inline-addnew-link', self.req,
+                        etype=ttype, rtype=rschema, role=role,
+                        peid=self.edited_entity.eid, pform=self, card=card)
+                    formviews.append(addnewlink)
+        return formviews
+
+    def inlined_forms(self):
+        for formview in self.inlined_form_views():
+            if formview.form: # may be None for the addnew_link artefact form
+                yield formview.form
+
     def should_inline_relation_form(self, rschema, targettype, role):
         """return true if the given relation with entity has role and a
         targettype target should be inlined
@@ -344,25 +389,6 @@
         return self.rinlined.etype_get(self.edited_entity.id, rschema, role,
                                        targettype)
 
-    def display_inline_edition_form(self, w, rschema, targettype, role,
-                                     i18nctx):
-        """display inline forms for already related entities.
-
-        Return True if some inlined form are actually displayed
-        """
-        existant = False
-        entity = self.edited_entity
-        related = entity.has_eid() and entity.related(rschema, role)
-        if related:
-            # display inline-edition view for all existing related entities
-            for i, relentity in enumerate(related.entities()):
-                if relentity.has_perm('update'):
-                    w(self.view('inline-edition', related, row=i, col=0,
-                                rtype=rschema, role=role, ptype=entity.e_schema,
-                                peid=entity.eid, i18nctx=i18nctx))
-                    existant = True
-        return existant
-
     def should_display_inline_creation_form(self, rschema, existant, card):
         """return true if a creation form should be inlined
 
@@ -370,17 +396,6 @@
         """
         return not existant and card in '1+' or self.req.form.has_key('force_%s_display' % rschema)
 
-    def display_inline_creation_form(self, w, rschema, targettype, role,
-                                     i18nctx):
-        """display inline forms to a newly related (hence created) entity.
-
-        Return True if some inlined form are actually displayed
-        """
-        entity = self.edited_entity
-        w(self.view('inline-creation', None, etype=targettype,
-                    peid=entity.eid, ptype=entity.e_schema,
-                    rtype=rschema, role=role, i18nctx=i18nctx))
-
     def should_display_add_new_relation_link(self, rschema, existant, card):
         """return true if we should add a link to add a new creation form
         (through ajax call)
@@ -398,6 +413,29 @@
         """
         return card in '1?'
 
+    def inline_edition_form_view(self, rschema, ttype, role):
+        """yield inline form views for already related entities through the
+        given relation
+        """
+        entity = self.edited_entity
+        related = entity.has_eid() and entity.related(rschema, role)
+        if related:
+            vvreg = self.vreg['views']
+            # display inline-edition view for all existing related entities
+            for i, relentity in enumerate(related.entities()):
+                if relentity.has_perm('update'):
+                    yield vvreg.select('inline-edition', self.req, related,
+                                       row=i, col=0, rtype=rschema, role=role,
+                                       peid=entity.eid, pform=self)
+
+    def inline_creation_form_view(self, rschema, ttype, role):
+        """yield inline form views to a newly related (hence created) entity
+        through the given relation
+        """
+        yield self.vreg['views'].select('inline-creation', self.req,
+                                        etype=ttype, rtype=rschema, role=role,
+                                        peid=self.edited_entity.eid, pform=self)
+
 
 def etype_relation_field(etype, rtype, role='subject'):
     eschema = AutomaticEntityForm.schema.eschema(etype)
--- a/web/views/basecontrollers.py	Tue Sep 29 12:44:06 2009 +0200
+++ b/web/views/basecontrollers.py	Tue Sep 29 12:44:50 2009 +0200
@@ -381,8 +381,7 @@
         view = self.vreg['views'].select('inline-creation', self.req,
                                          etype=ttype, peid=peid, rtype=rtype,
                                          role=role)
-        return self._call_view(view, etype=ttype, peid=peid,
-                               rtype=rtype, role=role, i18nctx=i18nctx)
+        return self._call_view(view, i18nctx=i18nctx)
 
     @jsonize
     def js_validate_form(self, action, names, values):
--- a/web/views/editforms.py	Tue Sep 29 12:44:06 2009 +0200
+++ b/web/views/editforms.py	Tue Sep 29 12:44:50 2009 +0200
@@ -14,6 +14,7 @@
 from simplejson import dumps
 
 from logilab.mtconverter import xml_escape
+from logilab.common.decorators import cached
 
 from cubicweb.selectors import (match_kwargs, one_line_rset, non_final_entity,
                                 specified_etype_implements, yes)
@@ -439,66 +440,83 @@
 
 
 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
+    """
     id = 'inline-edition'
     __select__ = non_final_entity() & match_kwargs('peid', 'rtype')
+
+    _select_attrs = ('peid', 'rtype', 'role', 'pform')
     removejs = "removeInlinedEntity('%s', '%s', '%s')"
 
-    def call(self, **kwargs):
-        """redefine default call() method to avoid automatic
-        insertions of <div class="section"> between each row of
-        the resultset
-        """
-        rset = self.rset
-        for i in xrange(len(rset)):
-            self.wview(self.id, rset, row=i, **kwargs)
+    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.row is not None, self
+        return self.rset.get_entity(self.row, self.col)
 
-    def cell_call(self, row, col, peid, rtype, role, i18nctx, **kwargs):
+    @property
+    @cached
+    def form(self):
+        entity = self._entity()
+        form = self.vreg['forms'].select('edition', self.req,
+                                         entity=entity,
+                                         form_renderer_id='inline',
+                                         mainform=False, copy_nav_params=False,
+                                         **self.extra_kwargs)
+        form.parent_form = self.pform
+        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(row, col)
-        divonclick = "restoreInlinedEntity('%s', '%s', '%s')" % (peid, rtype,
-                                                                 entity.eid)
-        self.render_form(entity, peid, rtype, role, i18nctx,
-                         divonclick=divonclick)
+        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, entity, peid, rtype, role, i18nctx, **kwargs):
+    def render_form(self, i18nctx, **kwargs):
         """fetch and render the form"""
-        form = self.vreg['forms'].select('edition', self.req, entity=entity,
-                                         form_renderer_id='inline',
-                                         mainform=False, copy_nav_params=False)
-        self.add_hiddens(form, entity, peid, rtype, role)
-        divid = '%s-%s-%s' % (peid, rtype, entity.eid)
+        entity = self._entity()
+        divid = '%s-%s-%s' % (self.peid, self.rtype, entity.eid)
         title = self.req.pgettext(i18nctx, 'This %s' % entity.e_schema)
-        removejs = self.removejs % (peid, rtype, entity.eid)
-        countkey = '%s_count' % rtype
+        removejs = self.removejs % (self.peid, self.rtype, entity.eid)
+        countkey = '%s_count' % self.rtype
         try:
             self.req.data[countkey] += 1
-        except:
+        except KeyError:
             self.req.data[countkey] = 1
-        self.w(form.form_render(divid=divid, title=title, removejs=removejs,
-                                i18nctx=i18nctx,
-                                counter=self.req.data[countkey], **kwargs))
+        self.w(self.form.form_render(
+            divid=divid, title=title, removejs=removejs, i18nctx=i18nctx,
+            counter=self.req.data[countkey], **kwargs))
 
-    def add_hiddens(self, form, entity, peid, rtype, role):
+    def add_hiddens(self, form, entity):
         # to ease overriding (see cubes.vcsfile.views.forms for instance)
-        if self.keep_entity(form, entity, peid, rtype):
+        if self.keep_entity(form, entity):
             if entity.has_eid():
                 rval = entity.eid
             else:
                 rval = INTERNAL_FIELD_VALUE
-            form.form_add_hidden('edit%s-%s:%s' % (role[0], rtype, peid), rval)
-        form.form_add_hidden(name='%s:%s' % (rtype, peid), value=entity.eid,
-                             id='rel-%s-%s-%s'  % (peid, rtype, entity.eid))
+            form.form_add_hidden('edit%s-%s:%s' % (self.role[0], self.rtype, self.peid), rval)
+        form.form_add_hidden(name='%s:%s' % (self.rtype, self.peid), value=entity.eid,
+                             id='rel-%s-%s-%s'  % (self.peid, self.rtype, entity.eid))
 
-    def keep_entity(self, form, entity, peid, rtype):
+    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.req.list_form_param(eid_param(rtype, peid),
+            cdvalues = self.req.list_form_param(eid_param(self.rtype, self.peid),
                                                 form.form_previous_values)
             if unicode(entity.eid) not in cdvalues:
                 return False
@@ -506,24 +524,52 @@
 
 
 class InlineEntityCreationFormView(InlineEntityEditionFormView):
+    """
+    :attr etype: the entity type being created in the inline form
+    """
     id = 'inline-creation'
     __select__ = (match_kwargs('peid', 'rtype')
                   & specified_etype_implements('Any'))
+    _select_attrs = InlineEntityEditionFormView._select_attrs + ('etype',)
     removejs = "removeInlineForm('%s', '%s', '%s')"
 
-    def call(self, etype, peid, rtype, role, i18nctx, **kwargs):
-        """
-        :param etype: the entity type being created in the inline form
-        :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
-        """
+    @cached
+    def _entity(self):
         try:
-            cls = self.vreg['etypes'].etype_class(etype)
+            cls = self.vreg['etypes'].etype_class(self.etype)
         except:
             self.w(self.req._('no such entity type %s') % etype)
             return
         self.initialize_varmaker()
         entity = cls(self.req)
         entity.eid = self.varmaker.next()
-        self.render_form(entity, peid, rtype, role, i18nctx, **kwargs)
+        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`
+    """
+    id = '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):
+        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.req.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>')
+        self.w(u'<div class="trame_grise">&#160;</div>')
--- a/web/views/formrenderers.py	Tue Sep 29 12:44:06 2009 +0200
+++ b/web/views/formrenderers.py	Tue Sep 29 12:44:50 2009 +0200
@@ -474,51 +474,23 @@
 
     def inline_entities_form(self, w, form):
         """create a form to edit entity's inlined relations"""
-        if not hasattr(form, 'inlined_relations'):
+        if not hasattr(form, 'inlined_form_views'):
             return
-        for rschema, targettypes, role in form.inlined_relations():
-            # show inline forms only if there's one possible target type
-            # for rschema
-            if len(targettypes) != 1:
-                self.warning('entity related by the %s relation should have '
-                             'inlined form but there is multiple target types, '
-                             'dunno what to do', rschema)
-                continue
-            targettype = targettypes[0].type
-            if form.should_inline_relation_form(rschema, targettype, role):
-                self.inline_relation_form(w, form, rschema, targettype, role)
+        keysinorder = []
+        formviews = form.inlined_form_views()
+        for formview in formviews:
+            if not (formview.rtype, formview.role) in keysinorder:
+                keysinorder.append( (formview.rtype, formview.role) )
+        for key in keysinorder:
+            self.inline_relation_form(w, form, [fv for fv in formviews
+                                                if (fv.rtype, fv.role) == key])
 
-    def inline_relation_form(self, w, form, rschema, targettype, role):
-        entity = form.edited_entity
-        __ = self.req.pgettext
-        i18nctx = 'inlined:%s.%s.%s' % (entity.e_schema, rschema, role)
-        w(u'<div id="inline%sslot">' % rschema)
-        existant = form.display_inline_edition_form(w, rschema, targettype,
-                                                    role, i18nctx)
-        if role == 'subject':
-            card = rschema.rproperty(entity.e_schema, targettype, 'cardinality')[0]
-        else:
-            card = rschema.rproperty(targettype, entity.e_schema, 'cardinality')[1]
-        # there is no related entity and we need at least one: we need to
-        # display one explicit inline-creation view
-        if form.should_display_inline_creation_form(rschema, existant, card):
-            form.display_inline_creation_form(w, rschema, targettype,
-                                              role, i18nctx)
-            existant = True
-        # we can create more than one related entity, we thus display a link
-        # to add new related entities
-        if form.should_display_add_new_relation_link(rschema, existant, card):
-            divid = "addNew%s%s%s:%s" % (targettype, rschema, role, entity.eid)
-            w(u'<div class="inlinedform" id="%s" cubicweb:limit="true">'
-              % divid)
-            js = "addInlineCreationForm('%s', '%s', '%s', '%s', '%s')" % (
-                entity.eid, targettype, rschema, role, i18nctx)
-            if form.should_hide_add_new_relation_link(rschema, card):
-                js = "toggleVisibility('%s'); %s" % (divid, js)
-            w(u'<a class="addEntity" id="add%s:%slink" href="javascript: %s" >+ %s.</a>'
-              % (rschema, entity.eid, js, __(i18nctx, 'add a %s' % targettype)))
-            w(u'</div>')
-            w(u'<div class="trame_grise">&#160;</div>')
+    def inline_relation_form(self, w, form, formviews):
+        i18nctx = 'inlined:%s.%s.%s' % (form.edited_entity.e_schema,
+                                        formviews[0].rtype, formviews[0].role)
+        w(u'<div id="inline%sslot">' % formviews[0].rtype)
+        for formview in formviews:
+            w(formview.render(i18nctx=i18nctx, row=formview.row, col=formview.col))
         w(u'</div>')