summaryrefslogtreecommitdiff
path: root/django/db
diff options
context:
space:
mode:
authorAlexey Nigin <nigin@umich.edu>2020-12-13 19:02:45 -0500
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2020-12-16 10:52:59 +0100
commitb9ba85a7ce582f4821a21119a6c130dacab496c4 (patch)
treec6f64907e268a5c3e13a640f59da690d3e5842fd /django/db
parentbebd4cfa8f5e0d2dff2de5e50d86e849a40f4bb2 (diff)
Fixed #32089 -- Fixed prefetch_related_objects() when some objects are already fetched.
Thanks Dennis Kliban for the report and Adam Johnson for the initial patch. Co-authored-by: Adam Johnson <me@adamj.eu>
Diffstat (limited to 'django/db')
-rw-r--r--django/db/models/query.py36
1 files changed, 26 insertions, 10 deletions
diff --git a/django/db/models/query.py b/django/db/models/query.py
index 7956b1695e..9dc98c02d1 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -1720,8 +1720,17 @@ def prefetch_related_objects(model_instances, *related_lookups):
"prefetching - this is an invalid parameter to "
"prefetch_related()." % lookup.prefetch_through)
- if prefetcher is not None and not is_fetched:
- obj_list, additional_lookups = prefetch_one_level(obj_list, prefetcher, lookup, level)
+ obj_to_fetch = None
+ if prefetcher is not None:
+ obj_to_fetch = [obj for obj in obj_list if not is_fetched(obj)]
+
+ if obj_to_fetch:
+ obj_list, additional_lookups = prefetch_one_level(
+ obj_to_fetch,
+ prefetcher,
+ lookup,
+ level,
+ )
# We need to ensure we don't keep adding lookups from the
# same relationships to stop infinite recursion. So, if we
# are already on an automatically added lookup, don't add
@@ -1771,10 +1780,14 @@ def get_prefetcher(instance, through_attr, to_attr):
(the object with get_prefetch_queryset (or None),
the descriptor object representing this relationship (or None),
a boolean that is False if the attribute was not found at all,
- a boolean that is True if the attribute has already been fetched)
+ a function that takes an instance and returns a boolean that is True if
+ the attribute has already been fetched for that instance)
"""
+ def has_to_attr_attribute(instance):
+ return hasattr(instance, to_attr)
+
prefetcher = None
- is_fetched = False
+ is_fetched = has_to_attr_attribute
# For singly related objects, we have to avoid getting the attribute
# from the object, as this will trigger the query. So we first try
@@ -1789,8 +1802,7 @@ def get_prefetcher(instance, through_attr, to_attr):
# get_prefetch_queryset() method.
if hasattr(rel_obj_descriptor, 'get_prefetch_queryset'):
prefetcher = rel_obj_descriptor
- if rel_obj_descriptor.is_cached(instance):
- is_fetched = True
+ is_fetched = rel_obj_descriptor.is_cached
else:
# descriptor doesn't support prefetching, so we go ahead and get
# the attribute on the instance rather than the class to
@@ -1802,11 +1814,15 @@ def get_prefetcher(instance, through_attr, to_attr):
# Special case cached_property instances because hasattr
# triggers attribute computation and assignment.
if isinstance(getattr(instance.__class__, to_attr, None), cached_property):
- is_fetched = to_attr in instance.__dict__
- else:
- is_fetched = hasattr(instance, to_attr)
+ def has_cached_property(instance):
+ return to_attr in instance.__dict__
+
+ is_fetched = has_cached_property
else:
- is_fetched = through_attr in instance._prefetched_objects_cache
+ def in_prefetched_cache(instance):
+ return through_attr in instance._prefetched_objects_cache
+
+ is_fetched = in_prefetched_cache
return prefetcher, rel_obj_descriptor, attr_found, is_fetched