1
0
mirror of https://github.com/django/django.git synced 2024-12-23 01:25:58 +00:00

Fixed #34643 -- Moved admin form labels above fields.

This allows screen magnifier users to see the label and field
together, improving accessibility.
This commit is contained in:
Hrushikesh 2024-09-26 17:14:33 +02:00 committed by Sarah Boyce
parent 5ed72087c4
commit 6fbdfe14f6
9 changed files with 83 additions and 61 deletions

View File

@ -482,7 +482,7 @@ input, textarea, select, .form-row p, form .button {
font-size: 0.8125rem;
}
.form-row div.help {
padding: 2px 3px;
padding: 2px 3px 2px 0;
}
textarea {

View File

@ -24,10 +24,13 @@ form .form-row p {
.flex-container {
display: flex;
flex-flow: column wrap;
align-items: flex-start;
}
.form-multiline {
flex-wrap: wrap;
.flex-container.checkbox-row,
.flex-container.form-multiline {
flex-flow: row wrap;
}
.form-multiline > div {
@ -94,11 +97,15 @@ fieldset .inline-heading,
.aligned label {
display: block;
padding: 4px 10px 0 0;
min-width: 160px;
width: 160px;
margin-bottom: 6px;
word-wrap: break-word;
}
.aligned label:has(+.help),
.aligned label:has(+.errorlist) {
margin-bottom: 0;
}
.aligned label:not(.vCheckboxLabel):after {
content: '';
display: inline-block;
@ -127,11 +134,6 @@ fieldset .inline-heading,
width: 350px;
}
form .aligned ul {
margin-left: 160px;
padding-left: 10px;
}
form .aligned div.radiolist {
display: inline-block;
margin: 0;
@ -140,9 +142,12 @@ form .aligned div.radiolist {
form .aligned p.help,
form .aligned div.help {
margin-top: 0;
margin-left: 160px;
padding-left: 10px;
margin-bottom: 8px;
}
form .aligned .errors p.help,
form .aligned .errors div.help {
margin-bottom: 2px;
}
form .aligned p.date div.help.timezonewarning,
@ -159,16 +164,6 @@ form .aligned div.help:last-child {
padding-bottom: 0;
}
form .aligned input + p.help,
form .aligned textarea + p.help,
form .aligned select + p.help,
form .aligned input + div.help,
form .aligned textarea + div.help,
form .aligned select + div.help {
margin-left: 160px;
padding-left: 10px;
}
form .aligned ul li {
list-style: none;
}
@ -195,16 +190,14 @@ fieldset .fieldBox {
margin-right: 20px;
}
/* WIDE FIELDSETS */
.wide label {
width: 200px;
.form-multiline .fieldBox {
margin-right: 40px;
}
form .wide p.help,
form .wide ul.errorlist,
form .wide div.help {
padding-left: 50px;
/* WIDE FIELDSETS */
.wide .fieldBox {
margin-right: 80px;
}
form div.help ul {

View File

@ -545,11 +545,7 @@ input[type="submit"], button {
}
.flex-container {
flex-flow: column;
}
.flex-container.checkbox-row {
flex-flow: row;
align-items: stretch;
}
textarea {
@ -592,6 +588,10 @@ input[type="submit"], button {
padding: 1px 0 0 5px;
}
.aligned .checkbox-row + div.help {
margin-top: 5px;
}
.aligned label + p,
.aligned label + div.help,
.aligned label + div.readonly {
@ -619,7 +619,6 @@ input[type="submit"], button {
form .aligned .form-row div.help {
width: 100%;
margin: 5px 0 0;
padding: 0;
}

View File

@ -137,8 +137,7 @@ thead th.sorted .text {
}
form .aligned ul {
margin-right: 163px;
padding-right: 10px;
margin-right: 0;
margin-left: 0;
padding-left: 0;
}
@ -149,13 +148,6 @@ form ul.inline li {
padding-left: 7px;
}
form .aligned p.help,
form .aligned div.help {
margin-left: 0;
margin-right: 160px;
padding-right: 10px;
}
form div.help ul,
form .aligned .checkbox-row + .help,
form .aligned p.date div.help.timezonewarning,
@ -165,13 +157,6 @@ form .aligned p.time div.help.timezonewarning {
padding-right: 0;
}
form .wide p.help,
form .wide ul.errorlist,
form .wide div.help {
padding-left: 0;
padding-right: 50px;
}
.submit-row {
text-align: right;
}
@ -191,6 +176,16 @@ fieldset .fieldBox {
padding: 10px 12px;
}
.form-multiline .fieldBox {
margin-left: 40px;
margin-right: 0;
}
.wide .fieldBox {
margin-left: 80px;
margin-right: 0;
}
/* WIDGETS */
.calendarnav-previous {

View File

@ -574,6 +574,7 @@ ul.timelist, .timelist li {
flex-grow: 1;
flex-wrap: wrap;
margin-bottom: 5px;
width: 100%;
}
.related-widget-wrapper-link {

View File

@ -10,15 +10,22 @@
{% endif %}
{% for line in fieldset %}
<div class="form-row{% if line.fields|length == 1 and line.errors %} errors{% endif %}{% if not line.has_visible_field %} hidden{% endif %}{% for field in line %}{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% endfor %}">
{% if line.fields|length == 1 %}{{ line.errors }}{% else %}<div class="flex-container form-multiline">{% endif %}
{% if not line.fields|length == 1 %}<div class="flex-container form-multiline">{% endif %}
{% for field in line %}
<div>
{% if not line.fields|length == 1 and not field.is_readonly %}{{ field.errors }}{% endif %}
<div class="flex-container{% if not line.fields|length == 1 %} fieldBox{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% if not field.is_readonly and field.errors %} errors{% endif %}{% if field.field.is_hidden %} hidden{% endif %}{% endif %}{% if field.is_checkbox %} checkbox-row{% endif %}">
{% if field.is_checkbox %}
{% if line.fields|length == 1 %}{{ line.errors }}{% endif %}
{{ field.field }}{{ field.label_tag }}
{% else %}
{{ field.label_tag }}
{% if field.field.help_text %}
<div class="help"{% if field.field.id_for_label %} id="{{ field.field.id_for_label }}_helptext"{% endif %}>
<div>{{ field.field.help_text|safe }}</div>
</div>
{% endif %}
{% if line.fields|length == 1 %}{{ line.errors }}{% endif %}
{% if field.is_readonly %}
<div class="readonly">{{ field.contents }}</div>
{% else %}
@ -26,7 +33,7 @@
{% endif %}
{% endif %}
</div>
{% if field.field.help_text %}
{% if field.is_checkbox and 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>{{ field.field.help_text|safe }}</div>
</div>

View File

@ -41,6 +41,17 @@ Minor features
:ref:`extrabody <extrabody>` for adding custom code before the closing
``</body>`` tag.
* In order to improve accessibility:
* Form fields in the change form are now shown below their respective labels
instead of next to them.
* Help text in the change form is now shown after the field label and before
the field input.
* Validation errors in the change form are now shown after the help text and
before the field input.
:mod:`django.contrib.admindocs`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -384,15 +384,15 @@ class TestInline(TestDataMixin, TestCase):
self.assertInHTML(
'<div class="flex-container fieldBox field-position hidden">'
'<label class="inline">Position:</label>'
'<div class="readonly">0</div></div>'
'<div class="help hidden"><div>Position help_text.</div></div>',
'<div class="help"><div>Position help_text.</div></div>'
'<div class="readonly">0</div></div>',
response.rendered_content,
)
self.assertInHTML(
'<div class="flex-container fieldBox field-position hidden">'
'<label class="inline">Position:</label>'
'<div class="readonly">1</div></div>'
'<div class="help hidden"><div>Position help_text.</div></div>',
'<div class="help"><div>Position help_text.</div></div>'
'<div class="readonly">1</div></div>',
response.rendered_content,
)
@ -414,16 +414,16 @@ class TestInline(TestDataMixin, TestCase):
self.assertInHTML(
'<div class="form-row hidden field-position">'
'<div><div class="flex-container"><label>Position:</label>'
'<div class="help"><div>Position help_text.</div></div>'
'<div class="readonly">0</div></div>'
'<div class="help hidden"><div>Position help_text.</div></div>'
"</div></div>",
response.rendered_content,
)
self.assertInHTML(
'<div class="form-row hidden field-position">'
'<div><div class="flex-container"><label>Position:</label>'
'<div class="help"><div>Position help_text.</div></div>'
'<div class="readonly">1</div></div>'
'<div class="help hidden"><div>Position help_text.</div></div>'
"</div></div>",
response.rendered_content,
)

View File

@ -6206,6 +6206,22 @@ class SeleniumTests(AdminSeleniumTestCase):
)
self.take_screenshot("focus-multi-widget")
@screenshot_cases(["desktop_size", "mobile_size", "rtl", "dark", "high_contrast"])
def test_error_message_position(self):
"""Error messages are shown above the field and below the label"""
from selenium.webdriver.common.by import By
url = reverse("admin:admin_views_article_add")
full_url = self.live_server_url + url
self.admin_login(
username="super", password="secret", login_url=reverse("admin:index")
)
self.selenium.get(full_url)
# Submit an empty form to trigger validation errors
self.selenium.find_element(By.CSS_SELECTOR, 'input[type="submit"]').click()
self.wait_page_ready()
self.take_screenshot("error-message-position")
def test_cancel_delete_confirmation(self):
"Cancelling the deletion of an object takes the user back one page."
from selenium.webdriver.common.by import By