#36982: Docs for `ModelAdmin.list_filter` don't mention bare `ListFilter`
subclasses as a valid element type
------------------------+-----------------------------------------
Reporter: youtux | Type: Bug
Status: new | Component: Documentation
Version: 6.0 | Severity: Normal
Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
------------------------+-----------------------------------------
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:
1. A field name
2. A subclass of `django.contrib.admin.SimpleListFilter`
3. 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`"
--
Ticket URL: <
https://code.djangoproject.com/ticket/36982>
Django <
https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.