tests.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  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, TransactionTestCase
  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_reverse_relation_for_different_hierarchy_tree(self):
  42. # Even though p.supplier for a Place 'p' (a parent of a Supplier), a
  43. # Restaurant object cannot access that reverse relation, since it's not
  44. # part of the Place-Supplier Hierarchy.
  45. self.assertQuerysetEqual(Place.objects.filter(supplier__name="foo"), [])
  46. self.assertRaises(FieldError, Restaurant.objects.filter, supplier__name="foo")
  47. def test_model_with_distinct_accessors(self):
  48. # The Post model has distinct accessors for the Comment and Link models.
  49. post = Post.objects.create(title="Lorem Ipsum")
  50. post.attached_comment_set.create(content="Save $ on V1agr@", is_spam=True)
  51. post.attached_link_set.create(
  52. content="The Web framework for perfections with deadlines.",
  53. url="http://www.djangoproject.com/"
  54. )
  55. # The Post model doesn't have an attribute called
  56. # 'attached_%(class)s_set'.
  57. self.assertRaises(
  58. AttributeError, getattr, post, "attached_%(class)s_set"
  59. )
  60. def test_meta_fields_and_ordering(self):
  61. # Make sure Restaurant and ItalianRestaurant have the right fields in
  62. # the right order.
  63. self.assertEqual(
  64. [f.name for f in Restaurant._meta.fields],
  65. ["id", "name", "address", "place_ptr", "rating", "serves_hot_dogs",
  66. "serves_pizza", "chef"]
  67. )
  68. self.assertEqual(
  69. [f.name for f in ItalianRestaurant._meta.fields],
  70. ["id", "name", "address", "place_ptr", "rating", "serves_hot_dogs",
  71. "serves_pizza", "chef", "restaurant_ptr", "serves_gnocchi"],
  72. )
  73. self.assertEqual(Restaurant._meta.ordering, ["-rating"])
  74. def test_custompk_m2m(self):
  75. b = Base.objects.create()
  76. b.titles.add(Title.objects.create(title="foof"))
  77. s = SubBase.objects.create(sub_id=b.id)
  78. b = Base.objects.get(pk=s.id)
  79. self.assertNotEqual(b.pk, s.pk)
  80. # Low-level test for related_val
  81. self.assertEqual(s.titles.related_val, (s.id,))
  82. # Higher level test for correct query values (title foof not
  83. # accidentally found).
  84. self.assertQuerysetEqual(s.titles.all(), [])
  85. def test_update_parent_filtering(self):
  86. """
  87. Test that updating a field of a model subclass doesn't issue an UPDATE
  88. query constrained by an inner query.
  89. Refs #10399
  90. """
  91. supplier = Supplier.objects.create(
  92. name='Central market',
  93. address='610 some street',
  94. )
  95. # Capture the expected query in a database agnostic way
  96. with CaptureQueriesContext(connection) as captured_queries:
  97. Place.objects.filter(pk=supplier.pk).update(name=supplier.name)
  98. expected_sql = captured_queries[0]['sql']
  99. # Capture the queries executed when a subclassed model instance is saved.
  100. with CaptureQueriesContext(connection) as captured_queries:
  101. supplier.save(update_fields=('name',))
  102. for query in captured_queries:
  103. sql = query['sql']
  104. if 'UPDATE' in sql:
  105. self.assertEqual(expected_sql, sql)
  106. def test_eq(self):
  107. # Equality doesn't transfer in multitable inheritance.
  108. self.assertNotEqual(Place(id=1), Restaurant(id=1))
  109. self.assertNotEqual(Restaurant(id=1), Place(id=1))
  110. def test_mixin_init(self):
  111. m = MixinModel()
  112. self.assertEqual(m.other_attr, 1)
  113. class ModelInheritanceDataTests(TestCase):
  114. @classmethod
  115. def setUpTestData(cls):
  116. cls.restaurant = Restaurant.objects.create(
  117. name="Demon Dogs",
  118. address="944 W. Fullerton",
  119. serves_hot_dogs=True,
  120. serves_pizza=False,
  121. rating=2,
  122. )
  123. chef = Chef.objects.create(name="Albert")
  124. cls.italian_restaurant = ItalianRestaurant.objects.create(
  125. name="Ristorante Miron",
  126. address="1234 W. Ash",
  127. serves_hot_dogs=False,
  128. serves_pizza=False,
  129. serves_gnocchi=True,
  130. rating=4,
  131. chef=chef,
  132. )
  133. def test_filter_inherited_model(self):
  134. self.assertQuerysetEqual(
  135. ItalianRestaurant.objects.filter(address="1234 W. Ash"), [
  136. "Ristorante Miron",
  137. ],
  138. attrgetter("name")
  139. )
  140. def test_update_inherited_model(self):
  141. self.italian_restaurant.address = "1234 W. Elm"
  142. self.italian_restaurant.save()
  143. self.assertQuerysetEqual(
  144. ItalianRestaurant.objects.filter(address="1234 W. Elm"), [
  145. "Ristorante Miron",
  146. ],
  147. attrgetter("name")
  148. )
  149. def test_parent_fields_available_for_filtering_in_child_model(self):
  150. # Parent fields can be used directly in filters on the child model.
  151. self.assertQuerysetEqual(
  152. Restaurant.objects.filter(name="Demon Dogs"), [
  153. "Demon Dogs",
  154. ],
  155. attrgetter("name")
  156. )
  157. self.assertQuerysetEqual(
  158. ItalianRestaurant.objects.filter(address="1234 W. Ash"), [
  159. "Ristorante Miron",
  160. ],
  161. attrgetter("name")
  162. )
  163. def test_filter_on_parent_returns_object_of_parent_type(self):
  164. # Filters against the parent model return objects of the parent's type.
  165. p = Place.objects.get(name="Demon Dogs")
  166. self.assertIs(type(p), Place)
  167. def test_parent_child_one_to_one_link(self):
  168. # Since the parent and child are linked by an automatically created
  169. # OneToOneField, you can get from the parent to the child by using the
  170. # child's name.
  171. self.assertEqual(
  172. Place.objects.get(name="Demon Dogs").restaurant,
  173. Restaurant.objects.get(name="Demon Dogs")
  174. )
  175. self.assertEqual(
  176. Place.objects.get(name="Ristorante Miron").restaurant.italianrestaurant,
  177. ItalianRestaurant.objects.get(name="Ristorante Miron")
  178. )
  179. self.assertEqual(
  180. Restaurant.objects.get(name="Ristorante Miron").italianrestaurant,
  181. ItalianRestaurant.objects.get(name="Ristorante Miron")
  182. )
  183. def test_parent_child_one_to_one_link_on_nonrelated_objects(self):
  184. # This won't work because the Demon Dogs restaurant is not an Italian
  185. # restaurant.
  186. self.assertRaises(
  187. ItalianRestaurant.DoesNotExist,
  188. lambda: Place.objects.get(name="Demon Dogs").restaurant.italianrestaurant
  189. )
  190. def test_inherited_does_not_exist_exception(self):
  191. # An ItalianRestaurant which does not exist is also a Place which does
  192. # not exist.
  193. self.assertRaises(
  194. Place.DoesNotExist,
  195. ItalianRestaurant.objects.get, name="The Noodle Void"
  196. )
  197. def test_inherited_multiple_objects_returned_exception(self):
  198. # MultipleObjectsReturned is also inherited.
  199. self.assertRaises(
  200. Place.MultipleObjectsReturned,
  201. Restaurant.objects.get, id__lt=12321
  202. )
  203. def test_related_objects_for_inherited_models(self):
  204. # Related objects work just as they normally do.
  205. s1 = Supplier.objects.create(name="Joe's Chickens", address="123 Sesame St")
  206. s1.customers = [self.restaurant, self.italian_restaurant]
  207. s2 = Supplier.objects.create(name="Luigi's Pasta", address="456 Sesame St")
  208. s2.customers = [self.italian_restaurant]
  209. # This won't work because the Place we select is not a Restaurant (it's
  210. # a Supplier).
  211. p = Place.objects.get(name="Joe's Chickens")
  212. self.assertRaises(
  213. Restaurant.DoesNotExist, lambda: p.restaurant
  214. )
  215. self.assertEqual(p.supplier, s1)
  216. self.assertQuerysetEqual(
  217. self.italian_restaurant.provider.order_by("-name"), [
  218. "Luigi's Pasta",
  219. "Joe's Chickens"
  220. ],
  221. attrgetter("name")
  222. )
  223. self.assertQuerysetEqual(
  224. Restaurant.objects.filter(provider__name__contains="Chickens"), [
  225. "Ristorante Miron",
  226. "Demon Dogs",
  227. ],
  228. attrgetter("name")
  229. )
  230. self.assertQuerysetEqual(
  231. ItalianRestaurant.objects.filter(provider__name__contains="Chickens"), [
  232. "Ristorante Miron",
  233. ],
  234. attrgetter("name"),
  235. )
  236. ParkingLot.objects.create(
  237. name="Main St", address="111 Main St", main_site=s1
  238. )
  239. ParkingLot.objects.create(
  240. name="Well Lit", address="124 Sesame St", main_site=self.italian_restaurant
  241. )
  242. self.assertEqual(
  243. Restaurant.objects.get(lot__name="Well Lit").name,
  244. "Ristorante Miron"
  245. )
  246. def test_update_works_on_parent_and_child_models_at_once(self):
  247. # The update() command can update fields in parent and child classes at
  248. # once (although it executed multiple SQL queries to do so).
  249. rows = Restaurant.objects.filter(
  250. serves_hot_dogs=True, name__contains="D"
  251. ).update(
  252. name="Demon Puppies", serves_hot_dogs=False
  253. )
  254. self.assertEqual(rows, 1)
  255. r1 = Restaurant.objects.get(pk=self.restaurant.pk)
  256. self.assertFalse(r1.serves_hot_dogs)
  257. self.assertEqual(r1.name, "Demon Puppies")
  258. def test_values_works_on_parent_model_fields(self):
  259. # The values() command also works on fields from parent models.
  260. self.assertQuerysetEqual(
  261. ItalianRestaurant.objects.values("name", "rating"), [
  262. {"rating": 4, "name": "Ristorante Miron"},
  263. ],
  264. lambda o: o
  265. )
  266. def test_select_related_works_on_parent_model_fields(self):
  267. # select_related works with fields from the parent object as if they
  268. # were a normal part of the model.
  269. self.assertNumQueries(
  270. 2, lambda: ItalianRestaurant.objects.all()[0].chef
  271. )
  272. self.assertNumQueries(
  273. 1, lambda: ItalianRestaurant.objects.select_related("chef")[0].chef
  274. )
  275. def test_select_related_defer(self):
  276. """
  277. #23370 - Should be able to defer child fields when using
  278. select_related() from parent to child.
  279. """
  280. qs = (Restaurant.objects
  281. .select_related("italianrestaurant")
  282. .defer("italianrestaurant__serves_gnocchi")
  283. .order_by("rating"))
  284. # Test that the field was actually deferred
  285. with self.assertNumQueries(2):
  286. objs = list(qs.all())
  287. self.assertTrue(objs[1].italianrestaurant.serves_gnocchi)
  288. # Test that model fields where assigned correct values
  289. self.assertEqual(qs[0].name, 'Demon Dogs')
  290. self.assertEqual(qs[0].rating, 2)
  291. self.assertEqual(qs[1].italianrestaurant.name, 'Ristorante Miron')
  292. self.assertEqual(qs[1].italianrestaurant.rating, 4)
  293. def test_update_query_counts(self):
  294. """
  295. Test that update queries do not generate non-necessary queries.
  296. Refs #18304.
  297. """
  298. with self.assertNumQueries(3):
  299. self.italian_restaurant.save()
  300. def test_filter_inherited_on_null(self):
  301. # Refs #12567
  302. Supplier.objects.create(
  303. name="Central market",
  304. address="610 some street",
  305. )
  306. self.assertQuerysetEqual(
  307. Place.objects.filter(supplier__isnull=False), [
  308. "Central market",
  309. ],
  310. attrgetter("name")
  311. )
  312. self.assertQuerysetEqual(
  313. Place.objects.filter(supplier__isnull=True).order_by("name"), [
  314. "Demon Dogs",
  315. "Ristorante Miron",
  316. ],
  317. attrgetter("name")
  318. )
  319. def test_exclude_inherited_on_null(self):
  320. # Refs #12567
  321. Supplier.objects.create(
  322. name="Central market",
  323. address="610 some street",
  324. )
  325. self.assertQuerysetEqual(
  326. Place.objects.exclude(supplier__isnull=False).order_by("name"), [
  327. "Demon Dogs",
  328. "Ristorante Miron",
  329. ],
  330. attrgetter("name")
  331. )
  332. self.assertQuerysetEqual(
  333. Place.objects.exclude(supplier__isnull=True), [
  334. "Central market",
  335. ],
  336. attrgetter("name")
  337. )
  338. class InheritanceSameModelNameTests(TransactionTestCase):
  339. available_apps = ['model_inheritance']
  340. def setUp(self):
  341. # The Title model has distinct accessors for both
  342. # model_inheritance.Copy and model_inheritance_same_model_name.Copy
  343. # models.
  344. self.title = Title.objects.create(title='Lorem Ipsum')
  345. def test_inheritance_related_name(self):
  346. self.assertEqual(
  347. self.title.attached_model_inheritance_copy_set.create(
  348. content='Save $ on V1agr@',
  349. url='http://v1agra.com/',
  350. title='V1agra is spam',
  351. ), Copy.objects.get(
  352. content='Save $ on V1agr@',
  353. ))
  354. def test_inheritance_with_same_model_name(self):
  355. with self.modify_settings(
  356. INSTALLED_APPS={'append': ['model_inheritance.same_model_name']}):
  357. call_command('migrate', verbosity=0, run_syncdb=True)
  358. from .same_model_name.models import Copy
  359. copy = self.title.attached_same_model_name_copy_set.create(
  360. content='The Web framework for perfectionists with deadlines.',
  361. url='http://www.djangoproject.com/',
  362. title='Django Rocks'
  363. )
  364. self.assertEqual(
  365. copy,
  366. Copy.objects.get(
  367. content='The Web framework for perfectionists with deadlines.',
  368. ))
  369. # We delete the copy manually so that it doesn't block the flush
  370. # command under Oracle (which does not cascade deletions).
  371. copy.delete()
  372. def test_related_name_attribute_exists(self):
  373. # The Post model doesn't have an attribute called 'attached_%(app_label)s_%(class)s_set'.
  374. self.assertFalse(hasattr(self.title, 'attached_%(app_label)s_%(class)s_set'))