summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNick Pope <nick@nickpope.me.uk>2023-08-23 10:58:17 +0100
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2023-09-13 08:30:21 +0200
commitfe19b33e2f76045617067dd5123041ae4d3a91ee (patch)
tree4246376602ed64f24ae4a4a2537ce5f0f88cc8a5
parent170b0a47b09d735132bcfdcc9b303397eb03b34b (diff)
Modernized enumeration helpers on Python 3.11+.
- use @enum.property https://docs.python.org/3/library/enum.html#enum.property - use @enum.nonmember Using @property on an enum class does not yield the expected result. do_not_call_in_templates attribute works because a @property instance is truthy. We can make this a literal True value as expected by using @enum.nonmember in Python 3.11+. https://docs.python.org/3/library/enum.html#enum.nonmember - used enum.IntEnum/StrEnum Python 3.11+ has ReprEnum which uses int.__str__() and str.__str__() for __str__() in the `IntEnum` and `StrEnum` subclasses. We can emulate that for Python < 3.11. https://docs.python.org/3/library/enum.html#enum.ReprEnum https://docs.python.org/3/library/enum.html#enum.IntEnum https://docs.python.org/3/library/enum.html#enum.StrEnum
-rw-r--r--django/db/models/enums.py41
-rw-r--r--tests/model_enums/tests.py12
2 files changed, 37 insertions, 16 deletions
diff --git a/django/db/models/enums.py b/django/db/models/enums.py
index 24bec6b87b..bdc669bff0 100644
--- a/django/db/models/enums.py
+++ b/django/db/models/enums.py
@@ -1,15 +1,27 @@
import enum
import warnings
-from types import DynamicClassAttribute
from django.utils.deprecation import RemovedInDjango60Warning
from django.utils.functional import Promise
from django.utils.version import PY311, PY312
if PY311:
- from enum import EnumType
+ from enum import EnumType, IntEnum, StrEnum
+ from enum import property as enum_property
else:
from enum import EnumMeta as EnumType
+ from types import DynamicClassAttribute as enum_property
+
+ class ReprEnum(enum.Enum):
+ def __str__(self):
+ return str(self.value)
+
+ class IntEnum(int, ReprEnum):
+ pass
+
+ class StrEnum(str, ReprEnum):
+ pass
+
__all__ = ["Choices", "IntegerChoices", "TextChoices"]
@@ -69,33 +81,30 @@ class ChoicesType(EnumType):
class Choices(enum.Enum, metaclass=ChoicesType):
"""Class for creating enumerated choices."""
- @DynamicClassAttribute
- def label(self):
- return self._label_
+ if PY311:
+ do_not_call_in_templates = enum.nonmember(True)
+ else:
- @property
- def do_not_call_in_templates(self):
- return True
+ @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
- attributes are rendered as expected in templates and similar contexts.
- """
- return str(self.value)
+ @enum_property
+ def label(self):
+ return self._label_
# A similar format was proposed for Python 3.10.
def __repr__(self):
return f"{self.__class__.__qualname__}.{self._name_}"
-class IntegerChoices(int, Choices):
+class IntegerChoices(Choices, IntEnum):
"""Class for creating enumerated integer choices."""
pass
-class TextChoices(str, Choices):
+class TextChoices(Choices, StrEnum):
"""Class for creating enumerated string choices."""
@staticmethod
diff --git a/tests/model_enums/tests.py b/tests/model_enums/tests.py
index 264a4b1703..32f0ad3668 100644
--- a/tests/model_enums/tests.py
+++ b/tests/model_enums/tests.py
@@ -9,6 +9,7 @@ from django.test import SimpleTestCase
from django.utils.deprecation import RemovedInDjango60Warning
from django.utils.functional import Promise
from django.utils.translation import gettext_lazy as _
+from django.utils.version import PY311
class Suit(models.IntegerChoices):
@@ -187,6 +188,7 @@ class ChoicesTests(SimpleTestCase):
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.assertIn("do_not_call_in_templates", Special.__members__)
self.assertEqual(
Special.do_not_call_in_templates.label,
"Do Not Call In Templates",
@@ -197,6 +199,16 @@ class ChoicesTests(SimpleTestCase):
"do_not_call_in_templates",
)
+ def test_do_not_call_in_templates_nonmember(self):
+ self.assertNotIn("do_not_call_in_templates", Suit.__members__)
+ if PY311:
+ self.assertIs(Suit.do_not_call_in_templates, True)
+ else:
+ # Using @property on an enum does not behave as expected.
+ self.assertTrue(Suit.do_not_call_in_templates)
+ self.assertIsNot(Suit.do_not_call_in_templates, True)
+ self.assertIsInstance(Suit.do_not_call_in_templates, property)
+
class Separator(bytes, models.Choices):
FS = b"\x1c", "File Separator"