1
0
mirror of https://github.com/django/django.git synced 2024-12-22 09:05:43 +00:00

Refs #32819 -- Added aria-describedby property to BoundField.

This commit is contained in:
David Smith 2024-12-13 08:20:27 +00:00 committed by Sarah Boyce
parent 27375ad50e
commit 1e05431881
6 changed files with 58 additions and 14 deletions

View File

@ -289,20 +289,24 @@ class BoundField(RenderableFieldMixin):
attrs["disabled"] = True attrs["disabled"] = True
if not widget.is_hidden and self.errors: if not widget.is_hidden and self.errors:
attrs["aria-invalid"] = "true" attrs["aria-invalid"] = "true"
# If a custom aria-describedby attribute is given (either via the attrs # Preserve aria-describedby provided by the attrs argument so user
# argument or widget.attrs) and help_text is used, the custom # can set the desired order.
# aria-described by is preserved so user can set the desired order. if not attrs.get("aria-describedby") and not self.use_fieldset:
if ( if aria_describedby := self.aria_describedby:
not attrs.get("aria-describedby") attrs["aria-describedby"] = 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"
return attrs 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 @property
def widget_type(self): def widget_type(self):
return re.sub( return re.sub(

View File

@ -1,5 +1,5 @@
{% if field.use_fieldset %} {% 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 %} {% if field.label %}{{ field.legend_tag() }}{% endif %}
{% else %} {% else %}
{% if field.label %}{{ field.label_tag() }}{% endif %} {% if field.label %}{{ field.label_tag() }}{% endif %}

View File

@ -1,5 +1,5 @@
{% if field.use_fieldset %} {% 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 %} {% if field.label %}{{ field.legend_tag }}{% endif %}
{% else %} {% else %}
{% if field.label %}{{ field.label_tag }}{% endif %} {% if field.label %}{{ field.label_tag }}{% endif %}

View File

@ -1161,6 +1161,15 @@ The field-specific output honors the form object's ``auto_id`` setting:
Attributes of ``BoundField`` 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 .. attribute:: BoundField.auto_id
The HTML ID attribute for this ``BoundField``. Returns an empty string The HTML ID attribute for this ``BoundField``. Returns an empty string

View File

@ -256,6 +256,9 @@ Forms
HTML ``id`` attribute to be added in the error template. See HTML ``id`` attribute to be added in the error template. See
:attr:`.ErrorList.field_id` for details. :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 Generic Views
~~~~~~~~~~~~~ ~~~~~~~~~~~~~

View File

@ -4801,6 +4801,34 @@ Options: <select multiple name="options" aria-invalid="true" required>
with self.assertRaises(KeyError): with self.assertRaises(KeyError):
f["name"] 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 @jinja2_tests
class Jinja2FormsTestCase(FormsTestCase): class Jinja2FormsTestCase(FormsTestCase):