mirror of
https://github.com/django/django.git
synced 2024-12-22 17:16:24 +00:00
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
This commit is contained in:
parent
170b0a47b0
commit
fe19b33e2f
@ -1,15 +1,27 @@
|
|||||||
import enum
|
import enum
|
||||||
import warnings
|
import warnings
|
||||||
from types import DynamicClassAttribute
|
|
||||||
|
|
||||||
from django.utils.deprecation import RemovedInDjango60Warning
|
from django.utils.deprecation import RemovedInDjango60Warning
|
||||||
from django.utils.functional import Promise
|
from django.utils.functional import Promise
|
||||||
from django.utils.version import PY311, PY312
|
from django.utils.version import PY311, PY312
|
||||||
|
|
||||||
if PY311:
|
if PY311:
|
||||||
from enum import EnumType
|
from enum import EnumType, IntEnum, StrEnum
|
||||||
|
from enum import property as enum_property
|
||||||
else:
|
else:
|
||||||
from enum import EnumMeta as EnumType
|
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"]
|
__all__ = ["Choices", "IntegerChoices", "TextChoices"]
|
||||||
|
|
||||||
@ -69,33 +81,30 @@ class ChoicesType(EnumType):
|
|||||||
class Choices(enum.Enum, metaclass=ChoicesType):
|
class Choices(enum.Enum, metaclass=ChoicesType):
|
||||||
"""Class for creating enumerated choices."""
|
"""Class for creating enumerated choices."""
|
||||||
|
|
||||||
@DynamicClassAttribute
|
if PY311:
|
||||||
|
do_not_call_in_templates = enum.nonmember(True)
|
||||||
|
else:
|
||||||
|
|
||||||
|
@property
|
||||||
|
def do_not_call_in_templates(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
@enum_property
|
||||||
def label(self):
|
def label(self):
|
||||||
return self._label_
|
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
|
|
||||||
attributes are rendered as expected in templates and similar contexts.
|
|
||||||
"""
|
|
||||||
return str(self.value)
|
|
||||||
|
|
||||||
# A similar format was proposed for Python 3.10.
|
# A similar format was proposed for Python 3.10.
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"{self.__class__.__qualname__}.{self._name_}"
|
return f"{self.__class__.__qualname__}.{self._name_}"
|
||||||
|
|
||||||
|
|
||||||
class IntegerChoices(int, Choices):
|
class IntegerChoices(Choices, IntEnum):
|
||||||
"""Class for creating enumerated integer choices."""
|
"""Class for creating enumerated integer choices."""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TextChoices(str, Choices):
|
class TextChoices(Choices, StrEnum):
|
||||||
"""Class for creating enumerated string choices."""
|
"""Class for creating enumerated string choices."""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -9,6 +9,7 @@ from django.test import SimpleTestCase
|
|||||||
from django.utils.deprecation import RemovedInDjango60Warning
|
from django.utils.deprecation import RemovedInDjango60Warning
|
||||||
from django.utils.functional import Promise
|
from django.utils.functional import Promise
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.utils.version import PY311
|
||||||
|
|
||||||
|
|
||||||
class Suit(models.IntegerChoices):
|
class Suit(models.IntegerChoices):
|
||||||
@ -187,6 +188,7 @@ class ChoicesTests(SimpleTestCase):
|
|||||||
def test_do_not_call_in_templates_member(self):
|
def test_do_not_call_in_templates_member(self):
|
||||||
# do_not_call_in_templates is not implicitly treated as a member.
|
# do_not_call_in_templates is not implicitly treated as a member.
|
||||||
Special = models.IntegerChoices("Special", "do_not_call_in_templates")
|
Special = models.IntegerChoices("Special", "do_not_call_in_templates")
|
||||||
|
self.assertIn("do_not_call_in_templates", Special.__members__)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
Special.do_not_call_in_templates.label,
|
Special.do_not_call_in_templates.label,
|
||||||
"Do Not Call In Templates",
|
"Do Not Call In Templates",
|
||||||
@ -197,6 +199,16 @@ class ChoicesTests(SimpleTestCase):
|
|||||||
"do_not_call_in_templates",
|
"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):
|
class Separator(bytes, models.Choices):
|
||||||
FS = b"\x1c", "File Separator"
|
FS = b"\x1c", "File Separator"
|
||||||
|
Loading…
Reference in New Issue
Block a user