Django pagination

5 min. read

You may know that Django comes with a paginator out of the box. It's very fancy and it has a lot of cool methods.

I have seen that people use the paginator in the view as indicated in the docs, i.e. importing PageNotAnInteger and EmptyPage. They always go in the try block, and when you are done you return the pages to the template.

From the docs example:


from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.shortcuts import render


def listing(request):
    contact_list = Contacts.objects.all()
    paginator = Paginator(contact_list, 25) # Show 25 contacts per page

    page = request.GET.get('page')
    try:
        contacts = paginator.page(page)
    except PageNotAnInteger:
        # If page is not an integer, deliver first page.
        contacts = paginator.page(1)
    except EmptyPage:
        # If page is out of range (e.g. 9999), deliver last page of results.
        contacts = paginator.page(paginator.num_pages)

    return render(request, 'list.html', {'contacts': contacts})

This is a lot of logic to go in a view, so I was wondering if I could separate it into service objects so I can unit test it more easily. Just like people do in Rails.

So I created a small Django app to play with the Paginator. It's called Team Directory, and is basically a paginated list of people.

My thought process behind this was that if this code was in the view, then in tests I would have to read objects.all() from the database. But because the queryset behaves like an array, you can put this code in a separate class and unit test it separately with an array and SimpleTestCase, which doesn't hit the database. Then in the integration tests you can test the wiring of it all and hit the database.

I thought of using a class-based approach and inherit from ListView, to play with it as well (the app is very simple, I don't need it really) but I found that if I did it this way, the tests hit the database as well. So I will investigate that in another occasion.

The paginator gives you a page object. In the template, since you first loop into the items for that page, it can be confusing to call it contacts. Or at least it is confusing to me! So I called it current_page.

Then I added the pagination links. Here in particular is when it makes sense to have called it current_page, because of the methods has_next_page() etc. Imagine the sound of contacts.has_next_page()... nope.

I used other two projects for this:

  • In order to fill the database I found this awesome open-source project called http://www.generatedata.com, that allows you to generate stupid data to fill a database. It's ideal for pojects like this!
  • Another project that I like is https://placekitten.com/, which does the same with images, providing a placeholder kitten for your prototypes.

a kitten!

As a final thing to practice, I may check how to put this in Heroku, since they recently added support for Django apps. I'll come back and update this if I find the time to play with Heroku.

Comments