""" Testing signals emitted on changing m2m relations. """ from django.db import models class Part(models.Model): name = models.CharField(max_length=20) class Meta: ordering = ('name',) def __unicode__(self): return self.name class Car(models.Model): name = models.CharField(max_length=20) default_parts = models.ManyToManyField(Part) optional_parts = models.ManyToManyField(Part, related_name='cars_optional') class Meta: ordering = ('name',) def __unicode__(self): return self.name class SportsCar(Car): price = models.IntegerField() class Person(models.Model): name = models.CharField(max_length=20) fans = models.ManyToManyField('self', related_name='idols', symmetrical=False) friends = models.ManyToManyField('self') class Meta: ordering = ('name',) def __unicode__(self): return self.name def m2m_changed_test(signal, sender, **kwargs): print 'm2m_changed signal' print 'instance:', kwargs['instance'] print 'action:', kwargs['action'] print 'reverse:', kwargs['reverse'] print 'model:', kwargs['model'] if kwargs['pk_set']: print 'objects:',kwargs['model'].objects.filter(pk__in=kwargs['pk_set']) __test__ = {'API_TESTS':""" # Install a listener on one of the two m2m relations. >>> models.signals.m2m_changed.connect(m2m_changed_test, Car.optional_parts.through) # Test the add, remove and clear methods on both sides of the # many-to-many relation >>> c1 = Car.objects.create(name='VW') >>> c2 = Car.objects.create(name='BMW') >>> c3 = Car.objects.create(name='Toyota') >>> p1 = Part.objects.create(name='Wheelset') >>> p2 = Part.objects.create(name='Doors') >>> p3 = Part.objects.create(name='Engine') >>> p4 = Part.objects.create(name='Airbag') >>> p5 = Part.objects.create(name='Sunroof') # adding a default part to our car - no signal listener installed >>> c1.default_parts.add(p5) # Now install a listener >>> models.signals.m2m_changed.connect(m2m_changed_test, Car.default_parts.through) >>> c1.default_parts.add(p1, p2, p3) m2m_changed signal instance: VW action: add reverse: False model: objects: [, , ] # give the BMW and Toyata some doors as well >>> p2.car_set.add(c2, c3) m2m_changed signal instance: Doors action: add reverse: True model: objects: [, ] # remove the engine from the VW and the airbag (which is not set but is returned) >>> c1.default_parts.remove(p3, p4) m2m_changed signal instance: VW action: remove reverse: False model: objects: [, ] # give the VW some optional parts (second relation to same model) >>> c1.optional_parts.add(p4,p5) m2m_changed signal instance: VW action: add reverse: False model: objects: [, ] # add airbag to all the cars (even though the VW already has one) >>> p4.cars_optional.add(c1, c2, c3) m2m_changed signal instance: Airbag action: add reverse: True model: objects: [, ] # remove airbag from the VW (reverse relation with custom related_name) >>> p4.cars_optional.remove(c1) m2m_changed signal instance: Airbag action: remove reverse: True model: objects: [] # clear all parts of the VW >>> c1.default_parts.clear() m2m_changed signal instance: VW action: clear reverse: False model: # take all the doors off of cars >>> p2.car_set.clear() m2m_changed signal instance: Doors action: clear reverse: True model: # 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 reverse: True model: # alternative ways of setting relation: >>> c1.default_parts.create(name='Windows') m2m_changed signal instance: VW action: add reverse: False model: objects: [] # direct assignment clears the set first, then adds >>> c1.default_parts = [p1,p2,p3] m2m_changed signal instance: VW action: clear reverse: False model: m2m_changed signal instance: VW action: add reverse: False model: objects: [, , ] # Check that signals still work when model inheritance is involved >>> c4 = SportsCar.objects.create(name='Bugatti', price='1000000') >>> c4.default_parts = [p2] m2m_changed signal instance: Bugatti action: clear reverse: False model: m2m_changed signal instance: Bugatti action: add reverse: False model: objects: [] >>> p3.car_set.add(c4) m2m_changed signal instance: Engine action: add reverse: True model: objects: [] # Now test m2m relations with self >>> p1 = Person.objects.create(name='Alice') >>> p2 = Person.objects.create(name='Bob') >>> p3 = Person.objects.create(name='Chuck') >>> p4 = Person.objects.create(name='Daisy') >>> models.signals.m2m_changed.connect(m2m_changed_test, Person.fans.through) >>> models.signals.m2m_changed.connect(m2m_changed_test, Person.friends.through) >>> p1.friends = [p2, p3] m2m_changed signal instance: Alice action: clear reverse: False model: m2m_changed signal instance: Alice action: add reverse: False model: objects: [, ] >>> p1.fans = [p4] m2m_changed signal instance: Alice action: clear reverse: False model: m2m_changed signal instance: Alice action: add reverse: False model: objects: [] >>> p3.idols = [p1,p2] m2m_changed signal instance: Chuck action: clear reverse: True model: m2m_changed signal instance: Chuck action: add reverse: True model: objects: [, ] # Cleanup - disconnect all signal handlers >>> models.signals.m2m_changed.disconnect(m2m_changed_test, Car.default_parts.through) >>> models.signals.m2m_changed.disconnect(m2m_changed_test, Car.optional_parts.through) >>> models.signals.m2m_changed.disconnect(m2m_changed_test, Person.fans.through) >>> models.signals.m2m_changed.disconnect(m2m_changed_test, Person.friends.through) """}