Photo by Kevin Ku on Unsplash

Django: Using django-filter plugin with CBVs

Adrien Van Thong

--

Django-filter is an extremely powerful plugin for Django which extends the framework by providing a common set of APIs for easily setting up and managing user-facing filters on models. This plugin provides an immense amount of new functionality, all while avoid further boilerplate code for developers.

This plugin is so incredibly useful that the very popular Django Rest Framework plugin is built on top of the django-filter plugin for helping build out its REST API views.

Unfortunately, the django-filters documentation, while extremely concise and well-written, does not provide an example of how to integrate it with class based views. In this article, I will remedy this by providing one such example.

Our end goal in this article is to utilize the django-filter plugin to very quickly write a Django app which lets users browse a library’s available books and filter based on author, genre, rating, and other fields. Of course, all of this will be accomplished only using class based views.

Sample Models

Below are the Django models we’ll use for our example.

class Author(models.Model):
first_name = models.CharField(max_length=64)
last_name = models.CharField(max_length=64)

class Category(models.Model):
name = models.CharField(max_length=64)

class Book(models.Model):
RATINGS = [
(1, "Trash"),
(2, "C-tier"),
(3, "B-tier"),
(4, "A-tier"),
(5, "S-tier"),
]
title = models.CharField(max_length=128)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.CASCADE)
librarian_rating = models.IntegerField(choices=RATINGS, help_text="Our local librarian's rating of this book.")

These models are meant to represent a collection of books found in a library.

Next, let’s construct the django-filter class for this model, adapting from the examples already provided by the excellent django-filter documentation.

from django_filters.filterset import FilterSet
from .models import Book


class BookFilter(FilterSet):
class Meta:
model = Book
fields = ['author', 'category', 'librarian_rating']

As with any django-filter FilterSet classes, this is fully customizable to your desires. Consult the django-filter docs for all the customization options available. For now, we’ll move on to connecting this to our CBV.

Connecting the Filter to the CBV

Our filter-less class based view for our Book and Author classes appears below. This CBV provides an easy way for our end users to browse every book, all at once, without any filtering:

from django.views.generic import ListView
from .models import Book

class BookListView(ListView):
template_name = 'books.html'
model = Book
context_object_name = 'books'

Now, if we wanted to provide our users with the ability to filter on these books, we’ll need to leverage the filter class we defined above, and connect it to this CBV.

To do this, we’ll leverage the FilterView mixin that comes bundled with the django_filters plugin. This involves two simple steps:

  1. Switching our class inheritance from ListView to FilterView in the class definition.
  2. Setting the filterset_class field in the class and point it to the BookFilter class we created earlier.
from django_filters.views import FilterView
from .filters import BookFilter
from .models import Book

class BookListView(FilterView):
template_name = 'books.html'
model = Book
context_object_name = 'books'
filterset_class = BookFilter

This updated FilterView comes with a new filter variable which sends the entire FilterSet object to the template. Inside this object, is the FilterSet.form which is a standard Django form object, pre-populated with all the fields necessary for the user to manipulate the filter settings. We can interact with it on the template side as we would any normal form:

{% extends 'base.html' %}
{% block title %}Books{% endblock %}

{% block content %}
<h1>Books</h1>
<div class="row my-5 mx-3">
<div class="col">
<div class="card">
<form method="get" action="">
<h5 class="card-header">Filters</h5>
<div class="card-body">
{{ filter.form.as_p }}
</div>
<div class="card-footer text-center">
<input class="btn btn-secondary" type="submit" value="Filter" />
</div>
</form>
</div>
</div>
</div>

<div class="row">
<div class="col">
<table class="table table-striped table-hover">
<thead>
<tr>
<th scope="col">Title</th>
<th scope="col">Author</th>
<th scope="col">Category</th>
<th scope="col">Rating</th>
</tr>
</thead>
<tbody>
{% for book in books %}
<tr>
<td>{{ book.title }}</td>
<td>{{ book.author }}</td>
<td>{{ book.category }}</td>
<td>{{ book.get_librarian_rating_display }}</td>
</tr>
{% empty %}
<tr><td colspan="6"><i>No books in this filter.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}

And voila! It’s that simple! Thanks to the power of Django and django-filter, we now have a fully-functional filter form for the user, and we did this by writing very little extra code.

What the user will see in their browser.
Fully functional filtering capability for the users

Resources

--

--