tests.py 78 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026
  1. from unittest import mock
  2. from django.contrib.contenttypes.models import ContentType
  3. from django.core.exceptions import ObjectDoesNotExist
  4. from django.db import NotSupportedError, connection
  5. from django.db.models import Prefetch, QuerySet, prefetch_related_objects
  6. from django.db.models.query import get_prefetcher
  7. from django.db.models.sql import Query
  8. from django.test import (
  9. TestCase,
  10. override_settings,
  11. skipIfDBFeature,
  12. skipUnlessDBFeature,
  13. )
  14. from django.test.utils import CaptureQueriesContext
  15. from .models import (
  16. Article,
  17. Author,
  18. Author2,
  19. AuthorAddress,
  20. AuthorWithAge,
  21. Bio,
  22. Book,
  23. Bookmark,
  24. BookReview,
  25. BookWithYear,
  26. Comment,
  27. Department,
  28. Employee,
  29. FavoriteAuthors,
  30. House,
  31. LessonEntry,
  32. ModelIterableSubclass,
  33. Person,
  34. Qualification,
  35. Reader,
  36. Room,
  37. TaggedItem,
  38. Teacher,
  39. WordEntry,
  40. )
  41. class TestDataMixin:
  42. @classmethod
  43. def setUpTestData(cls):
  44. cls.book1 = Book.objects.create(title="Poems")
  45. cls.book2 = Book.objects.create(title="Jane Eyre")
  46. cls.book3 = Book.objects.create(title="Wuthering Heights")
  47. cls.book4 = Book.objects.create(title="Sense and Sensibility")
  48. cls.author1 = Author.objects.create(name="Charlotte", first_book=cls.book1)
  49. cls.author2 = Author.objects.create(name="Anne", first_book=cls.book1)
  50. cls.author3 = Author.objects.create(name="Emily", first_book=cls.book1)
  51. cls.author4 = Author.objects.create(name="Jane", first_book=cls.book4)
  52. cls.book1.authors.add(cls.author1, cls.author2, cls.author3)
  53. cls.book2.authors.add(cls.author1)
  54. cls.book3.authors.add(cls.author3)
  55. cls.book4.authors.add(cls.author4)
  56. cls.reader1 = Reader.objects.create(name="Amy")
  57. cls.reader2 = Reader.objects.create(name="Belinda")
  58. cls.reader1.books_read.add(cls.book1, cls.book4)
  59. cls.reader2.books_read.add(cls.book2, cls.book4)
  60. class PrefetchRelatedTests(TestDataMixin, TestCase):
  61. def assertWhereContains(self, sql, needle):
  62. where_idx = sql.index("WHERE")
  63. self.assertEqual(
  64. sql.count(str(needle), where_idx),
  65. 1,
  66. msg="WHERE clause doesn't contain %s, actual SQL: %s"
  67. % (needle, sql[where_idx:]),
  68. )
  69. def test_m2m_forward(self):
  70. with self.assertNumQueries(2):
  71. lists = [
  72. list(b.authors.all()) for b in Book.objects.prefetch_related("authors")
  73. ]
  74. normal_lists = [list(b.authors.all()) for b in Book.objects.all()]
  75. self.assertEqual(lists, normal_lists)
  76. def test_m2m_reverse(self):
  77. with self.assertNumQueries(2):
  78. lists = [
  79. list(a.books.all()) for a in Author.objects.prefetch_related("books")
  80. ]
  81. normal_lists = [list(a.books.all()) for a in Author.objects.all()]
  82. self.assertEqual(lists, normal_lists)
  83. def test_foreignkey_forward(self):
  84. with self.assertNumQueries(2):
  85. books = [
  86. a.first_book for a in Author.objects.prefetch_related("first_book")
  87. ]
  88. normal_books = [a.first_book for a in Author.objects.all()]
  89. self.assertEqual(books, normal_books)
  90. def test_foreignkey_reverse(self):
  91. with self.assertNumQueries(2):
  92. [
  93. list(b.first_time_authors.all())
  94. for b in Book.objects.prefetch_related("first_time_authors")
  95. ]
  96. self.assertSequenceEqual(self.book2.authors.all(), [self.author1])
  97. def test_onetoone_reverse_no_match(self):
  98. # Regression for #17439
  99. with self.assertNumQueries(2):
  100. book = Book.objects.prefetch_related("bookwithyear").all()[0]
  101. with self.assertNumQueries(0):
  102. with self.assertRaises(BookWithYear.DoesNotExist):
  103. book.bookwithyear
  104. def test_onetoone_reverse_with_to_field_pk(self):
  105. """
  106. A model (Bio) with a OneToOneField primary key (author) that references
  107. a non-pk field (name) on the related model (Author) is prefetchable.
  108. """
  109. Bio.objects.bulk_create(
  110. [
  111. Bio(author=self.author1),
  112. Bio(author=self.author2),
  113. Bio(author=self.author3),
  114. ]
  115. )
  116. authors = Author.objects.filter(
  117. name__in=[self.author1, self.author2, self.author3],
  118. ).prefetch_related("bio")
  119. with self.assertNumQueries(2):
  120. for author in authors:
  121. self.assertEqual(author.name, author.bio.author.name)
  122. def test_survives_clone(self):
  123. with self.assertNumQueries(2):
  124. [
  125. list(b.first_time_authors.all())
  126. for b in Book.objects.prefetch_related("first_time_authors").exclude(
  127. id=1000
  128. )
  129. ]
  130. def test_len(self):
  131. with self.assertNumQueries(2):
  132. qs = Book.objects.prefetch_related("first_time_authors")
  133. len(qs)
  134. [list(b.first_time_authors.all()) for b in qs]
  135. def test_bool(self):
  136. with self.assertNumQueries(2):
  137. qs = Book.objects.prefetch_related("first_time_authors")
  138. bool(qs)
  139. [list(b.first_time_authors.all()) for b in qs]
  140. def test_count(self):
  141. with self.assertNumQueries(2):
  142. qs = Book.objects.prefetch_related("first_time_authors")
  143. [b.first_time_authors.count() for b in qs]
  144. def test_exists(self):
  145. with self.assertNumQueries(2):
  146. qs = Book.objects.prefetch_related("first_time_authors")
  147. [b.first_time_authors.exists() for b in qs]
  148. def test_in_and_prefetch_related(self):
  149. """
  150. Regression test for #20242 - QuerySet "in" didn't work the first time
  151. when using prefetch_related. This was fixed by the removal of chunked
  152. reads from QuerySet iteration in
  153. 70679243d1786e03557c28929f9762a119e3ac14.
  154. """
  155. qs = Book.objects.prefetch_related("first_time_authors")
  156. self.assertIn(qs[0], qs)
  157. def test_clear(self):
  158. with self.assertNumQueries(5):
  159. with_prefetch = Author.objects.prefetch_related("books")
  160. without_prefetch = with_prefetch.prefetch_related(None)
  161. [list(a.books.all()) for a in without_prefetch]
  162. def test_m2m_then_m2m(self):
  163. """A m2m can be followed through another m2m."""
  164. with self.assertNumQueries(3):
  165. qs = Author.objects.prefetch_related("books__read_by")
  166. lists = [
  167. [[str(r) for r in b.read_by.all()] for b in a.books.all()] for a in qs
  168. ]
  169. self.assertEqual(
  170. lists,
  171. [
  172. [["Amy"], ["Belinda"]], # Charlotte - Poems, Jane Eyre
  173. [["Amy"]], # Anne - Poems
  174. [["Amy"], []], # Emily - Poems, Wuthering Heights
  175. [["Amy", "Belinda"]], # Jane - Sense and Sense
  176. ],
  177. )
  178. def test_overriding_prefetch(self):
  179. with self.assertNumQueries(3):
  180. qs = Author.objects.prefetch_related("books", "books__read_by")
  181. lists = [
  182. [[str(r) for r in b.read_by.all()] for b in a.books.all()] for a in qs
  183. ]
  184. self.assertEqual(
  185. lists,
  186. [
  187. [["Amy"], ["Belinda"]], # Charlotte - Poems, Jane Eyre
  188. [["Amy"]], # Anne - Poems
  189. [["Amy"], []], # Emily - Poems, Wuthering Heights
  190. [["Amy", "Belinda"]], # Jane - Sense and Sense
  191. ],
  192. )
  193. with self.assertNumQueries(3):
  194. qs = Author.objects.prefetch_related("books__read_by", "books")
  195. lists = [
  196. [[str(r) for r in b.read_by.all()] for b in a.books.all()] for a in qs
  197. ]
  198. self.assertEqual(
  199. lists,
  200. [
  201. [["Amy"], ["Belinda"]], # Charlotte - Poems, Jane Eyre
  202. [["Amy"]], # Anne - Poems
  203. [["Amy"], []], # Emily - Poems, Wuthering Heights
  204. [["Amy", "Belinda"]], # Jane - Sense and Sense
  205. ],
  206. )
  207. def test_get(self):
  208. """
  209. Objects retrieved with .get() get the prefetch behavior.
  210. """
  211. # Need a double
  212. with self.assertNumQueries(3):
  213. author = Author.objects.prefetch_related("books__read_by").get(
  214. name="Charlotte"
  215. )
  216. lists = [[str(r) for r in b.read_by.all()] for b in author.books.all()]
  217. self.assertEqual(lists, [["Amy"], ["Belinda"]]) # Poems, Jane Eyre
  218. def test_foreign_key_then_m2m(self):
  219. """
  220. A m2m relation can be followed after a relation like ForeignKey that
  221. doesn't have many objects.
  222. """
  223. with self.assertNumQueries(2):
  224. qs = Author.objects.select_related("first_book").prefetch_related(
  225. "first_book__read_by"
  226. )
  227. lists = [[str(r) for r in a.first_book.read_by.all()] for a in qs]
  228. self.assertEqual(lists, [["Amy"], ["Amy"], ["Amy"], ["Amy", "Belinda"]])
  229. def test_reverse_one_to_one_then_m2m(self):
  230. """
  231. A m2m relation can be followed after going through the select_related
  232. reverse of an o2o.
  233. """
  234. qs = Author.objects.prefetch_related("bio__books").select_related("bio")
  235. with self.assertNumQueries(1):
  236. list(qs.all())
  237. Bio.objects.create(author=self.author1)
  238. with self.assertNumQueries(2):
  239. list(qs.all())
  240. def test_attribute_error(self):
  241. qs = Reader.objects.prefetch_related("books_read__xyz")
  242. msg = (
  243. "Cannot find 'xyz' on Book object, 'books_read__xyz' "
  244. "is an invalid parameter to prefetch_related()"
  245. )
  246. with self.assertRaisesMessage(AttributeError, msg) as cm:
  247. list(qs)
  248. self.assertIn("prefetch_related", str(cm.exception))
  249. def test_invalid_final_lookup(self):
  250. qs = Book.objects.prefetch_related("authors__name")
  251. msg = (
  252. "'authors__name' does not resolve to an item that supports "
  253. "prefetching - this is an invalid parameter to prefetch_related()."
  254. )
  255. with self.assertRaisesMessage(ValueError, msg) as cm:
  256. list(qs)
  257. self.assertIn("prefetch_related", str(cm.exception))
  258. self.assertIn("name", str(cm.exception))
  259. def test_prefetch_eq(self):
  260. prefetch_1 = Prefetch("authors", queryset=Author.objects.all())
  261. prefetch_2 = Prefetch("books", queryset=Book.objects.all())
  262. self.assertEqual(prefetch_1, prefetch_1)
  263. self.assertEqual(prefetch_1, mock.ANY)
  264. self.assertNotEqual(prefetch_1, prefetch_2)
  265. def test_forward_m2m_to_attr_conflict(self):
  266. msg = "to_attr=authors conflicts with a field on the Book model."
  267. authors = Author.objects.all()
  268. with self.assertRaisesMessage(ValueError, msg):
  269. list(
  270. Book.objects.prefetch_related(
  271. Prefetch("authors", queryset=authors, to_attr="authors"),
  272. )
  273. )
  274. # Without the ValueError, an author was deleted due to the implicit
  275. # save of the relation assignment.
  276. self.assertEqual(self.book1.authors.count(), 3)
  277. def test_reverse_m2m_to_attr_conflict(self):
  278. msg = "to_attr=books conflicts with a field on the Author model."
  279. poems = Book.objects.filter(title="Poems")
  280. with self.assertRaisesMessage(ValueError, msg):
  281. list(
  282. Author.objects.prefetch_related(
  283. Prefetch("books", queryset=poems, to_attr="books"),
  284. )
  285. )
  286. # Without the ValueError, a book was deleted due to the implicit
  287. # save of reverse relation assignment.
  288. self.assertEqual(self.author1.books.count(), 2)
  289. def test_m2m_then_reverse_fk_object_ids(self):
  290. with CaptureQueriesContext(connection) as queries:
  291. list(Book.objects.prefetch_related("authors__addresses"))
  292. sql = queries[-1]["sql"]
  293. self.assertWhereContains(sql, self.author1.name)
  294. def test_m2m_then_m2m_object_ids(self):
  295. with CaptureQueriesContext(connection) as queries:
  296. list(Book.objects.prefetch_related("authors__favorite_authors"))
  297. sql = queries[-1]["sql"]
  298. self.assertWhereContains(sql, self.author1.name)
  299. def test_m2m_then_reverse_one_to_one_object_ids(self):
  300. with CaptureQueriesContext(connection) as queries:
  301. list(Book.objects.prefetch_related("authors__authorwithage"))
  302. sql = queries[-1]["sql"]
  303. self.assertWhereContains(sql, self.author1.id)
  304. def test_filter_deferred(self):
  305. """
  306. Related filtering of prefetched querysets is deferred on m2m and
  307. reverse m2o relations until necessary.
  308. """
  309. add_q = Query.add_q
  310. for relation in ["authors", "first_time_authors"]:
  311. with self.subTest(relation=relation):
  312. with mock.patch.object(
  313. Query,
  314. "add_q",
  315. autospec=True,
  316. side_effect=lambda self, q: add_q(self, q),
  317. ) as add_q_mock:
  318. list(Book.objects.prefetch_related(relation))
  319. self.assertEqual(add_q_mock.call_count, 1)
  320. def test_named_values_list(self):
  321. qs = Author.objects.prefetch_related("books")
  322. self.assertCountEqual(
  323. [value.name for value in qs.values_list("name", named=True)],
  324. ["Anne", "Charlotte", "Emily", "Jane"],
  325. )
  326. def test_m2m_prefetching_iterator_with_chunks(self):
  327. with self.assertNumQueries(3):
  328. authors = [
  329. b.authors.first()
  330. for b in Book.objects.prefetch_related("authors").iterator(chunk_size=2)
  331. ]
  332. self.assertEqual(
  333. authors,
  334. [self.author1, self.author1, self.author3, self.author4],
  335. )
  336. def test_m2m_prefetching_iterator_without_chunks_error(self):
  337. msg = (
  338. "chunk_size must be provided when using QuerySet.iterator() after "
  339. "prefetch_related()."
  340. )
  341. with self.assertRaisesMessage(ValueError, msg):
  342. Book.objects.prefetch_related("authors").iterator()
  343. class RawQuerySetTests(TestDataMixin, TestCase):
  344. def test_basic(self):
  345. with self.assertNumQueries(2):
  346. books = Book.objects.raw(
  347. "SELECT * FROM prefetch_related_book WHERE id = %s", (self.book1.id,)
  348. ).prefetch_related("authors")
  349. book1 = list(books)[0]
  350. with self.assertNumQueries(0):
  351. self.assertCountEqual(
  352. book1.authors.all(), [self.author1, self.author2, self.author3]
  353. )
  354. def test_prefetch_before_raw(self):
  355. with self.assertNumQueries(2):
  356. books = Book.objects.prefetch_related("authors").raw(
  357. "SELECT * FROM prefetch_related_book WHERE id = %s", (self.book1.id,)
  358. )
  359. book1 = list(books)[0]
  360. with self.assertNumQueries(0):
  361. self.assertCountEqual(
  362. book1.authors.all(), [self.author1, self.author2, self.author3]
  363. )
  364. def test_clear(self):
  365. with self.assertNumQueries(5):
  366. with_prefetch = Author.objects.raw(
  367. "SELECT * FROM prefetch_related_author"
  368. ).prefetch_related("books")
  369. without_prefetch = with_prefetch.prefetch_related(None)
  370. [list(a.books.all()) for a in without_prefetch]
  371. class CustomPrefetchTests(TestCase):
  372. @classmethod
  373. def traverse_qs(cls, obj_iter, path):
  374. """
  375. Helper method that returns a list containing a list of the objects in the
  376. obj_iter. Then for each object in the obj_iter, the path will be
  377. recursively travelled and the found objects are added to the return value.
  378. """
  379. ret_val = []
  380. if hasattr(obj_iter, "all"):
  381. obj_iter = obj_iter.all()
  382. try:
  383. iter(obj_iter)
  384. except TypeError:
  385. obj_iter = [obj_iter]
  386. for obj in obj_iter:
  387. rel_objs = []
  388. for part in path:
  389. if not part:
  390. continue
  391. try:
  392. related = getattr(obj, part[0])
  393. except ObjectDoesNotExist:
  394. continue
  395. if related is not None:
  396. rel_objs.extend(cls.traverse_qs(related, [part[1:]]))
  397. ret_val.append((obj, rel_objs))
  398. return ret_val
  399. @classmethod
  400. def setUpTestData(cls):
  401. cls.person1 = Person.objects.create(name="Joe")
  402. cls.person2 = Person.objects.create(name="Mary")
  403. # Set main_room for each house before creating the next one for
  404. # databases where supports_nullable_unique_constraints is False.
  405. cls.house1 = House.objects.create(
  406. name="House 1", address="123 Main St", owner=cls.person1
  407. )
  408. cls.room1_1 = Room.objects.create(name="Dining room", house=cls.house1)
  409. cls.room1_2 = Room.objects.create(name="Lounge", house=cls.house1)
  410. cls.room1_3 = Room.objects.create(name="Kitchen", house=cls.house1)
  411. cls.house1.main_room = cls.room1_1
  412. cls.house1.save()
  413. cls.person1.houses.add(cls.house1)
  414. cls.house2 = House.objects.create(
  415. name="House 2", address="45 Side St", owner=cls.person1
  416. )
  417. cls.room2_1 = Room.objects.create(name="Dining room", house=cls.house2)
  418. cls.room2_2 = Room.objects.create(name="Lounge", house=cls.house2)
  419. cls.room2_3 = Room.objects.create(name="Kitchen", house=cls.house2)
  420. cls.house2.main_room = cls.room2_1
  421. cls.house2.save()
  422. cls.person1.houses.add(cls.house2)
  423. cls.house3 = House.objects.create(
  424. name="House 3", address="6 Downing St", owner=cls.person2
  425. )
  426. cls.room3_1 = Room.objects.create(name="Dining room", house=cls.house3)
  427. cls.room3_2 = Room.objects.create(name="Lounge", house=cls.house3)
  428. cls.room3_3 = Room.objects.create(name="Kitchen", house=cls.house3)
  429. cls.house3.main_room = cls.room3_1
  430. cls.house3.save()
  431. cls.person2.houses.add(cls.house3)
  432. cls.house4 = House.objects.create(
  433. name="house 4", address="7 Regents St", owner=cls.person2
  434. )
  435. cls.room4_1 = Room.objects.create(name="Dining room", house=cls.house4)
  436. cls.room4_2 = Room.objects.create(name="Lounge", house=cls.house4)
  437. cls.room4_3 = Room.objects.create(name="Kitchen", house=cls.house4)
  438. cls.house4.main_room = cls.room4_1
  439. cls.house4.save()
  440. cls.person2.houses.add(cls.house4)
  441. def test_traverse_qs(self):
  442. qs = Person.objects.prefetch_related("houses")
  443. related_objs_normal = ([list(p.houses.all()) for p in qs],)
  444. related_objs_from_traverse = [
  445. [inner[0] for inner in o[1]] for o in self.traverse_qs(qs, [["houses"]])
  446. ]
  447. self.assertEqual(related_objs_normal, (related_objs_from_traverse,))
  448. def test_ambiguous(self):
  449. # Ambiguous: Lookup was already seen with a different queryset.
  450. msg = (
  451. "'houses' lookup was already seen with a different queryset. You "
  452. "may need to adjust the ordering of your lookups."
  453. )
  454. # lookup.queryset shouldn't be evaluated.
  455. with self.assertNumQueries(3):
  456. with self.assertRaisesMessage(ValueError, msg):
  457. self.traverse_qs(
  458. Person.objects.prefetch_related(
  459. "houses__rooms",
  460. Prefetch("houses", queryset=House.objects.all()),
  461. ),
  462. [["houses", "rooms"]],
  463. )
  464. # Ambiguous: Lookup houses_lst doesn't yet exist when performing
  465. # houses_lst__rooms.
  466. msg = (
  467. "Cannot find 'houses_lst' on Person object, 'houses_lst__rooms' is "
  468. "an invalid parameter to prefetch_related()"
  469. )
  470. with self.assertRaisesMessage(AttributeError, msg):
  471. self.traverse_qs(
  472. Person.objects.prefetch_related(
  473. "houses_lst__rooms",
  474. Prefetch(
  475. "houses", queryset=House.objects.all(), to_attr="houses_lst"
  476. ),
  477. ),
  478. [["houses", "rooms"]],
  479. )
  480. # Not ambiguous.
  481. self.traverse_qs(
  482. Person.objects.prefetch_related("houses__rooms", "houses"),
  483. [["houses", "rooms"]],
  484. )
  485. self.traverse_qs(
  486. Person.objects.prefetch_related(
  487. "houses__rooms",
  488. Prefetch("houses", queryset=House.objects.all(), to_attr="houses_lst"),
  489. ),
  490. [["houses", "rooms"]],
  491. )
  492. def test_m2m(self):
  493. # Control lookups.
  494. with self.assertNumQueries(2):
  495. lst1 = self.traverse_qs(
  496. Person.objects.prefetch_related("houses"), [["houses"]]
  497. )
  498. # Test lookups.
  499. with self.assertNumQueries(2):
  500. lst2 = self.traverse_qs(
  501. Person.objects.prefetch_related(Prefetch("houses")), [["houses"]]
  502. )
  503. self.assertEqual(lst1, lst2)
  504. with self.assertNumQueries(2):
  505. lst2 = self.traverse_qs(
  506. Person.objects.prefetch_related(
  507. Prefetch("houses", to_attr="houses_lst")
  508. ),
  509. [["houses_lst"]],
  510. )
  511. self.assertEqual(lst1, lst2)
  512. def test_reverse_m2m(self):
  513. # Control lookups.
  514. with self.assertNumQueries(2):
  515. lst1 = self.traverse_qs(
  516. House.objects.prefetch_related("occupants"), [["occupants"]]
  517. )
  518. # Test lookups.
  519. with self.assertNumQueries(2):
  520. lst2 = self.traverse_qs(
  521. House.objects.prefetch_related(Prefetch("occupants")), [["occupants"]]
  522. )
  523. self.assertEqual(lst1, lst2)
  524. with self.assertNumQueries(2):
  525. lst2 = self.traverse_qs(
  526. House.objects.prefetch_related(
  527. Prefetch("occupants", to_attr="occupants_lst")
  528. ),
  529. [["occupants_lst"]],
  530. )
  531. self.assertEqual(lst1, lst2)
  532. def test_m2m_through_fk(self):
  533. # Control lookups.
  534. with self.assertNumQueries(3):
  535. lst1 = self.traverse_qs(
  536. Room.objects.prefetch_related("house__occupants"),
  537. [["house", "occupants"]],
  538. )
  539. # Test lookups.
  540. with self.assertNumQueries(3):
  541. lst2 = self.traverse_qs(
  542. Room.objects.prefetch_related(Prefetch("house__occupants")),
  543. [["house", "occupants"]],
  544. )
  545. self.assertEqual(lst1, lst2)
  546. with self.assertNumQueries(3):
  547. lst2 = self.traverse_qs(
  548. Room.objects.prefetch_related(
  549. Prefetch("house__occupants", to_attr="occupants_lst")
  550. ),
  551. [["house", "occupants_lst"]],
  552. )
  553. self.assertEqual(lst1, lst2)
  554. def test_m2m_through_gfk(self):
  555. TaggedItem.objects.create(tag="houses", content_object=self.house1)
  556. TaggedItem.objects.create(tag="houses", content_object=self.house2)
  557. # Control lookups.
  558. with self.assertNumQueries(3):
  559. lst1 = self.traverse_qs(
  560. TaggedItem.objects.filter(tag="houses").prefetch_related(
  561. "content_object__rooms"
  562. ),
  563. [["content_object", "rooms"]],
  564. )
  565. # Test lookups.
  566. with self.assertNumQueries(3):
  567. lst2 = self.traverse_qs(
  568. TaggedItem.objects.prefetch_related(
  569. Prefetch("content_object"),
  570. Prefetch("content_object__rooms", to_attr="rooms_lst"),
  571. ),
  572. [["content_object", "rooms_lst"]],
  573. )
  574. self.assertEqual(lst1, lst2)
  575. def test_o2m_through_m2m(self):
  576. # Control lookups.
  577. with self.assertNumQueries(3):
  578. lst1 = self.traverse_qs(
  579. Person.objects.prefetch_related("houses", "houses__rooms"),
  580. [["houses", "rooms"]],
  581. )
  582. # Test lookups.
  583. with self.assertNumQueries(3):
  584. lst2 = self.traverse_qs(
  585. Person.objects.prefetch_related(Prefetch("houses"), "houses__rooms"),
  586. [["houses", "rooms"]],
  587. )
  588. self.assertEqual(lst1, lst2)
  589. with self.assertNumQueries(3):
  590. lst2 = self.traverse_qs(
  591. Person.objects.prefetch_related(
  592. Prefetch("houses"), Prefetch("houses__rooms")
  593. ),
  594. [["houses", "rooms"]],
  595. )
  596. self.assertEqual(lst1, lst2)
  597. with self.assertNumQueries(3):
  598. lst2 = self.traverse_qs(
  599. Person.objects.prefetch_related(
  600. Prefetch("houses", to_attr="houses_lst"), "houses_lst__rooms"
  601. ),
  602. [["houses_lst", "rooms"]],
  603. )
  604. self.assertEqual(lst1, lst2)
  605. with self.assertNumQueries(3):
  606. lst2 = self.traverse_qs(
  607. Person.objects.prefetch_related(
  608. Prefetch("houses", to_attr="houses_lst"),
  609. Prefetch("houses_lst__rooms", to_attr="rooms_lst"),
  610. ),
  611. [["houses_lst", "rooms_lst"]],
  612. )
  613. self.assertEqual(lst1, lst2)
  614. def test_generic_rel(self):
  615. bookmark = Bookmark.objects.create(url="http://www.djangoproject.com/")
  616. TaggedItem.objects.create(content_object=bookmark, tag="django")
  617. TaggedItem.objects.create(
  618. content_object=bookmark, favorite=bookmark, tag="python"
  619. )
  620. # Control lookups.
  621. with self.assertNumQueries(4):
  622. lst1 = self.traverse_qs(
  623. Bookmark.objects.prefetch_related(
  624. "tags", "tags__content_object", "favorite_tags"
  625. ),
  626. [["tags", "content_object"], ["favorite_tags"]],
  627. )
  628. # Test lookups.
  629. with self.assertNumQueries(4):
  630. lst2 = self.traverse_qs(
  631. Bookmark.objects.prefetch_related(
  632. Prefetch("tags", to_attr="tags_lst"),
  633. Prefetch("tags_lst__content_object"),
  634. Prefetch("favorite_tags"),
  635. ),
  636. [["tags_lst", "content_object"], ["favorite_tags"]],
  637. )
  638. self.assertEqual(lst1, lst2)
  639. def test_traverse_single_item_property(self):
  640. # Control lookups.
  641. with self.assertNumQueries(5):
  642. lst1 = self.traverse_qs(
  643. Person.objects.prefetch_related(
  644. "houses__rooms",
  645. "primary_house__occupants__houses",
  646. ),
  647. [["primary_house", "occupants", "houses"]],
  648. )
  649. # Test lookups.
  650. with self.assertNumQueries(5):
  651. lst2 = self.traverse_qs(
  652. Person.objects.prefetch_related(
  653. "houses__rooms",
  654. Prefetch("primary_house__occupants", to_attr="occupants_lst"),
  655. "primary_house__occupants_lst__houses",
  656. ),
  657. [["primary_house", "occupants_lst", "houses"]],
  658. )
  659. self.assertEqual(lst1, lst2)
  660. def test_traverse_multiple_items_property(self):
  661. # Control lookups.
  662. with self.assertNumQueries(4):
  663. lst1 = self.traverse_qs(
  664. Person.objects.prefetch_related(
  665. "houses",
  666. "all_houses__occupants__houses",
  667. ),
  668. [["all_houses", "occupants", "houses"]],
  669. )
  670. # Test lookups.
  671. with self.assertNumQueries(4):
  672. lst2 = self.traverse_qs(
  673. Person.objects.prefetch_related(
  674. "houses",
  675. Prefetch("all_houses__occupants", to_attr="occupants_lst"),
  676. "all_houses__occupants_lst__houses",
  677. ),
  678. [["all_houses", "occupants_lst", "houses"]],
  679. )
  680. self.assertEqual(lst1, lst2)
  681. def test_custom_qs(self):
  682. # Test basic.
  683. with self.assertNumQueries(2):
  684. lst1 = list(Person.objects.prefetch_related("houses"))
  685. with self.assertNumQueries(2):
  686. lst2 = list(
  687. Person.objects.prefetch_related(
  688. Prefetch(
  689. "houses", queryset=House.objects.all(), to_attr="houses_lst"
  690. )
  691. )
  692. )
  693. self.assertEqual(
  694. self.traverse_qs(lst1, [["houses"]]),
  695. self.traverse_qs(lst2, [["houses_lst"]]),
  696. )
  697. # Test queryset filtering.
  698. with self.assertNumQueries(2):
  699. lst2 = list(
  700. Person.objects.prefetch_related(
  701. Prefetch(
  702. "houses",
  703. queryset=House.objects.filter(
  704. pk__in=[self.house1.pk, self.house3.pk]
  705. ),
  706. to_attr="houses_lst",
  707. )
  708. )
  709. )
  710. self.assertEqual(len(lst2[0].houses_lst), 1)
  711. self.assertEqual(lst2[0].houses_lst[0], self.house1)
  712. self.assertEqual(len(lst2[1].houses_lst), 1)
  713. self.assertEqual(lst2[1].houses_lst[0], self.house3)
  714. # Test flattened.
  715. with self.assertNumQueries(3):
  716. lst1 = list(Person.objects.prefetch_related("houses__rooms"))
  717. with self.assertNumQueries(3):
  718. lst2 = list(
  719. Person.objects.prefetch_related(
  720. Prefetch(
  721. "houses__rooms",
  722. queryset=Room.objects.all(),
  723. to_attr="rooms_lst",
  724. )
  725. )
  726. )
  727. self.assertEqual(
  728. self.traverse_qs(lst1, [["houses", "rooms"]]),
  729. self.traverse_qs(lst2, [["houses", "rooms_lst"]]),
  730. )
  731. # Test inner select_related.
  732. with self.assertNumQueries(3):
  733. lst1 = list(Person.objects.prefetch_related("houses__owner"))
  734. with self.assertNumQueries(2):
  735. lst2 = list(
  736. Person.objects.prefetch_related(
  737. Prefetch("houses", queryset=House.objects.select_related("owner"))
  738. )
  739. )
  740. self.assertEqual(
  741. self.traverse_qs(lst1, [["houses", "owner"]]),
  742. self.traverse_qs(lst2, [["houses", "owner"]]),
  743. )
  744. # Test inner prefetch.
  745. inner_rooms_qs = Room.objects.filter(pk__in=[self.room1_1.pk, self.room1_2.pk])
  746. houses_qs_prf = House.objects.prefetch_related(
  747. Prefetch("rooms", queryset=inner_rooms_qs, to_attr="rooms_lst")
  748. )
  749. with self.assertNumQueries(4):
  750. lst2 = list(
  751. Person.objects.prefetch_related(
  752. Prefetch(
  753. "houses",
  754. queryset=houses_qs_prf.filter(pk=self.house1.pk),
  755. to_attr="houses_lst",
  756. ),
  757. Prefetch("houses_lst__rooms_lst__main_room_of"),
  758. )
  759. )
  760. self.assertEqual(len(lst2[0].houses_lst[0].rooms_lst), 2)
  761. self.assertEqual(lst2[0].houses_lst[0].rooms_lst[0], self.room1_1)
  762. self.assertEqual(lst2[0].houses_lst[0].rooms_lst[1], self.room1_2)
  763. self.assertEqual(lst2[0].houses_lst[0].rooms_lst[0].main_room_of, self.house1)
  764. self.assertEqual(len(lst2[1].houses_lst), 0)
  765. # Test ForwardManyToOneDescriptor.
  766. houses = House.objects.select_related("owner")
  767. with self.assertNumQueries(6):
  768. rooms = Room.objects.prefetch_related("house")
  769. lst1 = self.traverse_qs(rooms, [["house", "owner"]])
  770. with self.assertNumQueries(2):
  771. rooms = Room.objects.prefetch_related(Prefetch("house", queryset=houses))
  772. lst2 = self.traverse_qs(rooms, [["house", "owner"]])
  773. self.assertEqual(lst1, lst2)
  774. with self.assertNumQueries(2):
  775. houses = House.objects.select_related("owner")
  776. rooms = Room.objects.prefetch_related(
  777. Prefetch("house", queryset=houses, to_attr="house_attr")
  778. )
  779. lst2 = self.traverse_qs(rooms, [["house_attr", "owner"]])
  780. self.assertEqual(lst1, lst2)
  781. room = Room.objects.prefetch_related(
  782. Prefetch("house", queryset=houses.filter(address="DoesNotExist"))
  783. ).first()
  784. with self.assertRaises(ObjectDoesNotExist):
  785. getattr(room, "house")
  786. room = Room.objects.prefetch_related(
  787. Prefetch(
  788. "house",
  789. queryset=houses.filter(address="DoesNotExist"),
  790. to_attr="house_attr",
  791. )
  792. ).first()
  793. self.assertIsNone(room.house_attr)
  794. rooms = Room.objects.prefetch_related(
  795. Prefetch("house", queryset=House.objects.only("name"))
  796. )
  797. with self.assertNumQueries(2):
  798. getattr(rooms.first().house, "name")
  799. with self.assertNumQueries(3):
  800. getattr(rooms.first().house, "address")
  801. # Test ReverseOneToOneDescriptor.
  802. houses = House.objects.select_related("owner")
  803. with self.assertNumQueries(6):
  804. rooms = Room.objects.prefetch_related("main_room_of")
  805. lst1 = self.traverse_qs(rooms, [["main_room_of", "owner"]])
  806. with self.assertNumQueries(2):
  807. rooms = Room.objects.prefetch_related(
  808. Prefetch("main_room_of", queryset=houses)
  809. )
  810. lst2 = self.traverse_qs(rooms, [["main_room_of", "owner"]])
  811. self.assertEqual(lst1, lst2)
  812. with self.assertNumQueries(2):
  813. rooms = list(
  814. Room.objects.prefetch_related(
  815. Prefetch(
  816. "main_room_of",
  817. queryset=houses,
  818. to_attr="main_room_of_attr",
  819. )
  820. )
  821. )
  822. lst2 = self.traverse_qs(rooms, [["main_room_of_attr", "owner"]])
  823. self.assertEqual(lst1, lst2)
  824. room = (
  825. Room.objects.filter(main_room_of__isnull=False)
  826. .prefetch_related(
  827. Prefetch("main_room_of", queryset=houses.filter(address="DoesNotExist"))
  828. )
  829. .first()
  830. )
  831. with self.assertRaises(ObjectDoesNotExist):
  832. getattr(room, "main_room_of")
  833. room = (
  834. Room.objects.filter(main_room_of__isnull=False)
  835. .prefetch_related(
  836. Prefetch(
  837. "main_room_of",
  838. queryset=houses.filter(address="DoesNotExist"),
  839. to_attr="main_room_of_attr",
  840. )
  841. )
  842. .first()
  843. )
  844. self.assertIsNone(room.main_room_of_attr)
  845. # The custom queryset filters should be applied to the queryset
  846. # instance returned by the manager.
  847. person = Person.objects.prefetch_related(
  848. Prefetch("houses", queryset=House.objects.filter(name="House 1")),
  849. ).get(pk=self.person1.pk)
  850. self.assertEqual(
  851. list(person.houses.all()),
  852. list(person.houses.all().all()),
  853. )
  854. def test_nested_prefetch_related_are_not_overwritten(self):
  855. # Regression test for #24873
  856. houses_2 = House.objects.prefetch_related(Prefetch("rooms"))
  857. persons = Person.objects.prefetch_related(Prefetch("houses", queryset=houses_2))
  858. houses = House.objects.prefetch_related(Prefetch("occupants", queryset=persons))
  859. list(houses) # queryset must be evaluated once to reproduce the bug.
  860. self.assertEqual(
  861. houses.all()[0].occupants.all()[0].houses.all()[1].rooms.all()[0],
  862. self.room2_1,
  863. )
  864. def test_nested_prefetch_related_with_duplicate_prefetcher(self):
  865. """
  866. Nested prefetches whose name clashes with descriptor names
  867. (Person.houses here) are allowed.
  868. """
  869. occupants = Person.objects.prefetch_related(
  870. Prefetch("houses", to_attr="some_attr_name"),
  871. Prefetch("houses", queryset=House.objects.prefetch_related("main_room")),
  872. )
  873. houses = House.objects.prefetch_related(
  874. Prefetch("occupants", queryset=occupants)
  875. )
  876. with self.assertNumQueries(5):
  877. self.traverse_qs(list(houses), [["occupants", "houses", "main_room"]])
  878. def test_nested_prefetch_related_with_duplicate_prefetch_and_depth(self):
  879. people = Person.objects.prefetch_related(
  880. Prefetch(
  881. "houses__main_room",
  882. queryset=Room.objects.filter(name="Dining room"),
  883. to_attr="dining_room",
  884. ),
  885. "houses__main_room",
  886. )
  887. with self.assertNumQueries(4):
  888. main_room = people[0].houses.all()[0]
  889. people = Person.objects.prefetch_related(
  890. "houses__main_room",
  891. Prefetch(
  892. "houses__main_room",
  893. queryset=Room.objects.filter(name="Dining room"),
  894. to_attr="dining_room",
  895. ),
  896. )
  897. with self.assertNumQueries(4):
  898. main_room = people[0].houses.all()[0]
  899. self.assertEqual(main_room.main_room, self.room1_1)
  900. def test_values_queryset(self):
  901. msg = "Prefetch querysets cannot use raw(), values(), and values_list()."
  902. with self.assertRaisesMessage(ValueError, msg):
  903. Prefetch("houses", House.objects.values("pk"))
  904. with self.assertRaisesMessage(ValueError, msg):
  905. Prefetch("houses", House.objects.values_list("pk"))
  906. # That error doesn't affect managers with custom ModelIterable subclasses
  907. self.assertIs(
  908. Teacher.objects_custom.all()._iterable_class, ModelIterableSubclass
  909. )
  910. Prefetch("teachers", Teacher.objects_custom.all())
  911. def test_raw_queryset(self):
  912. msg = "Prefetch querysets cannot use raw(), values(), and values_list()."
  913. with self.assertRaisesMessage(ValueError, msg):
  914. Prefetch("houses", House.objects.raw("select pk from house"))
  915. def test_to_attr_doesnt_cache_through_attr_as_list(self):
  916. house = House.objects.prefetch_related(
  917. Prefetch("rooms", queryset=Room.objects.all(), to_attr="to_rooms"),
  918. ).get(pk=self.house3.pk)
  919. self.assertIsInstance(house.rooms.all(), QuerySet)
  920. def test_to_attr_cached_property(self):
  921. persons = Person.objects.prefetch_related(
  922. Prefetch("houses", House.objects.all(), to_attr="cached_all_houses"),
  923. )
  924. for person in persons:
  925. # To bypass caching at the related descriptor level, don't use
  926. # person.houses.all() here.
  927. all_houses = list(House.objects.filter(occupants=person))
  928. with self.assertNumQueries(0):
  929. self.assertEqual(person.cached_all_houses, all_houses)
  930. def test_filter_deferred(self):
  931. """
  932. Related filtering of prefetched querysets is deferred until necessary.
  933. """
  934. add_q = Query.add_q
  935. with mock.patch.object(
  936. Query,
  937. "add_q",
  938. autospec=True,
  939. side_effect=lambda self, q: add_q(self, q),
  940. ) as add_q_mock:
  941. list(
  942. House.objects.prefetch_related(
  943. Prefetch("occupants", queryset=Person.objects.all())
  944. )
  945. )
  946. self.assertEqual(add_q_mock.call_count, 1)
  947. class DefaultManagerTests(TestCase):
  948. @classmethod
  949. def setUpTestData(cls):
  950. cls.qual1 = Qualification.objects.create(name="BA")
  951. cls.qual2 = Qualification.objects.create(name="BSci")
  952. cls.qual3 = Qualification.objects.create(name="MA")
  953. cls.qual4 = Qualification.objects.create(name="PhD")
  954. cls.teacher1 = Teacher.objects.create(name="Mr Cleese")
  955. cls.teacher2 = Teacher.objects.create(name="Mr Idle")
  956. cls.teacher3 = Teacher.objects.create(name="Mr Chapman")
  957. cls.teacher1.qualifications.add(cls.qual1, cls.qual2, cls.qual3, cls.qual4)
  958. cls.teacher2.qualifications.add(cls.qual1)
  959. cls.teacher3.qualifications.add(cls.qual2)
  960. cls.dept1 = Department.objects.create(name="English")
  961. cls.dept2 = Department.objects.create(name="Physics")
  962. cls.dept1.teachers.add(cls.teacher1, cls.teacher2)
  963. cls.dept2.teachers.add(cls.teacher1, cls.teacher3)
  964. def test_m2m_then_m2m(self):
  965. with self.assertNumQueries(3):
  966. # When we prefetch the teachers, and force the query, we don't want
  967. # the default manager on teachers to immediately get all the related
  968. # qualifications, since this will do one query per teacher.
  969. qs = Department.objects.prefetch_related("teachers")
  970. depts = "".join(
  971. "%s department: %s\n"
  972. % (dept.name, ", ".join(str(t) for t in dept.teachers.all()))
  973. for dept in qs
  974. )
  975. self.assertEqual(
  976. depts,
  977. "English department: Mr Cleese (BA, BSci, MA, PhD), Mr Idle (BA)\n"
  978. "Physics department: Mr Cleese (BA, BSci, MA, PhD), Mr Chapman "
  979. "(BSci)\n",
  980. )
  981. class GenericRelationTests(TestCase):
  982. @classmethod
  983. def setUpTestData(cls):
  984. book1 = Book.objects.create(title="Winnie the Pooh")
  985. book2 = Book.objects.create(title="Do you like green eggs and spam?")
  986. book3 = Book.objects.create(title="Three Men In A Boat")
  987. reader1 = Reader.objects.create(name="me")
  988. reader2 = Reader.objects.create(name="you")
  989. reader3 = Reader.objects.create(name="someone")
  990. book1.read_by.add(reader1, reader2)
  991. book2.read_by.add(reader2)
  992. book3.read_by.add(reader3)
  993. cls.book1, cls.book2, cls.book3 = book1, book2, book3
  994. cls.reader1, cls.reader2, cls.reader3 = reader1, reader2, reader3
  995. def test_prefetch_GFK(self):
  996. TaggedItem.objects.create(tag="awesome", content_object=self.book1)
  997. TaggedItem.objects.create(tag="great", content_object=self.reader1)
  998. TaggedItem.objects.create(tag="outstanding", content_object=self.book2)
  999. TaggedItem.objects.create(tag="amazing", content_object=self.reader3)
  1000. # 1 for TaggedItem table, 1 for Book table, 1 for Reader table
  1001. with self.assertNumQueries(3):
  1002. qs = TaggedItem.objects.prefetch_related("content_object")
  1003. list(qs)
  1004. def test_prefetch_GFK_nonint_pk(self):
  1005. Comment.objects.create(comment="awesome", content_object=self.book1)
  1006. # 1 for Comment table, 1 for Book table
  1007. with self.assertNumQueries(2):
  1008. qs = Comment.objects.prefetch_related("content_object")
  1009. [c.content_object for c in qs]
  1010. def test_prefetch_GFK_uuid_pk(self):
  1011. article = Article.objects.create(name="Django")
  1012. Comment.objects.create(comment="awesome", content_object_uuid=article)
  1013. qs = Comment.objects.prefetch_related("content_object_uuid")
  1014. self.assertEqual([c.content_object_uuid for c in qs], [article])
  1015. def test_prefetch_GFK_fk_pk(self):
  1016. book = Book.objects.create(title="Poems")
  1017. book_with_year = BookWithYear.objects.create(book=book, published_year=2019)
  1018. Comment.objects.create(comment="awesome", content_object=book_with_year)
  1019. qs = Comment.objects.prefetch_related("content_object")
  1020. self.assertEqual([c.content_object for c in qs], [book_with_year])
  1021. def test_traverse_GFK(self):
  1022. """
  1023. A 'content_object' can be traversed with prefetch_related() and
  1024. get to related objects on the other side (assuming it is suitably
  1025. filtered)
  1026. """
  1027. TaggedItem.objects.create(tag="awesome", content_object=self.book1)
  1028. TaggedItem.objects.create(tag="awesome", content_object=self.book2)
  1029. TaggedItem.objects.create(tag="awesome", content_object=self.book3)
  1030. TaggedItem.objects.create(tag="awesome", content_object=self.reader1)
  1031. TaggedItem.objects.create(tag="awesome", content_object=self.reader2)
  1032. ct = ContentType.objects.get_for_model(Book)
  1033. # We get 3 queries - 1 for main query, 1 for content_objects since they
  1034. # all use the same table, and 1 for the 'read_by' relation.
  1035. with self.assertNumQueries(3):
  1036. # If we limit to books, we know that they will have 'read_by'
  1037. # attributes, so the following makes sense:
  1038. qs = TaggedItem.objects.filter(
  1039. content_type=ct, tag="awesome"
  1040. ).prefetch_related("content_object__read_by")
  1041. readers_of_awesome_books = {
  1042. r.name for tag in qs for r in tag.content_object.read_by.all()
  1043. }
  1044. self.assertEqual(readers_of_awesome_books, {"me", "you", "someone"})
  1045. def test_nullable_GFK(self):
  1046. TaggedItem.objects.create(
  1047. tag="awesome", content_object=self.book1, created_by=self.reader1
  1048. )
  1049. TaggedItem.objects.create(tag="great", content_object=self.book2)
  1050. TaggedItem.objects.create(tag="rubbish", content_object=self.book3)
  1051. with self.assertNumQueries(2):
  1052. result = [
  1053. t.created_by for t in TaggedItem.objects.prefetch_related("created_by")
  1054. ]
  1055. self.assertEqual(result, [t.created_by for t in TaggedItem.objects.all()])
  1056. def test_generic_relation(self):
  1057. bookmark = Bookmark.objects.create(url="http://www.djangoproject.com/")
  1058. TaggedItem.objects.create(content_object=bookmark, tag="django")
  1059. TaggedItem.objects.create(content_object=bookmark, tag="python")
  1060. with self.assertNumQueries(2):
  1061. tags = [
  1062. t.tag
  1063. for b in Bookmark.objects.prefetch_related("tags")
  1064. for t in b.tags.all()
  1065. ]
  1066. self.assertEqual(sorted(tags), ["django", "python"])
  1067. def test_charfield_GFK(self):
  1068. b = Bookmark.objects.create(url="http://www.djangoproject.com/")
  1069. TaggedItem.objects.create(content_object=b, tag="django")
  1070. TaggedItem.objects.create(content_object=b, favorite=b, tag="python")
  1071. with self.assertNumQueries(3):
  1072. bookmark = Bookmark.objects.filter(pk=b.pk).prefetch_related(
  1073. "tags", "favorite_tags"
  1074. )[0]
  1075. self.assertEqual(
  1076. sorted(i.tag for i in bookmark.tags.all()), ["django", "python"]
  1077. )
  1078. self.assertEqual([i.tag for i in bookmark.favorite_tags.all()], ["python"])
  1079. def test_custom_queryset(self):
  1080. bookmark = Bookmark.objects.create(url="http://www.djangoproject.com/")
  1081. django_tag = TaggedItem.objects.create(content_object=bookmark, tag="django")
  1082. TaggedItem.objects.create(content_object=bookmark, tag="python")
  1083. with self.assertNumQueries(2):
  1084. bookmark = Bookmark.objects.prefetch_related(
  1085. Prefetch("tags", TaggedItem.objects.filter(tag="django")),
  1086. ).get()
  1087. with self.assertNumQueries(0):
  1088. self.assertEqual(list(bookmark.tags.all()), [django_tag])
  1089. # The custom queryset filters should be applied to the queryset
  1090. # instance returned by the manager.
  1091. self.assertEqual(list(bookmark.tags.all()), list(bookmark.tags.all().all()))
  1092. def test_deleted_GFK(self):
  1093. TaggedItem.objects.create(tag="awesome", content_object=self.book1)
  1094. TaggedItem.objects.create(tag="awesome", content_object=self.book2)
  1095. ct = ContentType.objects.get_for_model(Book)
  1096. book1_pk = self.book1.pk
  1097. self.book1.delete()
  1098. with self.assertNumQueries(2):
  1099. qs = TaggedItem.objects.filter(tag="awesome").prefetch_related(
  1100. "content_object"
  1101. )
  1102. result = [
  1103. (tag.object_id, tag.content_type_id, tag.content_object) for tag in qs
  1104. ]
  1105. self.assertEqual(
  1106. result,
  1107. [
  1108. (book1_pk, ct.pk, None),
  1109. (self.book2.pk, ct.pk, self.book2),
  1110. ],
  1111. )
  1112. def test_reverse_generic_relation(self):
  1113. # Create two distinct bookmarks to ensure the bookmark and
  1114. # tagged item models primary are offset.
  1115. first_bookmark = Bookmark.objects.create()
  1116. second_bookmark = Bookmark.objects.create()
  1117. TaggedItem.objects.create(
  1118. content_object=first_bookmark, favorite=second_bookmark
  1119. )
  1120. with self.assertNumQueries(2):
  1121. obj = TaggedItem.objects.prefetch_related("favorite_bookmarks").get()
  1122. with self.assertNumQueries(0):
  1123. prefetched_bookmarks = obj.favorite_bookmarks.all()
  1124. self.assertQuerySetEqual(prefetched_bookmarks, [second_bookmark])
  1125. class MultiTableInheritanceTest(TestCase):
  1126. @classmethod
  1127. def setUpTestData(cls):
  1128. cls.book1 = BookWithYear.objects.create(title="Poems", published_year=2010)
  1129. cls.book2 = BookWithYear.objects.create(title="More poems", published_year=2011)
  1130. cls.author1 = AuthorWithAge.objects.create(
  1131. name="Jane", first_book=cls.book1, age=50
  1132. )
  1133. cls.author2 = AuthorWithAge.objects.create(
  1134. name="Tom", first_book=cls.book1, age=49
  1135. )
  1136. cls.author3 = AuthorWithAge.objects.create(
  1137. name="Robert", first_book=cls.book2, age=48
  1138. )
  1139. cls.author_address = AuthorAddress.objects.create(
  1140. author=cls.author1, address="SomeStreet 1"
  1141. )
  1142. cls.book2.aged_authors.add(cls.author2, cls.author3)
  1143. cls.br1 = BookReview.objects.create(book=cls.book1, notes="review book1")
  1144. cls.br2 = BookReview.objects.create(book=cls.book2, notes="review book2")
  1145. def test_foreignkey(self):
  1146. with self.assertNumQueries(2):
  1147. qs = AuthorWithAge.objects.prefetch_related("addresses")
  1148. addresses = [
  1149. [str(address) for address in obj.addresses.all()] for obj in qs
  1150. ]
  1151. self.assertEqual(addresses, [[str(self.author_address)], [], []])
  1152. def test_foreignkey_to_inherited(self):
  1153. with self.assertNumQueries(2):
  1154. qs = BookReview.objects.prefetch_related("book")
  1155. titles = [obj.book.title for obj in qs]
  1156. self.assertCountEqual(titles, ["Poems", "More poems"])
  1157. def test_m2m_to_inheriting_model(self):
  1158. qs = AuthorWithAge.objects.prefetch_related("books_with_year")
  1159. with self.assertNumQueries(2):
  1160. lst = [
  1161. [str(book) for book in author.books_with_year.all()] for author in qs
  1162. ]
  1163. qs = AuthorWithAge.objects.all()
  1164. lst2 = [[str(book) for book in author.books_with_year.all()] for author in qs]
  1165. self.assertEqual(lst, lst2)
  1166. qs = BookWithYear.objects.prefetch_related("aged_authors")
  1167. with self.assertNumQueries(2):
  1168. lst = [[str(author) for author in book.aged_authors.all()] for book in qs]
  1169. qs = BookWithYear.objects.all()
  1170. lst2 = [[str(author) for author in book.aged_authors.all()] for book in qs]
  1171. self.assertEqual(lst, lst2)
  1172. def test_parent_link_prefetch(self):
  1173. with self.assertNumQueries(2):
  1174. [a.author for a in AuthorWithAge.objects.prefetch_related("author")]
  1175. @override_settings(DEBUG=True)
  1176. def test_child_link_prefetch(self):
  1177. with self.assertNumQueries(2):
  1178. authors = [
  1179. a.authorwithage
  1180. for a in Author.objects.prefetch_related("authorwithage")
  1181. ]
  1182. # Regression for #18090: the prefetching query must include an IN clause.
  1183. # Note that on Oracle the table name is upper case in the generated SQL,
  1184. # thus the .lower() call.
  1185. self.assertIn("authorwithage", connection.queries[-1]["sql"].lower())
  1186. self.assertIn(" IN ", connection.queries[-1]["sql"])
  1187. self.assertEqual(authors, [a.authorwithage for a in Author.objects.all()])
  1188. class ForeignKeyToFieldTest(TestCase):
  1189. @classmethod
  1190. def setUpTestData(cls):
  1191. cls.book = Book.objects.create(title="Poems")
  1192. cls.author1 = Author.objects.create(name="Jane", first_book=cls.book)
  1193. cls.author2 = Author.objects.create(name="Tom", first_book=cls.book)
  1194. cls.author3 = Author.objects.create(name="Robert", first_book=cls.book)
  1195. cls.author_address = AuthorAddress.objects.create(
  1196. author=cls.author1, address="SomeStreet 1"
  1197. )
  1198. FavoriteAuthors.objects.create(author=cls.author1, likes_author=cls.author2)
  1199. FavoriteAuthors.objects.create(author=cls.author2, likes_author=cls.author3)
  1200. FavoriteAuthors.objects.create(author=cls.author3, likes_author=cls.author1)
  1201. def test_foreignkey(self):
  1202. with self.assertNumQueries(2):
  1203. qs = Author.objects.prefetch_related("addresses")
  1204. addresses = [
  1205. [str(address) for address in obj.addresses.all()] for obj in qs
  1206. ]
  1207. self.assertEqual(addresses, [[str(self.author_address)], [], []])
  1208. def test_m2m(self):
  1209. with self.assertNumQueries(3):
  1210. qs = Author.objects.prefetch_related("favorite_authors", "favors_me")
  1211. favorites = [
  1212. (
  1213. [str(i_like) for i_like in author.favorite_authors.all()],
  1214. [str(likes_me) for likes_me in author.favors_me.all()],
  1215. )
  1216. for author in qs
  1217. ]
  1218. self.assertEqual(
  1219. favorites,
  1220. [
  1221. ([str(self.author2)], [str(self.author3)]),
  1222. ([str(self.author3)], [str(self.author1)]),
  1223. ([str(self.author1)], [str(self.author2)]),
  1224. ],
  1225. )
  1226. class LookupOrderingTest(TestCase):
  1227. """
  1228. Test cases that demonstrate that ordering of lookups is important, and
  1229. ensure it is preserved.
  1230. """
  1231. @classmethod
  1232. def setUpTestData(cls):
  1233. person1 = Person.objects.create(name="Joe")
  1234. person2 = Person.objects.create(name="Mary")
  1235. # Set main_room for each house before creating the next one for
  1236. # databases where supports_nullable_unique_constraints is False.
  1237. house1 = House.objects.create(address="123 Main St")
  1238. room1_1 = Room.objects.create(name="Dining room", house=house1)
  1239. Room.objects.create(name="Lounge", house=house1)
  1240. Room.objects.create(name="Kitchen", house=house1)
  1241. house1.main_room = room1_1
  1242. house1.save()
  1243. person1.houses.add(house1)
  1244. house2 = House.objects.create(address="45 Side St")
  1245. room2_1 = Room.objects.create(name="Dining room", house=house2)
  1246. Room.objects.create(name="Lounge", house=house2)
  1247. house2.main_room = room2_1
  1248. house2.save()
  1249. person1.houses.add(house2)
  1250. house3 = House.objects.create(address="6 Downing St")
  1251. room3_1 = Room.objects.create(name="Dining room", house=house3)
  1252. Room.objects.create(name="Lounge", house=house3)
  1253. Room.objects.create(name="Kitchen", house=house3)
  1254. house3.main_room = room3_1
  1255. house3.save()
  1256. person2.houses.add(house3)
  1257. house4 = House.objects.create(address="7 Regents St")
  1258. room4_1 = Room.objects.create(name="Dining room", house=house4)
  1259. Room.objects.create(name="Lounge", house=house4)
  1260. house4.main_room = room4_1
  1261. house4.save()
  1262. person2.houses.add(house4)
  1263. def test_order(self):
  1264. with self.assertNumQueries(4):
  1265. # The following two queries must be done in the same order as written,
  1266. # otherwise 'primary_house' will cause non-prefetched lookups
  1267. qs = Person.objects.prefetch_related(
  1268. "houses__rooms", "primary_house__occupants"
  1269. )
  1270. [list(p.primary_house.occupants.all()) for p in qs]
  1271. class NullableTest(TestCase):
  1272. @classmethod
  1273. def setUpTestData(cls):
  1274. boss = Employee.objects.create(name="Peter")
  1275. Employee.objects.create(name="Joe", boss=boss)
  1276. Employee.objects.create(name="Angela", boss=boss)
  1277. def test_traverse_nullable(self):
  1278. # Because we use select_related() for 'boss', it doesn't need to be
  1279. # prefetched, but we can still traverse it although it contains some nulls
  1280. with self.assertNumQueries(2):
  1281. qs = Employee.objects.select_related("boss").prefetch_related("boss__serfs")
  1282. co_serfs = [
  1283. list(e.boss.serfs.all()) if e.boss is not None else [] for e in qs
  1284. ]
  1285. qs2 = Employee.objects.select_related("boss")
  1286. co_serfs2 = [
  1287. list(e.boss.serfs.all()) if e.boss is not None else [] for e in qs2
  1288. ]
  1289. self.assertEqual(co_serfs, co_serfs2)
  1290. def test_prefetch_nullable(self):
  1291. # One for main employee, one for boss, one for serfs
  1292. with self.assertNumQueries(3):
  1293. qs = Employee.objects.prefetch_related("boss__serfs")
  1294. co_serfs = [
  1295. list(e.boss.serfs.all()) if e.boss is not None else [] for e in qs
  1296. ]
  1297. qs2 = Employee.objects.all()
  1298. co_serfs2 = [
  1299. list(e.boss.serfs.all()) if e.boss is not None else [] for e in qs2
  1300. ]
  1301. self.assertEqual(co_serfs, co_serfs2)
  1302. def test_in_bulk(self):
  1303. """
  1304. In-bulk does correctly prefetch objects by not using .iterator()
  1305. directly.
  1306. """
  1307. boss1 = Employee.objects.create(name="Peter")
  1308. boss2 = Employee.objects.create(name="Jack")
  1309. with self.assertNumQueries(2):
  1310. # Prefetch is done and it does not cause any errors.
  1311. bulk = Employee.objects.prefetch_related("serfs").in_bulk(
  1312. [boss1.pk, boss2.pk]
  1313. )
  1314. for b in bulk.values():
  1315. list(b.serfs.all())
  1316. class MultiDbTests(TestCase):
  1317. databases = {"default", "other"}
  1318. def test_using_is_honored_m2m(self):
  1319. B = Book.objects.using("other")
  1320. A = Author.objects.using("other")
  1321. book1 = B.create(title="Poems")
  1322. book2 = B.create(title="Jane Eyre")
  1323. book3 = B.create(title="Wuthering Heights")
  1324. book4 = B.create(title="Sense and Sensibility")
  1325. author1 = A.create(name="Charlotte", first_book=book1)
  1326. author2 = A.create(name="Anne", first_book=book1)
  1327. author3 = A.create(name="Emily", first_book=book1)
  1328. author4 = A.create(name="Jane", first_book=book4)
  1329. book1.authors.add(author1, author2, author3)
  1330. book2.authors.add(author1)
  1331. book3.authors.add(author3)
  1332. book4.authors.add(author4)
  1333. # Forward
  1334. qs1 = B.prefetch_related("authors")
  1335. with self.assertNumQueries(2, using="other"):
  1336. books = "".join(
  1337. "%s (%s)\n"
  1338. % (book.title, ", ".join(a.name for a in book.authors.all()))
  1339. for book in qs1
  1340. )
  1341. self.assertEqual(
  1342. books,
  1343. "Poems (Charlotte, Anne, Emily)\n"
  1344. "Jane Eyre (Charlotte)\n"
  1345. "Wuthering Heights (Emily)\n"
  1346. "Sense and Sensibility (Jane)\n",
  1347. )
  1348. # Reverse
  1349. qs2 = A.prefetch_related("books")
  1350. with self.assertNumQueries(2, using="other"):
  1351. authors = "".join(
  1352. "%s: %s\n"
  1353. % (author.name, ", ".join(b.title for b in author.books.all()))
  1354. for author in qs2
  1355. )
  1356. self.assertEqual(
  1357. authors,
  1358. "Charlotte: Poems, Jane Eyre\n"
  1359. "Anne: Poems\n"
  1360. "Emily: Poems, Wuthering Heights\n"
  1361. "Jane: Sense and Sensibility\n",
  1362. )
  1363. def test_using_is_honored_fkey(self):
  1364. B = Book.objects.using("other")
  1365. A = Author.objects.using("other")
  1366. book1 = B.create(title="Poems")
  1367. book2 = B.create(title="Sense and Sensibility")
  1368. A.create(name="Charlotte Bronte", first_book=book1)
  1369. A.create(name="Jane Austen", first_book=book2)
  1370. # Forward
  1371. with self.assertNumQueries(2, using="other"):
  1372. books = ", ".join(
  1373. a.first_book.title for a in A.prefetch_related("first_book")
  1374. )
  1375. self.assertEqual("Poems, Sense and Sensibility", books)
  1376. # Reverse
  1377. with self.assertNumQueries(2, using="other"):
  1378. books = "".join(
  1379. "%s (%s)\n"
  1380. % (b.title, ", ".join(a.name for a in b.first_time_authors.all()))
  1381. for b in B.prefetch_related("first_time_authors")
  1382. )
  1383. self.assertEqual(
  1384. books,
  1385. "Poems (Charlotte Bronte)\nSense and Sensibility (Jane Austen)\n",
  1386. )
  1387. def test_using_is_honored_inheritance(self):
  1388. B = BookWithYear.objects.using("other")
  1389. A = AuthorWithAge.objects.using("other")
  1390. book1 = B.create(title="Poems", published_year=2010)
  1391. B.create(title="More poems", published_year=2011)
  1392. A.create(name="Jane", first_book=book1, age=50)
  1393. A.create(name="Tom", first_book=book1, age=49)
  1394. # parent link
  1395. with self.assertNumQueries(2, using="other"):
  1396. authors = ", ".join(a.author.name for a in A.prefetch_related("author"))
  1397. self.assertEqual(authors, "Jane, Tom")
  1398. # child link
  1399. with self.assertNumQueries(2, using="other"):
  1400. ages = ", ".join(
  1401. str(a.authorwithage.age) for a in A.prefetch_related("authorwithage")
  1402. )
  1403. self.assertEqual(ages, "50, 49")
  1404. def test_using_is_honored_custom_qs(self):
  1405. B = Book.objects.using("other")
  1406. A = Author.objects.using("other")
  1407. book1 = B.create(title="Poems")
  1408. book2 = B.create(title="Sense and Sensibility")
  1409. A.create(name="Charlotte Bronte", first_book=book1)
  1410. A.create(name="Jane Austen", first_book=book2)
  1411. # Implicit hinting
  1412. with self.assertNumQueries(2, using="other"):
  1413. prefetch = Prefetch("first_time_authors", queryset=Author.objects.all())
  1414. books = "".join(
  1415. "%s (%s)\n"
  1416. % (b.title, ", ".join(a.name for a in b.first_time_authors.all()))
  1417. for b in B.prefetch_related(prefetch)
  1418. )
  1419. self.assertEqual(
  1420. books,
  1421. "Poems (Charlotte Bronte)\nSense and Sensibility (Jane Austen)\n",
  1422. )
  1423. # Explicit using on the same db.
  1424. with self.assertNumQueries(2, using="other"):
  1425. prefetch = Prefetch(
  1426. "first_time_authors", queryset=Author.objects.using("other")
  1427. )
  1428. books = "".join(
  1429. "%s (%s)\n"
  1430. % (b.title, ", ".join(a.name for a in b.first_time_authors.all()))
  1431. for b in B.prefetch_related(prefetch)
  1432. )
  1433. self.assertEqual(
  1434. books,
  1435. "Poems (Charlotte Bronte)\nSense and Sensibility (Jane Austen)\n",
  1436. )
  1437. # Explicit using on a different db.
  1438. with (
  1439. self.assertNumQueries(1, using="default"),
  1440. self.assertNumQueries(1, using="other"),
  1441. ):
  1442. prefetch = Prefetch(
  1443. "first_time_authors", queryset=Author.objects.using("default")
  1444. )
  1445. books = "".join(
  1446. "%s (%s)\n"
  1447. % (b.title, ", ".join(a.name for a in b.first_time_authors.all()))
  1448. for b in B.prefetch_related(prefetch)
  1449. )
  1450. self.assertEqual(books, "Poems ()\n" "Sense and Sensibility ()\n")
  1451. class Ticket19607Tests(TestCase):
  1452. @classmethod
  1453. def setUpTestData(cls):
  1454. LessonEntry.objects.bulk_create(
  1455. LessonEntry(id=id_, name1=name1, name2=name2)
  1456. for id_, name1, name2 in [
  1457. (1, "einfach", "simple"),
  1458. (2, "schwierig", "difficult"),
  1459. ]
  1460. )
  1461. WordEntry.objects.bulk_create(
  1462. WordEntry(id=id_, lesson_entry_id=lesson_entry_id, name=name)
  1463. for id_, lesson_entry_id, name in [
  1464. (1, 1, "einfach"),
  1465. (2, 1, "simple"),
  1466. (3, 2, "schwierig"),
  1467. (4, 2, "difficult"),
  1468. ]
  1469. )
  1470. def test_bug(self):
  1471. list(
  1472. WordEntry.objects.prefetch_related(
  1473. "lesson_entry", "lesson_entry__wordentry_set"
  1474. )
  1475. )
  1476. class Ticket21410Tests(TestCase):
  1477. @classmethod
  1478. def setUpTestData(cls):
  1479. book1 = Book.objects.create(title="Poems")
  1480. book2 = Book.objects.create(title="Jane Eyre")
  1481. book3 = Book.objects.create(title="Wuthering Heights")
  1482. book4 = Book.objects.create(title="Sense and Sensibility")
  1483. author1 = Author2.objects.create(name="Charlotte", first_book=book1)
  1484. author2 = Author2.objects.create(name="Anne", first_book=book1)
  1485. author3 = Author2.objects.create(name="Emily", first_book=book1)
  1486. author4 = Author2.objects.create(name="Jane", first_book=book4)
  1487. author1.favorite_books.add(book1, book2, book3)
  1488. author2.favorite_books.add(book1)
  1489. author3.favorite_books.add(book2)
  1490. author4.favorite_books.add(book3)
  1491. def test_bug(self):
  1492. list(Author2.objects.prefetch_related("first_book", "favorite_books"))
  1493. class Ticket21760Tests(TestCase):
  1494. @classmethod
  1495. def setUpTestData(cls):
  1496. cls.rooms = []
  1497. for _ in range(3):
  1498. house = House.objects.create()
  1499. for _ in range(3):
  1500. cls.rooms.append(Room.objects.create(house=house))
  1501. # Set main_room for each house before creating the next one for
  1502. # databases where supports_nullable_unique_constraints is False.
  1503. house.main_room = cls.rooms[-3]
  1504. house.save()
  1505. def test_bug(self):
  1506. prefetcher = get_prefetcher(self.rooms[0], "house", "house")[0]
  1507. queryset = prefetcher.get_prefetch_querysets(list(Room.objects.all()))[0]
  1508. self.assertNotIn(" JOIN ", str(queryset.query))
  1509. class DirectPrefetchedObjectCacheReuseTests(TestCase):
  1510. """
  1511. prefetch_related() reuses objects fetched in _prefetched_objects_cache.
  1512. When objects are prefetched and not stored as an instance attribute (often
  1513. intermediary relationships), they are saved to the
  1514. _prefetched_objects_cache attribute. prefetch_related() takes
  1515. _prefetched_objects_cache into account when determining whether an object
  1516. has been fetched[1] and retrieves results from it when it is populated [2].
  1517. [1]: #25546 (duplicate queries on nested Prefetch)
  1518. [2]: #27554 (queryset evaluation fails with a mix of nested and flattened
  1519. prefetches)
  1520. """
  1521. @classmethod
  1522. def setUpTestData(cls):
  1523. cls.book1, cls.book2 = [
  1524. Book.objects.create(title="book1"),
  1525. Book.objects.create(title="book2"),
  1526. ]
  1527. cls.author11, cls.author12, cls.author21 = [
  1528. Author.objects.create(first_book=cls.book1, name="Author11"),
  1529. Author.objects.create(first_book=cls.book1, name="Author12"),
  1530. Author.objects.create(first_book=cls.book2, name="Author21"),
  1531. ]
  1532. cls.author1_address1, cls.author1_address2, cls.author2_address1 = [
  1533. AuthorAddress.objects.create(author=cls.author11, address="Happy place"),
  1534. AuthorAddress.objects.create(author=cls.author12, address="Haunted house"),
  1535. AuthorAddress.objects.create(author=cls.author21, address="Happy place"),
  1536. ]
  1537. cls.bookwithyear1 = BookWithYear.objects.create(
  1538. title="Poems", published_year=2010
  1539. )
  1540. cls.bookreview1 = BookReview.objects.create(book=cls.bookwithyear1)
  1541. def test_detect_is_fetched(self):
  1542. """
  1543. Nested prefetch_related() shouldn't trigger duplicate queries for the same
  1544. lookup.
  1545. """
  1546. with self.assertNumQueries(3):
  1547. books = Book.objects.filter(title__in=["book1", "book2"]).prefetch_related(
  1548. Prefetch(
  1549. "first_time_authors",
  1550. Author.objects.prefetch_related(
  1551. Prefetch(
  1552. "addresses",
  1553. AuthorAddress.objects.filter(address="Happy place"),
  1554. )
  1555. ),
  1556. ),
  1557. )
  1558. book1, book2 = list(books)
  1559. with self.assertNumQueries(0):
  1560. self.assertSequenceEqual(
  1561. book1.first_time_authors.all(), [self.author11, self.author12]
  1562. )
  1563. self.assertSequenceEqual(book2.first_time_authors.all(), [self.author21])
  1564. self.assertSequenceEqual(
  1565. book1.first_time_authors.all()[0].addresses.all(),
  1566. [self.author1_address1],
  1567. )
  1568. self.assertSequenceEqual(
  1569. book1.first_time_authors.all()[1].addresses.all(), []
  1570. )
  1571. self.assertSequenceEqual(
  1572. book2.first_time_authors.all()[0].addresses.all(),
  1573. [self.author2_address1],
  1574. )
  1575. self.assertEqual(
  1576. list(book1.first_time_authors.all()),
  1577. list(book1.first_time_authors.all().all()),
  1578. )
  1579. self.assertEqual(
  1580. list(book2.first_time_authors.all()),
  1581. list(book2.first_time_authors.all().all()),
  1582. )
  1583. self.assertEqual(
  1584. list(book1.first_time_authors.all()[0].addresses.all()),
  1585. list(book1.first_time_authors.all()[0].addresses.all().all()),
  1586. )
  1587. self.assertEqual(
  1588. list(book1.first_time_authors.all()[1].addresses.all()),
  1589. list(book1.first_time_authors.all()[1].addresses.all().all()),
  1590. )
  1591. self.assertEqual(
  1592. list(book2.first_time_authors.all()[0].addresses.all()),
  1593. list(book2.first_time_authors.all()[0].addresses.all().all()),
  1594. )
  1595. def test_detect_is_fetched_with_to_attr(self):
  1596. with self.assertNumQueries(3):
  1597. books = Book.objects.filter(title__in=["book1", "book2"]).prefetch_related(
  1598. Prefetch(
  1599. "first_time_authors",
  1600. Author.objects.prefetch_related(
  1601. Prefetch(
  1602. "addresses",
  1603. AuthorAddress.objects.filter(address="Happy place"),
  1604. to_attr="happy_place",
  1605. )
  1606. ),
  1607. to_attr="first_authors",
  1608. ),
  1609. )
  1610. book1, book2 = list(books)
  1611. with self.assertNumQueries(0):
  1612. self.assertEqual(book1.first_authors, [self.author11, self.author12])
  1613. self.assertEqual(book2.first_authors, [self.author21])
  1614. self.assertEqual(
  1615. book1.first_authors[0].happy_place, [self.author1_address1]
  1616. )
  1617. self.assertEqual(book1.first_authors[1].happy_place, [])
  1618. self.assertEqual(
  1619. book2.first_authors[0].happy_place, [self.author2_address1]
  1620. )
  1621. def test_prefetch_reverse_foreign_key(self):
  1622. with self.assertNumQueries(2):
  1623. (bookwithyear1,) = BookWithYear.objects.prefetch_related("bookreview_set")
  1624. with self.assertNumQueries(0):
  1625. self.assertCountEqual(
  1626. bookwithyear1.bookreview_set.all(), [self.bookreview1]
  1627. )
  1628. with self.assertNumQueries(0):
  1629. prefetch_related_objects([bookwithyear1], "bookreview_set")
  1630. def test_add_clears_prefetched_objects(self):
  1631. bookwithyear = BookWithYear.objects.get(pk=self.bookwithyear1.pk)
  1632. prefetch_related_objects([bookwithyear], "bookreview_set")
  1633. self.assertCountEqual(bookwithyear.bookreview_set.all(), [self.bookreview1])
  1634. new_review = BookReview.objects.create()
  1635. bookwithyear.bookreview_set.add(new_review)
  1636. self.assertCountEqual(
  1637. bookwithyear.bookreview_set.all(), [self.bookreview1, new_review]
  1638. )
  1639. def test_remove_clears_prefetched_objects(self):
  1640. bookwithyear = BookWithYear.objects.get(pk=self.bookwithyear1.pk)
  1641. prefetch_related_objects([bookwithyear], "bookreview_set")
  1642. self.assertCountEqual(bookwithyear.bookreview_set.all(), [self.bookreview1])
  1643. bookwithyear.bookreview_set.remove(self.bookreview1)
  1644. self.assertCountEqual(bookwithyear.bookreview_set.all(), [])
  1645. class ReadPrefetchedObjectsCacheTests(TestCase):
  1646. @classmethod
  1647. def setUpTestData(cls):
  1648. cls.book1 = Book.objects.create(title="Les confessions Volume I")
  1649. cls.book2 = Book.objects.create(title="Candide")
  1650. cls.author1 = AuthorWithAge.objects.create(
  1651. name="Rousseau", first_book=cls.book1, age=70
  1652. )
  1653. cls.author2 = AuthorWithAge.objects.create(
  1654. name="Voltaire", first_book=cls.book2, age=65
  1655. )
  1656. cls.book1.authors.add(cls.author1)
  1657. cls.book2.authors.add(cls.author2)
  1658. FavoriteAuthors.objects.create(author=cls.author1, likes_author=cls.author2)
  1659. def test_retrieves_results_from_prefetched_objects_cache(self):
  1660. """
  1661. When intermediary results are prefetched without a destination
  1662. attribute, they are saved in the RelatedManager's cache
  1663. (_prefetched_objects_cache). prefetch_related() uses this cache
  1664. (#27554).
  1665. """
  1666. authors = AuthorWithAge.objects.prefetch_related(
  1667. Prefetch(
  1668. "author",
  1669. queryset=Author.objects.prefetch_related(
  1670. # Results are saved in the RelatedManager's cache
  1671. # (_prefetched_objects_cache) and do not replace the
  1672. # RelatedManager on Author instances (favorite_authors)
  1673. Prefetch("favorite_authors__first_book"),
  1674. ),
  1675. ),
  1676. )
  1677. with self.assertNumQueries(4):
  1678. # AuthorWithAge -> Author -> FavoriteAuthors, Book
  1679. self.assertSequenceEqual(authors, [self.author1, self.author2])
  1680. class NestedPrefetchTests(TestCase):
  1681. @classmethod
  1682. def setUpTestData(cls):
  1683. house = House.objects.create(name="Big house", address="123 Main St")
  1684. cls.room = Room.objects.create(name="Kitchen", house=house)
  1685. def test_nested_prefetch_is_not_overwritten_by_related_object(self):
  1686. """
  1687. The prefetched relationship is used rather than populating the reverse
  1688. relationship from the parent, when prefetching a set of child objects
  1689. related to a set of parent objects and the child queryset itself
  1690. specifies a prefetch back to the parent.
  1691. """
  1692. queryset = House.objects.only("name").prefetch_related(
  1693. Prefetch(
  1694. "rooms",
  1695. queryset=Room.objects.prefetch_related(
  1696. Prefetch("house", queryset=House.objects.only("address")),
  1697. ),
  1698. ),
  1699. )
  1700. with self.assertNumQueries(3):
  1701. house = queryset.first()
  1702. self.assertIs(Room.house.is_cached(self.room), True)
  1703. with self.assertNumQueries(0):
  1704. house.rooms.first().house.address
  1705. class PrefetchLimitTests(TestDataMixin, TestCase):
  1706. @skipUnlessDBFeature("supports_over_clause")
  1707. def test_m2m_forward(self):
  1708. authors = Author.objects.all() # Meta.ordering
  1709. with self.assertNumQueries(3):
  1710. books = list(
  1711. Book.objects.prefetch_related(
  1712. Prefetch("authors", authors),
  1713. Prefetch("authors", authors[1:], to_attr="authors_sliced"),
  1714. )
  1715. )
  1716. for book in books:
  1717. with self.subTest(book=book):
  1718. self.assertEqual(book.authors_sliced, list(book.authors.all())[1:])
  1719. @skipUnlessDBFeature("supports_over_clause")
  1720. def test_m2m_reverse(self):
  1721. books = Book.objects.order_by("title")
  1722. with self.assertNumQueries(3):
  1723. authors = list(
  1724. Author.objects.prefetch_related(
  1725. Prefetch("books", books),
  1726. Prefetch("books", books[1:2], to_attr="books_sliced"),
  1727. )
  1728. )
  1729. for author in authors:
  1730. with self.subTest(author=author):
  1731. self.assertEqual(author.books_sliced, list(author.books.all())[1:2])
  1732. @skipUnlessDBFeature("supports_over_clause")
  1733. def test_foreignkey_reverse(self):
  1734. authors = Author.objects.order_by("-name")
  1735. with self.assertNumQueries(3):
  1736. books = list(
  1737. Book.objects.prefetch_related(
  1738. Prefetch(
  1739. "first_time_authors",
  1740. authors,
  1741. ),
  1742. Prefetch(
  1743. "first_time_authors",
  1744. authors[1:],
  1745. to_attr="first_time_authors_sliced",
  1746. ),
  1747. )
  1748. )
  1749. for book in books:
  1750. with self.subTest(book=book):
  1751. self.assertEqual(
  1752. book.first_time_authors_sliced,
  1753. list(book.first_time_authors.all())[1:],
  1754. )
  1755. @skipUnlessDBFeature("supports_over_clause")
  1756. def test_reverse_ordering(self):
  1757. authors = Author.objects.reverse() # Reverse Meta.ordering
  1758. with self.assertNumQueries(3):
  1759. books = list(
  1760. Book.objects.prefetch_related(
  1761. Prefetch("authors", authors),
  1762. Prefetch("authors", authors[1:], to_attr="authors_sliced"),
  1763. )
  1764. )
  1765. for book in books:
  1766. with self.subTest(book=book):
  1767. self.assertEqual(book.authors_sliced, list(book.authors.all())[1:])
  1768. @skipIfDBFeature("supports_over_clause")
  1769. def test_window_not_supported(self):
  1770. authors = Author.objects.all()
  1771. msg = (
  1772. "Prefetching from a limited queryset is only supported on backends that "
  1773. "support window functions."
  1774. )
  1775. with self.assertRaisesMessage(NotSupportedError, msg):
  1776. list(Book.objects.prefetch_related(Prefetch("authors", authors[1:])))
  1777. @skipUnlessDBFeature("supports_over_clause")
  1778. def test_empty_order(self):
  1779. authors = Author.objects.order_by()
  1780. with self.assertNumQueries(3):
  1781. books = list(
  1782. Book.objects.prefetch_related(
  1783. Prefetch("authors", authors),
  1784. Prefetch("authors", authors[:1], to_attr="authors_sliced"),
  1785. )
  1786. )
  1787. for book in books:
  1788. with self.subTest(book=book):
  1789. self.assertEqual(len(book.authors_sliced), 1)
  1790. self.assertIn(book.authors_sliced[0], list(book.authors.all()))