From 296b75a3c0309a936a6c07d8f711f722e3b96e63 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Fri, 6 Oct 2023 20:18:41 +0200 Subject: [PATCH] Fixed #34889 -- Fixed get_prefetch_queryset() fallback in prefetch_one_level(). Thanks Matt Westcott for the report. Regression in cac94dd8aa2fb49cd2e06b5b37cf039257284bb0. --- django/db/models/query.py | 7 +++--- tests/prefetch_related/tests.py | 44 +++++++++++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/django/db/models/query.py b/django/db/models/query.py index 1125302933..de00bba8d7 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -2550,6 +2550,9 @@ def prefetch_one_level(instances, prefetcher, lookup, level): RemovedInDjango60Warning, stacklevel=2, ) + queryset = None + if querysets := lookup.get_current_querysets(level): + queryset = querysets[0] ( rel_qs, rel_obj_attr, @@ -2557,9 +2560,7 @@ def prefetch_one_level(instances, prefetcher, lookup, level): single, cache_name, is_descriptor, - ) = prefetcher.get_prefetch_queryset( - instances, lookup.get_current_querysets(level) - ) + ) = prefetcher.get_prefetch_queryset(instances, queryset) # We have to handle the possibility that the QuerySet we just got back # contains some prefetch_related lookups. We don't want to trigger the # prefetch_related functionality by evaluating the query. Rather, we need diff --git a/tests/prefetch_related/tests.py b/tests/prefetch_related/tests.py index dd56664f68..08f87d55de 100644 --- a/tests/prefetch_related/tests.py +++ b/tests/prefetch_related/tests.py @@ -4,10 +4,12 @@ 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.fields.related import ForwardManyToOneDescriptor +from django.db.models.query import get_prefetcher, prefetch_one_level from django.db.models.sql import Query from django.test import ( TestCase, + ignore_warnings, override_settings, skipIfDBFeature, skipUnlessDBFeature, @@ -1997,7 +1999,7 @@ class PrefetchLimitTests(TestDataMixin, TestCase): list(Book.objects.prefetch_related(Prefetch("authors", authors[1:]))) -class GetCurrentQuerySetDeprecation(TestCase): +class DeprecationTests(TestCase): def test_get_current_queryset_warning(self): msg = ( "Prefetch.get_current_queryset() is deprecated. Use " @@ -2011,3 +2013,41 @@ class GetCurrentQuerySetDeprecation(TestCase): ) with self.assertWarnsMessage(RemovedInDjango60Warning, msg): self.assertIsNone(Prefetch("authors").get_current_queryset(1)) + + @ignore_warnings(category=RemovedInDjango60Warning) + def test_prefetch_one_level_fallback(self): + class NoGetPrefetchQuerySetsDescriptor(ForwardManyToOneDescriptor): + def get_prefetch_queryset(self, instances, queryset=None): + if queryset is None: + return super().get_prefetch_querysets(instances) + return super().get_prefetch_querysets(instances, [queryset]) + + def __getattribute__(self, name): + if name == "get_prefetch_querysets": + raise AttributeError + return super().__getattribute__(name) + + house = House.objects.create() + room = Room.objects.create(house=house) + house.main_room = room + house.save() + + # prefetch_one_level() fallbacks to get_prefetch_queryset(). + prefetcher = NoGetPrefetchQuerySetsDescriptor(Room._meta.get_field("house")) + obj_list, additional_lookups = prefetch_one_level( + [room], + prefetcher, + Prefetch("house", House.objects.all()), + 0, + ) + self.assertEqual(obj_list, [house]) + self.assertEqual(additional_lookups, []) + + obj_list, additional_lookups = prefetch_one_level( + [room], + prefetcher, + Prefetch("house"), + 0, + ) + self.assertEqual(obj_list, [house]) + self.assertEqual(additional_lookups, [])