Skip to content

Commit

Permalink
Fixed #27471 -- Made admin's filter choices collapsable.
Browse files Browse the repository at this point in the history
  • Loading branch information
mgaligniana authored and felixxm committed Apr 26, 2022
1 parent 37602e4 commit 27aa703
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 6 deletions.
25 changes: 24 additions & 1 deletion django/contrib/admin/static/admin/css/changelists.css
Original file line number Diff line number Diff line change
Expand Up @@ -148,12 +148,35 @@
border-bottom: none;
}

#changelist-filter h3 {
#changelist-filter h3,
#changelist-filter details summary {
font-weight: 400;
padding: 0 15px;
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 {
margin: 5px 0;
padding: 0 15px 15px;
Expand Down
30 changes: 30 additions & 0 deletions django/contrib/admin/static/admin/js/filters.js
Original file line number Diff line number Diff line change
@@ -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));
});
});
}
1 change: 1 addition & 0 deletions django/contrib/admin/templates/admin/change_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
{% block extrahead %}
{{ block.super }}
{{ media.js }}
<script src="{% static 'admin/js/filters.js' %}" defer></script>
{% endblock %}

{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} change-list{% endblock %}
Expand Down
14 changes: 9 additions & 5 deletions django/contrib/admin/templates/admin/filter.html
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
{% load i18n %}
<h3>{% blocktranslate with filter_title=title %} By {{ filter_title }} {% endblocktranslate %}</h3>
<ul>
{% for choice in choices %}
<details data-filter-title="{{ title }}" open>
<summary>
{% blocktranslate with filter_title=title %} By {{ filter_title }} {% endblocktranslate %}
</summary>
<ul>
{% for choice in choices %}
<li{% if choice.selected %} class="selected"{% endif %}>
<a href="{{ choice.query_string|iriencode }}">{{ choice.display }}</a></li>
{% endfor %}
</ul>
{% endfor %}
</ul>
</details>
Binary file modified docs/intro/_images/admin13t.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/ref/contrib/admin/_images/list_filter.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
52 changes: 52 additions & 0 deletions tests/admin_changelist/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1810,3 +1810,55 @@ def test_save_without_changes_warns_on_pending_action(self):
)
finally:
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")
)

0 comments on commit 27aa703

Please sign in to comment.