1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
|
from io import StringIO
from django.contrib.auth.models import User
from django.core import management
from django.test import TestCase
from .models import (
Car, CarDriver, Driver, Group, Membership, Person, UserMembership,
)
class M2MThroughTestCase(TestCase):
@classmethod
def setUpTestData(cls):
cls.bob = Person.objects.create(name="Bob")
cls.jim = Person.objects.create(name="Jim")
cls.rock = Group.objects.create(name="Rock")
cls.roll = Group.objects.create(name="Roll")
cls.frank = User.objects.create_user("frank", "frank@example.com", "password")
cls.jane = User.objects.create_user("jane", "jane@example.com", "password")
# normal intermediate model
cls.bob_rock = Membership.objects.create(person=cls.bob, group=cls.rock)
cls.bob_roll = Membership.objects.create(person=cls.bob, group=cls.roll, price=50)
cls.jim_rock = Membership.objects.create(person=cls.jim, group=cls.rock, price=50)
# intermediate model with custom id column
cls.frank_rock = UserMembership.objects.create(user=cls.frank, group=cls.rock)
cls.frank_roll = UserMembership.objects.create(user=cls.frank, group=cls.roll)
cls.jane_rock = UserMembership.objects.create(user=cls.jane, group=cls.rock)
def test_retrieve_reverse_m2m_items(self):
self.assertCountEqual(self.bob.group_set.all(), [self.rock, self.roll])
def test_retrieve_forward_m2m_items(self):
self.assertSequenceEqual(self.roll.members.all(), [self.bob])
def test_retrieve_reverse_m2m_items_via_custom_id_intermediary(self):
self.assertCountEqual(self.frank.group_set.all(), [self.rock, self.roll])
def test_retrieve_forward_m2m_items_via_custom_id_intermediary(self):
self.assertSequenceEqual(self.roll.user_members.all(), [self.frank])
def test_join_trimming_forwards(self):
"""
Too many copies of the intermediate table aren't involved when doing a
join (#8046, #8254).
"""
self.assertSequenceEqual(
self.rock.members.filter(membership__price=50),
[self.jim],
)
def test_join_trimming_reverse(self):
self.assertSequenceEqual(
self.bob.group_set.filter(membership__price=50),
[self.roll],
)
class M2MThroughSerializationTestCase(TestCase):
@classmethod
def setUpTestData(cls):
cls.bob = Person.objects.create(name="Bob")
cls.roll = Group.objects.create(name="Roll")
cls.bob_roll = Membership.objects.create(person=cls.bob, group=cls.roll)
def test_serialization(self):
"m2m-through models aren't serialized as m2m fields. Refs #8134"
pks = {"p_pk": self.bob.pk, "g_pk": self.roll.pk, "m_pk": self.bob_roll.pk}
out = StringIO()
management.call_command("dumpdata", "m2m_through_regress", format="json", stdout=out)
self.assertJSONEqual(
out.getvalue().strip(),
'[{"pk": %(m_pk)s, "model": "m2m_through_regress.membership", "fields": {"person": %(p_pk)s, "price": '
'100, "group": %(g_pk)s}}, {"pk": %(p_pk)s, "model": "m2m_through_regress.person", "fields": {"name": '
'"Bob"}}, {"pk": %(g_pk)s, "model": "m2m_through_regress.group", "fields": {"name": "Roll"}}]'
% pks
)
out = StringIO()
management.call_command("dumpdata", "m2m_through_regress", format="xml", indent=2, stdout=out)
self.assertXMLEqual(out.getvalue().strip(), """
<?xml version="1.0" encoding="utf-8"?>
<django-objects version="1.0">
<object pk="%(m_pk)s" model="m2m_through_regress.membership">
<field to="m2m_through_regress.person" name="person" rel="ManyToOneRel">%(p_pk)s</field>
<field to="m2m_through_regress.group" name="group" rel="ManyToOneRel">%(g_pk)s</field>
<field type="IntegerField" name="price">100</field>
</object>
<object pk="%(p_pk)s" model="m2m_through_regress.person">
<field type="CharField" name="name">Bob</field>
</object>
<object pk="%(g_pk)s" model="m2m_through_regress.group">
<field type="CharField" name="name">Roll</field>
</object>
</django-objects>
""".strip() % pks)
class ToFieldThroughTests(TestCase):
@classmethod
def setUpTestData(cls):
cls.car = Car.objects.create(make="Toyota")
cls.driver = Driver.objects.create(name="Ryan Briscoe")
CarDriver.objects.create(car=cls.car, driver=cls.driver)
# We are testing if wrong objects get deleted due to using wrong
# field value in m2m queries. So, it is essential that the pk
# numberings do not match.
# Create one intentionally unused driver to mix up the autonumbering
cls.unused_driver = Driver.objects.create(name="Barney Gumble")
# And two intentionally unused cars.
cls.unused_car1 = Car.objects.create(make="Trabant")
cls.unused_car2 = Car.objects.create(make="Wartburg")
def test_to_field(self):
self.assertSequenceEqual(self.car.drivers.all(), [self.driver])
def test_to_field_reverse(self):
self.assertSequenceEqual(self.driver.car_set.all(), [self.car])
def test_to_field_clear_reverse(self):
self.driver.car_set.clear()
self.assertSequenceEqual(self.driver.car_set.all(), [])
def test_to_field_clear(self):
self.car.drivers.clear()
self.assertSequenceEqual(self.car.drivers.all(), [])
# Low level tests for _add_items and _remove_items. We test these methods
# because .add/.remove aren't available for m2m fields with through, but
# through is the only way to set to_field currently. We do want to make
# sure these methods are ready if the ability to use .add or .remove with
# to_field relations is added some day.
def test_add(self):
self.assertSequenceEqual(self.car.drivers.all(), [self.driver])
# Yikes - barney is going to drive...
self.car.drivers._add_items('car', 'driver', self.unused_driver)
self.assertSequenceEqual(
self.car.drivers.all(),
[self.unused_driver, self.driver],
)
def test_m2m_relations_unusable_on_null_to_field(self):
nullcar = Car(make=None)
msg = (
'"<Car: None>" needs to have a value for field "make" before this '
'many-to-many relationship can be used.'
)
with self.assertRaisesMessage(ValueError, msg):
nullcar.drivers.all()
def test_m2m_relations_unusable_on_null_pk_obj(self):
msg = (
"'Car' instance needs to have a primary key value before a "
"many-to-many relationship can be used."
)
with self.assertRaisesMessage(ValueError, msg):
Car(make='Ford').drivers.all()
def test_add_related_null(self):
nulldriver = Driver.objects.create(name=None)
msg = 'Cannot add "<Driver: None>": the value for field "driver" is None'
with self.assertRaisesMessage(ValueError, msg):
self.car.drivers._add_items('car', 'driver', nulldriver)
def test_add_reverse(self):
car2 = Car.objects.create(make="Honda")
self.assertCountEqual(self.driver.car_set.all(), [self.car])
self.driver.car_set._add_items('driver', 'car', car2)
self.assertCountEqual(self.driver.car_set.all(), [self.car, car2])
def test_add_null_reverse(self):
nullcar = Car.objects.create(make=None)
msg = 'Cannot add "<Car: None>": the value for field "car" is None'
with self.assertRaisesMessage(ValueError, msg):
self.driver.car_set._add_items('driver', 'car', nullcar)
def test_add_null_reverse_related(self):
nulldriver = Driver.objects.create(name=None)
msg = (
'"<Driver: None>" needs to have a value for field "name" before '
'this many-to-many relationship can be used.'
)
with self.assertRaisesMessage(ValueError, msg):
nulldriver.car_set._add_items('driver', 'car', self.car)
def test_remove(self):
self.assertSequenceEqual(self.car.drivers.all(), [self.driver])
self.car.drivers._remove_items('car', 'driver', self.driver)
self.assertSequenceEqual(self.car.drivers.all(), [])
def test_remove_reverse(self):
self.assertSequenceEqual(self.driver.car_set.all(), [self.car])
self.driver.car_set._remove_items('driver', 'car', self.car)
self.assertSequenceEqual(self.driver.car_set.all(), [])
class ThroughLoadDataTestCase(TestCase):
fixtures = ["m2m_through"]
def test_sequence_creation(self):
"""
Sequences on an m2m_through are created for the through model, not a
phantom auto-generated m2m table (#11107).
"""
out = StringIO()
management.call_command("dumpdata", "m2m_through_regress", format="json", stdout=out)
self.assertJSONEqual(
out.getvalue().strip(),
'[{"pk": 1, "model": "m2m_through_regress.usermembership", "fields": {"price": 100, "group": 1, "user"'
': 1}}, {"pk": 1, "model": "m2m_through_regress.person", "fields": {"name": "Guido"}}, {"pk": 1, '
'"model": "m2m_through_regress.group", "fields": {"name": "Python Core Group"}}]'
)
|