Преглед изворни кода

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>.
David Smith пре 1 година
родитељ
комит
eec7e9ba89

+ 1 - 1
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 %}

+ 2 - 3
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 %}

+ 8 - 3
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``
 ------------------
 

+ 3 - 1
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
 ~~~~~~~~~~~~~

+ 77 - 0
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.")