From b7d6077517c6cb2daa5e5faf2ae9f94698c06ca9 Mon Sep 17 00:00:00 2001
From: Adam Johnson <me@adamj.eu>
Date: Sun, 4 Jun 2017 22:58:24 +0100
Subject: [PATCH] [1.11.x] Fixed #28269 -- Fixed Model.__init__() crash on
 models with a field that has an instance only descriptor.

Regression in d2a26c1a90e837777dabdf3d67ceec4d2a70fb86.

Backport of ed244199c72f5bbf33ab4547e06e69873d7271d0 from master
---
 django/db/models/options.py | 14 ++++++++++----
 docs/releases/1.11.3.txt    |  3 +++
 tests/model_meta/models.py  |  9 +++++++++
 tests/model_meta/tests.py   |  2 ++
 4 files changed, 24 insertions(+), 4 deletions(-)

diff --git a/django/db/models/options.py b/django/db/models/options.py
index 56a7e87b70..71b80b8abb 100644
--- a/django/db/models/options.py
+++ b/django/db/models/options.py
@@ -882,7 +882,13 @@ class Options(object):
     @cached_property
     def _property_names(self):
         """Return a set of the names of the properties defined on the model."""
-        return frozenset({
-            attr for attr in
-            dir(self.model) if isinstance(getattr(self.model, attr), property)
-        })
+        names = []
+        for name in dir(self.model):
+            try:
+                attr = getattr(self.model, name)
+            except AttributeError:
+                pass
+            else:
+                if isinstance(attr, property):
+                    names.append(name)
+        return frozenset(names)
diff --git a/docs/releases/1.11.3.txt b/docs/releases/1.11.3.txt
index 15200fafd5..ed2bf31cf5 100644
--- a/docs/releases/1.11.3.txt
+++ b/docs/releases/1.11.3.txt
@@ -12,3 +12,6 @@ Bugfixes
 * Removed an incorrect deprecation warning about a missing ``renderer``
   argument if a ``Widget.render()`` method accepts ``**kwargs``
   (:ticket:`28265`).
+
+* Fixed a regression causing ``Model.__init__()`` to crash if a field has an
+  instance only descriptor (:ticket:`28269`).
diff --git a/tests/model_meta/models.py b/tests/model_meta/models.py
index 882ac2c9fd..bd7e7f1889 100644
--- a/tests/model_meta/models.py
+++ b/tests/model_meta/models.py
@@ -9,6 +9,13 @@ class Relation(models.Model):
     pass
 
 
+class InstanceOnlyDescriptor(object):
+    def __get__(self, instance, cls=None):
+        if instance is None:
+            raise AttributeError('Instance only')
+        return 1
+
+
 class AbstractPerson(models.Model):
     # DATA fields
     data_abstract = models.CharField(max_length=10)
@@ -43,6 +50,8 @@ class AbstractPerson(models.Model):
     def test_property(self):
         return 1
 
+    test_instance_only_descriptor = InstanceOnlyDescriptor()
+
 
 class BasePerson(AbstractPerson):
     # DATA fields
diff --git a/tests/model_meta/tests.py b/tests/model_meta/tests.py
index 265e180331..1b71c95b3e 100644
--- a/tests/model_meta/tests.py
+++ b/tests/model_meta/tests.py
@@ -276,4 +276,6 @@ class ParentListTests(SimpleTestCase):
 
 class PropertyNamesTests(SimpleTestCase):
     def test_person(self):
+        # Instance only descriptors don't appear in _property_names.
+        self.assertEqual(AbstractPerson().test_instance_only_descriptor, 1)
         self.assertEqual(AbstractPerson._meta._property_names, frozenset(['pk', 'test_property']))