Skip to content

Commit 273a002

Browse files
committed
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
1 parent 539cfe8 commit 273a002

File tree

3 files changed

+170
-27
lines changed

3 files changed

+170
-27
lines changed

django/db/models/fields/related.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -559,6 +559,13 @@ def _add_items(self, source_field_name, target_field_name, *objs):
559559
'%s__in' % target_field_name: new_ids,
560560
})
561561
new_ids = new_ids - set(vals)
562+
563+
if self.reverse or source_field_name == self.source_field_name:
564+
# Don't send the signal when we are inserting the
565+
# duplicate data row for symmetrical reverse entries.
566+
signals.m2m_changed.send(sender=rel.through, action='pre_add',
567+
instance=self.instance, reverse=self.reverse,
568+
model=self.model, pk_set=new_ids)
562569
# Add the ones that aren't there already
563570
for obj_id in new_ids:
564571
self.through._default_manager.using(db).create(**{
@@ -568,7 +575,7 @@ def _add_items(self, source_field_name, target_field_name, *objs):
568575
if self.reverse or source_field_name == self.source_field_name:
569576
# Don't send the signal when we are inserting the
570577
# duplicate data row for symmetrical reverse entries.
571-
signals.m2m_changed.send(sender=rel.through, action='add',
578+
signals.m2m_changed.send(sender=rel.through, action='post_add',
572579
instance=self.instance, reverse=self.reverse,
573580
model=self.model, pk_set=new_ids)
574581

@@ -586,6 +593,12 @@ def _remove_items(self, source_field_name, target_field_name, *objs):
586593
old_ids.add(obj.pk)
587594
else:
588595
old_ids.add(obj)
596+
if self.reverse or source_field_name == self.source_field_name:
597+
# Don't send the signal when we are deleting the
598+
# duplicate data row for symmetrical reverse entries.
599+
signals.m2m_changed.send(sender=rel.through, action="pre_remove",
600+
instance=self.instance, reverse=self.reverse,
601+
model=self.model, pk_set=old_ids)
589602
# Remove the specified objects from the join table
590603
db = router.db_for_write(self.through.__class__, instance=self.instance)
591604
self.through._default_manager.using(db).filter(**{
@@ -595,7 +608,7 @@ def _remove_items(self, source_field_name, target_field_name, *objs):
595608
if self.reverse or source_field_name == self.source_field_name:
596609
# Don't send the signal when we are deleting the
597610
# duplicate data row for symmetrical reverse entries.
598-
signals.m2m_changed.send(sender=rel.through, action="remove",
611+
signals.m2m_changed.send(sender=rel.through, action="post_remove",
599612
instance=self.instance, reverse=self.reverse,
600613
model=self.model, pk_set=old_ids)
601614

@@ -604,13 +617,19 @@ def _clear_items(self, source_field_name):
604617
if self.reverse or source_field_name == self.source_field_name:
605618
# Don't send the signal when we are clearing the
606619
# duplicate data rows for symmetrical reverse entries.
607-
signals.m2m_changed.send(sender=rel.through, action="clear",
620+
signals.m2m_changed.send(sender=rel.through, action="pre_clear",
608621
instance=self.instance, reverse=self.reverse,
609622
model=self.model, pk_set=None)
610623
db = router.db_for_write(self.through.__class__, instance=self.instance)
611624
self.through._default_manager.using(db).filter(**{
612625
source_field_name: self._pk_val
613626
}).delete()
627+
if self.reverse or source_field_name == self.source_field_name:
628+
# Don't send the signal when we are clearing the
629+
# duplicate data rows for symmetrical reverse entries.
630+
signals.m2m_changed.send(sender=rel.through, action="post_clear",
631+
instance=self.instance, reverse=self.reverse,
632+
model=self.model, pk_set=None)
614633

615634
return ManyRelatedManager
616635

docs/ref/signals.txt

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -201,12 +201,18 @@ Arguments sent with this signal:
201201
A string indicating the type of update that is done on the relation.
202202
This can be one of the following:
203203

204-
``"add"``
204+
``"pre_add"``
205+
Sent *before* one or more objects are added to the relation
206+
``"post_add"``
205207
Sent *after* one or more objects are added to the relation
206-
``"remove"``
208+
``"pre_remove"``
207209
Sent *after* one or more objects are removed from the relation
208-
``"clear"``
210+
``"post_remove"``
211+
Sent *after* one or more objects are removed from the relation
212+
``"pre_clear"``
209213
Sent *before* the relation is cleared
214+
``"post_clear"``
215+
Sent *after* the relation is cleared
210216

211217
``reverse``
212218
Indicates which side of the relation is updated (i.e., if it is the

tests/modeltests/m2m_signals/models.py

Lines changed: 139 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,13 @@ def m2m_changed_test(signal, sender, **kwargs):
7373
>>> c1.default_parts.add(p1, p2, p3)
7474
m2m_changed signal
7575
instance: VW
76-
action: add
76+
action: pre_add
77+
reverse: False
78+
model: <class 'modeltests.m2m_signals.models.Part'>
79+
objects: [<Part: Doors>, <Part: Engine>, <Part: Wheelset>]
80+
m2m_changed signal
81+
instance: VW
82+
action: post_add
7783
reverse: False
7884
model: <class 'modeltests.m2m_signals.models.Part'>
7985
objects: [<Part: Doors>, <Part: Engine>, <Part: Wheelset>]
@@ -82,7 +88,13 @@ def m2m_changed_test(signal, sender, **kwargs):
8288
>>> p2.car_set.add(c2, c3)
8389
m2m_changed signal
8490
instance: Doors
85-
action: add
91+
action: pre_add
92+
reverse: True
93+
model: <class 'modeltests.m2m_signals.models.Car'>
94+
objects: [<Car: BMW>, <Car: Toyota>]
95+
m2m_changed signal
96+
instance: Doors
97+
action: post_add
8698
reverse: True
8799
model: <class 'modeltests.m2m_signals.models.Car'>
88100
objects: [<Car: BMW>, <Car: Toyota>]
@@ -91,7 +103,13 @@ def m2m_changed_test(signal, sender, **kwargs):
91103
>>> c1.default_parts.remove(p3, p4)
92104
m2m_changed signal
93105
instance: VW
94-
action: remove
106+
action: pre_remove
107+
reverse: False
108+
model: <class 'modeltests.m2m_signals.models.Part'>
109+
objects: [<Part: Airbag>, <Part: Engine>]
110+
m2m_changed signal
111+
instance: VW
112+
action: post_remove
95113
reverse: False
96114
model: <class 'modeltests.m2m_signals.models.Part'>
97115
objects: [<Part: Airbag>, <Part: Engine>]
@@ -100,7 +118,13 @@ def m2m_changed_test(signal, sender, **kwargs):
100118
>>> c1.optional_parts.add(p4,p5)
101119
m2m_changed signal
102120
instance: VW
103-
action: add
121+
action: pre_add
122+
reverse: False
123+
model: <class 'modeltests.m2m_signals.models.Part'>
124+
objects: [<Part: Airbag>, <Part: Sunroof>]
125+
m2m_changed signal
126+
instance: VW
127+
action: post_add
104128
reverse: False
105129
model: <class 'modeltests.m2m_signals.models.Part'>
106130
objects: [<Part: Airbag>, <Part: Sunroof>]
@@ -109,7 +133,13 @@ def m2m_changed_test(signal, sender, **kwargs):
109133
>>> p4.cars_optional.add(c1, c2, c3)
110134
m2m_changed signal
111135
instance: Airbag
112-
action: add
136+
action: pre_add
137+
reverse: True
138+
model: <class 'modeltests.m2m_signals.models.Car'>
139+
objects: [<Car: BMW>, <Car: Toyota>]
140+
m2m_changed signal
141+
instance: Airbag
142+
action: post_add
113143
reverse: True
114144
model: <class 'modeltests.m2m_signals.models.Car'>
115145
objects: [<Car: BMW>, <Car: Toyota>]
@@ -118,7 +148,13 @@ def m2m_changed_test(signal, sender, **kwargs):
118148
>>> p4.cars_optional.remove(c1)
119149
m2m_changed signal
120150
instance: Airbag
121-
action: remove
151+
action: pre_remove
152+
reverse: True
153+
model: <class 'modeltests.m2m_signals.models.Car'>
154+
objects: [<Car: VW>]
155+
m2m_changed signal
156+
instance: Airbag
157+
action: post_remove
122158
reverse: True
123159
model: <class 'modeltests.m2m_signals.models.Car'>
124160
objects: [<Car: VW>]
@@ -127,23 +163,38 @@ def m2m_changed_test(signal, sender, **kwargs):
127163
>>> c1.default_parts.clear()
128164
m2m_changed signal
129165
instance: VW
130-
action: clear
166+
action: pre_clear
167+
reverse: False
168+
model: <class 'modeltests.m2m_signals.models.Part'>
169+
m2m_changed signal
170+
instance: VW
171+
action: post_clear
131172
reverse: False
132173
model: <class 'modeltests.m2m_signals.models.Part'>
133174
134175
# take all the doors off of cars
135176
>>> p2.car_set.clear()
136177
m2m_changed signal
137178
instance: Doors
138-
action: clear
179+
action: pre_clear
180+
reverse: True
181+
model: <class 'modeltests.m2m_signals.models.Car'>
182+
m2m_changed signal
183+
instance: Doors
184+
action: post_clear
139185
reverse: True
140186
model: <class 'modeltests.m2m_signals.models.Car'>
141187
142188
# take all the airbags off of cars (clear reverse relation with custom related_name)
143189
>>> p4.cars_optional.clear()
144190
m2m_changed signal
145191
instance: Airbag
146-
action: clear
192+
action: pre_clear
193+
reverse: True
194+
model: <class 'modeltests.m2m_signals.models.Car'>
195+
m2m_changed signal
196+
instance: Airbag
197+
action: post_clear
147198
reverse: True
148199
model: <class 'modeltests.m2m_signals.models.Car'>
149200
@@ -152,7 +203,13 @@ def m2m_changed_test(signal, sender, **kwargs):
152203
>>> c1.default_parts.create(name='Windows')
153204
m2m_changed signal
154205
instance: VW
155-
action: add
206+
action: pre_add
207+
reverse: False
208+
model: <class 'modeltests.m2m_signals.models.Part'>
209+
objects: [<Part: Windows>]
210+
m2m_changed signal
211+
instance: VW
212+
action: post_add
156213
reverse: False
157214
model: <class 'modeltests.m2m_signals.models.Part'>
158215
objects: [<Part: Windows>]
@@ -162,12 +219,23 @@ def m2m_changed_test(signal, sender, **kwargs):
162219
>>> c1.default_parts = [p1,p2,p3]
163220
m2m_changed signal
164221
instance: VW
165-
action: clear
222+
action: pre_clear
166223
reverse: False
167224
model: <class 'modeltests.m2m_signals.models.Part'>
168225
m2m_changed signal
169226
instance: VW
170-
action: add
227+
action: post_clear
228+
reverse: False
229+
model: <class 'modeltests.m2m_signals.models.Part'>
230+
m2m_changed signal
231+
instance: VW
232+
action: pre_add
233+
reverse: False
234+
model: <class 'modeltests.m2m_signals.models.Part'>
235+
objects: [<Part: Doors>, <Part: Engine>, <Part: Wheelset>]
236+
m2m_changed signal
237+
instance: VW
238+
action: post_add
171239
reverse: False
172240
model: <class 'modeltests.m2m_signals.models.Part'>
173241
objects: [<Part: Doors>, <Part: Engine>, <Part: Wheelset>]
@@ -177,20 +245,37 @@ def m2m_changed_test(signal, sender, **kwargs):
177245
>>> c4.default_parts = [p2]
178246
m2m_changed signal
179247
instance: Bugatti
180-
action: clear
248+
action: pre_clear
249+
reverse: False
250+
model: <class 'modeltests.m2m_signals.models.Part'>
251+
m2m_changed signal
252+
instance: Bugatti
253+
action: post_clear
254+
reverse: False
255+
model: <class 'modeltests.m2m_signals.models.Part'>
256+
m2m_changed signal
257+
instance: Bugatti
258+
action: pre_add
181259
reverse: False
182260
model: <class 'modeltests.m2m_signals.models.Part'>
261+
objects: [<Part: Doors>]
183262
m2m_changed signal
184263
instance: Bugatti
185-
action: add
264+
action: post_add
186265
reverse: False
187266
model: <class 'modeltests.m2m_signals.models.Part'>
188267
objects: [<Part: Doors>]
189268
190269
>>> p3.car_set.add(c4)
191270
m2m_changed signal
192271
instance: Engine
193-
action: add
272+
action: pre_add
273+
reverse: True
274+
model: <class 'modeltests.m2m_signals.models.Car'>
275+
objects: [<Car: Bugatti>]
276+
m2m_changed signal
277+
instance: Engine
278+
action: post_add
194279
reverse: True
195280
model: <class 'modeltests.m2m_signals.models.Car'>
196281
objects: [<Car: Bugatti>]
@@ -207,38 +292,71 @@ def m2m_changed_test(signal, sender, **kwargs):
207292
>>> p1.friends = [p2, p3]
208293
m2m_changed signal
209294
instance: Alice
210-
action: clear
295+
action: pre_clear
296+
reverse: False
297+
model: <class 'modeltests.m2m_signals.models.Person'>
298+
m2m_changed signal
299+
instance: Alice
300+
action: post_clear
301+
reverse: False
302+
model: <class 'modeltests.m2m_signals.models.Person'>
303+
m2m_changed signal
304+
instance: Alice
305+
action: pre_add
211306
reverse: False
212307
model: <class 'modeltests.m2m_signals.models.Person'>
308+
objects: [<Person: Bob>, <Person: Chuck>]
213309
m2m_changed signal
214310
instance: Alice
215-
action: add
311+
action: post_add
216312
reverse: False
217313
model: <class 'modeltests.m2m_signals.models.Person'>
218314
objects: [<Person: Bob>, <Person: Chuck>]
219315
220316
>>> p1.fans = [p4]
221317
m2m_changed signal
222318
instance: Alice
223-
action: clear
319+
action: pre_clear
320+
reverse: False
321+
model: <class 'modeltests.m2m_signals.models.Person'>
322+
m2m_changed signal
323+
instance: Alice
324+
action: post_clear
325+
reverse: False
326+
model: <class 'modeltests.m2m_signals.models.Person'>
327+
m2m_changed signal
328+
instance: Alice
329+
action: pre_add
224330
reverse: False
225331
model: <class 'modeltests.m2m_signals.models.Person'>
332+
objects: [<Person: Daisy>]
226333
m2m_changed signal
227334
instance: Alice
228-
action: add
335+
action: post_add
229336
reverse: False
230337
model: <class 'modeltests.m2m_signals.models.Person'>
231338
objects: [<Person: Daisy>]
232339
233340
>>> p3.idols = [p1,p2]
234341
m2m_changed signal
235342
instance: Chuck
236-
action: clear
343+
action: pre_clear
344+
reverse: True
345+
model: <class 'modeltests.m2m_signals.models.Person'>
346+
m2m_changed signal
347+
instance: Chuck
348+
action: post_clear
349+
reverse: True
350+
model: <class 'modeltests.m2m_signals.models.Person'>
351+
m2m_changed signal
352+
instance: Chuck
353+
action: pre_add
237354
reverse: True
238355
model: <class 'modeltests.m2m_signals.models.Person'>
356+
objects: [<Person: Alice>, <Person: Bob>]
239357
m2m_changed signal
240358
instance: Chuck
241-
action: add
359+
action: post_add
242360
reverse: True
243361
model: <class 'modeltests.m2m_signals.models.Person'>
244362
objects: [<Person: Alice>, <Person: Bob>]

0 commit comments

Comments
 (0)