Introduction
When a page contains too many items, it becomes difficult to read and slow to navigate. Imagine showing 500 blog posts, 1,000 products, or hundreds of comments on one single page. This is not good for user experience or performance.
That is why Django provides pagination.
Pagination means splitting a large list of objects into smaller pages. Instead of showing everything at once, you show a limited number of items per page, such as:
- 5 posts per page
- 10 products per page
- 20 comments per page
In this tutorial, you will learn how pagination works in Django, how to use it in both function-based views and class-based views, and how to build clean page navigation links.
What You Will Learn
By the end of this tutorial, you will understand:
- what pagination is
- why pagination is important
- how to use Django’s
Paginator - how to paginate in function-based views
- how to paginate in class-based views
- how to build previous/next page links
- how to show page numbers
- common mistakes and best practices
Prerequisites
Before starting, you should already know:
- Django models
- QuerySets
- views and templates
- class-based views basics
1. What Is Pagination?
Pagination is the process of dividing a large set of results into multiple smaller pages.
For example, instead of:
Showing 100 blog posts on one page
you can show:
- page 1 → posts 1 to 10
- page 2 → posts 11 to 20
- page 3 → posts 21 to 30
This improves:
- readability
- performance
- navigation
- user experience
2. Why Use Pagination?
Pagination is useful because it:
- prevents long and heavy pages
- improves loading speed
- makes content easier to browse
- gives a more professional layout
- helps organize large datasets
It is especially important for:
- blogs
- product lists
- search results
- comments
- dashboards
- tables of data
3. Django’s Pagination Tool
Django provides a built-in class called Paginator.
Import it like this:
from django.core.paginator import Paginator
It takes two main arguments:
- a list or QuerySet
- number of items per page
4. Basic Example with Paginator
views.py
from django.core.paginator import Paginator
from django.shortcuts import render
from .models import Post
def post_list(request):
posts = Post.objects.all()
paginator = Paginator(posts, 5) # 5 posts per page
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
return render(request, 'blog/post_list.html', {'page_obj': page_obj})Explanation
postsgets all postsPaginator(posts, 5)splits them into pages of 5 itemsrequest.GET.get('page')gets the current page number from the URLget_page(page_number)returns the requested page safelypage_objis sent to the template
5. The URL Format for Pagination
Pagination usually works with query parameters.
Examples:
/posts/?page=1
/posts/?page=2
/posts/?page=3Django reads the page number from:
request.GET.get('page')6. Displaying Paginated Items in the Template
templates/blog/post_list.html
<h1>Blog Posts</h1>
{% for post in page_obj %}
<h2>{{ post.title }}</h2>
<p>{{ post.content|truncatewords:20 }}</p>
<hr>
{% empty %}
<p>No posts found.</p>
{% endfor %}Notice that we loop over page_obj, not over posts.
7. Previous and Next Links
Now let us add navigation links.
<div class="pagination">
{% if page_obj.has_previous %}
<a href="?page=1">First</a>
<a href="?page={{ page_obj.previous_page_number }}">Previous</a>
{% endif %}
<span>
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 %}
</div>Explanation
has_previouschecks if a previous page existsprevious_page_numbergets the previous page numbernumberis the current pagepaginator.num_pagesis the total number of pageshas_nextchecks if a next page existsnext_page_numbergets the next page number
8. Showing Numbered Page Links
Many websites show clickable page numbers.
<div class="pagination">
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<strong>{{ num }}</strong>
{% else %}
<a href="?page={{ num }}">{{ num }}</a>
{% endif %}
{% endfor %}
</div>This shows all page numbers and highlights the current one.
9. Full Function-Based Pagination Example
views.py
from django.core.paginator import Paginator
from django.shortcuts import render
from .models import Post
def post_list(request):
posts = Post.objects.all().order_by('-id')
paginator = Paginator(posts, 5)
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
return render(request, 'blog/post_list.html', {'page_obj': page_obj})post_list.html
<h1>Blog Posts</h1>
{% for post in page_obj %}
<h2>{{ post.title }}</h2>
<p>{{ post.content|truncatewords:20 }}</p>
<hr>
{% empty %}
<p>No posts found.</p>
{% endfor %}
<div class="pagination">
{% if page_obj.has_previous %}
<a href="?page=1">First</a>
<a href="?page={{ page_obj.previous_page_number }}">Previous</a>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<strong>{{ num }}</strong>
{% else %}
<a href="?page={{ num }}">{{ num }}</a>
{% endif %}
{% endfor %}
{% 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 %}
</div>10. How get_page() Helps
Django provides two ways:
page()
Strict and can raise errors
page_obj = paginator.page(page_number)get_page()
Safer and beginner-friendly
page_obj = paginator.get_page(page_number)If the user enters an invalid page number like:
?page=abcor
?page=9999get_page() handles it gracefully.
For beginners, get_page() is usually the better choice.
11. Paginating Class-Based Views
Pagination is even easier with class-based views.
Example with ListView
from django.views.generic import ListView
from .models import Post
class PostListView(ListView):
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'
paginate_by = 5
ordering = ['-id']Explanation
paginate_by = 5tells Django to show 5 items per page- Django automatically adds pagination context variables
12. Template for CBV Pagination
With ListView, the template gets:
page_objis_paginatedpaginator- object list (
postsin this example)
Example:
<h1>Blog Posts</h1>
{% for post in posts %}
<h2>{{ post.title }}</h2>
<p>{{ post.content|truncatewords:20 }}</p>
<hr>
{% empty %}
<p>No posts found.</p>
{% endfor %}
{% if is_paginated %}
<div class="pagination">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">Previous</a>
{% endif %}
<span>Page {{ page_obj.number }} of {{ paginator.num_pages }}</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">Next</a>
{% endif %}
</div>
{% endif %}13. Full Class-Based Example
views.py
from django.views.generic import ListView
from .models import Post
class PostListView(ListView):
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'
paginate_by = 5
ordering = ['-id']urls.py
from django.urls import path
from .views import PostListView
urlpatterns = [
path('posts/', PostListView.as_view(), name='post_list'),
]post_list.html
<h1>Blog Posts</h1>
{% for post in posts %}
<h2>{{ post.title }}</h2>
<p>{{ post.content|truncatewords:20 }}</p>
{% empty %}
<p>No posts found.</p>
{% endfor %}
{% if is_paginated %}
<div class="pagination">
{% if page_obj.has_previous %}
<a href="?page=1">First</a>
<a href="?page={{ page_obj.previous_page_number }}">Previous</a>
{% endif %}
{% for num in paginator.page_range %}
{% if page_obj.number == num %}
<strong>{{ num }}</strong>
{% else %}
<a href="?page={{ num }}">{{ num }}</a>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">Next</a>
<a href="?page={{ paginator.num_pages }}">Last</a>
{% endif %}
</div>
{% endif %}14. Pagination with Search or Filters
A common issue happens when pagination is combined with search.
Example search URL:
/posts/?q=django&page=2When generating pagination links, you must preserve the search query.
A simple example:
<a href="?q={{ request.GET.q }}&page={{ num }}">{{ num }}</a>This ensures the search term stays while moving between pages.
15. Example: Search + Pagination
views.py
from django.core.paginator import Paginator
from django.shortcuts import render
from .models import Post
def search_posts(request):
query = request.GET.get('q', '')
posts = Post.objects.filter(title__icontains=query).order_by('-id')
paginator = Paginator(posts, 5)
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
return render(request, 'blog/search_results.html', {
'page_obj': page_obj,
'query': query,
})Template
<h1>Search Results</h1>
<form method="get">
<input type="text" name="q" value="{{ query }}" placeholder="Search...">
<button type="submit">Search</button>
</form>
{% for post in page_obj %}
<h2>{{ post.title }}</h2>
{% empty %}
<p>No results found.</p>
{% endfor %}
<div class="pagination">
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<strong>{{ num }}</strong>
{% else %}
<a href="?q={{ query }}&page={{ num }}">{{ num }}</a>
{% endif %}
{% endfor %}
</div>16. Common Pagination Attributes
Here are useful pagination values:
page_obj.number→ current page numberpage_obj.has_previous→ whether previous page existspage_obj.has_next→ whether next page existspage_obj.previous_page_number→ previous page numberpage_obj.next_page_number→ next page numberpage_obj.paginator.num_pages→ total page countpage_obj.paginator.count→ total object count
17. Improving Large Page Lists
If there are many pages, showing every number may look messy.
Example:
- 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ...
In advanced projects, developers often show only nearby pages.
For now, beginners can display all page numbers, which is simpler.
18. Common Beginner Mistakes
Looping over the wrong variable
Wrong:
{% for post in posts %}when using FBV pagination that only sent page_obj.
Correct:
{% for post in page_obj %}Forgetting to read page from the URL
You need:
page_number = request.GET.get('page')Not preserving search/filter values
This breaks pagination when using search.
Using page() instead of get_page() without handling errors
This can raise exceptions.
19. Best Practices
- paginate large lists
- use
get_page()for safer handling - keep page size reasonable
- preserve search/filter query parameters
- highlight the current page
- do not overload the page with too many navigation links
A common page size is:
- 5 for blogs
- 10 to 20 for products or dashboards
20. Mini Project Example
Imagine a blog with 53 posts and you want to show 6 per page.
views.py
from django.core.paginator import Paginator
from django.shortcuts import render
from .models import Post
def blog_posts(request):
posts = Post.objects.all().order_by('-created_at')
paginator = Paginator(posts, 6)
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
return render(request, 'blog/blog_posts.html', {'page_obj': page_obj})Result
- page 1 → first 6 posts
- page 2 → next 6 posts
- page 3 → next 6 posts
- and so on
This makes the blog much cleaner and easier to navigate.
21. Summary
In this tutorial, you learned that:
- pagination splits large data into smaller pages
- Django provides the
Paginatorclass - function-based views use
Paginatormanually - class-based views use
paginate_by - templates can display previous/next links and page numbers
- pagination improves readability and performance
- search and filter parameters should be preserved in pagination links
Pagination is one of the most useful features in real Django applications because large lists are everywhere.
22. Mini Quiz
1. Which Django class is used for pagination?
A. Pager
B. Paginator
C. PageManager
D. SplitView
2. Which query parameter is commonly used for pagination?
A. id
B. num
C. page
D. index
3. Which method is safer for beginners?
A. page()
B. get_page()
C. paginate()
D. split_page()
4. Which attribute is used in ListView for pagination?
A. page_size
B. limit
C. paginate_by
D. items_per_page
5. What does page_obj.has_next check?
A. If there are more objects in the database
B. If a next page exists
C. If the page is empty
D. If the current page is valid
23. What Comes Next?
The next ideal tutorial is:
Tutorial: Search Functionality in Django
Subject: searching records using forms, QuerySets, and filters.
💬 Comments
No comments yet. Be the first to comment!
Login to comment.