diff options
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/internals/contributing/writing-code/coding-style.txt | 17 | ||||
| -rw-r--r-- | docs/ref/checks.txt | 8 | ||||
| -rw-r--r-- | docs/ref/contrib/admin/actions.txt | 10 | ||||
| -rw-r--r-- | docs/ref/forms/fields.txt | 5 | ||||
| -rw-r--r-- | docs/ref/forms/widgets.txt | 18 | ||||
| -rw-r--r-- | docs/ref/models/conditional-expressions.txt | 10 | ||||
| -rw-r--r-- | docs/ref/models/fields.txt | 80 | ||||
| -rw-r--r-- | docs/ref/models/instances.txt | 10 | ||||
| -rw-r--r-- | docs/releases/5.0.txt | 57 | ||||
| -rw-r--r-- | docs/topics/db/managers.txt | 8 | ||||
| -rw-r--r-- | docs/topics/db/models.txt | 10 | ||||
| -rw-r--r-- | docs/topics/forms/modelforms.txt | 10 |
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): |
