Primary image for Custom Filters in the Django Admin

Custom Filters in the Django Admin

A few weeks ago Django’s team revealed a data leakage bug in the admin application that affects an extremely interesting and undocumented feature. A user that has access to a change_list page of an object in the admin interface can filter this list by adding some parameters in the URL. Django will parse them and filter the queryset using the given criteria. Before this security fix you could use this feature out of the box and there was no control over the criteria that were passed by the user. Information outside of an admin user’s permission could leak.

In order to avoid this a new method called lookup_allowed has been added to the ModelAdmin. By default this method restricts the lookup to what is declared in list_filter or date_hierarchy. As with most things in Django, you can easily override this function in the subclass of ModelAdmin to allow for additional lookups. For the purpose of this blog post, we’ll leave that as an exercise for the reader, but see this blog post by Chris Adams for more details.

The Basics

Let’s assume we have an app in our project called newsroom that is used to manage published content. A peek into models.py shows us:

class Article(models.Model):
teaser = models.CharField(max_length=200)
source = models.CharField(max_length=5)
section = models.ForeignKey("Section")
categories = models.ForeignKey("Category")
update_date = models.DateTimeField()
is_published = models.BooleanField(default=False)

class Section(models.Model):
name = models.CharField(max_length=50)
slug = models.SlugField()

class Category(models.Model):
name = models.CharField(max_length=100)
slug = models.SlugField()
After hooking your models into the admin, you can filter them simply by appending a query string to the admin change list <span class="caps">URL</span>. For example:
/admin/newsroom/article/?teaser__isnull=False
You can even chain filters, using the & between each one:
/admin/newsroom/article/?teaser__isnull=False&source__exact=GR
This is not all you can also do join tables to narrow down your choices:
/admin/newsroom/article/?categories__slug__exact=politics§ion__slug__exact=news
datetime is also supported and there is very little suprised in the syntax:
/admin/newsroom/article/?updated_date__gt=2010-09-01
boolean fields are a bit more tricky because you need to actually pass an integer 0 (False) 1(True)
/admin/newsroom/article/?is_published__exact=1
These are just a few examples, but you can create any number of complex filters by following Django’s documentation on database queries.

Admin integration

Being able to dynamically craft the URLs to build the query is useful but limited to people that are “in the know.” While working on this article, Martin brought to my attention that these links can easily be integrated into the django.contrib.admin interface by overriding the admin template.

The change_list.html template has its own block:

{% block filters %} {% if cl.has_filters %}

{% trans ‘Filter’ %}

{% for spec in cl.filter_specs }{ admin_list_filter cl spec }{ endfor %}
{% endif %} {% endblock %}

Due to the current structure of the admin templates, you’ll need to maintain some boiler-plate code1. You can minimize that, however, thanks to block.super.

{% block filters %}
{{ block.super }}
<div id="changelist-filter">
<h2>{% trans 'Custom Filters' %}</h2>
<!-- list of filter links -->
</div>
{% endblock %}
See the Django documentation about overriding admin templates for more information. Note: In order to avoid hard-coding the URL you can reverse admin URLs
from django.core.urlresolver import reverse
reverse('admin:%s_%s_changelist' % ('newsroom', 'article'))
The snippet above will return something like : /admin/newsroom/article/ If you want to use this in a template you can take advantage of the {% url %} tag.

Conclusion

Even though the Django admin does not currently provide a nice UI for adding custom filters, you can still take advantage of this hidden gem. They provide your users with easily bookmarked links to the information they care about. You could also use this technique to create a dashboard or reports page.

1 Ticket #5833 aims to make this process even easier in the future

Yann Malet

About the author

Yann Malet

Yann builds and architects performant digital platforms for publishers. In 2015, Yann co-authored High-Performance Django with Peter Baumgartner. Prior to his involvement with Lincoln Loop, Yann focused on Product Lifecycle Management systems (PLM) for several large …

View Yann's profile