mirror of
https://github.com/django/django.git
synced 2025-08-21 01:09:13 +00:00
Fixed #36511 -- Ensured filters came before table in keyboard navigation in admin changelist.
This commit is contained in:
parent
792ca148a2
commit
6ea3319079
@ -1,14 +1,22 @@
|
|||||||
/* CHANGELISTS */
|
/* CHANGELISTS */
|
||||||
|
|
||||||
#changelist {
|
#changelist .changelist-form-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
justify-content: space-between;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#changelist .changelist-form-container {
|
#changelist .changelist-form-container > div {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
min-width: 0;
|
}
|
||||||
|
|
||||||
|
#changelist .changelist-form-container:not(:has(#changelist-filter)) > div {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#changelist .changelist-form-container:has(#changelist-filter) > div {
|
||||||
|
max-width: calc(100% - 270px);
|
||||||
}
|
}
|
||||||
|
|
||||||
#changelist table {
|
#changelist table {
|
||||||
|
@ -409,11 +409,15 @@ input[type="submit"], button {
|
|||||||
|
|
||||||
/* Changelist */
|
/* Changelist */
|
||||||
|
|
||||||
#changelist {
|
#changelist .changelist-form-container {
|
||||||
align-items: stretch;
|
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#changelist .changelist-form-container:has(#changelist-filter) > div {
|
||||||
|
max-width: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
#toolbar {
|
#toolbar {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
@ -436,8 +440,7 @@ input[type="submit"], button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#changelist-filter {
|
#changelist-filter {
|
||||||
position: static;
|
width: 100%;
|
||||||
width: auto;
|
|
||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,29 +56,8 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<div class="module{% if cl.has_filters %} filtered{% endif %}" id="changelist">
|
<div class="module{% if cl.has_filters %} filtered{% endif %}" id="changelist">
|
||||||
<div class="changelist-form-container">
|
<div class="changelist-form-container">
|
||||||
{% block search %}{% search_form cl %}{% endblock %}
|
{% block filters %}
|
||||||
{% block date_hierarchy %}{% if cl.date_hierarchy %}{% date_hierarchy cl %}{% endif %}{% endblock %}
|
{% if cl.has_filters %}
|
||||||
|
|
||||||
<form id="changelist-form" method="post"{% if cl.formset and cl.formset.is_multipart %} enctype="multipart/form-data"{% endif %} novalidate>{% csrf_token %}
|
|
||||||
{% if cl.formset %}
|
|
||||||
<div>{{ cl.formset.management_form }}</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{% block result_list %}
|
|
||||||
{% if action_form and actions_on_top and cl.show_admin_actions %}{% admin_actions %}{% endif %}
|
|
||||||
{% result_list cl %}
|
|
||||||
{% if action_form and actions_on_bottom and cl.show_admin_actions %}{% admin_actions %}{% endif %}
|
|
||||||
{% 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 %}
|
|
||||||
{% if cl.has_filters %}
|
|
||||||
<search id="changelist-filter" aria-labelledby="changelist-filter-header">
|
<search id="changelist-filter" aria-labelledby="changelist-filter-header">
|
||||||
<h2 id="changelist-filter-header">{% translate 'Filter' %}</h2>
|
<h2 id="changelist-filter-header">{% translate 'Filter' %}</h2>
|
||||||
{% if cl.is_facets_optional or cl.has_active_filters %}<div id="changelist-filter-extra-actions">
|
{% if cl.is_facets_optional or cl.has_active_filters %}<div id="changelist-filter-extra-actions">
|
||||||
@ -92,8 +71,31 @@
|
|||||||
</div>{% endif %}
|
</div>{% endif %}
|
||||||
{% for spec in cl.filter_specs %}{% admin_list_filter cl spec %}{% endfor %}
|
{% for spec in cl.filter_specs %}{% admin_list_filter cl spec %}{% endfor %}
|
||||||
</search>
|
</search>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
<div>
|
||||||
|
{% block search %}{% search_form cl %}{% endblock %}
|
||||||
|
{% block date_hierarchy %}{% if cl.date_hierarchy %}{% date_hierarchy cl %}{% endif %}{% endblock %}
|
||||||
|
|
||||||
|
<form id="changelist-form" method="post"{% if cl.formset and cl.formset.is_multipart %} enctype="multipart/form-data"{% endif %} novalidate>{% csrf_token %}
|
||||||
|
{% if cl.formset %}
|
||||||
|
<div>{{ cl.formset.management_form }}</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% block result_list %}
|
||||||
|
{% if action_form and actions_on_top and cl.show_admin_actions %}{% admin_actions %}{% endif %}
|
||||||
|
{% result_list cl %}
|
||||||
|
{% if action_form and actions_on_bottom and cl.show_admin_actions %}{% admin_actions %}{% endif %}
|
||||||
|
{% 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>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -431,6 +431,8 @@ class PodcastAdmin(admin.ModelAdmin):
|
|||||||
list_display = ("name", "release_date")
|
list_display = ("name", "release_date")
|
||||||
list_editable = ("release_date",)
|
list_editable = ("release_date",)
|
||||||
date_hierarchy = "release_date"
|
date_hierarchy = "release_date"
|
||||||
|
list_filter = ("name",)
|
||||||
|
search_fields = ("name",)
|
||||||
ordering = ("name",)
|
ordering = ("name",)
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,6 +3,8 @@ from django.contrib.auth.models import User
|
|||||||
from django.test import override_settings
|
from django.test import override_settings
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from .models import Podcast
|
||||||
|
|
||||||
|
|
||||||
@override_settings(ROOT_URLCONF="admin_views.urls")
|
@override_settings(ROOT_URLCONF="admin_views.urls")
|
||||||
class SeleniumTests(AdminSeleniumTestCase):
|
class SeleniumTests(AdminSeleniumTestCase):
|
||||||
@ -125,3 +127,45 @@ class SeleniumTests(AdminSeleniumTestCase):
|
|||||||
)
|
)
|
||||||
self.assertTrue(is_vertical_scrolleable)
|
self.assertTrue(is_vertical_scrolleable)
|
||||||
self.assertFalse(is_horizontal_scrolleable)
|
self.assertFalse(is_horizontal_scrolleable)
|
||||||
|
|
||||||
|
def test_skip_link_keyboard_navigation_in_changelist(self):
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
|
from selenium.webdriver.common.keys import Keys
|
||||||
|
|
||||||
|
Podcast.objects.create(name="apple", release_date="2000-09-19")
|
||||||
|
self.admin_login(
|
||||||
|
username="super", password="secret", login_url=reverse("admin:index")
|
||||||
|
)
|
||||||
|
self.selenium.get(
|
||||||
|
self.live_server_url + reverse("admin:admin_views_podcast_changelist")
|
||||||
|
)
|
||||||
|
selectors = [
|
||||||
|
"ul.object-tools", # object_tools.
|
||||||
|
"search#changelist-filter", # list_filter.
|
||||||
|
"form#changelist-search", # search_fields.
|
||||||
|
"nav.toplinks", # date_hierarchy.
|
||||||
|
"form#changelist-form div.actions", # action.
|
||||||
|
"table#result_list", # table.
|
||||||
|
"div.changelist-footer", # footer.
|
||||||
|
]
|
||||||
|
content = self.selenium.find_element(By.ID, "content-start")
|
||||||
|
content.send_keys(Keys.TAB)
|
||||||
|
|
||||||
|
for selector in selectors:
|
||||||
|
with self.subTest(selector=selector):
|
||||||
|
# Currently focused element.
|
||||||
|
focused_element = self.selenium.switch_to.active_element
|
||||||
|
expected_element = self.selenium.find_element(By.CSS_SELECTOR, selector)
|
||||||
|
element_points = self.selenium.find_elements(
|
||||||
|
By.CSS_SELECTOR,
|
||||||
|
f"{selector} a, {selector} input, {selector} button",
|
||||||
|
)
|
||||||
|
self.assertIn(
|
||||||
|
focused_element.get_attribute("outerHTML"),
|
||||||
|
expected_element.get_attribute("innerHTML"),
|
||||||
|
)
|
||||||
|
# Move to the next container element via TAB.
|
||||||
|
for point in element_points[::-1]:
|
||||||
|
if point.is_displayed():
|
||||||
|
point.send_keys(Keys.TAB)
|
||||||
|
break
|
||||||
|
Loading…
x
Reference in New Issue
Block a user