Django’s admin interface is pretty much the best thing ever. It’s one of Django’s biggest selling points, and for good reason — it’s a blessing for developers and, erm, non-developers alike. I work part-time at William and Mary’s AidData branch, and our backend runs on Django. It’s perfect — gives our non-techy researchers enough flexibility and access to the data without dirtying their hands with SQL, and gives me and the other developers a great frontend to do spot updates and easy cascading.
The admin interface is incredibly flexible, too — you can pretty much customize any aspect of it. For instance, take these filters (basically clickable
These are auto-generated by the
Admin class for the given table you’re accessing, usually by just giving it a tuple of an attribute for each filter. So if we were a nationwide used car agency, a filter on a database of cars is as easy as:
list_filter = [‘state’, ‘make’, ‘model’]
Tada! Suddenly we have three filters, autopopulated with each possible state, make, or model in our database.
Creating custom filters
These filters, unfortunately, are verbose by default. The issue I ran in today came from the following email:
We’re tracking all citations in foreign aid journalism in
trip_citation. I’d love to be able to filter this on each publication being cited, but adding this in
list_filtersadds a list of twelve thousand books… when right now there are only 25 that have been cited. Any ideas?
The issue was that the field being filtered was a foreign key to a massive table, and filtering on that foreign key meant Django was grabbing all of the entries in that foreign key’s table instead of only the ones being referenced in
Thankfully, the solution — posted below — is pretty simple:
class CitedBooksFilter(SimpleListFilter): title = _('cited book') parameter_name = 'book_title' def lookups(self, request, model_admin): cited_books = set([c.cited_book for c in Citation.objects.all()]) return [(c.id, c.title) for c in cited_books] def queryset(self, request, queryset): if self.value(): return queryset.filter(cited_book__id__exact=self.value()) else: return queryset
Django supplies a
SimpleListFilter parent class — basically how all of the default filters work — and it’s up to us to overwrite four specific things:
- title — exactly what it sounds like. How you’d describe the filter.
- parameter_name — basically a less verbose title. This is what goes in the URL when you apply the filter.
- lookups — All of the possible values for the filter. In my above case, I iterated over
trip_citationand grabbed the distinct foreign key elements, thus giving me only the publications which were being cited.
- queryset — The other side of
lookups: how to filter a given queryset if the filter is being used. In the above case, it’s pretty simple: make sure the foreign key is equal. (Note: surrounding this with an if/else clause is incredibly important. All filters are evaluated: if an option isn’t clicked, that just means
self.value() == 0. If you don’t surround this with an if/else clause, you’ll be searching for entries that have a foreign key of zero, which would never end well.
And invoking the filter is crazy simple:
list_filter = (CitedBooksFilter)
Hope this helped! If you like programming in Django, you should follow me on Twitter.