我在这里阅读教程:https://docs.djangoproject.com/en/1.5/ref/models/fields/#choices,我正在尝试创建一个框,用户可以在其中选择他出生的月份。我尝试的是
MONTH_CHOICES = (
(JANUARY, "January"),
(FEBRUARY, "February"),
(MARCH, "March"),
....
(DECEMBER, "December"),
)
month = CharField(max_length=9,
choices=MONTHS_CHOICES,
default=JANUARY)
这个对吗?我看到在我正在阅读的教程中,他们出于某种原因首先创建了变量,就像这样
FRESHMAN = 'FR'
SOPHOMORE = 'SO'
JUNIOR = 'JR'
SENIOR = 'SR'
他们为什么要创建这些变量?此外,MONTHS_CHOICES 位于名为 People 的模型中,因此我提供的代码是否会在名为“People”的数据库中创建一个“月份选择”列,并会在用户单击月份后显示用户出生的月份并提交表格?
我认为实际上没有人回答第一个问题:
他们为什么要创建这些变量?
这些变量并不是绝对必要的。这是真的。你可以完美地做这样的事情:
MONTH_CHOICES = (
("JANUARY", "January"),
("FEBRUARY", "February"),
("MARCH", "March"),
# ....
("DECEMBER", "December"),
)
month = models.CharField(max_length=9,
choices=MONTH_CHOICES,
default="JANUARY")
为什么使用变量更好?错误预防和逻辑分离。
JAN = "JANUARY"
FEB = "FEBRUARY"
MAR = "MAR"
# (...)
MONTH_CHOICES = (
(JAN, "January"),
(FEB, "February"),
(MAR, "March"),
# ....
(DEC, "December"),
)
现在,假设您有一个视图,您可以在其中创建一个新的模型实例。而不是这样做:
new_instance = MyModel(month='JANUARY')
你会这样做:
new_instance = MyModel(month=MyModel.JAN)
在第一个选项中,您对值进行硬编码。如果您可以输入一组值,则应在编码时限制这些选项。此外,如果您最终需要在 Model 层更改代码,现在您不需要在 Views 层进行任何更改。
对于 Django3.0+,使用 models.TextChoices
(请参阅 docs-v3.0 了解枚举类型)
from django.db import models
class MyModel(models.Model):
class Month(models.TextChoices):
JAN = "1", "JANUARY"
FEB = "2", "FEBRUARY"
MAR = "3", "MAR"
# (...)
month = models.CharField(
max_length=2,
choices=Month.choices,
default=Month.JAN
)
用法::
>>> obj = MyModel.objects.create(month='1')
>>> assert obj.month == obj.Month.JAN == '1'
>>> assert MyModel.Month(obj.month) is obj.Month.JAN
>>> assert MyModel.Month(obj.month).value is '1'
>>> assert MyModel.Month(obj.month).label == 'JANUARY'
>>> assert MyModel.Month(obj.month).name == 'JAN'
>>> assert MyModel.objects.filter(month=MyModel.Month.JAN).count() >= 1
>>> obj2 = MyModel(month=MyModel.Month.FEB)
>>> assert obj2.get_month_display() == obj2.Month(obj2.month).label
假设我们知道标签是“JANUARY”,如何获得名称“JAN”和值“1”?
label = "JANUARY"
name = {i.label: i.name for i in MyModel.Month}[label]
print(repr(name)) # 'JAN'
value = {i.label: i.value for i in MyModel.Month}[label]
print(repr(value)) # '1'
就个人而言,我宁愿使用 models.IntegerChoices
class MyModel(models.Model):
class Month(models.IntegerChoices):
JAN = 1, "JANUARY"
FEB = 2, "FEBRUARY"
MAR = 3, "MAR"
# (...)
month = models.PositiveSmallIntegerField(
choices=Month.choices,
default=Month.JAN
)
TextChoices
也可以定义为主类并在模型之间共享。永不更改数据的好选择。
JANUARY = 1
、FEBRUARY = 2
等而不指定标签,并且 Django 足够聪明,可以从选择选项列表中生成 'January'、'February' 等。
Field.choices 一个可迭代对象(例如,一个列表或元组),它自身由恰好两个项目的可迭代对象组成(例如 [(A, B), (A, B) ...]),用作该字段的选项。如果给出了这个,默认的表单小部件将是一个带有这些选项的选择框,而不是标准的文本字段。每个元组中的第一个元素是要存储的实际值,第二个元素是人类可读的名称。
因此,您的代码是正确的,除了您应该定义变量 JANUARY
、FEBRUARY
等或使用 calendar
模块来定义 MONTH_CHOICES
:
import calendar
...
class MyModel(models.Model):
...
MONTH_CHOICES = [(str(i), calendar.month_name[i]) for i in range(1,13)]
month = models.CharField(max_length=9, choices=MONTH_CHOICES, default='1')
最干净的解决方案是使用 django-model-utils
库:
from model_utils import Choices
class Article(models.Model):
STATUS = Choices('draft', 'published')
status = models.CharField(choices=STATUS, default=STATUS.draft, max_length=20)
https://django-model-utils.readthedocs.io/en/latest/utilities.html#choices
我建议使用 django-model-utils 而不是 Django 内置解决方案。此解决方案的主要优点是没有字符串声明重复。所有选项都只声明一次。这也是使用 3 个值声明选择并存储与源代码中的用法不同的数据库值的最简单方法。
from django.utils.translation import ugettext_lazy as _
from model_utils import Choices
class MyModel(models.Model):
MONTH = Choices(
('JAN', _('January')),
('FEB', _('February')),
('MAR', _('March')),
)
# [..]
month = models.CharField(
max_length=3,
choices=MONTH,
default=MONTH.JAN,
)
而使用 IntegerField 代替:
from django.utils.translation import ugettext_lazy as _
from model_utils import Choices
class MyModel(models.Model):
MONTH = Choices(
(1, 'JAN', _('January')),
(2, 'FEB', _('February')),
(3, 'MAR', _('March')),
)
# [..]
month = models.PositiveSmallIntegerField(
choices=MONTH,
default=MONTH.JAN,
)
这种方法有一个小缺点:在任何 IDE(例如 PyCharm)中,都没有可用选项的代码完成(这是因为这些值不是 Choices 类的标准成员)。
您不能在代码中使用裸词,这就是他们创建变量的原因(您的代码将因 NameError
而失败)。
您提供的代码将创建一个名为 month
的数据库表(加上 django 添加的任何前缀),因为那是 CharField
的名称。
但是有更好的方法来创建您想要的特定选择。请参阅a previous Stack Overflow question。
import calendar
tuple((m, m) for m in calendar.month_name[1:])
2022 年 3 月更新:
最简单、最简单、最好的新方法是使用内置的“models.TextChoices”,这意味着“您不需要安装任何包”。
“模型.py”:
from django.db import models
class MyModel(models.Model):
class Months(models.TextChoices):
JANUARY = 'JAN', 'January'
FEBRUARY = 'FEB', 'February'
MARCH = 'MAR', 'March'
APRIL = 'APR', 'April'
MAY = 'MAY', 'May'
month = models.CharField(
max_length=3,
choices=Months.choices,
default=Months.APRIL
)
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.SOPHOMORE,
)
我还以内置的旧方式重写了上面的代码。
“模型.py”:
from django.db import models
class MyModel(models.Model):
JANUARY = 'JAN'
FEBRUARY = 'FEB'
MARCH = 'MAR'
APRIL = 'APR'
MAY = 'MAY'
MANTHS = [
(JANUARY, 'January'),
(FEBRUARY, 'February'),
(MARCH, 'March'),
(APRIL, 'April'),
(MAY, 'May'),
]
month = models.CharField(
max_length=3,
choices=MANTHS,
default=APRIL # or "default=MANTHS[4]"
)
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=SOPHOMORE # or "default=YEAR_IN_SCHOOL_CHOICES[1]"
)
如您所见,新方法比旧方法简单得多。
$ pip install django-better-choices
对于那些感兴趣的人,我创建了 django-better-choices
库,它提供了一个很好的界面来使用 Python 3.7+ 的 Django 选项。它支持自定义参数、许多有用的功能并且非常友好。
您可以将您的选择定义为一个类:
from django_better_choices import Choices
class PAGE_STATUS(Choices):
CREATED = 'Created'
PENDING = Choices.Value('Pending', help_text='This set status to pending')
ON_HOLD = Choices.Value('On Hold', value='custom_on_hold')
VALID = Choices.Subset('CREATED', 'ON_HOLD')
class INTERNAL_STATUS(Choices):
REVIEW = 'On Review'
@classmethod
def get_help_text(cls):
return tuple(
value.help_text
for value in cls.values()
if hasattr(value, 'help_text')
)
然后执行以下操作等等:
print( PAGE_STATUS.CREATED ) # 'created'
print( PAGE_STATUS.ON_HOLD ) # 'custom_on_hold'
print( PAGE_STATUS.PENDING.display ) # 'Pending'
print( PAGE_STATUS.PENDING.help_text ) # 'This set status to pending'
'custom_on_hold' in PAGE_STATUS.VALID # True
PAGE_STATUS.CREATED in PAGE_STATUS.VALID # True
PAGE_STATUS.extract('CREATED', 'ON_HOLD') # ~= PAGE_STATUS.VALID
for value, display in PAGE_STATUS:
print( value, display )
PAGE_STATUS.get_help_text()
PAGE_STATUS.VALID.get_help_text()
当然,Django 和 Django Migrations 完全支持它:
class Page(models.Model):
status = models.CharField(choices=PAGE_STATUS, default=PAGE_STATUS.CREATED)
此处的完整文档:https://pypi.org/project/django-better-choices/
不定期副业成功案例分享
MONTH_CHOICES
中声明变量可能更方便,例如MONTH_CHOICES = ((JAN := "JANUARY", "January")...)
。