summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
Diffstat (limited to 'docs')
-rw-r--r--docs/internals/contributing/writing-code/coding-style.txt17
-rw-r--r--docs/ref/checks.txt8
-rw-r--r--docs/ref/contrib/admin/actions.txt10
-rw-r--r--docs/ref/forms/fields.txt5
-rw-r--r--docs/ref/forms/widgets.txt18
-rw-r--r--docs/ref/models/conditional-expressions.txt10
-rw-r--r--docs/ref/models/fields.txt80
-rw-r--r--docs/ref/models/instances.txt10
-rw-r--r--docs/releases/5.0.txt57
-rw-r--r--docs/topics/db/managers.txt8
-rw-r--r--docs/topics/db/models.txt10
-rw-r--r--docs/topics/forms/modelforms.txt10
12 files changed, 160 insertions, 83 deletions
diff --git a/docs/internals/contributing/writing-code/coding-style.txt b/docs/internals/contributing/writing-code/coding-style.txt
index d227e04ba0..6871d43d7b 100644
--- a/docs/internals/contributing/writing-code/coding-style.txt
+++ b/docs/internals/contributing/writing-code/coding-style.txt
@@ -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``
===============================
diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt
index a208114596..cd539aa964 100644
--- a/docs/ref/checks.txt
+++ b/docs/ref/checks.txt
@@ -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.
diff --git a/docs/ref/contrib/admin/actions.txt b/docs/ref/contrib/admin/actions.txt
index f5f91982c1..263d43f8a3 100644
--- a/docs/ref/contrib/admin/actions.txt
+++ b/docs/ref/contrib/admin/actions.txt
@@ -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):
diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt
index 307ebb15a2..5e42404f94 100644
--- a/docs/ref/forms/fields.txt
+++ b/docs/ref/forms/fields.txt
@@ -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``
-------------
diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt
index efff81ddbc..f76759b254 100644
--- a/docs/ref/forms/widgets.txt
+++ b/docs/ref/forms/widgets.txt
@@ -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),
diff --git a/docs/ref/models/conditional-expressions.txt b/docs/ref/models/conditional-expressions.txt
index d14312870f..cfdbd0790a 100644
--- a/docs/ref/models/conditional-expressions.txt
+++ b/docs/ref/models/conditional-expressions.txt
@@ -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(
diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt
index ff67312949..fbc90e5420 100644
--- a/docs/ref/models/fields.txt
+++ b/docs/ref/models/fields.txt
@@ -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
diff --git a/docs/ref/models/instances.txt b/docs/ref/models/instances.txt
index 6bfd521aaa..6ceb0703ab 100644
--- a/docs/ref/models/instances.txt
+++ b/docs/ref/models/instances.txt
@@ -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)
diff --git a/docs/releases/5.0.txt b/docs/releases/5.0.txt
index 36c2c650fa..cae3668bfc 100644
--- a/docs/releases/5.0.txt
+++ b/docs/releases/5.0.txt
@@ -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.
diff --git a/docs/topics/db/managers.txt b/docs/topics/db/managers.txt
index 047d02ebae..61de153898 100644
--- a/docs/topics/db/managers.txt
+++ b/docs/topics/db/managers.txt
@@ -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
diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt
index 33a515f14f..cc6c1f5298 100644
--- a/docs/topics/db/models.txt
+++ b/docs/topics/db/models.txt
@@ -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)
diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt
index 7f3c042f30..fbd5695c17 100644
--- a/docs/topics/forms/modelforms.txt
+++ b/docs/topics/forms/modelforms.txt
@@ -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):