Skip to content

Commit

Permalink
Fixed #13087 -- Modified m2m signals to provide greater flexibility o…
Browse files Browse the repository at this point in the history
…ver 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
  • Loading branch information
freakboy3742 committed Mar 30, 2010
1 parent 539cfe8 commit 273a002
Show file tree
Hide file tree
Showing 3 changed files with 170 additions and 27 deletions.
25 changes: 22 additions & 3 deletions django/db/models/fields/related.py
Expand Up @@ -559,6 +559,13 @@ def _add_items(self, source_field_name, target_field_name, *objs):
'%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(**{
Expand All @@ -568,7 +575,7 @@ def _add_items(self, source_field_name, target_field_name, *objs):
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)

Expand All @@ -586,6 +593,12 @@ def _remove_items(self, source_field_name, target_field_name, *objs):
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(**{
Expand All @@ -595,7 +608,7 @@ def _remove_items(self, source_field_name, target_field_name, *objs):
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)

Expand All @@ -604,13 +617,19 @@ def _clear_items(self, source_field_name):
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

Expand Down
12 changes: 9 additions & 3 deletions docs/ref/signals.txt
Expand Up @@ -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
Expand Down
160 changes: 139 additions & 21 deletions tests/modeltests/m2m_signals/models.py
Expand Up @@ -73,7 +73,13 @@ def m2m_changed_test(signal, sender, **kwargs):
>>> c1.default_parts.add(p1, p2, p3)
m2m_changed signal
instance: VW
action: add
action: pre_add
reverse: False
model: <class 'modeltests.m2m_signals.models.Part'>
objects: [<Part: Doors>, <Part: Engine>, <Part: Wheelset>]
m2m_changed signal
instance: VW
action: post_add
reverse: False
model: <class 'modeltests.m2m_signals.models.Part'>
objects: [<Part: Doors>, <Part: Engine>, <Part: Wheelset>]
Expand All @@ -82,7 +88,13 @@ def m2m_changed_test(signal, sender, **kwargs):
>>> p2.car_set.add(c2, c3)
m2m_changed signal
instance: Doors
action: add
action: pre_add
reverse: True
model: <class 'modeltests.m2m_signals.models.Car'>
objects: [<Car: BMW>, <Car: Toyota>]
m2m_changed signal
instance: Doors
action: post_add
reverse: True
model: <class 'modeltests.m2m_signals.models.Car'>
objects: [<Car: BMW>, <Car: Toyota>]
Expand All @@ -91,7 +103,13 @@ def m2m_changed_test(signal, sender, **kwargs):
>>> c1.default_parts.remove(p3, p4)
m2m_changed signal
instance: VW
action: remove
action: pre_remove
reverse: False
model: <class 'modeltests.m2m_signals.models.Part'>
objects: [<Part: Airbag>, <Part: Engine>]
m2m_changed signal
instance: VW
action: post_remove
reverse: False
model: <class 'modeltests.m2m_signals.models.Part'>
objects: [<Part: Airbag>, <Part: Engine>]
Expand All @@ -100,7 +118,13 @@ def m2m_changed_test(signal, sender, **kwargs):
>>> c1.optional_parts.add(p4,p5)
m2m_changed signal
instance: VW
action: add
action: pre_add
reverse: False
model: <class 'modeltests.m2m_signals.models.Part'>
objects: [<Part: Airbag>, <Part: Sunroof>]
m2m_changed signal
instance: VW
action: post_add
reverse: False
model: <class 'modeltests.m2m_signals.models.Part'>
objects: [<Part: Airbag>, <Part: Sunroof>]
Expand All @@ -109,7 +133,13 @@ def m2m_changed_test(signal, sender, **kwargs):
>>> p4.cars_optional.add(c1, c2, c3)
m2m_changed signal
instance: Airbag
action: add
action: pre_add
reverse: True
model: <class 'modeltests.m2m_signals.models.Car'>
objects: [<Car: BMW>, <Car: Toyota>]
m2m_changed signal
instance: Airbag
action: post_add
reverse: True
model: <class 'modeltests.m2m_signals.models.Car'>
objects: [<Car: BMW>, <Car: Toyota>]
Expand All @@ -118,7 +148,13 @@ def m2m_changed_test(signal, sender, **kwargs):
>>> p4.cars_optional.remove(c1)
m2m_changed signal
instance: Airbag
action: remove
action: pre_remove
reverse: True
model: <class 'modeltests.m2m_signals.models.Car'>
objects: [<Car: VW>]
m2m_changed signal
instance: Airbag
action: post_remove
reverse: True
model: <class 'modeltests.m2m_signals.models.Car'>
objects: [<Car: VW>]
Expand All @@ -127,23 +163,38 @@ def m2m_changed_test(signal, sender, **kwargs):
>>> c1.default_parts.clear()
m2m_changed signal
instance: VW
action: clear
action: pre_clear
reverse: False
model: <class 'modeltests.m2m_signals.models.Part'>
m2m_changed signal
instance: VW
action: post_clear
reverse: False
model: <class 'modeltests.m2m_signals.models.Part'>
# take all the doors off of cars
>>> p2.car_set.clear()
m2m_changed signal
instance: Doors
action: clear
action: pre_clear
reverse: True
model: <class 'modeltests.m2m_signals.models.Car'>
m2m_changed signal
instance: Doors
action: post_clear
reverse: True
model: <class 'modeltests.m2m_signals.models.Car'>
# take all the airbags off of cars (clear reverse relation with custom related_name)
>>> p4.cars_optional.clear()
m2m_changed signal
instance: Airbag
action: clear
action: pre_clear
reverse: True
model: <class 'modeltests.m2m_signals.models.Car'>
m2m_changed signal
instance: Airbag
action: post_clear
reverse: True
model: <class 'modeltests.m2m_signals.models.Car'>
Expand All @@ -152,7 +203,13 @@ def m2m_changed_test(signal, sender, **kwargs):
>>> c1.default_parts.create(name='Windows')
m2m_changed signal
instance: VW
action: add
action: pre_add
reverse: False
model: <class 'modeltests.m2m_signals.models.Part'>
objects: [<Part: Windows>]
m2m_changed signal
instance: VW
action: post_add
reverse: False
model: <class 'modeltests.m2m_signals.models.Part'>
objects: [<Part: Windows>]
Expand All @@ -162,12 +219,23 @@ def m2m_changed_test(signal, sender, **kwargs):
>>> c1.default_parts = [p1,p2,p3]
m2m_changed signal
instance: VW
action: clear
action: pre_clear
reverse: False
model: <class 'modeltests.m2m_signals.models.Part'>
m2m_changed signal
instance: VW
action: add
action: post_clear
reverse: False
model: <class 'modeltests.m2m_signals.models.Part'>
m2m_changed signal
instance: VW
action: pre_add
reverse: False
model: <class 'modeltests.m2m_signals.models.Part'>
objects: [<Part: Doors>, <Part: Engine>, <Part: Wheelset>]
m2m_changed signal
instance: VW
action: post_add
reverse: False
model: <class 'modeltests.m2m_signals.models.Part'>
objects: [<Part: Doors>, <Part: Engine>, <Part: Wheelset>]
Expand All @@ -177,20 +245,37 @@ def m2m_changed_test(signal, sender, **kwargs):
>>> c4.default_parts = [p2]
m2m_changed signal
instance: Bugatti
action: clear
action: pre_clear
reverse: False
model: <class 'modeltests.m2m_signals.models.Part'>
m2m_changed signal
instance: Bugatti
action: post_clear
reverse: False
model: <class 'modeltests.m2m_signals.models.Part'>
m2m_changed signal
instance: Bugatti
action: pre_add
reverse: False
model: <class 'modeltests.m2m_signals.models.Part'>
objects: [<Part: Doors>]
m2m_changed signal
instance: Bugatti
action: add
action: post_add
reverse: False
model: <class 'modeltests.m2m_signals.models.Part'>
objects: [<Part: Doors>]
>>> p3.car_set.add(c4)
m2m_changed signal
instance: Engine
action: add
action: pre_add
reverse: True
model: <class 'modeltests.m2m_signals.models.Car'>
objects: [<Car: Bugatti>]
m2m_changed signal
instance: Engine
action: post_add
reverse: True
model: <class 'modeltests.m2m_signals.models.Car'>
objects: [<Car: Bugatti>]
Expand All @@ -207,38 +292,71 @@ def m2m_changed_test(signal, sender, **kwargs):
>>> p1.friends = [p2, p3]
m2m_changed signal
instance: Alice
action: clear
action: pre_clear
reverse: False
model: <class 'modeltests.m2m_signals.models.Person'>
m2m_changed signal
instance: Alice
action: post_clear
reverse: False
model: <class 'modeltests.m2m_signals.models.Person'>
m2m_changed signal
instance: Alice
action: pre_add
reverse: False
model: <class 'modeltests.m2m_signals.models.Person'>
objects: [<Person: Bob>, <Person: Chuck>]
m2m_changed signal
instance: Alice
action: add
action: post_add
reverse: False
model: <class 'modeltests.m2m_signals.models.Person'>
objects: [<Person: Bob>, <Person: Chuck>]
>>> p1.fans = [p4]
m2m_changed signal
instance: Alice
action: clear
action: pre_clear
reverse: False
model: <class 'modeltests.m2m_signals.models.Person'>
m2m_changed signal
instance: Alice
action: post_clear
reverse: False
model: <class 'modeltests.m2m_signals.models.Person'>
m2m_changed signal
instance: Alice
action: pre_add
reverse: False
model: <class 'modeltests.m2m_signals.models.Person'>
objects: [<Person: Daisy>]
m2m_changed signal
instance: Alice
action: add
action: post_add
reverse: False
model: <class 'modeltests.m2m_signals.models.Person'>
objects: [<Person: Daisy>]
>>> p3.idols = [p1,p2]
m2m_changed signal
instance: Chuck
action: clear
action: pre_clear
reverse: True
model: <class 'modeltests.m2m_signals.models.Person'>
m2m_changed signal
instance: Chuck
action: post_clear
reverse: True
model: <class 'modeltests.m2m_signals.models.Person'>
m2m_changed signal
instance: Chuck
action: pre_add
reverse: True
model: <class 'modeltests.m2m_signals.models.Person'>
objects: [<Person: Alice>, <Person: Bob>]
m2m_changed signal
instance: Chuck
action: add
action: post_add
reverse: True
model: <class 'modeltests.m2m_signals.models.Person'>
objects: [<Person: Alice>, <Person: Bob>]
Expand Down

0 comments on commit 273a002

Please sign in to comment.