Fixed #35521 -- Allowed overriding BoundField class on fields, forms and renderers.

Thank you Sarah Boyce, Carlton Gibson, Tim Schilling and Adam Johnson
for reviews.

Co-authored-by: Christophe Henry <contact@c-henry.fr>
Co-authored-by: David Smith <smithdc@gmail.com>
Co-authored-by: Natalia <124304+nessita@users.noreply.github.com>
Co-authored-by: Matthias Kestenholz <mk@feinheit.ch>
This commit is contained in:
Matthias Kestenholz 2025-01-15 21:04:26 +01:00 committed by GitHub
parent 0cabed9efa
commit 6a7ee02f59
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 362 additions and 14 deletions

View file

@ -822,6 +822,9 @@ classes, as needed. The HTML will look something like:
>>> f["subject"].legend_tag(attrs={"class": "foo"})
<legend for="id_subject" class="foo required">Subject:</legend>
You may further modify the rendering of form rows by using a
:ref:`custom BoundField <custom-boundfield>`.
.. _ref-forms-api-configuring-label:
Configuring form elements' HTML ``id`` attributes and ``<label>`` tags
@ -1149,6 +1152,12 @@ they're not the only way a form object can be displayed.
The ``__str__()`` method of this object displays the HTML for this field.
You can use :attr:`.Form.bound_field_class` and
:attr:`.Field.bound_field_class` to specify a different ``BoundField`` class
per form or per field, respectively.
See :ref:`custom-boundfield` for examples of overriding a ``BoundField``.
To retrieve a single ``BoundField``, use dictionary lookup syntax on your form
using the field's name as the key:
@ -1488,23 +1497,34 @@ Methods of ``BoundField``
>>> print(bound_form["subject"].value())
hi
.. _custom-boundfield:
Customizing ``BoundField``
==========================
If you need to access some additional information about a form field in a
template and using a subclass of :class:`~django.forms.Field` isn't
sufficient, consider also customizing :class:`~django.forms.BoundField`.
.. attribute:: Form.bound_field_class
A custom form field can override ``get_bound_field()``:
.. versionadded:: 5.2
.. method:: Field.get_bound_field(form, field_name)
Define a custom :class:`~django.forms.BoundField` class to use when rendering
the form. This takes precedence over the project-level
:attr:`.BaseRenderer.bound_field_class` (along with a custom
:setting:`FORM_RENDERER`), but can be overridden by the field-level
:attr:`.Field.bound_field_class`.
Takes an instance of :class:`~django.forms.Form` and the name of the field.
The return value will be used when accessing the field in a template. Most
likely it will be an instance of a subclass of
:class:`~django.forms.BoundField`.
If not defined as a class variable, ``bound_field_class`` can be set via the
``bound_field_class`` argument in the :class:`Form` or :class:`Field`
constructor.
If you have a ``GPSCoordinatesField``, for example, and want to be able to
For compatibility reasons, a custom form field can still override
:meth:`.Field.get_bound_field()` to use a custom class, though any of the
previous options are preferred.
You may want to use a custom :class:`.BoundField` if you need to access some
additional information about a form field in a template and using a subclass of
:class:`~django.forms.Field` isn't sufficient.
For example, if you have a ``GPSCoordinatesField``, and want to be able to
access additional information about the coordinates in a template, this could
be implemented as follows::
@ -1523,12 +1543,74 @@ be implemented as follows::
class GPSCoordinatesField(Field):
def get_bound_field(self, form, field_name):
return GPSCoordinatesBoundField(form, self, field_name)
bound_field_class = GPSCoordinatesBoundField
Now you can access the country in a template with
``{{ form.coordinates.country }}``.
You may also want to customize the default form field template rendering. For
example, you can override :meth:`.BoundField.label_tag` to add a custom class::
class StyledLabelBoundField(BoundField):
def label_tag(self, contents=None, attrs=None, label_suffix=None, tag=None):
attrs = attrs or {}
attrs["class"] = "wide"
return super().label_tag(contents, attrs, label_suffix, tag)
class UserForm(forms.Form):
bound_field_class = StyledLabelBoundField
name = CharField()
This would update the default form rendering:
.. code-block:: pycon
>>> f = UserForm()
>>> print(f["name"].label_tag)
<label for="id_name" class="wide">Name:</label>
To add a CSS class to the wrapping HTML element of all fields, a ``BoundField``
can be overridden to return a different collection of CSS classes::
class WrappedBoundField(BoundField):
def css_classes(self, extra_classes=None):
parent_css_classes = super().css_classes(extra_classes)
return f"field-class {parent_css_classes}".strip()
class UserForm(forms.Form):
bound_field_class = WrappedBoundField
name = CharField()
This would update the form rendering as follows:
.. code-block:: pycon
>>> f = UserForm()
>>> print(f)
<div class="field-class"><label for="id_name">Name:</label><input type="text" name="name" required id="id_name"></div>
Alternatively, to override the ``BoundField`` class at the project level,
:attr:`.BaseRenderer.bound_field_class` can be defined on a custom
:setting:`FORM_RENDERER`:
.. code-block:: python
:caption: ``mysite/renderers.py``
from django.forms.renderers import DjangoTemplates
from .forms import CustomBoundField
class CustomRenderer(DjangoTemplates):
bound_field_class = CustomBoundField
.. code-block:: python
:caption: ``settings.py``
FORM_RENDERER = "mysite.renderers.CustomRenderer"
.. _binding-uploaded-files:
Binding uploaded files to a form

View file

@ -397,6 +397,16 @@ default this value is set to ``"django/forms/field.html"``. Can be changed per
field by overriding this attribute or more generally by overriding the default
template, see also :ref:`overriding-built-in-field-templates`.
``bound_field_class``
---------------------
.. attribute:: Field.bound_field_class
.. versionadded:: 5.2
The ``bound_field_class`` attribute allows a per-field override of
:attr:`.Form.bound_field_class`.
Checking if the field data has changed
======================================
@ -1635,4 +1645,14 @@ only requirements are that it implement a ``clean()`` method and that its
``label``, ``initial``, ``widget``, ``help_text``).
You can also customize how a field will be accessed by overriding
:meth:`~django.forms.Field.get_bound_field()`.
:attr:`~django.forms.Field.bound_field_class` or override
:meth:`.Field.get_bound_field()` if you need more flexibility when creating
the ``BoundField``:
.. method:: Field.get_bound_field(form, field_name)
Takes an instance of :class:`~django.forms.Form` and the name of the field.
The returned :class:`.BoundField` instance will be used when accessing the
field in a template.
See :ref:`custom-boundfield` for examples of overriding a ``BoundField``.

View file

@ -65,6 +65,18 @@ should return a rendered templates (as a string) or raise
Defaults to ``"django/forms/field.html"``
.. attribute:: bound_field_class
.. versionadded:: 5.2
The default class used to represent form fields across the project.
Defaults to :class:`.BoundField` class.
This can be customized further using :attr:`.Form.bound_field_class`
for per-form overrides, or :attr:`.Field.bound_field_class` for
per-field overrides.
.. method:: get_template(template_name)
Subclasses must implement this method with the appropriate template