2
Testing signals emitted on changing m2m relations.
5
from django.db import models
7
class Part(models.Model):
8
name = models.CharField(max_length=20)
13
def __unicode__(self):
16
class Car(models.Model):
17
name = models.CharField(max_length=20)
18
default_parts = models.ManyToManyField(Part)
19
optional_parts = models.ManyToManyField(Part, related_name='cars_optional')
24
def __unicode__(self):
28
price = models.IntegerField()
30
class Person(models.Model):
31
name = models.CharField(max_length=20)
32
fans = models.ManyToManyField('self', related_name='idols', symmetrical=False)
33
friends = models.ManyToManyField('self')
38
def __unicode__(self):
41
def m2m_changed_test(signal, sender, **kwargs):
42
print 'm2m_changed signal'
43
print 'instance:', kwargs['instance']
44
print 'action:', kwargs['action']
45
print 'reverse:', kwargs['reverse']
46
print 'model:', kwargs['model']
48
print 'objects:',kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
51
__test__ = {'API_TESTS':"""
52
# Install a listener on one of the two m2m relations.
53
>>> models.signals.m2m_changed.connect(m2m_changed_test, Car.optional_parts.through)
55
# Test the add, remove and clear methods on both sides of the
56
# many-to-many relation
58
>>> c1 = Car.objects.create(name='VW')
59
>>> c2 = Car.objects.create(name='BMW')
60
>>> c3 = Car.objects.create(name='Toyota')
61
>>> p1 = Part.objects.create(name='Wheelset')
62
>>> p2 = Part.objects.create(name='Doors')
63
>>> p3 = Part.objects.create(name='Engine')
64
>>> p4 = Part.objects.create(name='Airbag')
65
>>> p5 = Part.objects.create(name='Sunroof')
67
# adding a default part to our car - no signal listener installed
68
>>> c1.default_parts.add(p5)
70
# Now install a listener
71
>>> models.signals.m2m_changed.connect(m2m_changed_test, Car.default_parts.through)
73
>>> c1.default_parts.add(p1, p2, p3)
78
model: <class 'modeltests.m2m_signals.models.Part'>
79
objects: [<Part: Doors>, <Part: Engine>, <Part: Wheelset>]
84
model: <class 'modeltests.m2m_signals.models.Part'>
85
objects: [<Part: Doors>, <Part: Engine>, <Part: Wheelset>]
87
# give the BMW and Toyata some doors as well
88
>>> p2.car_set.add(c2, c3)
93
model: <class 'modeltests.m2m_signals.models.Car'>
94
objects: [<Car: BMW>, <Car: Toyota>]
99
model: <class 'modeltests.m2m_signals.models.Car'>
100
objects: [<Car: BMW>, <Car: Toyota>]
102
# remove the engine from the VW and the airbag (which is not set but is returned)
103
>>> c1.default_parts.remove(p3, p4)
108
model: <class 'modeltests.m2m_signals.models.Part'>
109
objects: [<Part: Airbag>, <Part: Engine>]
114
model: <class 'modeltests.m2m_signals.models.Part'>
115
objects: [<Part: Airbag>, <Part: Engine>]
117
# give the VW some optional parts (second relation to same model)
118
>>> c1.optional_parts.add(p4,p5)
123
model: <class 'modeltests.m2m_signals.models.Part'>
124
objects: [<Part: Airbag>, <Part: Sunroof>]
129
model: <class 'modeltests.m2m_signals.models.Part'>
130
objects: [<Part: Airbag>, <Part: Sunroof>]
132
# add airbag to all the cars (even though the VW already has one)
133
>>> p4.cars_optional.add(c1, c2, c3)
138
model: <class 'modeltests.m2m_signals.models.Car'>
139
objects: [<Car: BMW>, <Car: Toyota>]
144
model: <class 'modeltests.m2m_signals.models.Car'>
145
objects: [<Car: BMW>, <Car: Toyota>]
147
# remove airbag from the VW (reverse relation with custom related_name)
148
>>> p4.cars_optional.remove(c1)
153
model: <class 'modeltests.m2m_signals.models.Car'>
159
model: <class 'modeltests.m2m_signals.models.Car'>
162
# clear all parts of the VW
163
>>> c1.default_parts.clear()
168
model: <class 'modeltests.m2m_signals.models.Part'>
173
model: <class 'modeltests.m2m_signals.models.Part'>
175
# take all the doors off of cars
176
>>> p2.car_set.clear()
181
model: <class 'modeltests.m2m_signals.models.Car'>
186
model: <class 'modeltests.m2m_signals.models.Car'>
188
# take all the airbags off of cars (clear reverse relation with custom related_name)
189
>>> p4.cars_optional.clear()
194
model: <class 'modeltests.m2m_signals.models.Car'>
199
model: <class 'modeltests.m2m_signals.models.Car'>
201
# alternative ways of setting relation:
203
>>> c1.default_parts.create(name='Windows')
208
model: <class 'modeltests.m2m_signals.models.Part'>
209
objects: [<Part: Windows>]
214
model: <class 'modeltests.m2m_signals.models.Part'>
215
objects: [<Part: Windows>]
218
# direct assignment clears the set first, then adds
219
>>> c1.default_parts = [p1,p2,p3]
224
model: <class 'modeltests.m2m_signals.models.Part'>
229
model: <class 'modeltests.m2m_signals.models.Part'>
234
model: <class 'modeltests.m2m_signals.models.Part'>
235
objects: [<Part: Doors>, <Part: Engine>, <Part: Wheelset>]
240
model: <class 'modeltests.m2m_signals.models.Part'>
241
objects: [<Part: Doors>, <Part: Engine>, <Part: Wheelset>]
243
# Check that signals still work when model inheritance is involved
244
>>> c4 = SportsCar.objects.create(name='Bugatti', price='1000000')
245
>>> c4.default_parts = [p2]
250
model: <class 'modeltests.m2m_signals.models.Part'>
255
model: <class 'modeltests.m2m_signals.models.Part'>
260
model: <class 'modeltests.m2m_signals.models.Part'>
261
objects: [<Part: Doors>]
266
model: <class 'modeltests.m2m_signals.models.Part'>
267
objects: [<Part: Doors>]
269
>>> p3.car_set.add(c4)
274
model: <class 'modeltests.m2m_signals.models.Car'>
275
objects: [<Car: Bugatti>]
280
model: <class 'modeltests.m2m_signals.models.Car'>
281
objects: [<Car: Bugatti>]
283
# Now test m2m relations with self
284
>>> p1 = Person.objects.create(name='Alice')
285
>>> p2 = Person.objects.create(name='Bob')
286
>>> p3 = Person.objects.create(name='Chuck')
287
>>> p4 = Person.objects.create(name='Daisy')
289
>>> models.signals.m2m_changed.connect(m2m_changed_test, Person.fans.through)
290
>>> models.signals.m2m_changed.connect(m2m_changed_test, Person.friends.through)
292
>>> p1.friends = [p2, p3]
297
model: <class 'modeltests.m2m_signals.models.Person'>
302
model: <class 'modeltests.m2m_signals.models.Person'>
307
model: <class 'modeltests.m2m_signals.models.Person'>
308
objects: [<Person: Bob>, <Person: Chuck>]
313
model: <class 'modeltests.m2m_signals.models.Person'>
314
objects: [<Person: Bob>, <Person: Chuck>]
321
model: <class 'modeltests.m2m_signals.models.Person'>
326
model: <class 'modeltests.m2m_signals.models.Person'>
331
model: <class 'modeltests.m2m_signals.models.Person'>
332
objects: [<Person: Daisy>]
337
model: <class 'modeltests.m2m_signals.models.Person'>
338
objects: [<Person: Daisy>]
340
>>> p3.idols = [p1,p2]
345
model: <class 'modeltests.m2m_signals.models.Person'>
350
model: <class 'modeltests.m2m_signals.models.Person'>
355
model: <class 'modeltests.m2m_signals.models.Person'>
356
objects: [<Person: Alice>, <Person: Bob>]
361
model: <class 'modeltests.m2m_signals.models.Person'>
362
objects: [<Person: Alice>, <Person: Bob>]
364
# Cleanup - disconnect all signal handlers
365
>>> models.signals.m2m_changed.disconnect(m2m_changed_test, Car.default_parts.through)
366
>>> models.signals.m2m_changed.disconnect(m2m_changed_test, Car.optional_parts.through)
367
>>> models.signals.m2m_changed.disconnect(m2m_changed_test, Person.fans.through)
368
>>> models.signals.m2m_changed.disconnect(m2m_changed_test, Person.friends.through)