web/views/editcontroller.py
changeset 11063 de20b0903d7d
parent 11061 c01325774d21
child 11064 113e9da47afc
--- a/web/views/editcontroller.py	Wed Nov 25 16:28:41 2015 +0100
+++ b/web/views/editcontroller.py	Fri Dec 04 14:56:20 2015 +0100
@@ -27,7 +27,7 @@
 
 from rql.utils import rqlvar_maker
 
-from cubicweb import Binary, ValidationError
+from cubicweb import Binary, ValidationError, neg_role
 from cubicweb.view import EntityAdapter
 from cubicweb.predicates import is_instance
 from cubicweb.web import (INTERNAL_FIELD_VALUE, RequestError, NothingToEdit,
@@ -186,6 +186,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)
@@ -204,6 +205,13 @@
         # then execute rql to set all relations
         for querydef in self.relations_rql:
             self._cw.execute(*querydef)
+        # delete pending composite
+        for entity, rtype, role, targettype in req.data['pending_composite_delete']:
+            # Check the composite relation was not re-set in the same form
+            # (see test_reparent_subentity in web/test/unittest_application.py)
+            entity.cw_clear_relation_cache(rtype, role)
+            if not entity.related(rtype, role=role, targettypes=(targettype,)):
+                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)
@@ -303,6 +311,12 @@
                 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)
+
                 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():
@@ -313,6 +327,34 @@
         except ProcessFormError as exc:
             self.errors.append((field, exc))
 
+    def handle_composite_removal(self, form, field, removed_values):
+        """
+        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)
+        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:
+                    targettype = form.edited_entity.e_schema
+                    to_be_removed = unlinked_entity
+                else:
+                    targettype = unlinked_entity.e_schema
+                    to_be_removed = form.edited_entity
+                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, field.name,
+                     neg_role(rdef.composite), targettype))
+
     def handle_inlined_relation(self, form, field, values, origvalues, rqlquery):
         """handle edition for the (rschema, x) relation of the given entity
         """