
[英]How would I use django.forms to prepopulate a choice field with rows from a 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)
我关注了两篇描述如何使用枚举的文章:
例子:
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 ,但有一些修改:
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.ERROR
或TransactionStatus.PENDING
。
你的其余代码是正确的。 您可以通过创建option.name
、 option.value
元组来获得choices
。
更新:对于Django的3.x和更高,使用内置类型TextChoices
, IntegerChoices
并Choices
所描述这里。 这样您就不必自己构建choices
元组。
您的代码中的问题是INITIATED = "INITIATED",
在INITIATED
选项和其他选项之后的逗号。 当我们在任何字符串后添加逗号时,它将成为一个元组。 请参阅下面的示例
s = 'my str'
print(type(s))
# output: str
s = 'my str',
print(type(s))
# output: tuple
class Transaction(models.Model):
trasaction_status = models.CharField(max_length=255, choices=TransactionStatus.choices())
transaction_type = models.CharField(max_length=255, choices=TransactionType.choices())
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还提供从枚举属性扩展的IntegerChoices
和TextChoices
类型,这使得非常丰富的枚举字段成为可能。
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.