DEV Community

DoriDoro
DoriDoro

Posted on • Edited on

Django Pagination: A Comprehensive Guide

Introduction

Pagination in Django is efficiently implemented using the Paginator class from django.core.paginator. It helps divide large querysets or lists into smaller, manageable chunks displayed page-by-page. This approach improves performance and enhances the user experience by preventing information overload.

To use pagination, you create a Paginator object with the queryset and the desired number of items per page. You can then access the appropriate page using the page() method. The resulting page_obj object, passed to the template context, offers attributes like has_previous, has_next, and number, enabling easy navigation.


1. Pagination with Function-Based Views (FBVs)

Here's an implementation of pagination in a function-based view:

app/views.py

from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
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

    try:
        posts = paginator.page(page_number)  # 4
    except PageNotAnInteger:
        posts = paginator.page(1)  # If page is not an integer, deliver first page.
    except EmptyPage:
        posts = paginator.page(paginator.num_pages)  # If page is out of range, deliver last page.

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

Explanation:

  1. Retrieve Posts: All posts are retrieved using Post.objects.all().
  2. Create Paginator: A Paginator object is created to split the posts into pages, each containing 5 posts.
  3. Get Current Page: The page number is obtained from the URL's query parameters (e.g., ?page=2).
  4. Handle Exceptions:
    • PageNotAnInteger: Defaults to page 1 if the provided page number is not an integer.
    • EmptyPage: Defaults to the last page if the requested page exceeds the available pages.
  5. Render Template: The list.html template is rendered with the paginated posts.

Pagination Template

The following template allows users to navigate between pages.

<!-- core/templates/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

Explanation:

  • Displays "Previous" and "Next" buttons depending on the availability of pages.
  • Shows the current page number and the total number of pages.

Include the Pagination Template

To display the paginated posts and navigation links, include the pagination.html template in your main list template.

<!-- app/templates/post/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 -->
  {% include "pagination.html" with page_obj=posts %}
{% endblock %}
Enter fullscreen mode Exit fullscreen mode

Note:

  • The page_obj variable is passed explicitly via the with clause to make it accessible in the pagination.html template.

2. Pagination with Class-Based Views (CBVs)

Django's ListView class provides built-in support for pagination, simplifying the implementation.

# app/views.py

from django.views.generic import ListView
from blog.models import Post

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

Explanation:

  1. Model: Specifies the Post model as the data source.
  2. Context Variable: Renames the default object_list to posts.
  3. Pagination: Automatically paginates the results with 5 posts per page.
  4. Template: Uses the post/list.html template.

Pagination Template: Reuse the pagination.html template from the FBV implementation.

Error Handling for CBVs

To handle pagination exceptions explicitly in a CBV, override the paginate_queryset method:

# app/views.py

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

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

    def paginate_queryset(self, queryset, page_size):
        paginator = self.get_paginator(queryset, page_size)
        page_number = self.request.GET.get('page')

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

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

Explanation:

  • PageNotAnInteger: If the provided page is invalid, display the first page.
  • EmptyPage: If the page number is too large, display the last available page.

Include the Pagination Template

To display the paginated posts and navigation links, include the pagination.html template in your main list template.

<!-- app/templates/post/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 -->
  {% include "pagination.html" %}
{% endblock %}
Enter fullscreen mode Exit fullscreen mode

Note:
The context variable page_obj is automatically included in the context of the Listview when pagniate_by is specified.


With these steps, you have a complete pagination system implemented in Django, supporting both function-based and class-based views with robust error handling and user-friendly navigation.


Qodo Takeover

Introducing Qodo Gen 1.0: Transform Your Workflow with Agentic AI

While many AI coding tools operate as simple command-response systems, Qodo Gen 1.0 represents the next generation: autonomous, multi-step problem-solving agents that work alongside you.

Read full post

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs