diff --git a/django/contrib/admin/static/admin/css/changelists.css b/django/contrib/admin/static/admin/css/changelists.css
index 30a6386a31..3eefd00fb1 100644
--- a/django/contrib/admin/static/admin/css/changelists.css
+++ b/django/contrib/admin/static/admin/css/changelists.css
@@ -187,6 +187,12 @@
color: #036;
}
+#changelist-filter #changelist-filter-clear a {
+ font-size: 13px;
+ padding-bottom: 10px;
+ border-bottom: 1px solid #eaeaea;
+}
+
/* DATE DRILLDOWN */
.change-list ul.toplinks {
diff --git a/django/contrib/admin/templates/admin/change_list.html b/django/contrib/admin/templates/admin/change_list.html
index 8b275362af..4dc9ed52d7 100644
--- a/django/contrib/admin/templates/admin/change_list.html
+++ b/django/contrib/admin/templates/admin/change_list.html
@@ -60,6 +60,11 @@
{% if cl.has_filters %}
{% translate 'Filter' %}
+ {% if cl.has_filters or cl.search_fields %}
+ {% if cl.preserved_filters %}
{% endif %}
+ {% endif %}
{% for spec in cl.filter_specs %}{% admin_list_filter cl spec %}{% endfor %}
{% endif %}
diff --git a/docs/releases/3.1.txt b/docs/releases/3.1.txt
index ced98c2e47..709c3917bc 100644
--- a/docs/releases/3.1.txt
+++ b/docs/releases/3.1.txt
@@ -37,6 +37,9 @@ Minor features
:attr:`.ModelAdmin.list_filter` allows filtering on empty values (empty
strings and nulls) in the admin changelist view.
+* Filters in the right sidebar of the admin changelist view now contains a link
+ to clear all filters.
+
:mod:`django.contrib.admindocs`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/tests/admin_changelist/tests.py b/tests/admin_changelist/tests.py
index 8cb6f7eff9..933d2ac1d2 100644
--- a/tests/admin_changelist/tests.py
+++ b/tests/admin_changelist/tests.py
@@ -707,6 +707,18 @@ class ChangeListTests(TestCase):
link = reverse('admin:admin_changelist_parent_change', args=(p.pk,))
self.assertNotContains(response, '' % link)
+ def test_clear_all_filters_link(self):
+ self.client.force_login(self.superuser)
+ link = '✖ Clear all filters'
+ response = self.client.get(reverse('admin:auth_user_changelist'))
+ self.assertNotContains(response, link)
+ for data in (
+ {SEARCH_VAR: 'test'},
+ {'is_staff__exact': '0'},
+ ):
+ response = self.client.get(reverse('admin:auth_user_changelist'), data=data)
+ self.assertContains(response, link)
+
def test_tuple_list_display(self):
swallow = Swallow.objects.create(origin='Africa', load='12.34', speed='22.2')
swallow2 = Swallow.objects.create(origin='Africa', load='12.34', speed='22.2')