tests.py 73 KB

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