Loading django/db/models/query.py +2 −0 Original line number Diff line number Diff line Loading @@ -1521,6 +1521,7 @@ def get_prefetcher(instance, attr): rel_obj = getattr(instance, attr) if hasattr(rel_obj, 'get_prefetch_queryset'): prefetcher = rel_obj is_fetched = attr in instance._prefetched_objects_cache return prefetcher, rel_obj_descriptor, attr_found, is_fetched Loading Loading @@ -1597,6 +1598,7 @@ def prefetch_one_level(instances, prefetcher, lookup, level): else: if as_attr: setattr(obj, to_attr, vals) obj._prefetched_objects_cache[cache_name] = vals else: # Cache in the QuerySet.all(). qs = getattr(obj, to_attr).all() Loading tests/prefetch_related/tests.py +79 −0 Original line number Diff line number Diff line Loading @@ -1241,3 +1241,82 @@ class Ticket21760Tests(TestCase): prefetcher = get_prefetcher(self.rooms[0], 'house')[0] queryset = prefetcher.get_prefetch_queryset(list(Room.objects.all()))[0] self.assertNotIn(' JOIN ', force_text(queryset.query)) class Ticket25546Tests(TestCase): """ Nested prefetch_related() shouldn't trigger duplicate queries for the same lookup. Before, prefetch queries were for 'addresses', 'first_time_authors', and 'first_time_authors__addresses'. The last query is the duplicate. """ @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'), ] def test_prefetch(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'), ) ), ), ) book1, book2 = list(books) with self.assertNumQueries(0): self.assertListEqual(list(book1.first_time_authors.all()), [self.author11, self.author12]) self.assertListEqual(list(book2.first_time_authors.all()), [self.author21]) self.assertListEqual(list(book1.first_time_authors.all()[0].addresses.all()), [self.author1_address1]) self.assertListEqual(list(book1.first_time_authors.all()[1].addresses.all()), []) self.assertListEqual(list(book2.first_time_authors.all()[0].addresses.all()), [self.author2_address1]) def test_prefetch_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.assertListEqual(book1.first_authors, [self.author11, self.author12]) self.assertListEqual(book2.first_authors, [self.author21]) self.assertListEqual(book1.first_authors[0].happy_place, [self.author1_address1]) self.assertListEqual(book1.first_authors[1].happy_place, []) self.assertListEqual(book2.first_authors[0].happy_place, [self.author2_address1]) Loading
django/db/models/query.py +2 −0 Original line number Diff line number Diff line Loading @@ -1521,6 +1521,7 @@ def get_prefetcher(instance, attr): rel_obj = getattr(instance, attr) if hasattr(rel_obj, 'get_prefetch_queryset'): prefetcher = rel_obj is_fetched = attr in instance._prefetched_objects_cache return prefetcher, rel_obj_descriptor, attr_found, is_fetched Loading Loading @@ -1597,6 +1598,7 @@ def prefetch_one_level(instances, prefetcher, lookup, level): else: if as_attr: setattr(obj, to_attr, vals) obj._prefetched_objects_cache[cache_name] = vals else: # Cache in the QuerySet.all(). qs = getattr(obj, to_attr).all() Loading
tests/prefetch_related/tests.py +79 −0 Original line number Diff line number Diff line Loading @@ -1241,3 +1241,82 @@ class Ticket21760Tests(TestCase): prefetcher = get_prefetcher(self.rooms[0], 'house')[0] queryset = prefetcher.get_prefetch_queryset(list(Room.objects.all()))[0] self.assertNotIn(' JOIN ', force_text(queryset.query)) class Ticket25546Tests(TestCase): """ Nested prefetch_related() shouldn't trigger duplicate queries for the same lookup. Before, prefetch queries were for 'addresses', 'first_time_authors', and 'first_time_authors__addresses'. The last query is the duplicate. """ @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'), ] def test_prefetch(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'), ) ), ), ) book1, book2 = list(books) with self.assertNumQueries(0): self.assertListEqual(list(book1.first_time_authors.all()), [self.author11, self.author12]) self.assertListEqual(list(book2.first_time_authors.all()), [self.author21]) self.assertListEqual(list(book1.first_time_authors.all()[0].addresses.all()), [self.author1_address1]) self.assertListEqual(list(book1.first_time_authors.all()[1].addresses.all()), []) self.assertListEqual(list(book2.first_time_authors.all()[0].addresses.all()), [self.author2_address1]) def test_prefetch_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.assertListEqual(book1.first_authors, [self.author11, self.author12]) self.assertListEqual(book2.first_authors, [self.author21]) self.assertListEqual(book1.first_authors[0].happy_place, [self.author1_address1]) self.assertListEqual(book1.first_authors[1].happy_place, []) self.assertListEqual(book2.first_authors[0].happy_place, [self.author2_address1])