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
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">« 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 »</a>
{% endif %}
</span>
</div>
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 %}
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})
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
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()
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.
- Link for pagination in Django documentation
- Link of the HTML template in Django documentation
- Link to the Paginator() class in Django documentation
- Link to the template tag
include()
in Django documentation
Top comments (0)