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)
from django.db import models
from django.contrib.admin.filterspecs import FilterSpec, DateFieldFilterSpec
from django.utils.translation import ugettext as _
from datetime import datetime
class IsActiveFilterSpec(DateFieldFilterSpec):
"""
Adds filtering by future and previous values in the admin
filter sidebar. Set the is_active_filter filter in the model field attribute 'is_active_filter'.
my_model_field.is_active_filter = True
"""
def __init__(self, f, request, params, model, model_admin):
super(IsActiveFilterSpec, self).__init__(f, request, params, model, model_admin)
today = datetime.now()
self.links = (
(_('Any'), {}),
(_('Yes'), {'%s__gte' % self.field.name: str(today), }),
(_('No'), {'%s__lte' % self.field.name: str(today), }),
)
def title(self):
return "Active"
# Register the filter
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".
class Poll(models.Model):
start_date = models.DateTimeField('date published')
end_date = models.DateTimeField()
title = models.CharField(max_length = 100, default = 'Weekly Poll')
question = models.CharField(max_length = 200)
# Filter
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.
from polls.filterspecs import IsActiveFilterSpec
class PollAdmin(admin.ModelAdmin):
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.