Pagination

Django provides a great way to manage paginated data that is split across several pages with “Previous/Next” links.

How Pagination objects interact

We can open Python shell: python manage.py shell.

# Import Paginator class
>>> from django.core.paginator import Paginator

# Create dummy posts that we'd like to paginate
>>> posts = ['1', '2', '3', '4', '5']

# Create paginator objects
>>> p = Paginator(objects, 2) # Create 2 posts per page

# Number of posts
>>> p.count
5

# Number of pages
>>> p.num_pages
3

# Check page range
>>> p.page_range
range(1, 3)

# Loop over the pages
>>> for page in p.page_range
...     print(page)

# Look at each page
>>> page1 = p.page(1)
>>> page1
<Page 1 of 3>

# Check the page number
>>> page1.number
1

# Check posts on the page
>>> page1.object_list
['1', '2']

# Create variable for page 2
>>> page2 = p.page(2)
>>> page2.object_list
['3', '4']

# Check if page 2 has a previous page
>>> page2.has_previous()
True

# Check if page 2 has a next page
>>> page2.has_next()
True

# Check page 2's next page number
>>> page2.next_page_number()
3

# Check page 2's previous page number
>>> page2.previous_page_number()
1

# Check if page 2 has other pages
>>> page2.has_other_pages()
True

# The 1-based index of the first item on this page
>>> page2.start_index()
3

# The 1-based index of the last item on this page
>>> page2.end_index()
4

# Check page 0
>>> p.page(0)
Traceback (most recent call last):
...
EmptyPage: That page number is less than 1

# Check page 4
>>> p.page(4)
Traceback (most recent call last):
...
EmptyPage: That page contains no results

Example 1: Add pagination to our blog app

  1. In our blog/views.py We add paginate_by = 3, so we have 3 posts per page:
class PostListView(ListView):
    model = Post
    template_name = 'blog/index.html'  # <app>/<model>_<viewtype>.html
    context_object_name = 'posts'
    ordering = ['-date']
    paginate_by = 3
  1. In our blog/index.html We add paginate under the {% for post in posts %} part.
<!-- If the page is paginated -->
{% if is_paginated %}

<!-- If page has a previous page, give an option to go to the first page and previous page -->
{% if page_obj.has_previous %}
<a class="btn btn-outline-info mb-4" href="?page=1">First</a>
<a
  class="btn btn-outline-info mb-4"
  href="?page={{ page_obj.previous_page_number }}"
  >Previous</a
>
{% endif %}

<!-- Navigate pages -->
{% for num in page_obj.paginator.page_range %}
<!-- Display current page -->
{% if page_obj.number == num %}
<a class="btn btn-info mb-4" href="?page={{ num }}">{{ num }}</a>
<!-- Display 2 pages before and after the current page -->
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<a class="btn btn-outline-info mb-4" href="?page={{ num }}">{{ num }}</a>
{% endif %} {% endfor %}

<!-- If page has a next page, give an option to go to the next page and final page -->
{% if page_obj.has_next %}
<a
  class="btn btn-outline-info mb-4"
  href="?page={{ page_obj.next_page_number }}"
  >Next</a
>
<a
  class="btn btn-outline-info mb-4"
  href="?page={{ page_obj.paginator.num_pages }}"
  >Last</a
>
{% endif %} {% endif %}

Example 2: Create pagination for each user’s post list

  1. In our views.py, add:

Import

from django.shortcuts import render, get_object_or_404
from django.contrib.auth.models import User
class UserPostListView(ListView):
    model = Post
    template_name = 'blog/user_posts.html'  # <app>/<model>_<viewtype>.html
    context_object_name = 'posts'
    paginate_by = 3

    def get_queryset(self):
        user = get_object_or_404(User, username=self.kwargs.get('username')) # If user doesn't exist
        return Post.objects.filter(author=user).order_by('-date')
  1. In urls.py add
from django.urls import path
from .views import (
    PostListView,
    PostDetailView,
    PostCreateView,
    PostUpdateView,
    PostDeleteView,
    UserPostListView # Import UserPostListView
)
from . import views

urlpatterns = [
    path('', PostListView.as_view(), name='blog-home'),
    path('user/<str:username>', UserPostListView.as_view(), name='user-posts'), # add path
    path('post/<int:pk>/', PostDetailView.as_view(), name='post-detail'),
    path('post/new/', PostCreateView.as_view(), name='post-create'),
    path('post/<int:pk>/update/', PostUpdateView.as_view(), name='post-update'),
    path('post/<int:pk>/delete/', PostDeleteView.as_view(), name='post-delete'),
    path('about/', views.about, name='blog-about'),
]
  1. Create blog/user_posts.html template The difference between the User post page and Homepage:
  • <h1 class="mb-3">Posts by {{ view.kwargs.username }} ({{ page_obj.paginator.count }})</h1>
  • Add link to user page: <a class="mr-2" href="{% url 'user-posts' post.author.username %}">{{ post.author }}</a>
{% extends "blog/base.html" %} {% block content %}
<h1 class="mb-3">
  Posts by {{ view.kwargs.username }} ({{ page_obj.paginator.count }})
</h1>
{% for post in posts %}
<article class="media content-section">
  <img
    class="rounded-circle article-img"
    src="{{ post.author.profile.image.url }}"
  />
  <div class="media-body">
    <div class="article-metadata">
      <a class="mr-2" href="{% url 'user-posts' post.author.username %}"
        >{{ post.author }}</a
      >
      <small class="text-muted">{{ post.date|date:"F d, Y" }}</small>
    </div>
    <h2>
      <a class="article-title" href="{% url 'post-detail' post.id %}"
        >{{ post.title }}</a
      >
    </h2>
    <p class="article-content">{{ post.content }}</p>
  </div>
</article>
{% endfor %} {% if is_paginated %} {% if page_obj.has_previous %}
<a class="btn btn-outline-info mb-4" href="?page=1">First</a>
<a
  class="btn btn-outline-info mb-4"
  href="?page={{ page_obj.previous_page_number }}"
  >Previous</a
>
{% endif %} {% for num in page_obj.paginator.page_range %} {% if page_obj.number
== num %}
<a class="btn btn-info mb-4" href="?page={{ num }}">{{ num }}</a>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<a class="btn btn-outline-info mb-4" href="?page={{ num }}">{{ num }}</a>
{% endif %} {% endfor %} {% if page_obj.has_next %}
<a
  class="btn btn-outline-info mb-4"
  href="?page={{ page_obj.next_page_number }}"
  >Next</a
>
<a
  class="btn btn-outline-info mb-4"
  href="?page={{ page_obj.paginator.num_pages }}"
  >Last</a
>
{% endif %} {% endif %} {% endblock content %}
  1. Update the User page link in index.html:
<a class="mr-2" href="{% url 'user-posts' post.author.username %}"
  >{{ post.author }}</a
>
  1. Update the User page link in post_detail.html:
<a class="mr-2" href="{% url 'user-posts' object.author.username %}"
  >{{ object.author }}</a
>

The difference is post_detail.html has {{ object.author }}