Converted m2m_signals from doctests to unittests. Thanks to Gregor Müllegger for the patch. We have always been at war with doctests.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@14548 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
8a7a44ffa2
commit
102d230f94
|
@ -1,9 +1,6 @@
|
|||
"""
|
||||
Testing signals emitted on changing m2m relations.
|
||||
"""
|
||||
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Part(models.Model):
|
||||
name = models.CharField(max_length=20)
|
||||
|
||||
|
@ -37,334 +34,3 @@ class Person(models.Model):
|
|||
|
||||
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: 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>]
|
||||
|
||||
# give the BMW and Toyata some doors as well
|
||||
>>> p2.car_set.add(c2, c3)
|
||||
m2m_changed signal
|
||||
instance: Doors
|
||||
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>]
|
||||
|
||||
# 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: 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>]
|
||||
|
||||
# give the VW some optional parts (second relation to same model)
|
||||
>>> c1.optional_parts.add(p4,p5)
|
||||
m2m_changed signal
|
||||
instance: VW
|
||||
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>]
|
||||
|
||||
# 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: 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>]
|
||||
|
||||
# remove airbag from the VW (reverse relation with custom related_name)
|
||||
>>> p4.cars_optional.remove(c1)
|
||||
m2m_changed signal
|
||||
instance: Airbag
|
||||
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>]
|
||||
|
||||
# clear all parts of the VW
|
||||
>>> c1.default_parts.clear()
|
||||
m2m_changed signal
|
||||
instance: VW
|
||||
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: 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: 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'>
|
||||
|
||||
# alternative ways of setting relation:
|
||||
|
||||
>>> c1.default_parts.create(name='Windows')
|
||||
m2m_changed signal
|
||||
instance: VW
|
||||
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>]
|
||||
<Part: Windows>
|
||||
|
||||
# direct assignment clears the set first, then adds
|
||||
>>> c1.default_parts = [p1,p2,p3]
|
||||
m2m_changed signal
|
||||
instance: VW
|
||||
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'>
|
||||
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>]
|
||||
|
||||
# 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: 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: 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: 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>]
|
||||
|
||||
# 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: 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: 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: 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: post_add
|
||||
reverse: False
|
||||
model: <class 'modeltests.m2m_signals.models.Person'>
|
||||
objects: [<Person: Daisy>]
|
||||
|
||||
>>> p3.idols = [p1,p2]
|
||||
m2m_changed signal
|
||||
instance: Chuck
|
||||
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: post_add
|
||||
reverse: True
|
||||
model: <class 'modeltests.m2m_signals.models.Person'>
|
||||
objects: [<Person: Alice>, <Person: Bob>]
|
||||
|
||||
# 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)
|
||||
|
||||
"""}
|
||||
|
|
|
@ -0,0 +1,427 @@
|
|||
"""
|
||||
Testing signals emitted on changing m2m relations.
|
||||
"""
|
||||
|
||||
from django.db import models
|
||||
from django.test import TestCase
|
||||
|
||||
from models import Part, Car, SportsCar, Person
|
||||
|
||||
|
||||
class ManyToManySignalsTest(TestCase):
|
||||
def m2m_changed_signal_receiver(self, signal, sender, **kwargs):
|
||||
message = {
|
||||
'instance': kwargs['instance'],
|
||||
'action': kwargs['action'],
|
||||
'reverse': kwargs['reverse'],
|
||||
'model': kwargs['model'],
|
||||
}
|
||||
if kwargs['pk_set']:
|
||||
message['objects'] = list(
|
||||
kwargs['model'].objects.filter(pk__in=kwargs['pk_set'])
|
||||
)
|
||||
self.m2m_changed_messages.append(message)
|
||||
|
||||
def setUp(self):
|
||||
self.m2m_changed_messages = []
|
||||
|
||||
self.vw = Car.objects.create(name='VW')
|
||||
self.bmw = Car.objects.create(name='BMW')
|
||||
self.toyota = Car.objects.create(name='Toyota')
|
||||
self.wheelset = Part.objects.create(name='Wheelset')
|
||||
self.doors = Part.objects.create(name='Doors')
|
||||
self.engine = Part.objects.create(name='Engine')
|
||||
self.airbag = Part.objects.create(name='Airbag')
|
||||
self.sunroof = Part.objects.create(name='Sunroof')
|
||||
|
||||
self.alice = Person.objects.create(name='Alice')
|
||||
self.bob = Person.objects.create(name='Bob')
|
||||
self.chuck = Person.objects.create(name='Chuck')
|
||||
self.daisy = Person.objects.create(name='Daisy')
|
||||
|
||||
def tearDown(self):
|
||||
# disconnect all signal handlers
|
||||
models.signals.m2m_changed.disconnect(
|
||||
self.m2m_changed_signal_receiver, Car.default_parts.through
|
||||
)
|
||||
models.signals.m2m_changed.disconnect(
|
||||
self.m2m_changed_signal_receiver, Car.optional_parts.through
|
||||
)
|
||||
models.signals.m2m_changed.disconnect(
|
||||
self.m2m_changed_signal_receiver, Person.fans.through
|
||||
)
|
||||
models.signals.m2m_changed.disconnect(
|
||||
self.m2m_changed_signal_receiver, Person.friends.through
|
||||
)
|
||||
|
||||
def test_m2m_relations_add_remove_clear(self):
|
||||
expected_messages = []
|
||||
|
||||
# Install a listener on one of the two m2m relations.
|
||||
models.signals.m2m_changed.connect(
|
||||
self.m2m_changed_signal_receiver, Car.optional_parts.through
|
||||
)
|
||||
|
||||
# Test the add, remove and clear methods on both sides of the
|
||||
# many-to-many relation
|
||||
|
||||
# adding a default part to our car - no signal listener installed
|
||||
self.vw.default_parts.add(self.sunroof)
|
||||
|
||||
# Now install a listener
|
||||
models.signals.m2m_changed.connect(
|
||||
self.m2m_changed_signal_receiver, Car.default_parts.through
|
||||
)
|
||||
|
||||
self.vw.default_parts.add(self.wheelset, self.doors, self.engine)
|
||||
expected_messages.append({
|
||||
'instance': self.vw,
|
||||
'action': 'pre_add',
|
||||
'reverse': False,
|
||||
'model': Part,
|
||||
'objects': [self.doors, self.engine, self.wheelset],
|
||||
})
|
||||
expected_messages.append({
|
||||
'instance': self.vw,
|
||||
'action': 'post_add',
|
||||
'reverse': False,
|
||||
'model': Part,
|
||||
'objects': [self.doors, self.engine, self.wheelset],
|
||||
})
|
||||
self.assertEqual(self.m2m_changed_messages, expected_messages)
|
||||
|
||||
# give the BMW and Toyata some doors as well
|
||||
self.doors.car_set.add(self.bmw, self.toyota)
|
||||
expected_messages.append({
|
||||
'instance': self.doors,
|
||||
'action': 'pre_add',
|
||||
'reverse': True,
|
||||
'model': Car,
|
||||
'objects': [self.bmw, self.toyota],
|
||||
})
|
||||
expected_messages.append({
|
||||
'instance': self.doors,
|
||||
'action': 'post_add',
|
||||
'reverse': True,
|
||||
'model': Car,
|
||||
'objects': [self.bmw, self.toyota],
|
||||
})
|
||||
self.assertEqual(self.m2m_changed_messages, expected_messages)
|
||||
|
||||
# remove the engine from the self.vw and the airbag (which is not set
|
||||
# but is returned)
|
||||
self.vw.default_parts.remove(self.engine, self.airbag)
|
||||
expected_messages.append({
|
||||
'instance': self.vw,
|
||||
'action': 'pre_remove',
|
||||
'reverse': False,
|
||||
'model': Part,
|
||||
'objects': [self.airbag, self.engine],
|
||||
})
|
||||
expected_messages.append({
|
||||
'instance': self.vw,
|
||||
'action': 'post_remove',
|
||||
'reverse': False,
|
||||
'model': Part,
|
||||
'objects': [self.airbag, self.engine],
|
||||
})
|
||||
self.assertEqual(self.m2m_changed_messages, expected_messages)
|
||||
|
||||
# give the self.vw some optional parts (second relation to same model)
|
||||
self.vw.optional_parts.add(self.airbag, self.sunroof)
|
||||
expected_messages.append({
|
||||
'instance': self.vw,
|
||||
'action': 'pre_add',
|
||||
'reverse': False,
|
||||
'model': Part,
|
||||
'objects': [self.airbag, self.sunroof],
|
||||
})
|
||||
expected_messages.append({
|
||||
'instance': self.vw,
|
||||
'action': 'post_add',
|
||||
'reverse': False,
|
||||
'model': Part,
|
||||
'objects': [self.airbag, self.sunroof],
|
||||
})
|
||||
self.assertEqual(self.m2m_changed_messages, expected_messages)
|
||||
|
||||
# add airbag to all the cars (even though the self.vw already has one)
|
||||
self.airbag.cars_optional.add(self.vw, self.bmw, self.toyota)
|
||||
expected_messages.append({
|
||||
'instance': self.airbag,
|
||||
'action': 'pre_add',
|
||||
'reverse': True,
|
||||
'model': Car,
|
||||
'objects': [self.bmw, self.toyota],
|
||||
})
|
||||
expected_messages.append({
|
||||
'instance': self.airbag,
|
||||
'action': 'post_add',
|
||||
'reverse': True,
|
||||
'model': Car,
|
||||
'objects': [self.bmw, self.toyota],
|
||||
})
|
||||
self.assertEqual(self.m2m_changed_messages, expected_messages)
|
||||
|
||||
# remove airbag from the self.vw (reverse relation with custom
|
||||
# related_name)
|
||||
self.airbag.cars_optional.remove(self.vw)
|
||||
expected_messages.append({
|
||||
'instance': self.airbag,
|
||||
'action': 'pre_remove',
|
||||
'reverse': True,
|
||||
'model': Car,
|
||||
'objects': [self.vw],
|
||||
})
|
||||
expected_messages.append({
|
||||
'instance': self.airbag,
|
||||
'action': 'post_remove',
|
||||
'reverse': True,
|
||||
'model': Car,
|
||||
'objects': [self.vw],
|
||||
})
|
||||
self.assertEqual(self.m2m_changed_messages, expected_messages)
|
||||
|
||||
# clear all parts of the self.vw
|
||||
self.vw.default_parts.clear()
|
||||
expected_messages.append({
|
||||
'instance': self.vw,
|
||||
'action': 'pre_clear',
|
||||
'reverse': False,
|
||||
'model': Part,
|
||||
})
|
||||
expected_messages.append({
|
||||
'instance': self.vw,
|
||||
'action': 'post_clear',
|
||||
'reverse': False,
|
||||
'model': Part,
|
||||
})
|
||||
self.assertEqual(self.m2m_changed_messages, expected_messages)
|
||||
|
||||
# take all the doors off of cars
|
||||
self.doors.car_set.clear()
|
||||
expected_messages.append({
|
||||
'instance': self.doors,
|
||||
'action': 'pre_clear',
|
||||
'reverse': True,
|
||||
'model': Car,
|
||||
})
|
||||
expected_messages.append({
|
||||
'instance': self.doors,
|
||||
'action': 'post_clear',
|
||||
'reverse': True,
|
||||
'model': Car,
|
||||
})
|
||||
self.assertEqual(self.m2m_changed_messages, expected_messages)
|
||||
|
||||
# take all the airbags off of cars (clear reverse relation with custom
|
||||
# related_name)
|
||||
self.airbag.cars_optional.clear()
|
||||
expected_messages.append({
|
||||
'instance': self.airbag,
|
||||
'action': 'pre_clear',
|
||||
'reverse': True,
|
||||
'model': Car,
|
||||
})
|
||||
expected_messages.append({
|
||||
'instance': self.airbag,
|
||||
'action': 'post_clear',
|
||||
'reverse': True,
|
||||
'model': Car,
|
||||
})
|
||||
self.assertEqual(self.m2m_changed_messages, expected_messages)
|
||||
|
||||
# alternative ways of setting relation:
|
||||
self.vw.default_parts.create(name='Windows')
|
||||
p6 = Part.objects.get(name='Windows')
|
||||
expected_messages.append({
|
||||
'instance': self.vw,
|
||||
'action': 'pre_add',
|
||||
'reverse': False,
|
||||
'model': Part,
|
||||
'objects': [p6],
|
||||
})
|
||||
expected_messages.append({
|
||||
'instance': self.vw,
|
||||
'action': 'post_add',
|
||||
'reverse': False,
|
||||
'model': Part,
|
||||
'objects': [p6],
|
||||
})
|
||||
self.assertEqual(self.m2m_changed_messages, expected_messages)
|
||||
|
||||
# direct assignment clears the set first, then adds
|
||||
self.vw.default_parts = [self.wheelset,self.doors,self.engine]
|
||||
expected_messages.append({
|
||||
'instance': self.vw,
|
||||
'action': 'pre_clear',
|
||||
'reverse': False,
|
||||
'model': Part,
|
||||
})
|
||||
expected_messages.append({
|
||||
'instance': self.vw,
|
||||
'action': 'post_clear',
|
||||
'reverse': False,
|
||||
'model': Part,
|
||||
})
|
||||
expected_messages.append({
|
||||
'instance': self.vw,
|
||||
'action': 'pre_add',
|
||||
'reverse': False,
|
||||
'model': Part,
|
||||
'objects': [self.doors, self.engine, self.wheelset],
|
||||
})
|
||||
expected_messages.append({
|
||||
'instance': self.vw,
|
||||
'action': 'post_add',
|
||||
'reverse': False,
|
||||
'model': Part,
|
||||
'objects': [self.doors, self.engine, self.wheelset],
|
||||
})
|
||||
self.assertEqual(self.m2m_changed_messages, expected_messages)
|
||||
|
||||
# Check that signals still work when model inheritance is involved
|
||||
c4 = SportsCar.objects.create(name='Bugatti', price='1000000')
|
||||
c4b = Car.objects.get(name='Bugatti')
|
||||
c4.default_parts = [self.doors]
|
||||
expected_messages.append({
|
||||
'instance': c4,
|
||||
'action': 'pre_clear',
|
||||
'reverse': False,
|
||||
'model': Part,
|
||||
})
|
||||
expected_messages.append({
|
||||
'instance': c4,
|
||||
'action': 'post_clear',
|
||||
'reverse': False,
|
||||
'model': Part,
|
||||
})
|
||||
expected_messages.append({
|
||||
'instance': c4,
|
||||
'action': 'pre_add',
|
||||
'reverse': False,
|
||||
'model': Part,
|
||||
'objects': [self.doors],
|
||||
})
|
||||
expected_messages.append({
|
||||
'instance': c4,
|
||||
'action': 'post_add',
|
||||
'reverse': False,
|
||||
'model': Part,
|
||||
'objects': [self.doors],
|
||||
})
|
||||
self.assertEqual(self.m2m_changed_messages, expected_messages)
|
||||
|
||||
self.engine.car_set.add(c4)
|
||||
expected_messages.append({
|
||||
'instance': self.engine,
|
||||
'action': 'pre_add',
|
||||
'reverse': True,
|
||||
'model': Car,
|
||||
'objects': [c4b],
|
||||
})
|
||||
expected_messages.append({
|
||||
'instance': self.engine,
|
||||
'action': 'post_add',
|
||||
'reverse': True,
|
||||
'model': Car,
|
||||
'objects': [c4b],
|
||||
})
|
||||
self.assertEqual(self.m2m_changed_messages, expected_messages)
|
||||
|
||||
def test_m2m_relations_with_self(self):
|
||||
expected_messages = []
|
||||
|
||||
models.signals.m2m_changed.connect(
|
||||
self.m2m_changed_signal_receiver, Person.fans.through
|
||||
)
|
||||
models.signals.m2m_changed.connect(
|
||||
self.m2m_changed_signal_receiver, Person.friends.through
|
||||
)
|
||||
|
||||
self.alice.friends = [self.bob, self.chuck]
|
||||
expected_messages.append({
|
||||
'instance': self.alice,
|
||||
'action': 'pre_clear',
|
||||
'reverse': False,
|
||||
'model': Person,
|
||||
})
|
||||
expected_messages.append({
|
||||
'instance': self.alice,
|
||||
'action': 'post_clear',
|
||||
'reverse': False,
|
||||
'model': Person,
|
||||
})
|
||||
expected_messages.append({
|
||||
'instance': self.alice,
|
||||
'action': 'pre_add',
|
||||
'reverse': False,
|
||||
'model': Person,
|
||||
'objects': [self.bob, self.chuck],
|
||||
})
|
||||
expected_messages.append({
|
||||
'instance': self.alice,
|
||||
'action': 'post_add',
|
||||
'reverse': False,
|
||||
'model': Person,
|
||||
'objects': [self.bob, self.chuck],
|
||||
})
|
||||
self.assertEqual(self.m2m_changed_messages, expected_messages)
|
||||
|
||||
self.alice.fans = [self.daisy]
|
||||
expected_messages.append({
|
||||
'instance': self.alice,
|
||||
'action': 'pre_clear',
|
||||
'reverse': False,
|
||||
'model': Person,
|
||||
})
|
||||
expected_messages.append({
|
||||
'instance': self.alice,
|
||||
'action': 'post_clear',
|
||||
'reverse': False,
|
||||
'model': Person,
|
||||
})
|
||||
expected_messages.append({
|
||||
'instance': self.alice,
|
||||
'action': 'pre_add',
|
||||
'reverse': False,
|
||||
'model': Person,
|
||||
'objects': [self.daisy],
|
||||
})
|
||||
expected_messages.append({
|
||||
'instance': self.alice,
|
||||
'action': 'post_add',
|
||||
'reverse': False,
|
||||
'model': Person,
|
||||
'objects': [self.daisy],
|
||||
})
|
||||
self.assertEqual(self.m2m_changed_messages, expected_messages)
|
||||
|
||||
self.chuck.idols = [self.alice,self.bob]
|
||||
expected_messages.append({
|
||||
'instance': self.chuck,
|
||||
'action': 'pre_clear',
|
||||
'reverse': True,
|
||||
'model': Person,
|
||||
})
|
||||
expected_messages.append({
|
||||
'instance': self.chuck,
|
||||
'action': 'post_clear',
|
||||
'reverse': True,
|
||||
'model': Person,
|
||||
})
|
||||
expected_messages.append({
|
||||
'instance': self.chuck,
|
||||
'action': 'pre_add',
|
||||
'reverse': True,
|
||||
'model': Person,
|
||||
'objects': [self.alice, self.bob],
|
||||
})
|
||||
expected_messages.append({
|
||||
'instance': self.chuck,
|
||||
'action': 'post_add',
|
||||
'reverse': True,
|
||||
'model': Person,
|
||||
'objects': [self.alice, self.bob],
|
||||
})
|
||||
self.assertEqual(self.m2m_changed_messages, expected_messages)
|
Loading…
Reference in New Issue