From f5233dce309543c826224be9dfa9c9f4f855f73c Mon Sep 17 00:00:00 2001 From: Jamie Matthews Date: Tue, 4 Jan 2022 11:10:49 +0000 Subject: [PATCH] Fixed #32511 -- Corrected handling prefetched nested reverse relationships. When prefetching a set of child objects related to a set of parent objects, we usually want to populate the relationship back from the child to the parent to avoid a query when accessing that relationship attribute. However, there's an edge case where the child queryset itself specifies a prefetch back to the parent. In that case, we want to use the prefetched relationship rather than populating the reverse relationship from the parent. --- AUTHORS | 1 + .../db/models/fields/related_descriptors.py | 5 ++-- tests/prefetch_related/tests.py | 26 +++++++++++++++++++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index 3ca8ad40f5..77053c9568 100644 --- a/AUTHORS +++ b/AUTHORS @@ -423,6 +423,7 @@ answer newbie questions, and generally made Django that much better: James Timmins James Turk James Wheare + Jamie Matthews Jannis Leidel Janos Guljas Jan Pazdziora diff --git a/django/db/models/fields/related_descriptors.py b/django/db/models/fields/related_descriptors.py index d5aa968400..9c50ef16ce 100644 --- a/django/db/models/fields/related_descriptors.py +++ b/django/db/models/fields/related_descriptors.py @@ -646,8 +646,9 @@ def create_reverse_many_to_one_manager(superclass, rel): # Since we just bypassed this class' get_queryset(), we must manage # the reverse relation manually. for rel_obj in queryset: - instance = instances_dict[rel_obj_attr(rel_obj)] - setattr(rel_obj, self.field.name, instance) + if not self.field.is_cached(rel_obj): + instance = instances_dict[rel_obj_attr(rel_obj)] + setattr(rel_obj, self.field.name, instance) cache_name = self.field.remote_field.get_cache_name() return queryset, rel_obj_attr, instance_attr, False, cache_name, False diff --git a/tests/prefetch_related/tests.py b/tests/prefetch_related/tests.py index 4ea7fccd5e..5040f3d886 100644 --- a/tests/prefetch_related/tests.py +++ b/tests/prefetch_related/tests.py @@ -1614,3 +1614,29 @@ class ReadPrefetchedObjectsCacheTests(TestCase): 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