diff options
Diffstat (limited to 'django')
| -rw-r--r-- | django/db/models/fields/related_descriptors.py | 61 |
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: |
