2019-01-01 01:57:35 +08:00
|
|
|
import enum
|
2021-02-20 01:17:11 +08:00
|
|
|
from types import DynamicClassAttribute
|
2019-01-01 01:57:35 +08:00
|
|
|
|
|
|
|
from django.utils.functional import Promise
|
|
|
|
|
|
|
|
__all__ = ['Choices', 'IntegerChoices', 'TextChoices']
|
|
|
|
|
|
|
|
|
|
|
|
class ChoicesMeta(enum.EnumMeta):
|
|
|
|
"""A metaclass for creating a enum choices."""
|
|
|
|
|
2021-02-03 23:00:04 +08:00
|
|
|
def __new__(metacls, classname, bases, classdict, **kwds):
|
2019-01-01 01:57:35 +08:00
|
|
|
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)
|
2021-02-03 23:00:04 +08:00
|
|
|
cls = super().__new__(metacls, classname, bases, classdict, **kwds)
|
2021-02-20 01:17:11 +08:00
|
|
|
for member, label in zip(cls.__members__.values(), labels):
|
|
|
|
member._label_ = label
|
2019-01-01 01:57:35 +08:00
|
|
|
return enum.unique(cls)
|
|
|
|
|
|
|
|
def __contains__(cls, member):
|
|
|
|
if not isinstance(member, enum.Enum):
|
|
|
|
# Allow non-enums to match against member values.
|
2019-12-16 18:22:45 +08:00
|
|
|
return any(x.value == member for x in cls)
|
2019-01-01 01:57:35 +08:00
|
|
|
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=ChoicesMeta):
|
|
|
|
"""Class for creating enumerated choices."""
|
2019-10-23 22:14:06 +08:00
|
|
|
|
2021-02-20 01:17:11 +08:00
|
|
|
@DynamicClassAttribute
|
|
|
|
def label(self):
|
|
|
|
return self._label_
|
|
|
|
|
|
|
|
@property
|
|
|
|
def do_not_call_in_templates(self):
|
|
|
|
return True
|
|
|
|
|
2019-10-23 22:14:06 +08:00
|
|
|
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)
|
2019-01-01 01:57:35 +08:00
|
|
|
|
2021-07-12 17:46:37 +08:00
|
|
|
# A similar format was proposed for Python 3.10.
|
|
|
|
def __repr__(self):
|
|
|
|
return f'{self.__class__.__qualname__}.{self._name_}'
|
2021-04-07 16:28:40 +08:00
|
|
|
|
2019-01-01 01:57:35 +08:00
|
|
|
|
|
|
|
class IntegerChoices(int, Choices):
|
|
|
|
"""Class for creating enumerated integer choices."""
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class TextChoices(str, Choices):
|
|
|
|
"""Class for creating enumerated string choices."""
|
|
|
|
|
|
|
|
def _generate_next_value_(name, start, count, last_values):
|
|
|
|
return name
|