Django Pagination Tutorial with Example
Django’s pagination module allows you to split a large set of data into smaller chunks (pages). This is particularly useful for displaying a list of items, such as blog posts or products, in a manageable way. Let’s dive into how to implement pagination step-by-step.
Table of Contents
- What is Pagination?
- Setup Django Project
- Setting Up Models
- Creating a View with Pagination
- Using Pagination in Templates
- Handling Edge Cases
- Final Example
1. What is Pagination?
Pagination is the process of dividing a large dataset into smaller sets or pages. Each page contains a limited number of items, and users can navigate through pages.
2. Setup Django Project
First, create & activate virtualenv and also create Django project and app if you haven’t already.
mkdir myproject
cd myproject
python -m venv venv # create virtualenv
source venv\bin\activate # activate the virtualenv
django-admin startproject myproject .
python manage.py startapp myapp
Update INSTALLED_APPS
in your settings.py
to include the app:
INSTALLED_APPS = [
# Other apps
'myapp',
]
3. Setting Up Models
Let’s create a simple Post
model to display as paginated content.
# myapp/models.py
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
def __str__(self):
return self.title
Now, make migrations and migrate the model:
python manage.py makemigrations
python manage.py migrate
You can also add some sample data in the Django admin panel or use the shell
:
python manage.py shell
from myapp.models import Post
for i in range(1, 101):
Post.objects.create(title=f"Post {i}", content=f"Content for post {i}")
4. Creating a View with Pagination
Django provides the Paginator
class for handling pagination. Let's create a view to paginate the Post
objects.
# myapp/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, 10) # Show 10 posts per page.
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
return render(request, 'myapp/post_list.html', {'page_obj': page_obj})
Here, we use Paginator
to divide the Post
objects into pages of 10 posts each. We also retrieve the page_number
from the URL query parameters (?page=2
, etc.) and pass the page_obj
to the template.
5. Using Pagination in Templates
Now, let’s create the template to display the paginated posts.
<!-- myapp/templates/myapp/post_list.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Paginated Post List</title>
</head>
<body>
<h1>Paginated Posts</h1>
<ul>
{% for post in page_obj %}
<li>{{ post.title }}</li>
{% endfor %}
</ul>
<div class="pagination">
<span>
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
</span>
{% 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>
</body>
</html>
Explanation:
- The
for
loop displays each post title from thepage_obj
. - We create pagination controls that show:
- First and Previous buttons if there are previous pages.
- Page numbers and the current page highlighted.
- Next and Last buttons if there are more pages.
6. Handling Edge Cases
- Empty Pages: When a user requests a page that doesn’t exist (e.g.,
page=1000
), Django raises anEmptyPage
exception. You can handle this in your view:
# myapp/views.py
from django.core.paginator import EmptyPage, PageNotAnInteger
def post_list(request):
posts = Post.objects.all()
paginator = Paginator(posts, 10)
page_number = request.GET.get('page')
try:
page_obj = paginator.get_page(page_number)
except PageNotAnInteger:
page_obj = paginator.page(1)
except EmptyPage:
page_obj = paginator.page(paginator.num_pages)
return render(request, 'myapp/post_list.html', {'page_obj': page_obj})
2. Performance Considerations: When working with large datasets, pagination queries can become slow. Consider using select_related
or prefetch_related
to reduce database hits when dealing with related models.
7. Final Example
Let’s put everything together:
- Model: The
Post
model has been defined to store post titles and content. - View: The
post_list
view uses Django's pagination to divide posts into pages of 10. - Template: The template shows posts and includes pagination controls with links to navigate through pages.
Complete Code Example:
- Models (
models.py
):
# myapp/views.py
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
def __str__(self):
return self.title
2. Views (views.py
):
# myapp/views.py
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from django.shortcuts import render
from .models import Post
def post_list(request):
posts = Post.objects.all()
paginator = Paginator(posts, 10)
page_number = request.GET.get('page')
try:
page_obj = paginator.get_page(page_number)
except PageNotAnInteger:
page_obj = paginator.page(1)
except EmptyPage:
page_obj = paginator.page(paginator.num_pages)
return render(request, 'myapp/post_list.html', {'page_obj': page_obj})
3. Templates (post_list.html
):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Paginated Post List</title>
</head>
<body>
<h1>Paginated Posts</h1>
<ul>
{% for post in page_obj %}
<li>{{ post.title }}</li>
{% endfor %}
</ul>
<div class="pagination">
<span>
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
</span>
{% 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>
</body>
</html>
Conclusion
This tutorial covers the basics of Django pagination with examples. Pagination is essential for improving user experience when dealing with large data sets.