For the majority of the time, the built in Django admin filters are sufficient. However, sometimes we need something a bit more customised which will cater to the behaviour of our models.
For example, I needed a filter which filters any active polls. However, the date of which the poll starts and ends depends on the current time and there is (at the time of writing) no filter for that.
I found it quite difficult to find a tutorial on this topic that covers all steps from A to B for a person who is doing this for the first time.
After a few attempts, I managed to get it working.
Firstly, create the filter in "yourapp/filterspecs.py". Because I needed a filter based on the expiry date of a model, it was based off the DateFieldFilterSpec (and Mark Ellul's example in the StackOverflow link)
01.
from
django.db
import
models
02.
from
django.contrib.admin.filterspecs
import
FilterSpec, DateFieldFilterSpec
03.
from
django.utils.translation
import
ugettext as _
04.
from
datetime
import
datetime
05.
06.
class
IsActiveFilterSpec(DateFieldFilterSpec):
07.
"""
08.
Adds filtering by future and previous values in the admin
09.
filter sidebar. Set the is_active_filter filter in the model field attribute 'is_active_filter'.
10.
11.
my_model_field.is_active_filter = True
12.
"""
13.
14.
def
__init__(
self
, f, request, params, model, model_admin):
15.
super(IsActiveFilterSpec,
self
).__init__(f, request, params, model, model_admin)
16.
today
=
datetime.now()
17.
self
.links
=
(
18.
(_(
'Any'
), {}),
19.
(_(
'Yes'
), {
'%s__gte'
%
self
.field.name: str(today), }),
20.
(_(
'No'
), {
'%s__lte'
%
self
.field.name: str(today), }),
21.
)
22.
23.
def
title(
self
):
24.
return
"Active"
25.
26.
# Register the filter
27.
FilterSpec.filter_specs.insert(
0
, (
lambda
f: getattr(f,
'is_active_filter'
,
False
), IsActiveFilterSpec)
This creates a filter which will check a field for the "is_active_filter" attribute. If found, it'll be compatible with the new filter.
Now to enable the filter on our field. Edit "yourapp/models.py".
1.
class
Poll(models.Model):
2.
start_date
=
models.DateTimeField(
'date published'
)
3.
end_date
=
models.DateTimeField()
4.
title
=
models.CharField(max_length
=
100
, default
=
'Weekly Poll'
)
5.
question
=
models.CharField(max_length
=
200
)
6.
7.
# Filter
8.
end_date.is_active_filter
=
True
By adding the "is_active_filter" attribute to "end_date", our new filter will see that we want to use it and present itself if needed.
Lastly, the bit I found least information on, you'll need to edit "yourapp/admin.py" to load the filter and display the field on the filters panel.
1.
from
polls.filterspecs
import
IsActiveFilterSpec
2.
3.
class
PollAdmin(admin.ModelAdmin):
4.
list_filter
=
(
'end_date'
, )
For me, the magic line here was the import statement. Prior to this, no other tutorial implicitly mentioned that it needed to be loaded. Without that magic line, the filter would not be registered and would display the normal date filter.
If all is done correctly, the end result is this!
Sources
The most useful sites I found on this topic.