diff options
| author | michalpokusa <72110769+michalpokusa@users.noreply.github.com> | 2025-06-05 13:04:00 +0200 |
|---|---|---|
| committer | Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> | 2025-06-18 08:37:44 +0200 |
| commit | 1520870c4a6c47df3ed2f597785a8d35a1710bc4 (patch) | |
| tree | 3ecb5b0afbcd312239e8f9438edea1cdadca22d9 | |
| parent | 74b31cd26b9ad4ad85f131850a734f02aae988bb (diff) | |
Fixed #36437 -- Improved accessibility of messages in admin.
12 files changed, 104 insertions, 29 deletions
diff --git a/django/contrib/admin/static/admin/css/base.css b/django/contrib/admin/static/admin/css/base.css index ae0a55c496..62fe9868b7 100644 --- a/django/contrib/admin/static/admin/css/base.css +++ b/django/contrib/admin/static/admin/css/base.css @@ -35,8 +35,11 @@ html[data-theme="light"], --error-fg: #ba2121; --message-success-bg: #dfd; + --message-success-icon: url(../img/icon-yes.svg); --message-warning-bg: #ffc; + --message-warning-icon: url(../img/icon-alert.svg); --message-error-bg: #ffefef; + --message-error-icon: url(../img/icon-no.svg); --darkened-bg: #f8f8f8; /* A bit darker than --body-bg */ --selected-bg: #e4e4e4; /* E.g. selected table cells */ @@ -637,20 +640,29 @@ ul.messagelist li { font-size: 0.8125rem; padding: 10px 10px 10px 65px; margin: 0 0 10px 0; - background: var(--message-success-bg) url(../img/icon-yes.svg) 40px 12px no-repeat; - background-size: 16px auto; color: var(--body-fg); word-break: break-word; + background-color: var(--message-success-bg); + background-image: var(--message-success-icon); + background-position: 40px 12px; + background-repeat: no-repeat; + background-size: 16px auto; } ul.messagelist li.warning { - background: var(--message-warning-bg) url(../img/icon-alert.svg) 40px 14px no-repeat; - background-size: 14px auto; + background-color: var(--message-warning-bg); + background-image: var(--message-warning-icon); } ul.messagelist li.error { - background: var(--message-error-bg) url(../img/icon-no.svg) 40px 12px no-repeat; - background-size: 16px auto; + background-color: var(--message-error-bg); + background-image: var(--message-error-icon); +} + +@media (forced-colors: active) { + ul.messagelist li { + border: 1px solid; + } } .errornote { diff --git a/django/contrib/admin/static/admin/css/dark_mode.css b/django/contrib/admin/static/admin/css/dark_mode.css index 65b58d035f..50f11affc0 100644 --- a/django/contrib/admin/static/admin/css/dark_mode.css +++ b/django/contrib/admin/static/admin/css/dark_mode.css @@ -21,8 +21,11 @@ --error-fg: #e35f5f; --message-success-bg: #006b1b; + --message-success-icon: url(../img/icon-yes-dark.svg); --message-warning-bg: #583305; + --message-warning-icon: url(../img/icon-alert-dark.svg); --message-error-bg: #570808; + --message-error-icon: url(../img/icon-no-dark.svg); --darkened-bg: #212121; --selected-bg: #1b1b1b; @@ -58,8 +61,11 @@ html[data-theme="dark"] { --error-fg: #e35f5f; --message-success-bg: #006b1b; + --message-success-icon: url(../img/icon-yes-dark.svg); --message-warning-bg: #583305; + --message-warning-icon: url(../img/icon-alert-dark.svg); --message-error-bg: #570808; + --message-error-icon: url(../img/icon-no-dark.svg); --darkened-bg: #212121; --selected-bg: #1b1b1b; diff --git a/django/contrib/admin/static/admin/css/responsive.css b/django/contrib/admin/static/admin/css/responsive.css index f0fcade41c..7a2c321ceb 100644 --- a/django/contrib/admin/static/admin/css/responsive.css +++ b/django/contrib/admin/static/admin/css/responsive.css @@ -337,16 +337,8 @@ input[type="submit"], button { /* Messages */ ul.messagelist li { - padding-left: 55px; - background-position: 30px 12px; - } - - ul.messagelist li.error { - background-position: 30px 12px; - } - - ul.messagelist li.warning { - background-position: 30px 14px; + padding: 10px 10px 10px 55px; + background-position-x: 30px; } /* Login */ @@ -739,16 +731,8 @@ input[type="submit"], button { /* Messages */ ul.messagelist li { - padding-left: 40px; - background-position: 15px 12px; - } - - ul.messagelist li.error { - background-position: 15px 12px; - } - - ul.messagelist li.warning { - background-position: 15px 14px; + padding: 10px 10px 10px 40px; + background-position-x: 15px; } /* Paginator */ diff --git a/django/contrib/admin/static/admin/css/responsive_rtl.css b/django/contrib/admin/static/admin/css/responsive_rtl.css index 5e8f5c5943..acad9e8b6c 100644 --- a/django/contrib/admin/static/admin/css/responsive_rtl.css +++ b/django/contrib/admin/static/admin/css/responsive_rtl.css @@ -47,6 +47,11 @@ padding-left: 0; padding-right: 16px; } + + [dir="rtl"] ul.messagelist li { + padding: 10px 55px 10px 10px; + background-position-x: calc(100% - 30px); + } } /* MOBILE */ @@ -86,4 +91,9 @@ [dir="rtl"] :enabled.selector-add:focus, :enabled.selector-add:hover { background-position: 0 -72px; } + + [dir="rtl"] ul.messagelist li { + padding: 10px 40px 10px 10px; + background-position-x: calc(100% - 15px); + } } diff --git a/django/contrib/admin/static/admin/css/rtl.css b/django/contrib/admin/static/admin/css/rtl.css index a2556d0478..0d843726bf 100644 --- a/django/contrib/admin/static/admin/css/rtl.css +++ b/django/contrib/admin/static/admin/css/rtl.css @@ -291,3 +291,8 @@ form .form-row p.datetime { .selector .selector-chooser { margin: 0; } + +ul.messagelist li { + padding: 10px 65px 10px 10px; + background-position-x: calc(100% - 40px); +} diff --git a/django/contrib/admin/static/admin/img/icon-alert-dark.svg b/django/contrib/admin/static/admin/img/icon-alert-dark.svg new file mode 100644 index 0000000000..a6365f5ac8 --- /dev/null +++ b/django/contrib/admin/static/admin/img/icon-alert-dark.svg @@ -0,0 +1,9 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="14" height="14"> + <!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--> + <!-- + Icon Name: triangle-exclamation + Icon Family: classic + Icon Style: solid + --> + <path fill="#efb80b" d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/> +</svg> diff --git a/django/contrib/admin/static/admin/img/icon-alert.svg b/django/contrib/admin/static/admin/img/icon-alert.svg index a6365f5ac8..9b4ee36750 100644 --- a/django/contrib/admin/static/admin/img/icon-alert.svg +++ b/django/contrib/admin/static/admin/img/icon-alert.svg @@ -5,5 +5,5 @@ Icon Family: classic Icon Style: solid --> - <path fill="#efb80b" d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/> + <path fill="#b78b02" d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480L40 480c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24l0 112c0 13.3 10.7 24 24 24s24-10.7 24-24l0-112c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/> </svg> diff --git a/django/contrib/admin/static/admin/img/icon-no-dark.svg b/django/contrib/admin/static/admin/img/icon-no-dark.svg new file mode 100644 index 0000000000..bb55c52686 --- /dev/null +++ b/django/contrib/admin/static/admin/img/icon-no-dark.svg @@ -0,0 +1,9 @@ +<svg width="13" height="13" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> + <!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--> + <!-- + Icon Name: circle-xmark + Icon Family: classic + Icon Style: solid + --> + <path fill="#f15f5f" d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM175 175c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47 47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z"/> +</svg> diff --git a/django/contrib/admin/static/admin/img/icon-no.svg b/django/contrib/admin/static/admin/img/icon-no.svg index 27089244ce..6c5b15df05 100644 --- a/django/contrib/admin/static/admin/img/icon-no.svg +++ b/django/contrib/admin/static/admin/img/icon-no.svg @@ -5,5 +5,5 @@ Icon Family: classic Icon Style: solid --> - <path fill="#dd4646" d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM175 175c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47 47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z"/> + <path fill="#c63d3d" d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM175 175c9.4-9.4 24.6-9.4 33.9 0l47 47 47-47c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9l-47 47 47 47c9.4 9.4 9.4 24.6 0 33.9s-24.6 9.4-33.9 0l-47-47-47 47c-9.4 9.4-24.6 9.4-33.9 0s-9.4-24.6 0-33.9l47-47-47-47c-9.4-9.4-9.4-24.6 0-33.9z"/> </svg> diff --git a/django/contrib/admin/static/admin/img/icon-yes-dark.svg b/django/contrib/admin/static/admin/img/icon-yes-dark.svg new file mode 100644 index 0000000000..482292c627 --- /dev/null +++ b/django/contrib/admin/static/admin/img/icon-yes-dark.svg @@ -0,0 +1,9 @@ +<svg width="13" height="13" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"> + <!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.--> + <!-- + Icon Name: circle-check + Icon Family: classic + Icon Style: solid + --> + <path fill="#73c12f" d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z"/> +</svg> diff --git a/django/contrib/admin/static/admin/img/icon-yes.svg b/django/contrib/admin/static/admin/img/icon-yes.svg index 4ee7cbae90..71683dcc3a 100644 --- a/django/contrib/admin/static/admin/img/icon-yes.svg +++ b/django/contrib/admin/static/admin/img/icon-yes.svg @@ -5,5 +5,5 @@ Icon Family: classic Icon Style: solid --> - <path fill="#70bf2b" d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z"/> + <path fill="#649c35" d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z"/> </svg> diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index 9ea603423c..2029909df9 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -135,6 +135,7 @@ from .models import ( UnchangeableObject, UndeletableObject, UnorderedObject, + UserMessenger, UserProxy, Villain, Vodcast, @@ -6876,6 +6877,36 @@ class SeleniumTests(AdminSeleniumTestCase): name_input_value = name_input.get_attribute("value") self.assertEqual(name_input_value, "Test section 1") + @screenshot_cases(["desktop_size", "mobile_size", "rtl", "dark", "high_contrast"]) + @override_settings(MESSAGE_LEVEL=10) + def test_messages(self): + from selenium.webdriver.common.by import By + from selenium.webdriver.support.ui import Select + + with override_settings(MESSAGE_LEVEL=10): + self.admin_login( + username="super", password="secret", login_url=reverse("admin:index") + ) + UserMessenger.objects.create() + for level in ["warning", "info", "error", "success", "debug"]: + self.selenium.get( + self.live_server_url + + reverse("admin:admin_views_usermessenger_changelist"), + ) + checkbox = self.selenium.find_element( + By.CSS_SELECTOR, "tr input.action-select" + ) + checkbox.click() + Select(self.selenium.find_element(By.NAME, "action")).select_by_value( + f"message_{level}" + ) + self.selenium.find_element(By.XPATH, '//button[text()="Run"]').click() + message = self.selenium.find_element( + By.CSS_SELECTOR, "ul.messagelist li" + ) + self.assertEqual(message.get_attribute("innerText"), f"Test {level}") + self.take_screenshot(level) + @override_settings(ROOT_URLCONF="admin_views.urls") class ReadonlyTest(AdminFieldExtractionMixin, TestCase): |
