diff options
| author | Marcelo Galigniana <marcelogaligniana@gmail.com> | 2022-02-14 00:03:53 -0300 |
|---|---|---|
| committer | Mariusz Felisiak <felisiak.mariusz@gmail.com> | 2022-04-26 08:44:31 +0200 |
| commit | 27aa7035f57f0db30b6632e4274e18b430906799 (patch) | |
| tree | 8573068be094049e218fe2b35c8a67b123451d86 | |
| parent | 37602e49484a88867f40e9498f86c49c2d1c5d7c (diff) | |
Fixed #27471 -- Made admin's filter choices collapsable.
| -rw-r--r-- | django/contrib/admin/static/admin/css/changelists.css | 25 | ||||
| -rw-r--r-- | django/contrib/admin/static/admin/js/filters.js | 30 | ||||
| -rw-r--r-- | django/contrib/admin/templates/admin/change_list.html | 1 | ||||
| -rw-r--r-- | django/contrib/admin/templates/admin/filter.html | 14 | ||||
| -rw-r--r-- | docs/intro/_images/admin13t.png | bin | 16961 -> 26713 bytes | |||
| -rw-r--r-- | docs/ref/contrib/admin/_images/list_filter.png | bin | 38081 -> 40590 bytes | |||
| -rw-r--r-- | tests/admin_changelist/tests.py | 52 |
7 files changed, 116 insertions, 6 deletions
diff --git a/django/contrib/admin/static/admin/css/changelists.css b/django/contrib/admin/static/admin/css/changelists.css index 4f8b89ae7c..68ba55787b 100644 --- a/django/contrib/admin/static/admin/css/changelists.css +++ b/django/contrib/admin/static/admin/css/changelists.css @@ -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; diff --git a/django/contrib/admin/static/admin/js/filters.js b/django/contrib/admin/static/admin/js/filters.js new file mode 100644 index 0000000000..ba691ac82f --- /dev/null +++ b/django/contrib/admin/static/admin/js/filters.js @@ -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)); + }); + }); +} diff --git a/django/contrib/admin/templates/admin/change_list.html b/django/contrib/admin/templates/admin/change_list.html index 12a495504f..29747c317e 100644 --- a/django/contrib/admin/templates/admin/change_list.html +++ b/django/contrib/admin/templates/admin/change_list.html @@ -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 %} diff --git a/django/contrib/admin/templates/admin/filter.html b/django/contrib/admin/templates/admin/filter.html index b5a1bbab0e..a6094ecb5a 100644 --- a/django/contrib/admin/templates/admin/filter.html +++ b/django/contrib/admin/templates/admin/filter.html @@ -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> diff --git a/docs/intro/_images/admin13t.png b/docs/intro/_images/admin13t.png Binary files differindex 8827932461..3fed5f1985 100644 --- a/docs/intro/_images/admin13t.png +++ b/docs/intro/_images/admin13t.png diff --git a/docs/ref/contrib/admin/_images/list_filter.png b/docs/ref/contrib/admin/_images/list_filter.png Binary files differindex 0d912d1925..8b6a5a7a54 100644 --- a/docs/ref/contrib/admin/_images/list_filter.png +++ b/docs/ref/contrib/admin/_images/list_filter.png diff --git a/tests/admin_changelist/tests.py b/tests/admin_changelist/tests.py index e24654dfcb..24c24a670b 100644 --- a/tests/admin_changelist/tests.py +++ b/tests/admin_changelist/tests.py @@ -1810,3 +1810,55 @@ class SeleniumTests(AdminSeleniumTestCase): ) 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") + ) |
