1import enum
2
3from django.utils.functional import Promise
4from django.utils.version import PY311, PY312
5
6if PY311:
7 from enum import EnumType, IntEnum, StrEnum
8 from enum import property as enum_property
9else:
10 from enum import EnumMeta as EnumType
11 from types import DynamicClassAttribute as enum_property
12
13 class ReprEnum(enum.Enum):
14 def __str__(self):
15 return str(self.value)
16
17 class IntEnum(int, ReprEnum):
18 pass
19
20 class StrEnum(str, ReprEnum):
21 pass
22
23
24__all__ = ["Choices", "IntegerChoices", "TextChoices"]
25
26
27class ChoicesType(EnumType):
28 """A metaclass for creating a enum choices."""
29
30 def __new__(metacls, classname, bases, classdict, **kwds):
31 labels = []
32 for key in classdict._member_names:
33 value = classdict[key]
34 if (
35 isinstance(value, (list, tuple))
36 and len(value) > 1
37 and isinstance(value[-1], (Promise, str))
38 ):
39 *value, label = value
40 value = tuple(value)
41 else:
42 label = key.replace("_", " ").title()
43 labels.append(label)
44 # Use dict.__setitem__() to suppress defenses against double
45 # assignment in enum's classdict.
46 dict.__setitem__(classdict, key, value)
47 cls = super().__new__(metacls, classname, bases, classdict, **kwds)
48 for member, label in zip(cls.__members__.values(), labels):
49 member._label_ = label
50 return enum.unique(cls)
51
52 if not PY312:
53
54 def __contains__(cls, member):
55 if not isinstance(member, enum.Enum):
56 # Allow non-enums to match against member values.
57 return any(x.value == member for x in cls)
58 return super().__contains__(member)
59
60 @property
61 def names(cls):
62 empty = ["__empty__"] if hasattr(cls, "__empty__") else []
63 return empty + [member.name for member in cls]
64
65 @property
66 def choices(cls):
67 empty = [(None, cls.__empty__)] if hasattr(cls, "__empty__") else []
68 return empty + [(member.value, member.label) for member in cls]
69
70 @property
71 def labels(cls):
72 return [label for _, label in cls.choices]
73
74 @property
75 def values(cls):
76 return [value for value, _ in cls.choices]
77
78
79class Choices(enum.Enum, metaclass=ChoicesType):
80 """Class for creating enumerated choices."""
81
82 if PY311:
83 do_not_call_in_templates = enum.nonmember(True)
84 else:
85
86 @property
87 def do_not_call_in_templates(self):
88 return True
89
90 @enum_property
91 def label(self):
92 return self._label_
93
94 # A similar format was proposed for Python 3.10.
95 def __repr__(self):
96 return f"{self.__class__.__qualname__}.{self._name_}"
97
98
99class IntegerChoices(Choices, IntEnum):
100 """Class for creating enumerated integer choices."""
101
102 pass
103
104
105class TextChoices(Choices, StrEnum):
106 """Class for creating enumerated string choices."""
107
108 @staticmethod
109 def _generate_next_value_(name, start, count, last_values):
110 return name