1
0
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:
antoliny0919 2025-07-20 22:12:15 +09:00 committed by Sarah Boyce
parent 792ca148a2
commit 6ea3319079
5 changed files with 92 additions and 33 deletions

View File

@ -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 {

View File

@ -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;
} }

View File

@ -56,6 +56,24 @@
{% 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 filters %}
{% if cl.has_filters %}
<search id="changelist-filter" aria-labelledby="changelist-filter-header">
<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 %}<h3>
{% if cl.add_facets %}<a href="{{ cl.remove_facet_link }}" class="hidelink">{% translate "Hide counts" %}</a>
{% else %}<a href="{{ cl.add_facet_link }}" class="viewlink">{% translate "Show counts" %}</a>{% endif %}
</h3>{% endif %}
{% if cl.has_active_filters %}<h3>
<a href="{{ cl.clear_all_filters_qs }}">&#10006; {% translate "Clear all filters" %}</a>
</h3>{% endif %}
</div>{% endif %}
{% for spec in cl.filter_specs %}{% admin_list_filter cl spec %}{% endfor %}
</search>
{% endif %}
{% endblock %}
<div>
{% block search %}{% search_form cl %}{% endblock %} {% block search %}{% search_form cl %}{% endblock %}
{% block date_hierarchy %}{% if cl.date_hierarchy %}{% date_hierarchy cl %}{% endif %}{% endblock %} {% block date_hierarchy %}{% if cl.date_hierarchy %}{% date_hierarchy cl %}{% endif %}{% endblock %}
@ -77,23 +95,7 @@
</div> </div>
</form> </form>
</div> </div>
{% block filters %} </div>
{% if cl.has_filters %}
<search id="changelist-filter" aria-labelledby="changelist-filter-header">
<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 %}<h3>
{% if cl.add_facets %}<a href="{{ cl.remove_facet_link }}" class="hidelink">{% translate "Hide counts" %}</a>
{% else %}<a href="{{ cl.add_facet_link }}" class="viewlink">{% translate "Show counts" %}</a>{% endif %}
</h3>{% endif %}
{% if cl.has_active_filters %}<h3>
<a href="{{ cl.clear_all_filters_qs }}">&#10006; {% translate "Clear all filters" %}</a>
</h3>{% endif %}
</div>{% endif %}
{% for spec in cl.filter_specs %}{% admin_list_filter cl spec %}{% endfor %}
</search>
{% endif %}
{% endblock %}
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -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",)

View File

@ -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