Refs #27910 -- Improved documentation for model field choice enumeration types.

This commit is contained in:
Nick Pope 2019-09-13 14:37:41 +01:00 committed by Mariusz Felisiak
parent ea25bdc2b9
commit 1c66767d4e
2 changed files with 89 additions and 63 deletions

View File

@ -130,6 +130,59 @@ model class keeps all of that information with the class that uses it,
and helps reference the choices (e.g, ``Student.SOPHOMORE``
will work anywhere that the ``Student`` model has been imported).
.. _field-choices-named-groups:
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 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
a proper database table with a :class:`ForeignKey`. :attr:`~Field.choices` is
meant for static data that doesn't change much, if ever.
.. note::
A new migration is created each time the order of ``choices`` changes.
.. _field-choices-blank-label:
Unless :attr:`blank=False<Field.blank>` is set on the field along with a
:attr:`~Field.default` then a label containing ``"---------"`` will be rendered
with the select box. To override this behavior, add a tuple to ``choices``
containing ``None``; e.g. ``(None, 'Your String For Display')``.
Alternatively, you can use an empty string instead of ``None`` where this makes
sense - such as on a :class:`~django.db.models.CharField`.
.. _field-choices-enum-types:
Enumeration types
~~~~~~~~~~~~~~~~~
In addition, Django provides enumeration types that you can subclass to define
choices in a concise way::
@ -156,11 +209,17 @@ choices in a concise way::
These work similar to :mod:`enum` from Python's standard library, but with some
modifications:
* Instead of values in the ``enum``, Django uses ``(value, label)`` tuples. The
``label`` can be a lazy translatable string. If a tuple is not provided, the
label is automatically generated from the member name.
* ``.label`` property is added on values, to return the label specified.
* Number of custom properties are added to the enumeration classes --
* Enum member values are a tuple of arguments to use when constructing the
concrete data type. Django supports adding an extra string value to the end
of this tuple to be used as the human-readable name, or ``label``. The
``label`` can be a lazy translatable string. Thus, in most cases, the member
value will be a ``(value, label)`` two-tuple. See below for :ref:`an example
of subclassing choices <field-choices-enum-subclassing>` using a more complex
data type. If a tuple is not provided, or the last item is not a (lazy)
string, the ``label`` is :ref:`automatically generated
<field-choices-enum-auto-label>` from the member name.
* A ``.label`` property is added on values, to return the human-readable name.
* A number of custom properties are added to the enumeration classes --
``.choices``, ``.labels``, ``.values``, and ``.names`` -- to make it easier
to access lists of those separate parts of the enumeration. Use ``.choices``
as a suitable value to pass to :attr:`~Field.choices` in a field definition.
@ -168,23 +227,26 @@ modifications:
defined multiple times. This is unlikely to be expected in choices for a
field.
Note that ``YearInSchool.SENIOR``, ``YearInSchool['SENIOR']``,
``YearInSchool('SR')`` work as expected, while ``YearInSchool.SENIOR.label`` is
a translatable string.
Note that using ``YearInSchool.SENIOR``, ``YearInSchool['SENIOR']``, or
``YearInSchool('SR')`` to access or lookup enum members work as expected, as do
the ``.name`` and ``.value`` properties on the members.
.. _field-choices-enum-auto-label:
If you don't need to have the human-readable names translated, you can have
them inferred from the member name (replacing underscores to spaces and using
them inferred from the member name (replacing underscores with spaces and using
title-case)::
class YearInSchool(models.TextChoices):
FRESHMAN = 'FR'
SOPHOMORE = 'SO'
JUNIOR = 'JR'
SENIOR = 'SR'
GRADUATE = 'GR'
>>> class Vehicle(models.TextChoices):
... CAR = 'C'
... TRUCK = 'T'
... JET_SKI = 'J'
...
>>> Vehicle.JET_SKI.label
'Jet Ski'
Since the case where the enum values need to be integers is extremely common,
Django provides a ``IntegerChoices`` class. For example::
Django provides an ``IntegerChoices`` class. For example::
class Card(models.Model):
@ -207,9 +269,11 @@ that labels are automatically generated as highlighted above::
>>> Place.choices
[(1, 'First'), (2, 'Second'), (3, 'Third')]
.. _field-choices-enum-subclassing:
If you require support for a concrete data type other than ``int`` or ``str``,
you can subclass ``Choices`` and the required concrete data type, e.g.
:class:``datetime.date`` for use with :class:`~django.db.models.DateField`::
:class:`~datetime.date` for use with :class:`~django.db.models.DateField`::
class MoonLandings(datetime.date, models.Choices):
APOLLO_11 = 1969, 7, 20, 'Apollo 11 (Eagle)'
@ -219,52 +283,14 @@ you can subclass ``Choices`` and the required concrete data type, e.g.
APOLLO_16 = 1972, 4, 21, 'Apollo 16 (Orion)'
APOLLO_17 = 1972, 12, 11, 'Apollo 17 (Challenger)'
You can also collect your available choices into named groups that can
be used for organizational purposes::
There are some additional caveats to be aware of:
MEDIA_CHOICES = [
('Audio', (
('vinyl', 'Vinyl'),
('cd', 'CD'),
)
),
('Video', (
('vhs', 'VHS Tape'),
('dvd', 'DVD'),
)
),
('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). Grouping is not supported by the custom
enumeration types for managing choices.
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
a proper database table with a :class:`ForeignKey`. :attr:`~Field.choices` is
meant for static data that doesn't change much, if ever.
.. note::
A new migration is created each time the order of ``choices`` changes.
Unless :attr:`blank=False<Field.blank>` is set on the field along with a
:attr:`~Field.default` then a label containing ``"---------"`` will be rendered
with the select box. To override this behavior, add a tuple to ``choices``
containing ``None``; e.g. ``(None, 'Your String For Display')``.
Alternatively, you can use an empty string instead of ``None`` where this makes
sense - such as on a :class:`~django.db.models.CharField`. To change the label
when using one of the custom enumeration types, set the ``__empty__`` attribute
on the class::
- Enumeration types do not support :ref:`named groups
<field-choices-named-groups>`.
- Because an enumeration with a concrete data type requires all values to match
the type, overriding the :ref:`blank label <field-choices-blank-label>`
cannot be achieved by creating a member with a value of ``None``. Instead,
set the ``__empty__`` attribute on the class::
class Answer(models.IntegerChoices):
NO = 0, _('No')

View File

@ -90,7 +90,7 @@ and ``IntegerChoices`` types are provided for text and integer fields. The
``Choices`` class allows defining a compatible enumeration for other concrete
data types. These custom enumeration types support human-readable labels that
can be translated and accessed via a property on the enumeration or its
members. See :ref:`Field.choices documentation <field-choices>` for more
members. See :ref:`Enumeration types <field-choices-enum-types>` for more
details and examples.
Minor features