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
returnsTrue
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
returnsTrue
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 therequest.GET.get()
function and passed to thepaginator.get_page()
function. This gets us theposts
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 %}
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/
.