How can I change the default filter choice from \'ALL\'? I have a field named as status
which has three values: activate
, pending
and
Here's the Cleanest version I was able to generate of a filter with a redefined 'All' and a Default value that is selected.
If shows me by default the Trips currently happening.
class HappeningTripFilter(admin.SimpleListFilter):
"""
Filter the Trips Happening in the Past, Future or now.
"""
default_value = 'now'
title = 'Happening'
parameter_name = 'happening'
def lookups(self, request, model_admin):
"""
List the Choices available for this filter.
"""
return (
('all', 'All'),
('future', 'Not yet started'),
('now', 'Happening now'),
('past', 'Already finished'),
)
def choices(self, changelist):
"""
Overwrite this method to prevent the default "All".
"""
value = self.value() or self.default_value
for lookup, title in self.lookup_choices:
yield {
'selected': value == force_text(lookup),
'query_string': changelist.get_query_string({
self.parameter_name: lookup,
}, []),
'display': title,
}
def queryset(self, request, queryset):
"""
Returns the Queryset depending on the Choice.
"""
value = self.value() or self.default_value
now = timezone.now()
if value == 'future':
return queryset.filter(start_date_time__gt=now)
if value == 'now':
return queryset.filter(start_date_time__lte=now, end_date_time__gte=now)
if value == 'past':
return queryset.filter(end_date_time__lt=now)
return queryset.all()
Created a reusable Filter sub-class, inspired by some of the answers here (mostly Greg's).
Reusable - Pluggable in any standard ModelAdmin
classes
Extendable - Easy to add additional/custom logic for QuerySet
filtering
Easy to use - In its most basic form, only one custom attribute and one custom method need to be implemented (apart from those required for SimpleListFilter subclassing)
Intuitive admin - The "All" filter link is working as expected; as are all the others
No redirects - No need to inspect GET
request payload, agnostic of HTTP_REFERER
(or any other request related stuff, in its basic form)
No (changelist) view manipulation - And no template manipulations (god forbid)
(most of the import
s are just for type hints and exceptions)
from typing import List, Tuple, Any
from django.contrib.admin.filters import SimpleListFilter
from django.contrib.admin.options import IncorrectLookupParameters
from django.contrib.admin.views.main import ChangeList
from django.db.models.query import QuerySet
from django.utils.encoding import force_str
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError
class PreFilteredListFilter(SimpleListFilter):
# Either set this or override .get_default_value()
default_value = None
no_filter_value = 'all'
no_filter_name = _("All")
# Human-readable title which will be displayed in the
# right admin sidebar just above the filter options.
title = None
# Parameter for the filter that will be used in the URL query.
parameter_name = None
def get_default_value(self):
if self.default_value is not None:
return self.default_value
raise NotImplementedError(
'Either the .default_value attribute needs to be set or '
'the .get_default_value() method must be overridden to '
'return a URL query argument for parameter_name.'
)
def get_lookups(self) -> List[Tuple[Any, str]]:
"""
Returns a list of tuples. The first element in each
tuple is the coded value for the option that will
appear in the URL query. The second element is the
human-readable name for the option that will appear
in the right sidebar.
"""
raise NotImplementedError(
'The .get_lookups() method must be overridden to '
'return a list of tuples (value, verbose value).'
)
# Overriding parent class:
def lookups(self, request, model_admin) -> List[Tuple[Any, str]]:
return [(self.no_filter_value, self.no_filter_name)] + self.get_lookups()
# Overriding parent class:
def queryset(self, request, queryset: QuerySet) -> QuerySet:
"""
Returns the filtered queryset based on the value
provided in the query string and retrievable via
`self.value()`.
"""
if self.value() is None:
return self.get_default_queryset(queryset)
if self.value() == self.no_filter_value:
return queryset.all()
return self.get_filtered_queryset(queryset)
def get_default_queryset(self, queryset: QuerySet) -> QuerySet:
return queryset.filter(**{self.parameter_name: self.get_default_value()})
def get_filtered_queryset(self, queryset: QuerySet) -> QuerySet:
try:
return queryset.filter(**self.used_parameters)
except (ValueError, ValidationError) as e:
# Fields may raise a ValueError or ValidationError when converting
# the parameters to the correct type.
raise IncorrectLookupParameters(e)
# Overriding parent class:
def choices(self, changelist: ChangeList):
"""
Overridden to prevent the default "All".
"""
value = self.value() or force_str(self.get_default_value())
for lookup, title in self.lookup_choices:
yield {
'selected': value == force_str(lookup),
'query_string': changelist.get_query_string({self.parameter_name: lookup}),
'display': title,
}
from django.contrib import admin
from .models import SomeModelWithStatus
class StatusFilter(PreFilteredListFilter):
default_value = SomeModelWithStatus.Status.FOO
title = _('Status')
parameter_name = 'status'
def get_lookups(self):
return SomeModelWithStatus.Status.choices
@admin.register(SomeModelWithStatus)
class SomeModelAdmin(admin.ModelAdmin):
list_filter = (StatusFilter, )
Hope this helps somebody; feedback always appreciated.
I know this question is quite old now, but it's still valid. I believe this is the most correct way of doing this. It's essentially the same as Greg's method, but formulated as an extendible class for easy re-use.
from django.contrib.admin import SimpleListFilter
from django.utils.encoding import force_text
from django.utils.translation import ugettext as _
class DefaultListFilter(SimpleListFilter):
all_value = '_all'
def default_value(self):
raise NotImplementedError()
def queryset(self, request, queryset):
if self.parameter_name in request.GET and request.GET[self.parameter_name] == self.all_value:
return queryset
if self.parameter_name in request.GET:
return queryset.filter(**{self.parameter_name:request.GET[self.parameter_name]})
return queryset.filter(**{self.parameter_name:self.default_value()})
def choices(self, cl):
yield {
'selected': self.value() == self.all_value,
'query_string': cl.get_query_string({self.parameter_name: self.all_value}, []),
'display': _('All'),
}
for lookup, title in self.lookup_choices:
yield {
'selected': self.value() == force_text(lookup) or (self.value() == None and force_text(self.default_value()) == force_text(lookup)),
'query_string': cl.get_query_string({
self.parameter_name: lookup,
}, []),
'display': title,
}
class StatusFilter(DefaultListFilter):
title = _('Status ')
parameter_name = 'status__exact'
def lookups(self, request, model_admin):
return ((0,'activate'), (1,'pending'), (2,'rejected'))
def default_value(self):
return 1
class MyModelAdmin(admin.ModelAdmin):
list_filter = (StatusFilter,)