summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid <smithdc@gmail.com>2022-01-13 23:08:38 +0000
committerCarlton Gibson <carlton@noumenal.es>2022-03-30 16:28:14 +0200
commitc8459708a7e0a2474255b77d0f104a7f16e8b32c (patch)
treeadd60b1d21cfaa37e51c116348054e71c91f73a1
parent04ad0f26ba4b8c79dc311e1789457e0c4d1b8832 (diff)
Refs #32339 -- Added use_fieldset to Widget.
-rw-r--r--django/forms/boundfield.py7
-rw-r--r--django/forms/widgets.py4
-rw-r--r--docs/ref/forms/api.txt6
-rw-r--r--docs/ref/forms/widgets.txt13
-rw-r--r--docs/releases/4.1.txt4
-rw-r--r--docs/topics/forms/index.txt26
-rw-r--r--tests/forms_tests/templates/forms_tests/use_fieldset.html18
-rw-r--r--tests/forms_tests/widget_tests/test_checkboxinput.py15
-rw-r--r--tests/forms_tests/widget_tests/test_checkboxselectmultiple.py23
-rw-r--r--tests/forms_tests/widget_tests/test_clearablefileinput.py27
-rw-r--r--tests/forms_tests/widget_tests/test_dateinput.py15
-rw-r--r--tests/forms_tests/widget_tests/test_datetimeinput.py15
-rw-r--r--tests/forms_tests/widget_tests/test_fileinput.py15
-rw-r--r--tests/forms_tests/widget_tests/test_hiddeninput.py14
-rw-r--r--tests/forms_tests/widget_tests/test_multiplehiddeninput.py19
-rw-r--r--tests/forms_tests/widget_tests/test_multiwidget.py24
-rw-r--r--tests/forms_tests/widget_tests/test_nullbooleanselect.py18
-rw-r--r--tests/forms_tests/widget_tests/test_numberinput.py21
-rw-r--r--tests/forms_tests/widget_tests/test_passwordinput.py15
-rw-r--r--tests/forms_tests/widget_tests/test_radioselect.py25
-rw-r--r--tests/forms_tests/widget_tests/test_select.py19
-rw-r--r--tests/forms_tests/widget_tests/test_selectdatewidget.py43
-rw-r--r--tests/forms_tests/widget_tests/test_selectmultiple.py20
-rw-r--r--tests/forms_tests/widget_tests/test_splitdatetimewidget.py25
-rw-r--r--tests/forms_tests/widget_tests/test_splithiddendatetimewidget.py34
-rw-r--r--tests/forms_tests/widget_tests/test_textarea.py16
-rw-r--r--tests/forms_tests/widget_tests/test_textinput.py15
-rw-r--r--tests/forms_tests/widget_tests/test_timeinput.py15
28 files changed, 489 insertions, 22 deletions
diff --git a/django/forms/boundfield.py b/django/forms/boundfield.py
index e83160645e..9f2ae59ab4 100644
--- a/django/forms/boundfield.py
+++ b/django/forms/boundfield.py
@@ -286,6 +286,13 @@ class BoundField:
r"widget$|input$", "", self.field.widget.__class__.__name__.lower()
)
+ @property
+ def use_fieldset(self):
+ """
+ Return the value of this BoundField widget's use_fieldset attribute.
+ """
+ return self.field.widget.use_fieldset
+
@html_safe
class BoundWidget:
diff --git a/django/forms/widgets.py b/django/forms/widgets.py
index 208464b60a..760579ebe5 100644
--- a/django/forms/widgets.py
+++ b/django/forms/widgets.py
@@ -234,6 +234,7 @@ class Widget(metaclass=MediaDefiningClass):
is_localized = False
is_required = False
supports_microseconds = True
+ use_fieldset = False
def __init__(self, attrs=None):
self.attrs = {} if attrs is None else attrs.copy()
@@ -821,6 +822,7 @@ class RadioSelect(ChoiceWidget):
input_type = "radio"
template_name = "django/forms/widgets/radio.html"
option_template_name = "django/forms/widgets/radio_option.html"
+ use_fieldset = True
def id_for_label(self, id_, index=None):
"""
@@ -862,6 +864,7 @@ class MultiWidget(Widget):
"""
template_name = "django/forms/widgets/multiwidget.html"
+ use_fieldset = True
def __init__(self, widgets, attrs=None):
if isinstance(widgets, dict):
@@ -1027,6 +1030,7 @@ class SelectDateWidget(Widget):
input_type = "select"
select_widget = Select
date_re = _lazy_re_compile(r"(\d{4}|0)-(\d\d?)-(\d\d?)$")
+ use_fieldset = True
def __init__(self, attrs=None, years=None, months=None, empty_label=None):
self.attrs = attrs or {}
diff --git a/docs/ref/forms/api.txt b/docs/ref/forms/api.txt
index e092a2f59e..4d3cf8997d 100644
--- a/docs/ref/forms/api.txt
+++ b/docs/ref/forms/api.txt
@@ -1160,6 +1160,12 @@ Attributes of ``BoundField``
>>> print(f['message'].name)
message
+.. attribute:: BoundField.use_fieldset
+
+ .. versionadded:: 4.1
+
+ Returns the value of this BoundField widget's ``use_fieldset`` attribute.
+
.. attribute:: BoundField.widget_type
Returns the lowercased class name of the wrapped field's widget, with any
diff --git a/docs/ref/forms/widgets.txt b/docs/ref/forms/widgets.txt
index 970e30456c..e0f175af79 100644
--- a/docs/ref/forms/widgets.txt
+++ b/docs/ref/forms/widgets.txt
@@ -315,6 +315,19 @@ foundation for custom widgets.
``<select multiple>`` don't appear in the data of an HTML form
submission, so it's unknown whether or not the user submitted a value.
+ .. attribute:: Widget.use_fieldset
+
+ .. versionadded:: 4.1
+
+ An attribute to identify if the widget should be grouped in a
+ ``<fieldset>`` with a ``<legend>`` when rendered. Defaults to ``False``
+ but is ``True`` when the widget contains multiple ``<input>`` tags such as
+ :class:`~django.forms.CheckboxSelectMultiple`,
+ :class:`~django.forms.RadioSelect`,
+ :class:`~django.forms.MultiWidget`,
+ :class:`~django.forms.SplitDateTimeWidget`, and
+ :class:`~django.forms.SelectDateWidget`.
+
.. method:: use_required_attribute(initial)
Given a form field's ``initial`` value, returns whether or not the
diff --git a/docs/releases/4.1.txt b/docs/releases/4.1.txt
index 8fc81e5ee7..7660780ae5 100644
--- a/docs/releases/4.1.txt
+++ b/docs/releases/4.1.txt
@@ -217,6 +217,10 @@ Forms
objects implement the ``__html__()`` method (typically when decorated with
the :func:`~django.utils.html.html_safe` decorator).
+* The new :attr:`.BoundField.use_fieldset` and :attr:`.Widget.use_fieldset`
+ attributes help to identify widgets where its inputs should be grouped in a
+ ``<fieldset>`` with a ``<legend>``.
+
Generic Views
~~~~~~~~~~~~~
diff --git a/docs/topics/forms/index.txt b/docs/topics/forms/index.txt
index d87b9be8d6..2d0d61aa02 100644
--- a/docs/topics/forms/index.txt
+++ b/docs/topics/forms/index.txt
@@ -686,6 +686,32 @@ Useful attributes on ``{{ field }}`` include:
<label for="id_email">Email address:</label>
+``{{ field.legend_tag }}``
+
+ .. versionadded:: 4.1
+
+ Similar to ``field.label_tag`` but uses a ``<legend>`` tag in place of
+ ``<label>``, for widgets with multiple inputs wrapped in a ``<fieldset>``.
+
+``{{ field.use_fieldset }}``
+
+ .. versionadded:: 4.1
+
+ This attribute is ``True`` if the form field's widget contains multiple
+ inputs that should be semantically grouped in a ``<fieldset>`` with a
+ ``<legend>`` to improve accessibility. An example use in a template:
+
+.. code-block:: html+django
+
+ {% if field.use_fieldset %}
+ <fieldset>
+ {% if field.label %}{{ field.legend_tag }}{% endif %}
+ {% else %}
+ {% if field.label %}{{ field.label_tag }}{% endif %}
+ {% endif %}
+ {{ field }}
+ {% if field.use_fieldset %}</fieldset>{% endif %}
+
``{{ field.value }}``
The value of the field. e.g ``someone@example.com``.
diff --git a/tests/forms_tests/templates/forms_tests/use_fieldset.html b/tests/forms_tests/templates/forms_tests/use_fieldset.html
new file mode 100644
index 0000000000..81efe6d5cc
--- /dev/null
+++ b/tests/forms_tests/templates/forms_tests/use_fieldset.html
@@ -0,0 +1,18 @@
+{% for field, errors in fields %}
+ <div>
+ {% if field.use_fieldset %}
+ <fieldset>
+ {% if field.label %}{{ field.legend_tag }}{% endif %}
+ {% else %}
+ {% if field.label %}{{ field.label_tag }}{% endif %}
+ {% endif %}
+ {{ 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/tests/forms_tests/widget_tests/test_checkboxinput.py b/tests/forms_tests/widget_tests/test_checkboxinput.py
index c30aadbbb1..386ae00840 100644
--- a/tests/forms_tests/widget_tests/test_checkboxinput.py
+++ b/tests/forms_tests/widget_tests/test_checkboxinput.py
@@ -1,4 +1,4 @@
-from django.forms import CheckboxInput
+from django.forms import BooleanField, CheckboxInput, Form
from .base import WidgetTest
@@ -124,3 +124,16 @@ class CheckboxInputTest(WidgetTest):
attrs = {"checked": False}
self.widget.get_context("name", True, attrs)
self.assertIs(attrs["checked"], False)
+
+ def test_fieldset(self):
+ class TestForm(Form):
+ template_name = "forms_tests/use_fieldset.html"
+ field = BooleanField(widget=self.widget)
+
+ form = TestForm()
+ self.assertIs(self.widget.use_fieldset, False)
+ self.assertHTMLEqual(
+ form.render(),
+ '<div><label for="id_field">Field:</label>'
+ '<input id="id_field" name="field" required type="checkbox"></div>',
+ )
diff --git a/tests/forms_tests/widget_tests/test_checkboxselectmultiple.py b/tests/forms_tests/widget_tests/test_checkboxselectmultiple.py
index 89cd473638..0e9fc1acc4 100644
--- a/tests/forms_tests/widget_tests/test_checkboxselectmultiple.py
+++ b/tests/forms_tests/widget_tests/test_checkboxselectmultiple.py
@@ -1,7 +1,7 @@
import datetime
from django import forms
-from django.forms import CheckboxSelectMultiple
+from django.forms import CheckboxSelectMultiple, ChoiceField, Form
from django.test import override_settings
from .base import WidgetTest
@@ -254,3 +254,24 @@ class CheckboxSelectMultipleTest(WidgetTest):
self.assertEqual(bound_field.field.widget.id_for_label("id"), "")
self.assertEqual(bound_field.label_tag(), "<label>F:</label>")
self.assertEqual(bound_field.legend_tag(), "<legend>F:</legend>")
+
+ def test_fieldset(self):
+ class TestForm(Form):
+ template_name = "forms_tests/use_fieldset.html"
+ field = ChoiceField(widget=self.widget, choices=self.beatles)
+
+ form = TestForm()
+ self.assertIs(self.widget.use_fieldset, True)
+ self.assertHTMLEqual(
+ form.render(),
+ '<div><fieldset><legend>Field:</legend><div id="id_field">'
+ '<div><label for="id_field_0"><input type="checkbox" '
+ 'name="field" value="J" id="id_field_0"> John</label></div>'
+ '<div><label for="id_field_1"><input type="checkbox" '
+ 'name="field" value="P" id="id_field_1">Paul</label></div>'
+ '<div><label for="id_field_2"><input type="checkbox" '
+ 'name="field" value="G" id="id_field_2"> George</label></div>'
+ '<div><label for="id_field_3"><input type="checkbox" '
+ 'name="field" value="R" id="id_field_3">'
+ "Ringo</label></div></div></fieldset></div>",
+ )
diff --git a/tests/forms_tests/widget_tests/test_clearablefileinput.py b/tests/forms_tests/widget_tests/test_clearablefileinput.py
index 630ff79486..fb6a2f6b72 100644
--- a/tests/forms_tests/widget_tests/test_clearablefileinput.py
+++ b/tests/forms_tests/widget_tests/test_clearablefileinput.py
@@ -1,5 +1,5 @@
from django.core.files.uploadedfile import SimpleUploadedFile
-from django.forms import ClearableFileInput, MultiWidget
+from django.forms import ClearableFileInput, FileField, Form, MultiWidget
from .base import WidgetTest
@@ -207,3 +207,28 @@ class ClearableFileInputTest(WidgetTest):
self.assertIs(
widget.value_omitted_from_data({"field-clear": "y"}, {}, "field"), False
)
+
+ def test_fieldset(self):
+ class TestForm(Form):
+ template_name = "forms_tests/use_fieldset.html"
+ field = FileField(widget=self.widget)
+ with_file = FileField(widget=self.widget, initial=FakeFieldFile())
+ clearable_file = FileField(
+ widget=self.widget, initial=FakeFieldFile(), required=False
+ )
+
+ form = TestForm()
+ self.assertIs(self.widget.use_fieldset, False)
+ self.assertHTMLEqual(
+ '<div><label for="id_field">Field:</label>'
+ '<input id="id_field" name="field" type="file" required></div>'
+ '<div><label for="id_with_file">With file:</label>Currently: '
+ '<a href="something">something</a><br>Change:<input type="file" '
+ 'name="with_file" id="id_with_file"></div>'
+ '<div><label for="id_clearable_file">Clearable file:</label>'
+ 'Currently: <a href="something">something</a><input '
+ 'type="checkbox" name="clearable_file-clear" id="clearable_file-clear_id">'
+ '<label for="clearable_file-clear_id">Clear</label><br>Change:'
+ '<input type="file" name="clearable_file" id="id_clearable_file"></div>',
+ form.render(),
+ )
diff --git a/tests/forms_tests/widget_tests/test_dateinput.py b/tests/forms_tests/widget_tests/test_dateinput.py
index 07446480f6..889f4e9eaa 100644
--- a/tests/forms_tests/widget_tests/test_dateinput.py
+++ b/tests/forms_tests/widget_tests/test_dateinput.py
@@ -1,6 +1,6 @@
from datetime import date
-from django.forms import DateInput
+from django.forms import CharField, DateInput, Form
from django.utils import translation
from .base import WidgetTest
@@ -60,3 +60,16 @@ class DateInputTest(WidgetTest):
date(2007, 9, 17),
html='<input type="text" name="date" value="17.09.2007">',
)
+
+ def test_fieldset(self):
+ class TestForm(Form):
+ template_name = "forms_tests/use_fieldset.html"
+ field = CharField(widget=self.widget)
+
+ form = TestForm()
+ self.assertIs(self.widget.use_fieldset, False)
+ self.assertHTMLEqual(
+ form.render(),
+ '<div><label for="id_field">Field:</label>'
+ '<input id="id_field" name="field" required type="text"></div>',
+ )
diff --git a/tests/forms_tests/widget_tests/test_datetimeinput.py b/tests/forms_tests/widget_tests/test_datetimeinput.py
index e9bb77eb69..9871a66b36 100644
--- a/tests/forms_tests/widget_tests/test_datetimeinput.py
+++ b/tests/forms_tests/widget_tests/test_datetimeinput.py
@@ -1,6 +1,6 @@
from datetime import datetime
-from django.forms import DateTimeInput
+from django.forms import CharField, DateTimeInput, Form
from django.test import ignore_warnings
from django.utils import translation
from django.utils.deprecation import RemovedInDjango50Warning
@@ -97,3 +97,16 @@ class DateTimeInputTest(WidgetTest):
d,
html='<input type="text" name="date" value="17/09/2007 12:51:34">',
)
+
+ def test_fieldset(self):
+ class TestForm(Form):
+ template_name = "forms_tests/use_fieldset.html"
+ field = CharField(widget=self.widget)
+
+ form = TestForm()
+ self.assertIs(self.widget.use_fieldset, False)
+ self.assertHTMLEqual(
+ '<div><label for="id_field">Field:</label>'
+ '<input id="id_field" name="field" required type="text"></div>',
+ form.render(),
+ )
diff --git a/tests/forms_tests/widget_tests/test_fileinput.py b/tests/forms_tests/widget_tests/test_fileinput.py
index ea11c639e4..ea73e577ce 100644
--- a/tests/forms_tests/widget_tests/test_fileinput.py
+++ b/tests/forms_tests/widget_tests/test_fileinput.py
@@ -1,4 +1,4 @@
-from django.forms import FileInput
+from django.forms import FileField, FileInput, Form
from .base import WidgetTest
@@ -35,3 +35,16 @@ class FileInputTest(WidgetTest):
# user to keep the existing, initial value.
self.assertIs(self.widget.use_required_attribute(None), True)
self.assertIs(self.widget.use_required_attribute("resume.txt"), False)
+
+ def test_fieldset(self):
+ class TestForm(Form):
+ template_name = "forms_tests/use_fieldset.html"
+ field = FileField(widget=self.widget)
+
+ form = TestForm()
+ self.assertIs(self.widget.use_fieldset, False)
+ self.assertHTMLEqual(
+ '<div><label for="id_field">Field:</label><input id="id_field" '
+ 'name="field" required type="file"></div>',
+ form.render(),
+ )
diff --git a/tests/forms_tests/widget_tests/test_hiddeninput.py b/tests/forms_tests/widget_tests/test_hiddeninput.py
index ba5e796501..f3627aa220 100644
--- a/tests/forms_tests/widget_tests/test_hiddeninput.py
+++ b/tests/forms_tests/widget_tests/test_hiddeninput.py
@@ -1,4 +1,4 @@
-from django.forms import HiddenInput
+from django.forms import CharField, Form, HiddenInput
from .base import WidgetTest
@@ -17,3 +17,15 @@ class HiddenInputTest(WidgetTest):
self.assertIs(self.widget.use_required_attribute(None), False)
self.assertIs(self.widget.use_required_attribute(""), False)
self.assertIs(self.widget.use_required_attribute("foo"), False)
+
+ def test_fieldset(self):
+ class TestForm(Form):
+ template_name = "forms_tests/use_fieldset.html"
+ field = CharField(widget=self.widget)
+
+ form = TestForm()
+ self.assertIs(self.widget.use_fieldset, False)
+ self.assertHTMLEqual(
+ '<input type="hidden" name="field" id="id_field">',
+ form.render(),
+ )
diff --git a/tests/forms_tests/widget_tests/test_multiplehiddeninput.py b/tests/forms_tests/widget_tests/test_multiplehiddeninput.py
index 9498b756e9..010ee7d44e 100644
--- a/tests/forms_tests/widget_tests/test_multiplehiddeninput.py
+++ b/tests/forms_tests/widget_tests/test_multiplehiddeninput.py
@@ -1,4 +1,5 @@
-from django.forms import MultipleHiddenInput
+from django.forms import Form, MultipleChoiceField, MultipleHiddenInput
+from django.utils.datastructures import MultiValueDict
from .base import WidgetTest
@@ -104,3 +105,19 @@ class MultipleHiddenInputTest(WidgetTest):
'<input type="hidden" name="letters" value="c" id="hideme_2">'
),
)
+
+ def test_fieldset(self):
+ class TestForm(Form):
+ template_name = "forms_tests/use_fieldset.html"
+ composers = MultipleChoiceField(
+ choices=[("J", "John Lennon"), ("P", "Paul McCartney")],
+ widget=MultipleHiddenInput,
+ )
+
+ form = TestForm(MultiValueDict({"composers": ["J", "P"]}))
+ self.assertIs(self.widget.use_fieldset, False)
+ self.assertHTMLEqual(
+ '<input type="hidden" name="composers" value="J" id="id_composers_0">'
+ '<input type="hidden" name="composers" value="P" id="id_composers_1">',
+ form.render(),
+ )
diff --git a/tests/forms_tests/widget_tests/test_multiwidget.py b/tests/forms_tests/widget_tests/test_multiwidget.py
index a89082c4a6..aac859a4b8 100644
--- a/tests/forms_tests/widget_tests/test_multiwidget.py
+++ b/tests/forms_tests/widget_tests/test_multiwidget.py
@@ -4,6 +4,7 @@ from datetime import datetime
from django.forms import (
CharField,
FileInput,
+ Form,
MultipleChoiceField,
MultiValueField,
MultiWidget,
@@ -51,7 +52,9 @@ class ComplexField(MultiValueField):
MultipleChoiceField(choices=WidgetTest.beatles),
SplitDateTimeField(),
)
- super().__init__(fields, required, widget, label, initial)
+ super().__init__(
+ fields, required=required, widget=widget, label=label, initial=initial
+ )
def compress(self, data_list):
if data_list:
@@ -296,3 +299,22 @@ class MultiWidgetTest(WidgetTest):
# w2 ought to be independent of w1, since MultiWidget ought
# to make a copy of its sub-widgets when it is copied.
self.assertEqual(w1.choices, [1, 2, 3])
+
+ def test_fieldset(self):
+ class TestForm(Form):
+ template_name = "forms_tests/use_fieldset.html"
+ field = ComplexField(widget=ComplexMultiWidget)
+
+ form = TestForm()
+ self.assertIs(form["field"].field.widget.use_fieldset, True)
+ self.assertHTMLEqual(
+ "<div><fieldset><legend>Field:</legend>"
+ '<input type="text" name="field_0" required id="id_field_0">'
+ '<select name="field_1" required id="id_field_1" multiple>'
+ '<option value="J">John</option><option value="P">Paul</option>'
+ '<option value="G">George</option><option value="R">Ringo</option></select>'
+ '<input type="text" name="field_2_0" required id="id_field_2_0">'
+ '<input type="text" name="field_2_1" required id="id_field_2_1">'
+ "</fieldset></div>",
+ form.render(),
+ )
diff --git a/tests/forms_tests/widget_tests/test_nullbooleanselect.py b/tests/forms_tests/widget_tests/test_nullbooleanselect.py
index 927abdba01..307ae56579 100644
--- a/tests/forms_tests/widget_tests/test_nullbooleanselect.py
+++ b/tests/forms_tests/widget_tests/test_nullbooleanselect.py
@@ -1,4 +1,4 @@
-from django.forms import NullBooleanSelect
+from django.forms import Form, NullBooleanField, NullBooleanSelect
from django.utils import translation
from .base import WidgetTest
@@ -154,3 +154,19 @@ class NullBooleanSelectTest(WidgetTest):
"""
),
)
+
+ def test_fieldset(self):
+ class TestForm(Form):
+ template_name = "forms_tests/use_fieldset.html"
+ field = NullBooleanField(widget=self.widget)
+
+ form = TestForm()
+ self.assertIs(self.widget.use_fieldset, False)
+ self.assertHTMLEqual(
+ '<div><label for="id_field">Field:</label>'
+ '<select name="field" id="id_field">'
+ '<option value="unknown" selected>Unknown</option>'
+ '<option value="true">Yes</option>'
+ '<option value="false">No</option></select></div>',
+ form.render(),
+ )
diff --git a/tests/forms_tests/widget_tests/test_numberinput.py b/tests/forms_tests/widget_tests/test_numberinput.py
index 15df02db4f..a1c7a4c7d2 100644
--- a/tests/forms_tests/widget_tests/test_numberinput.py
+++ b/tests/forms_tests/widget_tests/test_numberinput.py
@@ -1,17 +1,32 @@
-from django.forms.widgets import NumberInput
+from django.forms import CharField, Form, NumberInput
from django.test import override_settings
from .base import WidgetTest
class NumberInputTests(WidgetTest):
+ widget = NumberInput(attrs={"max": 12345, "min": 1234, "step": 9999})
+
@override_settings(USE_THOUSAND_SEPARATOR=True)
def test_attrs_not_localized(self):
- widget = NumberInput(attrs={"max": 12345, "min": 1234, "step": 9999})
self.check_html(
- widget,
+ self.widget,
"name",
"value",
'<input type="number" name="name" value="value" max="12345" min="1234" '
'step="9999">',
)
+
+ def test_fieldset(self):
+ class TestForm(Form):
+ template_name = "forms_tests/use_fieldset.html"
+ field = CharField(widget=self.widget)
+
+ form = TestForm()
+ self.assertIs(self.widget.use_fieldset, False)
+ self.assertHTMLEqual(
+ '<div><label for="id_field">Field:</label>'
+ '<input id="id_field" max="12345" min="1234" '
+ 'name="field" required step="9999" type="number"></div>',
+ form.render(),
+ )
diff --git a/tests/forms_tests/widget_tests/test_passwordinput.py b/tests/forms_tests/widget_tests/test_passwordinput.py
index 1adf0cc3cb..82d1a63901 100644
--- a/tests/forms_tests/widget_tests/test_passwordinput.py
+++ b/tests/forms_tests/widget_tests/test_passwordinput.py
@@ -1,4 +1,4 @@
-from django.forms import PasswordInput
+from django.forms import CharField, Form, PasswordInput
from .base import WidgetTest
@@ -37,3 +37,16 @@ class PasswordInputTest(WidgetTest):
"test@example.com",
html='<input type="password" name="password" value="test@example.com">',
)
+
+ def test_fieldset(self):
+ class TestForm(Form):
+ template_name = "forms_tests/use_fieldset.html"
+ field = CharField(widget=self.widget)
+
+ form = TestForm()
+ self.assertIs(self.widget.use_fieldset, False)
+ self.assertHTMLEqual(
+ '<div><label for="id_field">Field:</label>'
+ '<input type="password" name="field" required id="id_field"></div>',
+ form.render(),
+ )
diff --git a/tests/forms_tests/widget_tests/test_radioselect.py b/tests/forms_tests/widget_tests/test_radioselect.py
index 5aa4c194de..dc3f3d9bad 100644
--- a/tests/forms_tests/widget_tests/test_radioselect.py
+++ b/tests/forms_tests/widget_tests/test_radioselect.py
@@ -1,6 +1,6 @@
import datetime
-from django.forms import MultiWidget, RadioSelect
+from django.forms import ChoiceField, Form, MultiWidget, RadioSelect
from django.test import override_settings
from .base import WidgetTest
@@ -199,3 +199,26 @@ class RadioSelectTest(WidgetTest):
</div>
""",
)
+
+ def test_fieldset(self):
+ class TestForm(Form):
+ template_name = "forms_tests/use_fieldset.html"
+ field = ChoiceField(
+ widget=self.widget, choices=self.beatles, required=False
+ )
+
+ form = TestForm()
+ self.assertIs(self.widget.use_fieldset, True)
+ self.assertHTMLEqual(
+ '<div><fieldset><legend>Field:</legend><div id="id_field">'
+ '<div><label for="id_field_0">'
+ '<input type="radio" name="field" value="J" id="id_field_0"> John'
+ '</label></div><div><label for="id_field_1">'
+ '<input type="radio" name="field" value="P" id="id_field_1">Paul'
+ '</label></div><div><label for="id_field_2"><input type="radio" '
+ 'name="field" value="G" id="id_field_2"> George</label></div>'
+ '<div><label for="id_field_3"><input type="radio" name="field" '
+ 'value="R" id="id_field_3">Ringo</label></div></div></fieldset>'
+ "</div>",
+ form.render(),
+ )
diff --git a/tests/forms_tests/widget_tests/test_select.py b/tests/forms_tests/widget_tests/test_select.py
index 8a40bd8b6a..77450e3716 100644
--- a/tests/forms_tests/widget_tests/test_select.py
+++ b/tests/forms_tests/widget_tests/test_select.py
@@ -1,7 +1,7 @@
import copy
import datetime
-from django.forms import Select
+from django.forms import ChoiceField, Form, Select
from django.test import override_settings
from django.utils.safestring import mark_safe
@@ -485,3 +485,20 @@ class SelectTest(WidgetTest):
def test_doesnt_render_required_when_no_choices_are_available(self):
widget = self.widget(choices=[])
self.assertIs(widget.use_required_attribute(initial=None), False)
+
+ def test_fieldset(self):
+ class TestForm(Form):
+ template_name = "forms_tests/use_fieldset.html"
+ field = ChoiceField(widget=self.widget, choices=self.beatles)
+
+ form = TestForm()
+ self.assertIs(self.widget.use_fieldset, False)
+ self.assertHTMLEqual(
+ '<div><label for="id_field">Field:</label>'
+ '<select name="field" id="id_field">'
+ '<option value="J">John</option> '
+ '<option value="P">Paul</option>'
+ '<option value="G">George</option>'
+ '<option value="R">Ringo</option></select></div>',
+ form.render(),
+ )
diff --git a/tests/forms_tests/widget_tests/test_selectdatewidget.py b/tests/forms_tests/widget_tests/test_selectdatewidget.py
index 9192e4c1f1..cc1b6918eb 100644
--- a/tests/forms_tests/widget_tests/test_selectdatewidget.py
+++ b/tests/forms_tests/widget_tests/test_selectdatewidget.py
@@ -707,3 +707,46 @@ class SelectDateWidgetTest(WidgetTest):
"""
),
)
+
+ def test_fieldset(self):
+ class TestForm(Form):
+ template_name = "forms_tests/use_fieldset.html"
+ field = DateField(widget=self.widget)
+
+ form = TestForm()
+ self.assertIs(self.widget.use_fieldset, True)
+ self.assertHTMLEqual(
+ '<div><fieldset><legend for="id_field_month">Field:</legend>'
+ '<select name="field_month" required id="id_field_month">'
+ '<option value="1">January</option><option value="2">February</option>'
+ '<option value="3">March</option><option value="4">April</option>'
+ '<option value="5">May</option><option value="6">June</option>'
+ '<option value="7">July</option><option value="8">August</option>'
+ '<option value="9">September</option><option value="10">October</option>'
+ '<option value="11">November</option><option value="12">December</option>'
+ '</select><select name="field_day" required id="id_field_day">'
+ '<option value="1">1</option><option value="2">2</option>'
+ '<option value="3">3</option><option value="4">4</option>'
+ '<option value="5">5</option><option value="6">6</option>'
+ '<option value="7">7</option><option value="8">8</option>'
+ '<option value="9">9</option><option value="10">10</option>'
+ '<option value="11">11</option><option value="12">12</option>'
+ '<option value="13">13</option><option value="14">14</option>'
+ '<option value="15">15</option><option value="16">16</option>'
+ '<option value="17">17</option><option value="18">18</option>'
+ '<option value="19">19</option><option value="20">20</option>'
+ '<option value="21">21</option><option value="22">22</option>'
+ '<option value="23">23</option><option value="24">24</option>'
+ '<option value="25">25</option><option value="26">26</option>'
+ '<option value="27">27</option><option value="28">28</option>'
+ '<option value="29">29</option><option value="30">30</option>'
+ '<option value="31">31</option></select>'
+ '<select name="field_year" required id="id_field_year">'
+ '<option value="2007">2007</option><option value="2008">2008</option>'
+ '<option value="2009">2009</option><option value="2010">2010</option>'
+ '<option value="2011">2011</option><option value="2012">2012</option>'
+ '<option value="2013">2013</option><option value="2014">2014</option>'
+ '<option value="2015">2015</option><option value="2016">2016</option>'
+ "</select></fieldset></div>",
+ form.render(),
+ )
diff --git a/tests/forms_tests/widget_tests/test_selectmultiple.py b/tests/forms_tests/widget_tests/test_selectmultiple.py
index 99b53521e7..c4fbea4490 100644
--- a/tests/forms_tests/widget_tests/test_selectmultiple.py
+++ b/tests/forms_tests/widget_tests/test_selectmultiple.py
@@ -1,4 +1,4 @@
-from django.forms import SelectMultiple
+from django.forms import ChoiceField, Form, SelectMultiple
from .base import WidgetTest
@@ -190,3 +190,21 @@ class SelectMultipleTest(WidgetTest):
self.assertIs(
widget.value_omitted_from_data({"field": "value"}, {}, "field"), False
)
+
+ def test_fieldset(self):
+ class TestForm(Form):
+ template_name = "forms_tests/use_fieldset.html"
+ field = ChoiceField(
+ widget=self.widget, choices=self.beatles, required=False
+ )
+
+ form = TestForm()
+ self.assertIs(self.widget.use_fieldset, False)
+ self.assertHTMLEqual(
+ '<div><label for="id_field">Field:</label>'
+ '<select multiple name="field" id="id_field">'
+ '<option value="J">John</option> <option value="P">Paul</option>'
+ '<option value="G">George</option><option value="R">Ringo'
+ "</option></select></div>",
+ form.render(),
+ )
diff --git a/tests/forms_tests/widget_tests/test_splitdatetimewidget.py b/tests/forms_tests/widget_tests/test_splitdatetimewidget.py
index ca48505b85..ba221de01b 100644
--- a/tests/forms_tests/widget_tests/test_splitdatetimewidget.py
+++ b/tests/forms_tests/widget_tests/test_splitdatetimewidget.py
@@ -1,6 +1,6 @@
from datetime import date, datetime, time
-from django.forms import SplitDateTimeWidget
+from django.forms import Form, SplitDateTimeField, SplitDateTimeWidget
from .base import WidgetTest
@@ -94,3 +94,26 @@ class SplitDateTimeWidgetTest(WidgetTest):
'<input type="text" name="date_1" value="07:30">'
),
)
+ self.check_html(
+ widget,
+ "date",
+ datetime(2006, 1, 10, 7, 30),
+ html=(
+ '<input type="text" name="date_0" value="10/01/2006">'
+ '<input type="text" name="date_1" value="07:30">'
+ ),
+ )
+
+ def test_fieldset(self):
+ class TestForm(Form):
+ template_name = "forms_tests/use_fieldset.html"
+ field = SplitDateTimeField(widget=self.widget)
+
+ form = TestForm()
+ self.assertIs(self.widget.use_fieldset, True)
+ self.assertHTMLEqual(
+ '<div><fieldset><legend>Field:</legend><input type="text" '
+ 'name="field_0" required id="id_field_0"><input type="text" '
+ 'name="field_1" required id="id_field_1"></fieldset></div>',
+ form.render(),
+ )
diff --git a/tests/forms_tests/widget_tests/test_splithiddendatetimewidget.py b/tests/forms_tests/widget_tests/test_splithiddendatetimewidget.py
index 549f8c7754..93ded6ac6d 100644
--- a/tests/forms_tests/widget_tests/test_splithiddendatetimewidget.py
+++ b/tests/forms_tests/widget_tests/test_splithiddendatetimewidget.py
@@ -1,6 +1,6 @@
from datetime import datetime
-from django.forms import SplitHiddenDateTimeWidget
+from django.forms import Form, SplitDateTimeField, SplitHiddenDateTimeWidget
from django.utils import translation
from .base import WidgetTest
@@ -81,3 +81,35 @@ class SplitHiddenDateTimeWidgetTest(WidgetTest):
time_attrs={"class": "bar"}, attrs={"class": "foo"}
)
self.check_html(widget, "date", datetime(2006, 1, 10, 7, 30), html=html)
+
+ def test_fieldset(self):
+ class TestForm(Form):
+ template_name = "forms_tests/use_fieldset.html"
+ field = SplitDateTimeField(widget=self.widget)
+
+ form = TestForm()
+ self.assertIs(self.widget.use_fieldset, True)
+ self.assertHTMLEqual(
+ '<input type="hidden" name="field_0" id="id_field_0">'
+ '<input type="hidden" name="field_1" id="id_field_1">',
+ form.render(),
+ )
+
+ def test_fieldset_with_unhidden_field(self):
+ class TestForm(Form):
+ template_name = "forms_tests/use_fieldset.html"
+ hidden_field = SplitDateTimeField(widget=self.widget)
+ unhidden_field = SplitDateTimeField()
+
+ form = TestForm()
+ self.assertIs(self.widget.use_fieldset, True)
+ self.assertHTMLEqual(
+ "<div><fieldset><legend>Unhidden field:</legend>"
+ '<input type="text" name="unhidden_field_0" required '
+ 'id="id_unhidden_field_0"><input type="text" '
+ 'name="unhidden_field_1" required id="id_unhidden_field_1">'
+ '</fieldset><input type="hidden" name="hidden_field_0" '
+ 'id="id_hidden_field_0"><input type="hidden" '
+ 'name="hidden_field_1" id="id_hidden_field_1"></div>',
+ form.render(),
+ )
diff --git a/tests/forms_tests/widget_tests/test_textarea.py b/tests/forms_tests/widget_tests/test_textarea.py
index e7695ab018..9994e15fb7 100644
--- a/tests/forms_tests/widget_tests/test_textarea.py
+++ b/tests/forms_tests/widget_tests/test_textarea.py
@@ -1,4 +1,4 @@
-from django.forms import Textarea
+from django.forms import CharField, Form, Textarea
from django.utils.safestring import mark_safe
from .base import WidgetTest
@@ -62,3 +62,17 @@ class TextareaTest(WidgetTest):
"</textarea>"
),
)
+
+ def test_fieldset(self):
+ class TestForm(Form):
+ template_name = "forms_tests/use_fieldset.html"
+ field = CharField(widget=self.widget)
+
+ form = TestForm()
+ self.assertIs(self.widget.use_fieldset, False)
+ self.assertHTMLEqual(
+ '<div><label for="id_field">Field:</label>'
+ '<textarea cols="40" id="id_field" name="field" '
+ 'required rows="10"></textarea></div>',
+ form.render(),
+ )
diff --git a/tests/forms_tests/widget_tests/test_textinput.py b/tests/forms_tests/widget_tests/test_textinput.py
index efd0cfed4b..63b20ef63d 100644
--- a/tests/forms_tests/widget_tests/test_textinput.py
+++ b/tests/forms_tests/widget_tests/test_textinput.py
@@ -1,4 +1,4 @@
-from django.forms import TextInput
+from django.forms import CharField, Form, TextInput
from django.utils.safestring import mark_safe
from .base import WidgetTest
@@ -119,3 +119,16 @@ class TextInputTest(WidgetTest):
self.assertIs(self.widget.use_required_attribute(None), True)
self.assertIs(self.widget.use_required_attribute(""), True)
self.assertIs(self.widget.use_required_attribute("resume.txt"), True)
+
+ def test_fieldset(self):
+ class TestForm(Form):
+ template_name = "forms_tests/use_fieldset.html"
+ field = CharField(widget=self.widget)
+
+ form = TestForm()
+ self.assertIs(self.widget.use_fieldset, False)
+ self.assertHTMLEqual(
+ '<div><label for="id_field">Field:</label>'
+ '<input type="text" name="field" required id="id_field"></div>',
+ form.render(),
+ )
diff --git a/tests/forms_tests/widget_tests/test_timeinput.py b/tests/forms_tests/widget_tests/test_timeinput.py
index 6f56a07f5e..572985d76a 100644
--- a/tests/forms_tests/widget_tests/test_timeinput.py
+++ b/tests/forms_tests/widget_tests/test_timeinput.py
@@ -1,6 +1,6 @@
from datetime import time
-from django.forms import TimeInput
+from django.forms import CharField, Form, TimeInput
from django.utils import translation
from .base import WidgetTest
@@ -67,3 +67,16 @@ class TimeInputTest(WidgetTest):
t,
html='<input type="text" name="time" value="12:51:34">',
)
+
+ def test_fieldset(self):
+ class TestForm(Form):
+ template_name = "forms_tests/use_fieldset.html"
+ field = CharField(widget=self.widget)
+
+ form = TestForm()
+ self.assertIs(self.widget.use_fieldset, False)
+ self.assertHTMLEqual(
+ '<div><label for="id_field">Field:</label>'
+ '<input id="id_field" name="field" required type="text"></div>',
+ form.render(),
+ )