mirror of
https://github.com/django/django.git
synced 2025-10-23 21:59:11 +00:00
Fixed #31262 -- Added support for mappings on model fields and ChoiceField's choices.
This commit is contained in:
@@ -298,16 +298,23 @@ Model style
|
||||
* Any custom methods
|
||||
|
||||
* If ``choices`` is defined for a given model field, define each choice as a
|
||||
list of tuples, with an all-uppercase name as a class attribute on the model.
|
||||
mapping, with an all-uppercase name as a class attribute on the model.
|
||||
Example::
|
||||
|
||||
class MyModel(models.Model):
|
||||
DIRECTION_UP = "U"
|
||||
DIRECTION_DOWN = "D"
|
||||
DIRECTION_CHOICES = [
|
||||
(DIRECTION_UP, "Up"),
|
||||
(DIRECTION_DOWN, "Down"),
|
||||
]
|
||||
DIRECTION_CHOICES = {
|
||||
DIRECTION_UP: "Up",
|
||||
DIRECTION_DOWN: "Down",
|
||||
}
|
||||
|
||||
Alternatively, consider using :ref:`field-choices-enum-types`::
|
||||
|
||||
class MyModel(models.Model):
|
||||
class Direction(models.TextChoices):
|
||||
UP = U, "Up"
|
||||
DOWN = D, "Down"
|
||||
|
||||
Use of ``django.conf.settings``
|
||||
===============================
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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``
|
||||
-------------
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -129,6 +129,55 @@ sets a database-computed default value. For example::
|
||||
created = models.DateTimeField(db_default=Now())
|
||||
circumference = models.FloatField(db_default=2 * Pi())
|
||||
|
||||
More options for declaring field choices
|
||||
----------------------------------------
|
||||
|
||||
:attr:`.Field.choices` *(for model fields)* and :attr:`.ChoiceField.choices`
|
||||
*(for form fields)* allow for more flexibility when declaring their values. In
|
||||
previous versions of Django, ``choices`` should either be a list of 2-tuples,
|
||||
or an :ref:`field-choices-enum-types` subclass, but the latter required
|
||||
accessing the ``.choices`` attribute to provide the values in the expected
|
||||
form::
|
||||
|
||||
from django.db import models
|
||||
|
||||
Medal = models.TextChoices("Medal", "GOLD SILVER BRONZE")
|
||||
|
||||
SPORT_CHOICES = [
|
||||
("Martial Arts", [("judo", "Judo"), ("karate", "Karate")]),
|
||||
("Racket", [("badminton", "Badminton"), ("tennis", "Tennis")]),
|
||||
("unknown", "Unknown"),
|
||||
]
|
||||
|
||||
|
||||
class Winners(models.Model):
|
||||
name = models.CharField(...)
|
||||
medal = models.CharField(..., choices=Medal.choices)
|
||||
sport = models.CharField(..., choices=SPORT_CHOICES)
|
||||
|
||||
Django 5.0 supports providing a mapping instead of an iterable, and also no
|
||||
longer requires ``.choices`` to be used directly to expand :ref:`enumeration
|
||||
types <field-choices-enum-types>`::
|
||||
|
||||
from django.db import models
|
||||
|
||||
Medal = models.TextChoices("Medal", "GOLD SILVER BRONZE")
|
||||
|
||||
SPORT_CHOICES = { # Using a mapping instead of a list of 2-tuples.
|
||||
"Martial Arts": {"judo": "Judo", "karate": "Karate"},
|
||||
"Racket": {"badminton": "Badminton", "tennis": "Tennis"},
|
||||
"unknown": "Unknown",
|
||||
}
|
||||
|
||||
|
||||
class Winners(models.Model):
|
||||
name = models.CharField(...)
|
||||
medal = models.CharField(..., choices=Medal) # Using `.choices` not required.
|
||||
sport = models.CharField(..., choices=SPORT_CHOICES)
|
||||
|
||||
Under the hood the provided ``choices`` are normalized into a list of 2-tuples
|
||||
as the canonical form whenever the ``choices`` value is updated.
|
||||
|
||||
Minor features
|
||||
--------------
|
||||
|
||||
@@ -304,10 +353,6 @@ File Uploads
|
||||
Forms
|
||||
~~~~~
|
||||
|
||||
* :attr:`.ChoiceField.choices` now accepts
|
||||
:ref:`Choices classes <field-choices-enum-types>` directly instead of
|
||||
requiring expansion with the ``choices`` attribute.
|
||||
|
||||
* The new ``assume_scheme`` argument for :class:`~django.forms.URLField` allows
|
||||
specifying a default URL scheme.
|
||||
|
||||
@@ -357,10 +402,6 @@ Models
|
||||
of ``ValidationError`` raised during
|
||||
:ref:`model validation <validating-objects>`.
|
||||
|
||||
* :attr:`.Field.choices` now accepts
|
||||
:ref:`Choices classes <field-choices-enum-types>` directly instead of
|
||||
requiring expansion with the ``choices`` attribute.
|
||||
|
||||
* The :ref:`force_insert <ref-models-force-insert>` argument of
|
||||
:meth:`.Model.save` now allows specifying a tuple of parent classes that must
|
||||
be forced to be inserted.
|
||||
|
||||
@@ -154,9 +154,7 @@ For example::
|
||||
class Person(models.Model):
|
||||
first_name = models.CharField(max_length=50)
|
||||
last_name = models.CharField(max_length=50)
|
||||
role = models.CharField(
|
||||
max_length=1, choices=[("A", _("Author")), ("E", _("Editor"))]
|
||||
)
|
||||
role = models.CharField(max_length=1, choices={"A": _("Author"), "E": _("Editor")})
|
||||
people = models.Manager()
|
||||
authors = AuthorManager()
|
||||
editors = EditorManager()
|
||||
@@ -259,9 +257,7 @@ custom ``QuerySet`` if you also implement them on the ``Manager``::
|
||||
class Person(models.Model):
|
||||
first_name = models.CharField(max_length=50)
|
||||
last_name = models.CharField(max_length=50)
|
||||
role = models.CharField(
|
||||
max_length=1, choices=[("A", _("Author")), ("E", _("Editor"))]
|
||||
)
|
||||
role = models.CharField(max_length=1, choices={"A": _("Author"), "E": _("Editor")})
|
||||
people = PersonManager()
|
||||
|
||||
This example allows you to call both ``authors()`` and ``editors()`` directly from
|
||||
|
||||
@@ -185,11 +185,11 @@ ones:
|
||||
|
||||
|
||||
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=1, choices=SHIRT_SIZES)
|
||||
|
||||
|
||||
@@ -173,11 +173,11 @@ Consider this set of models::
|
||||
from django.db import models
|
||||
from django.forms import ModelForm
|
||||
|
||||
TITLE_CHOICES = [
|
||||
("MR", "Mr."),
|
||||
("MRS", "Mrs."),
|
||||
("MS", "Ms."),
|
||||
]
|
||||
TITLE_CHOICES = {
|
||||
"MR": "Mr.",
|
||||
"MRS": "Mrs.",
|
||||
"MS": "Ms.",
|
||||
}
|
||||
|
||||
|
||||
class Author(models.Model):
|
||||
|
||||
Reference in New Issue
Block a user