diff --git a/django/db/models/options.py b/django/db/models/options.py index 82d96b739c..90462278d9 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -1,4 +1,5 @@ import copy +import inspect import warnings from bisect import bisect from collections import OrderedDict, defaultdict @@ -829,7 +830,9 @@ class Options: @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): + attr = inspect.getattr_static(self.model, name) + 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 752bf4fc67..b62fff01bd 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']))