diff options
| author | Matthias Kestenholz <mk@feinheit.ch> | 2025-01-15 21:04:26 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-01-15 17:04:26 -0300 |
| commit | 6a7ee02f5994c65bbefe92a3da74f22326970cf9 (patch) | |
| tree | d49780a99dbb0ad8b85522b55dcaf54fafde0264 /docs/ref/forms | |
| parent | 0cabed9efa2c7abd1693860069f20ec5db41fcd8 (diff) | |
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>
Diffstat (limited to 'docs/ref/forms')
| -rw-r--r-- | docs/ref/forms/api.txt | 106 | ||||
| -rw-r--r-- | docs/ref/forms/fields.txt | 22 | ||||
| -rw-r--r-- | docs/ref/forms/renderers.txt | 12 |
3 files changed, 127 insertions, 13 deletions
diff --git a/docs/ref/forms/api.txt b/docs/ref/forms/api.txt index 1b1ecbec6c..9b827ca69a 100644 --- a/docs/ref/forms/api.txt +++ b/docs/ref/forms/api.txt @@ -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 diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index c0ab4f2434..7ae5c121eb 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -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``. diff --git a/docs/ref/forms/renderers.txt b/docs/ref/forms/renderers.txt index e527a70c57..f52534651a 100644 --- a/docs/ref/forms/renderers.txt +++ b/docs/ref/forms/renderers.txt @@ -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 |
