2
0

tests.py 59 KB

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