mirror of
https://github.com/django/django.git
synced 2025-06-30 15:59:11 +00:00
Fixed #36366 -- Improved accessibility of pagination in the admin.
This commit is contained in:
parent
29f5e1e97d
commit
3f59711581
@ -1164,7 +1164,19 @@ a.deletelink:focus, a.deletelink:hover {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.paginator ul {
|
||||
margin: 0;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.paginator ul li {
|
||||
display: inline-block;
|
||||
line-height: 22px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.paginator a:link, .paginator a:visited {
|
||||
display: inline-block;
|
||||
padding: 2px 6px;
|
||||
background: var(--button-bg);
|
||||
text-decoration: none;
|
||||
@ -1182,18 +1194,15 @@ a.deletelink:focus, a.deletelink:hover {
|
||||
color: var(--link-hover-color);
|
||||
}
|
||||
|
||||
.paginator .end {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.paginator .this-page {
|
||||
padding: 2px 6px;
|
||||
.paginator a[aria-current="page"] {
|
||||
color: var(--body-quiet-color);
|
||||
background: transparent;
|
||||
font-weight: bold;
|
||||
font-size: 0.8125rem;
|
||||
vertical-align: top;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.paginator a:focus, .paginator a:hover {
|
||||
.paginator a:not([aria-current="page"]):focus,
|
||||
.paginator a:not([aria-current="page"]):hover {
|
||||
color: white;
|
||||
background: var(--link-hover-color);
|
||||
}
|
||||
|
@ -43,6 +43,22 @@
|
||||
border-bottom: 1px solid var(--hairline-color);
|
||||
}
|
||||
|
||||
#changelist .changelist-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
border-top: 1px solid var(--hairline-color);
|
||||
border-bottom: 1px solid var(--hairline-color);
|
||||
}
|
||||
|
||||
#changelist .changelist-footer .paginator {
|
||||
color: var(--body-quiet-color);
|
||||
background: var(--body-bg);
|
||||
border: none;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#changelist .paginator {
|
||||
color: var(--body-quiet-color);
|
||||
border-bottom: 1px solid var(--hairline-color);
|
||||
@ -50,6 +66,10 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#changelist .paginator ul {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* CHANGELIST TABLES */
|
||||
|
||||
#changelist table thead th {
|
||||
|
@ -107,7 +107,7 @@ thead th.sorted .text {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
.paginator .end {
|
||||
.paginator ul {
|
||||
margin-left: 6px;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
@ -69,7 +69,12 @@
|
||||
{% result_list cl %}
|
||||
{% if action_form and actions_on_bottom and cl.show_admin_actions %}{% admin_actions %}{% endif %}
|
||||
{% endblock %}
|
||||
{% block pagination %}{% pagination cl %}{% endblock %}
|
||||
{% block pagination %}
|
||||
<div class="changelist-footer">
|
||||
{% pagination cl %}
|
||||
{% if cl.formset and cl.result_count %}<input type="submit" name="_save" class="default" value="{% translate 'Save' %}">{% endif %}
|
||||
{% endblock %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% block filters %}
|
||||
|
@ -34,20 +34,23 @@
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="paginator">
|
||||
<nav class="paginator" aria-labelledby="pagination">
|
||||
<h2 id="pagination" class="visually-hidden">{% blocktranslate with name=opts.verbose_name %}Pagination {{ name }} entries{% endblocktranslate %}</h2>
|
||||
{% if pagination_required %}
|
||||
<ul>
|
||||
{% for i in page_range %}
|
||||
{% if i == action_list.paginator.ELLIPSIS %}
|
||||
{{ action_list.paginator.ELLIPSIS }}
|
||||
<li>{{ action_list.paginator.ELLIPSIS }}</li>
|
||||
{% elif i == action_list.number %}
|
||||
<span class="this-page">{{ i }}</span>
|
||||
<li><a role="button" href="" aria-current="page">{{ i }}</a></li>
|
||||
{% else %}
|
||||
<a role="button" href="?{{ page_var }}={{ i }}" {% if i == action_list.paginator.num_pages %} class="end" {% endif %}>{{ i }}</a>
|
||||
<li><a role="button" href="?{{ page_var }}={{ i }}">{{ i }}</a></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{{ action_list.paginator.count }} {% blocktranslate count counter=action_list.paginator.count %}entry{% plural %}entries{% endblocktranslate %}
|
||||
</p>
|
||||
</nav>
|
||||
{% else %}
|
||||
<p>{% translate 'This object doesn’t have a change history. It probably wasn’t added via this admin site.' %}</p>
|
||||
{% endif %}
|
||||
|
@ -1,12 +1,14 @@
|
||||
{% load admin_list %}
|
||||
{% load i18n %}
|
||||
<p class="paginator">
|
||||
<nav class="paginator" aria-labelledby="pagination">
|
||||
<h2 id="pagination" class="visually-hidden">{% blocktranslate with name=cl.opts.verbose_name_plural %}Pagination {{ name }}{% endblocktranslate %}</h2>
|
||||
{% if pagination_required %}
|
||||
<ul>
|
||||
{% for i in page_range %}
|
||||
{% paginator_number cl i %}
|
||||
<li>{% paginator_number cl i %}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
{{ cl.result_count }} {% if cl.result_count == 1 %}{{ cl.opts.verbose_name }}{% else %}{{ cl.opts.verbose_name_plural }}{% endif %}
|
||||
{% if show_all_url %}<a href="{{ show_all_url }}" class="showall">{% translate 'Show all' %}</a>{% endif %}
|
||||
{% if cl.formset and cl.result_count %}<input type="submit" name="_save" class="default" value="{% translate 'Save' %}">{% endif %}
|
||||
</p>
|
||||
</nav>
|
||||
|
@ -42,12 +42,14 @@ def paginator_number(cl, i):
|
||||
if i == cl.paginator.ELLIPSIS:
|
||||
return format_html("{} ", cl.paginator.ELLIPSIS)
|
||||
elif i == cl.page_num:
|
||||
return format_html('<span class="this-page">{}</span> ', i)
|
||||
return format_html(
|
||||
'<a role="button" href="" aria-current="page">{}</a> ',
|
||||
i,
|
||||
)
|
||||
else:
|
||||
return format_html(
|
||||
'<a role="button" href="{}"{}>{}</a> ',
|
||||
'<a role="button" href="{}">{}</a> ',
|
||||
cl.get_query_string({PAGE_VAR: i}),
|
||||
mark_safe(' class="end"' if i == cl.paginator.num_pages else ""),
|
||||
i,
|
||||
)
|
||||
|
||||
|
@ -946,6 +946,41 @@ class ChangeListTests(TestCase):
|
||||
self.assertEqual(cl.paginator.count, 30)
|
||||
self.assertEqual(list(cl.paginator.page_range), [1, 2, 3])
|
||||
|
||||
def test_pagination_render(self):
|
||||
objs = [Swallow(origin=f"Swallow {i}", load=i, speed=i) for i in range(1, 5)]
|
||||
Swallow.objects.bulk_create(objs)
|
||||
|
||||
request = self.factory.get("/child/")
|
||||
request.user = self.superuser
|
||||
|
||||
admin = SwallowAdmin(Swallow, custom_site)
|
||||
cl = admin.get_changelist_instance(request)
|
||||
template = Template(
|
||||
"{% load admin_list %}{% spaceless %}{% pagination cl %}{% endspaceless %}"
|
||||
)
|
||||
context = Context({"cl": cl, "opts": cl.opts})
|
||||
pagination_output = template.render(context)
|
||||
self.assertTrue(
|
||||
pagination_output.startswith(
|
||||
'<nav class="paginator" aria-labelledby="pagination">'
|
||||
)
|
||||
)
|
||||
self.assertInHTML(
|
||||
'<h2 id="pagination" class="visually-hidden">Pagination swallows</h2>',
|
||||
pagination_output,
|
||||
)
|
||||
self.assertTrue(pagination_output.endswith("</nav>"))
|
||||
self.assertInHTML(
|
||||
'<li><a role="button" href="" aria-current="page">1</a></li>',
|
||||
pagination_output,
|
||||
)
|
||||
self.assertInHTML(
|
||||
'<li><a role="button" href="?p=2">2</a></li>',
|
||||
pagination_output,
|
||||
)
|
||||
self.assertEqual(pagination_output.count('aria-current="page"'), 1)
|
||||
self.assertEqual(pagination_output.count('href=""'), 1)
|
||||
|
||||
def test_computed_list_display_localization(self):
|
||||
"""
|
||||
Regression test for #13196: output of functions should be localized
|
||||
|
@ -80,20 +80,29 @@ class SeleniumTests(AdminSeleniumTestCase):
|
||||
self.selenium.get(self.live_server_url + user_history_url)
|
||||
|
||||
paginator = self.selenium.find_element(By.CSS_SELECTOR, ".paginator")
|
||||
self.assertEqual(paginator.tag_name, "nav")
|
||||
labelledby = paginator.get_attribute("aria-labelledby")
|
||||
description = self.selenium.find_element(By.CSS_SELECTOR, "#%s" % labelledby)
|
||||
self.assertHTMLEqual(
|
||||
description.get_attribute("outerHTML"),
|
||||
'<h2 id="pagination" class="visually-hidden">Pagination user entries</h2>',
|
||||
)
|
||||
self.assertTrue(paginator.is_displayed())
|
||||
aria_current_link = paginator.find_elements(By.CSS_SELECTOR, "[aria-current]")
|
||||
self.assertEqual(len(aria_current_link), 1)
|
||||
# The current page.
|
||||
current_page_link = aria_current_link[0]
|
||||
self.assertEqual(current_page_link.get_attribute("aria-current"), "page")
|
||||
self.assertEqual(current_page_link.get_attribute("href"), "")
|
||||
self.assertIn("%s entries" % LogEntry.objects.count(), paginator.text)
|
||||
self.assertIn(str(Paginator.ELLIPSIS), paginator.text)
|
||||
# The current page.
|
||||
current_page_link = self.selenium.find_element(
|
||||
By.CSS_SELECTOR, "span.this-page"
|
||||
)
|
||||
self.assertEqual(current_page_link.text, "1")
|
||||
# The last page.
|
||||
last_page_link = self.selenium.find_element(By.CSS_SELECTOR, ".end")
|
||||
last_page_link = self.selenium.find_element(By.XPATH, "//ul/li[last()]/a")
|
||||
self.assertTrue(last_page_link.text, "20")
|
||||
# Select the second page.
|
||||
pages = paginator.find_elements(By.TAG_NAME, "a")
|
||||
second_page_link = pages[0]
|
||||
second_page_link = pages[1]
|
||||
self.assertEqual(second_page_link.text, "2")
|
||||
second_page_link.click()
|
||||
self.assertIn("?p=2", self.selenium.current_url)
|
||||
|
Loading…
x
Reference in New Issue
Block a user