tests.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. from __future__ import unicode_literals
  2. from operator import attrgetter
  3. from django.core.exceptions import FieldError
  4. from django.core.management import call_command
  5. from django.db import connection
  6. from django.test import TestCase
  7. from django.test.utils import CaptureQueriesContext
  8. from django.utils import six
  9. from .models import (
  10. Chef, CommonInfo, ItalianRestaurant, ParkingLot, Place, Post,
  11. Restaurant, Student, Supplier, Worker, MixinModel,
  12. Title, Copy, Base, SubBase)
  13. class ModelInheritanceTests(TestCase):
  14. def test_abstract(self):
  15. # The Student and Worker models both have 'name' and 'age' fields on
  16. # them and inherit the __unicode__() method, just as with normal Python
  17. # subclassing. This is useful if you want to factor out common
  18. # information for programming purposes, but still completely
  19. # independent separate models at the database level.
  20. w1 = Worker.objects.create(name="Fred", age=35, job="Quarry worker")
  21. Worker.objects.create(name="Barney", age=34, job="Quarry worker")
  22. s = Student.objects.create(name="Pebbles", age=5, school_class="1B")
  23. self.assertEqual(six.text_type(w1), "Worker Fred")
  24. self.assertEqual(six.text_type(s), "Student Pebbles")
  25. # The children inherit the Meta class of their parents (if they don't
  26. # specify their own).
  27. self.assertQuerysetEqual(
  28. Worker.objects.values("name"), [
  29. {"name": "Barney"},
  30. {"name": "Fred"},
  31. ],
  32. lambda o: o
  33. )
  34. # Since Student does not subclass CommonInfo's Meta, it has the effect
  35. # of completely overriding it. So ordering by name doesn't take place
  36. # for Students.
  37. self.assertEqual(Student._meta.ordering, [])
  38. # However, the CommonInfo class cannot be used as a normal model (it
  39. # doesn't exist as a model).
  40. self.assertRaises(AttributeError, lambda: CommonInfo.objects.all())
  41. def test_multiple_table(self):
  42. post = Post.objects.create(title="Lorem Ipsum")
  43. # The Post model has distinct accessors for the Comment and Link models.
  44. post.attached_comment_set.create(content="Save $ on V1agr@", is_spam=True)
  45. post.attached_link_set.create(
  46. content="The Web framework for perfections with deadlines.",
  47. url="http://www.djangoproject.com/"
  48. )
  49. # The Post model doesn't have an attribute called
  50. # 'attached_%(class)s_set'.
  51. self.assertRaises(
  52. AttributeError, getattr, post, "attached_%(class)s_set"
  53. )
  54. # The Place/Restaurant/ItalianRestaurant models all exist as
  55. # independent models. However, the subclasses also have transparent
  56. # access to the fields of their ancestors.
  57. # Create a couple of Places.
  58. Place.objects.create(name="Master Shakes", address="666 W. Jersey")
  59. Place.objects.create(name="Ace Harware", address="1013 N. Ashland")
  60. # Test constructor for Restaurant.
  61. r = Restaurant.objects.create(
  62. name="Demon Dogs",
  63. address="944 W. Fullerton",
  64. serves_hot_dogs=True,
  65. serves_pizza=False,
  66. rating=2
  67. )
  68. # Test the constructor for ItalianRestaurant.
  69. c = Chef.objects.create(name="Albert")
  70. ir = ItalianRestaurant.objects.create(
  71. name="Ristorante Miron",
  72. address="1234 W. Ash",
  73. serves_hot_dogs=False,
  74. serves_pizza=False,
  75. serves_gnocchi=True,
  76. rating=4,
  77. chef=c
  78. )
  79. self.assertQuerysetEqual(
  80. ItalianRestaurant.objects.filter(address="1234 W. Ash"), [
  81. "Ristorante Miron",
  82. ],
  83. attrgetter("name")
  84. )
  85. ir.address = "1234 W. Elm"
  86. ir.save()
  87. self.assertQuerysetEqual(
  88. ItalianRestaurant.objects.filter(address="1234 W. Elm"), [
  89. "Ristorante Miron",
  90. ],
  91. attrgetter("name")
  92. )
  93. # Make sure Restaurant and ItalianRestaurant have the right fields in
  94. # the right order.
  95. self.assertEqual(
  96. [f.name for f in Restaurant._meta.fields],
  97. ["id", "name", "address", "place_ptr", "rating", "serves_hot_dogs",
  98. "serves_pizza", "chef"]
  99. )
  100. self.assertEqual(
  101. [f.name for f in ItalianRestaurant._meta.fields],
  102. ["id", "name", "address", "place_ptr", "rating", "serves_hot_dogs",
  103. "serves_pizza", "chef", "restaurant_ptr", "serves_gnocchi"],
  104. )
  105. self.assertEqual(Restaurant._meta.ordering, ["-rating"])
  106. # Even though p.supplier for a Place 'p' (a parent of a Supplier), a
  107. # Restaurant object cannot access that reverse relation, since it's not
  108. # part of the Place-Supplier Hierarchy.
  109. self.assertQuerysetEqual(Place.objects.filter(supplier__name="foo"), [])
  110. self.assertRaises(
  111. FieldError, Restaurant.objects.filter, supplier__name="foo"
  112. )
  113. # Parent fields can be used directly in filters on the child model.
  114. self.assertQuerysetEqual(
  115. Restaurant.objects.filter(name="Demon Dogs"), [
  116. "Demon Dogs",
  117. ],
  118. attrgetter("name")
  119. )
  120. self.assertQuerysetEqual(
  121. ItalianRestaurant.objects.filter(address="1234 W. Elm"), [
  122. "Ristorante Miron",
  123. ],
  124. attrgetter("name")
  125. )
  126. # Filters against the parent model return objects of the parent's type.
  127. p = Place.objects.get(name="Demon Dogs")
  128. self.assertIs(type(p), Place)
  129. # Since the parent and child are linked by an automatically created
  130. # OneToOneField, you can get from the parent to the child by using the
  131. # child's name.
  132. self.assertEqual(
  133. p.restaurant, Restaurant.objects.get(name="Demon Dogs")
  134. )
  135. self.assertEqual(
  136. Place.objects.get(name="Ristorante Miron").restaurant.italianrestaurant,
  137. ItalianRestaurant.objects.get(name="Ristorante Miron")
  138. )
  139. self.assertEqual(
  140. Restaurant.objects.get(name="Ristorante Miron").italianrestaurant,
  141. ItalianRestaurant.objects.get(name="Ristorante Miron")
  142. )
  143. # This won't work because the Demon Dogs restaurant is not an Italian
  144. # restaurant.
  145. self.assertRaises(
  146. ItalianRestaurant.DoesNotExist,
  147. lambda: p.restaurant.italianrestaurant
  148. )
  149. # An ItalianRestaurant which does not exist is also a Place which does
  150. # not exist.
  151. self.assertRaises(
  152. Place.DoesNotExist,
  153. ItalianRestaurant.objects.get, name="The Noodle Void"
  154. )
  155. # MultipleObjectsReturned is also inherited.
  156. self.assertRaises(
  157. Place.MultipleObjectsReturned,
  158. Restaurant.objects.get, id__lt=12321
  159. )
  160. # Related objects work just as they normally do.
  161. s1 = Supplier.objects.create(name="Joe's Chickens", address="123 Sesame St")
  162. s1.customers = [r, ir]
  163. s2 = Supplier.objects.create(name="Luigi's Pasta", address="456 Sesame St")
  164. s2.customers = [ir]
  165. # This won't work because the Place we select is not a Restaurant (it's
  166. # a Supplier).
  167. p = Place.objects.get(name="Joe's Chickens")
  168. self.assertRaises(
  169. Restaurant.DoesNotExist, lambda: p.restaurant
  170. )
  171. self.assertEqual(p.supplier, s1)
  172. self.assertQuerysetEqual(
  173. ir.provider.order_by("-name"), [
  174. "Luigi's Pasta",
  175. "Joe's Chickens"
  176. ],
  177. attrgetter("name")
  178. )
  179. self.assertQuerysetEqual(
  180. Restaurant.objects.filter(provider__name__contains="Chickens"), [
  181. "Ristorante Miron",
  182. "Demon Dogs",
  183. ],
  184. attrgetter("name")
  185. )
  186. self.assertQuerysetEqual(
  187. ItalianRestaurant.objects.filter(provider__name__contains="Chickens"), [
  188. "Ristorante Miron",
  189. ],
  190. attrgetter("name"),
  191. )
  192. ParkingLot.objects.create(
  193. name="Main St", address="111 Main St", main_site=s1
  194. )
  195. ParkingLot.objects.create(
  196. name="Well Lit", address="124 Sesame St", main_site=ir
  197. )
  198. self.assertEqual(
  199. Restaurant.objects.get(lot__name="Well Lit").name,
  200. "Ristorante Miron"
  201. )
  202. # The update() command can update fields in parent and child classes at
  203. # once (although it executed multiple SQL queries to do so).
  204. rows = Restaurant.objects.filter(
  205. serves_hot_dogs=True, name__contains="D"
  206. ).update(
  207. name="Demon Puppies", serves_hot_dogs=False
  208. )
  209. self.assertEqual(rows, 1)
  210. r1 = Restaurant.objects.get(pk=r.pk)
  211. self.assertFalse(r1.serves_hot_dogs)
  212. self.assertEqual(r1.name, "Demon Puppies")
  213. # The values() command also works on fields from parent models.
  214. self.assertQuerysetEqual(
  215. ItalianRestaurant.objects.values("name", "rating"), [
  216. {"rating": 4, "name": "Ristorante Miron"}
  217. ],
  218. lambda o: o
  219. )
  220. # select_related works with fields from the parent object as if they
  221. # were a normal part of the model.
  222. self.assertNumQueries(
  223. 2, lambda: ItalianRestaurant.objects.all()[0].chef
  224. )
  225. self.assertNumQueries(
  226. 1, lambda: ItalianRestaurant.objects.select_related("chef")[0].chef
  227. )
  228. def test_mixin_init(self):
  229. m = MixinModel()
  230. self.assertEqual(m.other_attr, 1)
  231. def test_update_query_counts(self):
  232. """
  233. Test that update queries do not generate non-necessary queries.
  234. Refs #18304.
  235. """
  236. c = Chef.objects.create(name="Albert")
  237. ir = ItalianRestaurant.objects.create(
  238. name="Ristorante Miron",
  239. address="1234 W. Ash",
  240. serves_hot_dogs=False,
  241. serves_pizza=False,
  242. serves_gnocchi=True,
  243. rating=4,
  244. chef=c
  245. )
  246. with self.assertNumQueries(3):
  247. ir.save()
  248. def test_update_parent_filtering(self):
  249. """
  250. Test that updating a field of a model subclass doesn't issue an UPDATE
  251. query constrained by an inner query.
  252. Refs #10399
  253. """
  254. supplier = Supplier.objects.create(
  255. name='Central market',
  256. address='610 some street'
  257. )
  258. # Capture the expected query in a database agnostic way
  259. with CaptureQueriesContext(connection) as captured_queries:
  260. Place.objects.filter(pk=supplier.pk).update(name=supplier.name)
  261. expected_sql = captured_queries[0]['sql']
  262. # Capture the queries executed when a subclassed model instance is saved.
  263. with CaptureQueriesContext(connection) as captured_queries:
  264. supplier.save(update_fields=('name',))
  265. for query in captured_queries:
  266. sql = query['sql']
  267. if 'UPDATE' in sql:
  268. self.assertEqual(expected_sql, sql)
  269. def test_eq(self):
  270. # Equality doesn't transfer in multitable inheritance.
  271. self.assertNotEqual(Place(id=1), Restaurant(id=1))
  272. self.assertNotEqual(Restaurant(id=1), Place(id=1))
  273. def test_ticket_12567(self):
  274. r = Restaurant.objects.create(name='n1', address='a1')
  275. s = Supplier.objects.create(name='s1', address='a2')
  276. self.assertQuerysetEqual(
  277. Place.objects.filter(supplier__isnull=False),
  278. [Place.objects.get(pk=s.pk)],
  279. lambda x: x
  280. )
  281. self.assertQuerysetEqual(
  282. Place.objects.filter(supplier__isnull=True),
  283. [Place.objects.get(pk=r.pk)],
  284. lambda x: x
  285. )
  286. self.assertQuerysetEqual(
  287. Place.objects.exclude(supplier__isnull=False),
  288. [Place.objects.get(pk=r.pk)],
  289. lambda x: x
  290. )
  291. self.assertQuerysetEqual(
  292. Place.objects.exclude(supplier__isnull=True),
  293. [Place.objects.get(pk=s.pk)],
  294. lambda x: x
  295. )
  296. def test_custompk_m2m(self):
  297. b = Base.objects.create()
  298. b.titles.add(Title.objects.create(title="foof"))
  299. s = SubBase.objects.create(sub_id=b.id)
  300. b = Base.objects.get(pk=s.id)
  301. self.assertNotEqual(b.pk, s.pk)
  302. # Low-level test for related_val
  303. self.assertEqual(s.titles.related_val, (s.id,))
  304. # Higher level test for correct query values (title foof not
  305. # accidentally found).
  306. self.assertQuerysetEqual(
  307. s.titles.all(), [])
  308. class InheritanceSameModelNameTests(TestCase):
  309. def setUp(self):
  310. # The Title model has distinct accessors for both
  311. # model_inheritance.Copy and model_inheritance_same_model_name.Copy
  312. # models.
  313. self.title = Title.objects.create(title='Lorem Ipsum')
  314. def test_inheritance_related_name(self):
  315. self.assertEqual(
  316. self.title.attached_model_inheritance_copy_set.create(
  317. content='Save $ on V1agr@',
  318. url='http://v1agra.com/',
  319. title='V1agra is spam',
  320. ), Copy.objects.get(
  321. content='Save $ on V1agr@',
  322. ))
  323. def test_inheritance_with_same_model_name(self):
  324. with self.modify_settings(
  325. INSTALLED_APPS={'append': ['model_inheritance.same_model_name']}):
  326. call_command('migrate', verbosity=0)
  327. from .same_model_name.models import Copy
  328. self.assertEqual(
  329. self.title.attached_same_model_name_copy_set.create(
  330. content='The Web framework for perfectionists with deadlines.',
  331. url='http://www.djangoproject.com/',
  332. title='Django Rocks'
  333. ), Copy.objects.get(
  334. content='The Web framework for perfectionists with deadlines.',
  335. ))
  336. def test_related_name_attribute_exists(self):
  337. # The Post model doesn't have an attribute called 'attached_%(app_label)s_%(class)s_set'.
  338. self.assertFalse(hasattr(self.title, 'attached_%(app_label)s_%(class)s_set'))