Refs #32339 -- Restructured outputting HTML form docs.

In the topic doc, promoted the Reusable form templates section.

In the reference, re-grouped and promoted the default __str__()
rendering path, and then gathered the various as_*() helpers
subsequently.

Thanks to David Smith for review.
This commit is contained in:
Carlton Gibson 2022-05-03 16:07:01 +02:00 committed by Carlton Gibson
parent 5d91dc8ee3
commit fde946daff
2 changed files with 188 additions and 157 deletions

View File

@ -520,9 +520,15 @@ Although ``<table>`` output is the default output style when you ``print`` a
form, other output styles are available. Each style is available as a method on form, other output styles are available. Each style is available as a method on
a form object, and each rendering method returns a string. a form object, and each rendering method returns a string.
``template_name`` Default rendering
----------------- -----------------
The default rendering when you ``print`` a form uses the following methods and
attributes.
``template_name``
~~~~~~~~~~~~~~~~~
.. versionadded:: 4.0 .. versionadded:: 4.0
.. attribute:: Form.template_name .. attribute:: Form.template_name
@ -540,95 +546,8 @@ class.
In older versions ``template_name`` defaulted to the string value In older versions ``template_name`` defaulted to the string value
``'django/forms/default.html'``. ``'django/forms/default.html'``.
``template_name_label``
-----------------------
.. versionadded:: 4.0
.. attribute:: Form.template_name_label
The template used to render a field's ``<label>``, used when calling
:meth:`BoundField.label_tag`/:meth:`~BoundField.legend_tag`. Can be changed per
form by overriding this attribute or more generally by overriding the default
template, see also :ref:`overriding-built-in-form-templates`.
``as_p()``
----------
.. method:: Form.as_p()
``as_p()`` renders the form using the template assigned to the forms
``template_name_p`` attribute, by default this template is
``'django/forms/p.html'``. This template renders the form as a series of
``<p>`` tags, with each ``<p>`` containing one field::
>>> f = ContactForm()
>>> f.as_p()
'<p><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" required></p>\n<p><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" required></p>\n<p><label for="id_sender">Sender:</label> <input type="text" name="sender" id="id_sender" required></p>\n<p><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself"></p>'
>>> print(f.as_p())
<p><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" required></p>
<p><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" required></p>
<p><label for="id_sender">Sender:</label> <input type="email" name="sender" id="id_sender" required></p>
<p><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself"></p>
``as_ul()``
-----------
.. method:: Form.as_ul()
``as_ul()`` renders the form using the template assigned to the forms
``template_name_ul`` attribute, by default this template is
``'django/forms/ul.html'``. This template renders the form as a series of
``<li>`` tags, with each ``<li>`` containing one field. It does *not* include
the ``<ul>`` or ``</ul>``, so that you can specify any HTML attributes on the
``<ul>`` for flexibility::
>>> f = ContactForm()
>>> f.as_ul()
'<li><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" required></li>\n<li><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" required></li>\n<li><label for="id_sender">Sender:</label> <input type="email" name="sender" id="id_sender" required></li>\n<li><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself"></li>'
>>> print(f.as_ul())
<li><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" required></li>
<li><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" required></li>
<li><label for="id_sender">Sender:</label> <input type="email" name="sender" id="id_sender" required></li>
<li><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself"></li>
``as_table()``
--------------
.. method:: Form.as_table()
Finally, ``as_table()`` renders the form using the template assigned to the
forms ``template_name_table`` attribute, by default this template is
``'django/forms/table.html'``. This template outputs the form as an HTML
``<table>``::
>>> f = ContactForm()
>>> f.as_table()
'<tr><th><label for="id_subject">Subject:</label></th><td><input id="id_subject" type="text" name="subject" maxlength="100" required></td></tr>\n<tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" required></td></tr>\n<tr><th><label for="id_sender">Sender:</label></th><td><input type="email" name="sender" id="id_sender" required></td></tr>\n<tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself"></td></tr>'
>>> print(f)
<tr><th><label for="id_subject">Subject:</label></th><td><input id="id_subject" type="text" name="subject" maxlength="100" required></td></tr>
<tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" required></td></tr>
<tr><th><label for="id_sender">Sender:</label></th><td><input type="email" name="sender" id="id_sender" required></td></tr>
<tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself"></td></tr>
``get_context()``
-----------------
.. versionadded:: 4.0
.. method:: Form.get_context()
Return context for form rendering in a template.
The available context is:
* ``form``: The bound form.
* ``fields``: All bound fields, except the hidden fields.
* ``hidden_fields``: All hidden bound fields.
* ``errors``: All non field related or hidden field related form errors.
``render()`` ``render()``
------------ ~~~~~~~~~~~~
.. versionadded:: 4.0 .. versionadded:: 4.0
@ -642,6 +561,115 @@ All arguments are optional and default to:
* ``context``: Value returned by :meth:`.Form.get_context` * ``context``: Value returned by :meth:`.Form.get_context`
* ``renderer``: Value returned by :attr:`.Form.default_renderer` * ``renderer``: Value returned by :attr:`.Form.default_renderer`
By passing ``template_name`` you can customize the template used for just a
single call.
``get_context()``
~~~~~~~~~~~~~~~~~
.. versionadded:: 4.0
.. method:: Form.get_context()
Return the template context for rendering the form.
The available context is:
* ``form``: The bound form.
* ``fields``: All bound fields, except the hidden fields.
* ``hidden_fields``: All hidden bound fields.
* ``errors``: All non field related or hidden field related form errors.
``template_name_label``
~~~~~~~~~~~~~~~~~~~~~~~
.. versionadded:: 4.0
.. attribute:: Form.template_name_label
The template used to render a field's ``<label>``, used when calling
:meth:`BoundField.label_tag`/:meth:`~BoundField.legend_tag`. Can be changed per
form by overriding this attribute or more generally by overriding the default
template, see also :ref:`overriding-built-in-form-templates`.
Output styles
-------------
As well as rendering the form directly, such as in a template with
``{{ form }}``, the following helper functions serve as a proxy to
:meth:`Form.render` passing a particular ``template_name`` value.
These helpers are most useful in a template, where you need to override the
form renderer or form provided value but cannot pass the additional parameter
to :meth:`~Form.render`. For example, you can render a form as an unordered
list using ``{{ form.as_ul }}``.
Each helper pairs a form method with an attribute giving the appropriate
template name.
``as_p()``
~~~~~~~~~~
.. attribute:: Form.template_name_p
The template used by ``as_p()``. Default: ``'django/forms/p.html'``.
.. method:: Form.as_p()
``as_p()`` renders the form as a series of ``<p>`` tags, with each ``<p>``
containing one field::
>>> f = ContactForm()
>>> f.as_p()
'<p><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" required></p>\n<p><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" required></p>\n<p><label for="id_sender">Sender:</label> <input type="text" name="sender" id="id_sender" required></p>\n<p><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself"></p>'
>>> print(f.as_p())
<p><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" required></p>
<p><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" required></p>
<p><label for="id_sender">Sender:</label> <input type="email" name="sender" id="id_sender" required></p>
<p><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself"></p>
``as_ul()``
~~~~~~~~~~~
.. attribute:: Form.template_name_ul
The template used by ``as_ul()``. Default: ``'django/forms/ul.html'``.
.. method:: Form.as_ul()
``as_ul()`` renders the form as a series of ``<li>`` tags, with each ``<li>``
containing one field. It does *not* include the ``<ul>`` or ``</ul>``, so that
you can specify any HTML attributes on the ``<ul>`` for flexibility::
>>> f = ContactForm()
>>> f.as_ul()
'<li><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" required></li>\n<li><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" required></li>\n<li><label for="id_sender">Sender:</label> <input type="email" name="sender" id="id_sender" required></li>\n<li><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself"></li>'
>>> print(f.as_ul())
<li><label for="id_subject">Subject:</label> <input id="id_subject" type="text" name="subject" maxlength="100" required></li>
<li><label for="id_message">Message:</label> <input type="text" name="message" id="id_message" required></li>
<li><label for="id_sender">Sender:</label> <input type="email" name="sender" id="id_sender" required></li>
<li><label for="id_cc_myself">Cc myself:</label> <input type="checkbox" name="cc_myself" id="id_cc_myself"></li>
``as_table()``
~~~~~~~~~~~~~~
.. attribute:: Form.template_name_table
The template used by ``as_table()``. Default: ``'django/forms/table.html'``.
.. method:: Form.as_table()
``as_table()`` renders the form as an HTML ``<table>``::
>>> f = ContactForm()
>>> f.as_table()
'<tr><th><label for="id_subject">Subject:</label></th><td><input id="id_subject" type="text" name="subject" maxlength="100" required></td></tr>\n<tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" required></td></tr>\n<tr><th><label for="id_sender">Sender:</label></th><td><input type="email" name="sender" id="id_sender" required></td></tr>\n<tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself"></td></tr>'
>>> print(f)
<tr><th><label for="id_subject">Subject:</label></th><td><input id="id_subject" type="text" name="subject" maxlength="100" required></td></tr>
<tr><th><label for="id_message">Message:</label></th><td><input type="text" name="message" id="id_message" required></td></tr>
<tr><th><label for="id_sender">Sender:</label></th><td><input type="email" name="sender" id="id_sender" required></td></tr>
<tr><th><label for="id_cc_myself">Cc myself:</label></th><td><input type="checkbox" name="cc_myself" id="id_cc_myself"></td></tr>
.. _ref-forms-api-styling-form-rows: .. _ref-forms-api-styling-form-rows:
Styling required or erroneous form rows Styling required or erroneous form rows

View File

@ -487,15 +487,83 @@ instance into the template context. So if your form is called ``form`` in the
context, ``{{ form }}`` will render its ``<label>`` and ``<input>`` elements context, ``{{ form }}`` will render its ``<label>`` and ``<input>`` elements
appropriately. appropriately.
Form rendering options
----------------------
.. admonition:: Additional form template furniture .. admonition:: Additional form template furniture
Don't forget that a form's output does *not* include the surrounding Don't forget that a form's output does *not* include the surrounding
``<form>`` tags, or the form's ``submit`` control. You will have to provide ``<form>`` tags, or the form's ``submit`` control. You will have to provide
these yourself. these yourself.
Reusable form templates
-----------------------
The HTML output when rendering a form is itself generated via a template. You
can control this by creating an appropriate template file and setting a custom
:setting:`FORM_RENDERER` to use that
:attr:`~django.forms.renderers.BaseRenderer.form_template_name` site-wide. You
can also customize per-form by overriding the form's
:attr:`~django.forms.Form.template_name` attribute to render the form using the
custom template, or by passing the template name directly to
:meth:`.Form.render`.
The example below will result in ``{{ form }}`` being rendered as the output of
the ``form_snippet.html`` template.
In your templates:
.. code-block:: html+django
# In your template:
{{ form }}
# In form_snippet.html:
{% for field in form %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
</div>
{% endfor %}
Then you can configure the :setting:`FORM_RENDERER` setting:
.. code-block:: python
:caption: settings.py
from django.forms.renderers import TemplatesSetting
class CustomFormRenderer(TemplatesSetting):
form_template_name = "form_snippet.html"
FORM_RENDERER = "project.settings.CustomFormRenderer"
… or for a single form::
class MyForm(forms.Form):
template_name = "form_snippet.html"
...
… or for a single render of a form instance, passing in the template name to
the :meth:`.Form.render`. Here's an example of this being used in a view::
def index(request):
form = MyForm()
rendered_form = form.render("form_snippet.html")
context = {'form': rendered_form}
return render(request, 'index.html', context)
See :ref:`ref-forms-api-outputting-html` for more details.
.. versionchanged:: 4.0
Template rendering of forms was added.
.. versionchanged:: 4.1
The ability to set the default ``form_template_name`` on the form renderer
was added.
Form rendering options
----------------------
There are other output options though for the ``<label>``/``<input>`` pairs: There are other output options though for the ``<label>``/``<input>`` pairs:
* ``{{ form.as_table }}`` will render them as table cells wrapped in ``<tr>`` * ``{{ form.as_table }}`` will render them as table cells wrapped in ``<tr>``
@ -754,71 +822,6 @@ error in a hidden field is a sign of form tampering, since normal form
interaction won't alter them. However, you could easily insert some error interaction won't alter them. However, you could easily insert some error
displays for those form errors, as well. displays for those form errors, as well.
Reusable form templates
-----------------------
If your site uses the same rendering logic for forms in multiple places, you
can reduce duplication by saving the form's loop in a standalone template and
setting a custom :setting:`FORM_RENDERER` to use that
:attr:`~django.forms.renderers.BaseRenderer.form_template_name` site-wide. You
can also customize per-form by overriding the form's
:attr:`~django.forms.Form.template_name` attribute to render the form using the
custom template.
The below example will result in ``{{ form }}`` being rendered as the output of
the ``form_snippet.html`` template.
In your templates:
.. code-block:: html+django
# In your template:
{{ form }}
# In form_snippet.html:
{% for field in form %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
</div>
{% endfor %}
Then you can configure the :setting:`FORM_RENDERER` setting:
.. code-block:: python
:caption: settings.py
from django.forms.renderers import TemplatesSetting
class CustomFormRenderer(TemplatesSetting):
form_template_name = "form_snippet.html"
FORM_RENDERER = "project.settings.CustomFormRenderer"
… or for a single form::
class MyForm(forms.Form):
template_name = "form_snippet.html"
...
… or for a single render of a form instance, passing in the template name to
the :meth:`.Form.render()`. Here's an example of this being used in a view::
def index(request):
form = MyForm()
rendered_form = form.render("form_snippet.html")
context = {'form': rendered_form}
return render(request, 'index.html', context)
.. versionchanged:: 4.0
Template rendering of forms was added.
.. versionchanged:: 4.1
The ability to set the default ``form_template_name`` on the form renderer
was added.
Further topics Further topics
============== ==============