diff options
| author | David Smith <smithdc@gmail.com> | 2024-12-13 08:20:27 +0000 |
|---|---|---|
| committer | Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> | 2024-12-17 12:04:44 +0100 |
| commit | 1e05431881d64e5e009cd9a709225744c05a48f1 (patch) | |
| tree | e27c5e21a7f3198b3b47572a7ba6a92d47e2edc9 | |
| parent | 27375ad50ea3306844aab8122de13e9b3e0d1189 (diff) | |
Refs #32819 -- Added aria-describedby property to BoundField.
| -rw-r--r-- | django/forms/boundfield.py | 28 | ||||
| -rw-r--r-- | django/forms/jinja2/django/forms/field.html | 2 | ||||
| -rw-r--r-- | django/forms/templates/django/forms/field.html | 2 | ||||
| -rw-r--r-- | docs/ref/forms/api.txt | 9 | ||||
| -rw-r--r-- | docs/releases/5.2.txt | 3 | ||||
| -rw-r--r-- | tests/forms_tests/tests/test_forms.py | 28 |
6 files changed, 58 insertions, 14 deletions
diff --git a/django/forms/boundfield.py b/django/forms/boundfield.py index d0d5c13b3d..4d3cd88660 100644 --- a/django/forms/boundfield.py +++ b/django/forms/boundfield.py @@ -289,21 +289,25 @@ class BoundField(RenderableFieldMixin): attrs["disabled"] = True if not widget.is_hidden and self.errors: attrs["aria-invalid"] = "true" - # If a custom aria-describedby attribute is given (either via the attrs - # argument or widget.attrs) and help_text is used, the custom - # aria-described by is preserved so user can set the desired order. - if ( - not attrs.get("aria-describedby") - and not widget.attrs.get("aria-describedby") - and self.field.help_text - and not self.use_fieldset - and self.auto_id - and not self.is_hidden - ): - attrs["aria-describedby"] = f"{self.auto_id}_helptext" + # Preserve aria-describedby provided by the attrs argument so user + # can set the desired order. + if not attrs.get("aria-describedby") and not self.use_fieldset: + if aria_describedby := self.aria_describedby: + attrs["aria-describedby"] = aria_describedby return attrs @property + def aria_describedby(self): + # Preserve aria-describedby set on the widget. + if self.field.widget.attrs.get("aria-describedby"): + return None + aria_describedby = [] + if self.auto_id and not self.is_hidden: + if self.help_text: + aria_describedby.append(f"{self.auto_id}_helptext") + return " ".join(aria_describedby) + + @property def widget_type(self): return re.sub( r"widget$|input$", "", self.field.widget.__class__.__name__.lower() diff --git a/django/forms/jinja2/django/forms/field.html b/django/forms/jinja2/django/forms/field.html index 642d45ddc7..faeed0a45b 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{% if field.help_text and field.auto_id and "aria-describedby" not in field.field.widget.attrs %} aria-describedby="{{ field.auto_id }}_helptext"{% endif %}> + <fieldset{% if field.aria_describedby %} aria-describedby="{{ field.aria_describedby }}"{% 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 a4548fe54f..791bc49754 100644 --- a/django/forms/templates/django/forms/field.html +++ b/django/forms/templates/django/forms/field.html @@ -1,5 +1,5 @@ {% if field.use_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 %}> + <fieldset{% if field.aria_describedby %} aria-describedby="{{ field.aria_describedby }}"{% endif %}> {% if field.label %}{{ field.legend_tag }}{% endif %} {% else %} {% if field.label %}{{ field.label_tag }}{% endif %} diff --git a/docs/ref/forms/api.txt b/docs/ref/forms/api.txt index 4875a1ab72..494f3f244e 100644 --- a/docs/ref/forms/api.txt +++ b/docs/ref/forms/api.txt @@ -1161,6 +1161,15 @@ The field-specific output honors the form object's ``auto_id`` setting: Attributes of ``BoundField`` ---------------------------- +.. attribute:: BoundField.aria_describedby + + .. versionadded:: 5.2 + + Returns an ``aria-describedby`` reference to associate a field with its + help text. Returns ``None`` if ``aria-describedby`` is set in + :attr:`Widget.attrs` to preserve the user defined attribute when rendering + the form. + .. attribute:: BoundField.auto_id The HTML ID attribute for this ``BoundField``. Returns an empty string diff --git a/docs/releases/5.2.txt b/docs/releases/5.2.txt index 918bd6a700..aaf47ff8e8 100644 --- a/docs/releases/5.2.txt +++ b/docs/releases/5.2.txt @@ -256,6 +256,9 @@ Forms HTML ``id`` attribute to be added in the error template. See :attr:`.ErrorList.field_id` for details. +* An :attr:`~django.forms.BoundField.aria_describedby` property is added to + ``BoundField`` to ease use of this HTML attribute in templates. + Generic Views ~~~~~~~~~~~~~ diff --git a/tests/forms_tests/tests/test_forms.py b/tests/forms_tests/tests/test_forms.py index b49f0a8a69..31ab0a15aa 100644 --- a/tests/forms_tests/tests/test_forms.py +++ b/tests/forms_tests/tests/test_forms.py @@ -4801,6 +4801,34 @@ Options: <select multiple name="options" aria-invalid="true" required> with self.assertRaises(KeyError): f["name"] + def test_aria_describedby_property(self): + class TestForm(Form): + name = CharField(help_text="Some help text") + + form = TestForm({"name": "MyName"}) + self.assertEqual(form["name"].aria_describedby, "id_name_helptext") + + form = TestForm(auto_id=None) + self.assertEqual(form["name"].aria_describedby, "") + + class TestFormHidden(Form): + name = CharField(help_text="Some help text", widget=HiddenInput) + + form = TestFormHidden() + self.assertEqual(form["name"].aria_describedby, "") + + class TestFormWithAttrs(Form): + name = CharField(widget=TextInput(attrs={"aria-describedby": "my-id"})) + + form = TestFormWithAttrs({"name": "MyName"}) + self.assertIs(form["name"].aria_describedby, None) + + class TestFormWithoutHelpText(Form): + name = CharField() + + form = TestFormWithoutHelpText() + self.assertEqual(form["name"].aria_describedby, "") + @jinja2_tests class Jinja2FormsTestCase(FormsTestCase): |
