summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCoen van der Kamp <coen@fourdigits.nl>2023-06-02 12:20:17 +0100
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2023-06-05 14:43:51 +0200
commit10d9d0ccb24ebefbcc12f8bc1b90becec64340ee (patch)
tree9a61c4488d8e2c6ce60d02074fc84b58569d9347
parent0faad0193815c5f07a08dba4eaa04193ca9eb268 (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.
-rw-r--r--django/contrib/admin/static/admin/js/admin/RelatedObjectLookups.js2
-rw-r--r--django/contrib/admin/templates/admin/widgets/related_widget_wrapper.html8
-rw-r--r--tests/admin_views/test_related_object_lookups.py76
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"))