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
Explanation:
-
Retrieve Posts: All posts are retrieved using
Post.objects.all()
. -
Create Paginator: A
Paginator
object is created to split the posts into pages, each containing 5 posts. -
Get Current Page: The page number is obtained from the URL's query parameters (e.g.,
?page=2
). -
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.
-
-
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">« 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>
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 %}
Note:
- The
page_obj
variable is passed explicitly via thewith
clause to make it accessible in thepagination.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
Explanation:
-
Model: Specifies the
Post
model as the data source. -
Context Variable: Renames the default
object_list
toposts
. - Pagination: Automatically paginates the results with 5 posts per page.
-
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()
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 %}
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.
- 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)