Django Dynamic Queryset Filtering With Pagination
This blog article does a very good job at explaining how to setup dynamic queryset filtering in Django. However, it doesn't go into paginating the filtered queryset results.
I had a very hard time trying to implement this pagination feature using class based views. More specifically, using the FilterView
that comes with the django-filter package.
In the end it turned out to be very simple. The documentation was a bit misleading on this so it took a while to understand and find the solution.
Enabling Pagination
We can enable pagination by adding a paginate_by
attribute in a FilterView
, just like when we do so in a Django ListView
:
class CanalAdvancedSearch(FilterView):
filterset_class = BookFilter
template_name = 'advanced_search.html'
paginate_by = 10
This is because FilterView
inherits from a BaseFilterView
, which in turn inherits from Django's MultipleObjectMixin
which allows pagination capabilities. See source code
The trick though is that in the template we no longer iterate over form.qs
as specified in the documentation, as this will make the template show the whole queryset.
Instead, we must iterate over object_list
in the template, like so:
{% for book in object_list %}
<h4>{{ book.title }}</h4>
{% empty %}
<p>No results!</p>
{% endfor %}
{% include '_partials/_pagination.html' %}
And we can include the typical pagination partial at the end. For completeness, here is a snippet of that:
{% if is_paginated %}
<div class="row">
<div class="col-md-12">
<div>
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}"><span class="pg-arrow_left"></span></a>
{% endif %}
<span class="small">Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}"><span class="pg-arrow_right"></span></a>
{% endif %}
</div>
</div>
</div>
{% endif %}
With this we are now able to show the filtered results paginated nicely.
Filter Form Widgets
If we wanted to show each field in the template separately instead of using {{ filter.form.as_p }}
, so that we can add CSS classes and layouting to the form, we can specify a widget to each filter inside the FilterSet
object:
class BookFilter(django_filters.FilterSet):
title = django_filters.CharFilter(
lookup_expr='icontains',
widget=forms.TextInput(attrs={'class': 'form-control'})
)
In the template we can then show it like this:
<div class="form-group">
<label>{{ filter.form.title.label_tag }}</label>
{{ filter.form.title }}
</div>