
How to split content across pages.
Introduction
We use Django's pagination tools to spread content across multiple pages.
Quickstart (with a class-based view)
Edit the blog app views.py file and add these lines to it:
from django.shortcuts import render
from django.views.generic import ListView
from .models import Post
class HomepageView(ListView):
model = Post
paginate_by = 5
template_name = 'blog/index.html'
context_object_name = 'posts'
Edit the blog/index.html template file and add these lines to it:
<ul>
{% for post in posts %}
<li>
{{ post }}
</li>
{% endfor %}
</ul>
{% if page_obj.paginator.num_pages > 1 %}
{% if page_obj.has_previous %}
<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>
{% endif %}
{% endif %}
Full tutorial
Setup
Run these commands to setup a new project:
mkdir mysite && cd mysite
python3 -m venv venv
source venv/bin/activate
pip install django
django-admin startproject mysite .
python manage.py startapp blog
Add blog configuration class to the INSTALLED_APPS list:
INSTALLED_APPS = [
# HERE
'blog.apps.BlogConfig',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
blog/models.py
Add a class called Post in the blog app models.py file:
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=255)
def __str__(self):
return self.title
Run migrations:
python manage.py makemigrations
python manage.py migrate
Create posts
Create some content:
python manage.py shell
>>> from blog.models import Post
>>> for i in range(10):
... Post.objects.create(title="Lorem ipsum %s" % i)
mysite/urls.py
Edit the project urls.py file and add the following path to it:
from django.contrib import admin
from django.urls import path
from blog import views
urlpatterns = [
# HERE
path('', views.HomepageView.as_view(), name='home'),
path('admin/', admin.site.urls),
]
blog/views.py
Edit the blog app views.py file and add these lines to it:
from django.shortcuts import render
from django.views.generic import ListView
from .models import Post
class HomepageView(ListView):
model = Post
paginate_by = 5
template_name = 'blog/index.html'
context_object_name = 'posts'
The paginate_by attribute specifies how many objects we want to display on each page.
blog/templates/blog/index.html
Create a file called index.html in the blog app templates/blog directory and add these lines to it:
<ul>
{% for post in posts %}
<li>
{{ post }}
</li>
{% endfor %}
</ul>
{% if page_obj.paginator.num_pages > 1 %}
{% include 'blog/_pagination.html' with page_obj=page_obj %}
{% endif %}
The page_obj template variable contains everything we need to build the pagination markup.
page_obj.paginator.num_pages tells us the total number of pages.
The include tag loads and renders the markup from a separate template.
blog/templates/_pagination.html
Create a file called _pagination.html in the blog app templates directory and add these lines to it:
{% if page_obj.has_previous %}
<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>
{% endif %}
We pass the page number to the view using the ?page query string.
page_obj.has_previous returns True if there is a previous page.
page_obj.previous_page_number returns the previous page number.
page_obj.number is the current page number.
page_obj.has_next returns True if there is a next page.
page_obj.next_page_number returns the next page number.
Underscore(_) indicates that this template is meant to be included within other templates. We can now re-use this template across the site when we need to paginate content.
Function-based views
blog/views.py
With function-based views you can use the Paginator class directly. Edit the blog app views.py file and add these lines to it:
from django.core.paginator import Paginator
def function_based(request):
posts = Post.objects.all()
paginator = Paginator(posts, 5)
page = request.GET.get('page')
posts = paginator.get_page(page)
return render(request,
'blog/function_based.html',
{'posts': posts})
We pass the objects we want to paginate and the number of objects per page to the Paginator class.
The page query string value is fetched with the request.GET.get() function and passed to the paginator.get_page() function. This gets us the posts object that contains post objects for a specific page and access to the pagination data.
blog/templates/blog/function_based.html
Create a file called function_based.html in the blog app templates directory and add these lines to it:
<ul>
{% for post in posts %}
<li>
{{ post }}
</li>
{% endfor %}
</ul>
{% if posts.paginator.num_pages > 1 %}
{% if posts.has_previous %}
<a href="?page={{ posts.previous_page_number }}">previous</a>
{% endif %}
<span>Page {{ posts.number }} of {{ posts.paginator.num_pages }}</span>
{% if posts.has_next %}
<a href="?page={{ posts.next_page_number }}">next</a>
{% endif %}
{% endif %}
Note: with this function-based example we use the "posts" object to access the pagination data, not "page_obj".
mysite/urls.py
Edit the project urls.py file and add the following path to it:
from blog import views
urlpatterns = [
path('', views.HomepageView.as_view(), name='home'),
# HERE
path('function-based/', views.function_based, name='function_based'),
path('admin/', admin.site.urls),
]
Visit /function-based/.
Alternative Themes
Blue Theme
Create a file called theme_blue.html in the blog/templates/blog directory and add these lines to it:
<!doctype html>
{% load static %}
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css"
integrity="sha384-oS3vJWv+0UjzBfQzYUhtDYW+Pj2yciDJxpsK1OYPAYjqT085Qq/1cq5FLXAZQ7Ay" crossorigin="anonymous">
<link rel="stylesheet" href="{% static 'blog/css/pagination_blue.css' %}">
<title>Document</title>
</head>
<body>
<ul>
{% for post in posts %}
<li>
{{ post }}
</li>
{% endfor %}
</ul>
{% if page_obj.paginator.num_pages > 1 %}
{% include 'blog/_pagination_blue.html' with page_obj=page_obj %}
{% endif %}
</body>
</html>
Create a file called _pagination_blue.html in the blog/templates/blog directory and these lines to it:
<div class="pagination">
{% if page_obj.has_previous %}
<a class="pagination-action" href="?page=1">
<i class="fa fa-angle-double-left" aria-hidden="true"></i> </a>
<a class="pagination-action" href="?page={{ page_obj.previous_page_number }}">
<i class="fa fa-angle-left" aria-hidden="true"></i>
</a>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<span class="pagination-number pagination-current">{{ num }}</span>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<a class="pagination-number" href="?page={{ num }}">{{ num }}</a>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<a class="pagination-action" href="?page={{ page_obj.next_page_number }}">
<i class="fa fa-angle-right" aria-hidden="true"></i>
</a>
<a class="pagination-action" href="?page={{ page_obj.paginator.num_pages }}">
<i class="fa fa-angle-double-right" aria-hidden="true"></i>
</a>
{% endif %}
</div>
Create a file called pagination_blue.css file in the blog/static/blog/css directory and add these lines to it:
/* BLUE THEME */
.pagination {
margin-top: 1em;
}
.pagination a {
text-decoration: none;
}
.pagination-number {
padding: 0.5em 0.8em;
border-radius: 2px;
color: #fff;
background-color: #6D85C7;
}
.pagination-number:hover,
.pagination-current {
background-color: #3354AA;
}
.pagination-action {
margin: 0 0.1em;
display: inline-block;
padding: 0.5em 0.5em;
color: #B9B9B9;
font-size: 1.3em;
}
.pagination-action:hover,
.pagination-previous,
.pagination-next {
color: #3354AA;
}
Add this view to the blog app views.py file:
class BlueThemeView(ListView):
model = Post
paginate_by = 5
template_name = 'blog/theme_blue.html'
context_object_name = 'posts'
Add this path to the project urls.py file:
urlpatterns = [
path('', views.HomepageView.as_view(), name='home'),
path('function-based/', views.function_based, name='function_based'),
# HERE
path('blue-theme/', views.BlueThemeView.as_view(), name='blue_theme'),
path('admin/', admin.site.urls),
]
Visit /blue-theme/.
Green Theme
Create a file called theme_green.html in the blog/templates/blog directory and add these lines to it:
<!doctype html>
{% load static %}
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.2/css/all.css"
integrity="sha384-oS3vJWv+0UjzBfQzYUhtDYW+Pj2yciDJxpsK1OYPAYjqT085Qq/1cq5FLXAZQ7Ay" crossorigin="anonymous">
<link rel="stylesheet" href="{% static 'blog/css/pagination_green.css' %}">
<title>Document</title>
</head>
<body>
<ul>
{% for post in posts %}
<li>
{{ post }}
</li>
{% endfor %}
</ul>
{% if page_obj.paginator.num_pages > 1 %}
{% include 'blog/_pagination_green.html' with page_obj=page_obj %}
{% endif %}
</body>
</html>
Create a file called _pagination_green.html in the blog/templates/blog directory and add these lines to it:
<div class="pagination">
{% if page_obj.has_previous %}
<a class="pagination-action" href="?page=1">
<i class="fa fa-angle-double-left" aria-hidden="true"></i>
</a>
<a class="pagination-action" href="?page={{ page_obj.previous_page_number }}">
<i class="fa fa-angle-left" aria-hidden="true"></i>
</a>
{% endif %}
<span class="pagination-current">{{ page_obj.number }}</span>
<span class="pagination-of">of</span>
<span class="pagination-total">{{ page_obj.paginator.num_pages }}</span>
{% if page_obj.has_next %}
<a class="pagination-action" href="?page={{ page_obj.next_page_number }}">
<i class="fa fa-angle-right" aria-hidden="true"></i> </a>
<a class="pagination-action" href="?page={{ page_obj.paginator.num_pages }}">
<i class="fa fa-angle-double-right" aria-hidden="true"></i>
</a>
{% endif %}
</div>
Create a file called pagination_green.css file in the blog/static/blog/css directory and add these lines to it:
/* GREEN THEME */
.pagination {
margin-top: 1em;
}
.pagination a {
text-decoration: none;
}
.pagination-current,
.pagination-total {
padding: 0.5em 0.8em;
border-radius: 2px;
color: #fff;
background-color: #30BF61;
}
.pagination-total {
background-color: #B9B9B9;
}
.pagination-action {
margin: 0 0.1em;
display: inline-block;
padding: 0.5em 0.5em;
color: #B9B9B9;
font-size: 1.3em;
}
.pagination-of {
color: #B9B9B9;
padding: 0 1em;
}
.pagination-action:hover {
color: #3354AA;
}
Add this view to the blog app views.py file:
class GreenThemeView(ListView):
model = Post
paginate_by = 5
template_name = 'blog/theme_green.html'
context_object_name = 'posts'
Add this path to the project urls.py file:
urlpatterns = [
path('', views.HomepageView.as_view(), name='home'),
path('function-based/', views.function_based, name='function_based'),
path('blue-theme/', views.BlueThemeView.as_view(), name='blue_theme'),
# HERE
path('green-theme/', views.GreenThemeView.as_view(), name='green_theme'),
path('admin/', admin.site.urls),
]
Visit /green-theme/.