簡體   English   中英

如何正確使用 Django 中的“選擇”字段選項

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

我在這里閱讀教程: 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”的數據庫中創建一個“Months Choices”列,它會在用戶單擊月份后顯示用戶出生的月份並提交表格?

我認為實際上沒有人回答第一個問題:

他們為什么要創建這些變量?

這些變量並不是絕對必要的。 這是真的。 你可以完美地做這樣的事情:

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 層進行任何更改。

對於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
    )

用法::

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

根據文檔

字段選擇

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

最干凈的解決方案是使用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的名稱。

但是有更好的方法來創建您想要的特定選擇。 請參閱之前的 Stack Overflow 問題

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/

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM