mirror of
https://github.com/django/django.git
synced 2025-10-24 06:06:09 +00:00
Fixed #15667 -- Added template-based widget rendering.
Thanks Carl Meyer and Tim Graham for contributing to the patch.
This commit is contained in:
committed by
Tim Graham
parent
51cde873d9
commit
b52c73008a
@@ -720,6 +720,29 @@ When set to ``True`` (the default), required form fields will have the
|
||||
``use_required_attribute=False`` to avoid incorrect browser validation when
|
||||
adding and deleting forms from a formset.
|
||||
|
||||
Configuring the rendering of a form's widgets
|
||||
---------------------------------------------
|
||||
|
||||
.. attribute:: Form.default_renderer
|
||||
|
||||
.. versionadded:: 1.11
|
||||
|
||||
Specifies the :doc:`renderer <renderers>` to use for the form. Defaults to
|
||||
``None`` which means to use the default renderer specified by the
|
||||
:setting:`FORM_RENDERER` setting.
|
||||
|
||||
You can set this as a class attribute when declaring your form or use the
|
||||
``renderer`` argument to ``Form.__init__()``. For example::
|
||||
|
||||
from django import forms
|
||||
|
||||
class MyForm(forms.Form):
|
||||
default_renderer = MyRenderer()
|
||||
|
||||
or::
|
||||
|
||||
form = MyForm(renderer=MyRenderer())
|
||||
|
||||
Notes on field ordering
|
||||
-----------------------
|
||||
|
||||
|
||||
@@ -12,5 +12,6 @@ Detailed form API reference. For introductory material, see the
|
||||
fields
|
||||
models
|
||||
formsets
|
||||
renderers
|
||||
widgets
|
||||
validation
|
||||
|
||||
131
docs/ref/forms/renderers.txt
Normal file
131
docs/ref/forms/renderers.txt
Normal file
@@ -0,0 +1,131 @@
|
||||
======================
|
||||
The form rendering API
|
||||
======================
|
||||
|
||||
.. module:: django.forms.renderers
|
||||
:synopsis: Built-in form renderers.
|
||||
|
||||
.. versionadded:: 1.11
|
||||
|
||||
In older versions, widgets are rendered using Python. All APIs described
|
||||
in this document are new.
|
||||
|
||||
Django's form widgets are rendered using Django's :doc:`template engines
|
||||
system </topics/templates>`.
|
||||
|
||||
The form rendering process can be customized at several levels:
|
||||
|
||||
* Widgets can specify custom template names.
|
||||
* Forms and widgets can specify custom renderer classes.
|
||||
* A widget's template can be overridden by a project. (Reusable applications
|
||||
typically shouldn't override built-in templates because they might conflict
|
||||
with a project's custom templates.)
|
||||
|
||||
.. _low-level-widget-render-api:
|
||||
|
||||
The low-level render API
|
||||
========================
|
||||
|
||||
The rendering of form templates is controlled by a customizable renderer class.
|
||||
A custom renderer can be specified by updating the :setting:`FORM_RENDERER`
|
||||
setting. It defaults to
|
||||
``'``:class:`django.forms.renderers.DjangoTemplates`\ ``'``.
|
||||
|
||||
You can also provide a custom renderer by setting the
|
||||
:attr:`.Form.default_renderer` attribute or by using the ``renderer`` argument
|
||||
of :meth:`.Widget.render`.
|
||||
|
||||
Use one of the :ref:`built-in template form renderers
|
||||
<built-in-template-form-renderers>` or implement your own. Custom renderers
|
||||
must implement a ``render(template_name, context, request=None)`` method. It
|
||||
should return a rendered templates (as a string) or raise
|
||||
:exc:`~django.template.TemplateDoesNotExist`.
|
||||
|
||||
.. _built-in-template-form-renderers:
|
||||
|
||||
Built-in-template form renderers
|
||||
================================
|
||||
|
||||
``DjangoTemplates``
|
||||
-------------------
|
||||
|
||||
.. class:: DjangoTemplates
|
||||
|
||||
This renderer uses a standalone
|
||||
:class:`~django.template.backends.django.DjangoTemplates`
|
||||
engine (unconnected to what you might have configured in the
|
||||
:setting:`TEMPLATES` setting). It loads templates first from the built-in form
|
||||
templates directory in ``django/forms/templates`` and then from the installed
|
||||
apps' templates directories using the :class:`app_directories
|
||||
<django.template.loaders.app_directories.Loader>` loader.
|
||||
|
||||
If you want to render templates with customizations from your
|
||||
:setting:`TEMPLATES` setting, such as context processors for example, use the
|
||||
:class:`TemplatesSetting` renderer.
|
||||
|
||||
``Jinja2``
|
||||
----------
|
||||
|
||||
.. class:: Jinja2
|
||||
|
||||
This renderer is the same as the :class:`DjangoTemplates` renderer except that
|
||||
it uses a :class:`~django.template.backends.jinja2.Jinja2` backend. Templates
|
||||
for the built-in widgets are located in ``django/forms/jinja2`` and installed
|
||||
apps can provide templates in a ``jinja2`` directory.
|
||||
|
||||
To use this backend, all the widgets in your project and its third-party apps
|
||||
must have Jinja2 templates. Unless you provide your own Jinja2 templates for
|
||||
widgets that don't have any, you can't use this renderer. For example,
|
||||
:mod:`django.contrib.admin` doesn't include Jinja2 templates for its widgets
|
||||
due to their usage of Django template tags.
|
||||
|
||||
``TemplatesSetting``
|
||||
--------------------
|
||||
|
||||
.. class:: TemplatesSetting
|
||||
|
||||
This renderer gives you complete control of how widget templates are sourced.
|
||||
It uses :func:`~django.template.loader.get_template` to find widget
|
||||
templates based on what's configured in the :setting:`TEMPLATES` setting.
|
||||
|
||||
Using this renderer along with the built-in widget templates requires either:
|
||||
|
||||
#. ``'django.forms'`` in :setting:`INSTALLED_APPS` and at least one engine
|
||||
with :setting:`APP_DIRS=True <TEMPLATES-APP_DIRS>`.
|
||||
|
||||
#. Adding the built-in widgets templates directory (``django/forms/templates``
|
||||
or ``django/forms/jinja2``) in :setting:`DIRS <TEMPLATES-DIRS>` of one of
|
||||
your template engines.
|
||||
|
||||
Using this renderer requires you to make sure the form templates your project
|
||||
needs can be located.
|
||||
|
||||
Context available in widget templates
|
||||
=====================================
|
||||
|
||||
Widget templates receive a context from :meth:`.Widget.get_context`. By
|
||||
default, widgets receive a single value in the context, ``widget``. This is a
|
||||
dictionary that contains values like:
|
||||
|
||||
* ``name``
|
||||
* ``value``
|
||||
* ``attrs``
|
||||
* ``is_hidden``
|
||||
* ``template_name``
|
||||
|
||||
Some widgets add further information to the context. For instance, all widgets
|
||||
that subclass ``Input`` defines ``widget['type']`` and :class:`.MultiWidget`
|
||||
defines ``widget['subwidgets']`` for looping purposes.
|
||||
|
||||
Overriding built-in widget templates
|
||||
====================================
|
||||
|
||||
Each widget has a ``template_name`` attribute with a value such as
|
||||
``input.html``. Built-in widget templates are stored in the
|
||||
``django/forms/widgets`` path. You can provide a custom template for
|
||||
``input.html`` by defining ``django/forms/widgets/input.html``, for example.
|
||||
See :ref:`built-in widgets` for the name of each widget's template.
|
||||
|
||||
If you use the :class:`TemplatesSetting` renderer, overriding widget templates
|
||||
works the same as overriding any other template in your project. You can't
|
||||
override built-in widget templates using the other built-in renderers.
|
||||
@@ -241,6 +241,28 @@ foundation for custom widgets.
|
||||
In older versions, this method is a private API named
|
||||
``_format_value()``. The old name will work until Django 2.0.
|
||||
|
||||
.. method:: get_context(name, value, attrs=None)
|
||||
|
||||
.. versionadded:: 1.11
|
||||
|
||||
Returns a dictionary of values to use when rendering the widget
|
||||
template. By default, the dictionary contains a single key,
|
||||
``'widget'``, which is a dictionary representation of the widget
|
||||
containing the following keys:
|
||||
|
||||
* ``'name'``: The name of the field from the ``name`` argument.
|
||||
* ``'is_hidden'``: A boolean indicating whether or not this widget is
|
||||
hidden.
|
||||
* ``'required'``: A boolean indicating whether or not the field for
|
||||
this widget is required.
|
||||
* ``'value'``: The value as returned by :meth:`format_value`.
|
||||
* ``'attrs'``: HTML attributes to be set on the rendered widget. The
|
||||
combination of the :attr:`attrs` attribute and the ``attrs`` argument.
|
||||
* ``'template_name'``: The value of ``self.template_name``.
|
||||
|
||||
``Widget`` subclasses can provide custom context values by overriding
|
||||
this method.
|
||||
|
||||
.. method:: id_for_label(self, id_)
|
||||
|
||||
Returns the HTML ID attribute of this widget for use by a ``<label>``,
|
||||
@@ -251,14 +273,16 @@ foundation for custom widgets.
|
||||
return an ID value that corresponds to the first ID in the widget's
|
||||
tags.
|
||||
|
||||
.. method:: render(name, value, attrs=None)
|
||||
.. method:: render(name, value, attrs=None, renderer=None)
|
||||
|
||||
Returns HTML for the widget, as a Unicode string. This method must be
|
||||
implemented by the subclass, otherwise ``NotImplementedError`` will be
|
||||
raised.
|
||||
Renders a widget to HTML using the given renderer. If ``renderer`` is
|
||||
``None``, the renderer from the :setting:`FORM_RENDERER` setting is
|
||||
used.
|
||||
|
||||
The 'value' given is not guaranteed to be valid input, therefore
|
||||
subclass implementations should program defensively.
|
||||
.. versionchanged:: 1.11
|
||||
|
||||
The ``renderer`` argument was added. Support for subclasses that
|
||||
don't accept it will be removed in Django 2.1.
|
||||
|
||||
.. method:: value_from_datadict(data, files, name)
|
||||
|
||||
@@ -360,40 +384,21 @@ foundation for custom widgets.
|
||||
with the opposite responsibility - to combine cleaned values of
|
||||
all member fields into one.
|
||||
|
||||
Other methods that may be useful to override include:
|
||||
It provides some custom context:
|
||||
|
||||
.. method:: render(name, value, attrs=None)
|
||||
.. method:: get_context(name, value, attrs=None)
|
||||
|
||||
Argument ``value`` is handled differently in this method from the
|
||||
subclasses of :class:`~Widget` because it has to figure out how to
|
||||
split a single value for display in multiple widgets.
|
||||
In addition to the ``'widget'`` key described in
|
||||
:meth:`Widget.get_context`, ``MultiValueWidget`` adds a
|
||||
``widget['subwidgets']`` key.
|
||||
|
||||
The ``value`` argument used when rendering can be one of two things:
|
||||
These can be looped over in the widget template:
|
||||
|
||||
* A ``list``.
|
||||
* A single value (e.g., a string) that is the "compressed" representation
|
||||
of a ``list`` of values.
|
||||
.. code-block:: html+django
|
||||
|
||||
If ``value`` is a list, the output of :meth:`~MultiWidget.render` will
|
||||
be a concatenation of rendered child widgets. If ``value`` is not a
|
||||
list, it will first be processed by the method
|
||||
:meth:`~MultiWidget.decompress()` to create the list and then rendered.
|
||||
|
||||
When ``render()`` executes its HTML rendering, each value in the list
|
||||
is rendered with the corresponding widget -- the first value is
|
||||
rendered in the first widget, the second value is rendered in the
|
||||
second widget, etc.
|
||||
|
||||
Unlike in the single value widgets, method :meth:`~MultiWidget.render`
|
||||
need not be implemented in the subclasses.
|
||||
|
||||
.. method:: format_output(rendered_widgets)
|
||||
|
||||
Given a list of rendered widgets (as strings), returns a Unicode string
|
||||
representing the HTML for the whole lot.
|
||||
|
||||
This hook allows you to format the HTML design of the widgets any way
|
||||
you'd like.
|
||||
{% for subwidget in widget.subwidgets %}
|
||||
{% include widget.template_name with widget=subwidget %}
|
||||
{% endfor %}
|
||||
|
||||
Here's an example widget which subclasses :class:`MultiWidget` to display
|
||||
a date with the day, month, and year in different select boxes. This widget
|
||||
@@ -421,9 +426,6 @@ foundation for custom widgets.
|
||||
return [value.day, value.month, value.year]
|
||||
return [None, None, None]
|
||||
|
||||
def format_output(self, rendered_widgets):
|
||||
return ''.join(rendered_widgets)
|
||||
|
||||
def value_from_datadict(self, data, files, name):
|
||||
datelist = [
|
||||
widget.value_from_datadict(data, files, name + '_%s' % i)
|
||||
@@ -442,11 +444,6 @@ foundation for custom widgets.
|
||||
The constructor creates several :class:`Select` widgets in a tuple. The
|
||||
``super`` class uses this tuple to setup the widget.
|
||||
|
||||
The :meth:`~MultiWidget.format_output` method is fairly vanilla here (in
|
||||
fact, it's the same as what's been implemented as the default for
|
||||
``MultiWidget``), but the idea is that you could add custom HTML between
|
||||
the widgets should you wish.
|
||||
|
||||
The required method :meth:`~MultiWidget.decompress` breaks up a
|
||||
``datetime.date`` value into the day, month, and year values corresponding
|
||||
to each widget. Note how the method handles the case where ``value`` is
|
||||
@@ -485,14 +482,18 @@ These widgets make use of the HTML elements ``input`` and ``textarea``.
|
||||
|
||||
.. class:: TextInput
|
||||
|
||||
Text input: ``<input type="text" ...>``
|
||||
* ``input_type``: ``'text'``
|
||||
* ``template_name``: ``'django/forms/widgets/text.html'``
|
||||
* Renders as: ``<input type="text" ...>``
|
||||
|
||||
``NumberInput``
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
.. class:: NumberInput
|
||||
|
||||
Text input: ``<input type="number" ...>``
|
||||
* ``input_type``: ``'number'``
|
||||
* ``template_name``: ``'django/forms/widgets/number.html'``
|
||||
* Renders as: ``<input type="number" ...>``
|
||||
|
||||
Beware that not all browsers support entering localized numbers in
|
||||
``number`` input types. Django itself avoids using them for fields having
|
||||
@@ -503,21 +504,27 @@ These widgets make use of the HTML elements ``input`` and ``textarea``.
|
||||
|
||||
.. class:: EmailInput
|
||||
|
||||
Text input: ``<input type="email" ...>``
|
||||
* ``input_type``: ``'email'``
|
||||
* ``template_name``: ``'django/forms/widgets/email.html'``
|
||||
* Renders as: ``<input type="email" ...>``
|
||||
|
||||
``URLInput``
|
||||
~~~~~~~~~~~~
|
||||
|
||||
.. class:: URLInput
|
||||
|
||||
Text input: ``<input type="url" ...>``
|
||||
* ``input_type``: ``'url'``
|
||||
* ``template_name``: ``'django/forms/widgets/url.html'``
|
||||
* Renders as: ``<input type="url" ...>``
|
||||
|
||||
``PasswordInput``
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. class:: PasswordInput
|
||||
|
||||
Password input: ``<input type='password' ...>``
|
||||
* ``input_type``: ``'password'``
|
||||
* ``template_name``: ``'django/forms/widgets/password.html'``
|
||||
* Renders as: ``<input type='password' ...>``
|
||||
|
||||
Takes one optional argument:
|
||||
|
||||
@@ -531,7 +538,9 @@ These widgets make use of the HTML elements ``input`` and ``textarea``.
|
||||
|
||||
.. class:: HiddenInput
|
||||
|
||||
Hidden input: ``<input type='hidden' ...>``
|
||||
* ``input_type``: ``'hidden'``
|
||||
* ``template_name``: ``'django/forms/widgets/hidden.html'``
|
||||
* Renders as: ``<input type='hidden' ...>``
|
||||
|
||||
Note that there also is a :class:`MultipleHiddenInput` widget that
|
||||
encapsulates a set of hidden input elements.
|
||||
@@ -541,7 +550,9 @@ These widgets make use of the HTML elements ``input`` and ``textarea``.
|
||||
|
||||
.. class:: DateInput
|
||||
|
||||
Date input as a simple text box: ``<input type='text' ...>``
|
||||
* ``input_type``: ``'text'``
|
||||
* ``template_name``: ``'django/forms/widgets/date.html'``
|
||||
* Renders as: ``<input type='text' ...>``
|
||||
|
||||
Takes same arguments as :class:`TextInput`, with one more optional argument:
|
||||
|
||||
@@ -558,7 +569,9 @@ These widgets make use of the HTML elements ``input`` and ``textarea``.
|
||||
|
||||
.. class:: DateTimeInput
|
||||
|
||||
Date/time input as a simple text box: ``<input type='text' ...>``
|
||||
* ``input_type``: ``'text'``
|
||||
* ``template_name``: ``'django/forms/widgets/datetime.html'``
|
||||
* Renders as: ``<input type='text' ...>``
|
||||
|
||||
Takes same arguments as :class:`TextInput`, with one more optional argument:
|
||||
|
||||
@@ -579,7 +592,9 @@ These widgets make use of the HTML elements ``input`` and ``textarea``.
|
||||
|
||||
.. class:: TimeInput
|
||||
|
||||
Time input as a simple text box: ``<input type='text' ...>``
|
||||
* ``input_type``: ``'text'``
|
||||
* ``template_name``: ``'django/forms/widgets/time.html'``
|
||||
* Renders as: ``<input type='text' ...>``
|
||||
|
||||
Takes same arguments as :class:`TextInput`, with one more optional argument:
|
||||
|
||||
@@ -598,7 +613,8 @@ These widgets make use of the HTML elements ``input`` and ``textarea``.
|
||||
|
||||
.. class:: Textarea
|
||||
|
||||
Text area: ``<textarea>...</textarea>``
|
||||
* ``template_name``: ``'django/forms/widgets/textarea.html'``
|
||||
* Renders as: ``<textarea>...</textarea>``
|
||||
|
||||
.. _selector-widgets:
|
||||
|
||||
@@ -610,7 +626,9 @@ Selector and checkbox widgets
|
||||
|
||||
.. class:: CheckboxInput
|
||||
|
||||
Checkbox: ``<input type='checkbox' ...>``
|
||||
* ``input_type``: ``'checkbox'``
|
||||
* ``template_name``: ``'django/forms/widgets/checkbox.html'``
|
||||
* Renders as: ``<input type='checkbox' ...>``
|
||||
|
||||
Takes one optional argument:
|
||||
|
||||
@@ -624,7 +642,8 @@ Selector and checkbox widgets
|
||||
|
||||
.. class:: Select
|
||||
|
||||
Select widget: ``<select><option ...>...</select>``
|
||||
* ``template_name``: ``'django/forms/widgets/select.html'``
|
||||
* Renders as: ``<select><option ...>...</select>``
|
||||
|
||||
.. attribute:: Select.choices
|
||||
|
||||
@@ -637,6 +656,8 @@ Selector and checkbox widgets
|
||||
|
||||
.. class:: NullBooleanSelect
|
||||
|
||||
* ``template_name``: ``'django/forms/widgets/select.html'``
|
||||
|
||||
Select widget with options 'Unknown', 'Yes' and 'No'
|
||||
|
||||
``SelectMultiple``
|
||||
@@ -644,6 +665,8 @@ Selector and checkbox widgets
|
||||
|
||||
.. class:: SelectMultiple
|
||||
|
||||
* ``template_name``: ``'django/forms/widgets/select.html'``
|
||||
|
||||
Similar to :class:`Select`, but allows multiple selection:
|
||||
``<select multiple='multiple'>...</select>``
|
||||
|
||||
@@ -652,6 +675,8 @@ Selector and checkbox widgets
|
||||
|
||||
.. class:: RadioSelect
|
||||
|
||||
* ``template_name``: ``'django/forms/widgets/radio.html'``
|
||||
|
||||
Similar to :class:`Select`, but rendered as a list of radio buttons within
|
||||
``<li>`` tags:
|
||||
|
||||
@@ -744,6 +769,8 @@ Selector and checkbox widgets
|
||||
|
||||
.. class:: CheckboxSelectMultiple
|
||||
|
||||
* ``template_name``: ``'django/forms/widgets/checkbox_select.html'``
|
||||
|
||||
Similar to :class:`SelectMultiple`, but rendered as a list of check
|
||||
buttons:
|
||||
|
||||
@@ -776,16 +803,18 @@ File upload widgets
|
||||
|
||||
.. class:: FileInput
|
||||
|
||||
File upload input: ``<input type='file' ...>``
|
||||
* ``template_name``: ``'django/forms/widgets/file.html'``
|
||||
* Renders as: ``<input type='file' ...>``
|
||||
|
||||
``ClearableFileInput``
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. class:: ClearableFileInput
|
||||
|
||||
File upload input: ``<input type='file' ...>``, with an additional checkbox
|
||||
input to clear the field's value, if the field is not required and has
|
||||
initial data.
|
||||
* ``template_name``: ``'django/forms/widgets/clearable_file_input.html'``
|
||||
* Renders as: ``<input type='file' ...>`` with an additional checkbox
|
||||
input to clear the field's value, if the field is not required and has
|
||||
initial data.
|
||||
|
||||
.. _composite-widgets:
|
||||
|
||||
@@ -797,7 +826,8 @@ Composite widgets
|
||||
|
||||
.. class:: MultipleHiddenInput
|
||||
|
||||
Multiple ``<input type='hidden' ...>`` widgets.
|
||||
* ``template_name``: ``'django/forms/widgets/multiple_hidden.html'``
|
||||
* Renders as: multiple ``<input type='hidden' ...>`` tags
|
||||
|
||||
A widget that handles multiple hidden widgets for fields that have a list
|
||||
of values.
|
||||
@@ -813,6 +843,8 @@ Composite widgets
|
||||
|
||||
.. class:: SplitDateTimeWidget
|
||||
|
||||
* ``template_name``: ``'django/forms/widgets/splitdatetime.html'``
|
||||
|
||||
Wrapper (using :class:`MultiWidget`) around two widgets: :class:`DateInput`
|
||||
for the date, and :class:`TimeInput` for the time. Must be used with
|
||||
:class:`SplitDateTimeField` rather than :class:`DateTimeField`.
|
||||
@@ -832,6 +864,8 @@ Composite widgets
|
||||
|
||||
.. class:: SplitHiddenDateTimeWidget
|
||||
|
||||
* ``template_name``: ``'django/forms/widgets/splithiddendatetime.html'``
|
||||
|
||||
Similar to :class:`SplitDateTimeWidget`, but uses :class:`HiddenInput` for
|
||||
both date and time.
|
||||
|
||||
@@ -840,6 +874,8 @@ Composite widgets
|
||||
|
||||
.. class:: SelectDateWidget
|
||||
|
||||
* ``template_name``: ``'django/forms/widgets/select_date.html'``
|
||||
|
||||
Wrapper around three :class:`~django.forms.Select` widgets: one each for
|
||||
month, day, and year.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user