Refs #32339 -- Allowed renderer to specify default form and formset templates.
Co-authored-by: David Smith <smithdc@gmail.com>
This commit is contained in:
parent
832096478c
commit
476d4d5087
|
@ -66,7 +66,6 @@ class BaseForm(RenderableFormMixin):
|
||||||
prefix = None
|
prefix = None
|
||||||
use_required_attribute = True
|
use_required_attribute = True
|
||||||
|
|
||||||
template_name = "django/forms/default.html"
|
|
||||||
template_name_p = "django/forms/p.html"
|
template_name_p = "django/forms/p.html"
|
||||||
template_name_table = "django/forms/table.html"
|
template_name_table = "django/forms/table.html"
|
||||||
template_name_ul = "django/forms/ul.html"
|
template_name_ul = "django/forms/ul.html"
|
||||||
|
@ -316,6 +315,10 @@ class BaseForm(RenderableFormMixin):
|
||||||
output.append(str_hidden)
|
output.append(str_hidden)
|
||||||
return mark_safe("\n".join(output))
|
return mark_safe("\n".join(output))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def template_name(self):
|
||||||
|
return self.renderer.form_template_name
|
||||||
|
|
||||||
def get_context(self):
|
def get_context(self):
|
||||||
fields = []
|
fields = []
|
||||||
hidden_fields = []
|
hidden_fields = []
|
||||||
|
|
|
@ -62,7 +62,7 @@ class BaseFormSet(RenderableFormMixin):
|
||||||
"%(field_names)s. You may need to file a bug report if the issue persists."
|
"%(field_names)s. You may need to file a bug report if the issue persists."
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
template_name = "django/forms/formsets/default.html"
|
|
||||||
template_name_p = "django/forms/formsets/p.html"
|
template_name_p = "django/forms/formsets/p.html"
|
||||||
template_name_table = "django/forms/formsets/table.html"
|
template_name_table = "django/forms/formsets/table.html"
|
||||||
template_name_ul = "django/forms/formsets/ul.html"
|
template_name_ul = "django/forms/formsets/ul.html"
|
||||||
|
@ -517,6 +517,10 @@ class BaseFormSet(RenderableFormMixin):
|
||||||
else:
|
else:
|
||||||
return self.empty_form.media
|
return self.empty_form.media
|
||||||
|
|
||||||
|
@property
|
||||||
|
def template_name(self):
|
||||||
|
return self.renderer.formset_template_name
|
||||||
|
|
||||||
def get_context(self):
|
def get_context(self):
|
||||||
return {"formset": self}
|
return {"formset": self}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,9 @@ def get_default_renderer():
|
||||||
|
|
||||||
|
|
||||||
class BaseRenderer:
|
class BaseRenderer:
|
||||||
|
form_template_name = "django/forms/default.html"
|
||||||
|
formset_template_name = "django/forms/formsets/default.html"
|
||||||
|
|
||||||
def get_template(self, template_name):
|
def get_template(self, template_name):
|
||||||
raise NotImplementedError("subclasses must implement get_template()")
|
raise NotImplementedError("subclasses must implement get_template()")
|
||||||
|
|
||||||
|
|
|
@ -527,12 +527,18 @@ a form object, and each rendering method returns a string.
|
||||||
|
|
||||||
.. attribute:: Form.template_name
|
.. attribute:: Form.template_name
|
||||||
|
|
||||||
The name of a template that is going to be rendered if the form is cast into a
|
The name of the template rendered if the form is cast into a string, e.g. via
|
||||||
string, e.g. via ``print(form)`` or in a template via ``{{ form }}``. By
|
``print(form)`` or in a template via ``{{ form }}``.
|
||||||
default this template is ``'django/forms/default.html'``, which is a proxy for
|
|
||||||
``'django/forms/table.html'``. The template can be changed per form by
|
By default, a property returning the value of the renderer's
|
||||||
overriding the ``template_name`` attribute or more generally by overriding the
|
:attr:`~django.forms.renderers.BaseRenderer.form_template_name`. You may set it
|
||||||
default template, see also :ref:`overriding-built-in-form-templates`.
|
as a string template name in order to override that for a particular form
|
||||||
|
class.
|
||||||
|
|
||||||
|
.. versionchanged:: 4.1
|
||||||
|
|
||||||
|
In older versions ``template_name`` defaulted to the string value
|
||||||
|
``'django/forms/default.html'``.
|
||||||
|
|
||||||
``template_name_label``
|
``template_name_label``
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
|
@ -26,9 +26,16 @@ A custom renderer can be specified by updating the :setting:`FORM_RENDERER`
|
||||||
setting. It defaults to
|
setting. It defaults to
|
||||||
``'``:class:`django.forms.renderers.DjangoTemplates`\ ``'``.
|
``'``:class:`django.forms.renderers.DjangoTemplates`\ ``'``.
|
||||||
|
|
||||||
You can also provide a custom renderer by setting the
|
By specifying a custom form renderer and overriding
|
||||||
|
:attr:`~.BaseRenderer.form_template_name` you can adjust the default form
|
||||||
|
markup across your project from a single place.
|
||||||
|
|
||||||
|
You can also provide a custom renderer per-form or per-widget by setting the
|
||||||
:attr:`.Form.default_renderer` attribute or by using the ``renderer`` argument
|
:attr:`.Form.default_renderer` attribute or by using the ``renderer`` argument
|
||||||
of :meth:`.Widget.render`.
|
of :meth:`.Form.render`, or :meth:`.Widget.render`.
|
||||||
|
|
||||||
|
Matching points apply to formset rendering. See :ref:`formset-rendering` for
|
||||||
|
discussion.
|
||||||
|
|
||||||
Use one of the :ref:`built-in template form renderers
|
Use one of the :ref:`built-in template form renderers
|
||||||
<built-in-template-form-renderers>` or implement your own. Custom renderers
|
<built-in-template-form-renderers>` or implement your own. Custom renderers
|
||||||
|
@ -40,6 +47,24 @@ should return a rendered templates (as a string) or raise
|
||||||
|
|
||||||
The base class for the built-in form renderers.
|
The base class for the built-in form renderers.
|
||||||
|
|
||||||
|
.. attribute:: form_template_name
|
||||||
|
|
||||||
|
.. versionadded:: 4.1
|
||||||
|
|
||||||
|
The default name of the template to use to render a form.
|
||||||
|
|
||||||
|
Defaults to ``"django/forms/default.html"``, which is a proxy for
|
||||||
|
``"django/forms/table.html"``.
|
||||||
|
|
||||||
|
.. attribute:: formset_template_name
|
||||||
|
|
||||||
|
.. versionadded:: 4.1
|
||||||
|
|
||||||
|
The default name of the template to use to render a formset.
|
||||||
|
|
||||||
|
Defaults to ``"django/forms/formsets/default.html"``, which is a proxy
|
||||||
|
for ``"django/forms/formsets/table.html"``.
|
||||||
|
|
||||||
.. method:: get_template(template_name)
|
.. method:: get_template(template_name)
|
||||||
|
|
||||||
Subclasses must implement this method with the appropriate template
|
Subclasses must implement this method with the appropriate template
|
||||||
|
|
|
@ -244,6 +244,20 @@ File Uploads
|
||||||
Forms
|
Forms
|
||||||
~~~~~
|
~~~~~
|
||||||
|
|
||||||
|
* The default template used to render forms when cast to a string, e.g. in
|
||||||
|
templates as ``{{ form }}``, is now configurable at the project-level by
|
||||||
|
setting :attr:`~django.forms.renderers.BaseRenderer.form_template_name` on
|
||||||
|
the class provided for :setting:`FORM_RENDERER`.
|
||||||
|
|
||||||
|
:attr:`.Form.template_name` is now a property deferring to the renderer, but
|
||||||
|
may be overridden with a string value to specify the template name per-form
|
||||||
|
class.
|
||||||
|
|
||||||
|
Similarly, the default template used to render formsets can be specified via
|
||||||
|
the matching
|
||||||
|
:attr:`~django.forms.renderers.BaseRenderer.formset_template_name` renderer
|
||||||
|
attribute.
|
||||||
|
|
||||||
* The new :meth:`~django.forms.BoundField.legend_tag` allows rendering field
|
* The new :meth:`~django.forms.BoundField.legend_tag` allows rendering field
|
||||||
labels in ``<legend>`` tags via the new ``tag`` argument of
|
labels in ``<legend>`` tags via the new ``tag`` argument of
|
||||||
:meth:`~django.forms.BoundField.label_tag`.
|
:meth:`~django.forms.BoundField.label_tag`.
|
||||||
|
|
|
@ -783,11 +783,22 @@ Formsets have the following attributes and methods associated with rendering:
|
||||||
|
|
||||||
.. versionadded:: 4.0
|
.. versionadded:: 4.0
|
||||||
|
|
||||||
The name of the template used when calling ``__str__`` or :meth:`.render`.
|
The name of the template rendered if the formset is cast into a string,
|
||||||
This template renders the formset's management form and then each form in
|
e.g. via ``print(formset)`` or in a template via ``{{ formset }}``.
|
||||||
the formset as per the template defined by the form's
|
|
||||||
:attr:`~django.forms.Form.template_name`. This is a proxy of ``as_table``
|
By default, a property returning the value of the renderer's
|
||||||
by default.
|
:attr:`~django.forms.renderers.BaseRenderer.formset_template_name`. You may
|
||||||
|
set it as a string template name in order to override that for a particular
|
||||||
|
formset class.
|
||||||
|
|
||||||
|
This template will be used to render the formset's management form, and
|
||||||
|
then each form in the formset as per the template defined by the form's
|
||||||
|
:attr:`~django.forms.Form.template_name`.
|
||||||
|
|
||||||
|
.. versionchanged:: 4.1
|
||||||
|
|
||||||
|
In older versions ``template_name`` defaulted to the string value
|
||||||
|
``'django/forms/formset/default.html'``.
|
||||||
|
|
||||||
.. attribute:: BaseFormSet.template_name_p
|
.. attribute:: BaseFormSet.template_name_p
|
||||||
|
|
||||||
|
|
|
@ -759,10 +759,14 @@ Reusable form templates
|
||||||
|
|
||||||
If your site uses the same rendering logic for forms in multiple places, you
|
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
|
can reduce duplication by saving the form's loop in a standalone template and
|
||||||
overriding the forms :attr:`~django.forms.Form.template_name` attribute to
|
setting a custom :setting:`FORM_RENDERER` to use that
|
||||||
render the form using the custom template. The below example will result in
|
:attr:`~django.forms.renderers.BaseRenderer.form_template_name` site-wide. You
|
||||||
``{{ form }}`` being rendered as the output of the ``form_snippet.html``
|
can also customize per-form by overriding the form's
|
||||||
template.
|
: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:
|
In your templates:
|
||||||
|
|
||||||
|
@ -779,16 +783,42 @@ In your templates:
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
In your form::
|
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):
|
class MyForm(forms.Form):
|
||||||
template_name = 'form_snippet.html'
|
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
|
.. versionchanged:: 4.0
|
||||||
|
|
||||||
Template rendering of forms was added.
|
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
|
||||||
==============
|
==============
|
||||||
|
|
||||||
|
|
|
@ -4397,7 +4397,7 @@ class Jinja2FormsTestCase(FormsTestCase):
|
||||||
|
|
||||||
|
|
||||||
class CustomRenderer(DjangoTemplates):
|
class CustomRenderer(DjangoTemplates):
|
||||||
pass
|
form_template_name = "forms_tests/form_snippet.html"
|
||||||
|
|
||||||
|
|
||||||
class RendererTests(SimpleTestCase):
|
class RendererTests(SimpleTestCase):
|
||||||
|
@ -4813,7 +4813,22 @@ class TemplateTests(SimpleTestCase):
|
||||||
|
|
||||||
|
|
||||||
class OverrideTests(SimpleTestCase):
|
class OverrideTests(SimpleTestCase):
|
||||||
def test_use_custom_template(self):
|
@override_settings(FORM_RENDERER="forms_tests.tests.test_forms.CustomRenderer")
|
||||||
|
def test_custom_renderer_template_name(self):
|
||||||
|
class Person(Form):
|
||||||
|
first_name = CharField()
|
||||||
|
|
||||||
|
get_default_renderer.cache_clear()
|
||||||
|
t = Template("{{ form }}")
|
||||||
|
html = t.render(Context({"form": Person()}))
|
||||||
|
expected = """
|
||||||
|
<div class="fieldWrapper"><label for="id_first_name">First name:</label>
|
||||||
|
<input type="text" name="first_name" required id="id_first_name"></div>
|
||||||
|
"""
|
||||||
|
self.assertHTMLEqual(html, expected)
|
||||||
|
get_default_renderer.cache_clear()
|
||||||
|
|
||||||
|
def test_per_form_template_name(self):
|
||||||
class Person(Form):
|
class Person(Form):
|
||||||
first_name = CharField()
|
first_name = CharField()
|
||||||
template_name = "forms_tests/form_snippet.html"
|
template_name = "forms_tests/form_snippet.html"
|
||||||
|
|
|
@ -23,6 +23,7 @@ from django.forms.formsets import (
|
||||||
all_valid,
|
all_valid,
|
||||||
formset_factory,
|
formset_factory,
|
||||||
)
|
)
|
||||||
|
from django.forms.renderers import TemplatesSetting
|
||||||
from django.forms.utils import ErrorList
|
from django.forms.utils import ErrorList
|
||||||
from django.forms.widgets import HiddenInput
|
from django.forms.widgets import HiddenInput
|
||||||
from django.test import SimpleTestCase
|
from django.test import SimpleTestCase
|
||||||
|
@ -1435,6 +1436,26 @@ class FormsFormsetTestCase(SimpleTestCase):
|
||||||
self.assertIs(formset._should_delete_form(formset.forms[1]), False)
|
self.assertIs(formset._should_delete_form(formset.forms[1]), False)
|
||||||
self.assertIs(formset._should_delete_form(formset.forms[2]), False)
|
self.assertIs(formset._should_delete_form(formset.forms[2]), False)
|
||||||
|
|
||||||
|
def test_template_name_uses_renderer_value(self):
|
||||||
|
class CustomRenderer(TemplatesSetting):
|
||||||
|
formset_template_name = "a/custom/formset/template.html"
|
||||||
|
|
||||||
|
ChoiceFormSet = formset_factory(Choice, renderer=CustomRenderer)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
ChoiceFormSet().template_name, "a/custom/formset/template.html"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_template_name_can_be_overridden(self):
|
||||||
|
class CustomFormSet(BaseFormSet):
|
||||||
|
template_name = "a/custom/formset/template.html"
|
||||||
|
|
||||||
|
ChoiceFormSet = formset_factory(Choice, formset=CustomFormSet)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
ChoiceFormSet().template_name, "a/custom/formset/template.html"
|
||||||
|
)
|
||||||
|
|
||||||
def test_custom_renderer(self):
|
def test_custom_renderer(self):
|
||||||
"""
|
"""
|
||||||
A custom renderer passed to a formset_factory() is passed to all forms
|
A custom renderer passed to a formset_factory() is passed to all forms
|
||||||
|
|
Loading…
Reference in New Issue