In the previous post we talked about pagination with function based views, In this one we will see how to use pagination with class based views. We will be using the generic view ListView.
I am going to use the project that I used in the previous post, so you can download it from this link or run the command below to clone the repository:
git clone https://github.com/Rouizi/django-pagination-with-fbc
In the previous tutorial we used page as an extra argument for the view, while this time we are going to pass the page parameter to the class via the GET parameter. Passing the page parameter as an extra argument is still feasible though.
To enable pagination in a class based view we just need to add the paginate_by attribute. Our class will be as simple as:
core/views.py
# core/views.py
from django.contrib.auth.models import User
from django.core.paginator import Paginator, EmptyPage
from django.views.generic import ListView
class ListUsers(ListView):
model = User
template_name = 'home.html'
paginate_by = 5
app/urls.py
# app/urls.py
from django.contrib import admin
from django.urls import path
from core.views import ListUsers
urlpatterns = [
path('admin/', admin.site.urls),
path('list_users/', ListUsers.as_view(), name='list_users')
]
In the template, we have some context variables that were created: paginator, page_obj, is_paginated, and object_list
templates/home.html
<!-- templates/home.html -->
<div class='container'>
<div class='mt-5 pt-5 ml-5 pl-5'>
<p><strong>Username:</strong></p>
{% for user in object_list %}
<p>{{ user.username }}</p>
{% endfor %}
</div>
<!-- Pagination -->
<div class='pagination justify-content-center'>
{% if page_obj.has_previous %}
<a href='{% url "list_users" %}?page={{ page_obj.previous_page_number }}'>Previous </a>
{% endif %}
<span class='mx-4'>
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
</span>
{% if page_obj.has_next %}
<a href='{% url "list_users" %}?page={{ page_obj.next_page_number }}' > Next</a>
{% endif %}
</div>
<!-- END Pagination -->
</div>
And we are done!
But this simple implementation does not handle the edge cases. For example, below you can see that we get a 404 page not found if we exceed the page limit:
To handle the edge cases (page number below 1 or above the maxim number of pages) we need to subclass the Paginator class and override its validate_number method. Let's see how to do it in the next section.
Let's say that we want to return the first page if we get a value below 1 for the page parameter and return the last page if we get a value above the maximum number of pages for the page parameter (ex: page=9999).
To do that we need to subclass the Paginator class and catch the EmptyPage exception.
# core/views.py
# ...
class MyPaginator(Paginator):
def validate_number(self, number):
try:
return super().validate_number(number)
except EmptyPage:
if int(number) > 1:
# return the last page
return self.num_pages
elif int(number) < 1:
# return the first page
return 1
else:
raise
class ListUsers(ListView):
model = User
template_name = 'home.html'
paginate_by = 5
paginator_class = MyPaginator # We use our paginator class
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
page = self.request.GET.get('page', 1)
users = User.objects.all()
paginator = self.paginator_class(users, self.paginate_by)
users = paginator.page(page)
context['users'] = users
return context
I could be wrong, but from what I tested we can't catch the PageNotAnInteger exception by overriding the validate_number method. I think we need to override the paginate_queryset method of the ListView class for this. Honestly, I tried but couldn't get it to work.
Pagination with class based view is easier than with function based view, but it can be a bit complicated when we want to handle the edge cases.
With this tutorial as well as the previous one, we have seen most of the cases of pagination. If you ever think that there are other cases to be treated, please let me know.
As always, you can find the final code on my GitHub repository at: https://github.com/Rouizi/django-pagination-with-cbv