diff options
| author | David <smithdc@gmail.com> | 2022-01-13 23:08:38 +0000 |
|---|---|---|
| committer | Carlton Gibson <carlton@noumenal.es> | 2022-03-30 16:28:14 +0200 |
| commit | c8459708a7e0a2474255b77d0f104a7f16e8b32c (patch) | |
| tree | add60b1d21cfaa37e51c116348054e71c91f73a1 | |
| parent | 04ad0f26ba4b8c79dc311e1789457e0c4d1b8832 (diff) | |
Refs #32339 -- Added use_fieldset to Widget.
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(), + ) |
