diff -r b5762ac9a82e -r 570208f74a84 web/views/editcontroller.py --- 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))