你需要做一些类似于python 枚举文档建议,但与 python 不同Enum
,标签已经由models.Choices
:
class Fruit(models.TextChoices):
APPLE = ('myvalue', True, 'mylabel')
def __new__(cls, value, is_tasty):
obj = str.__new__(cls, value)
obj._value_ = value
obj.is_tasty = is_tasty
return obj
如果您在IntegerChoices
, 你需要int.__new__
。如果你使用__init__
代替__new__
,枚举值将变为('myvalue', True)
,它被用于Fruit.choices
并且可能不适合您的模型字段。
尽管请注意,当在模型字段上使用选择枚举时,您实际上从未将枚举传递给它,因此它不知道枚举。例如。从中派生出的表单字段ModelForm
会将他们视为str
值并且在 POST 之后字段值将是常规值str
,不是枚举值。对于表单案例,您可以定义MyForm.clean_fruit
或提供手动表单字段TypedChoiceField(coerce=Fruit)
,在其他地方您可能需要再次查找枚举值Fruit(value)
或者您可以将此 mixin 添加到您的字段中:
class EnumMixin:
'Convert a DB value back to its Choices value'
def __init__(self, *args, enum: models.Choices, **kwargs):
self.__enum = enum
# it sets choices for you using the enum
super().__init__(*args, choices=enum.choices, **kwargs)
def deconstruct(self):
'Get constructor args to reconstruct this field with later'
name, path, args, kwargs = super().deconstruct()
kwargs['enum'] = self.__enum
del kwargs['choices']
return name, path, args, kwargs
def from_db_value(self, value, expression, connection):
# Convert from db value
return self.__to_enum(value)
def to_python(self, value):
'Called by deserialization and during clean() method used in forms'
return self.__to_enum(value)
def __to_enum(self, value):
if value is None:
return None
return self.__enum(value)
class EnumCharField(EnumMixin, models.CharField):
pass
class MyModel(models.Model):
field = EnumCharField(enum=Fruit, ...)
deconstruct
由 django 迁移使用,但请注意它不会重建Enum
在您进行迁移时,它将使用您的任何内容Enum
是在您应用迁移时。
为什么不叫super呢?
While super().__new__
一般情况下工作正常,但情况并非如此Enum
子类(models.TextChoices
is a models.Choices
这是一个Enum
)。 Python 文档注意以下几点:
The __new__()
方法(如果已定义)将在创建 Enum 成员期间使用;然后它被 Enum 替换__new__()
它在类创建后用于查找现有成员。
So, EnumMeta
取代班级'__new__
with Enum.__new__
on Fruit
, TextChoices
,...如果你打电话super().__new__
in Fruit.__new__
那么这调用TextChoices.__new__
这实际上是Enum.__new__
并且它不会期望您传递给它的参数(即使它确实接受了您的参数,它也不会调用super().__new__
本身)。
它将提高:
ValueError: 'myvalue' is not a valid Fruit