Adding Pagination With Django

While working on a modern web application quite often you will need to paginate the app be it for better user experience or performance. Fortunately, Django comes with built-in pagination classes for managing paginating data of your application.

In this article, we will go through the pagination process with class-based views and function based views in Django.

Prerequisite

For the sake of this tutorial I am using a blog application  – Github repo

The above project is made on Python 3.7, Django 2.1 and Bootstrap 4.3. This is a very basic blog application displaying a list of posts on the homepage but when the number of posts increases we need to split them up.

Recommended Article:  Building A Blog Application With Django

Adding Pagination Using Class-Based-Views [ ListView ]

The Django ListView class comes with built-in support for pagination so all we need to do is take advantage of it. Pagination is controlled by the GET parameter that controls which page to show.

First, open the views.py file of your app.

from django.views import generic
from .models import Post


class PostList(generic.ListView):
    queryset = Post.objects.filter(status=1).order_by('-created_on')
    template_name = 'index.html'

class PostDetail(generic.DetailView):
    model = Post
    template_name = 'post_detail.html'

Now in the PostList view we will introduce a new attribute paginate_by which takes an integer specifying how many objects should be displayed per page. If this is given, the view will paginate objects with paginate_by objects per page. The view will expect either a page query string parameter (via request.GET) or a page variable specified in the URLconf.

class PostList(generic.ListView):
    queryset = Post.objects.filter(status=1).order_by('-created_on')
    template_name = 'index.html'
    paginate_by = 3

Now our posts are paginated by 3 posts a page.

Next, to see the pagination in action, we need to edit the template which for this application is the index.html file paste the below snippet.

{% if is_paginated %}
  <nav aria-label="Page navigation conatiner"></nav>
  <ul class="pagination justify-content-center">
    {% if page_obj.has_previous %}
    <li><a href="?page={{ page_obj.previous_page_number }}" class="page-link">&laquo; PREV </a></li>
    {% endif %}
    {% if page_obj.has_next %}
    <li><a href="?page={{ page_obj.next_page_number }}" class="page-link"> NEXT &raquo;</a></li>

    {% endif %}
  </ul>
  </nav>
</div>
{% endif %}

Note that we are using Bootstrap 4.3 for this project, if you are using any other frontend framework you may change the classes.

Now run the server and visit http://127.0.0.1:8000/ you should see the page navigation buttons below the posts.

Adding pagination with class based views in django

 

Adding Pagination Using Function-Based-Views

Equivalent function-based view for the above PosList class would be.

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

def PostList(request):
    object_list = Post.objects.filter(status=1).order_by('-created_on')
    paginator = Paginator(object_list, 3)  # 3 posts in each page
    page = request.GET.get('page')
    try:
        post_list = paginator.page(page)
    except PageNotAnInteger:
            # If page is not an integer deliver the first page
        post_list = paginator.page(1)
    except EmptyPage:
        # If page is out of range deliver last page of results
        post_list = paginator.page(paginator.num_pages)
    return render(request,
                  'index.html',
                  {'page': page,
                   'post_list': post_list})

So in the view, we instantiate the Paginator class with the number of objects to be displayed on each page i.e 3. Then we have the request.GET.get('page') parameter which returns the current page number. The page() method is used to obtain the objects from the desired page number. Below that we have two exception statements for PageNotAnInteger and EmptyPage both are subclasses of InvalidPage finally at the end we are rendering out the HTML file.

Now in your templates paste the below snippet.

{% if post_list.has_other_pages %}
  <nav aria-label="Page navigation conatiner"></nav>
  <ul class="pagination justify-content-center">
    {% if post_list.has_previous %}
    <li><a href="?page={{ post_list.previous_page_number }}" class="page-link">&laquo; PREV </a></li>
    {% endif %}
    {% if post_list.has_next %}
    <li><a href="?page={{ post_list.next_page_number }}" class="page-link"> NEXT &raquo;</a></li>
   {% endif %}
  </ul>
  </nav>
</div>
{% endif %}

Save the files and run the server you should see the NEXT button below the post list.

Adding Pagination Using Function-Based-Views

Related Articles

8 Comments

  1. Bro Can you tell me the files in which we have to make changes??
    Because I am having trouble in adding pagination to the blog project

  2. You should edit View.py and index.html first code you can copy and paste in your views directly(paginate_by = 3 just add this line your class view ) after come your index.page and

    {% if is_paginated %}

    {% if page_obj.has_previous %}
    « PREV
    {% endif %}
    {% if page_obj.has_next %}
    NEXT »

    {% endif %}

    {% endif %} paste this if you want to make your project with function based with you can use other portion 🙂

  3. Thanks for the post. Great work.

    Although I’m haveing a problem with the function-based solution.
    Do I need to put it in “views” instead of the class PostList?
    When I do that I get an error:

    File “C:\Users\176454\Desktop\Python\Projects\in_memory_of_David\blog\urls.py”, line 7, in
    path(”, views.PostList.as_view(), name=’home’),
    AttributeError: ‘function’ object has no attribute ‘as_view’

  4. Thanks 😀
    Great job with all the tutorials.
    Maybe you could help me with a differen thing.
    I would like to have a “blog” where people could post even without signing up.
    Of course I could use some capcha so I won’t get any spam.
    Could you help me with that as an additional feature to your python blog?

    1. I don’t feel this is gonna be a wise decision, the server can easily be chocked with this architecture.

Leave a Reply

Your email address will not be published. Required fields are marked *

Close