1
0
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:
Shai Berger
2018-12-31 19:57:35 +02:00
committed by Mariusz Felisiak
parent 25706d7285
commit 72ebe85a26
11 changed files with 554 additions and 4 deletions

View File

@@ -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,

View File

@@ -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
View 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