﻿id	summary	reporter	owner	description	type	status	component	version	severity	resolution	keywords	cc	stage	has_patch	needs_docs	needs_tests	needs_better_patch	easy	ui_ux
37298	Admin radio_fields have an invalid aria-describedby attribute.	David Smith		"#35892 added support for Widget.use_fieldset in admin forms. Refs 4187da258fe212d494cb578a0bc2b52c4979ab95

An incorrect aria-describedby attribute is generated for `radio_fields` with `help_text` and no reference is available for errors. 

Given this form:

{{{#!python
class Colour(models.Model):
    name = models.CharField(max_length=100, help_text='Enter your name')

    COLOUR_CHOICES = [
        ('red', 'Red'),
        ('green', 'Green'),
        ('blue', 'Blue'),]

    favourite_colour = models.CharField(
        max_length=10,
        choices=COLOUR_CHOICES,
        help_text='Colour of the favourite colour',
    )
}}}

... and model admin:

{{{#!python
class ColourAdmin(admin.ModelAdmin):
    radio_fields = {""favourite_colour"": admin.VERTICAL}
    model = Colour
}}}

Adding a new Colour record in the admin with no inputs gives the following HTML for the `favourite_colour` field. 

Notice how the aria-describedby in the `<fieldset>` is `_helptext`. This id does not exist, and also there is no reference to ""id_favourite_colour_error"" to reference the error list.

{{{
<fieldset aria-describedby=""_helptext"">
    <legend class=""required"">Favourite colour:</legend>
    <div class=""flex-container errors"">
        <div class=""help"">
            <div>Colour of the favourite colour</div>
        </div>

        <ul class=""errorlist"" id=""id_favourite_colour_error"">
            <li>This field is required.</li>
        </ul>

        <div id=""id_favourite_colour"" class=""radiolist"">
            <div>
                <label for=""id_favourite_colour_0"">
                    <input type=""radio"" name=""favourite_colour"" value=""red"" class=""radiolist"" required="""" aria-invalid=""true"" id=""id_favourite_colour_0"" /> Red
                </label>
            </div>
            <div>
                <label for=""id_favourite_colour_1"">
                    <input type=""radio"" name=""favourite_colour"" value=""green"" class=""radiolist"" required="""" aria-invalid=""true"" id=""id_favourite_colour_1"" /> Green
                </label>
            </div>
            <div>
                <label for=""id_favourite_colour_2"">
                    <input type=""radio"" name=""favourite_colour"" value=""blue"" class=""radiolist"" required="""" aria-invalid=""true"" id=""id_favourite_colour_2"" /> Blue
                </label>
            </div>
        </div>
    </div>
</fieldset>
}}}

Contrast that to the name field where both the help_text and errors have a valid aria-describedby references from the `<input>`.

Using aria-describedby from the field's boundfield (field.field in this case) is where I'd be looking to fix the issue.

{{{#!diff

diff --git a/django/contrib/admin/templates/admin/includes/fieldset.html b/django/contrib/admin/templates/admin/includes/fieldset.html
index 70c68655c5..4ef3d01589 100644
--- a/django/contrib/admin/templates/admin/includes/fieldset.html
+++ b/django/contrib/admin/templates/admin/includes/fieldset.html
@@ -10,7 +10,7 @@
     {% for line in fieldset %}
         <div class=""form-row{% if not line.fields|length == 1 %} flex-container form-multiline{% endif %}{% if not line.has_visible_field %} hidden{% endif %}{% for field in line %}{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% endfor %}"">
             {% for field in line %}
-                {% if field.is_fieldset %}<fieldset{% if field.field.help_text %} aria-describedby=""{{ field.field.id_for_label }}_helptext""{% endif %}>{{ field.label_tag }}{% endif %}
+                {% if field.is_fieldset %}<fieldset{% if field.field.aria_describedby %} aria-describedby=""{{ field.field.aria_describedby }}""{% endif %}>{{ field.label_tag }}{% endif %}
                 <div class=""flex-container{% if not field.is_readonly and field.errors or line.fields|length == 1 and line.errors %} errors{% endif %}{% if not line.fields|length == 1 %} fieldBox{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% if field.field.is_hidden %} hidden{% endif %}{% endif %}{% if field.is_checkbox %} checkbox-row{% endif %}"">
                     {% if field.is_checkbox %}
                         <div class=""checkbox"">
@@ -21,7 +21,7 @@
                         {% if not field.is_fieldset %}{{ field.label_tag }}{% endif %}
                     {% endif %}
                     {% if field.field.help_text %}
-                        <div class=""help{% if field.field.is_hidden %} hidden{% endif %}""{% if field.field.id_for_label %} id=""{{ field.field.id_for_label }}_helptext""{% endif %}>
+                        <div class=""help{% if field.field.is_hidden %} hidden{% endif %}""{% if field.field.auto_id %} id=""{{ field.field.auto_id }}_helptext""{% endif %}>
                             <div>{{ field.field.help_text|safe }}</div>
                         </div>
                     {% endif %}
}}}


Then for tests I'd suggest looking at the test that was added for admin fieldsets. Additional tests can be added to assert the correct aria-describedby attribute for both help_text and errors is present.  
https://github.com/django/django/blob/0b40210e4808937a7c0922e8b7502bff4752faa3/tests/admin_views/tests.py#L7365

"	Bug	new	contrib.admin	6.1	Normal				Unreviewed	0	0	0	0	0	0
