diff options
| author | David Smith <smithdc@gmail.com> | 2022-05-05 09:21:47 +0200 |
|---|---|---|
| committer | Carlton Gibson <carlton@noumenal.es> | 2022-05-05 14:32:43 +0200 |
| commit | ec5659382a5f5fc2daf0c87ccc89d0fb07534874 (patch) | |
| tree | 08a5183708f3490b97f61098d26272901d25b822 | |
| parent | 27b07a3246bc033cd9ded01238c6dc64731cce35 (diff) | |
Fixed #32339 -- Added div.html form template.
| -rw-r--r-- | django/forms/forms.py | 1 | ||||
| -rw-r--r-- | django/forms/formsets.py | 1 | ||||
| -rw-r--r-- | django/forms/jinja2/django/forms/div.html | 24 | ||||
| -rw-r--r-- | django/forms/jinja2/django/forms/formsets/div.html | 1 | ||||
| -rw-r--r-- | django/forms/templates/django/forms/div.html | 24 | ||||
| -rw-r--r-- | django/forms/templates/django/forms/formsets/div.html | 1 | ||||
| -rw-r--r-- | django/forms/utils.py | 4 | ||||
| -rw-r--r-- | docs/ref/forms/api.txt | 49 | ||||
| -rw-r--r-- | docs/releases/4.1.txt | 9 | ||||
| -rw-r--r-- | docs/topics/forms/formsets.txt | 16 | ||||
| -rw-r--r-- | docs/topics/forms/index.txt | 8 | ||||
| -rw-r--r-- | tests/forms_tests/tests/test_forms.py | 184 | ||||
| -rw-r--r-- | tests/forms_tests/tests/test_formsets.py | 12 |
13 files changed, 328 insertions, 6 deletions
diff --git a/django/forms/forms.py b/django/forms/forms.py index 2500dccc9b..35471345c9 100644 --- a/django/forms/forms.py +++ b/django/forms/forms.py @@ -66,6 +66,7 @@ class BaseForm(RenderableFormMixin): prefix = None use_required_attribute = True + template_name_div = "django/forms/div.html" template_name_p = "django/forms/p.html" template_name_table = "django/forms/table.html" template_name_ul = "django/forms/ul.html" diff --git a/django/forms/formsets.py b/django/forms/formsets.py index d51b13548e..2df80297d3 100644 --- a/django/forms/formsets.py +++ b/django/forms/formsets.py @@ -63,6 +63,7 @@ class BaseFormSet(RenderableFormMixin): ), } + template_name_div = "django/forms/formsets/div.html" template_name_p = "django/forms/formsets/p.html" template_name_table = "django/forms/formsets/table.html" template_name_ul = "django/forms/formsets/ul.html" diff --git a/django/forms/jinja2/django/forms/div.html b/django/forms/jinja2/django/forms/div.html new file mode 100644 index 0000000000..6de0bb038e --- /dev/null +++ b/django/forms/jinja2/django/forms/div.html @@ -0,0 +1,24 @@ +{{ errors }} +{% if errors and not fields %} + <div>{% for field in hidden_fields %}{{ field }}{% endfor %}</div> +{% endif %} +{% for field, errors in fields %} + <div{% set classes = field.css_classes() %}{% if classes %} class="{{ classes }}"{% endif %}> + {% if field.use_fieldset %} + <fieldset> + {% if field.label %}{{ field.legend_tag() }}{% endif %} + {% else %} + {% if field.label %}{{ field.label_tag() }}{% endif %} + {% endif %} + {% if field.help_text %}<div class="helptext">{{ field.help_text|safe }}</div>{% endif %} + {{ errors }} + {{ field }} + {% if field.use_fieldset %}</fieldset>{% endif %} + {% if loop.last %} + {% for field in hidden_fields %}{{ field }}{% endfor %} + {% endif %} +</div> +{% endfor %} +{% if not fields and not errors %} + {% for field in hidden_fields %}{{ field }}{% endfor %} +{% endif %} diff --git a/django/forms/jinja2/django/forms/formsets/div.html b/django/forms/jinja2/django/forms/formsets/div.html new file mode 100644 index 0000000000..0dda779d3f --- /dev/null +++ b/django/forms/jinja2/django/forms/formsets/div.html @@ -0,0 +1 @@ +{{ formset.management_form }}{% for form in formset %}{{ form.as_div() }}{% endfor %} diff --git a/django/forms/templates/django/forms/div.html b/django/forms/templates/django/forms/div.html new file mode 100644 index 0000000000..0328fdf8d3 --- /dev/null +++ b/django/forms/templates/django/forms/div.html @@ -0,0 +1,24 @@ +{{ errors }} +{% if errors and not fields %} + <div>{% for field in hidden_fields %}{{ field }}{% endfor %}</div> +{% endif %} +{% for field, errors in fields %} + <div{% with classes=field.css_classes %}{% if classes %} class="{{ classes }}"{% endif %}{% endwith %}> + {% if field.use_fieldset %} + <fieldset> + {% if field.label %}{{ field.legend_tag }}{% endif %} + {% else %} + {% if field.label %}{{ field.label_tag }}{% endif %} + {% endif %} + {% if field.help_text %}<div class="helptext">{{ field.help_text|safe }}</div>{% endif %} + {{ errors }} + {{ field }} + {% if field.use_fieldset %}</fieldset>{% endif %} + {% if forloop.last %} + {% for field in hidden_fields %}{{ field }}{% endfor %} + {% endif %} +</div> +{% endfor %} +{% if not fields and not errors %} + {% for field in hidden_fields %}{{ field }}{% endfor %} +{% endif %} diff --git a/django/forms/templates/django/forms/formsets/div.html b/django/forms/templates/django/forms/formsets/div.html new file mode 100644 index 0000000000..93499897d4 --- /dev/null +++ b/django/forms/templates/django/forms/formsets/div.html @@ -0,0 +1 @@ +{{ formset.management_form }}{% for form in formset %}{{ form.as_div }}{% endfor %} diff --git a/django/forms/utils.py b/django/forms/utils.py index 7d3bb5ad48..77678054db 100644 --- a/django/forms/utils.py +++ b/django/forms/utils.py @@ -73,6 +73,10 @@ class RenderableFormMixin(RenderableMixin): """Render as <li> elements excluding the surrounding <ul> tag.""" return self.render(self.template_name_ul) + def as_div(self): + """Render as <div> elements.""" + return self.render(self.template_name_div) + class RenderableErrorMixin(RenderableMixin): def as_json(self, escape_html=False): diff --git a/docs/ref/forms/api.txt b/docs/ref/forms/api.txt index 8530af2611..72937bfa9a 100644 --- a/docs/ref/forms/api.txt +++ b/docs/ref/forms/api.txt @@ -607,6 +607,55 @@ list using ``{{ form.as_ul }}``. Each helper pairs a form method with an attribute giving the appropriate template name. +``as_div()`` +~~~~~~~~~~~~ + +.. attribute:: Form.template_name_div + +.. versionadded:: 4.1 + +The template used by ``as_div()``. Default: ``'django/forms/div.html'``. + +.. method:: Form.as_div() + +.. versionadded:: 4.1 + +``as_div()`` renders the form as a series of ``<div>`` elements, with each +``<div>`` containing one field, such as: + +.. code-block:: pycon + + >>> f = ContactForm() + >>> f.as_div() + +… gives HTML like: + +.. code-block:: html + + <div> + <label for="id_subject">Subject:</label> + <input type="text" name="subject" maxlength="100" required id="id_subject"> + </div> + <div> + <label for="id_message">Message:</label> + <input type="text" name="message" required id="id_message"> + </div> + <div> + <label for="id_sender">Sender:</label> + <input type="email" name="sender" required id="id_sender"> + </div> + <div> + <label for="id_cc_myself">Cc myself:</label> + <input type="checkbox" name="cc_myself" id="id_cc_myself"> + </div> + +.. note:: + + Of the framework provided templates and output styles, ``as_div()`` is + recommended over the ``as_p()``, ``as_table()``, and ``as_ul()`` versions + as the template implements ``<fieldset>`` and ``<legend>`` to group related + inputs and is easier for screen reader users to navigate. + ``as_p()`` ~~~~~~~~~~ diff --git a/docs/releases/4.1.txt b/docs/releases/4.1.txt index 2864d179b2..1bd3fe68b0 100644 --- a/docs/releases/4.1.txt +++ b/docs/releases/4.1.txt @@ -258,6 +258,15 @@ Forms :attr:`~django.forms.renderers.BaseRenderer.formset_template_name` renderer attribute. +* The new ``div.html`` form template, referencing + :attr:`.Form.template_name_div` attribute, and matching :meth:`.Form.as_div` + method, render forms using HTML ``<div>`` elements. + + This new output style is recommended over the existing + :meth:`~.Form.as_table`, :meth:`~.Form.as_p` and :meth:`~.Form.as_ul` styles, + as the template implements ``<fieldset>`` and ``<legend>`` to group related + inputs and is easier for screen reader users to navigate. + * 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`. diff --git a/docs/topics/forms/formsets.txt b/docs/topics/forms/formsets.txt index 0206959f1d..1b54f2accb 100644 --- a/docs/topics/forms/formsets.txt +++ b/docs/topics/forms/formsets.txt @@ -800,12 +800,22 @@ Formsets have the following attributes and methods associated with rendering: In older versions ``template_name`` defaulted to the string value ``'django/forms/formset/default.html'``. + +.. attribute:: BaseFormSet.template_name_div + + .. versionadded:: 4.1 + + The name of the template used when calling :meth:`.as_div`. By default this + is ``"django/forms/formsets/div.html"``. This template renders the + formset's management form and then each form in the formset as per the + form's :meth:`~django.forms.Form.as_div` method. + .. attribute:: BaseFormSet.template_name_p .. versionadded:: 4.0 The name of the template used when calling :meth:`.as_p`. By default this - is ``'django/forms/formsets/p.html'``. This template renders the formset's + is ``"django/forms/formsets/p.html"``. This template renders the formset's management form and then each form in the formset as per the form's :meth:`~django.forms.Form.as_p` method. @@ -814,7 +824,7 @@ Formsets have the following attributes and methods associated with rendering: .. versionadded:: 4.0 The name of the template used when calling :meth:`.as_table`. By default - this is ``'django/forms/formsets/table.html'``. This template renders the + this is ``"django/forms/formsets/table.html"``. This template renders the formset's management form and then each form in the formset as per the form's :meth:`~django.forms.Form.as_table` method. @@ -823,7 +833,7 @@ Formsets have the following attributes and methods associated with rendering: .. versionadded:: 4.0 The name of the template used when calling :meth:`.as_ul`. By default this - is ``'django/forms/formsets/ul.html'``. This template renders the formset's + is ``"django/forms/formsets/ul.html"``. This template renders the formset's management form and then each form in the formset as per the form's :meth:`~django.forms.Form.as_ul` method. diff --git a/docs/topics/forms/index.txt b/docs/topics/forms/index.txt index c8e43389a0..e153d73bab 100644 --- a/docs/topics/forms/index.txt +++ b/docs/topics/forms/index.txt @@ -566,12 +566,14 @@ Form rendering options There are other output options though for the ``<label>``/``<input>`` pairs: +* ``{{ form.as_div }}`` will render them wrapped in ``<div>`` tags. + * ``{{ form.as_table }}`` will render them as table cells wrapped in ``<tr>`` - tags + tags. -* ``{{ form.as_p }}`` will render them wrapped in ``<p>`` tags +* ``{{ form.as_p }}`` will render them wrapped in ``<p>`` tags. -* ``{{ form.as_ul }}`` will render them wrapped in ``<li>`` tags +* ``{{ form.as_ul }}`` will render them wrapped in ``<li>`` tags. Note that you'll have to provide the surrounding ``<table>`` or ``<ul>`` elements yourself. diff --git a/tests/forms_tests/tests/test_forms.py b/tests/forms_tests/tests/test_forms.py index ad8f7b9a31..e6e396c8a1 100644 --- a/tests/forms_tests/tests/test_forms.py +++ b/tests/forms_tests/tests/test_forms.py @@ -161,6 +161,15 @@ class FormsTestCase(SimpleTestCase): required></td></tr> """, ) + self.assertHTMLEqual( + p.as_div(), + '<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>', + ) def test_empty_dict(self): # Empty dictionaries are valid, too. @@ -219,6 +228,18 @@ class FormsTestCase(SimpleTestCase): <p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" required></p>""", ) + self.assertHTMLEqual( + p.as_div(), + '<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>', + ) def test_empty_querydict_args(self): data = QueryDict() @@ -274,6 +295,15 @@ class FormsTestCase(SimpleTestCase): <p><label for="id_birthday">Birthday:</label> <input type="text" name="birthday" id="id_birthday" required></p>""", ) + self.assertHTMLEqual( + p.as_div(), + '<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>", + ) def test_unicode_values(self): # Unicode values are handled properly. @@ -323,6 +353,17 @@ class FormsTestCase(SimpleTestCase): '<input type="text" name="birthday" value="1940-10-9" id="id_birthday" ' "required></p>", ) + self.assertHTMLEqual( + p.as_div(), + '<div><label for="id_first_name">First name:</label>' + '<input type="text" name="first_name" value="John" id="id_first_name" ' + 'required></div><div><label for="id_last_name">Last name:</label>' + '<input type="text" name="last_name"' + 'value="\u0160\u0110\u0106\u017d\u0107\u017e\u0161\u0111" ' + 'id="id_last_name" required></div><div><label for="id_birthday">' + 'Birthday:</label><input type="text" name="birthday" value="1940-10-9" ' + 'id="id_birthday" required></div>', + ) p = Person({"last_name": "Lennon"}) self.assertEqual(p.errors["first_name"], ["This field is required."]) @@ -439,6 +480,15 @@ class FormsTestCase(SimpleTestCase): <p><label for="birthday_id">Birthday:</label> <input type="text" name="birthday" id="birthday_id" required></p>""", ) + self.assertHTMLEqual( + p.as_div(), + '<div><label for="first_name_id">First name:</label><input type="text" ' + 'name="first_name" id="first_name_id" required></div><div><label ' + 'for="last_name_id">Last name:</label><input type="text" ' + 'name="last_name" id="last_name_id" required></div><div><label ' + 'for="birthday_id">Birthday:</label><input type="text" name="birthday" ' + 'id="birthday_id" required></div>', + ) def test_auto_id_true(self): # If auto_id is any True value whose str() does not contain '%s', the "id" @@ -746,6 +796,15 @@ class FormsTestCase(SimpleTestCase): <div><label><input type="radio" name="language" value="J" required> Java</label></div> </div></li>""", ) + # Need an auto_id to generate legend. + self.assertHTMLEqual( + f.render(f.template_name_div), + '<div> Name: <input type="text" name="name" required></div><div><fieldset>' + 'Language:<div><div><label><input type="radio" name="language" value="P" ' + 'required> Python</label></div><div><label><input type="radio" ' + 'name="language" value="J" required> Java</label></div></div></fieldset>' + "</div>", + ) # Regarding auto_id and <label>, RadioSelect is a special case. Each # radio button gets a distinct ID, formed by appending an underscore @@ -812,6 +871,16 @@ class FormsTestCase(SimpleTestCase): </div></p> """, ) + self.assertHTMLEqual( + f.render(f.template_name_div), + '<div><label for="id_name">Name:</label><input type="text" name="name" ' + 'required id="id_name"></div><div><fieldset><legend>Language:</legend>' + '<div id="id_language"><div><label for="id_language_0"><input ' + 'type="radio" name="language" value="P" required id="id_language_0">' + 'Python</label></div><div><label for="id_language_1"><input type="radio" ' + 'name="language" value="J" required id="id_language_1">Java</label></div>' + "</div></fieldset></div>", + ) def test_form_with_iterable_boundfield(self): class BeatleForm(Form): @@ -1022,6 +1091,14 @@ class FormsTestCase(SimpleTestCase): '<option value="P">Paul McCartney</option>' "</select></p>", ) + self.assertHTMLEqual( + f.render(f.template_name_div), + '<div><label for="id_name">Name:</label><input type="text" name="name" ' + 'required id="id_name"></div><div><label for="id_composers">Composers:' + '</label><select name="composers" required id="id_composers" multiple>' + '<option value="J">John Lennon</option><option value="P">Paul McCartney' + "</option></select></div>", + ) def test_multiple_checkbox_render(self): f = SongForm() @@ -1064,6 +1141,16 @@ class FormsTestCase(SimpleTestCase): 'id="id_composers_1">Paul McCartney</label></div>' "</div></p>", ) + self.assertHTMLEqual( + f.render(f.template_name_div), + '<div><label for="id_name">Name:</label><input type="text" name="name" ' + 'required id="id_name"></div><div><fieldset><legend>Composers:</legend>' + '<div id="id_composers"><div><label for="id_composers_0"><input ' + 'type="checkbox" name="composers" value="J" id="id_composers_0">' + 'John Lennon</label></div><div><label for="id_composers_1"><input ' + 'type="checkbox" name="composers" value="P" id="id_composers_1">' + "Paul McCartney</label></div></div></fieldset></div>", + ) def test_form_with_disabled_fields(self): class PersonForm(Form): @@ -1492,6 +1579,14 @@ class FormsTestCase(SimpleTestCase): <li>Password2: <input type="password" name="password2" required></li> """, ) + self.assertHTMLEqual( + f.render(f.template_name_div), + '<ul class="errorlist nonfield"><li>Please make sure your passwords match.' + '</li></ul><div>Username: <input type="text" name="username" ' + 'value="adrian" maxlength="10" required></div><div>Password1: <input ' + 'type="password" name="password1" required></div><div>Password2: <input ' + 'type="password" name="password2" required></div>', + ) f = UserRegistration( {"username": "adrian", "password1": "foo", "password2": "foo"}, @@ -1649,6 +1744,12 @@ class FormsTestCase(SimpleTestCase): "<li>(Hidden field hidden_input) This field is required.</li></ul>" '<p><input type="hidden" name="hidden_input" id="id_hidden_input"></p>', ) + self.assertHTMLEqual( + f.render(f.template_name_div), + '<ul class="errorlist nonfield"><li>Form error</li>' + "<li>(Hidden field hidden_input) This field is required.</li></ul>" + '<div><input type="hidden" name="hidden_input" id="id_hidden_input"></div>', + ) def test_dynamic_construction(self): # It's possible to construct a Form dynamically by adding to the self.fields @@ -1893,6 +1994,13 @@ class FormsTestCase(SimpleTestCase): <input type="hidden" name="hidden_text"></p> """, ) + self.assertHTMLEqual( + p.as_div(), + '<div>First name: <input type="text" name="first_name" required></div>' + '<div>Last name: <input type="text" name="last_name" required></div><div>' + 'Birthday: <input type="text" name="birthday" required><input ' + 'type="hidden" name="hidden_text"></div>', + ) # With auto_id set, a HiddenInput still gets an ID, but it doesn't get a label. p = Person(auto_id="id_%s") @@ -1926,6 +2034,15 @@ class FormsTestCase(SimpleTestCase): <input type="text" name="birthday" id="id_birthday" required> <input type="hidden" name="hidden_text" id="id_hidden_text"></p>""", ) + self.assertHTMLEqual( + p.as_div(), + '<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>' + '<input type="hidden" name="hidden_text" id="id_hidden_text"></div>', + ) # If a field with a HiddenInput has errors, the as_table() and as_ul() output # will include the error message(s) with the text "(Hidden field [fieldname]) " @@ -1976,6 +2093,15 @@ class FormsTestCase(SimpleTestCase): <input type="hidden" name="hidden_text"></p> """, ) + self.assertHTMLEqual( + p.as_div(), + '<ul class="errorlist nonfield"><li>(Hidden field hidden_text) This field ' + 'is required.</li></ul><div>First name: <input type="text" ' + 'name="first_name" value="John" required></div><div>Last name: <input ' + 'type="text" name="last_name" value="Lennon" required></div><div>' + 'Birthday: <input type="text" name="birthday" value="1940-10-9" required>' + '<input type="hidden" name="hidden_text"></div>', + ) # A corner case: It's possible for a form to have only HiddenInputs. class TestForm(Form): @@ -2811,6 +2937,13 @@ Options: <select multiple name="options" required> <br> <span class="helptext">Wählen Sie mit Bedacht.</span></td></tr>""", ) + self.assertHTMLEqual( + p.as_div(), + '<div>Username: <div class="helptext">e.g., user@example.com</div>' + '<input type="text" name="username" maxlength="10" required></div>' + '<div>Password: <div class="helptext">Wählen Sie mit Bedacht.</div>' + '<input type="password" name="password" required></div>', + ) # The help text is displayed whether or not data is provided for the form. p = UserRegistration({"username": "foo"}, auto_id=False) @@ -3431,6 +3564,21 @@ Password: <input type="password" name="password" required> <td><ul class="errorlist"><li>This field is required.</li></ul> <input type="number" name="age" id="id_age" required></td></tr>""", ) + self.assertHTMLEqual( + p.as_div(), + '<div class="required error"><label for="id_name" class="required">Name:' + '</label><ul class="errorlist"><li>This field is required.</li></ul>' + '<input type="text" name="name" required id="id_name" /></div>' + '<div class="required"><label for="id_is_cool" class="required">Is cool:' + '</label><select name="is_cool" id="id_is_cool">' + '<option value="unknown" selected>Unknown</option>' + '<option value="true">Yes</option><option value="false">No</option>' + '</select></div><div><label for="id_email">Email:</label>' + '<input type="email" name="email" id="id_email" /></div>' + '<div class="required error"><label for="id_age" class="required">Age:' + '</label><ul class="errorlist"><li>This field is required.</li></ul>' + '<input type="number" name="age" required id="id_age" /></div>', + ) def test_label_has_required_css_class(self): """ @@ -4027,6 +4175,13 @@ Password: <input type="password" name="password" required> <input id="id_first_name" name="first_name" type="text" value="John" required> <input id="id_last_name" name="last_name" type="hidden"></td></tr>""", ) + self.assertHTMLEqual( + p.as_div(), + '<ul class="errorlist nonfield"><li>(Hidden field last_name) This field ' + 'is required.</li></ul><div><label for="id_first_name">First name:</label>' + '<input id="id_first_name" name="first_name" type="text" value="John" ' + 'required><input id="id_last_name" name="last_name" type="hidden"></div>', + ) def test_error_list_with_non_field_errors_has_correct_class(self): class Person(Form): @@ -4076,6 +4231,15 @@ Password: <input type="password" name="password" required> </td></tr> """, ) + self.assertHTMLEqual( + p.as_div(), + '<ul class="errorlist nonfield"><li>Generic validation error</li></ul>' + '<div><label for="id_first_name">First name:</label><input ' + 'id="id_first_name" name="first_name" type="text" value="John" required>' + '</div><div><label for="id_last_name">Last name:</label><input ' + 'id="id_last_name" name="last_name" type="text" value="Lennon" required>' + "</div>", + ) def test_error_escaping(self): class TestForm(Form): @@ -4271,6 +4435,16 @@ Password: <input type="password" name="password" required> '<option value="J">Java</option>' "</select></td></tr>", ) + self.assertHTMLEqual( + form.render(form.template_name_div), + '<div><label for="id_f1">F1:</label><input id="id_f1" maxlength="30" ' + 'name="f1" type="text" required></div><div><label for="id_f2">F2:</label>' + '<input id="id_f2" maxlength="30" name="f2" type="text"></div><div><label ' + 'for="id_f3">F3:</label><textarea cols="40" id="id_f3" name="f3" ' + 'rows="10" required></textarea></div><div><label for="id_f4">F4:</label>' + '<select id="id_f4" name="f4"><option value="P">Python</option>' + '<option value="J">Java</option></select></div>', + ) def test_use_required_attribute_false(self): class MyForm(Form): @@ -4322,6 +4496,16 @@ Password: <input type="password" name="password" required> '<option value="J">Java</option>' "</select></td></tr>", ) + self.assertHTMLEqual( + form.render(form.template_name_div), + '<div><label for="id_f1">F1:</label> <input id="id_f1" maxlength="30" ' + 'name="f1" type="text"></div><div><label for="id_f2">F2:</label>' + '<input id="id_f2" maxlength="30" name="f2" type="text"></div><div>' + '<label for="id_f3">F3:</label> <textarea cols="40" id="id_f3" name="f3" ' + 'rows="10"></textarea></div><div><label for="id_f4">F4:</label>' + '<select id="id_f4" name="f4"><option value="P">Python</option>' + '<option value="J">Java</option></select></div>', + ) def test_only_hidden_fields(self): # A form with *only* hidden fields that has errors is going to be very unusual. diff --git a/tests/forms_tests/tests/test_formsets.py b/tests/forms_tests/tests/test_formsets.py index afa7a8a33d..c713c85bfb 100644 --- a/tests/forms_tests/tests/test_formsets.py +++ b/tests/forms_tests/tests/test_formsets.py @@ -1597,6 +1597,18 @@ class FormsetAsTagTests(SimpleTestCase): ), ) + def test_as_div(self): + self.assertHTMLEqual( + self.formset.as_div(), + self.management_form_html + + ( + "<div>Choice: " + '<input type="text" name="choices-0-choice" value="Calexico"></div>' + '<div>Votes: <input type="number" name="choices-0-votes" value="100">' + "</div>" + ), + ) + @jinja2_tests class Jinja2FormsetAsTagTests(FormsetAsTagTests): |
