Browse Source

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

Nick Pope 5 years ago
parent
commit
1c66767d4e
2 changed files with 89 additions and 63 deletions
  1. 88 62
      docs/ref/models/fields.txt
  2. 1 1
      docs/releases/3.0.txt

+ 88 - 62
docs/ref/models/fields.txt

@@ -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::
-
-    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.
+There are some additional caveats to be aware of:
 
-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')

+ 1 - 1
docs/releases/3.0.txt

@@ -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