diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 1a55d3d417..6bd95f542c 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -764,7 +764,11 @@ class Field(RegisterLookupMixin): if not getattr(cls, self.attname, None): setattr(cls, self.attname, self.descriptor_class(self)) if self.choices is not None: - if not hasattr(cls, 'get_%s_display' % self.name): + # Don't override a get_FOO_display() method defined explicitly on + # this class, but don't check methods derived from inheritance, to + # allow overriding inherited choices. For more complex inheritance + # structures users should override contribute_to_class(). + if 'get_%s_display' % self.name not in cls.__dict__: setattr( cls, 'get_%s_display' % self.name, diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt index 1524ad2fcd..6adcb979ba 100644 --- a/docs/ref/models/instances.txt +++ b/docs/ref/models/instances.txt @@ -834,6 +834,15 @@ Note that in the case of identical date values, these methods will use the primary key as a tie-breaker. This guarantees that no records are skipped or duplicated. That also means you cannot use those methods on unsaved objects. +.. admonition:: Overriding extra instance methods + + In most cases overriding or inheriting ``get_FOO_display()``, + ``get_next_by_FOO()``, and ``get_previous_by_FOO()` should work as + expected. Since they are added by the metaclass however, it is not + practical to account for all possible inheritance structures. In more + complex cases you should override ``Field.contribute_to_class()`` to set + the methods you need. + Other attributes ================ diff --git a/docs/releases/3.0.3.txt b/docs/releases/3.0.3.txt index c2cac9203d..2726e2d3ab 100644 --- a/docs/releases/3.0.3.txt +++ b/docs/releases/3.0.3.txt @@ -31,3 +31,7 @@ Bugfixes :class:`~django.contrib.postgres.aggregates.ArrayAgg` and :class:`~django.contrib.postgres.aggregates.StringAgg` with ``filter`` argument when used in a ``Subquery`` (:ticket:`31097`). + +* Fixed a regression in Django 2.2.7 that caused + :meth:`~django.db.models.Model.get_FOO_display` to work incorrectly when + overriding inherited choices (:ticket:`31124`). diff --git a/tests/model_fields/tests.py b/tests/model_fields/tests.py index a3b805409c..b97c99d42d 100644 --- a/tests/model_fields/tests.py +++ b/tests/model_fields/tests.py @@ -178,6 +178,19 @@ class GetFieldDisplayTests(SimpleTestCase): f = FooBar(foo_bar=1) self.assertEqual(f.get_foo_bar_display(), 'something') + def test_overriding_inherited_FIELD_display(self): + class Base(models.Model): + foo = models.CharField(max_length=254, choices=[('A', 'Base A')]) + + class Meta: + abstract = True + + class Child(Base): + foo = models.CharField(max_length=254, choices=[('A', 'Child A'), ('B', 'Child B')]) + + self.assertEqual(Child(foo='A').get_foo_display(), 'Child A') + self.assertEqual(Child(foo='B').get_foo_display(), 'Child B') + def test_iterator_choices(self): """ get_choices() works with Iterators.