diff -r 7f83e6a5acc3 -r 6464edfa95bb web/views/editcontroller.py --- a/web/views/editcontroller.py Fri Feb 12 16:13:43 2016 +0100 +++ b/web/views/editcontroller.py Tue Feb 16 19:18:37 2016 +0100 @@ -31,7 +31,7 @@ from rql.utils import rqlvar_maker -from cubicweb import _, Binary, ValidationError +from cubicweb import _, Binary, ValidationError, UnknownEid from cubicweb.view import EntityAdapter from cubicweb.predicates import is_instance from cubicweb.web import (INTERNAL_FIELD_VALUE, RequestError, NothingToEdit, @@ -79,12 +79,14 @@ self.edited = [] self.restrictions = [] self.kwargs = {} + self.canceled = False def __repr__(self): return ('Query ' % ( self.edited, self.restrictions, self.kwargs)) def insert_query(self, etype): + assert not self.canceled if self.edited: rql = 'INSERT %s X: %s' % (etype, ','.join(self.edited)) else: @@ -94,6 +96,7 @@ return rql def update_query(self, eid): + assert not self.canceled varmaker = rqlvar_maker() var = next(varmaker) while var in self.kwargs: @@ -192,6 +195,7 @@ # deserves special treatment req.data['pending_inlined'] = defaultdict(set) req.data['pending_others'] = set() + req.data['pending_composite_delete'] = set() try: for formparams in self._ordered_formparams(): eid = self.edit_entity(formparams) @@ -210,6 +214,9 @@ # then execute rql to set all relations for querydef in self.relations_rql: self._cw.execute(*querydef) + # delete pending composite + for entity in req.data['pending_composite_delete']: + entity.cw_delete() # XXX this processes *all* pending operations of *all* entities if '__delete' in req.form: todelete = req.list_form_param('__delete', req.form, pop=True) @@ -266,14 +273,17 @@ # 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(), text_type(ex)) for f, ex in self.errors) - raise ValidationError(valerror_eid(entity.eid), errors) - if eid is None: # creation or copy - entity.eid = eid = self._insert_entity(etype, formparams['eid'], rqlquery) - elif rqlquery.edited: # edition of an existant entity - self.check_concurrent_edition(formparams, eid) - self._update_entity(eid, rqlquery) + if not rqlquery.canceled: + if self.errors: + errors = dict((f.role_name(), text_type(ex)) for f, ex in self.errors) + raise ValidationError(valerror_eid(entity.eid), errors) + if eid is None: # creation or copy + entity.eid = eid = self._insert_entity(etype, formparams['eid'], rqlquery) + elif rqlquery.edited: # edition of an existant entity + self.check_concurrent_edition(formparams, eid) + self._update_entity(eid, rqlquery) + else: + self.errors = [] if is_main_entity: self.notify_edited(entity) if '__delete' in formparams: @@ -287,7 +297,8 @@ return eid def handle_formfield(self, form, field, rqlquery=None): - eschema = form.edited_entity.e_schema + entity = form.edited_entity + eschema = entity.e_schema try: for field, value in field.process_posted(form): if not ( @@ -295,25 +306,74 @@ or (field.role == 'object' and field.name in eschema.objrels)): continue + rschema = self._cw.vreg.schema.rschema(field.name) if rschema.final: rqlquery.set_attribute(field.name, value) + continue + + if entity.has_eid(): + origvalues = set(data[0] for data in entity.related(field.name, field.role).rows) else: - if form.edited_entity.has_eid(): - origvalues = set(entity.eid for entity in form.edited_entity.related(field.name, field.role, entities=True)) - else: - origvalues = set() - if value is None or value == origvalues: - continue # not edited / not modified / to do later - if rschema.inlined and rqlquery is not None and field.role == 'subject': - self.handle_inlined_relation(form, field, value, origvalues, rqlquery) - elif form.edited_entity.has_eid(): - self.handle_relation(form, field, value, origvalues) - else: - form._cw.data['pending_others'].add( (form, field) ) + origvalues = set() + if value is None or value == origvalues: + continue # not edited / not modified / to do later + + unlinked_eids = origvalues - value + + if unlinked_eids: + # Special handling of composite relation removal + self.handle_composite_removal( + form, field, unlinked_eids, value, rqlquery) + + if rschema.inlined and rqlquery is not None and field.role == 'subject': + self.handle_inlined_relation(form, field, value, origvalues, rqlquery) + elif form.edited_entity.has_eid(): + self.handle_relation(form, field, value, origvalues) + else: + form._cw.data['pending_others'].add( (form, field) ) + except ProcessFormError as exc: self.errors.append((field, exc)) + def handle_composite_removal(self, form, field, + removed_values, new_values, rqlquery): + """ + In EditController-handled forms, when the user removes a composite + relation, it triggers the removal of the related entity in the + composite. This is where this happens. + + See for instance test_subject_subentity_removal in + web/test/unittest_application.py. + """ + rschema = self._cw.vreg.schema.rschema(field.name) + new_value_etypes = set() + # the user could have included nonexisting eids in the POST; don't crash. + for eid in new_values: + try: + new_value_etypes.add(self._cw.entity_from_eid(eid).cw_etype) + except UnknownEid: + continue + for unlinked_eid in removed_values: + unlinked_entity = self._cw.entity_from_eid(unlinked_eid) + rdef = rschema.role_rdef(form.edited_entity.cw_etype, + unlinked_entity.cw_etype, + field.role) + if rdef.composite is not None: + if rdef.composite == field.role: + to_be_removed = unlinked_entity + else: + if unlinked_entity.cw_etype in new_value_etypes: + # This is a same-rdef re-parenting: do not remove the entity + continue + to_be_removed = form.edited_entity + self.info('Edition of %s is cancelled (deletion requested)', + to_be_removed) + rqlquery.canceled = True + self.info('Scheduling removal of %s as composite relation ' + '%s was removed', to_be_removed, rdef) + form._cw.data['pending_composite_delete'].add(to_be_removed) + def handle_inlined_relation(self, form, field, values, origvalues, rqlquery): """handle edition for the (rschema, x) relation of the given entity """