#36282: Prefetched relations are not used from parent classes in all cases
-------------------------------------+-------------------------------------
Reporter: Take Weiland | Owner: (none)
Type: Bug | Status: new
Component: Database layer | Version: 5.1
(models, ORM) |
Severity: Normal | Resolution:
Keywords: mti | Triage Stage: Accepted
prefetch_related |
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-------------------------------------+-------------------------------------
Changes (by Simon Charette):
* component: Uncategorized => Database layer (models, ORM)
* keywords: => mti prefetch_related
* stage: Unreviewed => Accepted
* type: Uncategorized => Bug
Comment:
Interestingly I ran into the same issue [
https://github.com/charettes
/django-
polymodels/pull/45/commits/cdb4c54f402619ff91f0559f4e5af14810e434fa
implementing similar logic 5 years ago].
If you're interested in solving this issue I'd have a few pointers
1. Avoid copy'ing `_prefetched_objects_cache` related cache between model
instances as it will make cache invalidation a nightmare (the workaround I
linked above is flawed in this regard). The logic should be adapted to
fully walk the ancestor chain. In other words, the cached value should
remain solely on the model hosting the field and subclasses accessors
should retrieve them from there.
2. When adding tests to `tests/prefetch_related` reuse the existing
models. It seems that `AuthorWithAge` is a good candidate for subclassing.
3. The logic should always ensure that ancestor links are cached before
accessing their parent (it's not always the case when field deferral is
used)
Here's a little patch I used to validate your report if it can be handy
{{{#!diff
diff --git a/django/db/models/fields/related_descriptors.py
b/django/db/models/fields/related_descriptors.py
index 77ebe798b4..36e8798340 100644
--- a/django/db/models/fields/related_descriptors.py
+++ b/django/db/models/fields/related_descriptors.py
@@ -226,20 +226,23 @@ def __get__(self, instance, cls=None):
rel_obj = self.field.get_cached_value(instance)
except KeyError:
has_value = None not in
self.field.get_local_related_value(instance)
+ field_model = self.field.model
ancestor_link = (
- instance._meta.get_ancestor_link(self.field.model)
- if has_value
- else None
+ instance._meta.get_ancestor_link(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
+ rel_obj = None
+ # An ancestor link will exist if this field is defined on a
+ # multi-table inheritance parent of the instance's class.
+ if ancestor_link:
+ ancestor = instance
+ while ancestor_link.is_cached(ancestor):
+ ancestor = ancestor_link.get_cached_value(ancestor)
+ if type(ancestor) is field_model:
+ # 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:
+ ancestor_link =
ancestor._meta.get_ancestor_link(field_model)
if rel_obj is None and has_value:
rel_obj = self.get_object(instance)
remote_field = self.field.remote_field
}}}
--
Ticket URL: <
https://code.djangoproject.com/ticket/36282#comment:2>
Django <
https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.