Opened 4 hours ago
Last modified 4 hours ago
#36982 new Bug
Docs for `ModelAdmin.list_filter` don't mention bare `ListFilter` subclasses as a valid element type — at Initial Version
| Reported by: | Alessio Bogon | Owned by: | |
|---|---|---|---|
| Component: | Documentation | Version: | 6.0 |
| Severity: | Normal | Keywords: | |
| Cc: | Triage Stage: | Unreviewed | |
| Has patch: | no | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
The [ModelAdmin List Filters documentation](https://docs.djangoproject.com/en/6.0/ref/contrib/admin/filters/) states that list_filter accepts three types of elements:
- A field name
- A subclass of
django.contrib.admin.SimpleListFilter - A 2-tuple containing a field name and a subclass of
django.contrib.admin.FieldListFilter
However, the actual implementation in ChangeList.get_filters() ([source](https://github.com/django/django/blob/3180ddb3f532ef246d318d64225886b7c0593676/django/contrib/admin/views/main.py#L188-L190)) accepts any callable — including bare ListFilter subclasses — and instantiates them with (request, lookup_params, self.model, self.model_admin):
`python
if callable(list_filter):
# This is simply a custom list filter class.
spec = list_filter(request, lookup_params, self.model, self.model_admin)
`
The entire admin filter rendering pipeline (ChangeList.get_filters(), ChangeList.get_queryset(), and the admin_list_filter template tag) only uses methods defined on ListFilter itself: has_output(), expected_parameters(), queryset(), choices(), title, and template. Nothing requires SimpleListFilter specifically.
This means a direct ListFilter subclass (not going through SimpleListFilter) works perfectly fine:
`python
class MyFilter(admin.ListFilter):
title = "My Filter"
parameter_name = "my_param"
template = "admin/my_filter.html"
def init(self, request, params, model, model_admin):
super().init(request, params, model, model_admin)
params.pop(self.parameter_name, None)
def has_output(self):
return True
def expected_parameters(self):
return [self.parameter_name]
def queryset(self, request, queryset):
return queryset
def choices(self, changelist):
yield {"selected": True, "display": "All"}
class MyAdmin(admin.ModelAdmin):
list_filter = [MyFilter]
`
This is useful when you need full control over the filter (custom template, multi-value parameters, etc.) without the constraints of SimpleListFilter's lookups()/value() API or FieldListFilter's field introspection.
The docs should generalise point 2 to say "A subclass of django.contrib.admin.ListFilter" instead of "A subclass of django.contrib.admin.SimpleListFilter"