This website is made possible by displaying online advertisements to our visitors.
Please consider supporting us by disabling your ad blocker. Thank you for your support.
This website is made possible by displaying online advertisements to our visitors.
Please consider supporting us by disabling your ad blocker.

Django Pagination With Class Based View

June 7 2021 Yacine Rouizi
Django Class Based View Pagination
Django Pagination With Class Based View

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

Simple Pagination

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:

404 page with edge cases

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.

Subclassing the Paginator Class

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

Page below 1

Page above page limit

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.

Conclusion

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

Leave a comment

(Your email address will not be published)