Refs #32339 -- Deprecated default.html form template.

Co-authored-by: Carlton Gibson <carlton.gibson@noumenal.es>
This commit is contained in:
David Smith 2022-05-05 14:26:09 +02:00 committed by Carlton Gibson
parent 6af8673255
commit d126eba363
12 changed files with 337 additions and 176 deletions

View File

@ -15,6 +15,9 @@ def get_default_renderer():
class BaseRenderer:
# RemovedInDjango50Warning: When the deprecation ends, replace with
# form_template_name = "django/forms/div.html"
# formset_template_name = "django/forms/formsets/div.html"
form_template_name = "django/forms/default.html"
formset_template_name = "django/forms/formsets/default.html"
@ -64,6 +67,31 @@ class Jinja2(EngineMixin, BaseRenderer):
return Jinja2
class DjangoDivFormRenderer(DjangoTemplates):
"""
Load Django templates from django/forms/templates and from apps'
'templates' directory and use the 'div.html' template to render forms and
formsets.
"""
# RemovedInDjango50Warning Deprecate this class in 5.0 and remove in 6.0.
form_template_name = "django/forms/div.html"
formset_template_name = "django/forms/formsets/div.html"
class Jinja2DivFormRenderer(Jinja2):
"""
Load Jinja2 templates from the built-in widget templates in
django/forms/jinja2 and from apps' 'jinja2' directory.
"""
# RemovedInDjango50Warning Deprecate this class in 5.0 and remove in 6.0.
form_template_name = "django/forms/div.html"
formset_template_name = "django/forms/formsets/div.html"
class TemplatesSetting(BaseRenderer):
"""
Load templates using template.loader.get_template() which is configured

View File

@ -1,13 +1,16 @@
import json
import warnings
from collections import UserList
from django.conf import settings
from django.core.exceptions import ValidationError
from django.forms.renderers import get_default_renderer
from django.utils import timezone
from django.utils.deprecation import RemovedInDjango50Warning
from django.utils.html import escape, format_html_join
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _
from django.utils.version import get_docs_version
def pretty_name(name):
@ -42,6 +45,16 @@ def flatatt(attrs):
)
DEFAULT_TEMPLATE_DEPRECATION_MSG = (
'The "default.html" templates for forms and formsets will be removed. These were '
'proxies to the equivalent "table.html" templates, but the new "div.html" '
"templates will be the default from Django 5.0. Transitional renderers are "
"provided to allow you to opt-in to the new output style now. See "
"https://docs.djangoproject.com/en/%s/releases/4.1/ for more details"
% get_docs_version()
)
class RenderableMixin:
def get_context(self):
raise NotImplementedError(
@ -49,12 +62,17 @@ class RenderableMixin:
)
def render(self, template_name=None, context=None, renderer=None):
return mark_safe(
(renderer or self.renderer).render(
template_name or self.template_name,
context or self.get_context(),
renderer = renderer or self.renderer
template = template_name or self.template_name
context = context or self.get_context()
if (
template == "django/forms/default.html"
or template == "django/forms/formsets/default.html"
):
warnings.warn(
DEFAULT_TEMPLATE_DEPRECATION_MSG, RemovedInDjango50Warning, stacklevel=2
)
)
return mark_safe(renderer.render(template, context))
__str__ = render
__html__ = render

View File

@ -105,6 +105,9 @@ details on these changes.
* The ``django.contrib.auth.hashers.CryptPasswordHasher`` will be removed.
* The ``"django/forms/default.html"`` and
``"django/forms/formsets/default.html"`` templates will be removed.
* The ability to pass ``nulls_first=False`` or ``nulls_last=False`` to
``Expression.asc()`` and ``Expression.desc()`` methods, and the ``OrderBy``
expression will be removed.

View File

@ -56,6 +56,12 @@ should return a rendered templates (as a string) or raise
Defaults to ``"django/forms/default.html"``, which is a proxy for
``"django/forms/table.html"``.
.. deprecated:: 4.1
The ``"django/forms/default.html"`` template is deprecated and will be
removed in Django 5.0. The default will become
``"django/forms/default.html"`` at that time.
.. attribute:: formset_template_name
.. versionadded:: 4.1
@ -65,6 +71,12 @@ should return a rendered templates (as a string) or raise
Defaults to ``"django/forms/formsets/default.html"``, which is a proxy
for ``"django/forms/formsets/table.html"``.
.. deprecated:: 4.1
The ``"django/forms/formset/default.html"`` template is deprecated and
will be removed in Django 5.0. The default will become
``"django/forms/formset/div.html"`` template.
.. method:: get_template(template_name)
Subclasses must implement this method with the appropriate template
@ -97,6 +109,26 @@ If you want to render templates with customizations from your
:setting:`TEMPLATES` setting, such as context processors for example, use the
:class:`TemplatesSetting` renderer.
.. class:: DjangoDivFormRenderer
.. versionadded:: 4.1
Subclass of :class:`DjangoTemplates` that specifies
:attr:`~BaseRenderer.form_template_name` and
:attr:`~BaseRenderer.formset_template_name` as ``"django/forms/div.html"`` and
``"django/forms/formset/div.html"`` respectively.
This is a transitional renderer for opt-in to the new ``<div>`` based
templates, which are the default from Django 5.0.
Apply this via the :setting:`FORM_RENDERER` setting::
FORM_RENDERER = "django.forms.renderers.DjangoDivFormRenderer"
Once the ``<div>`` templates are the default, this transitional renderer will
be deprecated, for removal in Django 6.0. The ``FORM_RENDERER`` declaration can
be removed at that time.
``Jinja2``
----------
@ -113,6 +145,17 @@ 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.
.. class:: Jinja2DivFormRenderer
.. versionadded:: 4.1
A transitional renderer as per :class:`DjangoDivFormRenderer` above, but
subclassing :class:`Jinja2` for use with the Jinja2 backend.
Apply this via the :setting:`FORM_RENDERER` setting::
FORM_RENDERER = "django.forms.renderers.Jinja2DivFormRenderer"
``TemplatesSetting``
--------------------

View File

@ -74,6 +74,24 @@ Validation of Constraints
in the :attr:`Meta.constraints <django.db.models.Options.constraints>` option
are now checked during :ref:`model validation <validating-objects>`.
Form rendering accessibility
----------------------------
In order to aid users with screen readers, and other assistive technology, new
``<div>`` based form templates are available from this release. These provide
more accessible navigation than the older templates, and are able to correctly
group related controls, such as radio-lists, into fieldsets.
The new templates are recommended, and will become the default form rendering
style when outputting a form, like ``{{ form }}`` in a template, from Django
5.0.
In order to ease adopting the new output style, the default form and formset
templates are now configurable at the project level via the
:setting:`FORM_RENDERER` setting.
See :ref:`the Forms section (below)<forms-4.1>` for full details.
.. _csrf-cookie-masked-usage:
``CSRF_COOKIE_MASKED`` setting
@ -253,6 +271,8 @@ File Uploads
* ...
.. _forms-4.1:
Forms
~~~~~
@ -279,6 +299,34 @@ Forms
as the template implements ``<fieldset>`` and ``<legend>`` to group related
inputs and is easier for screen reader users to navigate.
The div-based output will become the default rendering style from Django 5.0.
* In order to smooth adoption of the new ``<div>`` output style, two
transitional form renderer classes are available:
:class:`django.forms.renderers.DjangoDivFormRenderer` and
:class:`django.forms.renderers.Jinja2DivFormRenderer`, for the Django and
Jinja2 template backends respectively.
You can apply one of these via the :setting:`FORM_RENDERER` setting. For
example::
FORM_RENDERER = "django.forms.renderers.DjangoDivFormRenderer"
Once the ``<div>`` output style is the default, from Django 5.0, these
transitional renderers will be deprecated, for removal in Django 6.0. The
``FORM_RENDERER`` declaration can be removed at that time.
* If the new ``<div>`` output style is not appropriate for your project, you should
define a renderer subclass specifying
:attr:`~django.forms.renderers.BaseRenderer.form_template_name` and
:attr:`~django.forms.renderers.BaseRenderer.formset_template_name` for your
required style, and set :setting:`FORM_RENDERER` accordingly.
For example, for the ``<p>`` output style used by :meth:`~.Form.as_p`, you
would define a form renderer setting ``form_template_name`` to
``"django/forms/p.html"`` and ``formset_template_name`` to
``"django/forms/formsets/p.html"``.
* The new :meth:`~django.forms.BoundField.legend_tag` allows rendering field
labels in ``<legend>`` tags via the new ``tag`` argument of
:meth:`~django.forms.BoundField.label_tag`.
@ -718,6 +766,10 @@ Miscellaneous
``Expression.asc()`` and ``Expression.desc()`` methods, and the ``OrderBy``
expression is deprecated. Use ``None`` instead.
* The ``"django/forms/default.html"`` and
``"django/forms/formsets/default.html"`` templates which are a proxy to the
table-based templates are deprecated. Use the specific template instead.
Features removed in 4.1
=======================

View File

@ -11,6 +11,8 @@ except ImportError:
def jinja2_tests(test_func):
test_func = skipIf(jinja2 is None, "this test requires jinja2")(test_func)
return override_settings(
FORM_RENDERER="django.forms.renderers.Jinja2",
# RemovedInDjango50Warning: When the deprecation ends, revert to
# FORM_RENDERER="django.forms.renderers.Jinja2",
FORM_RENDERER="django.forms.renderers.Jinja2DivFormRenderer",
TEMPLATES={"BACKEND": "django.template.backends.jinja2.Jinja2"},
)(test_func)

View File

@ -42,8 +42,9 @@ from django.forms.utils import ErrorList
from django.http import QueryDict
from django.template import Context, Template
from django.test import SimpleTestCase
from django.test.utils import override_settings
from django.test.utils import isolate_lru_cache, override_settings
from django.utils.datastructures import MultiValueDict
from django.utils.deprecation import RemovedInDjango50Warning
from django.utils.safestring import mark_safe
from . import jinja2_tests
@ -149,17 +150,12 @@ class FormsTestCase(SimpleTestCase):
)
self.assertHTMLEqual(
str(p),
"""
<tr><th><label for="id_first_name">First name:</label></th><td>
<input type="text" name="first_name" value="John" id="id_first_name"
required></td></tr>
<tr><th><label for="id_last_name">Last name:</label></th><td>
<input type="text" name="last_name" value="Lennon" id="id_last_name"
required></td></tr>
<tr><th><label for="id_birthday">Birthday:</label></th><td>
<input type="text" name="birthday" value="1940-10-9" id="id_birthday"
required></td></tr>
""",
'<div><label for="id_first_name">First name:</label><input type="text" '
'name="first_name" value="John" required id="id_first_name"></div><div>'
'<label for="id_last_name">Last name:</label><input type="text" '
'name="last_name" value="Lennon" required id="id_last_name"></div><div>'
'<label for="id_birthday">Birthday:</label><input type="text" '
'name="birthday" value="1940-10-9" required id="id_birthday"></div>',
)
self.assertHTMLEqual(
p.as_div(),
@ -182,15 +178,15 @@ class FormsTestCase(SimpleTestCase):
self.assertEqual(p.cleaned_data, {})
self.assertHTMLEqual(
str(p),
"""<tr><th><label for="id_first_name">First name:</label></th><td>
<ul class="errorlist"><li>This field is required.</li></ul>
<input type="text" name="first_name" id="id_first_name" required></td></tr>
<tr><th><label for="id_last_name">Last name:</label></th>
<td><ul class="errorlist"><li>This field is required.</li></ul>
<input type="text" name="last_name" id="id_last_name" required></td></tr>
<tr><th><label for="id_birthday">Birthday:</label></th><td>
<ul class="errorlist"><li>This field is required.</li></ul>
<input type="text" name="birthday" id="id_birthday" required></td></tr>""",
'<div><label for="id_first_name">First name:</label>'
'<ul class="errorlist"><li>This field is required.</li></ul>'
'<input type="text" name="first_name" required id="id_first_name"></div>'
'<div><label for="id_last_name">Last name:</label>'
'<ul class="errorlist"><li>This field is required.</li></ul>'
'<input type="text" name="last_name" required id="id_last_name"></div><div>'
'<label for="id_birthday">Birthday:</label>'
'<ul class="errorlist"><li>This field is required.</li></ul>'
'<input type="text" name="birthday" required id="id_birthday"></div>',
)
self.assertHTMLEqual(
p.as_table(),
@ -261,12 +257,12 @@ class FormsTestCase(SimpleTestCase):
self.assertHTMLEqual(
str(p),
"""<tr><th><label for="id_first_name">First name:</label></th><td>
<input type="text" name="first_name" id="id_first_name" required></td></tr>
<tr><th><label for="id_last_name">Last name:</label></th><td>
<input type="text" name="last_name" id="id_last_name" required></td></tr>
<tr><th><label for="id_birthday">Birthday:</label></th><td>
<input type="text" name="birthday" id="id_birthday" required></td></tr>""",
'<div><label for="id_first_name">First name:</label><input type="text" '
'name="first_name" id="id_first_name" required></div><div><label '
'for="id_last_name">Last name:</label><input type="text" name="last_name" '
'id="id_last_name" required></div><div><label for="id_birthday">'
'Birthday:</label><input type="text" name="birthday" id="id_birthday" '
"required></div>",
)
self.assertHTMLEqual(
p.as_table(),
@ -4932,9 +4928,7 @@ class TemplateTests(SimpleTestCase):
t = Template(
'<form method="post">'
"<table>"
"{{ form }}"
"</table>"
'<input type="submit" required>'
"</form>"
)
@ -4944,14 +4938,12 @@ class TemplateTests(SimpleTestCase):
self.assertHTMLEqual(
my_function("GET", {}),
'<form method="post">'
"<table>"
"<tr><th>Username:</th><td>"
'<input type="text" name="username" maxlength="10" required></td></tr>'
"<tr><th>Password1:</th><td>"
'<input type="password" name="password1" required></td></tr>'
"<tr><th>Password2:</th><td>"
'<input type="password" name="password2" required></td></tr>'
"</table>"
"<div>Username:"
'<input type="text" name="username" maxlength="10" required></div>'
"<div>Password1:"
'<input type="password" name="password1" required></div>'
"<div>Password2:"
'<input type="password" name="password2" required></div>'
'<input type="submit" required>'
"</form>",
)
@ -4966,18 +4958,16 @@ class TemplateTests(SimpleTestCase):
},
),
'<form method="post">'
"<table>"
'<tr><td colspan="2"><ul class="errorlist nonfield">'
"<li>Please make sure your passwords match.</li></ul></td></tr>"
'<tr><th>Username:</th><td><ul class="errorlist">'
'<ul class="errorlist nonfield">'
"<li>Please make sure your passwords match.</li></ul>"
'<div>Username:<ul class="errorlist">'
"<li>Ensure this value has at most 10 characters (it has 23).</li></ul>"
'<input type="text" name="username" '
'value="this-is-a-long-username" maxlength="10" required></td></tr>'
"<tr><th>Password1:</th><td>"
'<input type="password" name="password1" required></td></tr>'
"<tr><th>Password2:</th><td>"
'<input type="password" name="password2" required></td></tr>'
"</table>"
'value="this-is-a-long-username" maxlength="10" required></div>'
"<div>Password1:"
'<input type="password" name="password1" required></div>'
"<div>Password2:"
'<input type="password" name="password2" required></div>'
'<input type="submit" required>'
"</form>",
)
@ -5054,7 +5044,7 @@ class OverrideTests(SimpleTestCase):
f = FirstNameForm()
try:
self.assertInHTML("<th>1</th>", f.render())
f.render()
except RecursionError:
self.fail("Cyclic reference in BoundField.render().")
@ -5069,3 +5059,16 @@ class OverrideTests(SimpleTestCase):
'<label for="id_name" class="required">Name:</label>'
'<legend class="required">Language:</legend>',
)
class DeprecationTests(SimpleTestCase):
def test_warning(self):
from django.forms.utils import DEFAULT_TEMPLATE_DEPRECATION_MSG
with isolate_lru_cache(get_default_renderer), self.settings(
FORM_RENDERER="django.forms.renderers.DjangoTemplates"
), self.assertRaisesMessage(
RemovedInDjango50Warning, DEFAULT_TEMPLATE_DEPRECATION_MSG
):
form = Person()
str(form)

View File

@ -23,10 +23,12 @@ from django.forms.formsets import (
all_valid,
formset_factory,
)
from django.forms.renderers import TemplatesSetting
from django.forms.renderers import TemplatesSetting, get_default_renderer
from django.forms.utils import ErrorList
from django.forms.widgets import HiddenInput
from django.test import SimpleTestCase
from django.test.utils import isolate_lru_cache
from django.utils.deprecation import RemovedInDjango50Warning
from . import jinja2_tests
@ -125,8 +127,8 @@ class FormsFormsetTestCase(SimpleTestCase):
<input type="hidden" name="choices-INITIAL_FORMS" value="0">
<input type="hidden" name="choices-MIN_NUM_FORMS" value="0">
<input type="hidden" name="choices-MAX_NUM_FORMS" value="1000">
<tr><th>Choice:</th><td><input type="text" name="choices-0-choice"></td></tr>
<tr><th>Votes:</th><td><input type="number" name="choices-0-votes"></td></tr>""",
<div>Choice:<input type="text" name="choices-0-choice"></div>
<div>Votes:<input type="number" name="choices-0-votes"></div>""",
)
# FormSet are treated similarly to Forms. FormSet has an is_valid()
# method, and a cleaned_data or errors attribute depending on whether
@ -976,12 +978,12 @@ class FormsFormsetTestCase(SimpleTestCase):
formset = LimitedFavoriteDrinkFormSet()
self.assertHTMLEqual(
"\n".join(str(form) for form in formset.forms),
"""<tr><th><label for="id_form-0-name">Name:</label></th>
<td><input type="text" name="form-0-name" id="id_form-0-name"></td></tr>
<tr><th><label for="id_form-1-name">Name:</label></th>
<td><input type="text" name="form-1-name" id="id_form-1-name"></td></tr>
<tr><th><label for="id_form-2-name">Name:</label></th>
<td><input type="text" name="form-2-name" id="id_form-2-name"></td></tr>""",
"""<div><label for="id_form-0-name">Name:</label>
<input type="text" name="form-0-name" id="id_form-0-name"></div>
<div><label for="id_form-1-name">Name:</label>
<input type="text" name="form-1-name" id="id_form-1-name"></div>
<div><label for="id_form-2-name">Name:</label>
<input type="text" name="form-2-name" id="id_form-2-name"></div>""",
)
# If max_num is 0 then no form is rendered at all.
LimitedFavoriteDrinkFormSet = formset_factory(
@ -997,10 +999,10 @@ class FormsFormsetTestCase(SimpleTestCase):
formset = LimitedFavoriteDrinkFormSet()
self.assertHTMLEqual(
"\n".join(str(form) for form in formset.forms),
"""<tr><th><label for="id_form-0-name">Name:</label></th><td>
<input type="text" name="form-0-name" id="id_form-0-name"></td></tr>
<tr><th><label for="id_form-1-name">Name:</label></th>
<td><input type="text" name="form-1-name" id="id_form-1-name"></td></tr>""",
"""<div><label for="id_form-0-name">Name:</label>
<input type="text" name="form-0-name" id="id_form-0-name"></div>
<div><label for="id_form-1-name">Name:</label>
<input type="text" name="form-1-name" id="id_form-1-name"></div>""",
)
def test_limiting_extra_lest_than_max_num(self):
@ -1011,8 +1013,8 @@ class FormsFormsetTestCase(SimpleTestCase):
formset = LimitedFavoriteDrinkFormSet()
self.assertHTMLEqual(
"\n".join(str(form) for form in formset.forms),
"""<tr><th><label for="id_form-0-name">Name:</label></th>
<td><input type="text" name="form-0-name" id="id_form-0-name"></td></tr>""",
"""<div><label for="id_form-0-name">Name:</label>
<input type="text" name="form-0-name" id="id_form-0-name"></div>""",
)
def test_max_num_with_initial_data(self):
@ -1024,11 +1026,11 @@ class FormsFormsetTestCase(SimpleTestCase):
self.assertHTMLEqual(
"\n".join(str(form) for form in formset.forms),
"""
<tr><th><label for="id_form-0-name">Name:</label></th>
<td><input type="text" name="form-0-name" value="Fernet and Coke"
id="id_form-0-name"></td></tr>
<tr><th><label for="id_form-1-name">Name:</label></th>
<td><input type="text" name="form-1-name" id="id_form-1-name"></td></tr>
<div><label for="id_form-0-name">Name:</label>
<input type="text" name="form-0-name" value="Fernet and Coke"
id="id_form-0-name"></div>
<div><label for="id_form-1-name">Name:</label>
<input type="text" name="form-1-name" id="id_form-1-name"></div>
""",
)
@ -1056,12 +1058,12 @@ class FormsFormsetTestCase(SimpleTestCase):
self.assertHTMLEqual(
"\n".join(str(form) for form in formset.forms),
"""
<tr><th><label for="id_form-0-name">Name:</label></th>
<td><input id="id_form-0-name" name="form-0-name" type="text"
value="Fernet and Coke"></td></tr>
<tr><th><label for="id_form-1-name">Name:</label></th>
<td><input id="id_form-1-name" name="form-1-name" type="text"
value="Bloody Mary"></td></tr>
<div><label for="id_form-0-name">Name:</label>
<input id="id_form-0-name" name="form-0-name" type="text"
value="Fernet and Coke"></div>
<div><label for="id_form-1-name">Name:</label>
<input id="id_form-1-name" name="form-1-name" type="text"
value="Bloody Mary"></div>
""",
)
@ -1082,18 +1084,15 @@ class FormsFormsetTestCase(SimpleTestCase):
self.assertHTMLEqual(
"\n".join(str(form) for form in formset.forms),
"""
<tr><th><label for="id_form-0-name">Name:</label></th>
<td>
<div><label for="id_form-0-name">Name:</label>
<input id="id_form-0-name" name="form-0-name" type="text" value="Gin Tonic">
</td></tr>
<tr><th><label for="id_form-1-name">Name:</label></th>
<td>
</div>
<div><label for="id_form-1-name">Name:</label>
<input id="id_form-1-name" name="form-1-name" type="text"
value="Bloody Mary"></td></tr>
<tr><th><label for="id_form-2-name">Name:</label></th>
<td>
value="Bloody Mary"></div>
<div><label for="id_form-2-name">Name:</label>
<input id="id_form-2-name" name="form-2-name" type="text"
value="Jack and Coke"></td></tr>
value="Jack and Coke"></div>
""",
)
@ -1173,12 +1172,11 @@ class FormsFormsetTestCase(SimpleTestCase):
self.assertHTMLEqual(
"\n".join(str(form) for form in formset.forms),
"""
<tr><th><label for="id_form-0-name">Name:</label></th>
<td>
<div><label for="id_form-0-name">Name:</label>
<input type="text" name="form-0-name" value="Gin Tonic" id="id_form-0-name">
</td></tr>
<tr><th><label for="id_form-1-name">Name:</label></th>
<td><input type="text" name="form-1-name" id="id_form-1-name"></td></tr>""",
</div>
<div><label for="id_form-1-name">Name:</label>
<input type="text" name="form-1-name" id="id_form-1-name"></div>""",
)
def test_management_form_field_names(self):
@ -1701,16 +1699,16 @@ class TestIsBoundBehavior(SimpleTestCase):
# Can still render the formset.
self.assertHTMLEqual(
str(formset),
'<tr><td colspan="2">'
'<ul class="errorlist nonfield">'
"<li>(Hidden field TOTAL_FORMS) This field is required.</li>"
"<li>(Hidden field INITIAL_FORMS) This field is required.</li>"
"</ul>"
"<div>"
'<input type="hidden" name="form-TOTAL_FORMS" id="id_form-TOTAL_FORMS">'
'<input type="hidden" name="form-INITIAL_FORMS" id="id_form-INITIAL_FORMS">'
'<input type="hidden" name="form-MIN_NUM_FORMS" id="id_form-MIN_NUM_FORMS">'
'<input type="hidden" name="form-MAX_NUM_FORMS" id="id_form-MAX_NUM_FORMS">'
"</td></tr>\n",
"</div>\n",
)
def test_management_form_invalid_data(self):
@ -1732,18 +1730,18 @@ class TestIsBoundBehavior(SimpleTestCase):
# Can still render the formset.
self.assertHTMLEqual(
str(formset),
'<tr><td colspan="2">'
'<ul class="errorlist nonfield">'
"<li>(Hidden field TOTAL_FORMS) Enter a whole number.</li>"
"<li>(Hidden field INITIAL_FORMS) Enter a whole number.</li>"
"</ul>"
"<div>"
'<input type="hidden" name="form-TOTAL_FORMS" value="two" '
'id="id_form-TOTAL_FORMS">'
'<input type="hidden" name="form-INITIAL_FORMS" value="one" '
'id="id_form-INITIAL_FORMS">'
'<input type="hidden" name="form-MIN_NUM_FORMS" id="id_form-MIN_NUM_FORMS">'
'<input type="hidden" name="form-MAX_NUM_FORMS" id="id_form-MAX_NUM_FORMS">'
"</td></tr>\n",
"</div>\n",
)
def test_customize_management_form_error(self):
@ -1889,3 +1887,17 @@ class AllValidTests(SimpleTestCase):
]
self.assertEqual(formset1._errors, expected_errors)
self.assertEqual(formset2._errors, expected_errors)
class DeprecationTests(SimpleTestCase):
def test_warning(self):
from django.forms.utils import DEFAULT_TEMPLATE_DEPRECATION_MSG
with isolate_lru_cache(get_default_renderer), self.settings(
FORM_RENDERER="django.forms.renderers.DjangoTemplates"
), self.assertRaisesMessage(
RemovedInDjango50Warning, DEFAULT_TEMPLATE_DEPRECATION_MSG
):
ChoiceFormSet = formset_factory(Choice)
formset = ChoiceFormSet()
str(formset)

View File

@ -687,12 +687,12 @@ class ModelFormBaseTest(TestCase):
self.assertHTMLEqual(
str(SubclassMeta()),
"""<tr><th><label for="id_name">Name:</label></th>
<td><input id="id_name" type="text" name="name" maxlength="20" required></td></tr>
<tr><th><label for="id_slug">Slug:</label></th>
<td><input id="id_slug" type="text" name="slug" maxlength="20" required></td></tr>
<tr><th><label for="id_checkbox">Checkbox:</label></th>
<td><input type="checkbox" name="checkbox" id="id_checkbox" required></td></tr>""",
'<div><label for="id_name">Name:</label>'
'<input type="text" name="name" maxlength="20" required id="id_name">'
'</div><div><label for="id_slug">Slug:</label><input type="text" '
'name="slug" maxlength="20" required id="id_slug"></div><div>'
'<label for="id_checkbox">Checkbox:</label>'
'<input type="checkbox" name="checkbox" required id="id_checkbox"></div>',
)
def test_orderfields_form(self):
@ -704,10 +704,10 @@ class ModelFormBaseTest(TestCase):
self.assertEqual(list(OrderFields.base_fields), ["url", "name"])
self.assertHTMLEqual(
str(OrderFields()),
"""<tr><th><label for="id_url">The URL:</label></th>
<td><input id="id_url" type="text" name="url" maxlength="40" required></td></tr>
<tr><th><label for="id_name">Name:</label></th>
<td><input id="id_name" type="text" name="name" maxlength="20" required></td></tr>""",
'<div><label for="id_url">The URL:</label>'
'<input type="text" name="url" maxlength="40" required id="id_url">'
'</div><div><label for="id_name">Name:</label><input type="text" '
'name="name" maxlength="20" required id="id_name"></div>',
)
def test_orderfields2_form(self):
@ -1460,12 +1460,11 @@ class ModelFormBasicTests(TestCase):
f = BaseCategoryForm()
self.assertHTMLEqual(
str(f),
"""<tr><th><label for="id_name">Name:</label></th>
<td><input id="id_name" type="text" name="name" maxlength="20" required></td></tr>
<tr><th><label for="id_slug">Slug:</label></th>
<td><input id="id_slug" type="text" name="slug" maxlength="20" required></td></tr>
<tr><th><label for="id_url">The URL:</label></th>
<td><input id="id_url" type="text" name="url" maxlength="40" required></td></tr>""",
'<div><label for="id_name">Name:</label><input type="text" name="name" '
'maxlength="20" required id="id_name"></div><div><label for="id_slug">Slug:'
'</label><input type="text" name="slug" maxlength="20" required '
'id="id_slug"></div><div><label for="id_url">The URL:</label>'
'<input type="text" name="url" maxlength="40" required id="id_url"></div>',
)
self.assertHTMLEqual(
str(f.as_ul()),
@ -1538,12 +1537,9 @@ class ModelFormBasicTests(TestCase):
f = RoykoForm(auto_id=False, instance=self.w_royko)
self.assertHTMLEqual(
str(f),
"""
<tr><th>Name:</th><td>
<input type="text" name="name" value="Mike Royko" maxlength="50" required>
<br>
<span class="helptext">Use both first and last names.</span></td></tr>
""",
'<div>Name:<div class="helptext">Use both first and last names.</div>'
'<input type="text" name="name" value="Mike Royko" maxlength="50" '
"required></div>",
)
art = Article.objects.create(
@ -1703,30 +1699,39 @@ class ModelFormBasicTests(TestCase):
self.assertHTMLEqual(
str(f),
"""
<tr><th>Headline:</th><td>
<input type="text" name="headline" maxlength="50" required></td></tr>
<tr><th>Slug:</th><td>
<input type="text" name="slug" maxlength="50" required></td></tr>
<tr><th>Pub date:</th><td>
<input type="text" name="pub_date" required></td></tr>
<tr><th>Writer:</th><td><select name="writer" required>
<option value="" selected>---------</option>
<option value="%s">Bob Woodward</option>
<option value="%s">Mike Royko</option>
</select></td></tr>
<tr><th>Article:</th><td>
<textarea rows="10" cols="40" name="article" required></textarea></td></tr>
<tr><th>Categories:</th><td><select multiple name="categories">
<option value="%s">Entertainment</option>
<option value="%s">It&#x27;s a test</option>
<option value="%s">Third test</option>
</select></td></tr>
<tr><th>Status:</th><td><select name="status">
<option value="" selected>---------</option>
<option value="1">Draft</option>
<option value="2">Pending</option>
<option value="3">Live</option>
</select></td></tr>
<div>Headline:
<input type="text" name="headline" maxlength="50" required>
</div>
<div>Slug:
<input type="text" name="slug" maxlength="50" required>
</div>
<div>Pub date:
<input type="text" name="pub_date" required>
</div>
<div>Writer:
<select name="writer" required>
<option value="" selected>---------</option>
<option value="%s">Bob Woodward</option>
<option value="%s">Mike Royko</option>
</select>
</div>
<div>Article:
<textarea name="article" cols="40" rows="10" required></textarea>
</div>
<div>Categories:
<select name="categories" multiple>
<option value="%s">Entertainment</option>
<option value="%s">It&#x27;s a test</option>
<option value="%s">Third test</option>
</select>
</div>
<div>Status:
<select name="status">
<option value="" selected>---------</option>
<option value="1">Draft</option><option value="2">Pending</option>
<option value="3">Live</option>
</select>
</div>
"""
% (self.w_woodward.pk, self.w_royko.pk, self.c1.pk, self.c2.pk, self.c3.pk),
)
@ -1791,12 +1796,8 @@ class ModelFormBasicTests(TestCase):
f = PartialArticleForm(auto_id=False)
self.assertHTMLEqual(
str(f),
"""
<tr><th>Headline:</th><td>
<input type="text" name="headline" maxlength="50" required></td></tr>
<tr><th>Pub date:</th><td>
<input type="text" name="pub_date" required></td></tr>
""",
'<div>Headline:<input type="text" name="headline" maxlength="50" required>'
'</div><div>Pub date:<input type="text" name="pub_date" required></div>',
)
class PartialArticleFormWithSlug(forms.ModelForm):
@ -2990,10 +2991,10 @@ class OtherModelFormTests(TestCase):
self.assertHTMLEqual(
str(CategoryForm()),
"""<tr><th><label for="id_description">Description:</label></th>
<td><input type="text" name="description" id="id_description" required></td></tr>
<tr><th><label for="id_url">The URL:</label></th>
<td><input id="id_url" type="text" name="url" maxlength="40" required></td></tr>""",
'<div><label for="id_description">Description:</label><input type="text" '
'name="description" required id="id_description"></div><div>'
'<label for="id_url">The URL:</label><input type="text" name="url" '
'maxlength="40" required id="id_url"></div>',
)
# to_field_name should also work on ModelMultipleChoiceField ##################
@ -3014,8 +3015,8 @@ class OtherModelFormTests(TestCase):
self.assertEqual(list(CustomFieldForExclusionForm.base_fields), ["name"])
self.assertHTMLEqual(
str(CustomFieldForExclusionForm()),
"""<tr><th><label for="id_name">Name:</label></th>
<td><input id="id_name" type="text" name="name" maxlength="10" required></td></tr>""",
'<div><label for="id_name">Name:</label><input type="text" '
'name="name" maxlength="10" required id="id_name"></div>',
)
def test_iterable_model_m2m(self):

View File

@ -1196,14 +1196,12 @@ class TestSplitFormField(PostgreSQLSimpleTestCase):
self.assertHTMLEqual(
str(SplitForm()),
"""
<tr>
<th><label for="id_array_0">Array:</label></th>
<td>
<input id="id_array_0" name="array_0" type="text" required>
<input id="id_array_1" name="array_1" type="text" required>
<input id="id_array_2" name="array_2" type="text" required>
</td>
</tr>
<div>
<label for="id_array_0">Array:</label>
<input id="id_array_0" name="array_0" type="text" required>
<input id="id_array_1" name="array_1" type="text" required>
<input id="id_array_2" name="array_2" type="text" required>
</div>
""",
)

View File

@ -687,17 +687,15 @@ class TestFormField(PostgreSQLSimpleTestCase):
self.assertHTMLEqual(
str(form),
"""
<tr>
<th>
<label>Field:</label>
</th>
<td>
<div>
<fieldset>
<legend>Field:</legend>
<input id="id_field_0_0" name="field_0_0" type="text">
<input id="id_field_0_1" name="field_0_1" type="text">
<input id="id_field_1_0" name="field_1_0" type="text">
<input id="id_field_1_1" name="field_1_1" type="text">
</td>
</tr>
</fieldset>
</div>
""",
)
form = SplitForm(
@ -788,13 +786,13 @@ class TestFormField(PostgreSQLSimpleTestCase):
self.assertHTMLEqual(
str(RangeForm()),
"""
<tr>
<th><label>Ints:</label></th>
<td>
<div>
<fieldset>
<legend>Ints:</legend>
<input id="id_ints_0" name="ints_0" type="number">
<input id="id_ints_1" name="ints_1" type="number">
</td>
</tr>
</fieldset>
</div>
""",
)

View File

@ -243,6 +243,9 @@ def setup_collect_tests(start_at, start_after, test_labels=None):
"fields.W342", # ForeignKey(unique=True) -> OneToOneField
]
# RemovedInDjango50Warning
settings.FORM_RENDERER = "django.forms.renderers.DjangoDivFormRenderer"
# Load all the ALWAYS_INSTALLED_APPS.
django.setup()