Fixed #31262 -- Added support for mappings on model fields and ChoiceField's choices.

This commit is contained in:
Nick Pope 2023-08-31 02:57:40 +01:00 committed by GitHub
parent 68a8996bdf
commit 500e01073a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 822 additions and 249 deletions

View file

@ -165,9 +165,11 @@ Model fields
* **fields.E002**: Field names must not contain ``"__"``.
* **fields.E003**: ``pk`` is a reserved word that cannot be used as a field
name.
* **fields.E004**: ``choices`` must be an iterable (e.g., a list or tuple).
* **fields.E005**: ``choices`` must be an iterable containing ``(actual value,
human readable name)`` tuples.
* **fields.E004**: ``choices`` must be a mapping (e.g. a dictionary) or an
iterable (e.g. a list or tuple).
* **fields.E005**: ``choices`` must be a mapping of actual values to human
readable names or an iterable containing ``(actual value, human readable
name)`` tuples.
* **fields.E006**: ``db_index`` must be ``None``, ``True`` or ``False``.
* **fields.E007**: Primary keys must not have ``null=True``.
* **fields.E008**: All ``validators`` must be callable.

View file

@ -47,11 +47,11 @@ news application with an ``Article`` model::
from django.db import models
STATUS_CHOICES = [
("d", "Draft"),
("p", "Published"),
("w", "Withdrawn"),
]
STATUS_CHOICES = {
"d": "Draft",
"p": "Published",
"w": "Withdrawn",
}
class Article(models.Model):

View file

@ -510,8 +510,9 @@ For each field, we describe the default widget used if you don't specify
.. versionchanged:: 5.0
Support for using :ref:`enumeration types <field-choices-enum-types>`
directly in the ``choices`` was added.
Support for mappings and using
:ref:`enumeration types <field-choices-enum-types>` directly in
``choices`` was added.
``DateField``
-------------

View file

@ -58,11 +58,11 @@ widget on the field. In the following example, the
from django import forms
BIRTH_YEAR_CHOICES = ["1980", "1981", "1982"]
FAVORITE_COLORS_CHOICES = [
("blue", "Blue"),
("green", "Green"),
("black", "Black"),
]
FAVORITE_COLORS_CHOICES = {
"blue": "Blue",
"green": "Green",
"black": "Black",
}
class SimpleForm(forms.Form):
@ -95,7 +95,7 @@ example:
.. code-block:: pycon
>>> from django import forms
>>> CHOICES = [("1", "First"), ("2", "Second")]
>>> CHOICES = {"1": "First", "2": "Second"}
>>> choice_field = forms.ChoiceField(widget=forms.RadioSelect, choices=CHOICES)
>>> choice_field.choices
[('1', 'First'), ('2', 'Second')]
@ -458,9 +458,9 @@ foundation for custom widgets.
class DateSelectorWidget(forms.MultiWidget):
def __init__(self, attrs=None):
days = [(day, day) for day in range(1, 32)]
months = [(month, month) for month in range(1, 13)]
years = [(year, year) for year in [2018, 2019, 2020]]
days = {day: day for day in range(1, 32)}
months = {month: month for month in range(1, 13)}
years = {year: year for year in [2018, 2019, 2020]}
widgets = [
forms.Select(attrs=attrs, choices=days),
forms.Select(attrs=attrs, choices=months),

View file

@ -22,11 +22,11 @@ We'll be using the following model in the subsequent examples::
REGULAR = "R"
GOLD = "G"
PLATINUM = "P"
ACCOUNT_TYPE_CHOICES = [
(REGULAR, "Regular"),
(GOLD, "Gold"),
(PLATINUM, "Platinum"),
]
ACCOUNT_TYPE_CHOICES = {
REGULAR: "Regular",
GOLD: "Gold",
PLATINUM: "Platinum",
}
name = models.CharField(max_length=50)
registered_on = models.DateField()
account_type = models.CharField(

View file

@ -86,14 +86,26 @@ If a field has ``blank=False``, the field will be required.
.. attribute:: Field.choices
A :term:`sequence` consisting itself of iterables of exactly two items (e.g.
``[(A, B), (A, B) ...]``) to use as choices for this field. If choices are
given, they're enforced by :ref:`model validation <validating-objects>` and the
default form widget will be a select box with these choices instead of the
standard text field.
A mapping or iterable in the format described below to use as choices for this
field. If choices are given, they're enforced by
:ref:`model validation <validating-objects>` and 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 set on the model,
and the second element is the human-readable name. For example::
If a mapping is given, the key element is the actual value to be set on the
model, and the second element is the human readable name. For example::
YEAR_IN_SCHOOL_CHOICES = {
"FR": "Freshman",
"SO": "Sophomore",
"JR": "Junior",
"SR": "Senior",
"GR": "Graduate",
}
You can also pass a :term:`sequence` consisting itself of iterables of exactly
two items (e.g. ``[(A1, B1), (A2, B2), …]``). The first element in each tuple
is the actual value to be set on the model, and the second element is the
human-readable name. For example::
YEAR_IN_SCHOOL_CHOICES = [
("FR", "Freshman"),
@ -103,6 +115,10 @@ and the second element is the human-readable name. For example::
("GR", "Graduate"),
]
.. versionchanged:: 5.0
Support for mappings was added.
Generally, it's best to define choices inside a model class, and to
define a suitably-named constant for each value::
@ -115,13 +131,13 @@ define a suitably-named constant for each value::
JUNIOR = "JR"
SENIOR = "SR"
GRADUATE = "GR"
YEAR_IN_SCHOOL_CHOICES = [
(FRESHMAN, "Freshman"),
(SOPHOMORE, "Sophomore"),
(JUNIOR, "Junior"),
(SENIOR, "Senior"),
(GRADUATE, "Graduate"),
]
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,
@ -142,6 +158,25 @@ will work anywhere that the ``Student`` model has been imported).
You can also collect your available choices into named groups that can
be used for organizational purposes::
MEDIA_CHOICES = {
"Audio": {
"vinyl": "Vinyl",
"cd": "CD",
},
"Video": {
"vhs": "VHS Tape",
"dvd": "DVD",
},
"unknown": "Unknown",
}
The key of the mapping is the name to apply to the group and the value is the
choices inside that group, consisting of the field value and a human-readable
name for an option. Grouped options may be combined with ungrouped options
within a single mapping (such as the ``"unknown"`` option in this example).
You can also use a sequence, e.g. a list of 2-tuples::
MEDIA_CHOICES = [
(
"Audio",
@ -160,17 +195,6 @@ be used for organizational purposes::
("unknown", "Unknown"),
]
The first element in each tuple is the name to apply to the group. The
second element is an iterable of 2-tuples, with each 2-tuple containing
a value and a human-readable name for an option. Grouped options may be
combined with ungrouped options within a single list (such as the
``'unknown'`` option in this example).
For each model field that has :attr:`~Field.choices` set, Django will add a
method to retrieve the human-readable name for the field's current value. See
:meth:`~django.db.models.Model.get_FOO_display` in the database API
documentation.
Note that choices can be any sequence object -- not necessarily a list or
tuple. This lets you construct choices dynamically. But if you find yourself
hacking :attr:`~Field.choices` to be dynamic, you're probably better off using
@ -180,6 +204,12 @@ meant for static data that doesn't change much, if ever.
.. note::
A new migration is created each time the order of ``choices`` changes.
For each model field that has :attr:`~Field.choices` set, Django will normalize
the choices to a list of 2-tuples and add a method to retrieve the
human-readable name for the field's current value. See
:meth:`~django.db.models.Model.get_FOO_display` in the database API
documentation.
.. _field-choices-blank-label:
Unless :attr:`blank=False<Field.blank>` is set on the field along with a

View file

@ -912,11 +912,11 @@ For example::
class Person(models.Model):
SHIRT_SIZES = [
("S", "Small"),
("M", "Medium"),
("L", "Large"),
]
SHIRT_SIZES = {
"S": "Small",
"M": "Medium",
"L": "Large",
}
name = models.CharField(max_length=60)
shirt_size = models.CharField(max_length=2, choices=SHIRT_SIZES)