web/views/editcontroller.py
branchstable
changeset 9179 570208f74a84
parent 9178 b5762ac9a82e
child 9196 13461cb8ff40
--- a/web/views/editcontroller.py	Mon Jul 22 12:07:46 2013 +0200
+++ b/web/views/editcontroller.py	Wed Jul 24 08:43:16 2013 +0200
@@ -20,8 +20,10 @@
 __docformat__ = "restructuredtext en"
 
 from warnings import warn
+from collections import defaultdict
 
 from logilab.common.deprecation import deprecated
+from logilab.common.graph import ordered_nodes
 
 from rql.utils import rqlvar_maker
 
@@ -129,6 +131,46 @@
         self._default_publish()
         self.reset()
 
+    def _ordered_formparams(self):
+        """ Return form parameters dictionaries for each edited entity.
+
+        We ensure that entities can be created in this order accounting for
+        mandatory inlined relations.
+        """
+        req = self._cw
+        graph = {}
+        get_rschema = self._cw.vreg.schema.rschema
+        # minparams = 2, because at least __type and eid are needed
+        values_by_eid = dict((eid, req.extract_entity_params(eid, minparams=2))
+                             for eid in req.edited_eids())
+        # iterate over all the edited entities
+        for eid, values in values_by_eid.iteritems():
+            # add eid to the dependency graph
+            graph.setdefault(eid, set())
+            # search entity's edited fields for mandatory inlined relation
+            for param in values['_cw_entity_fields'].split(','):
+                try:
+                    rtype, role = param.split('-')
+                except ValueError:
+                    # e.g. param='__type'
+                    continue
+                rschema = get_rschema(rtype)
+                if rschema.inlined:
+                    for target in rschema.targets(values['__type'], role):
+                        rdef = rschema.role_rdef(values['__type'], target, role)
+                        # if cardinality is 1 and if the target entity is being
+                        # simultaneously edited, the current entity must be
+                        # created before the target one
+                        if rdef.cardinality[0] == '1':
+                            target_eid = values[param]
+                            if target_eid in values_by_eid:
+                                # add dependency from the target entity to the
+                                # current one
+                                graph.setdefault(target_eid, set()).add(eid)
+                                break
+        for eid in reversed(ordered_nodes(graph)):
+            yield values_by_eid[eid]
+
     def _default_publish(self):
         req = self._cw
         self.errors = []
@@ -139,22 +181,27 @@
             req.set_shared_data('__maineid', form['__maineid'], txdata=True)
         # no specific action, generic edition
         self._to_create = req.data['eidmap'] = {}
-        self._pending_fields = req.data['pendingfields'] = set()
+        # those two data variables are used to handle relation from/to entities
+        # which doesn't exist at time where the entity is edited and that
+        # deserves special treatment
+        req.data['pending_inlined'] = defaultdict(set)
+        req.data['pending_others'] = set()
         try:
-            for eid in req.edited_eids():
-                # __type and eid
-                formparams = req.extract_entity_params(eid, minparams=2)
+            for formparams in self._ordered_formparams():
                 eid = self.edit_entity(formparams)
         except (RequestError, NothingToEdit) as ex:
             if '__linkto' in req.form and 'eid' in req.form:
                 self.execute_linkto()
             elif not ('__delete' in req.form or '__insert' in req.form):
                 raise ValidationError(None, {None: unicode(ex)})
-        # handle relations in newly created entities
-        if self._pending_fields:
-            for form, field in self._pending_fields:
-                self.handle_formfield(form, field)
-        # execute rql to set all relations
+        # all pending inlined relations to newly created entities have been
+        # treated now (pop to ensure there are no attempt to add new ones)
+        pending_inlined = req.data.pop('pending_inlined')
+        assert not pending_inlined, pending_inlined
+        # handle all other remaining relations now
+        for form_, field in req.data.pop('pending_others'):
+            self.handle_formfield(form_, field)
+        # then execute rql to set all relations
         for querydef in self.relations_rql:
             self._cw.execute(*querydef)
         # XXX this processes *all* pending operations of *all* entities
@@ -217,6 +264,10 @@
         form.formvalues = {} # init fields value cache
         for field in form.iter_modified_fields(editedfields, entity):
             self.handle_formfield(form, field, rqlquery)
+        # if there are some inlined field which were waiting for this entity's
+        # creation, add relevant data to the rqlquery
+        for form_, field in req.data['pending_inlined'].pop(entity.eid, ()):
+            rqlquery.set_inlined(field.name, form_.edited_entity.eid)
         if self.errors:
             errors = dict((f.role_name(), unicode(ex)) for f, ex in self.errors)
             raise ValidationError(valerror_eid(entity.eid), errors)
@@ -260,8 +311,7 @@
                     elif form.edited_entity.has_eid():
                         self.handle_relation(form, field, value, origvalues)
                     else:
-                        self._pending_fields.add( (form, field) )
-
+                        form._cw.data['pending_others'].add( (form, field) )
         except ProcessFormError as exc:
             self.errors.append((field, exc))