123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123 |
- import enum
- import warnings
- 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, 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"]
- class ChoicesType(EnumType):
- """A metaclass for creating a enum choices."""
- def __new__(metacls, classname, bases, classdict, **kwds):
- labels = []
- for key in classdict._member_names:
- value = classdict[key]
- if (
- isinstance(value, (list, tuple))
- and len(value) > 1
- and isinstance(value[-1], (Promise, str))
- ):
- *value, label = value
- value = tuple(value)
- else:
- label = key.replace("_", " ").title()
- labels.append(label)
- # Use dict.__setitem__() to suppress defenses against double
- # assignment in enum's classdict.
- dict.__setitem__(classdict, key, value)
- cls = super().__new__(metacls, classname, bases, classdict, **kwds)
- for member, label in zip(cls.__members__.values(), labels):
- member._label_ = label
- return enum.unique(cls)
- if not PY312:
- def __contains__(cls, member):
- if not isinstance(member, enum.Enum):
- # Allow non-enums to match against member values.
- return any(x.value == member for x in cls)
- return super().__contains__(member)
- @property
- def names(cls):
- empty = ["__empty__"] if hasattr(cls, "__empty__") else []
- return empty + [member.name for member in cls]
- @property
- def choices(cls):
- empty = [(None, cls.__empty__)] if hasattr(cls, "__empty__") else []
- return empty + [(member.value, member.label) for member in cls]
- @property
- def labels(cls):
- return [label for _, label in cls.choices]
- @property
- def values(cls):
- return [value for value, _ in cls.choices]
- class Choices(enum.Enum, metaclass=ChoicesType):
- """Class for creating enumerated choices."""
- 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):
- return self._label_
- # A similar format was proposed for Python 3.10.
- def __repr__(self):
- return f"{self.__class__.__qualname__}.{self._name_}"
- class IntegerChoices(Choices, IntEnum):
- """Class for creating enumerated integer choices."""
- pass
- class TextChoices(Choices, StrEnum):
- """Class for creating enumerated string choices."""
- @staticmethod
- def _generate_next_value_(name, start, count, last_values):
- return name
- def __getattr__(name):
- if name == "ChoicesMeta":
- warnings.warn(
- "ChoicesMeta is deprecated in favor of ChoicesType.",
- RemovedInDjango60Warning,
- stacklevel=2,
- )
- return ChoicesType
- raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|