diff options
| author | Coen van der Kamp <coen@fourdigits.nl> | 2023-06-02 12:20:17 +0100 |
|---|---|---|
| committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2023-06-05 14:43:51 +0200 |
| commit | 10d9d0ccb24ebefbcc12f8bc1b90becec64340ee (patch) | |
| tree | 9a61c4488d8e2c6ce60d02074fc84b58569d9347 | |
| parent | 0faad0193815c5f07a08dba4eaa04193ca9eb268 (diff) | |
Fixed #34622 -- Improved accessibility of related widget wrapper in admin.
This improves accessibility for screen reader users by adding
"aria-disabled" and removing "alt".
Thanks Thibaud Colas for the report.
3 files changed, 82 insertions, 4 deletions
diff --git a/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js b/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js index afb6b66c25..32e3f5b840 100644 --- a/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js +++ b/django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js @@ -79,9 +79,11 @@ siblings.each(function() { const elm = $(this); elm.attr('href', elm.attr('data-href-template').replace('__fk__', value)); + elm.removeAttr('aria-disabled'); }); } else { siblings.removeAttr('href'); + siblings.attr('aria-disabled', true); } } diff --git a/django/contrib/admin/templates/admin/widgets/related_widget_wrapper.html b/django/contrib/admin/templates/admin/widgets/related_widget_wrapper.html index 6c285ea044..805f48b947 100644 --- a/django/contrib/admin/templates/admin/widgets/related_widget_wrapper.html +++ b/django/contrib/admin/templates/admin/widgets/related_widget_wrapper.html @@ -9,7 +9,7 @@ data-href-template="{{ change_related_template_url }}?{{ url_params }}" data-popup="yes" title="{% blocktranslate %}Change selected {{ model }}{% endblocktranslate %}"> - <img src="{% static 'admin/img/icon-changelink.svg' %}" alt="{% translate 'Change' %}"> + <img src="{% static 'admin/img/icon-changelink.svg' %}" alt=""> </a> {% endif %} {% if can_add_related %} @@ -17,7 +17,7 @@ data-popup="yes" href="{{ add_related_url }}?{{ url_params }}" title="{% blocktranslate %}Add another {{ model }}{% endblocktranslate %}"> - <img src="{% static 'admin/img/icon-addlink.svg' %}" alt="{% translate 'Add' %}"> + <img src="{% static 'admin/img/icon-addlink.svg' %}" alt=""> </a> {% endif %} {% if can_delete_related %} @@ -25,14 +25,14 @@ data-href-template="{{ delete_related_template_url }}?{{ url_params }}" data-popup="yes" title="{% blocktranslate %}Delete selected {{ model }}{% endblocktranslate %}"> - <img src="{% static 'admin/img/icon-deletelink.svg' %}" alt="{% translate 'Delete' %}"> + <img src="{% static 'admin/img/icon-deletelink.svg' %}" alt=""> </a> {% endif %} {% if can_view_related %} <a class="related-widget-wrapper-link view-related" id="view_id_{{ name }}" data-href-template="{{ change_related_template_url }}?{{ view_related_url_params }}" title="{% blocktranslate %}View selected {{ model }}{% endblocktranslate %}"> - <img src="{% static 'admin/img/icon-viewlink.svg' %}" alt="{% translate 'View' %}"> + <img src="{% static 'admin/img/icon-viewlink.svg' %}" alt=""> </a> {% endif %} {% endif %} diff --git a/tests/admin_views/test_related_object_lookups.py b/tests/admin_views/test_related_object_lookups.py new file mode 100644 index 0000000000..d478214d90 --- /dev/null +++ b/tests/admin_views/test_related_object_lookups.py @@ -0,0 +1,76 @@ +from django.contrib.admin.tests import AdminSeleniumTestCase +from django.contrib.auth.models import User +from django.test import override_settings +from django.urls import reverse + + +@override_settings(ROOT_URLCONF="admin_views.urls") +class SeleniumTests(AdminSeleniumTestCase): + available_apps = ["admin_views"] + AdminSeleniumTestCase.available_apps + + def setUp(self): + self.superuser = User.objects.create_superuser( + username="super", password="secret", email="super@example.com" + ) + self.admin_login( + username="super", password="secret", login_url=reverse("admin:index") + ) + + def test_related_object_link_images_empty_alt(self): + from selenium.webdriver.common.by import By + + album_add_url = reverse("admin:admin_views_album_add") + self.selenium.get(self.live_server_url + album_add_url) + + tests = [ + "add_id_owner", + "change_id_owner", + "delete_id_owner", + "view_id_owner", + ] + for link_id in tests: + with self.subTest(link_id): + link_image = self.selenium.find_element( + By.XPATH, f'//*[@id="{link_id}"]/img' + ) + self.assertEqual(link_image.get_attribute("alt"), "") + + def test_related_object_lookup_link_initial_state(self): + from selenium.webdriver.common.by import By + + album_add_url = reverse("admin:admin_views_album_add") + self.selenium.get(self.live_server_url + album_add_url) + + tests = [ + "change_id_owner", + "delete_id_owner", + "view_id_owner", + ] + for link_id in tests: + with self.subTest(link_id): + link = self.selenium.find_element(By.XPATH, f'//*[@id="{link_id}"]') + self.assertEqual(link.get_attribute("aria-disabled"), "true") + + def test_related_object_lookup_link_enabled(self): + from selenium.webdriver.common.by import By + from selenium.webdriver.support.select import Select + + album_add_url = reverse("admin:admin_views_album_add") + self.selenium.get(self.live_server_url + album_add_url) + + select_element = self.selenium.find_element(By.XPATH, '//*[@id="id_owner"]') + option = Select(select_element).options[-1] + self.assertEqual(option.text, "super") + select_element.click() + option.click() + + tests = [ + "add_id_owner", + "change_id_owner", + "delete_id_owner", + "view_id_owner", + ] + for link_id in tests: + with self.subTest(link_id): + link = self.selenium.find_element(By.XPATH, f'//*[@id="{link_id}"]') + self.assertIsNone(link.get_attribute("aria-disabled")) |
