Pagination

Django provides a great way to manage paginated data that splits 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 in 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 a 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 }}