Fixed #26916 -- Fixed prefetch_related when using a cached_property as to_attr.
Thanks Trac alias karyon for the report and Tim for the review.
This commit is contained in:
parent
081fdaf110
commit
271bfe65d9
|
@ -25,7 +25,7 @@ from django.db.models.query_utils import (
|
||||||
from django.db.models.sql.constants import CURSOR
|
from django.db.models.sql.constants import CURSOR
|
||||||
from django.utils import six, timezone
|
from django.utils import six, timezone
|
||||||
from django.utils.deprecation import RemovedInDjango20Warning
|
from django.utils.deprecation import RemovedInDjango20Warning
|
||||||
from django.utils.functional import partition
|
from django.utils.functional import cached_property, partition
|
||||||
from django.utils.version import get_version
|
from django.utils.version import get_version
|
||||||
|
|
||||||
# The maximum number of items to display in a QuerySet.__repr__
|
# The maximum number of items to display in a QuerySet.__repr__
|
||||||
|
@ -1545,7 +1545,12 @@ def get_prefetcher(instance, through_attr, to_attr):
|
||||||
if hasattr(rel_obj, 'get_prefetch_queryset'):
|
if hasattr(rel_obj, 'get_prefetch_queryset'):
|
||||||
prefetcher = rel_obj
|
prefetcher = rel_obj
|
||||||
if through_attr != to_attr:
|
if through_attr != to_attr:
|
||||||
is_fetched = hasattr(instance, to_attr)
|
# Special case cached_property instances because hasattr
|
||||||
|
# triggers attribute computation and assignment.
|
||||||
|
if isinstance(getattr(instance.__class__, to_attr, None), cached_property):
|
||||||
|
is_fetched = to_attr in instance.__dict__
|
||||||
|
else:
|
||||||
|
is_fetched = hasattr(instance, to_attr)
|
||||||
else:
|
else:
|
||||||
is_fetched = through_attr in instance._prefetched_objects_cache
|
is_fetched = through_attr in instance._prefetched_objects_cache
|
||||||
return prefetcher, rel_obj_descriptor, attr_found, is_fetched
|
return prefetcher, rel_obj_descriptor, attr_found, is_fetched
|
||||||
|
|
|
@ -6,6 +6,7 @@ from django.contrib.contenttypes.fields import (
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
from django.utils.encoding import python_2_unicode_compatible
|
||||||
|
from django.utils.functional import cached_property
|
||||||
|
|
||||||
|
|
||||||
# Basic tests
|
# Basic tests
|
||||||
|
@ -219,6 +220,10 @@ class Person(models.Model):
|
||||||
def all_houses(self):
|
def all_houses(self):
|
||||||
return list(self.houses.all())
|
return list(self.houses.all())
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def cached_all_houses(self):
|
||||||
|
return self.all_houses
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ['id']
|
ordering = ['id']
|
||||||
|
|
||||||
|
|
|
@ -743,6 +743,17 @@ class CustomPrefetchTests(TestCase):
|
||||||
).get(pk=self.house3.pk)
|
).get(pk=self.house3.pk)
|
||||||
self.assertIsInstance(house.rooms.all(), QuerySet)
|
self.assertIsInstance(house.rooms.all(), QuerySet)
|
||||||
|
|
||||||
|
def test_to_attr_cached_property(self):
|
||||||
|
persons = Person.objects.prefetch_related(
|
||||||
|
Prefetch('houses', House.objects.all(), to_attr='cached_all_houses'),
|
||||||
|
)
|
||||||
|
for person in persons:
|
||||||
|
# To bypass caching at the related descriptor level, don't use
|
||||||
|
# person.houses.all() here.
|
||||||
|
all_houses = list(House.objects.filter(occupants=person))
|
||||||
|
with self.assertNumQueries(0):
|
||||||
|
self.assertEqual(person.cached_all_houses, all_houses)
|
||||||
|
|
||||||
|
|
||||||
class DefaultManagerTests(TestCase):
|
class DefaultManagerTests(TestCase):
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue