tests.py 65 KB

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