1
0
mirror of https://github.com/django/django.git synced 2025-10-28 08:06:09 +00:00

Fixed #33651 -- Added support for prefetching GenericForeignKey.

Co-authored-by: revanthgss <revanthgss@almabase.com>
Co-authored-by: Mariusz Felisiak <felisiak.mariusz@gmail.com>
This commit is contained in:
Clément Escolano
2023-08-01 23:31:40 +02:00
committed by Mariusz Felisiak
parent 190874eadd
commit cac94dd8aa
15 changed files with 473 additions and 42 deletions

View File

@@ -33,6 +33,7 @@ from django.db.models.utils import (
resolve_callables,
)
from django.utils import timezone
from django.utils.deprecation import RemovedInDjango60Warning
from django.utils.functional import cached_property, partition
# The maximum number of results to fetch in a get() query.
@@ -2236,8 +2237,21 @@ class Prefetch:
return to_attr, as_attr
def get_current_queryset(self, level):
if self.get_current_prefetch_to(level) == self.prefetch_to:
return self.queryset
warnings.warn(
"Prefetch.get_current_queryset() is deprecated. Use "
"get_current_querysets() instead.",
RemovedInDjango60Warning,
stacklevel=2,
)
querysets = self.get_current_querysets(level)
return querysets[0] if querysets is not None else None
def get_current_querysets(self, level):
if (
self.get_current_prefetch_to(level) == self.prefetch_to
and self.queryset is not None
):
return [self.queryset]
return None
def __eq__(self, other):
@@ -2425,9 +2439,9 @@ async def aprefetch_related_objects(model_instances, *related_lookups):
def get_prefetcher(instance, through_attr, to_attr):
"""
For the attribute 'through_attr' on the given instance, find
an object that has a get_prefetch_queryset().
an object that has a get_prefetch_querysets().
Return a 4 tuple containing:
(the object with get_prefetch_queryset (or None),
(the object with get_prefetch_querysets (or None),
the descriptor object representing this relationship (or None),
a boolean that is False if the attribute was not found at all,
a function that takes an instance and returns a boolean that is True if
@@ -2462,8 +2476,12 @@ def get_prefetcher(instance, through_attr, to_attr):
attr_found = True
if rel_obj_descriptor:
# singly related object, descriptor object has the
# get_prefetch_queryset() method.
if hasattr(rel_obj_descriptor, "get_prefetch_queryset"):
# get_prefetch_querysets() method.
if (
hasattr(rel_obj_descriptor, "get_prefetch_querysets")
# RemovedInDjango60Warning.
or hasattr(rel_obj_descriptor, "get_prefetch_queryset")
):
prefetcher = rel_obj_descriptor
# If to_attr is set, check if the value has already been set,
# which is done with has_to_attr_attribute(). Do not use the
@@ -2476,7 +2494,11 @@ def get_prefetcher(instance, through_attr, to_attr):
# the attribute on the instance rather than the class to
# support many related managers
rel_obj = getattr(instance, through_attr)
if hasattr(rel_obj, "get_prefetch_queryset"):
if (
hasattr(rel_obj, "get_prefetch_querysets")
# RemovedInDjango60Warning.
or hasattr(rel_obj, "get_prefetch_queryset")
):
prefetcher = rel_obj
if through_attr == to_attr:
@@ -2497,7 +2519,7 @@ def prefetch_one_level(instances, prefetcher, lookup, level):
Return the prefetched objects along with any additional prefetches that
must be done due to prefetch_related lookups found from default managers.
"""
# prefetcher must have a method get_prefetch_queryset() which takes a list
# prefetcher must have a method get_prefetch_querysets() which takes a list
# of instances, and returns a tuple:
# (queryset of instances of self.model that are related to passed in instances,
@@ -2510,14 +2532,34 @@ def prefetch_one_level(instances, prefetcher, lookup, level):
# The 'values to be matched' must be hashable as they will be used
# in a dictionary.
(
rel_qs,
rel_obj_attr,
instance_attr,
single,
cache_name,
is_descriptor,
) = prefetcher.get_prefetch_queryset(instances, lookup.get_current_queryset(level))
if hasattr(prefetcher, "get_prefetch_querysets"):
(
rel_qs,
rel_obj_attr,
instance_attr,
single,
cache_name,
is_descriptor,
) = prefetcher.get_prefetch_querysets(
instances, lookup.get_current_querysets(level)
)
else:
warnings.warn(
"The usage of get_prefetch_queryset() in prefetch_related_objects() is "
"deprecated. Implement get_prefetch_querysets() instead.",
RemovedInDjango60Warning,
stacklevel=2,
)
(
rel_qs,
rel_obj_attr,
instance_attr,
single,
cache_name,
is_descriptor,
) = prefetcher.get_prefetch_queryset(
instances, lookup.get_current_querysets(level)
)
# 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