【问题标题】:How to use enums as a choice field in django model如何在 django 模型中使用枚举作为选择字段
【发布时间】:2019-07-15 02:29:09
【问题描述】:

我有一个模型类,我希望其中两个字段作为选择字段,因此为了填充这些选择,我使用下面列出的枚举

#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)

但是,当我尝试通过管理员访问此模型时,我收到以下错误:

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

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

【问题讨论】:

  • 您在“IN”和“INITIATED”之后有一个逗号...
  • 四行代码末尾有多余的逗号。

标签: python django python-3.x django-models enums


【解决方案1】:

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

欲了解更多信息,check the documentation

注意:

正如@Danielle Madeley 指出的那样,如果您尝试直接访问year_in_school 属性,Django 仍然返回原始字符串而不是 Enum 对象:

>>> student.year_in_school
'FR'

我通常做的是创建一个返回 Enum 对象的辅助方法:

class Student(models.Model):
    ...

    def get_year_in_school() -> YearInSchool:
        # Get value from choices enum
        return self.YearInSchool[self.year_in_school]

【讨论】:

  • 这对我不起作用 - Django 返回“'choices' 必须是包含(实际值,人类可读名称)元组的可迭代。”
  • @kellyfj 它应该可以工作,这个例子如果来自 Django 文档。您确定您使用的是 Django 版本 3 或更高版本吗?
  • 这里只是一个警告,student.year_in_school 返回一个字符串,而不是枚举。您必须手动转换结果或使用 ==/!=
  • @DavidPiao 恐怕我不明白你的问题。但这里有一个“a”问题的答案:不幸的是,Django 永远不会返回枚举类型,并且总是返回一个字符串。所以foo.state == State.DRAFT 将用于字符串比较,但Python 文档首选foo.state is State.DRAFT 不会。你可以做State(foo.state) is State.DRAFT
  • @CesarCanassa 如果从文档中逐字复制了某些内容,请引用它。
【解决方案2】:

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

您可以通过设置here 记录的各种选项来定义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,如 here 所述。这样您就不必自己构造 choices 元组。

【讨论】:

    【解决方案3】:

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

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

    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())
    

    枚举.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)
    

    【讨论】:

    • 这不是您定义Enum 的方式。只需删除逗号。
    【解决方案4】:

    如果您收到此错误:

    '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,
        )
    

    【讨论】:

      【解决方案5】:

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

      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')
      

      【讨论】:

        【解决方案6】:
        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)
        

        【讨论】:

          【解决方案7】:

          你可以尝试根据examples from docs.做这样的事情:

          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')]
          

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

          【讨论】:

            【解决方案8】:

            也可以写成:

            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'))
            

            【讨论】:

              【解决方案9】:

              根据您来自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]
              

              【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2021-03-26
              • 2017-02-06
              • 1970-01-01
              • 2020-03-16
              • 2015-03-09
              • 2015-05-28
              • 2016-07-03
              相关资源
              最近更新 更多