如何向 TextChoices 添加额外数据?

tim*_*els 1 django django-models

如何添加额外的数据django.db.models.TextChoices

class Fruit(models.TextChoices):

    APPLE = ('myvalue', True, 'mylabel')
Run Code Online (Sandbox Code Playgroud)

这样:

>>> Fruit.APPLE.is_tasty
True
>>> # And it still works otherwise
>>> Fruit.APPLE.value
'myvalue'
>>> Fruit.APPLE.label
'mylabel'
Run Code Online (Sandbox Code Playgroud)

tim*_*els 5

您需要执行类似于python Enum 文档建议的操作,但与 python 不同Enum,标签已由以下代码处理models.Choices

\n
class Fruit(models.TextChoices):\n\n    APPLE = (\'myvalue\', True, \'mylabel\')\n\n    def __new__(cls, value, is_tasty):\n        obj = str.__new__(cls, value)\n        obj._value_ = value\n        obj.is_tasty = is_tasty\n        return obj\n
Run Code Online (Sandbox Code Playgroud)\n

如果您在 上使用它IntegerChoices,您将需要int.__new__. 如果您使用__init__而不是__new__,则枚举值将变为(\'myvalue\', True),它会在您的模型字段中使用Fruit.choices,并且可能不适合您的模型字段。

\n

尽管请注意,当在模型字段上使用选择枚举时,您实际上从未将枚举传递给它,因此它不知道枚举。例如,从它派生的表单字段ModelForm会将它们视为str值,并且在 POST 之后,字段值将是常规值str,而不是枚举值。对于表单情况,您可以定义MyForm.clean_fruit或提供手动表单字段TypedChoiceField(coerce=Fruit),在其他地方您可能需要再次查找枚举值Fruit(value),或者您可以将此混合添加到您的字段中:

\n
class EnumMixin:\n\n    \'Convert a DB value back to its Choices value\'\n\n    def __init__(self, *args, enum: models.Choices, **kwargs):\n        self.__enum = enum\n        # it sets choices for you using the enum\n        super().__init__(*args, choices=enum.choices, **kwargs)\n\n    def deconstruct(self):\n        \'Get constructor args to reconstruct this field with later\'\n        name, path, args, kwargs = super().deconstruct()\n        kwargs[\'enum\'] = self.__enum\n        del kwargs[\'choices\']\n        return name, path, args, kwargs\n\n    def from_db_value(self, value, expression, connection):\n        # Convert from db value\n        return self.__to_enum(value)\n\n    def to_python(self, value):\n        \'Called by deserialization and during clean() method used in forms\'\n        return self.__to_enum(value)\n\n    def __to_enum(self, value):\n        if value is None:\n            return None\n        return self.__enum(value)\n\nclass EnumCharField(EnumMixin, models.CharField):\n    pass\n\nclass MyModel(models.Model):\n    field = EnumCharField(enum=Fruit, ...)\n
Run Code Online (Sandbox Code Playgroud)\n

deconstruct由 django 迁移使用,但请注意,它不会Enum在您进行迁移时重建,它将使用您Enum应用迁移时的任何内容。

\n

为什么不叫super呢?

\n

虽然super().__new__一般情况下工作得很好,但子类的情况却并非如此Enummodels.TextChoicesis a models.Choices, which is an Enum)。Python 文档注意以下几点:

\n
\n

__new__()方法(如果已定义)将在创建 Enum 成员期间使用;然后它被 Enum\xe2\x80\x99s 替换,__new__()该 Enum\xe2\x80\x99s 在类创建后用于查找现有成员。

\n
\n

因此,EnumMeta将 class\' 替换__new__Enum.__new__on Fruit, TextChoices, ... 如果您调用super().__new__Fruit.__new__则该调用TextChoices.__new__实际上是Enum.__new__并且不会期望您传递给它的参数(即使它确实接受您的参数,它也不会调用super().__new__自己)。

\n

它将提高:

\n
ValueError: \'myvalue\' is not a valid Fruit\n
Run Code Online (Sandbox Code Playgroud)\n