Unverified Kaydet (Commit) 271bfe65 authored tarafından Simon Charette's avatar Simon Charette

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.
üst 081fdaf1
...@@ -25,7 +25,7 @@ from django.db.models.query_utils import ( ...@@ -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): ...@@ -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 ( ...@@ -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): ...@@ -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): ...@@ -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):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment