PageRenderTime 45ms CodeModel.GetById 21ms app.highlight 20ms RepoModel.GetById 1ms app.codeStats 0ms

/tests/modeltests/m2m_signals/tests.py

https://code.google.com/p/mango-py/
Python | 427 lines | 373 code | 31 blank | 23 comment | 1 complexity | 24156599874c55bcb76db4df564aab18 MD5 | raw file
  1"""
  2Testing signals emitted on changing m2m relations.
  3"""
  4
  5from django.db import models
  6from django.test import TestCase
  7
  8from models import Part, Car, SportsCar, Person
  9
 10
 11class ManyToManySignalsTest(TestCase):
 12    def m2m_changed_signal_receiver(self, signal, sender, **kwargs):
 13        message = {
 14            'instance': kwargs['instance'],
 15            'action': kwargs['action'],
 16            'reverse': kwargs['reverse'],
 17            'model': kwargs['model'],
 18        }
 19        if kwargs['pk_set']:
 20            message['objects'] = list(
 21                kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
 22            )
 23        self.m2m_changed_messages.append(message)
 24
 25    def setUp(self):
 26        self.m2m_changed_messages = []
 27
 28        self.vw = Car.objects.create(name='VW')
 29        self.bmw = Car.objects.create(name='BMW')
 30        self.toyota = Car.objects.create(name='Toyota')
 31        self.wheelset = Part.objects.create(name='Wheelset')
 32        self.doors = Part.objects.create(name='Doors')
 33        self.engine = Part.objects.create(name='Engine')
 34        self.airbag = Part.objects.create(name='Airbag')
 35        self.sunroof = Part.objects.create(name='Sunroof')
 36
 37        self.alice = Person.objects.create(name='Alice')
 38        self.bob = Person.objects.create(name='Bob')
 39        self.chuck = Person.objects.create(name='Chuck')
 40        self.daisy = Person.objects.create(name='Daisy')
 41
 42    def tearDown(self):
 43        # disconnect all signal handlers
 44        models.signals.m2m_changed.disconnect(
 45            self.m2m_changed_signal_receiver, Car.default_parts.through
 46        )
 47        models.signals.m2m_changed.disconnect(
 48            self.m2m_changed_signal_receiver, Car.optional_parts.through
 49        )
 50        models.signals.m2m_changed.disconnect(
 51            self.m2m_changed_signal_receiver, Person.fans.through
 52        )
 53        models.signals.m2m_changed.disconnect(
 54            self.m2m_changed_signal_receiver, Person.friends.through
 55        )
 56
 57    def test_m2m_relations_add_remove_clear(self):
 58        expected_messages = []
 59
 60        # Install a listener on one of the two m2m relations.
 61        models.signals.m2m_changed.connect(
 62            self.m2m_changed_signal_receiver, Car.optional_parts.through
 63        )
 64
 65        # Test the add, remove and clear methods on both sides of the
 66        # many-to-many relation
 67
 68        # adding a default part to our car - no signal listener installed
 69        self.vw.default_parts.add(self.sunroof)
 70
 71        # Now install a listener
 72        models.signals.m2m_changed.connect(
 73            self.m2m_changed_signal_receiver, Car.default_parts.through
 74        )
 75
 76        self.vw.default_parts.add(self.wheelset, self.doors, self.engine)
 77        expected_messages.append({
 78            'instance': self.vw,
 79            'action': 'pre_add',
 80            'reverse': False,
 81            'model': Part,
 82            'objects': [self.doors, self.engine, self.wheelset],
 83        })
 84        expected_messages.append({
 85            'instance': self.vw,
 86            'action': 'post_add',
 87            'reverse': False,
 88            'model': Part,
 89            'objects': [self.doors, self.engine, self.wheelset],
 90        })
 91        self.assertEqual(self.m2m_changed_messages, expected_messages)
 92
 93        # give the BMW and Toyata some doors as well
 94        self.doors.car_set.add(self.bmw, self.toyota)
 95        expected_messages.append({
 96            'instance': self.doors,
 97            'action': 'pre_add',
 98            'reverse': True,
 99            'model': Car,
100            'objects': [self.bmw, self.toyota],
101        })
102        expected_messages.append({
103            'instance': self.doors,
104            'action': 'post_add',
105            'reverse': True,
106            'model': Car,
107            'objects': [self.bmw, self.toyota],
108        })
109        self.assertEqual(self.m2m_changed_messages, expected_messages)
110
111        # remove the engine from the self.vw and the airbag (which is not set
112        # but is returned)
113        self.vw.default_parts.remove(self.engine, self.airbag)
114        expected_messages.append({
115            'instance': self.vw,
116            'action': 'pre_remove',
117            'reverse': False,
118            'model': Part,
119            'objects': [self.airbag, self.engine],
120        })
121        expected_messages.append({
122            'instance': self.vw,
123            'action': 'post_remove',
124            'reverse': False,
125            'model': Part,
126            'objects': [self.airbag, self.engine],
127        })
128        self.assertEqual(self.m2m_changed_messages, expected_messages)
129
130        # give the self.vw some optional parts (second relation to same model)
131        self.vw.optional_parts.add(self.airbag, self.sunroof)
132        expected_messages.append({
133            'instance': self.vw,
134            'action': 'pre_add',
135            'reverse': False,
136            'model': Part,
137            'objects': [self.airbag, self.sunroof],
138        })
139        expected_messages.append({
140            'instance': self.vw,
141            'action': 'post_add',
142            'reverse': False,
143            'model': Part,
144            'objects': [self.airbag, self.sunroof],
145        })
146        self.assertEqual(self.m2m_changed_messages, expected_messages)
147
148        # add airbag to all the cars (even though the self.vw already has one)
149        self.airbag.cars_optional.add(self.vw, self.bmw, self.toyota)
150        expected_messages.append({
151            'instance': self.airbag,
152            'action': 'pre_add',
153            'reverse': True,
154            'model': Car,
155            'objects': [self.bmw, self.toyota],
156        })
157        expected_messages.append({
158            'instance': self.airbag,
159            'action': 'post_add',
160            'reverse': True,
161            'model': Car,
162            'objects': [self.bmw, self.toyota],
163        })
164        self.assertEqual(self.m2m_changed_messages, expected_messages)
165
166        # remove airbag from the self.vw (reverse relation with custom
167        # related_name)
168        self.airbag.cars_optional.remove(self.vw)
169        expected_messages.append({
170            'instance': self.airbag,
171            'action': 'pre_remove',
172            'reverse': True,
173            'model': Car,
174            'objects': [self.vw],
175        })
176        expected_messages.append({
177            'instance': self.airbag,
178            'action': 'post_remove',
179            'reverse': True,
180            'model': Car,
181            'objects': [self.vw],
182        })
183        self.assertEqual(self.m2m_changed_messages, expected_messages)
184
185        # clear all parts of the self.vw
186        self.vw.default_parts.clear()
187        expected_messages.append({
188            'instance': self.vw,
189            'action': 'pre_clear',
190            'reverse': False,
191            'model': Part,
192        })
193        expected_messages.append({
194            'instance': self.vw,
195            'action': 'post_clear',
196            'reverse': False,
197            'model': Part,
198        })
199        self.assertEqual(self.m2m_changed_messages, expected_messages)
200
201        # take all the doors off of cars
202        self.doors.car_set.clear()
203        expected_messages.append({
204            'instance': self.doors,
205            'action': 'pre_clear',
206            'reverse': True,
207            'model': Car,
208        })
209        expected_messages.append({
210            'instance': self.doors,
211            'action': 'post_clear',
212            'reverse': True,
213            'model': Car,
214        })
215        self.assertEqual(self.m2m_changed_messages, expected_messages)
216
217        # take all the airbags off of cars (clear reverse relation with custom
218        # related_name)
219        self.airbag.cars_optional.clear()
220        expected_messages.append({
221            'instance': self.airbag,
222            'action': 'pre_clear',
223            'reverse': True,
224            'model': Car,
225        })
226        expected_messages.append({
227            'instance': self.airbag,
228            'action': 'post_clear',
229            'reverse': True,
230            'model': Car,
231        })
232        self.assertEqual(self.m2m_changed_messages, expected_messages)
233
234        # alternative ways of setting relation:
235        self.vw.default_parts.create(name='Windows')
236        p6 = Part.objects.get(name='Windows')
237        expected_messages.append({
238            'instance': self.vw,
239            'action': 'pre_add',
240            'reverse': False,
241            'model': Part,
242            'objects': [p6],
243        })
244        expected_messages.append({
245            'instance': self.vw,
246            'action': 'post_add',
247            'reverse': False,
248            'model': Part,
249            'objects': [p6],
250        })
251        self.assertEqual(self.m2m_changed_messages, expected_messages)
252
253        # direct assignment clears the set first, then adds
254        self.vw.default_parts = [self.wheelset,self.doors,self.engine]
255        expected_messages.append({
256            'instance': self.vw,
257            'action': 'pre_clear',
258            'reverse': False,
259            'model': Part,
260        })
261        expected_messages.append({
262            'instance': self.vw,
263            'action': 'post_clear',
264            'reverse': False,
265            'model': Part,
266        })
267        expected_messages.append({
268            'instance': self.vw,
269            'action': 'pre_add',
270            'reverse': False,
271            'model': Part,
272            'objects': [self.doors, self.engine, self.wheelset],
273        })
274        expected_messages.append({
275            'instance': self.vw,
276            'action': 'post_add',
277            'reverse': False,
278            'model': Part,
279            'objects': [self.doors, self.engine, self.wheelset],
280        })
281        self.assertEqual(self.m2m_changed_messages, expected_messages)
282
283        # Check that signals still work when model inheritance is involved
284        c4 = SportsCar.objects.create(name='Bugatti', price='1000000')
285        c4b = Car.objects.get(name='Bugatti')
286        c4.default_parts = [self.doors]
287        expected_messages.append({
288            'instance': c4,
289            'action': 'pre_clear',
290            'reverse': False,
291            'model': Part,
292        })
293        expected_messages.append({
294            'instance': c4,
295            'action': 'post_clear',
296            'reverse': False,
297            'model': Part,
298        })
299        expected_messages.append({
300            'instance': c4,
301            'action': 'pre_add',
302            'reverse': False,
303            'model': Part,
304            'objects': [self.doors],
305        })
306        expected_messages.append({
307            'instance': c4,
308            'action': 'post_add',
309            'reverse': False,
310            'model': Part,
311            'objects': [self.doors],
312        })
313        self.assertEqual(self.m2m_changed_messages, expected_messages)
314
315        self.engine.car_set.add(c4)
316        expected_messages.append({
317            'instance': self.engine,
318            'action': 'pre_add',
319            'reverse': True,
320            'model': Car,
321            'objects': [c4b],
322        })
323        expected_messages.append({
324            'instance': self.engine,
325            'action': 'post_add',
326            'reverse': True,
327            'model': Car,
328            'objects': [c4b],
329        })
330        self.assertEqual(self.m2m_changed_messages, expected_messages)
331
332    def test_m2m_relations_with_self(self):
333        expected_messages = []
334
335        models.signals.m2m_changed.connect(
336            self.m2m_changed_signal_receiver, Person.fans.through
337        )
338        models.signals.m2m_changed.connect(
339            self.m2m_changed_signal_receiver, Person.friends.through
340        )
341
342        self.alice.friends = [self.bob, self.chuck]
343        expected_messages.append({
344            'instance': self.alice,
345            'action': 'pre_clear',
346            'reverse': False,
347            'model': Person,
348        })
349        expected_messages.append({
350            'instance': self.alice,
351            'action': 'post_clear',
352            'reverse': False,
353            'model': Person,
354        })
355        expected_messages.append({
356            'instance': self.alice,
357            'action': 'pre_add',
358            'reverse': False,
359            'model': Person,
360            'objects': [self.bob, self.chuck],
361        })
362        expected_messages.append({
363            'instance': self.alice,
364            'action': 'post_add',
365            'reverse': False,
366            'model': Person,
367            'objects': [self.bob, self.chuck],
368        })
369        self.assertEqual(self.m2m_changed_messages, expected_messages)
370
371        self.alice.fans = [self.daisy]
372        expected_messages.append({
373            'instance': self.alice,
374            'action': 'pre_clear',
375            'reverse': False,
376            'model': Person,
377        })
378        expected_messages.append({
379            'instance': self.alice,
380            'action': 'post_clear',
381            'reverse': False,
382            'model': Person,
383        })
384        expected_messages.append({
385            'instance': self.alice,
386            'action': 'pre_add',
387            'reverse': False,
388            'model': Person,
389            'objects': [self.daisy],
390        })
391        expected_messages.append({
392            'instance': self.alice,
393            'action': 'post_add',
394            'reverse': False,
395            'model': Person,
396            'objects': [self.daisy],
397        })
398        self.assertEqual(self.m2m_changed_messages, expected_messages)
399
400        self.chuck.idols = [self.alice,self.bob]
401        expected_messages.append({
402            'instance': self.chuck,
403            'action': 'pre_clear',
404            'reverse': True,
405            'model': Person,
406        })
407        expected_messages.append({
408            'instance': self.chuck,
409            'action': 'post_clear',
410            'reverse': True,
411            'model': Person,
412        })
413        expected_messages.append({
414            'instance': self.chuck,
415            'action': 'pre_add',
416            'reverse': True,
417            'model': Person,
418            'objects': [self.alice, self.bob],
419        })
420        expected_messages.append({
421            'instance': self.chuck,
422            'action': 'post_add',
423            'reverse': True,
424            'model': Person,
425            'objects': [self.alice, self.bob],
426        })
427        self.assertEqual(self.m2m_changed_messages, expected_messages)