From 273a0025448d30154626b3e962df9ad5b55d8b3b Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 30 Mar 2010 11:54:56 +0000 Subject: [PATCH] Fixed #13087 -- Modified m2m signals to provide greater flexibility over exactly when notifications are delivered. This is a BACKWARDS INCOMPATIBLE CHANGE for anyone using the signal names introduced in r12223. * If you were listening to "add", you should now listen to "post_add". * If you were listening to "remove", you should now listen to "post_remove". * If you were listening to "clear", you should now listen to "pre_clear". You may also want to examine your code to see whether the "pre_add", "pre_remove" or "post_clear" would be better suited to your application. git-svn-id: http://code.djangoproject.com/svn/django/trunk@12888 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/fields/related.py | 25 +++- docs/ref/signals.txt | 12 +- tests/modeltests/m2m_signals/models.py | 160 +++++++++++++++++++++---- 3 files changed, 170 insertions(+), 27 deletions(-) diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 5b9a348ca3e..2e63a49bcf1 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -559,6 +559,13 @@ def create_many_related_manager(superclass, rel=False): '%s__in' % target_field_name: new_ids, }) new_ids = new_ids - set(vals) + + if self.reverse or source_field_name == self.source_field_name: + # Don't send the signal when we are inserting the + # duplicate data row for symmetrical reverse entries. + signals.m2m_changed.send(sender=rel.through, action='pre_add', + instance=self.instance, reverse=self.reverse, + model=self.model, pk_set=new_ids) # Add the ones that aren't there already for obj_id in new_ids: self.through._default_manager.using(db).create(**{ @@ -568,7 +575,7 @@ def create_many_related_manager(superclass, rel=False): if self.reverse or source_field_name == self.source_field_name: # Don't send the signal when we are inserting the # duplicate data row for symmetrical reverse entries. - signals.m2m_changed.send(sender=rel.through, action='add', + signals.m2m_changed.send(sender=rel.through, action='post_add', instance=self.instance, reverse=self.reverse, model=self.model, pk_set=new_ids) @@ -586,6 +593,12 @@ def create_many_related_manager(superclass, rel=False): old_ids.add(obj.pk) else: old_ids.add(obj) + if self.reverse or source_field_name == self.source_field_name: + # Don't send the signal when we are deleting the + # duplicate data row for symmetrical reverse entries. + signals.m2m_changed.send(sender=rel.through, action="pre_remove", + instance=self.instance, reverse=self.reverse, + model=self.model, pk_set=old_ids) # Remove the specified objects from the join table db = router.db_for_write(self.through.__class__, instance=self.instance) self.through._default_manager.using(db).filter(**{ @@ -595,7 +608,7 @@ def create_many_related_manager(superclass, rel=False): if self.reverse or source_field_name == self.source_field_name: # Don't send the signal when we are deleting the # duplicate data row for symmetrical reverse entries. - signals.m2m_changed.send(sender=rel.through, action="remove", + signals.m2m_changed.send(sender=rel.through, action="post_remove", instance=self.instance, reverse=self.reverse, model=self.model, pk_set=old_ids) @@ -604,13 +617,19 @@ def create_many_related_manager(superclass, rel=False): if self.reverse or source_field_name == self.source_field_name: # Don't send the signal when we are clearing the # duplicate data rows for symmetrical reverse entries. - signals.m2m_changed.send(sender=rel.through, action="clear", + signals.m2m_changed.send(sender=rel.through, action="pre_clear", instance=self.instance, reverse=self.reverse, model=self.model, pk_set=None) db = router.db_for_write(self.through.__class__, instance=self.instance) self.through._default_manager.using(db).filter(**{ source_field_name: self._pk_val }).delete() + if self.reverse or source_field_name == self.source_field_name: + # Don't send the signal when we are clearing the + # duplicate data rows for symmetrical reverse entries. + signals.m2m_changed.send(sender=rel.through, action="post_clear", + instance=self.instance, reverse=self.reverse, + model=self.model, pk_set=None) return ManyRelatedManager diff --git a/docs/ref/signals.txt b/docs/ref/signals.txt index b01c1f0b340..d79232e1967 100644 --- a/docs/ref/signals.txt +++ b/docs/ref/signals.txt @@ -201,12 +201,18 @@ Arguments sent with this signal: A string indicating the type of update that is done on the relation. This can be one of the following: - ``"add"`` + ``"pre_add"`` + Sent *before* one or more objects are added to the relation + ``"post_add"`` Sent *after* one or more objects are added to the relation - ``"remove"`` + ``"pre_remove"`` Sent *after* one or more objects are removed from the relation - ``"clear"`` + ``"post_remove"`` + Sent *after* one or more objects are removed from the relation + ``"pre_clear"`` Sent *before* the relation is cleared + ``"post_clear"`` + Sent *after* the relation is cleared ``reverse`` Indicates which side of the relation is updated (i.e., if it is the diff --git a/tests/modeltests/m2m_signals/models.py b/tests/modeltests/m2m_signals/models.py index 3adcd796e3e..c76cde46fc3 100644 --- a/tests/modeltests/m2m_signals/models.py +++ b/tests/modeltests/m2m_signals/models.py @@ -73,7 +73,13 @@ __test__ = {'API_TESTS':""" >>> c1.default_parts.add(p1, p2, p3) m2m_changed signal instance: VW -action: add +action: pre_add +reverse: False +model: +objects: [, , ] +m2m_changed signal +instance: VW +action: post_add reverse: False model: objects: [, , ] @@ -82,7 +88,13 @@ objects: [, , ] >>> p2.car_set.add(c2, c3) m2m_changed signal instance: Doors -action: add +action: pre_add +reverse: True +model: +objects: [, ] +m2m_changed signal +instance: Doors +action: post_add reverse: True model: objects: [, ] @@ -91,7 +103,13 @@ objects: [, ] >>> c1.default_parts.remove(p3, p4) m2m_changed signal instance: VW -action: remove +action: pre_remove +reverse: False +model: +objects: [, ] +m2m_changed signal +instance: VW +action: post_remove reverse: False model: objects: [, ] @@ -100,7 +118,13 @@ objects: [, ] >>> c1.optional_parts.add(p4,p5) m2m_changed signal instance: VW -action: add +action: pre_add +reverse: False +model: +objects: [, ] +m2m_changed signal +instance: VW +action: post_add reverse: False model: objects: [, ] @@ -109,7 +133,13 @@ objects: [, ] >>> p4.cars_optional.add(c1, c2, c3) m2m_changed signal instance: Airbag -action: add +action: pre_add +reverse: True +model: +objects: [, ] +m2m_changed signal +instance: Airbag +action: post_add reverse: True model: objects: [, ] @@ -118,7 +148,13 @@ objects: [, ] >>> p4.cars_optional.remove(c1) m2m_changed signal instance: Airbag -action: remove +action: pre_remove +reverse: True +model: +objects: [] +m2m_changed signal +instance: Airbag +action: post_remove reverse: True model: objects: [] @@ -127,7 +163,12 @@ objects: [] >>> c1.default_parts.clear() m2m_changed signal instance: VW -action: clear +action: pre_clear +reverse: False +model: +m2m_changed signal +instance: VW +action: post_clear reverse: False model: @@ -135,7 +176,12 @@ model: >>> p2.car_set.clear() m2m_changed signal instance: Doors -action: clear +action: pre_clear +reverse: True +model: +m2m_changed signal +instance: Doors +action: post_clear reverse: True model: @@ -143,7 +189,12 @@ model: >>> p4.cars_optional.clear() m2m_changed signal instance: Airbag -action: clear +action: pre_clear +reverse: True +model: +m2m_changed signal +instance: Airbag +action: post_clear reverse: True model: @@ -152,7 +203,13 @@ model: >>> c1.default_parts.create(name='Windows') m2m_changed signal instance: VW -action: add +action: pre_add +reverse: False +model: +objects: [] +m2m_changed signal +instance: VW +action: post_add reverse: False model: objects: [] @@ -162,12 +219,23 @@ objects: [] >>> c1.default_parts = [p1,p2,p3] m2m_changed signal instance: VW -action: clear +action: pre_clear reverse: False model: m2m_changed signal instance: VW -action: add +action: post_clear +reverse: False +model: +m2m_changed signal +instance: VW +action: pre_add +reverse: False +model: +objects: [, , ] +m2m_changed signal +instance: VW +action: post_add reverse: False model: objects: [, , ] @@ -177,12 +245,23 @@ objects: [, , ] >>> c4.default_parts = [p2] m2m_changed signal instance: Bugatti -action: clear +action: pre_clear reverse: False model: m2m_changed signal instance: Bugatti -action: add +action: post_clear +reverse: False +model: +m2m_changed signal +instance: Bugatti +action: pre_add +reverse: False +model: +objects: [] +m2m_changed signal +instance: Bugatti +action: post_add reverse: False model: objects: [] @@ -190,7 +269,13 @@ objects: [] >>> p3.car_set.add(c4) m2m_changed signal instance: Engine -action: add +action: pre_add +reverse: True +model: +objects: [] +m2m_changed signal +instance: Engine +action: post_add reverse: True model: objects: [] @@ -207,12 +292,23 @@ objects: [] >>> p1.friends = [p2, p3] m2m_changed signal instance: Alice -action: clear +action: pre_clear reverse: False model: m2m_changed signal instance: Alice -action: add +action: post_clear +reverse: False +model: +m2m_changed signal +instance: Alice +action: pre_add +reverse: False +model: +objects: [, ] +m2m_changed signal +instance: Alice +action: post_add reverse: False model: objects: [, ] @@ -220,12 +316,23 @@ objects: [, ] >>> p1.fans = [p4] m2m_changed signal instance: Alice -action: clear +action: pre_clear reverse: False model: m2m_changed signal instance: Alice -action: add +action: post_clear +reverse: False +model: +m2m_changed signal +instance: Alice +action: pre_add +reverse: False +model: +objects: [] +m2m_changed signal +instance: Alice +action: post_add reverse: False model: objects: [] @@ -233,12 +340,23 @@ objects: [] >>> p3.idols = [p1,p2] m2m_changed signal instance: Chuck -action: clear +action: pre_clear reverse: True model: m2m_changed signal instance: Chuck -action: add +action: post_clear +reverse: True +model: +m2m_changed signal +instance: Chuck +action: pre_add +reverse: True +model: +objects: [, ] +m2m_changed signal +instance: Chuck +action: post_add reverse: True model: objects: [, ]