简体   繁体   English

如何正确使用 Django 中的“选择”字段选项

[英]How to properly use the "choices" field option in Django

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我在这里阅读教程: 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)

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?此外,MONTHS_CHOICES 在一个名为 People 的模型中,所以我提供的代码会在名为“People”的数据库中创建一个“Months Choices”列,它会在用户单击月份后显示用户出生的月份并提交表格?

I think no one actually has answered to the first question:我认为实际上没有人回答第一个问题:

Why did they create those variables?他们为什么要创建这些变量?

Those variables aren't strictly necessary.这些变量并不是绝对必要的。 It's true.这是真的。 You can perfectly do something like this:你可以完美地做这样的事情:

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

month = models.CharField(max_length=9,
                  choices=MONTH_CHOICES,
                  default="JANUARY")

Why using variables is better?为什么使用变量更好? Error prevention and logic separation.错误预防和逻辑分离。

JAN = "JANUARY"
FEB = "FEBRUARY"
MAR = "MAR"
# (...)

MONTH_CHOICES = (
    (JAN, "January"),
    (FEB, "February"),
    (MAR, "March"),
    # ....
    (DEC, "December"),
)

Now, imagine you have a view where you create a new Model instance.现在,假设您有一个视图,您可以在其中创建一个新的模型实例。 Instead of doing this:而不是这样做:

new_instance = MyModel(month='JANUARY')

You'll do this:你会这样做:

new_instance = MyModel(month=MyModel.JAN)

In the first option you are hardcoding the value.在第一个选项中,您对值进行硬编码。 If there is a set of values you can input, you should limit those options when coding.如果您可以输入一组值,则应在编码时限制这些选项。 Also, if you eventually need to change the code at the Model layer, now you don't need to make any change in the Views layer.此外,如果您最终需要在 Model 层更改代码,现在您不需要在 Views 层进行任何更改。

For Django3.0+, use models.TextChoices (see docs-v3.0 for enumeration types )对于models.TextChoices +,使用 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
    )

Usage::用法::

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

Let's say we known the label is 'JANUARY', how to get the name 'JAN' and the value '1'?假设我们知道标签是“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'

Personally, I would rather use models.IntegerChoices就个人而言,我宁愿使用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
    )

According to the documentation :根据文档

Field.choices字段选择

An iterable (eg, a list or tuple) consisting itself of iterables of exactly two items (eg [(A, B), (A, B) ...]) to use as choices for this field.一个iterable(例如,一个列表或元组),它本身由恰好两个项目(例如[(A,B),(A,B)...])的iterables组成,用作该字段的选择。 If this is given, the default form widget will be a select box with these choices instead of the standard text field.如果给出了这个,默认的表单小部件将是一个带有这些选项的选择框,而不是标准的文本字段。

The first element in each tuple is the actual value to be stored, and the second element is the human-readable name.每个元组中的第一个元素是要存储的实际值,第二个元素是人类可读的名称。

So, your code is correct, except that you should either define variables JANUARY , FEBRUARY etc. or use calendar module to define MONTH_CHOICES :因此,您的代码是正确的,除了您应该定义变量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')

The cleanest solution is to use the django-model-utils library:最干净的解决方案是使用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 https://django-model-utils.readthedocs.io/en/latest/utilities.html#choices

I would suggest to use django-model-utils instead of Django built-in solution.我建议使用django-model-utils而不是 Django 内置解决方案。 The main advantage of this solution is the lack of string declaration duplication.此解决方案的主要优点是没有字符串声明重复。 All choice items are declared exactly once.所有选项都只声明一次。 Also this is the easiest way for declaring choices using 3 values and storing database value different than usage in source code.这也是使用 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,
   )

And with usage IntegerField instead:而使用 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,
   )
  • This method has one small disadvantage: in any IDE (eg. PyCharm) there will be no code completion for available choices (it's because those values aren't standard members of Choices class).这种方法有一个小缺点:在任何 IDE(例如 PyCharm)中,都没有可用选项的代码完成(这是因为这些值不是 Choices 类的标准成员)。

You can't have bare words in the code, that's the reason why they created variables (your code will fail with NameError ).您不能在代码中使用裸词,这就是他们创建变量的原因(您的代码将因NameError而失败)。

The code you provided would create a database table named month (plus whatever prefix django adds to that), because that's the name of the CharField .您提供的代码将创建一个名为month的数据库表(加上 django 添加的任何前缀),因为这是CharField的名称。

But there are better ways to create the particular choices you want.但是有更好的方法来创建您想要的特定选择。 See a previous Stack Overflow question .请参阅之前的 Stack Overflow 问题

import calendar
tuple((m, m) for m in calendar.month_name[1:])

Mar, 2022 Update: 2022 年 3 月更新:

The simplest, easiest, best and new way is using "models.TextChoices" which is built-in which means "You don't need to install any packages" .最简单、最简单、最好的新方法是使用内置的“models.TextChoices” ,这意味着“您不需要安装任何包”

"models.py" : “模型.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,
    )

I also rewrote the code above in the old way which is also built-in .我还以旧的方式重写了上面的代码,它也是内置的

"models.py" : “模型.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]"
    )

As you can see, the new way is much simpler than the old way.如您所见,新方法比旧方法简单得多。

$ pip install django-better-choices $ pip install django-better-choices

For those who are interested, I have created django-better-choices library, that provides a nice interface to work with Django choices for Python 3.7+.对于那些感兴趣的人,我创建了django-better-choices库,它提供了一个很好的界面来处理 Python 3.7+ 的 Django 选择。 It supports custom parameters, lots of useful features and is very IDE friendly.它支持自定义参数、许多有用的功能并且非常友好。

You can define your choices as a class:您可以将您的选择定义为一个类:

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

Then do the following operations and much much more :然后执行以下操作等等

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

And of course, it is fully supported by Django and Django Migrations:当然,Django 和 Django Migrations 完全支持它:

class Page(models.Model):
    status = models.CharField(choices=PAGE_STATUS, default=PAGE_STATUS.CREATED)

Full documentation here: https://pypi.org/project/django-better-choices/此处的完整文档: https ://pypi.org/project/django-better-choices/

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM