tests.py 59 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390
  1. from __future__ import unicode_literals
  2. import warnings
  3. from django.contrib.contenttypes.models import ContentType
  4. from django.core.exceptions import ObjectDoesNotExist
  5. from django.db import connection
  6. from django.db.models import Prefetch, QuerySet
  7. from django.db.models.query import get_prefetcher
  8. from django.test import TestCase, override_settings
  9. from django.test.utils import CaptureQueriesContext
  10. from django.utils import six
  11. from django.utils.encoding import force_text
  12. from .models import (
  13. Author, Author2, AuthorAddress, AuthorWithAge, Bio, Book, Bookmark,
  14. BookReview, BookWithYear, Comment, Department, Employee, FavoriteAuthors,
  15. House, LessonEntry, Person, Qualification, Reader, Room, TaggedItem,
  16. Teacher, WordEntry,
  17. )
  18. class PrefetchRelatedTests(TestCase):
  19. @classmethod
  20. def setUpTestData(cls):
  21. cls.book1 = Book.objects.create(title='Poems')
  22. cls.book2 = Book.objects.create(title='Jane Eyre')
  23. cls.book3 = Book.objects.create(title='Wuthering Heights')
  24. cls.book4 = Book.objects.create(title='Sense and Sensibility')
  25. cls.author1 = Author.objects.create(name='Charlotte', first_book=cls.book1)
  26. cls.author2 = Author.objects.create(name='Anne', first_book=cls.book1)
  27. cls.author3 = Author.objects.create(name='Emily', first_book=cls.book1)
  28. cls.author4 = Author.objects.create(name='Jane', first_book=cls.book4)
  29. cls.book1.authors.add(cls.author1, cls.author2, cls.author3)
  30. cls.book2.authors.add(cls.author1)
  31. cls.book3.authors.add(cls.author3)
  32. cls.book4.authors.add(cls.author4)
  33. cls.reader1 = Reader.objects.create(name='Amy')
  34. cls.reader2 = Reader.objects.create(name='Belinda')
  35. cls.reader1.books_read.add(cls.book1, cls.book4)
  36. cls.reader2.books_read.add(cls.book2, cls.book4)
  37. def assertWhereContains(self, sql, needle):
  38. where_idx = sql.index('WHERE')
  39. self.assertEqual(
  40. sql.count(str(needle), where_idx), 1,
  41. msg="WHERE clause doesn't contain %s, actual SQL: %s" % (needle, sql[where_idx:])
  42. )
  43. def test_m2m_forward(self):
  44. with self.assertNumQueries(2):
  45. lists = [list(b.authors.all()) for b in Book.objects.prefetch_related('authors')]
  46. normal_lists = [list(b.authors.all()) for b in Book.objects.all()]
  47. self.assertEqual(lists, normal_lists)
  48. def test_m2m_reverse(self):
  49. with self.assertNumQueries(2):
  50. lists = [list(a.books.all()) for a in Author.objects.prefetch_related('books')]
  51. normal_lists = [list(a.books.all()) for a in Author.objects.all()]
  52. self.assertEqual(lists, normal_lists)
  53. def test_foreignkey_forward(self):
  54. with self.assertNumQueries(2):
  55. books = [a.first_book for a in Author.objects.prefetch_related('first_book')]
  56. normal_books = [a.first_book for a in Author.objects.all()]
  57. self.assertEqual(books, normal_books)
  58. def test_foreignkey_reverse(self):
  59. with self.assertNumQueries(2):
  60. [list(b.first_time_authors.all())
  61. for b in Book.objects.prefetch_related('first_time_authors')]
  62. self.assertQuerysetEqual(self.book2.authors.all(), ["<Author: Charlotte>"])
  63. def test_onetoone_reverse_no_match(self):
  64. # Regression for #17439
  65. with self.assertNumQueries(2):
  66. book = Book.objects.prefetch_related('bookwithyear').all()[0]
  67. with self.assertNumQueries(0):
  68. with self.assertRaises(BookWithYear.DoesNotExist):
  69. book.bookwithyear
  70. def test_survives_clone(self):
  71. with self.assertNumQueries(2):
  72. [list(b.first_time_authors.all())
  73. for b in Book.objects.prefetch_related('first_time_authors').exclude(id=1000)]
  74. def test_len(self):
  75. with self.assertNumQueries(2):
  76. qs = Book.objects.prefetch_related('first_time_authors')
  77. len(qs)
  78. [list(b.first_time_authors.all()) for b in qs]
  79. def test_bool(self):
  80. with self.assertNumQueries(2):
  81. qs = Book.objects.prefetch_related('first_time_authors')
  82. bool(qs)
  83. [list(b.first_time_authors.all()) for b in qs]
  84. def test_count(self):
  85. with self.assertNumQueries(2):
  86. qs = Book.objects.prefetch_related('first_time_authors')
  87. [b.first_time_authors.count() for b in qs]
  88. def test_exists(self):
  89. with self.assertNumQueries(2):
  90. qs = Book.objects.prefetch_related('first_time_authors')
  91. [b.first_time_authors.exists() for b in qs]
  92. def test_in_and_prefetch_related(self):
  93. """
  94. Regression test for #20242 - QuerySet "in" didn't work the first time
  95. when using prefetch_related. This was fixed by the removal of chunked
  96. reads from QuerySet iteration in
  97. 70679243d1786e03557c28929f9762a119e3ac14.
  98. """
  99. qs = Book.objects.prefetch_related('first_time_authors')
  100. self.assertIn(qs[0], qs)
  101. def test_clear(self):
  102. with self.assertNumQueries(5):
  103. with_prefetch = Author.objects.prefetch_related('books')
  104. without_prefetch = with_prefetch.prefetch_related(None)
  105. [list(a.books.all()) for a in without_prefetch]
  106. def test_m2m_then_m2m(self):
  107. """A m2m can be followed through another m2m."""
  108. with self.assertNumQueries(3):
  109. qs = Author.objects.prefetch_related('books__read_by')
  110. lists = [[[six.text_type(r) for r in b.read_by.all()]
  111. for b in a.books.all()]
  112. for a in qs]
  113. self.assertEqual(lists, [
  114. [["Amy"], ["Belinda"]], # Charlotte - Poems, Jane Eyre
  115. [["Amy"]], # Anne - Poems
  116. [["Amy"], []], # Emily - Poems, Wuthering Heights
  117. [["Amy", "Belinda"]], # Jane - Sense and Sense
  118. ])
  119. def test_overriding_prefetch(self):
  120. with self.assertNumQueries(3):
  121. qs = Author.objects.prefetch_related('books', 'books__read_by')
  122. lists = [[[six.text_type(r) for r in b.read_by.all()]
  123. for b in a.books.all()]
  124. for a in qs]
  125. self.assertEqual(lists, [
  126. [["Amy"], ["Belinda"]], # Charlotte - Poems, Jane Eyre
  127. [["Amy"]], # Anne - Poems
  128. [["Amy"], []], # Emily - Poems, Wuthering Heights
  129. [["Amy", "Belinda"]], # Jane - Sense and Sense
  130. ])
  131. with self.assertNumQueries(3):
  132. qs = Author.objects.prefetch_related('books__read_by', 'books')
  133. lists = [[[six.text_type(r) for r in b.read_by.all()]
  134. for b in a.books.all()]
  135. for a in qs]
  136. self.assertEqual(lists, [
  137. [["Amy"], ["Belinda"]], # Charlotte - Poems, Jane Eyre
  138. [["Amy"]], # Anne - Poems
  139. [["Amy"], []], # Emily - Poems, Wuthering Heights
  140. [["Amy", "Belinda"]], # Jane - Sense and Sense
  141. ])
  142. def test_get(self):
  143. """
  144. Objects retrieved with .get() get the prefetch behavior.
  145. """
  146. # Need a double
  147. with self.assertNumQueries(3):
  148. author = Author.objects.prefetch_related('books__read_by').get(name="Charlotte")
  149. lists = [[six.text_type(r) for r in b.read_by.all()] for b in author.books.all()]
  150. self.assertEqual(lists, [["Amy"], ["Belinda"]]) # Poems, Jane Eyre
  151. def test_foreign_key_then_m2m(self):
  152. """
  153. A m2m relation can be followed after a relation like ForeignKey that
  154. doesn't have many objects.
  155. """
  156. with self.assertNumQueries(2):
  157. qs = Author.objects.select_related('first_book').prefetch_related('first_book__read_by')
  158. lists = [[six.text_type(r) for r in a.first_book.read_by.all()]
  159. for a in qs]
  160. self.assertEqual(lists, [["Amy"], ["Amy"], ["Amy"], ["Amy", "Belinda"]])
  161. def test_reverse_one_to_one_then_m2m(self):
  162. """
  163. A m2m relation can be followed afterr going through the select_related
  164. reverse of an o2o.
  165. """
  166. qs = Author.objects.prefetch_related('bio__books').select_related('bio')
  167. with self.assertNumQueries(1):
  168. list(qs.all())
  169. Bio.objects.create(author=self.author1)
  170. with self.assertNumQueries(2):
  171. list(qs.all())
  172. def test_attribute_error(self):
  173. qs = Reader.objects.all().prefetch_related('books_read__xyz')
  174. with self.assertRaises(AttributeError) as cm:
  175. list(qs)
  176. self.assertIn('prefetch_related', str(cm.exception))
  177. def test_invalid_final_lookup(self):
  178. qs = Book.objects.prefetch_related('authors__name')
  179. with self.assertRaises(ValueError) as cm:
  180. list(qs)
  181. self.assertIn('prefetch_related', str(cm.exception))
  182. self.assertIn("name", str(cm.exception))
  183. def test_forward_m2m_to_attr_conflict(self):
  184. msg = 'to_attr=authors conflicts with a field on the Book model.'
  185. authors = Author.objects.all()
  186. with self.assertRaisesMessage(ValueError, msg):
  187. list(Book.objects.prefetch_related(
  188. Prefetch('authors', queryset=authors, to_attr='authors'),
  189. ))
  190. # Without the ValueError, an author was deleted due to the implicit
  191. # save of the relation assignment.
  192. self.assertEqual(self.book1.authors.count(), 3)
  193. def test_reverse_m2m_to_attr_conflict(self):
  194. msg = 'to_attr=books conflicts with a field on the Author model.'
  195. poems = Book.objects.filter(title='Poems')
  196. with self.assertRaisesMessage(ValueError, msg):
  197. list(Author.objects.prefetch_related(
  198. Prefetch('books', queryset=poems, to_attr='books'),
  199. ))
  200. # Without the ValueError, a book was deleted due to the implicit
  201. # save of reverse relation assignment.
  202. self.assertEqual(self.author1.books.count(), 2)
  203. def test_m2m_then_reverse_fk_object_ids(self):
  204. with CaptureQueriesContext(connection) as queries:
  205. list(Book.objects.prefetch_related('authors__addresses'))
  206. sql = queries[-1]['sql']
  207. self.assertWhereContains(sql, self.author1.name)
  208. def test_m2m_then_m2m_object_ids(self):
  209. with CaptureQueriesContext(connection) as queries:
  210. list(Book.objects.prefetch_related('authors__favorite_authors'))
  211. sql = queries[-1]['sql']
  212. self.assertWhereContains(sql, self.author1.name)
  213. def test_m2m_then_reverse_one_to_one_object_ids(self):
  214. with CaptureQueriesContext(connection) as queries:
  215. list(Book.objects.prefetch_related('authors__authorwithage'))
  216. sql = queries[-1]['sql']
  217. self.assertWhereContains(sql, self.author1.id)
  218. class CustomPrefetchTests(TestCase):
  219. @classmethod
  220. def traverse_qs(cls, obj_iter, path):
  221. """
  222. Helper method that returns a list containing a list of the objects in the
  223. obj_iter. Then for each object in the obj_iter, the path will be
  224. recursively travelled and the found objects are added to the return value.
  225. """
  226. ret_val = []
  227. if hasattr(obj_iter, 'all'):
  228. obj_iter = obj_iter.all()
  229. try:
  230. iter(obj_iter)
  231. except TypeError:
  232. obj_iter = [obj_iter]
  233. for obj in obj_iter:
  234. rel_objs = []
  235. for part in path:
  236. if not part:
  237. continue
  238. try:
  239. related = getattr(obj, part[0])
  240. except ObjectDoesNotExist:
  241. continue
  242. if related is not None:
  243. rel_objs.extend(cls.traverse_qs(related, [part[1:]]))
  244. ret_val.append((obj, rel_objs))
  245. return ret_val
  246. @classmethod
  247. def setUpTestData(cls):
  248. cls.person1 = Person.objects.create(name='Joe')
  249. cls.person2 = Person.objects.create(name='Mary')
  250. # Set main_room for each house before creating the next one for
  251. # databases where supports_nullable_unique_constraints is False.
  252. cls.house1 = House.objects.create(name='House 1', address='123 Main St', owner=cls.person1)
  253. cls.room1_1 = Room.objects.create(name='Dining room', house=cls.house1)
  254. cls.room1_2 = Room.objects.create(name='Lounge', house=cls.house1)
  255. cls.room1_3 = Room.objects.create(name='Kitchen', house=cls.house1)
  256. cls.house1.main_room = cls.room1_1
  257. cls.house1.save()
  258. cls.person1.houses.add(cls.house1)
  259. cls.house2 = House.objects.create(name='House 2', address='45 Side St', owner=cls.person1)
  260. cls.room2_1 = Room.objects.create(name='Dining room', house=cls.house2)
  261. cls.room2_2 = Room.objects.create(name='Lounge', house=cls.house2)
  262. cls.room2_3 = Room.objects.create(name='Kitchen', house=cls.house2)
  263. cls.house2.main_room = cls.room2_1
  264. cls.house2.save()
  265. cls.person1.houses.add(cls.house2)
  266. cls.house3 = House.objects.create(name='House 3', address='6 Downing St', owner=cls.person2)
  267. cls.room3_1 = Room.objects.create(name='Dining room', house=cls.house3)
  268. cls.room3_2 = Room.objects.create(name='Lounge', house=cls.house3)
  269. cls.room3_3 = Room.objects.create(name='Kitchen', house=cls.house3)
  270. cls.house3.main_room = cls.room3_1
  271. cls.house3.save()
  272. cls.person2.houses.add(cls.house3)
  273. cls.house4 = House.objects.create(name='house 4', address="7 Regents St", owner=cls.person2)
  274. cls.room4_1 = Room.objects.create(name='Dining room', house=cls.house4)
  275. cls.room4_2 = Room.objects.create(name='Lounge', house=cls.house4)
  276. cls.room4_3 = Room.objects.create(name='Kitchen', house=cls.house4)
  277. cls.house4.main_room = cls.room4_1
  278. cls.house4.save()
  279. cls.person2.houses.add(cls.house4)
  280. def test_traverse_qs(self):
  281. qs = Person.objects.prefetch_related('houses')
  282. related_objs_normal = [list(p.houses.all()) for p in qs],
  283. related_objs_from_traverse = [[inner[0] for inner in o[1]]
  284. for o in self.traverse_qs(qs, [['houses']])]
  285. self.assertEqual(related_objs_normal, (related_objs_from_traverse,))
  286. def test_ambiguous(self):
  287. # Ambiguous: Lookup was already seen with a different queryset.
  288. with self.assertRaises(ValueError):
  289. self.traverse_qs(
  290. Person.objects.prefetch_related('houses__rooms', Prefetch('houses', queryset=House.objects.all())),
  291. [['houses', 'rooms']]
  292. )
  293. # Ambiguous: Lookup houses_lst doesn't yet exist when performing houses_lst__rooms.
  294. with self.assertRaises(AttributeError):
  295. self.traverse_qs(
  296. Person.objects.prefetch_related(
  297. 'houses_lst__rooms',
  298. Prefetch('houses', queryset=House.objects.all(), to_attr='houses_lst')
  299. ),
  300. [['houses', 'rooms']]
  301. )
  302. # Not ambiguous.
  303. self.traverse_qs(
  304. Person.objects.prefetch_related('houses__rooms', 'houses'),
  305. [['houses', 'rooms']]
  306. )
  307. self.traverse_qs(
  308. Person.objects.prefetch_related(
  309. 'houses__rooms',
  310. Prefetch('houses', queryset=House.objects.all(), to_attr='houses_lst')
  311. ),
  312. [['houses', 'rooms']]
  313. )
  314. def test_m2m(self):
  315. # Control lookups.
  316. with self.assertNumQueries(2):
  317. lst1 = self.traverse_qs(
  318. Person.objects.prefetch_related('houses'),
  319. [['houses']]
  320. )
  321. # Test lookups.
  322. with self.assertNumQueries(2):
  323. lst2 = self.traverse_qs(
  324. Person.objects.prefetch_related(Prefetch('houses')),
  325. [['houses']]
  326. )
  327. self.assertEqual(lst1, lst2)
  328. with self.assertNumQueries(2):
  329. lst2 = self.traverse_qs(
  330. Person.objects.prefetch_related(Prefetch('houses', to_attr='houses_lst')),
  331. [['houses_lst']]
  332. )
  333. self.assertEqual(lst1, lst2)
  334. def test_reverse_m2m(self):
  335. # Control lookups.
  336. with self.assertNumQueries(2):
  337. lst1 = self.traverse_qs(
  338. House.objects.prefetch_related('occupants'),
  339. [['occupants']]
  340. )
  341. # Test lookups.
  342. with self.assertNumQueries(2):
  343. lst2 = self.traverse_qs(
  344. House.objects.prefetch_related(Prefetch('occupants')),
  345. [['occupants']]
  346. )
  347. self.assertEqual(lst1, lst2)
  348. with self.assertNumQueries(2):
  349. lst2 = self.traverse_qs(
  350. House.objects.prefetch_related(Prefetch('occupants', to_attr='occupants_lst')),
  351. [['occupants_lst']]
  352. )
  353. self.assertEqual(lst1, lst2)
  354. def test_m2m_through_fk(self):
  355. # Control lookups.
  356. with self.assertNumQueries(3):
  357. lst1 = self.traverse_qs(
  358. Room.objects.prefetch_related('house__occupants'),
  359. [['house', 'occupants']]
  360. )
  361. # Test lookups.
  362. with self.assertNumQueries(3):
  363. lst2 = self.traverse_qs(
  364. Room.objects.prefetch_related(Prefetch('house__occupants')),
  365. [['house', 'occupants']]
  366. )
  367. self.assertEqual(lst1, lst2)
  368. with self.assertNumQueries(3):
  369. lst2 = self.traverse_qs(
  370. Room.objects.prefetch_related(Prefetch('house__occupants', to_attr='occupants_lst')),
  371. [['house', 'occupants_lst']]
  372. )
  373. self.assertEqual(lst1, lst2)
  374. def test_m2m_through_gfk(self):
  375. TaggedItem.objects.create(tag="houses", content_object=self.house1)
  376. TaggedItem.objects.create(tag="houses", content_object=self.house2)
  377. # Control lookups.
  378. with self.assertNumQueries(3):
  379. lst1 = self.traverse_qs(
  380. TaggedItem.objects.filter(tag='houses').prefetch_related('content_object__rooms'),
  381. [['content_object', 'rooms']]
  382. )
  383. # Test lookups.
  384. with self.assertNumQueries(3):
  385. lst2 = self.traverse_qs(
  386. TaggedItem.objects.prefetch_related(
  387. Prefetch('content_object'),
  388. Prefetch('content_object__rooms', to_attr='rooms_lst')
  389. ),
  390. [['content_object', 'rooms_lst']]
  391. )
  392. self.assertEqual(lst1, lst2)
  393. def test_o2m_through_m2m(self):
  394. # Control lookups.
  395. with self.assertNumQueries(3):
  396. lst1 = self.traverse_qs(
  397. Person.objects.prefetch_related('houses', 'houses__rooms'),
  398. [['houses', 'rooms']]
  399. )
  400. # Test lookups.
  401. with self.assertNumQueries(3):
  402. lst2 = self.traverse_qs(
  403. Person.objects.prefetch_related(Prefetch('houses'), 'houses__rooms'),
  404. [['houses', 'rooms']]
  405. )
  406. self.assertEqual(lst1, lst2)
  407. with self.assertNumQueries(3):
  408. lst2 = self.traverse_qs(
  409. Person.objects.prefetch_related(Prefetch('houses'), Prefetch('houses__rooms')),
  410. [['houses', 'rooms']]
  411. )
  412. self.assertEqual(lst1, lst2)
  413. with self.assertNumQueries(3):
  414. lst2 = self.traverse_qs(
  415. Person.objects.prefetch_related(Prefetch('houses', to_attr='houses_lst'), 'houses_lst__rooms'),
  416. [['houses_lst', 'rooms']]
  417. )
  418. self.assertEqual(lst1, lst2)
  419. with self.assertNumQueries(3):
  420. lst2 = self.traverse_qs(
  421. Person.objects.prefetch_related(
  422. Prefetch('houses', to_attr='houses_lst'),
  423. Prefetch('houses_lst__rooms', to_attr='rooms_lst')
  424. ),
  425. [['houses_lst', 'rooms_lst']]
  426. )
  427. self.assertEqual(lst1, lst2)
  428. def test_generic_rel(self):
  429. bookmark = Bookmark.objects.create(url='http://www.djangoproject.com/')
  430. TaggedItem.objects.create(content_object=bookmark, tag='django')
  431. TaggedItem.objects.create(content_object=bookmark, favorite=bookmark, tag='python')
  432. # Control lookups.
  433. with self.assertNumQueries(4):
  434. lst1 = self.traverse_qs(
  435. Bookmark.objects.prefetch_related('tags', 'tags__content_object', 'favorite_tags'),
  436. [['tags', 'content_object'], ['favorite_tags']]
  437. )
  438. # Test lookups.
  439. with self.assertNumQueries(4):
  440. lst2 = self.traverse_qs(
  441. Bookmark.objects.prefetch_related(
  442. Prefetch('tags', to_attr='tags_lst'),
  443. Prefetch('tags_lst__content_object'),
  444. Prefetch('favorite_tags'),
  445. ),
  446. [['tags_lst', 'content_object'], ['favorite_tags']]
  447. )
  448. self.assertEqual(lst1, lst2)
  449. def test_traverse_single_item_property(self):
  450. # Control lookups.
  451. with self.assertNumQueries(5):
  452. lst1 = self.traverse_qs(
  453. Person.objects.prefetch_related(
  454. 'houses__rooms',
  455. 'primary_house__occupants__houses',
  456. ),
  457. [['primary_house', 'occupants', 'houses']]
  458. )
  459. # Test lookups.
  460. with self.assertNumQueries(5):
  461. lst2 = self.traverse_qs(
  462. Person.objects.prefetch_related(
  463. 'houses__rooms',
  464. Prefetch('primary_house__occupants', to_attr='occupants_lst'),
  465. 'primary_house__occupants_lst__houses',
  466. ),
  467. [['primary_house', 'occupants_lst', 'houses']]
  468. )
  469. self.assertEqual(lst1, lst2)
  470. def test_traverse_multiple_items_property(self):
  471. # Control lookups.
  472. with self.assertNumQueries(4):
  473. lst1 = self.traverse_qs(
  474. Person.objects.prefetch_related(
  475. 'houses',
  476. 'all_houses__occupants__houses',
  477. ),
  478. [['all_houses', 'occupants', 'houses']]
  479. )
  480. # Test lookups.
  481. with self.assertNumQueries(4):
  482. lst2 = self.traverse_qs(
  483. Person.objects.prefetch_related(
  484. 'houses',
  485. Prefetch('all_houses__occupants', to_attr='occupants_lst'),
  486. 'all_houses__occupants_lst__houses',
  487. ),
  488. [['all_houses', 'occupants_lst', 'houses']]
  489. )
  490. self.assertEqual(lst1, lst2)
  491. def test_custom_qs(self):
  492. # Test basic.
  493. with self.assertNumQueries(2):
  494. lst1 = list(Person.objects.prefetch_related('houses'))
  495. with self.assertNumQueries(2):
  496. lst2 = list(Person.objects.prefetch_related(
  497. Prefetch('houses', queryset=House.objects.all(), to_attr='houses_lst')))
  498. self.assertEqual(
  499. self.traverse_qs(lst1, [['houses']]),
  500. self.traverse_qs(lst2, [['houses_lst']])
  501. )
  502. # Test queryset filtering.
  503. with self.assertNumQueries(2):
  504. lst2 = list(
  505. Person.objects.prefetch_related(
  506. Prefetch(
  507. 'houses',
  508. queryset=House.objects.filter(pk__in=[self.house1.pk, self.house3.pk]),
  509. to_attr='houses_lst',
  510. )
  511. )
  512. )
  513. self.assertEqual(len(lst2[0].houses_lst), 1)
  514. self.assertEqual(lst2[0].houses_lst[0], self.house1)
  515. self.assertEqual(len(lst2[1].houses_lst), 1)
  516. self.assertEqual(lst2[1].houses_lst[0], self.house3)
  517. # Test flattened.
  518. with self.assertNumQueries(3):
  519. lst1 = list(Person.objects.prefetch_related('houses__rooms'))
  520. with self.assertNumQueries(3):
  521. lst2 = list(Person.objects.prefetch_related(
  522. Prefetch('houses__rooms', queryset=Room.objects.all(), to_attr='rooms_lst')))
  523. self.assertEqual(
  524. self.traverse_qs(lst1, [['houses', 'rooms']]),
  525. self.traverse_qs(lst2, [['houses', 'rooms_lst']])
  526. )
  527. # Test inner select_related.
  528. with self.assertNumQueries(3):
  529. lst1 = list(Person.objects.prefetch_related('houses__owner'))
  530. with self.assertNumQueries(2):
  531. lst2 = list(Person.objects.prefetch_related(
  532. Prefetch('houses', queryset=House.objects.select_related('owner'))))
  533. self.assertEqual(
  534. self.traverse_qs(lst1, [['houses', 'owner']]),
  535. self.traverse_qs(lst2, [['houses', 'owner']])
  536. )
  537. # Test inner prefetch.
  538. inner_rooms_qs = Room.objects.filter(pk__in=[self.room1_1.pk, self.room1_2.pk])
  539. houses_qs_prf = House.objects.prefetch_related(
  540. Prefetch('rooms', queryset=inner_rooms_qs, to_attr='rooms_lst'))
  541. with self.assertNumQueries(4):
  542. lst2 = list(Person.objects.prefetch_related(
  543. Prefetch('houses', queryset=houses_qs_prf.filter(pk=self.house1.pk), to_attr='houses_lst'),
  544. Prefetch('houses_lst__rooms_lst__main_room_of')
  545. ))
  546. self.assertEqual(len(lst2[0].houses_lst[0].rooms_lst), 2)
  547. self.assertEqual(lst2[0].houses_lst[0].rooms_lst[0], self.room1_1)
  548. self.assertEqual(lst2[0].houses_lst[0].rooms_lst[1], self.room1_2)
  549. self.assertEqual(lst2[0].houses_lst[0].rooms_lst[0].main_room_of, self.house1)
  550. self.assertEqual(len(lst2[1].houses_lst), 0)
  551. # Test ForwardManyToOneDescriptor.
  552. houses = House.objects.select_related('owner')
  553. with self.assertNumQueries(6):
  554. rooms = Room.objects.all().prefetch_related('house')
  555. lst1 = self.traverse_qs(rooms, [['house', 'owner']])
  556. with self.assertNumQueries(2):
  557. rooms = Room.objects.all().prefetch_related(Prefetch('house', queryset=houses.all()))
  558. lst2 = self.traverse_qs(rooms, [['house', 'owner']])
  559. self.assertEqual(lst1, lst2)
  560. with self.assertNumQueries(2):
  561. houses = House.objects.select_related('owner')
  562. rooms = Room.objects.all().prefetch_related(Prefetch('house', queryset=houses.all(), to_attr='house_attr'))
  563. lst2 = self.traverse_qs(rooms, [['house_attr', 'owner']])
  564. self.assertEqual(lst1, lst2)
  565. room = Room.objects.all().prefetch_related(
  566. Prefetch('house', queryset=houses.filter(address='DoesNotExist'))
  567. ).first()
  568. with self.assertRaises(ObjectDoesNotExist):
  569. getattr(room, 'house')
  570. room = Room.objects.all().prefetch_related(
  571. Prefetch('house', queryset=houses.filter(address='DoesNotExist'), to_attr='house_attr')
  572. ).first()
  573. self.assertIsNone(room.house_attr)
  574. rooms = Room.objects.all().prefetch_related(Prefetch('house', queryset=House.objects.only('name')))
  575. with self.assertNumQueries(2):
  576. getattr(rooms.first().house, 'name')
  577. with self.assertNumQueries(3):
  578. getattr(rooms.first().house, 'address')
  579. # Test ReverseOneToOneDescriptor.
  580. houses = House.objects.select_related('owner')
  581. with self.assertNumQueries(6):
  582. rooms = Room.objects.all().prefetch_related('main_room_of')
  583. lst1 = self.traverse_qs(rooms, [['main_room_of', 'owner']])
  584. with self.assertNumQueries(2):
  585. rooms = Room.objects.all().prefetch_related(Prefetch('main_room_of', queryset=houses.all()))
  586. lst2 = self.traverse_qs(rooms, [['main_room_of', 'owner']])
  587. self.assertEqual(lst1, lst2)
  588. with self.assertNumQueries(2):
  589. rooms = list(
  590. Room.objects.all().prefetch_related(
  591. Prefetch('main_room_of', queryset=houses.all(), to_attr='main_room_of_attr')
  592. )
  593. )
  594. lst2 = self.traverse_qs(rooms, [['main_room_of_attr', 'owner']])
  595. self.assertEqual(lst1, lst2)
  596. room = Room.objects.filter(main_room_of__isnull=False).prefetch_related(
  597. Prefetch('main_room_of', queryset=houses.filter(address='DoesNotExist'))
  598. ).first()
  599. with self.assertRaises(ObjectDoesNotExist):
  600. getattr(room, 'main_room_of')
  601. room = Room.objects.filter(main_room_of__isnull=False).prefetch_related(
  602. Prefetch('main_room_of', queryset=houses.filter(address='DoesNotExist'), to_attr='main_room_of_attr')
  603. ).first()
  604. self.assertIsNone(room.main_room_of_attr)
  605. # The custom queryset filters should be applied to the queryset
  606. # instance returned by the manager.
  607. person = Person.objects.prefetch_related(
  608. Prefetch('houses', queryset=House.objects.filter(name='House 1')),
  609. ).get(pk=self.person1.pk)
  610. self.assertEqual(
  611. list(person.houses.all()),
  612. list(person.houses.all().all()),
  613. )
  614. def test_nested_prefetch_related_are_not_overwritten(self):
  615. # Regression test for #24873
  616. houses_2 = House.objects.prefetch_related(Prefetch('rooms'))
  617. persons = Person.objects.prefetch_related(Prefetch('houses', queryset=houses_2))
  618. houses = House.objects.prefetch_related(Prefetch('occupants', queryset=persons))
  619. list(houses) # queryset must be evaluated once to reproduce the bug.
  620. self.assertEqual(
  621. houses.all()[0].occupants.all()[0].houses.all()[1].rooms.all()[0],
  622. self.room2_1
  623. )
  624. def test_apply_rel_filters_deprecation_shim(self):
  625. # Simulate a missing `_apply_rel_filters` method.
  626. del Person.houses.related_manager_cls._apply_rel_filters
  627. # Also remove `get_queryset` as it rely on `_apply_rel_filters`.
  628. del Person.houses.related_manager_cls.get_queryset
  629. try:
  630. with warnings.catch_warnings(record=True) as warns:
  631. warnings.simplefilter('always')
  632. list(Person.objects.prefetch_related(
  633. Prefetch('houses', queryset=House.objects.filter(name='House 1'))
  634. ))
  635. finally:
  636. # Deleting `related_manager_cls` will force the creation of a new
  637. # class since it's a `cached_property`.
  638. del Person.houses.related_manager_cls
  639. msg = (
  640. 'The `django.db.models.fields.related_descriptors.ManyRelatedManager` class '
  641. 'must implement a `_apply_rel_filters()` method that accepts a `QuerySet` as '
  642. 'its single argument and returns an appropriately filtered version of it.'
  643. )
  644. self.assertEqual(len(warns), 2) # Once person.
  645. self.assertEqual(str(warns[0].message), msg)
  646. self.assertEqual(str(warns[0].message), msg)
  647. def test_values_queryset(self):
  648. with self.assertRaisesMessage(ValueError, 'Prefetch querysets cannot use values().'):
  649. Prefetch('houses', House.objects.values('pk'))
  650. def test_to_attr_doesnt_cache_through_attr_as_list(self):
  651. house = House.objects.prefetch_related(
  652. Prefetch('rooms', queryset=Room.objects.all(), to_attr='to_rooms'),
  653. ).get(pk=self.house3.pk)
  654. self.assertIsInstance(house.rooms.all(), QuerySet)
  655. def test_to_attr_cached_property(self):
  656. persons = Person.objects.prefetch_related(
  657. Prefetch('houses', House.objects.all(), to_attr='cached_all_houses'),
  658. )
  659. for person in persons:
  660. # To bypass caching at the related descriptor level, don't use
  661. # person.houses.all() here.
  662. all_houses = list(House.objects.filter(occupants=person))
  663. with self.assertNumQueries(0):
  664. self.assertEqual(person.cached_all_houses, all_houses)
  665. class DefaultManagerTests(TestCase):
  666. def setUp(self):
  667. self.qual1 = Qualification.objects.create(name="BA")
  668. self.qual2 = Qualification.objects.create(name="BSci")
  669. self.qual3 = Qualification.objects.create(name="MA")
  670. self.qual4 = Qualification.objects.create(name="PhD")
  671. self.teacher1 = Teacher.objects.create(name="Mr Cleese")
  672. self.teacher2 = Teacher.objects.create(name="Mr Idle")
  673. self.teacher3 = Teacher.objects.create(name="Mr Chapman")
  674. self.teacher1.qualifications.add(self.qual1, self.qual2, self.qual3, self.qual4)
  675. self.teacher2.qualifications.add(self.qual1)
  676. self.teacher3.qualifications.add(self.qual2)
  677. self.dept1 = Department.objects.create(name="English")
  678. self.dept2 = Department.objects.create(name="Physics")
  679. self.dept1.teachers.add(self.teacher1, self.teacher2)
  680. self.dept2.teachers.add(self.teacher1, self.teacher3)
  681. def test_m2m_then_m2m(self):
  682. with self.assertNumQueries(3):
  683. # When we prefetch the teachers, and force the query, we don't want
  684. # the default manager on teachers to immediately get all the related
  685. # qualifications, since this will do one query per teacher.
  686. qs = Department.objects.prefetch_related('teachers')
  687. depts = "".join("%s department: %s\n" %
  688. (dept.name, ", ".join(six.text_type(t) for t in dept.teachers.all()))
  689. for dept in qs)
  690. self.assertEqual(depts,
  691. "English department: Mr Cleese (BA, BSci, MA, PhD), Mr Idle (BA)\n"
  692. "Physics department: Mr Cleese (BA, BSci, MA, PhD), Mr Chapman (BSci)\n")
  693. class GenericRelationTests(TestCase):
  694. @classmethod
  695. def setUpTestData(cls):
  696. book1 = Book.objects.create(title="Winnie the Pooh")
  697. book2 = Book.objects.create(title="Do you like green eggs and spam?")
  698. book3 = Book.objects.create(title="Three Men In A Boat")
  699. reader1 = Reader.objects.create(name="me")
  700. reader2 = Reader.objects.create(name="you")
  701. reader3 = Reader.objects.create(name="someone")
  702. book1.read_by.add(reader1, reader2)
  703. book2.read_by.add(reader2)
  704. book3.read_by.add(reader3)
  705. cls.book1, cls.book2, cls.book3 = book1, book2, book3
  706. cls.reader1, cls.reader2, cls.reader3 = reader1, reader2, reader3
  707. def test_prefetch_GFK(self):
  708. TaggedItem.objects.create(tag="awesome", content_object=self.book1)
  709. TaggedItem.objects.create(tag="great", content_object=self.reader1)
  710. TaggedItem.objects.create(tag="outstanding", content_object=self.book2)
  711. TaggedItem.objects.create(tag="amazing", content_object=self.reader3)
  712. # 1 for TaggedItem table, 1 for Book table, 1 for Reader table
  713. with self.assertNumQueries(3):
  714. qs = TaggedItem.objects.prefetch_related('content_object')
  715. list(qs)
  716. def test_prefetch_GFK_nonint_pk(self):
  717. Comment.objects.create(comment="awesome", content_object=self.book1)
  718. # 1 for Comment table, 1 for Book table
  719. with self.assertNumQueries(2):
  720. qs = Comment.objects.prefetch_related('content_object')
  721. [c.content_object for c in qs]
  722. def test_traverse_GFK(self):
  723. """
  724. A 'content_object' can be traversed with prefetch_related() and
  725. get to related objects on the other side (assuming it is suitably
  726. filtered)
  727. """
  728. TaggedItem.objects.create(tag="awesome", content_object=self.book1)
  729. TaggedItem.objects.create(tag="awesome", content_object=self.book2)
  730. TaggedItem.objects.create(tag="awesome", content_object=self.book3)
  731. TaggedItem.objects.create(tag="awesome", content_object=self.reader1)
  732. TaggedItem.objects.create(tag="awesome", content_object=self.reader2)
  733. ct = ContentType.objects.get_for_model(Book)
  734. # We get 3 queries - 1 for main query, 1 for content_objects since they
  735. # all use the same table, and 1 for the 'read_by' relation.
  736. with self.assertNumQueries(3):
  737. # If we limit to books, we know that they will have 'read_by'
  738. # attributes, so the following makes sense:
  739. qs = TaggedItem.objects.filter(content_type=ct, tag='awesome').prefetch_related('content_object__read_by')
  740. readers_of_awesome_books = {r.name for tag in qs
  741. for r in tag.content_object.read_by.all()}
  742. self.assertEqual(readers_of_awesome_books, {"me", "you", "someone"})
  743. def test_nullable_GFK(self):
  744. TaggedItem.objects.create(tag="awesome", content_object=self.book1,
  745. created_by=self.reader1)
  746. TaggedItem.objects.create(tag="great", content_object=self.book2)
  747. TaggedItem.objects.create(tag="rubbish", content_object=self.book3)
  748. with self.assertNumQueries(2):
  749. result = [t.created_by for t in TaggedItem.objects.prefetch_related('created_by')]
  750. self.assertEqual(result,
  751. [t.created_by for t in TaggedItem.objects.all()])
  752. def test_generic_relation(self):
  753. bookmark = Bookmark.objects.create(url='http://www.djangoproject.com/')
  754. TaggedItem.objects.create(content_object=bookmark, tag='django')
  755. TaggedItem.objects.create(content_object=bookmark, tag='python')
  756. with self.assertNumQueries(2):
  757. tags = [t.tag for b in Bookmark.objects.prefetch_related('tags')
  758. for t in b.tags.all()]
  759. self.assertEqual(sorted(tags), ["django", "python"])
  760. def test_charfield_GFK(self):
  761. b = Bookmark.objects.create(url='http://www.djangoproject.com/')
  762. TaggedItem.objects.create(content_object=b, tag='django')
  763. TaggedItem.objects.create(content_object=b, favorite=b, tag='python')
  764. with self.assertNumQueries(3):
  765. bookmark = Bookmark.objects.filter(pk=b.pk).prefetch_related('tags', 'favorite_tags')[0]
  766. self.assertEqual(sorted([i.tag for i in bookmark.tags.all()]), ["django", "python"])
  767. self.assertEqual([i.tag for i in bookmark.favorite_tags.all()], ["python"])
  768. def test_custom_queryset(self):
  769. bookmark = Bookmark.objects.create(url='http://www.djangoproject.com/')
  770. django_tag = TaggedItem.objects.create(content_object=bookmark, tag='django')
  771. TaggedItem.objects.create(content_object=bookmark, tag='python')
  772. with self.assertNumQueries(2):
  773. bookmark = Bookmark.objects.prefetch_related(
  774. Prefetch('tags', TaggedItem.objects.filter(tag='django')),
  775. ).get()
  776. with self.assertNumQueries(0):
  777. self.assertEqual(list(bookmark.tags.all()), [django_tag])
  778. # The custom queryset filters should be applied to the queryset
  779. # instance returned by the manager.
  780. self.assertEqual(list(bookmark.tags.all()), list(bookmark.tags.all().all()))
  781. class MultiTableInheritanceTest(TestCase):
  782. @classmethod
  783. def setUpTestData(cls):
  784. cls.book1 = BookWithYear.objects.create(title='Poems', published_year=2010)
  785. cls.book2 = BookWithYear.objects.create(title='More poems', published_year=2011)
  786. cls.author1 = AuthorWithAge.objects.create(name='Jane', first_book=cls.book1, age=50)
  787. cls.author2 = AuthorWithAge.objects.create(name='Tom', first_book=cls.book1, age=49)
  788. cls.author3 = AuthorWithAge.objects.create(name='Robert', first_book=cls.book2, age=48)
  789. cls.author_address = AuthorAddress.objects.create(author=cls.author1, address='SomeStreet 1')
  790. cls.book2.aged_authors.add(cls.author2, cls.author3)
  791. cls.br1 = BookReview.objects.create(book=cls.book1, notes='review book1')
  792. cls.br2 = BookReview.objects.create(book=cls.book2, notes='review book2')
  793. def test_foreignkey(self):
  794. with self.assertNumQueries(2):
  795. qs = AuthorWithAge.objects.prefetch_related('addresses')
  796. addresses = [[six.text_type(address) for address in obj.addresses.all()] for obj in qs]
  797. self.assertEqual(addresses, [[six.text_type(self.author_address)], [], []])
  798. def test_foreignkey_to_inherited(self):
  799. with self.assertNumQueries(2):
  800. qs = BookReview.objects.prefetch_related('book')
  801. titles = [obj.book.title for obj in qs]
  802. self.assertEqual(titles, ["Poems", "More poems"])
  803. def test_m2m_to_inheriting_model(self):
  804. qs = AuthorWithAge.objects.prefetch_related('books_with_year')
  805. with self.assertNumQueries(2):
  806. lst = [[six.text_type(book) for book in author.books_with_year.all()] for author in qs]
  807. qs = AuthorWithAge.objects.all()
  808. lst2 = [[six.text_type(book) for book in author.books_with_year.all()] for author in qs]
  809. self.assertEqual(lst, lst2)
  810. qs = BookWithYear.objects.prefetch_related('aged_authors')
  811. with self.assertNumQueries(2):
  812. lst = [[six.text_type(author) for author in book.aged_authors.all()] for book in qs]
  813. qs = BookWithYear.objects.all()
  814. lst2 = [[six.text_type(author) for author in book.aged_authors.all()] for book in qs]
  815. self.assertEqual(lst, lst2)
  816. def test_parent_link_prefetch(self):
  817. with self.assertNumQueries(2):
  818. [a.author for a in AuthorWithAge.objects.prefetch_related('author')]
  819. @override_settings(DEBUG=True)
  820. def test_child_link_prefetch(self):
  821. with self.assertNumQueries(2):
  822. authors = [a.authorwithage for a in Author.objects.prefetch_related('authorwithage')]
  823. # Regression for #18090: the prefetching query must include an IN clause.
  824. # Note that on Oracle the table name is upper case in the generated SQL,
  825. # thus the .lower() call.
  826. self.assertIn('authorwithage', connection.queries[-1]['sql'].lower())
  827. self.assertIn(' IN ', connection.queries[-1]['sql'])
  828. self.assertEqual(authors, [a.authorwithage for a in Author.objects.all()])
  829. class ForeignKeyToFieldTest(TestCase):
  830. @classmethod
  831. def setUpTestData(cls):
  832. cls.book = Book.objects.create(title='Poems')
  833. cls.author1 = Author.objects.create(name='Jane', first_book=cls.book)
  834. cls.author2 = Author.objects.create(name='Tom', first_book=cls.book)
  835. cls.author3 = Author.objects.create(name='Robert', first_book=cls.book)
  836. cls.author_address = AuthorAddress.objects.create(author=cls.author1, address='SomeStreet 1')
  837. FavoriteAuthors.objects.create(author=cls.author1, likes_author=cls.author2)
  838. FavoriteAuthors.objects.create(author=cls.author2, likes_author=cls.author3)
  839. FavoriteAuthors.objects.create(author=cls.author3, likes_author=cls.author1)
  840. def test_foreignkey(self):
  841. with self.assertNumQueries(2):
  842. qs = Author.objects.prefetch_related('addresses')
  843. addresses = [[six.text_type(address) for address in obj.addresses.all()]
  844. for obj in qs]
  845. self.assertEqual(addresses, [[six.text_type(self.author_address)], [], []])
  846. def test_m2m(self):
  847. with self.assertNumQueries(3):
  848. qs = Author.objects.all().prefetch_related('favorite_authors', 'favors_me')
  849. favorites = [(
  850. [six.text_type(i_like) for i_like in author.favorite_authors.all()],
  851. [six.text_type(likes_me) for likes_me in author.favors_me.all()]
  852. ) for author in qs]
  853. self.assertEqual(
  854. favorites,
  855. [
  856. ([six.text_type(self.author2)], [six.text_type(self.author3)]),
  857. ([six.text_type(self.author3)], [six.text_type(self.author1)]),
  858. ([six.text_type(self.author1)], [six.text_type(self.author2)])
  859. ]
  860. )
  861. class LookupOrderingTest(TestCase):
  862. """
  863. Test cases that demonstrate that ordering of lookups is important, and
  864. ensure it is preserved.
  865. """
  866. def setUp(self):
  867. self.person1 = Person.objects.create(name="Joe")
  868. self.person2 = Person.objects.create(name="Mary")
  869. # Set main_room for each house before creating the next one for
  870. # databases where supports_nullable_unique_constraints is False.
  871. self.house1 = House.objects.create(address="123 Main St")
  872. self.room1_1 = Room.objects.create(name="Dining room", house=self.house1)
  873. self.room1_2 = Room.objects.create(name="Lounge", house=self.house1)
  874. self.room1_3 = Room.objects.create(name="Kitchen", house=self.house1)
  875. self.house1.main_room = self.room1_1
  876. self.house1.save()
  877. self.person1.houses.add(self.house1)
  878. self.house2 = House.objects.create(address="45 Side St")
  879. self.room2_1 = Room.objects.create(name="Dining room", house=self.house2)
  880. self.room2_2 = Room.objects.create(name="Lounge", house=self.house2)
  881. self.house2.main_room = self.room2_1
  882. self.house2.save()
  883. self.person1.houses.add(self.house2)
  884. self.house3 = House.objects.create(address="6 Downing St")
  885. self.room3_1 = Room.objects.create(name="Dining room", house=self.house3)
  886. self.room3_2 = Room.objects.create(name="Lounge", house=self.house3)
  887. self.room3_3 = Room.objects.create(name="Kitchen", house=self.house3)
  888. self.house3.main_room = self.room3_1
  889. self.house3.save()
  890. self.person2.houses.add(self.house3)
  891. self.house4 = House.objects.create(address="7 Regents St")
  892. self.room4_1 = Room.objects.create(name="Dining room", house=self.house4)
  893. self.room4_2 = Room.objects.create(name="Lounge", house=self.house4)
  894. self.house4.main_room = self.room4_1
  895. self.house4.save()
  896. self.person2.houses.add(self.house4)
  897. def test_order(self):
  898. with self.assertNumQueries(4):
  899. # The following two queries must be done in the same order as written,
  900. # otherwise 'primary_house' will cause non-prefetched lookups
  901. qs = Person.objects.prefetch_related('houses__rooms',
  902. 'primary_house__occupants')
  903. [list(p.primary_house.occupants.all()) for p in qs]
  904. class NullableTest(TestCase):
  905. @classmethod
  906. def setUpTestData(cls):
  907. boss = Employee.objects.create(name="Peter")
  908. Employee.objects.create(name="Joe", boss=boss)
  909. Employee.objects.create(name="Angela", boss=boss)
  910. def test_traverse_nullable(self):
  911. # Because we use select_related() for 'boss', it doesn't need to be
  912. # prefetched, but we can still traverse it although it contains some nulls
  913. with self.assertNumQueries(2):
  914. qs = Employee.objects.select_related('boss').prefetch_related('boss__serfs')
  915. co_serfs = [list(e.boss.serfs.all()) if e.boss is not None else []
  916. for e in qs]
  917. qs2 = Employee.objects.select_related('boss')
  918. co_serfs2 = [list(e.boss.serfs.all()) if e.boss is not None else [] for e in qs2]
  919. self.assertEqual(co_serfs, co_serfs2)
  920. def test_prefetch_nullable(self):
  921. # One for main employee, one for boss, one for serfs
  922. with self.assertNumQueries(3):
  923. qs = Employee.objects.prefetch_related('boss__serfs')
  924. co_serfs = [list(e.boss.serfs.all()) if e.boss is not None else []
  925. for e in qs]
  926. qs2 = Employee.objects.all()
  927. co_serfs2 = [list(e.boss.serfs.all()) if e.boss is not None else [] for e in qs2]
  928. self.assertEqual(co_serfs, co_serfs2)
  929. def test_in_bulk(self):
  930. """
  931. In-bulk does correctly prefetch objects by not using .iterator()
  932. directly.
  933. """
  934. boss1 = Employee.objects.create(name="Peter")
  935. boss2 = Employee.objects.create(name="Jack")
  936. with self.assertNumQueries(2):
  937. # Prefetch is done and it does not cause any errors.
  938. bulk = Employee.objects.prefetch_related('serfs').in_bulk([boss1.pk, boss2.pk])
  939. for b in bulk.values():
  940. list(b.serfs.all())
  941. class MultiDbTests(TestCase):
  942. multi_db = True
  943. def test_using_is_honored_m2m(self):
  944. B = Book.objects.using('other')
  945. A = Author.objects.using('other')
  946. book1 = B.create(title="Poems")
  947. book2 = B.create(title="Jane Eyre")
  948. book3 = B.create(title="Wuthering Heights")
  949. book4 = B.create(title="Sense and Sensibility")
  950. author1 = A.create(name="Charlotte", first_book=book1)
  951. author2 = A.create(name="Anne", first_book=book1)
  952. author3 = A.create(name="Emily", first_book=book1)
  953. author4 = A.create(name="Jane", first_book=book4)
  954. book1.authors.add(author1, author2, author3)
  955. book2.authors.add(author1)
  956. book3.authors.add(author3)
  957. book4.authors.add(author4)
  958. # Forward
  959. qs1 = B.prefetch_related('authors')
  960. with self.assertNumQueries(2, using='other'):
  961. books = "".join("%s (%s)\n" %
  962. (book.title, ", ".join(a.name for a in book.authors.all()))
  963. for book in qs1)
  964. self.assertEqual(books,
  965. "Poems (Charlotte, Anne, Emily)\n"
  966. "Jane Eyre (Charlotte)\n"
  967. "Wuthering Heights (Emily)\n"
  968. "Sense and Sensibility (Jane)\n")
  969. # Reverse
  970. qs2 = A.prefetch_related('books')
  971. with self.assertNumQueries(2, using='other'):
  972. authors = "".join("%s: %s\n" %
  973. (author.name, ", ".join(b.title for b in author.books.all()))
  974. for author in qs2)
  975. self.assertEqual(authors,
  976. "Charlotte: Poems, Jane Eyre\n"
  977. "Anne: Poems\n"
  978. "Emily: Poems, Wuthering Heights\n"
  979. "Jane: Sense and Sensibility\n")
  980. def test_using_is_honored_fkey(self):
  981. B = Book.objects.using('other')
  982. A = Author.objects.using('other')
  983. book1 = B.create(title="Poems")
  984. book2 = B.create(title="Sense and Sensibility")
  985. A.create(name="Charlotte Bronte", first_book=book1)
  986. A.create(name="Jane Austen", first_book=book2)
  987. # Forward
  988. with self.assertNumQueries(2, using='other'):
  989. books = ", ".join(a.first_book.title for a in A.prefetch_related('first_book'))
  990. self.assertEqual("Poems, Sense and Sensibility", books)
  991. # Reverse
  992. with self.assertNumQueries(2, using='other'):
  993. books = "".join("%s (%s)\n" %
  994. (b.title, ", ".join(a.name for a in b.first_time_authors.all()))
  995. for b in B.prefetch_related('first_time_authors'))
  996. self.assertEqual(books,
  997. "Poems (Charlotte Bronte)\n"
  998. "Sense and Sensibility (Jane Austen)\n")
  999. def test_using_is_honored_inheritance(self):
  1000. B = BookWithYear.objects.using('other')
  1001. A = AuthorWithAge.objects.using('other')
  1002. book1 = B.create(title="Poems", published_year=2010)
  1003. B.create(title="More poems", published_year=2011)
  1004. A.create(name='Jane', first_book=book1, age=50)
  1005. A.create(name='Tom', first_book=book1, age=49)
  1006. # parent link
  1007. with self.assertNumQueries(2, using='other'):
  1008. authors = ", ".join(a.author.name for a in A.prefetch_related('author'))
  1009. self.assertEqual(authors, "Jane, Tom")
  1010. # child link
  1011. with self.assertNumQueries(2, using='other'):
  1012. ages = ", ".join(str(a.authorwithage.age) for a in A.prefetch_related('authorwithage'))
  1013. self.assertEqual(ages, "50, 49")
  1014. def test_using_is_honored_custom_qs(self):
  1015. B = Book.objects.using('other')
  1016. A = Author.objects.using('other')
  1017. book1 = B.create(title="Poems")
  1018. book2 = B.create(title="Sense and Sensibility")
  1019. A.create(name="Charlotte Bronte", first_book=book1)
  1020. A.create(name="Jane Austen", first_book=book2)
  1021. # Implicit hinting
  1022. with self.assertNumQueries(2, using='other'):
  1023. prefetch = Prefetch('first_time_authors', queryset=Author.objects.all())
  1024. books = "".join("%s (%s)\n" %
  1025. (b.title, ", ".join(a.name for a in b.first_time_authors.all()))
  1026. for b in B.prefetch_related(prefetch))
  1027. self.assertEqual(books,
  1028. "Poems (Charlotte Bronte)\n"
  1029. "Sense and Sensibility (Jane Austen)\n")
  1030. # Explicit using on the same db.
  1031. with self.assertNumQueries(2, using='other'):
  1032. prefetch = Prefetch('first_time_authors', queryset=Author.objects.using('other'))
  1033. books = "".join("%s (%s)\n" %
  1034. (b.title, ", ".join(a.name for a in b.first_time_authors.all()))
  1035. for b in B.prefetch_related(prefetch))
  1036. self.assertEqual(books,
  1037. "Poems (Charlotte Bronte)\n"
  1038. "Sense and Sensibility (Jane Austen)\n")
  1039. # Explicit using on a different db.
  1040. with self.assertNumQueries(1, using='default'), self.assertNumQueries(1, using='other'):
  1041. prefetch = Prefetch('first_time_authors', queryset=Author.objects.using('default'))
  1042. books = "".join("%s (%s)\n" %
  1043. (b.title, ", ".join(a.name for a in b.first_time_authors.all()))
  1044. for b in B.prefetch_related(prefetch))
  1045. self.assertEqual(books,
  1046. "Poems ()\n"
  1047. "Sense and Sensibility ()\n")
  1048. class Ticket19607Tests(TestCase):
  1049. def setUp(self):
  1050. for id, name1, name2 in [
  1051. (1, 'einfach', 'simple'),
  1052. (2, 'schwierig', 'difficult'),
  1053. ]:
  1054. LessonEntry.objects.create(id=id, name1=name1, name2=name2)
  1055. for id, lesson_entry_id, name in [
  1056. (1, 1, 'einfach'),
  1057. (2, 1, 'simple'),
  1058. (3, 2, 'schwierig'),
  1059. (4, 2, 'difficult'),
  1060. ]:
  1061. WordEntry.objects.create(id=id, lesson_entry_id=lesson_entry_id, name=name)
  1062. def test_bug(self):
  1063. list(WordEntry.objects.prefetch_related('lesson_entry', 'lesson_entry__wordentry_set'))
  1064. class Ticket21410Tests(TestCase):
  1065. def setUp(self):
  1066. self.book1 = Book.objects.create(title="Poems")
  1067. self.book2 = Book.objects.create(title="Jane Eyre")
  1068. self.book3 = Book.objects.create(title="Wuthering Heights")
  1069. self.book4 = Book.objects.create(title="Sense and Sensibility")
  1070. self.author1 = Author2.objects.create(name="Charlotte", first_book=self.book1)
  1071. self.author2 = Author2.objects.create(name="Anne", first_book=self.book1)
  1072. self.author3 = Author2.objects.create(name="Emily", first_book=self.book1)
  1073. self.author4 = Author2.objects.create(name="Jane", first_book=self.book4)
  1074. self.author1.favorite_books.add(self.book1, self.book2, self.book3)
  1075. self.author2.favorite_books.add(self.book1)
  1076. self.author3.favorite_books.add(self.book2)
  1077. self.author4.favorite_books.add(self.book3)
  1078. def test_bug(self):
  1079. list(Author2.objects.prefetch_related('first_book', 'favorite_books'))
  1080. class Ticket21760Tests(TestCase):
  1081. def setUp(self):
  1082. self.rooms = []
  1083. for _ in range(3):
  1084. house = House.objects.create()
  1085. for _ in range(3):
  1086. self.rooms.append(Room.objects.create(house=house))
  1087. # Set main_room for each house before creating the next one for
  1088. # databases where supports_nullable_unique_constraints is False.
  1089. house.main_room = self.rooms[-3]
  1090. house.save()
  1091. def test_bug(self):
  1092. prefetcher = get_prefetcher(self.rooms[0], 'house', 'house')[0]
  1093. queryset = prefetcher.get_prefetch_queryset(list(Room.objects.all()))[0]
  1094. self.assertNotIn(' JOIN ', force_text(queryset.query))
  1095. class Ticket25546Tests(TestCase):
  1096. """
  1097. Nested prefetch_related() shouldn't trigger duplicate queries for the same
  1098. lookup.
  1099. Before, prefetch queries were for 'addresses', 'first_time_authors', and
  1100. 'first_time_authors__addresses'. The last query is the duplicate.
  1101. """
  1102. @classmethod
  1103. def setUpTestData(cls):
  1104. cls.book1, cls.book2 = [
  1105. Book.objects.create(title='book1'),
  1106. Book.objects.create(title='book2'),
  1107. ]
  1108. cls.author11, cls.author12, cls.author21 = [
  1109. Author.objects.create(first_book=cls.book1, name='Author11'),
  1110. Author.objects.create(first_book=cls.book1, name='Author12'),
  1111. Author.objects.create(first_book=cls.book2, name='Author21'),
  1112. ]
  1113. cls.author1_address1, cls.author1_address2, cls.author2_address1 = [
  1114. AuthorAddress.objects.create(author=cls.author11, address='Happy place'),
  1115. AuthorAddress.objects.create(author=cls.author12, address='Haunted house'),
  1116. AuthorAddress.objects.create(author=cls.author21, address='Happy place'),
  1117. ]
  1118. def test_prefetch(self):
  1119. with self.assertNumQueries(3):
  1120. books = Book.objects.filter(
  1121. title__in=['book1', 'book2'],
  1122. ).prefetch_related(
  1123. Prefetch(
  1124. 'first_time_authors',
  1125. Author.objects.prefetch_related(
  1126. Prefetch(
  1127. 'addresses',
  1128. AuthorAddress.objects.filter(address='Happy place'),
  1129. )
  1130. ),
  1131. ),
  1132. )
  1133. book1, book2 = list(books)
  1134. with self.assertNumQueries(0):
  1135. self.assertListEqual(list(book1.first_time_authors.all()), [self.author11, self.author12])
  1136. self.assertListEqual(list(book2.first_time_authors.all()), [self.author21])
  1137. self.assertListEqual(list(book1.first_time_authors.all()[0].addresses.all()), [self.author1_address1])
  1138. self.assertListEqual(list(book1.first_time_authors.all()[1].addresses.all()), [])
  1139. self.assertListEqual(list(book2.first_time_authors.all()[0].addresses.all()), [self.author2_address1])
  1140. self.assertEqual(
  1141. list(book1.first_time_authors.all()), list(book1.first_time_authors.all().all())
  1142. )
  1143. self.assertEqual(
  1144. list(book2.first_time_authors.all()), list(book2.first_time_authors.all().all())
  1145. )
  1146. self.assertEqual(
  1147. list(book1.first_time_authors.all()[0].addresses.all()),
  1148. list(book1.first_time_authors.all()[0].addresses.all().all())
  1149. )
  1150. self.assertEqual(
  1151. list(book1.first_time_authors.all()[1].addresses.all()),
  1152. list(book1.first_time_authors.all()[1].addresses.all().all())
  1153. )
  1154. self.assertEqual(
  1155. list(book2.first_time_authors.all()[0].addresses.all()),
  1156. list(book2.first_time_authors.all()[0].addresses.all().all())
  1157. )
  1158. def test_prefetch_with_to_attr(self):
  1159. with self.assertNumQueries(3):
  1160. books = Book.objects.filter(
  1161. title__in=['book1', 'book2'],
  1162. ).prefetch_related(
  1163. Prefetch(
  1164. 'first_time_authors',
  1165. Author.objects.prefetch_related(
  1166. Prefetch(
  1167. 'addresses',
  1168. AuthorAddress.objects.filter(address='Happy place'),
  1169. to_attr='happy_place',
  1170. )
  1171. ),
  1172. to_attr='first_authors',
  1173. ),
  1174. )
  1175. book1, book2 = list(books)
  1176. with self.assertNumQueries(0):
  1177. self.assertListEqual(book1.first_authors, [self.author11, self.author12])
  1178. self.assertListEqual(book2.first_authors, [self.author21])
  1179. self.assertListEqual(book1.first_authors[0].happy_place, [self.author1_address1])
  1180. self.assertListEqual(book1.first_authors[1].happy_place, [])
  1181. self.assertListEqual(book2.first_authors[0].happy_place, [self.author2_address1])