mirror of
https://github.com/django/django.git
synced 2024-12-22 09:05:43 +00:00
Fixed #34791 -- Fixed incorrect Prefetch()'s cache for singly related objects.
Changed the cache name used for singly related objects to be the to_attr parameter passed to a Prefetch object. This fixes issues with checking if values have already been fetched in cases where the Field already has some prefetched value, but not for the same model attr.
This commit is contained in:
parent
f333e3513e
commit
254df3a3bb
1
AUTHORS
1
AUTHORS
@ -682,6 +682,7 @@ answer newbie questions, and generally made Django that much better:
|
||||
Max Derkachev <mderk@yandex.ru>
|
||||
Max Smolens <msmolens@gmail.com>
|
||||
Maxime Lorant <maxime.lorant@gmail.com>
|
||||
Maxime Toussaint <m.toussaint@mail.com>
|
||||
Maxime Turcotte <maxocub@riseup.net>
|
||||
Maximilian Merz <django@mxmerz.de>
|
||||
Maximillian Dornseif <md@hudora.de>
|
||||
|
@ -2434,11 +2434,23 @@ def get_prefetcher(instance, through_attr, to_attr):
|
||||
the attribute has already been fetched for that instance)
|
||||
"""
|
||||
|
||||
def has_to_attr_attribute(instance):
|
||||
return hasattr(instance, to_attr)
|
||||
def is_to_attr_fetched(model, to_attr):
|
||||
# Special case cached_property instances because hasattr() triggers
|
||||
# attribute computation and assignment.
|
||||
if isinstance(getattr(model, to_attr, None), cached_property):
|
||||
|
||||
def has_cached_property(instance):
|
||||
return to_attr in instance.__dict__
|
||||
|
||||
return has_cached_property
|
||||
|
||||
def has_to_attr_attribute(instance):
|
||||
return hasattr(instance, to_attr)
|
||||
|
||||
return has_to_attr_attribute
|
||||
|
||||
prefetcher = None
|
||||
is_fetched = has_to_attr_attribute
|
||||
is_fetched = is_to_attr_fetched(instance.__class__, to_attr)
|
||||
|
||||
# For singly related objects, we have to avoid getting the attribute
|
||||
# from the object, as this will trigger the query. So we first try
|
||||
@ -2453,7 +2465,12 @@ def get_prefetcher(instance, through_attr, to_attr):
|
||||
# get_prefetch_queryset() method.
|
||||
if hasattr(rel_obj_descriptor, "get_prefetch_queryset"):
|
||||
prefetcher = rel_obj_descriptor
|
||||
is_fetched = rel_obj_descriptor.is_cached
|
||||
# If to_attr is set, check if the value has already been set,
|
||||
# which is done with has_to_attr_attribute(). Do not use the
|
||||
# method from the descriptor, as the cache_name it defines
|
||||
# checks the field name, not the to_attr value.
|
||||
if through_attr == to_attr:
|
||||
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
|
||||
@ -2461,18 +2478,7 @@ def get_prefetcher(instance, through_attr, to_attr):
|
||||
rel_obj = getattr(instance, through_attr)
|
||||
if hasattr(rel_obj, "get_prefetch_queryset"):
|
||||
prefetcher = rel_obj
|
||||
if 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
|
||||
):
|
||||
|
||||
def has_cached_property(instance):
|
||||
return to_attr in instance.__dict__
|
||||
|
||||
is_fetched = has_cached_property
|
||||
else:
|
||||
if through_attr == to_attr:
|
||||
|
||||
def in_prefetched_cache(instance):
|
||||
return through_attr in instance._prefetched_objects_cache
|
||||
|
@ -978,6 +978,31 @@ class CustomPrefetchTests(TestCase):
|
||||
with self.assertNumQueries(5):
|
||||
self.traverse_qs(list(houses), [["occupants", "houses", "main_room"]])
|
||||
|
||||
def test_nested_prefetch_related_with_duplicate_prefetch_and_depth(self):
|
||||
people = Person.objects.prefetch_related(
|
||||
Prefetch(
|
||||
"houses__main_room",
|
||||
queryset=Room.objects.filter(name="Dining room"),
|
||||
to_attr="dining_room",
|
||||
),
|
||||
"houses__main_room",
|
||||
)
|
||||
with self.assertNumQueries(4):
|
||||
main_room = people[0].houses.all()[0]
|
||||
|
||||
people = Person.objects.prefetch_related(
|
||||
"houses__main_room",
|
||||
Prefetch(
|
||||
"houses__main_room",
|
||||
queryset=Room.objects.filter(name="Dining room"),
|
||||
to_attr="dining_room",
|
||||
),
|
||||
)
|
||||
with self.assertNumQueries(4):
|
||||
main_room = people[0].houses.all()[0]
|
||||
|
||||
self.assertEqual(main_room.main_room, self.room1_1)
|
||||
|
||||
def test_values_queryset(self):
|
||||
msg = "Prefetch querysets cannot use raw(), values(), and values_list()."
|
||||
with self.assertRaisesMessage(ValueError, msg):
|
||||
|
Loading…
Reference in New Issue
Block a user