summaryrefslogtreecommitdiff
path: root/django
diff options
context:
space:
mode:
Diffstat (limited to 'django')
-rw-r--r--django/db/models/fields/related_descriptors.py61
1 files changed, 39 insertions, 22 deletions
diff --git a/django/db/models/fields/related_descriptors.py b/django/db/models/fields/related_descriptors.py
index 56a464f2d2..8da7aaef91 100644
--- a/django/db/models/fields/related_descriptors.py
+++ b/django/db/models/fields/related_descriptors.py
@@ -117,6 +117,18 @@ def _filter_prefetch_queryset(queryset, field_name, instances):
return queryset
+def _traverse_ancestors(model, starting_instance):
+ current_instance = starting_instance
+ while current_instance is not None:
+ ancestor_link = current_instance._meta.get_ancestor_link(model)
+ if not ancestor_link:
+ yield current_instance, None
+ break
+ ancestor = ancestor_link.get_cached_value(current_instance, None)
+ yield current_instance, ancestor
+ current_instance = ancestor
+
+
class ForwardManyToOneDescriptor:
"""
Accessor to the related object on the forward side of a many-to-one or
@@ -228,21 +240,19 @@ class ForwardManyToOneDescriptor:
try:
rel_obj = self.field.get_cached_value(instance)
except KeyError:
+ rel_obj = None
has_value = None not in self.field.get_local_related_value(instance)
- ancestor_link = (
- instance._meta.get_ancestor_link(self.field.model)
- if has_value
- else None
- )
- if ancestor_link and ancestor_link.is_cached(instance):
- # An ancestor link will exist if this field is defined on a
- # multi-table inheritance parent of the instance's class.
- ancestor = ancestor_link.get_cached_value(instance)
- # The value might be cached on an ancestor if the instance
- # originated from walking down the inheritance chain.
- rel_obj = self.field.get_cached_value(ancestor, default=None)
- else:
- rel_obj = None
+ if has_value:
+ model = self.field.model
+ for current_instance, ancestor in _traverse_ancestors(model, instance):
+ if ancestor:
+ # The value might be cached on an ancestor if the
+ # instance originated from walking down the inheritance
+ # chain.
+ rel_obj = self.field.get_cached_value(ancestor, default=None)
+ if rel_obj is not None:
+ break
+
if rel_obj is None and has_value:
rel_obj = self.get_object(instance)
remote_field = self.field.remote_field
@@ -1095,16 +1105,23 @@ def create_forward_many_to_many_manager(superclass, rel, reverse):
return queryset._next_is_sticky().filter(**self.core_filters)
def get_prefetch_cache(self):
- try:
- return self.instance._prefetched_objects_cache[self.prefetch_cache_name]
- except (AttributeError, KeyError):
- return None
+ # Walk up the ancestor-chain (if cached) to try and find a prefetch
+ # in an ancestor.
+ for instance, _ in _traverse_ancestors(rel.field.model, self.instance):
+ try:
+ return instance._prefetched_objects_cache[self.prefetch_cache_name]
+ except (AttributeError, KeyError):
+ pass
+ return None
def _remove_prefetched_objects(self):
- try:
- self.instance._prefetched_objects_cache.pop(self.prefetch_cache_name)
- except (AttributeError, KeyError):
- pass # nothing to clear from cache
+ # Walk up the ancestor-chain (if cached) to try and find a prefetch
+ # in an ancestor.
+ for instance, _ in _traverse_ancestors(rel.field.model, self.instance):
+ try:
+ instance._prefetched_objects_cache.pop(self.prefetch_cache_name)
+ except (AttributeError, KeyError):
+ pass # nothing to clear from cache
def get_queryset(self):
if (cache := self.get_prefetch_cache()) is not None: