繁体   English   中英

如何使用枚举作为选择字段 django model

[英]How to use enums as a choice field in django model

我有一个 model class,其中我希望两个字段成为一个选择字段,因此为了填充这些选择,我使用了如下所列的枚举

#models.py
class Transaction(models.Model):
    trasaction_status = models.CharField(max_length=255, choices=TransactionStatus.choices())
    transaction_type = models.CharField(max_length=255, choices=TransactionType.choices())

#enums.py
class TransactionType(Enum):

    IN = "IN",
    OUT = "OUT"

    @classmethod
    def choices(cls):
        print(tuple((i.name, i.value) for i in cls))
        return tuple((i.name, i.value) for i in cls)

class TransactionStatus(Enum):

    INITIATED = "INITIATED",
    PENDING = "PENDING",
    COMPLETED = "COMPLETED",
    FAILED = "FAILED"
    ERROR = "ERROR"

    @classmethod
    def choices(cls):
        print(tuple((i.name, i.value) for i in cls))
        return tuple((i.name, i.value) for i in cls)

但是,当我尝试通过管理员访问此 model 时,出现以下错误:

Django Version: 1.11
Exception Type: ValueError
Exception Value:    
too many values to unpack (expected 2)

我关注了两篇描述如何使用枚举的文章:

Django 3.0 内置了对枚举的支持

例子:

from django.utils.translation import gettext_lazy as _

class Student(models.Model):

    class YearInSchool(models.TextChoices):
        FRESHMAN = 'FR', _('Freshman')
        SOPHOMORE = 'SO', _('Sophomore')
        JUNIOR = 'JR', _('Junior')
        SENIOR = 'SR', _('Senior')
        GRADUATE = 'GR', _('Graduate')

    year_in_school = models.CharField(
        max_length=2,
        choices=YearInSchool.choices,
        default=YearInSchool.FRESHMAN,
    )

这些工作类似于 Python 标准库中的enum ,但有一些修改:

  • 枚举成员值是构造具体数据类型时要使用的参数元组。 Django 支持在该元组的末尾添加一个额外的字符串值,用作人类可读的名称或label label可以是一个懒惰的可翻译字符串。 因此,在大多数情况下,成员值将是一个(value, label)二元组。 如果未提供元组,或者最后一项不是(惰性)字符串,则标签将根据成员名称自动生成
  • .label属性被添加到值上,以返回人类可读的名称。 许多自定义属性添加到枚举类 - .choices.labels.values.names - 以便更轻松地访问枚举的这些单独部分的列表。 使用.choices作为合适的值传递给字段定义中的选择。
  • 强制使用enum.unique()以确保值不能被多次定义。 这在一个领域的选择中是不可能的。

有关更多信息, 请查看文档

对于 Django 2.x 及更低版本:

您可以通过设置此处记录的各种选项来定义Enum

class TransactionStatus(Enum):

    INITIATED = "INITIATED"
    PENDING = "PENDING"
    COMPLETED = "COMPLETED"
    FAILED = "FAILED"
    ERROR = "ERROR"

注意没有逗号! 这允许您稍后在代码中引用TransactionStatus.ERRORTransactionStatus.PENDING

你的其余代码是正确的。 您可以通过创建option.nameoption.value元组来获得choices

更新:对于Django的3.x和更高,使用内置类型TextChoicesIntegerChoicesChoices所描述这里 这样您就不必自己构建choices元组。

您的代码中的问题是INITIATED = "INITIATED",INITIATED选项和其他选项之后的逗号。 当我们在任何字符串后添加逗号时,它将成为一个元组。 请参阅下面的示例

s = 'my str'
print(type(s))
# output: str

s = 'my str',
print(type(s))
# output: tuple

模型.py

class Transaction(models.Model):
    trasaction_status = models.CharField(max_length=255, choices=TransactionStatus.choices())
    transaction_type = models.CharField(max_length=255, choices=TransactionType.choices())

枚举.py

class TransactionType(Enum):

    IN = "IN"
    OUT = "OUT"

    @classmethod
    def choices(cls):
        print(tuple((i.name, i.value) for i in cls))
        return tuple((i.name, i.value) for i in cls)

class TransactionStatus(Enum):

    INITIATED = "INITIATED"
    PENDING = "PENDING"
    COMPLETED = "COMPLETED"
    FAILED = "FAILED"
    ERROR = "ERROR"

    @classmethod
    def choices(cls):
        print(tuple((i.name, i.value) for i in cls))
        return tuple((i.name, i.value) for i in cls)

如果您收到此错误:

'choices' 必须是一个包含(实际值,人类可读名称)元组的可迭代对象

并且正在使用 Django3,那么您可能会遇到与我相同的问题:“枚举”必须嵌入到您尝试使用它们的模型中,并且不能在模型之外声明。 例如,这是不行的:

class YearInSchool(models.TextChoices):
    FRESHMAN = 'FR', _('Freshman')
    SOPHOMORE = 'SO', _('Sophomore')
    JUNIOR = 'JR', _('Junior')
    SENIOR = 'SR', _('Senior')
    GRADUATE = 'GR', _('Graduate')

class Student(models.Model):
   year_in_school = models.CharField(
        max_length=2,
        choices=YearInSchool.choices,
        default=YearInSchool.FRESHMAN,
    )

文档中的这个示例将:

class Student(models.Model):

    class YearInSchool(models.TextChoices):
        FRESHMAN = 'FR', _('Freshman')
        SOPHOMORE = 'SO', _('Sophomore')
        JUNIOR = 'JR', _('Junior')
        SENIOR = 'SR', _('Senior')
        GRADUATE = 'GR', _('Graduate')

    year_in_school = models.CharField(
        max_length=2,
        choices=YearInSchool.choices,
        default=YearInSchool.FRESHMAN,
    )

我的项目中的一个例子:

import enum

from django.contrib.postgres.fields import ArrayField
from django.db import models
from django.utils.translation import gettext_lazy as _


class NotificationTemplate(models.Model):
class Meta:
    verbose_name = _('notification template')
    verbose_name_plural = _('notification templates')

@enum.unique
class Name(str, enum.Enum):
    ONBOARDING = 'onboarding'
    TG_ERROR = 'tg_error'
    FB_ERROR = 'fb_error'

    @classmethod
    def choices(cls):
        return [(item.value, item.name) for item in cls]

@enum.unique
class Type(int, enum.Enum):
    PUSH = 1
    EMAIL = 2
    TELEGRAM = 3
    VK = 4
    OTHER = 5

    @classmethod
    def choices(cls):
        return [(item.value, item.name) for item in cls]

name = models.CharField(_('notification name'), max_length=64, unique=True, choices=Name.choices(), default=Name.ONBOARDING)
template_type = ArrayField(models.PositiveSmallIntegerField(_('type'), choices=Type.choices()))
max_count = models.PositiveSmallIntegerField(default=1)

def __str__(self):
    return self.Name(self.name).name

根据您来自https://hackernoon.com/using-enum-as-model-field-choice-in-django-92d8b97aaa63的参考。 选择应该是元组列表,而你的将返回一个元组元组。 i 与 i.name 不同。 尝试:

#enums.py
class TransactionType(Enum):

    IN = "IN",
    OUT = "OUT"

    @classmethod
    def choices(cls):
        return [(i, i.value) for i in cls]

顺便说一下,Djanog 还支持 Python 3 的 auto() 作为 Enum 值。 您可以使用以下 helperclass 使您的生活更轻松。

from django.db.models.enums import TextChoices

class AutoEnumChoices(TextChoices):
    def _generate_next_value_(name, start, count, last_values):  # @NoSelf
        return name.lower()
    
    @property
    def choices(cls):  # @NoSelf
        empty = [(None, cls.__empty__)] if hasattr(cls, '__empty__') else []
        return empty + [(member.value, member.label) for member in cls]

然后在您的选择定义中使用它:

class TransferBasicStatus(AutoEnumChoices):
    NONE = auto()
    WAITING = auto()
    PENDING = auto()
    PROGRESS = auto()
    SUCCESS = auto()
    DECLINED = auto()
    ENDED =  'ended', _('Ended - The transfer has ended with mixed states')

您可以尝试根据文档中的示例执行此类操作

from enum import Enum

class BaseEnum(Enum):
    def __new__(cls, *args):
        obj = object.__new__(cls)
        obj._value_ = args[0]
        obj.display_name = args[1]
        return obj

    @classmethod
    def model_choices(cls):
        return [(cls.__members__[member].value, cls.__members__[member].display_name)
            for member in cls.__members__.keys()]

这将导致:

>>> class TransactionType(BaseEnum):
...     IN = ('in', 'In')
...     OUT = ('out', 'Out')
...
>>> TransactionType.IN.value
'in'
>>> TransactionType.IN.display_name
'In'
>>> TransactionType.model_choices()
[('in', 'In'), ('out', 'Out')]

这可以用作字段选择的参数。

class YearInSchool(models.TextChoices):
        FRESHMAN = 'FR', _('Freshman')
        SOPHOMORE = 'SO', _('Sophomore')
        JUNIOR = 'JR', _('Junior')
        SENIOR = 'SR', _('Senior')
        GRADUATE = 'GR', _('Graduate')

    year_in_school = models.CharField(
        max_length=2,
        choices=YearInSchool.choices,
        default=YearInSchool.FRESHMAN,
    )

对于 Django 3.0 以上,你可以使用上面的例子。

对于整数选择,您可以使用以下代码。

class Suit(models.IntegerChoices):
        DIAMOND = 1
        SPADE = 2
        HEART = 3
        CLUB = 4

    suit = models.IntegerField(choices=Suit.choices)

也可以写成:

class Transaction(models.Model):
    class TransactionStatus(Enum):
        initiated = ('in', 'Initiated')
        pending = ('pe', 'Pending')
        completed = ('co', 'Completed')
        failed = ('fa', 'Failed')
        error = ('er', 'Error')
        
        @classmethod
        def get_value(cls, member):
            return cls[member].value[0]
    
    class TransactionType(Enum):
        _in = ('in', 'In')
        out = ('ou', 'Out')
   
        @classmethod
        def get_value(cls, member):
            return cls[member].value[0]

    trasaction_status = models.CharField(max_length=2, choices=[x.value for x in TransactionStatus])
    transaction_type = models.CharField(max_length=2, choices=[x.value for x in TransactionType])

使用get_value您可以编写例如:

Transaction.objects.filter(status=Transaction.TransactionStatus.get_value('initialited'))

@paras 你必须改变你的 model @classmethod def selection(cls): print(tuple((i.value, i.name) for i in cls)) return tuple((i.value, i.name) for i in分类)

它对我有用。

django-enum package 使这变得非常简单:

from django.db import models
from django_enum import EnumField

class MyModel(models.Model):

    class TextEnum(models.TextChoices):

        VALUE0 = 'V0', 'Value 0'
        VALUE1 = 'V1', 'Value 1'
        VALUE2 = 'V2', 'Value 2'

    class IntEnum(models.IntegerChoices):

        ONE   = 1, 'One'
        TWO   = 2, 'Two',
        THREE = 3, 'Three'

    # this is equivalent to:
    #  CharField(max_length=2, choices=TextEnum.choices, null=True, blank=True)
    txt_enum = EnumField(TextEnum, null=True, blank=True)

    # this is equivalent to
    #  PositiveSmallIntegerField(choices=IntEnum.choices)
    int_enum = EnumField(IntEnum)

EnumField不仅仅是一个别名。 这些字段现在可以作为枚举类型而不是按值分配和访问:

instance = MyModel.objects.create(
    txt_enum=MyModel.TextEnum.VALUE1,
    int_enum=3  # by-value assignment also works
)

assert instance.txt_enum == MyModel.TextEnum('V1')
assert instance.txt_enum.label == 'Value 1'

assert instance.int_enum == MyModel.IntEnum['THREE']
assert instance.int_enum.value == 3

django-enum还提供从枚举属性扩展的IntegerChoicesTextChoices类型,这使得非常丰富的枚举字段成为可能。

from enum_properties import s
from django_enum import TextChoices  # use instead of Django's TextChoices
from django.db import models

class TextChoicesExample(models.Model):

    class Color(TextChoices, s('rgb'), s('hex', case_fold=True)):

        # name   value   label       rgb       hex
        RED     = 'R',   'Red',   (1, 0, 0), 'ff0000'
        GREEN   = 'G',   'Green', (0, 1, 0), '00ff00'
        BLUE    = 'B',   'Blue',  (0, 0, 1), '0000ff'

        # any named s() values in the Enum's inheritance become properties on
        # each value, and the enumeration value may be instantiated from the
        # property's value

    color = EnumField(Color)

instance = TextChoicesExample.objects.create(
    color=TextChoicesExample.Color('FF0000')
)
assert instance.color == TextChoicesExample.Color('Red')
assert instance.color == TextChoicesExample.Color('R')
assert instance.color == TextChoicesExample.Color((1, 0, 0))

# direct comparison to any symmetric value also works
assert instance.color == 'Red'
assert instance.color == 'R'
assert instance.color == (1, 0, 0)

# save by any symmetric value
instance.color = 'FF0000'

# access any enum property right from the model field
assert instance.color.hex == 'ff0000'

# this also works!
assert instance.color == 'ff0000'

# and so does this!
assert instance.color == 'FF0000'

instance.save()

# filtering works by any symmetric value or enum type instance
assert TextChoicesExample.objects.filter(
    color=TextChoicesExample.Color.RED
).first() == instance

assert TextChoicesExample.objects.filter(color=(1, 0, 0)).first() == instance

assert TextChoicesExample.objects.filter(color='FF0000').first() == instance

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM