#37298 new Bug

Admin radio_fields have an invalid aria-describedby attribute.

Reported by: David Smith Owned by:
Component: contrib.admin Version: 6.1
Severity: Normal Keywords:
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

#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:

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:

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.

  • django/contrib/admin/templates/admin/includes/fieldset.html

    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 b  
    1010    {% for line in fieldset %}
    1111        <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 %}">
    1212            {% for field in line %}
    13                 {% if field.is_fieldset %}<fieldset{% if field.field.help_text %} aria-describedby="{{ field.field.id_for_label }}_helptext"{% endif %}>{{ field.label_tag }}{% endif %}
     13                {% if field.is_fieldset %}<fieldset{% if field.field.aria_describedby %} aria-describedby="{{ field.field.aria_describedby }}"{% endif %}>{{ field.label_tag }}{% endif %}
    1414                <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 %}">
    1515                    {% if field.is_checkbox %}
    1616                        <div class="checkbox">
     
    2121                        {% if not field.is_fieldset %}{{ field.label_tag }}{% endif %}
    2222                    {% endif %}
    2323                    {% if field.field.help_text %}
    24                         <div class="help{% if field.field.is_hidden %} hidden{% endif %}"{% if field.field.id_for_label %} id="{{ field.field.id_for_label }}_helptext"{% endif %}>
     24                        <div class="help{% if field.field.is_hidden %} hidden{% endif %}"{% if field.field.auto_id %} id="{{ field.field.auto_id }}_helptext"{% endif %}>
    2525                            <div>{{ field.field.help_text|safe }}</div>
    2626                        </div>
    2727                    {% 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

Change History (0)

Note: See TracTickets for help on using tickets.
Back to Top