1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026 |
- from unittest import mock
- from django.contrib.contenttypes.models import ContentType
- from django.core.exceptions import ObjectDoesNotExist
- from django.db import NotSupportedError, connection
- from django.db.models import Prefetch, QuerySet, prefetch_related_objects
- from django.db.models.query import get_prefetcher
- from django.db.models.sql import Query
- from django.test import (
- TestCase,
- override_settings,
- skipIfDBFeature,
- skipUnlessDBFeature,
- )
- from django.test.utils import CaptureQueriesContext
- from .models import (
- Article,
- Author,
- Author2,
- AuthorAddress,
- AuthorWithAge,
- Bio,
- Book,
- Bookmark,
- BookReview,
- BookWithYear,
- Comment,
- Department,
- Employee,
- FavoriteAuthors,
- House,
- LessonEntry,
- ModelIterableSubclass,
- Person,
- Qualification,
- Reader,
- Room,
- TaggedItem,
- Teacher,
- WordEntry,
- )
- class TestDataMixin:
- @classmethod
- def setUpTestData(cls):
- cls.book1 = Book.objects.create(title="Poems")
- cls.book2 = Book.objects.create(title="Jane Eyre")
- cls.book3 = Book.objects.create(title="Wuthering Heights")
- cls.book4 = Book.objects.create(title="Sense and Sensibility")
- cls.author1 = Author.objects.create(name="Charlotte", first_book=cls.book1)
- cls.author2 = Author.objects.create(name="Anne", first_book=cls.book1)
- cls.author3 = Author.objects.create(name="Emily", first_book=cls.book1)
- cls.author4 = Author.objects.create(name="Jane", first_book=cls.book4)
- cls.book1.authors.add(cls.author1, cls.author2, cls.author3)
- cls.book2.authors.add(cls.author1)
- cls.book3.authors.add(cls.author3)
- cls.book4.authors.add(cls.author4)
- cls.reader1 = Reader.objects.create(name="Amy")
- cls.reader2 = Reader.objects.create(name="Belinda")
- cls.reader1.books_read.add(cls.book1, cls.book4)
- cls.reader2.books_read.add(cls.book2, cls.book4)
- class PrefetchRelatedTests(TestDataMixin, TestCase):
- def assertWhereContains(self, sql, needle):
- where_idx = sql.index("WHERE")
- self.assertEqual(
- sql.count(str(needle), where_idx),
- 1,
- msg="WHERE clause doesn't contain %s, actual SQL: %s"
- % (needle, sql[where_idx:]),
- )
- def test_m2m_forward(self):
- with self.assertNumQueries(2):
- lists = [
- list(b.authors.all()) for b in Book.objects.prefetch_related("authors")
- ]
- normal_lists = [list(b.authors.all()) for b in Book.objects.all()]
- self.assertEqual(lists, normal_lists)
- def test_m2m_reverse(self):
- with self.assertNumQueries(2):
- lists = [
- list(a.books.all()) for a in Author.objects.prefetch_related("books")
- ]
- normal_lists = [list(a.books.all()) for a in Author.objects.all()]
- self.assertEqual(lists, normal_lists)
- def test_foreignkey_forward(self):
- with self.assertNumQueries(2):
- books = [
- a.first_book for a in Author.objects.prefetch_related("first_book")
- ]
- normal_books = [a.first_book for a in Author.objects.all()]
- self.assertEqual(books, normal_books)
- def test_foreignkey_reverse(self):
- with self.assertNumQueries(2):
- [
- list(b.first_time_authors.all())
- for b in Book.objects.prefetch_related("first_time_authors")
- ]
- self.assertSequenceEqual(self.book2.authors.all(), [self.author1])
- def test_onetoone_reverse_no_match(self):
- # Regression for #17439
- with self.assertNumQueries(2):
- book = Book.objects.prefetch_related("bookwithyear").all()[0]
- with self.assertNumQueries(0):
- with self.assertRaises(BookWithYear.DoesNotExist):
- book.bookwithyear
- def test_onetoone_reverse_with_to_field_pk(self):
- """
- A model (Bio) with a OneToOneField primary key (author) that references
- a non-pk field (name) on the related model (Author) is prefetchable.
- """
- Bio.objects.bulk_create(
- [
- Bio(author=self.author1),
- Bio(author=self.author2),
- Bio(author=self.author3),
- ]
- )
- authors = Author.objects.filter(
- name__in=[self.author1, self.author2, self.author3],
- ).prefetch_related("bio")
- with self.assertNumQueries(2):
- for author in authors:
- self.assertEqual(author.name, author.bio.author.name)
- def test_survives_clone(self):
- with self.assertNumQueries(2):
- [
- list(b.first_time_authors.all())
- for b in Book.objects.prefetch_related("first_time_authors").exclude(
- id=1000
- )
- ]
- def test_len(self):
- with self.assertNumQueries(2):
- qs = Book.objects.prefetch_related("first_time_authors")
- len(qs)
- [list(b.first_time_authors.all()) for b in qs]
- def test_bool(self):
- with self.assertNumQueries(2):
- qs = Book.objects.prefetch_related("first_time_authors")
- bool(qs)
- [list(b.first_time_authors.all()) for b in qs]
- def test_count(self):
- with self.assertNumQueries(2):
- qs = Book.objects.prefetch_related("first_time_authors")
- [b.first_time_authors.count() for b in qs]
- def test_exists(self):
- with self.assertNumQueries(2):
- qs = Book.objects.prefetch_related("first_time_authors")
- [b.first_time_authors.exists() for b in qs]
- def test_in_and_prefetch_related(self):
- """
- Regression test for #20242 - QuerySet "in" didn't work the first time
- when using prefetch_related. This was fixed by the removal of chunked
- reads from QuerySet iteration in
- 70679243d1786e03557c28929f9762a119e3ac14.
- """
- qs = Book.objects.prefetch_related("first_time_authors")
- self.assertIn(qs[0], qs)
- def test_clear(self):
- with self.assertNumQueries(5):
- with_prefetch = Author.objects.prefetch_related("books")
- without_prefetch = with_prefetch.prefetch_related(None)
- [list(a.books.all()) for a in without_prefetch]
- def test_m2m_then_m2m(self):
- """A m2m can be followed through another m2m."""
- with self.assertNumQueries(3):
- qs = Author.objects.prefetch_related("books__read_by")
- lists = [
- [[str(r) for r in b.read_by.all()] for b in a.books.all()] for a in qs
- ]
- self.assertEqual(
- lists,
- [
- [["Amy"], ["Belinda"]], # Charlotte - Poems, Jane Eyre
- [["Amy"]], # Anne - Poems
- [["Amy"], []], # Emily - Poems, Wuthering Heights
- [["Amy", "Belinda"]], # Jane - Sense and Sense
- ],
- )
- def test_overriding_prefetch(self):
- with self.assertNumQueries(3):
- qs = Author.objects.prefetch_related("books", "books__read_by")
- lists = [
- [[str(r) for r in b.read_by.all()] for b in a.books.all()] for a in qs
- ]
- self.assertEqual(
- lists,
- [
- [["Amy"], ["Belinda"]], # Charlotte - Poems, Jane Eyre
- [["Amy"]], # Anne - Poems
- [["Amy"], []], # Emily - Poems, Wuthering Heights
- [["Amy", "Belinda"]], # Jane - Sense and Sense
- ],
- )
- with self.assertNumQueries(3):
- qs = Author.objects.prefetch_related("books__read_by", "books")
- lists = [
- [[str(r) for r in b.read_by.all()] for b in a.books.all()] for a in qs
- ]
- self.assertEqual(
- lists,
- [
- [["Amy"], ["Belinda"]], # Charlotte - Poems, Jane Eyre
- [["Amy"]], # Anne - Poems
- [["Amy"], []], # Emily - Poems, Wuthering Heights
- [["Amy", "Belinda"]], # Jane - Sense and Sense
- ],
- )
- def test_get(self):
- """
- Objects retrieved with .get() get the prefetch behavior.
- """
- # Need a double
- with self.assertNumQueries(3):
- author = Author.objects.prefetch_related("books__read_by").get(
- name="Charlotte"
- )
- lists = [[str(r) for r in b.read_by.all()] for b in author.books.all()]
- self.assertEqual(lists, [["Amy"], ["Belinda"]]) # Poems, Jane Eyre
- def test_foreign_key_then_m2m(self):
- """
- A m2m relation can be followed after a relation like ForeignKey that
- doesn't have many objects.
- """
- with self.assertNumQueries(2):
- qs = Author.objects.select_related("first_book").prefetch_related(
- "first_book__read_by"
- )
- lists = [[str(r) for r in a.first_book.read_by.all()] for a in qs]
- self.assertEqual(lists, [["Amy"], ["Amy"], ["Amy"], ["Amy", "Belinda"]])
- def test_reverse_one_to_one_then_m2m(self):
- """
- A m2m relation can be followed after going through the select_related
- reverse of an o2o.
- """
- qs = Author.objects.prefetch_related("bio__books").select_related("bio")
- with self.assertNumQueries(1):
- list(qs.all())
- Bio.objects.create(author=self.author1)
- with self.assertNumQueries(2):
- list(qs.all())
- def test_attribute_error(self):
- qs = Reader.objects.prefetch_related("books_read__xyz")
- msg = (
- "Cannot find 'xyz' on Book object, 'books_read__xyz' "
- "is an invalid parameter to prefetch_related()"
- )
- with self.assertRaisesMessage(AttributeError, msg) as cm:
- list(qs)
- self.assertIn("prefetch_related", str(cm.exception))
- def test_invalid_final_lookup(self):
- qs = Book.objects.prefetch_related("authors__name")
- msg = (
- "'authors__name' does not resolve to an item that supports "
- "prefetching - this is an invalid parameter to prefetch_related()."
- )
- with self.assertRaisesMessage(ValueError, msg) as cm:
- list(qs)
- self.assertIn("prefetch_related", str(cm.exception))
- self.assertIn("name", str(cm.exception))
- def test_prefetch_eq(self):
- prefetch_1 = Prefetch("authors", queryset=Author.objects.all())
- prefetch_2 = Prefetch("books", queryset=Book.objects.all())
- self.assertEqual(prefetch_1, prefetch_1)
- self.assertEqual(prefetch_1, mock.ANY)
- self.assertNotEqual(prefetch_1, prefetch_2)
- def test_forward_m2m_to_attr_conflict(self):
- msg = "to_attr=authors conflicts with a field on the Book model."
- authors = Author.objects.all()
- with self.assertRaisesMessage(ValueError, msg):
- list(
- Book.objects.prefetch_related(
- Prefetch("authors", queryset=authors, to_attr="authors"),
- )
- )
- # Without the ValueError, an author was deleted due to the implicit
- # save of the relation assignment.
- self.assertEqual(self.book1.authors.count(), 3)
- def test_reverse_m2m_to_attr_conflict(self):
- msg = "to_attr=books conflicts with a field on the Author model."
- poems = Book.objects.filter(title="Poems")
- with self.assertRaisesMessage(ValueError, msg):
- list(
- Author.objects.prefetch_related(
- Prefetch("books", queryset=poems, to_attr="books"),
- )
- )
- # Without the ValueError, a book was deleted due to the implicit
- # save of reverse relation assignment.
- self.assertEqual(self.author1.books.count(), 2)
- def test_m2m_then_reverse_fk_object_ids(self):
- with CaptureQueriesContext(connection) as queries:
- list(Book.objects.prefetch_related("authors__addresses"))
- sql = queries[-1]["sql"]
- self.assertWhereContains(sql, self.author1.name)
- def test_m2m_then_m2m_object_ids(self):
- with CaptureQueriesContext(connection) as queries:
- list(Book.objects.prefetch_related("authors__favorite_authors"))
- sql = queries[-1]["sql"]
- self.assertWhereContains(sql, self.author1.name)
- def test_m2m_then_reverse_one_to_one_object_ids(self):
- with CaptureQueriesContext(connection) as queries:
- list(Book.objects.prefetch_related("authors__authorwithage"))
- sql = queries[-1]["sql"]
- self.assertWhereContains(sql, self.author1.id)
- def test_filter_deferred(self):
- """
- Related filtering of prefetched querysets is deferred on m2m and
- reverse m2o relations until necessary.
- """
- add_q = Query.add_q
- for relation in ["authors", "first_time_authors"]:
- with self.subTest(relation=relation):
- with mock.patch.object(
- Query,
- "add_q",
- autospec=True,
- side_effect=lambda self, q: add_q(self, q),
- ) as add_q_mock:
- list(Book.objects.prefetch_related(relation))
- self.assertEqual(add_q_mock.call_count, 1)
- def test_named_values_list(self):
- qs = Author.objects.prefetch_related("books")
- self.assertCountEqual(
- [value.name for value in qs.values_list("name", named=True)],
- ["Anne", "Charlotte", "Emily", "Jane"],
- )
- def test_m2m_prefetching_iterator_with_chunks(self):
- with self.assertNumQueries(3):
- authors = [
- b.authors.first()
- for b in Book.objects.prefetch_related("authors").iterator(chunk_size=2)
- ]
- self.assertEqual(
- authors,
- [self.author1, self.author1, self.author3, self.author4],
- )
- def test_m2m_prefetching_iterator_without_chunks_error(self):
- msg = (
- "chunk_size must be provided when using QuerySet.iterator() after "
- "prefetch_related()."
- )
- with self.assertRaisesMessage(ValueError, msg):
- Book.objects.prefetch_related("authors").iterator()
- class RawQuerySetTests(TestDataMixin, TestCase):
- def test_basic(self):
- with self.assertNumQueries(2):
- books = Book.objects.raw(
- "SELECT * FROM prefetch_related_book WHERE id = %s", (self.book1.id,)
- ).prefetch_related("authors")
- book1 = list(books)[0]
- with self.assertNumQueries(0):
- self.assertCountEqual(
- book1.authors.all(), [self.author1, self.author2, self.author3]
- )
- def test_prefetch_before_raw(self):
- with self.assertNumQueries(2):
- books = Book.objects.prefetch_related("authors").raw(
- "SELECT * FROM prefetch_related_book WHERE id = %s", (self.book1.id,)
- )
- book1 = list(books)[0]
- with self.assertNumQueries(0):
- self.assertCountEqual(
- book1.authors.all(), [self.author1, self.author2, self.author3]
- )
- def test_clear(self):
- with self.assertNumQueries(5):
- with_prefetch = Author.objects.raw(
- "SELECT * FROM prefetch_related_author"
- ).prefetch_related("books")
- without_prefetch = with_prefetch.prefetch_related(None)
- [list(a.books.all()) for a in without_prefetch]
- class CustomPrefetchTests(TestCase):
- @classmethod
- def traverse_qs(cls, obj_iter, path):
- """
- Helper method that returns a list containing a list of the objects in the
- obj_iter. Then for each object in the obj_iter, the path will be
- recursively travelled and the found objects are added to the return value.
- """
- ret_val = []
- if hasattr(obj_iter, "all"):
- obj_iter = obj_iter.all()
- try:
- iter(obj_iter)
- except TypeError:
- obj_iter = [obj_iter]
- for obj in obj_iter:
- rel_objs = []
- for part in path:
- if not part:
- continue
- try:
- related = getattr(obj, part[0])
- except ObjectDoesNotExist:
- continue
- if related is not None:
- rel_objs.extend(cls.traverse_qs(related, [part[1:]]))
- ret_val.append((obj, rel_objs))
- return ret_val
- @classmethod
- def setUpTestData(cls):
- cls.person1 = Person.objects.create(name="Joe")
- cls.person2 = Person.objects.create(name="Mary")
- # Set main_room for each house before creating the next one for
- # databases where supports_nullable_unique_constraints is False.
- cls.house1 = House.objects.create(
- name="House 1", address="123 Main St", owner=cls.person1
- )
- cls.room1_1 = Room.objects.create(name="Dining room", house=cls.house1)
- cls.room1_2 = Room.objects.create(name="Lounge", house=cls.house1)
- cls.room1_3 = Room.objects.create(name="Kitchen", house=cls.house1)
- cls.house1.main_room = cls.room1_1
- cls.house1.save()
- cls.person1.houses.add(cls.house1)
- cls.house2 = House.objects.create(
- name="House 2", address="45 Side St", owner=cls.person1
- )
- cls.room2_1 = Room.objects.create(name="Dining room", house=cls.house2)
- cls.room2_2 = Room.objects.create(name="Lounge", house=cls.house2)
- cls.room2_3 = Room.objects.create(name="Kitchen", house=cls.house2)
- cls.house2.main_room = cls.room2_1
- cls.house2.save()
- cls.person1.houses.add(cls.house2)
- cls.house3 = House.objects.create(
- name="House 3", address="6 Downing St", owner=cls.person2
- )
- cls.room3_1 = Room.objects.create(name="Dining room", house=cls.house3)
- cls.room3_2 = Room.objects.create(name="Lounge", house=cls.house3)
- cls.room3_3 = Room.objects.create(name="Kitchen", house=cls.house3)
- cls.house3.main_room = cls.room3_1
- cls.house3.save()
- cls.person2.houses.add(cls.house3)
- cls.house4 = House.objects.create(
- name="house 4", address="7 Regents St", owner=cls.person2
- )
- cls.room4_1 = Room.objects.create(name="Dining room", house=cls.house4)
- cls.room4_2 = Room.objects.create(name="Lounge", house=cls.house4)
- cls.room4_3 = Room.objects.create(name="Kitchen", house=cls.house4)
- cls.house4.main_room = cls.room4_1
- cls.house4.save()
- cls.person2.houses.add(cls.house4)
- def test_traverse_qs(self):
- qs = Person.objects.prefetch_related("houses")
- related_objs_normal = ([list(p.houses.all()) for p in qs],)
- related_objs_from_traverse = [
- [inner[0] for inner in o[1]] for o in self.traverse_qs(qs, [["houses"]])
- ]
- self.assertEqual(related_objs_normal, (related_objs_from_traverse,))
- def test_ambiguous(self):
- # Ambiguous: Lookup was already seen with a different queryset.
- msg = (
- "'houses' lookup was already seen with a different queryset. You "
- "may need to adjust the ordering of your lookups."
- )
- # lookup.queryset shouldn't be evaluated.
- with self.assertNumQueries(3):
- with self.assertRaisesMessage(ValueError, msg):
- self.traverse_qs(
- Person.objects.prefetch_related(
- "houses__rooms",
- Prefetch("houses", queryset=House.objects.all()),
- ),
- [["houses", "rooms"]],
- )
- # Ambiguous: Lookup houses_lst doesn't yet exist when performing
- # houses_lst__rooms.
- msg = (
- "Cannot find 'houses_lst' on Person object, 'houses_lst__rooms' is "
- "an invalid parameter to prefetch_related()"
- )
- with self.assertRaisesMessage(AttributeError, msg):
- self.traverse_qs(
- Person.objects.prefetch_related(
- "houses_lst__rooms",
- Prefetch(
- "houses", queryset=House.objects.all(), to_attr="houses_lst"
- ),
- ),
- [["houses", "rooms"]],
- )
- # Not ambiguous.
- self.traverse_qs(
- Person.objects.prefetch_related("houses__rooms", "houses"),
- [["houses", "rooms"]],
- )
- self.traverse_qs(
- Person.objects.prefetch_related(
- "houses__rooms",
- Prefetch("houses", queryset=House.objects.all(), to_attr="houses_lst"),
- ),
- [["houses", "rooms"]],
- )
- def test_m2m(self):
- # Control lookups.
- with self.assertNumQueries(2):
- lst1 = self.traverse_qs(
- Person.objects.prefetch_related("houses"), [["houses"]]
- )
- # Test lookups.
- with self.assertNumQueries(2):
- lst2 = self.traverse_qs(
- Person.objects.prefetch_related(Prefetch("houses")), [["houses"]]
- )
- self.assertEqual(lst1, lst2)
- with self.assertNumQueries(2):
- lst2 = self.traverse_qs(
- Person.objects.prefetch_related(
- Prefetch("houses", to_attr="houses_lst")
- ),
- [["houses_lst"]],
- )
- self.assertEqual(lst1, lst2)
- def test_reverse_m2m(self):
- # Control lookups.
- with self.assertNumQueries(2):
- lst1 = self.traverse_qs(
- House.objects.prefetch_related("occupants"), [["occupants"]]
- )
- # Test lookups.
- with self.assertNumQueries(2):
- lst2 = self.traverse_qs(
- House.objects.prefetch_related(Prefetch("occupants")), [["occupants"]]
- )
- self.assertEqual(lst1, lst2)
- with self.assertNumQueries(2):
- lst2 = self.traverse_qs(
- House.objects.prefetch_related(
- Prefetch("occupants", to_attr="occupants_lst")
- ),
- [["occupants_lst"]],
- )
- self.assertEqual(lst1, lst2)
- def test_m2m_through_fk(self):
- # Control lookups.
- with self.assertNumQueries(3):
- lst1 = self.traverse_qs(
- Room.objects.prefetch_related("house__occupants"),
- [["house", "occupants"]],
- )
- # Test lookups.
- with self.assertNumQueries(3):
- lst2 = self.traverse_qs(
- Room.objects.prefetch_related(Prefetch("house__occupants")),
- [["house", "occupants"]],
- )
- self.assertEqual(lst1, lst2)
- with self.assertNumQueries(3):
- lst2 = self.traverse_qs(
- Room.objects.prefetch_related(
- Prefetch("house__occupants", to_attr="occupants_lst")
- ),
- [["house", "occupants_lst"]],
- )
- self.assertEqual(lst1, lst2)
- def test_m2m_through_gfk(self):
- TaggedItem.objects.create(tag="houses", content_object=self.house1)
- TaggedItem.objects.create(tag="houses", content_object=self.house2)
- # Control lookups.
- with self.assertNumQueries(3):
- lst1 = self.traverse_qs(
- TaggedItem.objects.filter(tag="houses").prefetch_related(
- "content_object__rooms"
- ),
- [["content_object", "rooms"]],
- )
- # Test lookups.
- with self.assertNumQueries(3):
- lst2 = self.traverse_qs(
- TaggedItem.objects.prefetch_related(
- Prefetch("content_object"),
- Prefetch("content_object__rooms", to_attr="rooms_lst"),
- ),
- [["content_object", "rooms_lst"]],
- )
- self.assertEqual(lst1, lst2)
- def test_o2m_through_m2m(self):
- # Control lookups.
- with self.assertNumQueries(3):
- lst1 = self.traverse_qs(
- Person.objects.prefetch_related("houses", "houses__rooms"),
- [["houses", "rooms"]],
- )
- # Test lookups.
- with self.assertNumQueries(3):
- lst2 = self.traverse_qs(
- Person.objects.prefetch_related(Prefetch("houses"), "houses__rooms"),
- [["houses", "rooms"]],
- )
- self.assertEqual(lst1, lst2)
- with self.assertNumQueries(3):
- lst2 = self.traverse_qs(
- Person.objects.prefetch_related(
- Prefetch("houses"), Prefetch("houses__rooms")
- ),
- [["houses", "rooms"]],
- )
- self.assertEqual(lst1, lst2)
- with self.assertNumQueries(3):
- lst2 = self.traverse_qs(
- Person.objects.prefetch_related(
- Prefetch("houses", to_attr="houses_lst"), "houses_lst__rooms"
- ),
- [["houses_lst", "rooms"]],
- )
- self.assertEqual(lst1, lst2)
- with self.assertNumQueries(3):
- lst2 = self.traverse_qs(
- Person.objects.prefetch_related(
- Prefetch("houses", to_attr="houses_lst"),
- Prefetch("houses_lst__rooms", to_attr="rooms_lst"),
- ),
- [["houses_lst", "rooms_lst"]],
- )
- self.assertEqual(lst1, lst2)
- def test_generic_rel(self):
- bookmark = Bookmark.objects.create(url="http://www.djangoproject.com/")
- TaggedItem.objects.create(content_object=bookmark, tag="django")
- TaggedItem.objects.create(
- content_object=bookmark, favorite=bookmark, tag="python"
- )
- # Control lookups.
- with self.assertNumQueries(4):
- lst1 = self.traverse_qs(
- Bookmark.objects.prefetch_related(
- "tags", "tags__content_object", "favorite_tags"
- ),
- [["tags", "content_object"], ["favorite_tags"]],
- )
- # Test lookups.
- with self.assertNumQueries(4):
- lst2 = self.traverse_qs(
- Bookmark.objects.prefetch_related(
- Prefetch("tags", to_attr="tags_lst"),
- Prefetch("tags_lst__content_object"),
- Prefetch("favorite_tags"),
- ),
- [["tags_lst", "content_object"], ["favorite_tags"]],
- )
- self.assertEqual(lst1, lst2)
- def test_traverse_single_item_property(self):
- # Control lookups.
- with self.assertNumQueries(5):
- lst1 = self.traverse_qs(
- Person.objects.prefetch_related(
- "houses__rooms",
- "primary_house__occupants__houses",
- ),
- [["primary_house", "occupants", "houses"]],
- )
- # Test lookups.
- with self.assertNumQueries(5):
- lst2 = self.traverse_qs(
- Person.objects.prefetch_related(
- "houses__rooms",
- Prefetch("primary_house__occupants", to_attr="occupants_lst"),
- "primary_house__occupants_lst__houses",
- ),
- [["primary_house", "occupants_lst", "houses"]],
- )
- self.assertEqual(lst1, lst2)
- def test_traverse_multiple_items_property(self):
- # Control lookups.
- with self.assertNumQueries(4):
- lst1 = self.traverse_qs(
- Person.objects.prefetch_related(
- "houses",
- "all_houses__occupants__houses",
- ),
- [["all_houses", "occupants", "houses"]],
- )
- # Test lookups.
- with self.assertNumQueries(4):
- lst2 = self.traverse_qs(
- Person.objects.prefetch_related(
- "houses",
- Prefetch("all_houses__occupants", to_attr="occupants_lst"),
- "all_houses__occupants_lst__houses",
- ),
- [["all_houses", "occupants_lst", "houses"]],
- )
- self.assertEqual(lst1, lst2)
- def test_custom_qs(self):
- # Test basic.
- with self.assertNumQueries(2):
- lst1 = list(Person.objects.prefetch_related("houses"))
- with self.assertNumQueries(2):
- lst2 = list(
- Person.objects.prefetch_related(
- Prefetch(
- "houses", queryset=House.objects.all(), to_attr="houses_lst"
- )
- )
- )
- self.assertEqual(
- self.traverse_qs(lst1, [["houses"]]),
- self.traverse_qs(lst2, [["houses_lst"]]),
- )
- # Test queryset filtering.
- with self.assertNumQueries(2):
- lst2 = list(
- Person.objects.prefetch_related(
- Prefetch(
- "houses",
- queryset=House.objects.filter(
- pk__in=[self.house1.pk, self.house3.pk]
- ),
- to_attr="houses_lst",
- )
- )
- )
- self.assertEqual(len(lst2[0].houses_lst), 1)
- self.assertEqual(lst2[0].houses_lst[0], self.house1)
- self.assertEqual(len(lst2[1].houses_lst), 1)
- self.assertEqual(lst2[1].houses_lst[0], self.house3)
- # Test flattened.
- with self.assertNumQueries(3):
- lst1 = list(Person.objects.prefetch_related("houses__rooms"))
- with self.assertNumQueries(3):
- lst2 = list(
- Person.objects.prefetch_related(
- Prefetch(
- "houses__rooms",
- queryset=Room.objects.all(),
- to_attr="rooms_lst",
- )
- )
- )
- self.assertEqual(
- self.traverse_qs(lst1, [["houses", "rooms"]]),
- self.traverse_qs(lst2, [["houses", "rooms_lst"]]),
- )
- # Test inner select_related.
- with self.assertNumQueries(3):
- lst1 = list(Person.objects.prefetch_related("houses__owner"))
- with self.assertNumQueries(2):
- lst2 = list(
- Person.objects.prefetch_related(
- Prefetch("houses", queryset=House.objects.select_related("owner"))
- )
- )
- self.assertEqual(
- self.traverse_qs(lst1, [["houses", "owner"]]),
- self.traverse_qs(lst2, [["houses", "owner"]]),
- )
- # Test inner prefetch.
- inner_rooms_qs = Room.objects.filter(pk__in=[self.room1_1.pk, self.room1_2.pk])
- houses_qs_prf = House.objects.prefetch_related(
- Prefetch("rooms", queryset=inner_rooms_qs, to_attr="rooms_lst")
- )
- with self.assertNumQueries(4):
- lst2 = list(
- Person.objects.prefetch_related(
- Prefetch(
- "houses",
- queryset=houses_qs_prf.filter(pk=self.house1.pk),
- to_attr="houses_lst",
- ),
- Prefetch("houses_lst__rooms_lst__main_room_of"),
- )
- )
- self.assertEqual(len(lst2[0].houses_lst[0].rooms_lst), 2)
- self.assertEqual(lst2[0].houses_lst[0].rooms_lst[0], self.room1_1)
- self.assertEqual(lst2[0].houses_lst[0].rooms_lst[1], self.room1_2)
- self.assertEqual(lst2[0].houses_lst[0].rooms_lst[0].main_room_of, self.house1)
- self.assertEqual(len(lst2[1].houses_lst), 0)
- # Test ForwardManyToOneDescriptor.
- houses = House.objects.select_related("owner")
- with self.assertNumQueries(6):
- rooms = Room.objects.prefetch_related("house")
- lst1 = self.traverse_qs(rooms, [["house", "owner"]])
- with self.assertNumQueries(2):
- rooms = Room.objects.prefetch_related(Prefetch("house", queryset=houses))
- lst2 = self.traverse_qs(rooms, [["house", "owner"]])
- self.assertEqual(lst1, lst2)
- with self.assertNumQueries(2):
- houses = House.objects.select_related("owner")
- rooms = Room.objects.prefetch_related(
- Prefetch("house", queryset=houses, to_attr="house_attr")
- )
- lst2 = self.traverse_qs(rooms, [["house_attr", "owner"]])
- self.assertEqual(lst1, lst2)
- room = Room.objects.prefetch_related(
- Prefetch("house", queryset=houses.filter(address="DoesNotExist"))
- ).first()
- with self.assertRaises(ObjectDoesNotExist):
- getattr(room, "house")
- room = Room.objects.prefetch_related(
- Prefetch(
- "house",
- queryset=houses.filter(address="DoesNotExist"),
- to_attr="house_attr",
- )
- ).first()
- self.assertIsNone(room.house_attr)
- rooms = Room.objects.prefetch_related(
- Prefetch("house", queryset=House.objects.only("name"))
- )
- with self.assertNumQueries(2):
- getattr(rooms.first().house, "name")
- with self.assertNumQueries(3):
- getattr(rooms.first().house, "address")
- # Test ReverseOneToOneDescriptor.
- houses = House.objects.select_related("owner")
- with self.assertNumQueries(6):
- rooms = Room.objects.prefetch_related("main_room_of")
- lst1 = self.traverse_qs(rooms, [["main_room_of", "owner"]])
- with self.assertNumQueries(2):
- rooms = Room.objects.prefetch_related(
- Prefetch("main_room_of", queryset=houses)
- )
- lst2 = self.traverse_qs(rooms, [["main_room_of", "owner"]])
- self.assertEqual(lst1, lst2)
- with self.assertNumQueries(2):
- rooms = list(
- Room.objects.prefetch_related(
- Prefetch(
- "main_room_of",
- queryset=houses,
- to_attr="main_room_of_attr",
- )
- )
- )
- lst2 = self.traverse_qs(rooms, [["main_room_of_attr", "owner"]])
- self.assertEqual(lst1, lst2)
- room = (
- Room.objects.filter(main_room_of__isnull=False)
- .prefetch_related(
- Prefetch("main_room_of", queryset=houses.filter(address="DoesNotExist"))
- )
- .first()
- )
- with self.assertRaises(ObjectDoesNotExist):
- getattr(room, "main_room_of")
- room = (
- Room.objects.filter(main_room_of__isnull=False)
- .prefetch_related(
- Prefetch(
- "main_room_of",
- queryset=houses.filter(address="DoesNotExist"),
- to_attr="main_room_of_attr",
- )
- )
- .first()
- )
- self.assertIsNone(room.main_room_of_attr)
- # The custom queryset filters should be applied to the queryset
- # instance returned by the manager.
- person = Person.objects.prefetch_related(
- Prefetch("houses", queryset=House.objects.filter(name="House 1")),
- ).get(pk=self.person1.pk)
- self.assertEqual(
- list(person.houses.all()),
- list(person.houses.all().all()),
- )
- def test_nested_prefetch_related_are_not_overwritten(self):
- # Regression test for #24873
- houses_2 = House.objects.prefetch_related(Prefetch("rooms"))
- persons = Person.objects.prefetch_related(Prefetch("houses", queryset=houses_2))
- houses = House.objects.prefetch_related(Prefetch("occupants", queryset=persons))
- list(houses) # queryset must be evaluated once to reproduce the bug.
- self.assertEqual(
- houses.all()[0].occupants.all()[0].houses.all()[1].rooms.all()[0],
- self.room2_1,
- )
- def test_nested_prefetch_related_with_duplicate_prefetcher(self):
- """
- Nested prefetches whose name clashes with descriptor names
- (Person.houses here) are allowed.
- """
- occupants = Person.objects.prefetch_related(
- Prefetch("houses", to_attr="some_attr_name"),
- Prefetch("houses", queryset=House.objects.prefetch_related("main_room")),
- )
- houses = House.objects.prefetch_related(
- Prefetch("occupants", queryset=occupants)
- )
- with self.assertNumQueries(5):
- self.traverse_qs(list(houses), [["occupants", "houses", "main_room"]])
- def test_nested_prefetch_related_with_duplicate_prefetch_and_depth(self):
- people = Person.objects.prefetch_related(
- Prefetch(
- "houses__main_room",
- queryset=Room.objects.filter(name="Dining room"),
- to_attr="dining_room",
- ),
- "houses__main_room",
- )
- with self.assertNumQueries(4):
- main_room = people[0].houses.all()[0]
- people = Person.objects.prefetch_related(
- "houses__main_room",
- Prefetch(
- "houses__main_room",
- queryset=Room.objects.filter(name="Dining room"),
- to_attr="dining_room",
- ),
- )
- with self.assertNumQueries(4):
- main_room = people[0].houses.all()[0]
- self.assertEqual(main_room.main_room, self.room1_1)
- def test_values_queryset(self):
- msg = "Prefetch querysets cannot use raw(), values(), and values_list()."
- with self.assertRaisesMessage(ValueError, msg):
- Prefetch("houses", House.objects.values("pk"))
- with self.assertRaisesMessage(ValueError, msg):
- Prefetch("houses", House.objects.values_list("pk"))
- # That error doesn't affect managers with custom ModelIterable subclasses
- self.assertIs(
- Teacher.objects_custom.all()._iterable_class, ModelIterableSubclass
- )
- Prefetch("teachers", Teacher.objects_custom.all())
- def test_raw_queryset(self):
- msg = "Prefetch querysets cannot use raw(), values(), and values_list()."
- with self.assertRaisesMessage(ValueError, msg):
- Prefetch("houses", House.objects.raw("select pk from house"))
- def test_to_attr_doesnt_cache_through_attr_as_list(self):
- house = House.objects.prefetch_related(
- Prefetch("rooms", queryset=Room.objects.all(), to_attr="to_rooms"),
- ).get(pk=self.house3.pk)
- self.assertIsInstance(house.rooms.all(), QuerySet)
- def test_to_attr_cached_property(self):
- persons = Person.objects.prefetch_related(
- Prefetch("houses", House.objects.all(), to_attr="cached_all_houses"),
- )
- for person in persons:
- # To bypass caching at the related descriptor level, don't use
- # person.houses.all() here.
- all_houses = list(House.objects.filter(occupants=person))
- with self.assertNumQueries(0):
- self.assertEqual(person.cached_all_houses, all_houses)
- def test_filter_deferred(self):
- """
- Related filtering of prefetched querysets is deferred until necessary.
- """
- add_q = Query.add_q
- with mock.patch.object(
- Query,
- "add_q",
- autospec=True,
- side_effect=lambda self, q: add_q(self, q),
- ) as add_q_mock:
- list(
- House.objects.prefetch_related(
- Prefetch("occupants", queryset=Person.objects.all())
- )
- )
- self.assertEqual(add_q_mock.call_count, 1)
- class DefaultManagerTests(TestCase):
- @classmethod
- def setUpTestData(cls):
- cls.qual1 = Qualification.objects.create(name="BA")
- cls.qual2 = Qualification.objects.create(name="BSci")
- cls.qual3 = Qualification.objects.create(name="MA")
- cls.qual4 = Qualification.objects.create(name="PhD")
- cls.teacher1 = Teacher.objects.create(name="Mr Cleese")
- cls.teacher2 = Teacher.objects.create(name="Mr Idle")
- cls.teacher3 = Teacher.objects.create(name="Mr Chapman")
- cls.teacher1.qualifications.add(cls.qual1, cls.qual2, cls.qual3, cls.qual4)
- cls.teacher2.qualifications.add(cls.qual1)
- cls.teacher3.qualifications.add(cls.qual2)
- cls.dept1 = Department.objects.create(name="English")
- cls.dept2 = Department.objects.create(name="Physics")
- cls.dept1.teachers.add(cls.teacher1, cls.teacher2)
- cls.dept2.teachers.add(cls.teacher1, cls.teacher3)
- def test_m2m_then_m2m(self):
- with self.assertNumQueries(3):
- # When we prefetch the teachers, and force the query, we don't want
- # the default manager on teachers to immediately get all the related
- # qualifications, since this will do one query per teacher.
- qs = Department.objects.prefetch_related("teachers")
- depts = "".join(
- "%s department: %s\n"
- % (dept.name, ", ".join(str(t) for t in dept.teachers.all()))
- for dept in qs
- )
- self.assertEqual(
- depts,
- "English department: Mr Cleese (BA, BSci, MA, PhD), Mr Idle (BA)\n"
- "Physics department: Mr Cleese (BA, BSci, MA, PhD), Mr Chapman "
- "(BSci)\n",
- )
- class GenericRelationTests(TestCase):
- @classmethod
- def setUpTestData(cls):
- book1 = Book.objects.create(title="Winnie the Pooh")
- book2 = Book.objects.create(title="Do you like green eggs and spam?")
- book3 = Book.objects.create(title="Three Men In A Boat")
- reader1 = Reader.objects.create(name="me")
- reader2 = Reader.objects.create(name="you")
- reader3 = Reader.objects.create(name="someone")
- book1.read_by.add(reader1, reader2)
- book2.read_by.add(reader2)
- book3.read_by.add(reader3)
- cls.book1, cls.book2, cls.book3 = book1, book2, book3
- cls.reader1, cls.reader2, cls.reader3 = reader1, reader2, reader3
- def test_prefetch_GFK(self):
- TaggedItem.objects.create(tag="awesome", content_object=self.book1)
- TaggedItem.objects.create(tag="great", content_object=self.reader1)
- TaggedItem.objects.create(tag="outstanding", content_object=self.book2)
- TaggedItem.objects.create(tag="amazing", content_object=self.reader3)
- # 1 for TaggedItem table, 1 for Book table, 1 for Reader table
- with self.assertNumQueries(3):
- qs = TaggedItem.objects.prefetch_related("content_object")
- list(qs)
- def test_prefetch_GFK_nonint_pk(self):
- Comment.objects.create(comment="awesome", content_object=self.book1)
- # 1 for Comment table, 1 for Book table
- with self.assertNumQueries(2):
- qs = Comment.objects.prefetch_related("content_object")
- [c.content_object for c in qs]
- def test_prefetch_GFK_uuid_pk(self):
- article = Article.objects.create(name="Django")
- Comment.objects.create(comment="awesome", content_object_uuid=article)
- qs = Comment.objects.prefetch_related("content_object_uuid")
- self.assertEqual([c.content_object_uuid for c in qs], [article])
- def test_prefetch_GFK_fk_pk(self):
- book = Book.objects.create(title="Poems")
- book_with_year = BookWithYear.objects.create(book=book, published_year=2019)
- Comment.objects.create(comment="awesome", content_object=book_with_year)
- qs = Comment.objects.prefetch_related("content_object")
- self.assertEqual([c.content_object for c in qs], [book_with_year])
- def test_traverse_GFK(self):
- """
- A 'content_object' can be traversed with prefetch_related() and
- get to related objects on the other side (assuming it is suitably
- filtered)
- """
- TaggedItem.objects.create(tag="awesome", content_object=self.book1)
- TaggedItem.objects.create(tag="awesome", content_object=self.book2)
- TaggedItem.objects.create(tag="awesome", content_object=self.book3)
- TaggedItem.objects.create(tag="awesome", content_object=self.reader1)
- TaggedItem.objects.create(tag="awesome", content_object=self.reader2)
- ct = ContentType.objects.get_for_model(Book)
- # We get 3 queries - 1 for main query, 1 for content_objects since they
- # all use the same table, and 1 for the 'read_by' relation.
- with self.assertNumQueries(3):
- # If we limit to books, we know that they will have 'read_by'
- # attributes, so the following makes sense:
- qs = TaggedItem.objects.filter(
- content_type=ct, tag="awesome"
- ).prefetch_related("content_object__read_by")
- readers_of_awesome_books = {
- r.name for tag in qs for r in tag.content_object.read_by.all()
- }
- self.assertEqual(readers_of_awesome_books, {"me", "you", "someone"})
- def test_nullable_GFK(self):
- TaggedItem.objects.create(
- tag="awesome", content_object=self.book1, created_by=self.reader1
- )
- TaggedItem.objects.create(tag="great", content_object=self.book2)
- TaggedItem.objects.create(tag="rubbish", content_object=self.book3)
- with self.assertNumQueries(2):
- result = [
- t.created_by for t in TaggedItem.objects.prefetch_related("created_by")
- ]
- self.assertEqual(result, [t.created_by for t in TaggedItem.objects.all()])
- def test_generic_relation(self):
- bookmark = Bookmark.objects.create(url="http://www.djangoproject.com/")
- TaggedItem.objects.create(content_object=bookmark, tag="django")
- TaggedItem.objects.create(content_object=bookmark, tag="python")
- with self.assertNumQueries(2):
- tags = [
- t.tag
- for b in Bookmark.objects.prefetch_related("tags")
- for t in b.tags.all()
- ]
- self.assertEqual(sorted(tags), ["django", "python"])
- def test_charfield_GFK(self):
- b = Bookmark.objects.create(url="http://www.djangoproject.com/")
- TaggedItem.objects.create(content_object=b, tag="django")
- TaggedItem.objects.create(content_object=b, favorite=b, tag="python")
- with self.assertNumQueries(3):
- bookmark = Bookmark.objects.filter(pk=b.pk).prefetch_related(
- "tags", "favorite_tags"
- )[0]
- self.assertEqual(
- sorted(i.tag for i in bookmark.tags.all()), ["django", "python"]
- )
- self.assertEqual([i.tag for i in bookmark.favorite_tags.all()], ["python"])
- def test_custom_queryset(self):
- bookmark = Bookmark.objects.create(url="http://www.djangoproject.com/")
- django_tag = TaggedItem.objects.create(content_object=bookmark, tag="django")
- TaggedItem.objects.create(content_object=bookmark, tag="python")
- with self.assertNumQueries(2):
- bookmark = Bookmark.objects.prefetch_related(
- Prefetch("tags", TaggedItem.objects.filter(tag="django")),
- ).get()
- with self.assertNumQueries(0):
- self.assertEqual(list(bookmark.tags.all()), [django_tag])
- # The custom queryset filters should be applied to the queryset
- # instance returned by the manager.
- self.assertEqual(list(bookmark.tags.all()), list(bookmark.tags.all().all()))
- def test_deleted_GFK(self):
- TaggedItem.objects.create(tag="awesome", content_object=self.book1)
- TaggedItem.objects.create(tag="awesome", content_object=self.book2)
- ct = ContentType.objects.get_for_model(Book)
- book1_pk = self.book1.pk
- self.book1.delete()
- with self.assertNumQueries(2):
- qs = TaggedItem.objects.filter(tag="awesome").prefetch_related(
- "content_object"
- )
- result = [
- (tag.object_id, tag.content_type_id, tag.content_object) for tag in qs
- ]
- self.assertEqual(
- result,
- [
- (book1_pk, ct.pk, None),
- (self.book2.pk, ct.pk, self.book2),
- ],
- )
- def test_reverse_generic_relation(self):
- # Create two distinct bookmarks to ensure the bookmark and
- # tagged item models primary are offset.
- first_bookmark = Bookmark.objects.create()
- second_bookmark = Bookmark.objects.create()
- TaggedItem.objects.create(
- content_object=first_bookmark, favorite=second_bookmark
- )
- with self.assertNumQueries(2):
- obj = TaggedItem.objects.prefetch_related("favorite_bookmarks").get()
- with self.assertNumQueries(0):
- prefetched_bookmarks = obj.favorite_bookmarks.all()
- self.assertQuerySetEqual(prefetched_bookmarks, [second_bookmark])
- class MultiTableInheritanceTest(TestCase):
- @classmethod
- def setUpTestData(cls):
- cls.book1 = BookWithYear.objects.create(title="Poems", published_year=2010)
- cls.book2 = BookWithYear.objects.create(title="More poems", published_year=2011)
- cls.author1 = AuthorWithAge.objects.create(
- name="Jane", first_book=cls.book1, age=50
- )
- cls.author2 = AuthorWithAge.objects.create(
- name="Tom", first_book=cls.book1, age=49
- )
- cls.author3 = AuthorWithAge.objects.create(
- name="Robert", first_book=cls.book2, age=48
- )
- cls.author_address = AuthorAddress.objects.create(
- author=cls.author1, address="SomeStreet 1"
- )
- cls.book2.aged_authors.add(cls.author2, cls.author3)
- cls.br1 = BookReview.objects.create(book=cls.book1, notes="review book1")
- cls.br2 = BookReview.objects.create(book=cls.book2, notes="review book2")
- def test_foreignkey(self):
- with self.assertNumQueries(2):
- qs = AuthorWithAge.objects.prefetch_related("addresses")
- addresses = [
- [str(address) for address in obj.addresses.all()] for obj in qs
- ]
- self.assertEqual(addresses, [[str(self.author_address)], [], []])
- def test_foreignkey_to_inherited(self):
- with self.assertNumQueries(2):
- qs = BookReview.objects.prefetch_related("book")
- titles = [obj.book.title for obj in qs]
- self.assertCountEqual(titles, ["Poems", "More poems"])
- def test_m2m_to_inheriting_model(self):
- qs = AuthorWithAge.objects.prefetch_related("books_with_year")
- with self.assertNumQueries(2):
- lst = [
- [str(book) for book in author.books_with_year.all()] for author in qs
- ]
- qs = AuthorWithAge.objects.all()
- lst2 = [[str(book) for book in author.books_with_year.all()] for author in qs]
- self.assertEqual(lst, lst2)
- qs = BookWithYear.objects.prefetch_related("aged_authors")
- with self.assertNumQueries(2):
- lst = [[str(author) for author in book.aged_authors.all()] for book in qs]
- qs = BookWithYear.objects.all()
- lst2 = [[str(author) for author in book.aged_authors.all()] for book in qs]
- self.assertEqual(lst, lst2)
- def test_parent_link_prefetch(self):
- with self.assertNumQueries(2):
- [a.author for a in AuthorWithAge.objects.prefetch_related("author")]
- @override_settings(DEBUG=True)
- def test_child_link_prefetch(self):
- with self.assertNumQueries(2):
- authors = [
- a.authorwithage
- for a in Author.objects.prefetch_related("authorwithage")
- ]
- # Regression for #18090: the prefetching query must include an IN clause.
- # Note that on Oracle the table name is upper case in the generated SQL,
- # thus the .lower() call.
- self.assertIn("authorwithage", connection.queries[-1]["sql"].lower())
- self.assertIn(" IN ", connection.queries[-1]["sql"])
- self.assertEqual(authors, [a.authorwithage for a in Author.objects.all()])
- class ForeignKeyToFieldTest(TestCase):
- @classmethod
- def setUpTestData(cls):
- cls.book = Book.objects.create(title="Poems")
- cls.author1 = Author.objects.create(name="Jane", first_book=cls.book)
- cls.author2 = Author.objects.create(name="Tom", first_book=cls.book)
- cls.author3 = Author.objects.create(name="Robert", first_book=cls.book)
- cls.author_address = AuthorAddress.objects.create(
- author=cls.author1, address="SomeStreet 1"
- )
- FavoriteAuthors.objects.create(author=cls.author1, likes_author=cls.author2)
- FavoriteAuthors.objects.create(author=cls.author2, likes_author=cls.author3)
- FavoriteAuthors.objects.create(author=cls.author3, likes_author=cls.author1)
- def test_foreignkey(self):
- with self.assertNumQueries(2):
- qs = Author.objects.prefetch_related("addresses")
- addresses = [
- [str(address) for address in obj.addresses.all()] for obj in qs
- ]
- self.assertEqual(addresses, [[str(self.author_address)], [], []])
- def test_m2m(self):
- with self.assertNumQueries(3):
- qs = Author.objects.prefetch_related("favorite_authors", "favors_me")
- favorites = [
- (
- [str(i_like) for i_like in author.favorite_authors.all()],
- [str(likes_me) for likes_me in author.favors_me.all()],
- )
- for author in qs
- ]
- self.assertEqual(
- favorites,
- [
- ([str(self.author2)], [str(self.author3)]),
- ([str(self.author3)], [str(self.author1)]),
- ([str(self.author1)], [str(self.author2)]),
- ],
- )
- class LookupOrderingTest(TestCase):
- """
- Test cases that demonstrate that ordering of lookups is important, and
- ensure it is preserved.
- """
- @classmethod
- def setUpTestData(cls):
- person1 = Person.objects.create(name="Joe")
- person2 = Person.objects.create(name="Mary")
- # Set main_room for each house before creating the next one for
- # databases where supports_nullable_unique_constraints is False.
- house1 = House.objects.create(address="123 Main St")
- room1_1 = Room.objects.create(name="Dining room", house=house1)
- Room.objects.create(name="Lounge", house=house1)
- Room.objects.create(name="Kitchen", house=house1)
- house1.main_room = room1_1
- house1.save()
- person1.houses.add(house1)
- house2 = House.objects.create(address="45 Side St")
- room2_1 = Room.objects.create(name="Dining room", house=house2)
- Room.objects.create(name="Lounge", house=house2)
- house2.main_room = room2_1
- house2.save()
- person1.houses.add(house2)
- house3 = House.objects.create(address="6 Downing St")
- room3_1 = Room.objects.create(name="Dining room", house=house3)
- Room.objects.create(name="Lounge", house=house3)
- Room.objects.create(name="Kitchen", house=house3)
- house3.main_room = room3_1
- house3.save()
- person2.houses.add(house3)
- house4 = House.objects.create(address="7 Regents St")
- room4_1 = Room.objects.create(name="Dining room", house=house4)
- Room.objects.create(name="Lounge", house=house4)
- house4.main_room = room4_1
- house4.save()
- person2.houses.add(house4)
- def test_order(self):
- with self.assertNumQueries(4):
- # The following two queries must be done in the same order as written,
- # otherwise 'primary_house' will cause non-prefetched lookups
- qs = Person.objects.prefetch_related(
- "houses__rooms", "primary_house__occupants"
- )
- [list(p.primary_house.occupants.all()) for p in qs]
- class NullableTest(TestCase):
- @classmethod
- def setUpTestData(cls):
- boss = Employee.objects.create(name="Peter")
- Employee.objects.create(name="Joe", boss=boss)
- Employee.objects.create(name="Angela", boss=boss)
- def test_traverse_nullable(self):
- # Because we use select_related() for 'boss', it doesn't need to be
- # prefetched, but we can still traverse it although it contains some nulls
- with self.assertNumQueries(2):
- qs = Employee.objects.select_related("boss").prefetch_related("boss__serfs")
- co_serfs = [
- list(e.boss.serfs.all()) if e.boss is not None else [] for e in qs
- ]
- qs2 = Employee.objects.select_related("boss")
- co_serfs2 = [
- list(e.boss.serfs.all()) if e.boss is not None else [] for e in qs2
- ]
- self.assertEqual(co_serfs, co_serfs2)
- def test_prefetch_nullable(self):
- # One for main employee, one for boss, one for serfs
- with self.assertNumQueries(3):
- qs = Employee.objects.prefetch_related("boss__serfs")
- co_serfs = [
- list(e.boss.serfs.all()) if e.boss is not None else [] for e in qs
- ]
- qs2 = Employee.objects.all()
- co_serfs2 = [
- list(e.boss.serfs.all()) if e.boss is not None else [] for e in qs2
- ]
- self.assertEqual(co_serfs, co_serfs2)
- def test_in_bulk(self):
- """
- In-bulk does correctly prefetch objects by not using .iterator()
- directly.
- """
- boss1 = Employee.objects.create(name="Peter")
- boss2 = Employee.objects.create(name="Jack")
- with self.assertNumQueries(2):
- # Prefetch is done and it does not cause any errors.
- bulk = Employee.objects.prefetch_related("serfs").in_bulk(
- [boss1.pk, boss2.pk]
- )
- for b in bulk.values():
- list(b.serfs.all())
- class MultiDbTests(TestCase):
- databases = {"default", "other"}
- def test_using_is_honored_m2m(self):
- B = Book.objects.using("other")
- A = Author.objects.using("other")
- book1 = B.create(title="Poems")
- book2 = B.create(title="Jane Eyre")
- book3 = B.create(title="Wuthering Heights")
- book4 = B.create(title="Sense and Sensibility")
- author1 = A.create(name="Charlotte", first_book=book1)
- author2 = A.create(name="Anne", first_book=book1)
- author3 = A.create(name="Emily", first_book=book1)
- author4 = A.create(name="Jane", first_book=book4)
- book1.authors.add(author1, author2, author3)
- book2.authors.add(author1)
- book3.authors.add(author3)
- book4.authors.add(author4)
- # Forward
- qs1 = B.prefetch_related("authors")
- with self.assertNumQueries(2, using="other"):
- books = "".join(
- "%s (%s)\n"
- % (book.title, ", ".join(a.name for a in book.authors.all()))
- for book in qs1
- )
- self.assertEqual(
- books,
- "Poems (Charlotte, Anne, Emily)\n"
- "Jane Eyre (Charlotte)\n"
- "Wuthering Heights (Emily)\n"
- "Sense and Sensibility (Jane)\n",
- )
- # Reverse
- qs2 = A.prefetch_related("books")
- with self.assertNumQueries(2, using="other"):
- authors = "".join(
- "%s: %s\n"
- % (author.name, ", ".join(b.title for b in author.books.all()))
- for author in qs2
- )
- self.assertEqual(
- authors,
- "Charlotte: Poems, Jane Eyre\n"
- "Anne: Poems\n"
- "Emily: Poems, Wuthering Heights\n"
- "Jane: Sense and Sensibility\n",
- )
- def test_using_is_honored_fkey(self):
- B = Book.objects.using("other")
- A = Author.objects.using("other")
- book1 = B.create(title="Poems")
- book2 = B.create(title="Sense and Sensibility")
- A.create(name="Charlotte Bronte", first_book=book1)
- A.create(name="Jane Austen", first_book=book2)
- # Forward
- with self.assertNumQueries(2, using="other"):
- books = ", ".join(
- a.first_book.title for a in A.prefetch_related("first_book")
- )
- self.assertEqual("Poems, Sense and Sensibility", books)
- # Reverse
- with self.assertNumQueries(2, using="other"):
- books = "".join(
- "%s (%s)\n"
- % (b.title, ", ".join(a.name for a in b.first_time_authors.all()))
- for b in B.prefetch_related("first_time_authors")
- )
- self.assertEqual(
- books,
- "Poems (Charlotte Bronte)\nSense and Sensibility (Jane Austen)\n",
- )
- def test_using_is_honored_inheritance(self):
- B = BookWithYear.objects.using("other")
- A = AuthorWithAge.objects.using("other")
- book1 = B.create(title="Poems", published_year=2010)
- B.create(title="More poems", published_year=2011)
- A.create(name="Jane", first_book=book1, age=50)
- A.create(name="Tom", first_book=book1, age=49)
- # parent link
- with self.assertNumQueries(2, using="other"):
- authors = ", ".join(a.author.name for a in A.prefetch_related("author"))
- self.assertEqual(authors, "Jane, Tom")
- # child link
- with self.assertNumQueries(2, using="other"):
- ages = ", ".join(
- str(a.authorwithage.age) for a in A.prefetch_related("authorwithage")
- )
- self.assertEqual(ages, "50, 49")
- def test_using_is_honored_custom_qs(self):
- B = Book.objects.using("other")
- A = Author.objects.using("other")
- book1 = B.create(title="Poems")
- book2 = B.create(title="Sense and Sensibility")
- A.create(name="Charlotte Bronte", first_book=book1)
- A.create(name="Jane Austen", first_book=book2)
- # Implicit hinting
- with self.assertNumQueries(2, using="other"):
- prefetch = Prefetch("first_time_authors", queryset=Author.objects.all())
- books = "".join(
- "%s (%s)\n"
- % (b.title, ", ".join(a.name for a in b.first_time_authors.all()))
- for b in B.prefetch_related(prefetch)
- )
- self.assertEqual(
- books,
- "Poems (Charlotte Bronte)\nSense and Sensibility (Jane Austen)\n",
- )
- # Explicit using on the same db.
- with self.assertNumQueries(2, using="other"):
- prefetch = Prefetch(
- "first_time_authors", queryset=Author.objects.using("other")
- )
- books = "".join(
- "%s (%s)\n"
- % (b.title, ", ".join(a.name for a in b.first_time_authors.all()))
- for b in B.prefetch_related(prefetch)
- )
- self.assertEqual(
- books,
- "Poems (Charlotte Bronte)\nSense and Sensibility (Jane Austen)\n",
- )
- # Explicit using on a different db.
- with (
- self.assertNumQueries(1, using="default"),
- self.assertNumQueries(1, using="other"),
- ):
- prefetch = Prefetch(
- "first_time_authors", queryset=Author.objects.using("default")
- )
- books = "".join(
- "%s (%s)\n"
- % (b.title, ", ".join(a.name for a in b.first_time_authors.all()))
- for b in B.prefetch_related(prefetch)
- )
- self.assertEqual(books, "Poems ()\n" "Sense and Sensibility ()\n")
- class Ticket19607Tests(TestCase):
- @classmethod
- def setUpTestData(cls):
- LessonEntry.objects.bulk_create(
- LessonEntry(id=id_, name1=name1, name2=name2)
- for id_, name1, name2 in [
- (1, "einfach", "simple"),
- (2, "schwierig", "difficult"),
- ]
- )
- WordEntry.objects.bulk_create(
- WordEntry(id=id_, lesson_entry_id=lesson_entry_id, name=name)
- for id_, lesson_entry_id, name in [
- (1, 1, "einfach"),
- (2, 1, "simple"),
- (3, 2, "schwierig"),
- (4, 2, "difficult"),
- ]
- )
- def test_bug(self):
- list(
- WordEntry.objects.prefetch_related(
- "lesson_entry", "lesson_entry__wordentry_set"
- )
- )
- class Ticket21410Tests(TestCase):
- @classmethod
- def setUpTestData(cls):
- book1 = Book.objects.create(title="Poems")
- book2 = Book.objects.create(title="Jane Eyre")
- book3 = Book.objects.create(title="Wuthering Heights")
- book4 = Book.objects.create(title="Sense and Sensibility")
- author1 = Author2.objects.create(name="Charlotte", first_book=book1)
- author2 = Author2.objects.create(name="Anne", first_book=book1)
- author3 = Author2.objects.create(name="Emily", first_book=book1)
- author4 = Author2.objects.create(name="Jane", first_book=book4)
- author1.favorite_books.add(book1, book2, book3)
- author2.favorite_books.add(book1)
- author3.favorite_books.add(book2)
- author4.favorite_books.add(book3)
- def test_bug(self):
- list(Author2.objects.prefetch_related("first_book", "favorite_books"))
- class Ticket21760Tests(TestCase):
- @classmethod
- def setUpTestData(cls):
- cls.rooms = []
- for _ in range(3):
- house = House.objects.create()
- for _ in range(3):
- cls.rooms.append(Room.objects.create(house=house))
- # Set main_room for each house before creating the next one for
- # databases where supports_nullable_unique_constraints is False.
- house.main_room = cls.rooms[-3]
- house.save()
- def test_bug(self):
- prefetcher = get_prefetcher(self.rooms[0], "house", "house")[0]
- queryset = prefetcher.get_prefetch_querysets(list(Room.objects.all()))[0]
- self.assertNotIn(" JOIN ", str(queryset.query))
- class DirectPrefetchedObjectCacheReuseTests(TestCase):
- """
- prefetch_related() reuses objects fetched in _prefetched_objects_cache.
- When objects are prefetched and not stored as an instance attribute (often
- intermediary relationships), they are saved to the
- _prefetched_objects_cache attribute. prefetch_related() takes
- _prefetched_objects_cache into account when determining whether an object
- has been fetched[1] and retrieves results from it when it is populated [2].
- [1]: #25546 (duplicate queries on nested Prefetch)
- [2]: #27554 (queryset evaluation fails with a mix of nested and flattened
- prefetches)
- """
- @classmethod
- def setUpTestData(cls):
- cls.book1, cls.book2 = [
- Book.objects.create(title="book1"),
- Book.objects.create(title="book2"),
- ]
- cls.author11, cls.author12, cls.author21 = [
- Author.objects.create(first_book=cls.book1, name="Author11"),
- Author.objects.create(first_book=cls.book1, name="Author12"),
- Author.objects.create(first_book=cls.book2, name="Author21"),
- ]
- cls.author1_address1, cls.author1_address2, cls.author2_address1 = [
- AuthorAddress.objects.create(author=cls.author11, address="Happy place"),
- AuthorAddress.objects.create(author=cls.author12, address="Haunted house"),
- AuthorAddress.objects.create(author=cls.author21, address="Happy place"),
- ]
- cls.bookwithyear1 = BookWithYear.objects.create(
- title="Poems", published_year=2010
- )
- cls.bookreview1 = BookReview.objects.create(book=cls.bookwithyear1)
- def test_detect_is_fetched(self):
- """
- Nested prefetch_related() shouldn't trigger duplicate queries for the same
- lookup.
- """
- with self.assertNumQueries(3):
- books = Book.objects.filter(title__in=["book1", "book2"]).prefetch_related(
- Prefetch(
- "first_time_authors",
- Author.objects.prefetch_related(
- Prefetch(
- "addresses",
- AuthorAddress.objects.filter(address="Happy place"),
- )
- ),
- ),
- )
- book1, book2 = list(books)
- with self.assertNumQueries(0):
- self.assertSequenceEqual(
- book1.first_time_authors.all(), [self.author11, self.author12]
- )
- self.assertSequenceEqual(book2.first_time_authors.all(), [self.author21])
- self.assertSequenceEqual(
- book1.first_time_authors.all()[0].addresses.all(),
- [self.author1_address1],
- )
- self.assertSequenceEqual(
- book1.first_time_authors.all()[1].addresses.all(), []
- )
- self.assertSequenceEqual(
- book2.first_time_authors.all()[0].addresses.all(),
- [self.author2_address1],
- )
- self.assertEqual(
- list(book1.first_time_authors.all()),
- list(book1.first_time_authors.all().all()),
- )
- self.assertEqual(
- list(book2.first_time_authors.all()),
- list(book2.first_time_authors.all().all()),
- )
- self.assertEqual(
- list(book1.first_time_authors.all()[0].addresses.all()),
- list(book1.first_time_authors.all()[0].addresses.all().all()),
- )
- self.assertEqual(
- list(book1.first_time_authors.all()[1].addresses.all()),
- list(book1.first_time_authors.all()[1].addresses.all().all()),
- )
- self.assertEqual(
- list(book2.first_time_authors.all()[0].addresses.all()),
- list(book2.first_time_authors.all()[0].addresses.all().all()),
- )
- def test_detect_is_fetched_with_to_attr(self):
- with self.assertNumQueries(3):
- books = Book.objects.filter(title__in=["book1", "book2"]).prefetch_related(
- Prefetch(
- "first_time_authors",
- Author.objects.prefetch_related(
- Prefetch(
- "addresses",
- AuthorAddress.objects.filter(address="Happy place"),
- to_attr="happy_place",
- )
- ),
- to_attr="first_authors",
- ),
- )
- book1, book2 = list(books)
- with self.assertNumQueries(0):
- self.assertEqual(book1.first_authors, [self.author11, self.author12])
- self.assertEqual(book2.first_authors, [self.author21])
- self.assertEqual(
- book1.first_authors[0].happy_place, [self.author1_address1]
- )
- self.assertEqual(book1.first_authors[1].happy_place, [])
- self.assertEqual(
- book2.first_authors[0].happy_place, [self.author2_address1]
- )
- def test_prefetch_reverse_foreign_key(self):
- with self.assertNumQueries(2):
- (bookwithyear1,) = BookWithYear.objects.prefetch_related("bookreview_set")
- with self.assertNumQueries(0):
- self.assertCountEqual(
- bookwithyear1.bookreview_set.all(), [self.bookreview1]
- )
- with self.assertNumQueries(0):
- prefetch_related_objects([bookwithyear1], "bookreview_set")
- def test_add_clears_prefetched_objects(self):
- bookwithyear = BookWithYear.objects.get(pk=self.bookwithyear1.pk)
- prefetch_related_objects([bookwithyear], "bookreview_set")
- self.assertCountEqual(bookwithyear.bookreview_set.all(), [self.bookreview1])
- new_review = BookReview.objects.create()
- bookwithyear.bookreview_set.add(new_review)
- self.assertCountEqual(
- bookwithyear.bookreview_set.all(), [self.bookreview1, new_review]
- )
- def test_remove_clears_prefetched_objects(self):
- bookwithyear = BookWithYear.objects.get(pk=self.bookwithyear1.pk)
- prefetch_related_objects([bookwithyear], "bookreview_set")
- self.assertCountEqual(bookwithyear.bookreview_set.all(), [self.bookreview1])
- bookwithyear.bookreview_set.remove(self.bookreview1)
- self.assertCountEqual(bookwithyear.bookreview_set.all(), [])
- class ReadPrefetchedObjectsCacheTests(TestCase):
- @classmethod
- def setUpTestData(cls):
- cls.book1 = Book.objects.create(title="Les confessions Volume I")
- cls.book2 = Book.objects.create(title="Candide")
- cls.author1 = AuthorWithAge.objects.create(
- name="Rousseau", first_book=cls.book1, age=70
- )
- cls.author2 = AuthorWithAge.objects.create(
- name="Voltaire", first_book=cls.book2, age=65
- )
- cls.book1.authors.add(cls.author1)
- cls.book2.authors.add(cls.author2)
- FavoriteAuthors.objects.create(author=cls.author1, likes_author=cls.author2)
- def test_retrieves_results_from_prefetched_objects_cache(self):
- """
- When intermediary results are prefetched without a destination
- attribute, they are saved in the RelatedManager's cache
- (_prefetched_objects_cache). prefetch_related() uses this cache
- (#27554).
- """
- authors = AuthorWithAge.objects.prefetch_related(
- Prefetch(
- "author",
- queryset=Author.objects.prefetch_related(
- # Results are saved in the RelatedManager's cache
- # (_prefetched_objects_cache) and do not replace the
- # RelatedManager on Author instances (favorite_authors)
- Prefetch("favorite_authors__first_book"),
- ),
- ),
- )
- with self.assertNumQueries(4):
- # AuthorWithAge -> Author -> FavoriteAuthors, Book
- self.assertSequenceEqual(authors, [self.author1, self.author2])
- class NestedPrefetchTests(TestCase):
- @classmethod
- def setUpTestData(cls):
- house = House.objects.create(name="Big house", address="123 Main St")
- cls.room = Room.objects.create(name="Kitchen", house=house)
- def test_nested_prefetch_is_not_overwritten_by_related_object(self):
- """
- The prefetched relationship is used rather than populating the reverse
- relationship from the parent, when prefetching a set of child objects
- related to a set of parent objects and the child queryset itself
- specifies a prefetch back to the parent.
- """
- queryset = House.objects.only("name").prefetch_related(
- Prefetch(
- "rooms",
- queryset=Room.objects.prefetch_related(
- Prefetch("house", queryset=House.objects.only("address")),
- ),
- ),
- )
- with self.assertNumQueries(3):
- house = queryset.first()
- self.assertIs(Room.house.is_cached(self.room), True)
- with self.assertNumQueries(0):
- house.rooms.first().house.address
- class PrefetchLimitTests(TestDataMixin, TestCase):
- @skipUnlessDBFeature("supports_over_clause")
- def test_m2m_forward(self):
- authors = Author.objects.all() # Meta.ordering
- with self.assertNumQueries(3):
- books = list(
- Book.objects.prefetch_related(
- Prefetch("authors", authors),
- Prefetch("authors", authors[1:], to_attr="authors_sliced"),
- )
- )
- for book in books:
- with self.subTest(book=book):
- self.assertEqual(book.authors_sliced, list(book.authors.all())[1:])
- @skipUnlessDBFeature("supports_over_clause")
- def test_m2m_reverse(self):
- books = Book.objects.order_by("title")
- with self.assertNumQueries(3):
- authors = list(
- Author.objects.prefetch_related(
- Prefetch("books", books),
- Prefetch("books", books[1:2], to_attr="books_sliced"),
- )
- )
- for author in authors:
- with self.subTest(author=author):
- self.assertEqual(author.books_sliced, list(author.books.all())[1:2])
- @skipUnlessDBFeature("supports_over_clause")
- def test_foreignkey_reverse(self):
- authors = Author.objects.order_by("-name")
- with self.assertNumQueries(3):
- books = list(
- Book.objects.prefetch_related(
- Prefetch(
- "first_time_authors",
- authors,
- ),
- Prefetch(
- "first_time_authors",
- authors[1:],
- to_attr="first_time_authors_sliced",
- ),
- )
- )
- for book in books:
- with self.subTest(book=book):
- self.assertEqual(
- book.first_time_authors_sliced,
- list(book.first_time_authors.all())[1:],
- )
- @skipUnlessDBFeature("supports_over_clause")
- def test_reverse_ordering(self):
- authors = Author.objects.reverse() # Reverse Meta.ordering
- with self.assertNumQueries(3):
- books = list(
- Book.objects.prefetch_related(
- Prefetch("authors", authors),
- Prefetch("authors", authors[1:], to_attr="authors_sliced"),
- )
- )
- for book in books:
- with self.subTest(book=book):
- self.assertEqual(book.authors_sliced, list(book.authors.all())[1:])
- @skipIfDBFeature("supports_over_clause")
- def test_window_not_supported(self):
- authors = Author.objects.all()
- msg = (
- "Prefetching from a limited queryset is only supported on backends that "
- "support window functions."
- )
- with self.assertRaisesMessage(NotSupportedError, msg):
- list(Book.objects.prefetch_related(Prefetch("authors", authors[1:])))
- @skipUnlessDBFeature("supports_over_clause")
- def test_empty_order(self):
- authors = Author.objects.order_by()
- with self.assertNumQueries(3):
- books = list(
- Book.objects.prefetch_related(
- Prefetch("authors", authors),
- Prefetch("authors", authors[:1], to_attr="authors_sliced"),
- )
- )
- for book in books:
- with self.subTest(book=book):
- self.assertEqual(len(book.authors_sliced), 1)
- self.assertIn(book.authors_sliced[0], list(book.authors.all()))
|