DEV Community

DoriDoro
DoriDoro

Posted on

How to implement pagination in a Django project?

Introduction

Pagination in Django's is implemented using the Paginator class from django.core.paginator. It helps split large querysets or lists into smaller, manageable chunks that can be displayed page-by-page. To use pagination, you create a Paginator object with the queryset and desired number of items per page, then extract the relevant page using the page() method. This page object is passed to the template context, allowing for easy navigation with attributes like has_previous, has_next, and page_obj.number. Typically, GET parameters are used to track the current page, enabling users to move between pages seamlessly.


1. function-based view (FBV)

We define a function-based view named post_list that takes an HTTP request object (request) as a parameter.

# views.py

from django.core.paginator import Paginator
from django.shortcuts import render

from blog.models import Post


def post_list(request):
    list_of_posts = Post.objects.all()  # 1
    paginator = Paginator(list_of_posts, 5)  # 2
    page_number = request.GET.get("page", 1)  # 3
    posts = paginator.page(page_number)  # 4
    return render(request, "post/list.html", {"posts": posts})  # 5
Enter fullscreen mode Exit fullscreen mode

1) - We retrieve all the posts from the post model and store them in the list_of_posts variable.
2) - Create a paginator object using list_of_posts and set the number of posts to display per page to 5.
3) - Gets the current page number from the query parameters. If no page number is given, it defaults to 1.
4) - Retrieves the corresponding page of posts based on page_number. This will result in a subset of posts for the current page.
5) - Renders the post/list.html template and passes the paginated posts to the template context. This allows the template to display the posts for the current page.

The pagination template:

Django provides a template for pagination on its official website, where the user can navigate through the pages.

<!-- pagination.html -->

<div class="pagination">
    <span class="step-links">
        {% if page_obj.has_previous %}
            <a href="?page=1">&laquo; first</a>
            <a href="?page={{ page_obj.previous_page_number }}">Previous</a>
        {% endif %}

        <span class="current">
            Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}.
        </span>

        {% if page_obj.has_next %}
            <a href="?page={{ page_obj.next_page_number }}">Next</a>
            <a href="?page={{ page_obj.paginator.num_pages }}">last &raquo;</a>
        {% endif %}
    </span>
</div>
Enter fullscreen mode Exit fullscreen mode

Include the pagination template:

You have created a template called list.html for your view (post_list). You need to include the pagination template in your list.html template to implement pagination in the listing of your posts.

<!-- list.html -->

{% block content %}
  <h1>My Blog</h1>
  {% for post in posts %}
    <h2>
      <a href="{{ post.get_absolute_url }}">
        {{ post.title }}
      </a>
    </h2>
  <p class="date">
    Published {{ post.publish }} by {{ post.author }}
  </p>
  {{ post.body|truncatewords:30|linebreaks }}
  {% endfor %}
  {% include "pagination.html" with page_obj=posts %}
{% endblock %}
Enter fullscreen mode Exit fullscreen mode

The pagination.html template is included in your list.html template with this little line {% include "pagination.html" with page_obj=posts %}.


Use error management:

# views.py

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

from blog.models import Post


def post_list(request):
    list_of_posts = Post.published.all()
    paginator = Paginator(list_of_posts, 5)
    page_number = request.GET.get("page", 1)
    try:
        posts = paginator.page(page_number)
    except EmptyPage:  # 1
        posts = paginator.page(paginator.num_pages)
    except PageNotAnInteger:
        posts = paginator.page(1)

    return render(request, "post/list.html", {"posts": posts})
Enter fullscreen mode Exit fullscreen mode

A try-except block in Python is used to handle exceptions (errors) that may occur during the execution of a program. The try block contains code that is "tried" and may potentially cause an exception. If an error occurs within the try block, Python immediately stops execution at that point and jumps to the corresponding except block, where you can specify how to handle the error. This prevents the program from crashing and allows you to manage the exception gracefully by executing alternative code, logging the error, or providing a fallback response.

1) - The EmptyPage exception, which occurs when the requested page_number is greater than the total number of pages. In this case, it returns the last page of posts by calling paginator.page(paginator.num_pages).
2) - The PageNotAnInteger exception, thrown if the page_number is not a valid integer (e.g. if it's a string or None). When this happens, the default is to return the first page of posts (paginator.page(1)).


2. class-based view (CBV)

We define a class-based view called PostListView, which inherits from Django's built-in ListView. ListView is used to display a list of objects from a given model.

# views.py

from django.views.generic import ListView

from blog.models import Post


class PostListView(ListView):
    queryset = Post.objects.all()  # 1
    context_object_name = "posts"  # 2
    paginate_by = 5  # 3
    template_name = "post/list.html"  # 4
Enter fullscreen mode Exit fullscreen mode

1) - Specifies the queryset that the view will display, which in this case is all posts.
2) - Sets the name of the context variable used in the template to refer to the list of posts. By default, the ListView uses object_list, but here it is customised to posts.
3) - Enables pagination, specifying that only 5 posts should be displayed per page. Additional pages are handled automatically by Django, using GET parameters such as ?page=2.
4) - Specifies the template to use for rendering the view. Instead of using the default template naming convention (post_list.html), the specified post/list.html template will be rendered.


Use error management:

# views.py

from django.core.paginator import EmptyPage, PageNotAnInteger
from django.views.generic import ListView

from blog.models import Post

class CustomPostListView(ListView):
    queryset = Post.objects.all()
    paginate_by = 5
    template_name = "post/list.html"

    def paginate_queryset(self, queryset, page_size):
        """Custom pagination logic with error handling."""
        paginator = self.get_paginator(queryset, page_size)
        page_number = self.request.GET.get('page')

        try:
            page = paginator.page(page_number)
        except PageNotAnInteger:  # 1
            page = paginator.page(1)
        except EmptyPage:  # 2
            page = paginator.page(paginator.num_pages)

        return paginator, page, page.object_list, page.has_other_pages()
Enter fullscreen mode Exit fullscreen mode

1) - PageNotAnInteger: This exception is raised when the page number in the URL is not an integer (e.g., ?page=abc instead of ?page=2). If PageNotAnInteger is encountered, Django automatically defaults to showing the first page of the list.

2) - EmptyPage: This exception is raised when the page number in the URL is greater than the total number of pages available (e.g., ?page=99 when there are only 5 pages). When EmptyPage is encountered, Django returns the last available page.


Top comments (0)