Fixed #26916 -- Fixed prefetch_related when using a cached_property as to_attr.
diff --git a/django/db/models/query.py b/django/db/models/query.py
index cff7135ef6e8e..05f6ed370d6df 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -25,7 +25,7 @@
 from django.db.models.sql.constants import CURSOR
 from django.utils import six, timezone
 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
 
 # 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'):
                     prefetcher = rel_obj
                 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:
                     is_fetched = through_attr in instance._prefetched_objects_cache
     return prefetcher, rel_obj_descriptor, attr_found, is_fetched
diff --git a/tests/prefetch_related/models.py b/tests/prefetch_related/models.py
index 32570e9109da1..064ce1dfbdac7 100644
--- a/tests/prefetch_related/models.py
+++ b/tests/prefetch_related/models.py
@@ -6,6 +6,7 @@
 from django.contrib.contenttypes.models import ContentType
 from django.db import models
 from django.utils.encoding import python_2_unicode_compatible
+from django.utils.functional import cached_property
 
 
 # Basic tests
@@ -219,6 +220,10 @@ def primary_house(self):
     def all_houses(self):
         return list(self.houses.all())
 
+    @cached_property
+    def cached_all_houses(self):
+        return self.all_houses
+
     class Meta:
         ordering = ['id']
 
diff --git a/tests/prefetch_related/tests.py b/tests/prefetch_related/tests.py
index c34682a33d87a..7c36975084e78 100644
--- a/tests/prefetch_related/tests.py
+++ b/tests/prefetch_related/tests.py
@@ -743,6 +743,17 @@ def test_to_attr_doesnt_cache_through_attr_as_list(self):
         ).get(pk=self.house3.pk)
         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):
 

Thanks Trac alias karyon for the report.
