Lesson 52 of 60 – Django Pagination
87%

Django Pagination

Pagination is the process of dividing a large amount of data into smaller pages. Instead of displaying hundreds or thousands of records on one page, Django can display a limited number of records at a time.

Pagination is commonly used for student lists, product lists, blog posts, search results, employee records, and admin dashboards.

Note: Django provides the built-in Paginator class for dividing QuerySets and other data into pages.

1. What is Pagination?

Pagination divides a large collection of records into multiple smaller pages.

For example, if a website has 100 students and displays 10 students per page:

Page 1  → Students 1 - 10
Page 2  → Students 11 - 20
Page 3  → Students 21 - 30
...
Page 10 → Students 91 - 100

2. Why Use Pagination?

Pagination improves the usability of pages containing large datasets.

  • Displays fewer records at once.
  • Makes pages easier to read.
  • Reduces the amount of data displayed in one page.
  • Provides simple navigation between pages.
  • Works well with search and filtering.
  • Is useful for admin dashboards and public websites.

3. Django Paginator

Django provides the Paginator class through django.core.paginator.

from django.core.paginator import Paginator

It can divide a QuerySet or another list-like collection into pages.

4. Creating a Paginator

The basic syntax is:

paginator = Paginator(
    object_list,
    per_page
)

For example:

paginator = Paginator(
    students,
    10
)

This means 10 students will be displayed on each page.

5. Importing Paginator

Import Paginator before using it.

from django.core.paginator import Paginator

It can then be used inside a Django view.

6. Paginating a QuerySet

A QuerySet can be passed directly to Paginator.

students = Student.objects.all()

paginator = Paginator(
    students,
    10
)

The QuerySet is divided into pages containing up to 10 records each.

7. Getting a Specific Page

Use the page() method to retrieve a particular page.

page_number = 2

page_obj = paginator.page(
    page_number
)

The returned object represents page 2.

8. Page Number from URL

Usually, the page number is supplied through a query-string parameter.

For example:

/students/?page=2

Read the page number using:

page_number = request.GET.get(
    "page"
)

9. Converting the Page Number

The value returned by request.GET.get() is a string. Django's Paginator can work with the page number value, but you can also validate or convert it when needed.

page_number = request.GET.get(
    "page",
    1
)

Here, page 1 is used when no page parameter is supplied.

10. Basic Pagination View

from django.shortcuts import render
from django.core.paginator import Paginator
from .models import Student

def student_list(request):

    students = Student.objects.all()

    paginator = Paginator(
        students,
        10
    )

    page_number = request.GET.get(
        "page"
    )

    page_obj = paginator.get_page(
        page_number
    )

    return render(
        request,
        "students.html",
        {
            "page_obj": page_obj
        }
    )

11. get_page()

The get_page() method is convenient for handling page numbers that are invalid or outside the available range.

page_obj = paginator.get_page(
    page_number
)

It provides a convenient way to obtain a page object for a requested page.

12. Displaying Page Records

The page object can be looped over in the template.

{% for student in page_obj %}

    <p>
        {{ student.name }}
    </p>

{% endfor %}

Only the records belonging to the current page are displayed.

13. Number of Pages

The num_pages attribute provides the total number of pages.

{{ page_obj.paginator.num_pages }}

For example, 100 students with 10 students per page produce 10 pages.

14. Current Page Number

Use number to get the current page number.

{{ page_obj.number }}

For example:

Page 3 of 10

can be displayed with:

Page {{ page_obj.number }}
of {{ page_obj.paginator.num_pages }}

15. Checking for Previous Page

Use has_previous to determine whether a previous page exists.

{% if page_obj.has_previous %}

    Previous page is available.

{% endif %}

This is useful for displaying a Previous button.

16. Checking for Next Page

Use has_next to check whether another page exists.

{% if page_obj.has_next %}

    Next page is available.

{% endif %}

17. Getting Previous Page Number

The previous_page_number method returns the previous page number.

{{ page_obj.previous_page_number }}

Use it only when has_previous is true.

18. Getting Next Page Number

The next_page_number method returns the next page number.

{{ page_obj.next_page_number }}

Use it only when has_next is true.

19. Previous and Next Buttons

{% if page_obj.has_previous %}

    <a href="?page={{
        page_obj.previous_page_number
    }}">

        Previous

    </a>

{% endif %}


{% if page_obj.has_next %}

    <a href="?page={{
        page_obj.next_page_number
    }}">

        Next

    </a>

{% endif %}

20. First and Last Page Links

You can provide direct links to the first and last pages.

<a href="?page=1">
First
</a>

<a href="?page={{
    page_obj.paginator.num_pages
}}">

Last

</a>

These links can make navigation easier for large datasets.

21. Showing a Page Range

The page_range property of the paginator can be used to iterate over available page numbers.

{% for number in page_obj.paginator.page_range %}

    <a href="?page={{ number }}">
        {{ number }}
    </a>

{% endfor %}

22. Complete Pagination Navigation

<div>

{% if page_obj.has_previous %}

    <a href="?page=1">
        First
    </a>

    <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>

    <a href="?page={{
        page_obj.paginator.num_pages
    }}">

        Last

    </a>

{% endif %}

</div>

23. Pagination with Filtering

Pagination can be combined with filtering.

students = Student.objects.filter(
    course="Python"
)

paginator = Paginator(
    students,
    10
)

Only matching records are divided into pages.

24. Pagination with Ordering

It is usually useful to define a predictable ordering before paginating a QuerySet.

students = Student.objects.all().order_by(
    "name"
)

paginator = Paginator(
    students,
    10
)

This helps keep the page results consistently ordered.

25. Pagination with Search

Pagination can also work with search results.

query = request.GET.get(
    "q",
    ""
)

students = Student.objects.filter(
    name__icontains=query
).order_by("name")

paginator = Paginator(
    students,
    10
)

page_obj = paginator.get_page(
    request.GET.get("page")
)

The search results can then be displayed page by page.

26. Preserving Search Parameters

When search and pagination are combined, pagination links should preserve the search parameter.

<a href="?q={{ query }}&page=2">
Page 2
</a>

Otherwise, clicking a pagination link may remove the current search filter.

27. Common Pagination Mistakes

  • Forgetting to import Paginator.
  • Displaying the complete QuerySet instead of the page object.
  • Forgetting to pass the page number.
  • Not handling invalid page numbers.
  • Using incorrect pagination links.
  • Forgetting to preserve search parameters.
  • Paginating data without a predictable ordering.
  • Not checking has_previous before creating a Previous link.
  • Not checking has_next before creating a Next link.

28. Complete Pagination Workflow

Get QuerySet
     ↓
Filter / Search
     ↓
Order Data
     ↓
Create Paginator
     ↓
Read Page Number
     ↓
Get Page Object
     ↓
Send Page Object to Template
     ↓
Display Records
     ↓
Previous / Next Navigation

29. Complete Pagination Example

views.py

from django.shortcuts import render
from django.core.paginator import Paginator
from .models import Student

def student_list(request):

    students = Student.objects.all().order_by(
        "name"
    )

    paginator = Paginator(
        students,
        10
    )

    page_number = request.GET.get(
        "page"
    )

    page_obj = paginator.get_page(
        page_number
    )

    return render(
        request,
        "students.html",
        {
            "page_obj": page_obj
        }
    )

students.html

{% for student in page_obj %}

    <p>
        {{ student.name }}
    </p>

{% endfor %}

<div>

{% 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 %}

</div>

30. Django Pagination Summary

Django's Paginator makes it easy to divide large datasets into smaller pages.

  • Import Paginator from django.core.paginator.
  • Pass a QuerySet or list to Paginator.
  • Specify how many records should appear per page.
  • Use get_page() to obtain a page object.
  • Use page_obj in the template.
  • Use has_previous and has_next for navigation.
  • Use number for the current page.
  • Use num_pages for the total number of pages.
  • Pagination can be combined with filtering, ordering, and search.
  • Preserve query parameters when building pagination links.

📌 Key Points

  • Paginator divides large datasets into pages.
  • get_page() retrieves the requested page.
  • page_obj contains the records for the current page.
  • has_previous checks for a previous page.
  • has_next checks for a next page.
  • num_pages gives the total number of pages.
  • Pagination works well with QuerySets, filtering, ordering, and search.
  • Search parameters should be preserved in pagination URLs.

🧠 Quick Quiz

Question: Which Django class is used to divide a large QuerySet into multiple pages?