#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.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/django-updates/01070195e9ed6a10-879fbf62-6506-4f35-85d2-be1441710c18-000000%40eu-central-1.amazonses.com.

Reply via email to