YEAR_IN_SCHOOL_CHOICES = {
"FR": "Freshman",
"SO": "Sophomore",
"JR": "Junior",
"SR": "Senior",
"GR": "Graduate",
你还可以传递一个包含自身由两个项目的可迭代对象的 序列 (例如,[(A1, B1), (A2, B2), …]
)。每个元组中的第一个元素是要设置在模型上的实际值,第二个元素是可读的人类名称。例如:
YEAR_IN_SCHOOL_CHOICES = [
("FR", "Freshman"),
("SO", "Sophomore"),
("JR", "Junior"),
("SR", "Senior"),
("GR", "Graduate"),
choices
也可以定义为一个可调用对象,它不需要参数并返回上述任何格式之一。例如:
def get_currencies():
return {i: i for i in settings.CURRENCIES}
class Expense(models.Model):
amount = models.DecimalField(max_digits=10, decimal_places=2)
currency = models.CharField(max_length=3, choices=get_currencies)
当选项是以下情况时,将可调用对象传递给 choices
可以特别方便:
将可调用对象传递给 choices
尤其方便的情况是,它可以用于处理 I/O 绑定的操作结果(这些结果可能会被缓存),比如查询同一数据库或外部数据库中的表,或者从静态文件中获取选项。
一个大部分稳定但在不同时间或项目之间可能会有变化的列表。这种情况下的示例包括使用第三方应用程序,这些应用程序提供了已知的值清单,如货币、国家、语言、时区等。
Changed in Django 5.0: 添加了对映射和可调用对象的支持。
一般来说,最好在模型类内部定义选择,并为每个值定义一个合适的名称的常量:
from django.db import models
class Student(models.Model):
FRESHMAN = "FR"
SOPHOMORE = "SO"
JUNIOR = "JR"
SENIOR = "SR"
GRADUATE = "GR"
YEAR_IN_SCHOOL_CHOICES = {
FRESHMAN: "Freshman",
SOPHOMORE: "Sophomore",
JUNIOR: "Junior",
SENIOR: "Senior",
GRADUATE: "Graduate",
year_in_school = models.CharField(
max_length=2,
choices=YEAR_IN_SCHOOL_CHOICES,
default=FRESHMAN,
def is_upperclass(self):
return self.year_in_school in {self.JUNIOR, self.SENIOR}
虽然你可以在模型类之外定义一个选择列表,然后引用它,但在模型类内定义选择和每个选择的名称,可以将所有这些信息保留在使用它的类中,并帮助引用这些选择(例如,Student.SOPHOMORE
将在导入 Student
模型的任何地方工作)。
你还可以将你的可用选择收集到可用于组织目的的命名组中:
MEDIA_CHOICES = {
"Audio": {
"vinyl": "Vinyl",
"cd": "CD",
"Video": {
"vhs": "VHS Tape",
"dvd": "DVD",
"unknown": "Unknown",
映射的键是要应用于组的名称,值是该组内的选项,包括字段值和一个可读的选项名称。在单个映射内,可以将分组选项与非分组选项组合在一起(例如,此示例中的 "unknown"
选项)。
你还可以使用一个序列,例如,一个包含 2 元组的列表:
MEDIA_CHOICES = [
"Audio",
("vinyl", "Vinyl"),
("cd", "CD"),
"Video",
("vhs", "VHS Tape"),
("dvd", "DVD"),
("unknown", "Unknown"),
请注意,选择可以是任何序列对象——不一定是列表或元组。这让你可以动态地构造选择。但是如果你发现自己把 chips
魔改成动态的,你可能最好使用一个合适的的带有 ForeignKey
的数据库表。 chips
是用于静态数据的,如果有的话,不应该有太大的变化。
每当 choices
的顺序变动时将会创建新的迁移。
对于每个具有 choices
设置的模型字段,Django 将规范化选择项为一个 2-元组的列表,并添加一个方法来获取字段当前值的可读名称。请参阅数据库 API 文档中的 get_FOO_display()
。
除非 blank=False
与 default
一起设置在字段上,否则包含 "---------"
的标签将与选择框一起呈现。要覆盖这种行为,可以在 choices
中添加一个包含 None
的元组,例如 (None, 'Your String For Display')
。另外,你也可以在有意义的地方使用一个空字符串来代替 None
——比如在 CharField
。
枚举类型
此外,Django 还提供了枚举类型,你可以通过将其子类化来简洁地定义选择:
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,
default=YearInSchool.FRESHMAN,
def is_upperclass(self):
return self.year_in_school in {
self.YearInSchool.JUNIOR,
self.YearInSchool.SENIOR,
这些工作类似于 Python 标准库中的 enum
,但是做了一些修改。
枚举成员的值是用于构造具体数据类型的参数元组。Django 支持在此元组的末尾添加额外的字符串值,用作可读名称或 label
。这个 label
可以是懒惰可翻译的字符串。因此,在大多数情况下,成员值将是一个 (value, label)
的 2-元组。请参阅下面的示例,了解使用更复杂的数据类型来进行选择的 子类化选择项的示例。如果未提供元组,或者最后一项不是(懒惰)字符串,则从成员名称中 自动生成 label
。
在值上添加 .label
属性,以返回人类可读的名称。
一些自定义属性被添加到枚举类中——.choices
、.labels
、.values
和 .names
——以便更容易访问这些枚举的各个部分的列表。
这些属性名称不能作为成员名称使用,因为它们会发生冲突。
强制使用 enum.unique()
是为了确保不能多次定义值。在选择一个字段时,不太可能会出现这种情况。
请注意,使用 YearInSchool.SENIOR
、YearInSchool['SENIOR']
或 YearInSchool('SR')
来访问或查找枚举成员,与成员上的 .name
和 .value
属性一样,都能正常工作。
如果你不需要将可读的名称翻译成其他语言,你可以从成员名称中推断出它们(将下划线替换为空格,并使用标题大小写):
>>> class Vehicle(models.TextChoices):
... CAR = "C"
... TRUCK = "T"
... JET_SKI = "J"
>>> Vehicle.JET_SKI.label
'Jet Ski'
由于枚举值需要为整数的情况极为常见,Django 提供了一个 IntegerChoices
类。例如:
class Card(models.Model):
class Suit(models.IntegerChoices):
DIAMOND = 1
SPADE = 2
HEART = 3
CLUB = 4
suit = models.IntegerField(choices=Suit)
还可以利用 Enum Functional API,但需要注意自动生成的标签如上所示:
>>> MedalType = models.TextChoices("MedalType", "GOLD SILVER BRONZE")
>>> MedalType.choices
[('GOLD', 'Gold'), ('SILVER', 'Silver'), ('BRONZE', 'Bronze')]
>>> Place = models.IntegerChoices("Place", "FIRST SECOND THIRD")
>>> Place.choices
[(1, 'First'), (2, 'Second'), (3, 'Third')]
如果你需要支持 int
或 str
以外的具体数据类型,你可以将 Choices
和所需的具体数据类型子类化,例如 date
与 DateField
一起使用:
class MoonLandings(datetime.date, models.Choices):
APOLLO_11 = 1969, 7, 20, "Apollo 11 (Eagle)"
APOLLO_12 = 1969, 11, 19, "Apollo 12 (Intrepid)"
APOLLO_14 = 1971, 2, 5, "Apollo 14 (Antares)"
APOLLO_15 = 1971, 7, 30, "Apollo 15 (Falcon)"
APOLLO_16 = 1972, 4, 21, "Apollo 16 (Orion)"
APOLLO_17 = 1972, 12, 11, "Apollo 17 (Challenger)"
还有一些注意事项需要注意:
枚举类型不支持 命名组。
因为具有具体数据类型的枚举要求所有值都与类型相匹配,所以不能通过创建一个值为 None
的成员来覆盖 空白标签。相反,在类上设置 __empty__
属性:
class Answer(models.IntegerChoices):
NO = 0, _("No")
YES = 1, _("Yes")
__empty__ = _("(Unknown)")
这个字段要使用的数据库列名。如果没有给出列名,Django 将使用字段名。
如果你的数据库列名是 SQL 的保留字,或者包含了 Python 变量名中不允许的字符——特别是连字符——那也没关系。Django 会在幕后引用列名和表名。