100

I'm reading the tutorial here: https://docs.djangoproject.com/en/1.5/ref/models/fields/#choices and i'm trying to create a box where the user can select the month he was born in. What I tried was

 MONTH_CHOICES = (
    (JANUARY, "January"),
    (FEBRUARY, "February"),
    (MARCH, "March"),
    ....
    (DECEMBER, "December"),
)

month = CharField(max_length=9,
                  choices=MONTHS_CHOICES,
                  default=JANUARY)

Is this correct? I see that in the tutorial I was reading, they for some reason created variables first, like so

FRESHMAN = 'FR'
SOPHOMORE = 'SO'
JUNIOR = 'JR'
SENIOR = 'SR'

Why did they create those variables? Also, the MONTHS_CHOICES is in a model called People, so would the code I provided create a "Months Choices) column in the database called called "People" and would it say what month the user was born in after he clicks on of the months and submits the form?

4

7 回答 7

201

我认为实际上没有人回答第一个问题:

他们为什么要创建这些变量?

这些变量并不是绝对必要的。这是真的。你可以完美地做这样的事情:

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 层进行任何更改。

于 2015-09-18T17:19:32.000 回答
84

对于 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).label == 'JANUARY'
>>> assert MyModel.objects.filter(month=MyModel.Month.JAN).count() >= 1
>>> assert MyModel.Month(obj.month).name == 'JAN'

>>> 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
    )
于 2019-12-05T16:11:01.423 回答
60

根据文档

字段选择

一个iterable(例如,一个列表或元组),它本身由恰好两个项目(例如[(A,B),(A,B)...])的iterables组成,用作该字段的选择。如果给出了这个,默认的表单小部件将是一个带有这些选项的选择框,而不是标准的文本字段。

每个元组中的第一个元素是要存储的实际值,第二个元素是人类可读的名称。

所以,你的代码是正确的,除了你应该定义变量JANUARYFEBRUARY或使用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')
于 2013-09-07T18:03:53.760 回答
11

最干净的解决方案是使用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

于 2018-11-03T08:14:15.340 回答
5

我建议使用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 类的标准成员)。
于 2019-11-05T18:43:57.650 回答
4

您不能在代码中使用裸词,这就是他们创建变量的原因(您的代码将失败并显示NameError)。

您提供的代码将创建一个名为month(加上 django 添加的任何前缀)的数据库表,因为这是CharField.

但是有更好的方法来创建您想要的特定选择。请参阅之前的 Stack Overflow 问题

import calendar
tuple((m, m) for m in calendar.month_name[1:])
于 2013-09-07T18:04:16.260 回答
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/

于 2020-07-31T09:47:26.457 回答