Advanced, modern filtering for Django admin changelists, with saved filters and selectable columns.
- Search-bar style filter UI injected into Django admin changelists
- Type-aware filtering for text, numeric, boolean, choice, date, datetime and relation fields
- Field path traversal such as
author__email - Saved filters per authenticated user
- Column selection and ordering persisted with saved filters
- AJAX-backed relation picker using Django admin Select2 assets
- Support for custom filter fields via
SuperFilterField - Single JSON query parameter for rules (
sf) and one for visible columns (sfc) - Works without templates overrides
- Python 3.10+
- Django 4.0+
Install the package:
pip install django-admin-superfilterAdd the app to INSTALLED_APPS:
INSTALLED_APPS = [
# ...
"superfilter",
]Run migrations:
python manage.py migratefrom django.contrib import admin
from superfilter.admin import SuperFilterAdminMixin
from .models import Bird
@admin.register(Bird)
class BirdAdmin(SuperFilterAdminMixin, admin.ModelAdmin):
list_display = ("species", "location", "count")
search_fields = ("species",)Important:
- Put
SuperFilterAdminMixinbeforeadmin.ModelAdminin the MRO. - By default, filterable fields come from
list_display. - Non-model entries in
list_displayare ignored. - Traversed model fields like
location__city__nameare supported.
The package adds a search-bar-like control above the changelist with:
- an add-filter button
- filter badges for current rules
- an Apply button
- a split menu with Save
- a Reset button
- a Columns toggle button
- a collapsible column chooser
- saved filter chips
Saved filters store both:
- the active filter rules
- the selected column list and order
setnot_set
Text-like fields include CharField, TextField, EmailField, SlugField, URLField, UUIDField.
Operators:
setnot_seteqneqcontainsnot_containsinnot_in
Operators:
setnot_seteqneqgtltgtelte
Operators:
setnot_settruefalse
Operators:
setnot_setinnot_in
Operators:
setnot_setinnot_in
Operators:
setnot_seteqbeforeafterbetween
setmeans the field is considered populatednot_setmeans the field is empty- For text fields, empty means
NULLor empty string - For boolean fields,
set/not_setonly targetNULLvs non-NULL containsusesicontainsnot_containsnegatesicontainsbetweenon date/datetime expects exactly two values- Relation filters use selected related object primary keys
SuperFilterAdminMixin exposes a few attributes:
superfilter_param_name = "sf"superfilter_columns_param_name = "sfc"superfilter_fields = Nonesuperfilter_page_size = 25superfilter_all_limit = 2000
Example:
class BirdAdmin(SuperFilterAdminMixin, admin.ModelAdmin):
list_display = ("species", "location", "count")
superfilter_page_size = 50
superfilter_all_limit = 5000By default, filterable fields are taken from list_display.
To expose a different set of fields, use superfilter_fields:
class BirdAdmin(SuperFilterAdminMixin, admin.ModelAdmin):
list_display = ("species", "location", "count")
superfilter_fields = ("species", "count", "location__city")This only affects filterable fields. Column selection still uses list_display.
You can plug in custom fields that do not map directly to a Django model field.
Create a subclass of SuperFilterField:
from django.db.models import Q
from superfilter.logic import SuperFilterField
class HasLargeCountField(SuperFilterField):
path = "has_large_count"
label = "Large count"
kind = "choice"
choices = [
{"value": "yes", "label": "Yes"},
{"value": "no", "label": "No"},
]
def apply_rule(self, queryset, rule):
values = set(rule.get("value") or [])
if "yes" in values and "no" not in values:
return queryset.filter(count__gte=100)
if "no" in values and "yes" not in values:
return queryset.filter(count__lt=100)
return querysetRegister it in superfilter_fields:
class BirdAdmin(SuperFilterAdminMixin, admin.ModelAdmin):
list_display = ("species", "location", "count")
superfilter_fields = ("species", HasLargeCountField)Saved filters are stored in the SavedSuperFilter model and are scoped by:
- user
- app label
- model name
- saved filter name
Notes:
- saving requires an authenticated user
- saved filters are private to the user
Rules are sent in the sf query parameter as JSON:
[
{"field": "species", "op": "contains", "value": "owl"},
{"field": "location", "op": "in", "value": [1, 2]},
{"field": "count", "op": "gte", "value": 10}
]Columns are sent in the sfc query parameter as JSON:
["location", "species", "count"]For relation filters, the package exposes an admin endpoint that:
- reuses the related admin's
get_search_results()when available - otherwise falls back to searching up to three text fields
- otherwise falls back to PK search for numeric terms
The mixin injects:
admin/css/vendor/select2/select2.min.cssadmin/js/vendor/select2/select2.full.min.jssuperfilter/superfilter.csssuperfilter/superfilter.js
No custom template override is required.
- Filtering ignores callable/computed
list_displayentries unless implemented asSuperFilterField - Column selection only works on entries present in
list_display - The package ships with admin-focused frontend assets and is not intended for non-admin pages
A runnable sample project is available in:
examples/sampleapp/
Run it with:
cd examples/sampleapp
python manage.py migrate
python manage.py runserverRun tests from the repository root:
python manage.py test superfilterOr use the sample project:
cd examples/sampleapp
python manage.py testMIT. See LICENCE.md.