2
0

tests.py 10 KB

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