tests.py 79 KB

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