mirror of
https://github.com/django/django.git
synced 2024-11-25 07:59:34 +01:00
Refs #32339 -- Deprecated default.html form template.
Co-authored-by: Carlton Gibson <carlton.gibson@noumenal.es>
This commit is contained in:
parent
6af8673255
commit
d126eba363
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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``
|
||||
--------------------
|
||||
|
||||
|
@ -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
|
||||
=======================
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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'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'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):
|
||||
|
@ -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>
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -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>
|
||||
""",
|
||||
)
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user