mirror of
https://github.com/django/django.git
synced 2025-04-01 03:56:42 +00:00
Fixed #27471 -- Made admin's filter choices collapsable.
This commit is contained in:
parent
37602e4948
commit
27aa7035f5
@ -148,12 +148,35 @@
|
|||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#changelist-filter h3 {
|
#changelist-filter h3,
|
||||||
|
#changelist-filter details summary {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
padding: 0 15px;
|
padding: 0 15px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#changelist-filter details summary > * {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
#changelist-filter details > summary {
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#changelist-filter details > summary::-webkit-details-marker {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#changelist-filter details > summary::before {
|
||||||
|
content: '→';
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--link-hover-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
#changelist-filter details[open] > summary::before {
|
||||||
|
content: '↓';
|
||||||
|
}
|
||||||
|
|
||||||
#changelist-filter ul {
|
#changelist-filter ul {
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
padding: 0 15px 15px;
|
padding: 0 15px 15px;
|
||||||
|
30
django/contrib/admin/static/admin/js/filters.js
Normal file
30
django/contrib/admin/static/admin/js/filters.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Persist changelist filters state (collapsed/expanded).
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
{
|
||||||
|
// Init filters.
|
||||||
|
let filters = JSON.parse(sessionStorage.getItem('django.admin.filtersState'));
|
||||||
|
|
||||||
|
if (!filters) {
|
||||||
|
filters = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.entries(filters).forEach(([key, value]) => {
|
||||||
|
const detailElement = document.querySelector(`[data-filter-title='${key}']`);
|
||||||
|
|
||||||
|
// Check if the filter is present, it could be from other view.
|
||||||
|
if (detailElement) {
|
||||||
|
value ? detailElement.setAttribute('open', '') : detailElement.removeAttribute('open');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Save filter state when clicks.
|
||||||
|
const details = document.querySelectorAll('details');
|
||||||
|
details.forEach(detail => {
|
||||||
|
detail.addEventListener('toggle', event => {
|
||||||
|
filters[`${event.target.dataset.filterTitle}`] = detail.open;
|
||||||
|
sessionStorage.setItem('django.admin.filtersState', JSON.stringify(filters));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -21,6 +21,7 @@
|
|||||||
{% block extrahead %}
|
{% block extrahead %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
{{ media.js }}
|
{{ media.js }}
|
||||||
|
<script src="{% static 'admin/js/filters.js' %}" defer></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} change-list{% endblock %}
|
{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} change-list{% endblock %}
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
<h3>{% blocktranslate with filter_title=title %} By {{ filter_title }} {% endblocktranslate %}</h3>
|
<details data-filter-title="{{ title }}" open>
|
||||||
|
<summary>
|
||||||
|
{% blocktranslate with filter_title=title %} By {{ filter_title }} {% endblocktranslate %}
|
||||||
|
</summary>
|
||||||
<ul>
|
<ul>
|
||||||
{% for choice in choices %}
|
{% for choice in choices %}
|
||||||
<li{% if choice.selected %} class="selected"{% endif %}>
|
<li{% if choice.selected %} class="selected"{% endif %}>
|
||||||
<a href="{{ choice.query_string|iriencode }}">{{ choice.display }}</a></li>
|
<a href="{{ choice.query_string|iriencode }}">{{ choice.display }}</a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
</details>
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 26 KiB |
Binary file not shown.
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 40 KiB |
@ -1810,3 +1810,55 @@ class SeleniumTests(AdminSeleniumTestCase):
|
|||||||
)
|
)
|
||||||
finally:
|
finally:
|
||||||
alert.dismiss()
|
alert.dismiss()
|
||||||
|
|
||||||
|
def test_collapse_filters(self):
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
|
|
||||||
|
self.admin_login(username="super", password="secret")
|
||||||
|
self.selenium.get(self.live_server_url + reverse("admin:auth_user_changelist"))
|
||||||
|
|
||||||
|
# The UserAdmin has 3 field filters by default: "staff status",
|
||||||
|
# "superuser status", and "active".
|
||||||
|
details = self.selenium.find_elements(By.CSS_SELECTOR, "details")
|
||||||
|
# All filters are opened at first.
|
||||||
|
for detail in details:
|
||||||
|
self.assertTrue(detail.get_attribute("open"))
|
||||||
|
# Collapse "staff' and "superuser" filters.
|
||||||
|
for detail in details[:2]:
|
||||||
|
summary = detail.find_element(By.CSS_SELECTOR, "summary")
|
||||||
|
summary.click()
|
||||||
|
self.assertFalse(detail.get_attribute("open"))
|
||||||
|
# Filters are in the same state after refresh.
|
||||||
|
self.selenium.refresh()
|
||||||
|
self.assertFalse(
|
||||||
|
self.selenium.find_element(
|
||||||
|
By.CSS_SELECTOR, "[data-filter-title='staff status']"
|
||||||
|
).get_attribute("open")
|
||||||
|
)
|
||||||
|
self.assertFalse(
|
||||||
|
self.selenium.find_element(
|
||||||
|
By.CSS_SELECTOR, "[data-filter-title='superuser status']"
|
||||||
|
).get_attribute("open")
|
||||||
|
)
|
||||||
|
self.assertTrue(
|
||||||
|
self.selenium.find_element(
|
||||||
|
By.CSS_SELECTOR, "[data-filter-title='active']"
|
||||||
|
).get_attribute("open")
|
||||||
|
)
|
||||||
|
# Collapse a filter on another view (Bands).
|
||||||
|
self.selenium.get(
|
||||||
|
self.live_server_url + reverse("admin:admin_changelist_band_changelist")
|
||||||
|
)
|
||||||
|
self.selenium.find_element(By.CSS_SELECTOR, "summary").click()
|
||||||
|
# Go to Users view and then, back again to Bands view.
|
||||||
|
self.selenium.get(self.live_server_url + reverse("admin:auth_user_changelist"))
|
||||||
|
self.selenium.get(
|
||||||
|
self.live_server_url + reverse("admin:admin_changelist_band_changelist")
|
||||||
|
)
|
||||||
|
# The filter remains in the same state.
|
||||||
|
self.assertFalse(
|
||||||
|
self.selenium.find_element(
|
||||||
|
By.CSS_SELECTOR,
|
||||||
|
"[data-filter-title='number of members']",
|
||||||
|
).get_attribute("open")
|
||||||
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user