--- 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))