mirror of
https://github.com/django/django.git
synced 2025-10-31 09:41:08 +00:00
Fixed #27910 -- Added enumeration helpers for use in Field.choices.
These classes can serve as a base class for user enums, supporting translatable human-readable names, or names automatically inferred from the enum member name. Additional properties make it easy to access the list of names, values and display labels. Thanks to the following for ideas and reviews: Carlton Gibson, Fran Hrženjak, Ian Foote, Mariusz Felisiak, Shai Berger. Co-authored-by: Shai Berger <shai@platonix.com> Co-authored-by: Nick Pope <nick.pope@flightdataservices.com> Co-authored-by: Mariusz Felisiak <felisiak.mariusz@gmail.com>
This commit is contained in:
committed by
Mariusz Felisiak
parent
25706d7285
commit
72ebe85a26
@@ -46,6 +46,11 @@ class BaseSimpleSerializer(BaseSerializer):
|
||||
return repr(self.value), set()
|
||||
|
||||
|
||||
class ChoicesSerializer(BaseSerializer):
|
||||
def serialize(self):
|
||||
return serializer_factory(self.value.value).serialize()
|
||||
|
||||
|
||||
class DateTimeSerializer(BaseSerializer):
|
||||
"""For datetime.*, except datetime.datetime."""
|
||||
def serialize(self):
|
||||
@@ -279,6 +284,7 @@ class Serializer:
|
||||
set: SetSerializer,
|
||||
tuple: TupleSerializer,
|
||||
dict: DictionarySerializer,
|
||||
models.Choices: ChoicesSerializer,
|
||||
enum.Enum: EnumSerializer,
|
||||
datetime.datetime: DatetimeDatetimeSerializer,
|
||||
(datetime.date, datetime.timedelta, datetime.time): DateTimeSerializer,
|
||||
|
||||
@@ -7,6 +7,8 @@ from django.db.models.constraints import __all__ as constraints_all
|
||||
from django.db.models.deletion import (
|
||||
CASCADE, DO_NOTHING, PROTECT, SET, SET_DEFAULT, SET_NULL, ProtectedError,
|
||||
)
|
||||
from django.db.models.enums import * # NOQA
|
||||
from django.db.models.enums import __all__ as enums_all
|
||||
from django.db.models.expressions import (
|
||||
Case, Exists, Expression, ExpressionList, ExpressionWrapper, F, Func,
|
||||
OuterRef, RowRange, Subquery, Value, ValueRange, When, Window, WindowFrame,
|
||||
@@ -32,7 +34,7 @@ from django.db.models.fields.related import ( # isort:skip
|
||||
)
|
||||
|
||||
|
||||
__all__ = aggregates_all + constraints_all + fields_all + indexes_all
|
||||
__all__ = aggregates_all + constraints_all + enums_all + fields_all + indexes_all
|
||||
__all__ += [
|
||||
'ObjectDoesNotExist', 'signals',
|
||||
'CASCADE', 'DO_NOTHING', 'PROTECT', 'SET', 'SET_DEFAULT', 'SET_NULL',
|
||||
|
||||
75
django/db/models/enums.py
Normal file
75
django/db/models/enums.py
Normal file
@@ -0,0 +1,75 @@
|
||||
import enum
|
||||
|
||||
from django.utils.functional import Promise
|
||||
|
||||
__all__ = ['Choices', 'IntegerChoices', 'TextChoices']
|
||||
|
||||
|
||||
class ChoicesMeta(enum.EnumMeta):
|
||||
"""A metaclass for creating a enum choices."""
|
||||
|
||||
def __new__(metacls, classname, bases, classdict):
|
||||
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)
|
||||
cls._value2label_map_ = dict(zip(cls._value2member_map_, labels))
|
||||
# Add a label property to instances of enum which uses the enum member
|
||||
# that is passed in as "self" as the value to use when looking up the
|
||||
# label in the choices.
|
||||
cls.label = property(lambda self: cls._value2label_map_.get(self.value))
|
||||
return enum.unique(cls)
|
||||
|
||||
def __contains__(cls, member):
|
||||
if not isinstance(member, enum.Enum):
|
||||
# Allow non-enums to match against member values.
|
||||
return member in {x.value 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=ChoicesMeta):
|
||||
"""Class for creating enumerated choices."""
|
||||
pass
|
||||
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user