tests.py 17 KB

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