diff options
| author | David Smith <smithdc@gmail.com> | 2023-11-12 17:24:16 +0000 |
|---|---|---|
| committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2023-11-17 08:12:36 +0100 |
| commit | eec7e9ba894a7815d289ba7446eeead488fe0e33 (patch) | |
| tree | 8eed8c3d9182470731667c40758aced7f719a119 | |
| parent | 557fa51837a57534f8ca486133a412547a98a37e (diff) | |
Refs #32819 -- Established relationship between form fieldsets and their help text.
This adds aria-describedby for widgets rendered in a fieldset such as
radios. aria-describedby for these widgets is added to the <fieldset>
element rather than each <input>.
| -rw-r--r-- | django/forms/jinja2/django/forms/field.html | 2 | ||||
| -rw-r--r-- | django/forms/templates/django/forms/field.html | 5 | ||||
| -rw-r--r-- | docs/ref/forms/fields.txt | 11 | ||||
| -rw-r--r-- | docs/releases/5.1.txt | 4 | ||||
| -rw-r--r-- | tests/forms_tests/tests/test_forms.py | 77 |
5 files changed, 91 insertions, 8 deletions
diff --git a/django/forms/jinja2/django/forms/field.html b/django/forms/jinja2/django/forms/field.html index 21035b72e4..642d45ddc7 100644 --- a/django/forms/jinja2/django/forms/field.html +++ b/django/forms/jinja2/django/forms/field.html @@ -1,5 +1,5 @@ {% if field.use_fieldset %} - <fieldset> + <fieldset{% if field.help_text and field.auto_id and "aria-describedby" not in field.field.widget.attrs %} aria-describedby="{{ field.auto_id }}_helptext"{% endif %}> {% if field.label %}{{ field.legend_tag() }}{% endif %} {% else %} {% if field.label %}{{ field.label_tag() }}{% endif %} diff --git a/django/forms/templates/django/forms/field.html b/django/forms/templates/django/forms/field.html index f8b0cdedd5..a4548fe54f 100644 --- a/django/forms/templates/django/forms/field.html +++ b/django/forms/templates/django/forms/field.html @@ -1,10 +1,9 @@ {% if field.use_fieldset %} - <fieldset> + <fieldset{% if field.help_text and field.auto_id and "aria-describedby" not in field.field.widget.attrs %} aria-describedby="{{ field.auto_id }}_helptext"{% endif %}> {% if field.label %}{{ field.legend_tag }}{% endif %} {% else %} {% if field.label %}{{ field.label_tag }}{% endif %} {% endif %} {% if field.help_text %}<div class="helptext"{% if field.auto_id %} id="{{ field.auto_id }}_helptext"{% endif %}>{{ field.help_text|safe }}</div>{% endif %} {{ field.errors }} -{{ field }} -{% if field.use_fieldset %}</fieldset>{% endif %} +{{ field }}{% if field.use_fieldset %}</fieldset>{% endif %} diff --git a/docs/ref/forms/fields.txt b/docs/ref/forms/fields.txt index 9dff2d4007..ed685cfff6 100644 --- a/docs/ref/forms/fields.txt +++ b/docs/ref/forms/fields.txt @@ -283,9 +283,10 @@ fields. We've specified ``auto_id=False`` to simplify the output: <div>Sender:<div class="helptext">A valid email address, please.</div><input type="email" name="sender" required></div> <div>Cc myself:<input type="checkbox" name="cc_myself"></div> -When a field has help text and the widget is not rendered in a ``<fieldset>``, -``aria-describedby`` is added to the ``<input>`` to associate it to the -help text: +When a field has help text it is associated with its input using the +``aria-describedby`` HTML attribute. If the widget is rendered in a +``<fieldset>`` then ``aria-describedby`` is added to this element, otherwise it +is added to the widget's ``<input>``: .. code-block:: pycon @@ -325,6 +326,10 @@ inside ``aria-describedby``: ``aria-describedby`` was added to associate ``help_text`` with its input. +.. versionchanged:: 5.1 + + ``aria-describedby`` support was added for ``<fieldset>``. + ``error_messages`` ------------------ diff --git a/docs/releases/5.1.txt b/docs/releases/5.1.txt index d6c07d9c22..b90808be3c 100644 --- a/docs/releases/5.1.txt +++ b/docs/releases/5.1.txt @@ -138,7 +138,9 @@ File Uploads Forms ~~~~~ -* ... +* In order to improve accessibility and enable screen readers to associate + fieldsets with their help text, the form fieldset now includes the + ``aria-describedby`` HTML attribute. Generic Views ~~~~~~~~~~~~~ diff --git a/tests/forms_tests/tests/test_forms.py b/tests/forms_tests/tests/test_forms.py index 7ea88f0cc8..19ca978c45 100644 --- a/tests/forms_tests/tests/test_forms.py +++ b/tests/forms_tests/tests/test_forms.py @@ -3114,6 +3114,83 @@ Options: <select multiple name="options" aria-invalid="true" required> 'required aria-describedby="id_username_helptext"></div>', ) + def test_fieldset_aria_describedby(self): + class FieldsetForm(Form): + checkbox = MultipleChoiceField( + choices=[("a", "A"), ("b", "B")], + widget=CheckboxSelectMultiple, + help_text="Checkbox help text", + ) + radio = MultipleChoiceField( + choices=[("a", "A"), ("b", "B")], + widget=RadioSelect, + help_text="Radio help text", + ) + datetime = SplitDateTimeField(help_text="Enter Date and Time") + + f = FieldsetForm() + self.assertHTMLEqual( + str(f), + '<div><fieldset aria-describedby="id_checkbox_helptext">' + "<legend>Checkbox:</legend>" + '<div class="helptext" id="id_checkbox_helptext">Checkbox help text</div>' + '<div id="id_checkbox"><div>' + '<label for="id_checkbox_0"><input type="checkbox" name="checkbox" ' + 'value="a" id="id_checkbox_0" /> A</label>' + "</div><div>" + '<label for="id_checkbox_1"><input type="checkbox" name="checkbox" ' + 'value="b" id="id_checkbox_1" /> B</label>' + "</div></div></fieldset></div>" + '<div><fieldset aria-describedby="id_radio_helptext">' + "<legend>Radio:</legend>" + '<div class="helptext" id="id_radio_helptext">Radio help text</div>' + '<div id="id_radio"><div>' + '<label for="id_radio_0"><input type="radio" name="radio" value="a" ' + 'required id="id_radio_0" />A</label>' + "</div><div>" + '<label for="id_radio_1"><input type="radio" name="radio" value="b" ' + 'required id="id_radio_1" /> B</label>' + "</div></div></fieldset></div>" + '<div><fieldset aria-describedby="id_datetime_helptext">' + "<legend>Datetime:</legend>" + '<div class="helptext" id="id_datetime_helptext">Enter Date and Time</div>' + '<input type="text" name="datetime_0" required id="id_datetime_0" />' + '<input type="text" name="datetime_1" required id="id_datetime_1" />' + "</fieldset></div>", + ) + f = FieldsetForm(auto_id=False) + # aria-describedby is not included. + self.assertIn("<fieldset>", str(f)) + self.assertIn('<div class="helptext">', str(f)) + f = FieldsetForm(auto_id="custom_%s") + # aria-describedby uses custom auto_id. + self.assertIn('fieldset aria-describedby="custom_checkbox_helptext"', str(f)) + self.assertIn('<div class="helptext" id="custom_checkbox_helptext">', str(f)) + + def test_fieldset_custom_aria_describedby(self): + # aria-describedby set on widget results in aria-describedby being + # added to widget and not the <fieldset>. + class FieldsetForm(Form): + checkbox = MultipleChoiceField( + choices=[("a", "A"), ("b", "B")], + widget=CheckboxSelectMultiple(attrs={"aria-describedby": "custom-id"}), + help_text="Checkbox help text", + ) + + f = FieldsetForm() + self.assertHTMLEqual( + str(f), + "<div><fieldset><legend>Checkbox:</legend>" + '<div class="helptext" id="id_checkbox_helptext">Checkbox help text</div>' + '<div id="id_checkbox"><div>' + '<label for="id_checkbox_0"><input type="checkbox" name="checkbox" ' + 'value="a" aria-describedby="custom-id" id="id_checkbox_0" />A</label>' + "</div><div>" + '<label for="id_checkbox_1"><input type="checkbox" name="checkbox" ' + 'value="b" aria-describedby="custom-id" id="id_checkbox_1" />B</label>' + "</div></div></fieldset></div>", + ) + def test_as_widget_custom_aria_describedby(self): class FoodForm(Form): intl_name = CharField(help_text="The food's international name.") |
