tests.py 71 KB

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