From a96c730431196b119559bbb18a0e85e6ee8b2597 Mon Sep 17 00:00:00 2001 From: Nick Pope Date: Fri, 19 Feb 2021 17:17:11 +0000 Subject: [PATCH] Fixed #32460 -- Allowed "label"/"do_not_call_in_templates" members in model choice enums. --- django/db/models/enums.py | 17 +++++++++++------ tests/model_enums/tests.py | 20 ++++++++++++++++++++ 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/django/db/models/enums.py b/django/db/models/enums.py index f04698b9f6b..7082a397c23 100644 --- a/django/db/models/enums.py +++ b/django/db/models/enums.py @@ -1,4 +1,5 @@ import enum +from types import DynamicClassAttribute from django.utils.functional import Promise @@ -26,12 +27,8 @@ class ChoicesMeta(enum.EnumMeta): # assignment in enum's classdict. dict.__setitem__(classdict, key, value) cls = super().__new__(metacls, classname, bases, classdict, **kwds) - cls._value2label_map_ = dict(zip(cls._value2member_map_, labels)) - # Add a label property to instances of enum which uses the enum member - # that is passed in as "self" as the value to use when looking up the - # label in the choices. - cls.label = property(lambda self: cls._value2label_map_.get(self.value)) - cls.do_not_call_in_templates = True + for member, label in zip(cls.__members__.values(), labels): + member._label_ = label return enum.unique(cls) def __contains__(cls, member): @@ -62,6 +59,14 @@ class ChoicesMeta(enum.EnumMeta): class Choices(enum.Enum, metaclass=ChoicesMeta): """Class for creating enumerated choices.""" + @DynamicClassAttribute + def label(self): + return self._label_ + + @property + def do_not_call_in_templates(self): + return True + def __str__(self): """ Use value when cast to str, so that Choices set as model instance diff --git a/tests/model_enums/tests.py b/tests/model_enums/tests.py index 3bfec1596ab..78f8b146be9 100644 --- a/tests/model_enums/tests.py +++ b/tests/model_enums/tests.py @@ -159,6 +159,26 @@ class ChoicesTests(SimpleTestCase): with self.assertRaises(AttributeError): models.TextChoices('Properties', 'choices labels names values') + def test_label_member(self): + # label can be used as a member. + Stationery = models.TextChoices('Stationery', 'label stamp sticker') + self.assertEqual(Stationery.label.label, 'Label') + self.assertEqual(Stationery.label.value, 'label') + self.assertEqual(Stationery.label.name, 'label') + + def test_do_not_call_in_templates_member(self): + # do_not_call_in_templates is not implicitly treated as a member. + Special = models.IntegerChoices('Special', 'do_not_call_in_templates') + self.assertEqual( + Special.do_not_call_in_templates.label, + 'Do Not Call In Templates', + ) + self.assertEqual(Special.do_not_call_in_templates.value, 1) + self.assertEqual( + Special.do_not_call_in_templates.name, + 'do_not_call_in_templates', + ) + class Separator(bytes, models.Choices): FS = b'\x1c', 'File Separator'