tests.py 67 KB

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