Paramclasses Academy - Blog

Tutorials Details

←
Post 15 of 29
→

Django Pagination and Search Filters: Page Navigation, Dynamic Filtering, and Efficient Data Listing

Django Pagination and Search Filters: Page Navigation, Dynamic Filtering, and Efficient Data Listing

October 21, 2026

Django Pagination and Search Filters: Page Navigation, Dynamic Filtering, and Efficient Data Listing

Introduction

Django Pagination is a technique used to divide large sets of database records into smaller pages. Instead of displaying hundreds or thousands of records on a single page, pagination allows users to navigate through manageable groups of results.

Search filters make it easier to find specific records by entering keywords or selecting criteria such as category, city, status, or date.

In this module, you will learn how to implement pagination, build dynamic search forms, combine search filters with pagination, preserve filter values while navigating between pages, and improve the performance and usability of Django applications.

What You Will Learn

  1. Understanding pagination and its importance.

  2. Using Django's built-in Paginator class.

  3. Displaying records page by page.

  4. Creating Previous and Next navigation links.

  5. Implementing page numbers.

  6. Building search forms using GET requests.

  7. Filtering records using filter() and Q objects.

  8. Combining search filters with pagination.

  9. Preserving search values across pages.

  10. Handling invalid and out-of-range page numbers.

  11. Improving pagination performance.

  12. Building a practical Student Management listing page.

1. Understanding Pagination

Pagination divides a large collection of records into multiple pages.

For example, if a database contains 100 students and you display 10 students per page, the application will show 10 pages of results.

Pagination provides several benefits:

  • Makes long lists easier to navigate.

  • Reduces the number of records displayed at once.

  • Improves the user experience.

  • Helps organize large admin tables.

  • Can reduce the amount of data retrieved and rendered per request.

2. Importing Django's Paginator

Django provides a built-in Paginator class.

Import it in your view:

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

The Paginator class divides a collection into pages and provides information about the available pages.

3. Creating Basic Pagination

Suppose your project contains a Student model.

Create the following view in views.py:

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

def student_list(request):
    students = Student.objects.order_by('id')

    paginator = Paginator(students, 10)

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

    page_obj = paginator.get_page(page_number)

    context = {
        'page_obj': page_obj,
    }

    return render(
        request,
        'students/student_list.html',
        context
    )

Explanation

  • Student.objects.order_by('id') retrieves student records in a consistent order.

  • Paginator(students, 10) divides the records into pages of 10.

  • request.GET.get('page') reads the requested page number from the URL.

  • paginator.get_page(page_number) returns the requested page and handles invalid or out-of-range page numbers gracefully.

  • page_obj contains the records for the current page and pagination information.

4. Displaying Paginated Records in HTML

Create or update templates/students/student_list.html:

<h2>Student Management</h2>

<table border="1" cellpadding="10">
    <thead>
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Email</th>
            <th>City</th>
        </tr>
    </thead>

    <tbody>
        {% for student in page_obj %}
        <tr>
            <td>{{ student.id }}</td>
            <td>{{ student.name }}</td>
            <td>{{ student.email }}</td>
            <td>{{ student.city }}</td>
        </tr>
        {% empty %}
        <tr>
            <td colspan="4">No students found.</td>
        </tr>
        {% endfor %}
    </tbody>
</table>

The template iterates over page_obj, so it displays only the records belonging to the current page.

5. Adding Previous and Next Buttons

Add the following code below the table:

<div class="pagination">

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

Important pagination properties

  • has_previous checks whether a previous page exists.

  • has_next checks whether a next page exists.

  • previous_page_number returns the previous page number.

  • next_page_number returns the next page number.

  • number returns the current page number.

  • paginator.num_pages returns the total number of pages.

6. Displaying Page Numbers

You can display individual page numbers to make navigation easier.

<div class="pagination">

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

        {% if page_obj.number == num %}
            <strong>{{ num }}</strong>
        {% else %}
            <a href="?page={{ num }}">{{ num }}</a>
        {% endif %}

    {% endfor %}

</div>

This creates a link for each page and highlights the current page.

For very large datasets, displaying every page number may clutter the interface. In such cases, show a limited range around the current page.

7. Creating a Search Form

A search form allows users to find students by name.

Add this form above the table:

<form method="get">

    <input
        type="text"
        name="search"
        placeholder="Search student name"
        value="{{ search_query }}"
    >

    <button type="submit">Search</button>

    <a href="{% url 'student_list' %}">
        Reset
    </a>

</form>

The GET method places search criteria in the URL. This makes the search results easier to bookmark or share.

For example:

/students/?search=amit

The view must read the search parameter and apply the corresponding database filter.

8. Implementing Search in views.py

Update the view to include the search condition.

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

def student_list(request):

    students = Student.objects.order_by('id')

    search_query = request.GET.get('search', '').strip()

    if search_query:
        students = students.filter(
            name__icontains=search_query
        )

    paginator = Paginator(students, 10)

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

    page_obj = paginator.get_page(page_number)

    context = {
        'page_obj': page_obj,
        'search_query': search_query,
    }

    return render(
        request,
        'students/student_list.html',
        context
    )

Now users can search for students by name. The search is case-insensitive under Django's icontains lookup, subject to database backend behavior.

9. Combining Search with Pagination

When users search for a record and navigate to the next page, the search parameter must remain in the URL.

Otherwise, clicking Next may remove the search criteria and show unrelated records.

Use the following pagination links:

<div class="pagination">

    {% if page_obj.has_previous %}
        <a href="?search={{ search_query|urlencode }}&page=1">
            First
        </a>

        <a href="?search={{ search_query|urlencode }}&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="?search={{ search_query|urlencode }}&page={{ page_obj.next_page_number }}">
            Next
        </a>

        <a href="?search={{ search_query|urlencode }}&page={{ page_obj.paginator.num_pages }}">
            Last
        </a>
    {% endif %}

</div>

The urlencode template filter safely encodes the search value for use in a URL parameter.

This example preserves the name search while navigating between pages.

10. Adding Multiple Search Filters

Real applications often need more than one search condition.

For example, a student listing page may support:

  • Student name.

  • City.

  • Course.

  • Enrollment status.

Update the view:

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

def student_list(request):

    students = Student.objects.all()

    search_query = request.GET.get('search', '').strip()
    city = request.GET.get('city', '').strip()
    course = request.GET.get('course', '').strip()

    if search_query:
        students = students.filter(
            name__icontains=search_query
        )

    if city:
        students = students.filter(city=city)

    if course:
        students = students.filter(course=course)

    students = students.order_by('name')

    paginator = Paginator(students, 10)

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

    context = {
        'page_obj': page_obj,
        'search_query': search_query,
        'city': city,
        'course': course,
    }

    return render(
        request,
        'students/student_list.html',
        context
    )

Add the additional inputs to the search form:

<form method="get">

    <input
        type="text"
        name="search"
        placeholder="Student name"
        value="{{ search_query }}"
    >

    <input
        type="text"
        name="city"
        placeholder="City"
        value="{{ city }}"
    >

    <input
        type="text"
        name="course"
        placeholder="Course"
        value="{{ course }}"
    >

    <button type="submit">Apply Filters</button>

    <a href="{% url 'student_list' %}">Reset</a>

</form>

All active filters are applied before pagination, ensuring that the page count reflects the filtered results.

11. Using Q Objects for Flexible Search

Sometimes users need to search across multiple fields simultaneously.

For example, a search term could match either a student's name or email address.

from django.db.models import Q

students = Student.objects.filter(
    Q(name__icontains=search_query) |
    Q(email__icontains=search_query)
)

This returns students whose name or email contains the search term.

You can integrate this query into the existing student_list view before creating the paginator.

12. Handling Empty and Invalid Results

Django's get_page() method handles invalid page numbers by returning a valid page object when possible.

For example:

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

If the requested page number is invalid, Django returns the first page. If it is beyond the available range, Django returns the last page.

When no records match a search, the paginator has no result pages. The template's {% empty %} block can display a friendly message.

{% for student in page_obj %}
    <p>{{ student.name }}</p>
{% empty %}
    <p>No matching students found.</p>
{% endfor %}

13. Improving Pagination Performance

Pagination can help control how many records are displayed and processed at once, but it does not automatically make every database query efficient.

Follow these practices:

  1. Apply search filters before creating the paginator.

  2. Use consistent ordering to avoid unpredictable results.

  3. Add database indexes to fields frequently searched or filtered when appropriate.

  4. Use select_related() and prefetch_related() when accessing related objects.

  5. Avoid retrieving every record into a Python list unnecessarily.

  6. Consider database query costs when working with very large datasets.

  7. Use a reasonable number of records per page.

  8. Validate filter inputs when the application has complex search requirements.

Django's standard Paginator commonly uses a record count to determine the number of pages. Very large datasets and high-offset pages may require more advanced pagination strategies.

14. Practical Example: Complete Student Listing Workflow

The complete workflow is:

  1. A user opens the student listing page.

  2. Django retrieves student records from the database.

  3. The user enters a name or applies filters.

  4. Django applies the search conditions.

  5. The filtered QuerySet is passed to Paginator.

  6. The paginator returns the requested page.

  7. The template displays the current page's records.

  8. Previous, Next, and page-number links allow navigation.

  9. Active filter values remain available while navigating.

This pattern is useful for student management systems, employee directories, product listings, hospital patient lists, and administrative dashboards.

Frequently Asked Questions (FAQs)

Q1. What is pagination in Django?

Pagination divides a large collection of records into smaller pages using Django's Paginator class.

Q2. Why is pagination important?

It makes large lists easier to navigate and limits the number of records displayed on a single page.

Q3. What does Paginator(queryset, 10) mean?

It creates a paginator that divides the supplied collection into pages containing up to 10 records each.

Q4. What is the difference between page() and get_page()?

page() raises exceptions for invalid or out-of-range page numbers. get_page() handles these cases by returning an appropriate page when possible.

Q5. Why should search filters be applied before pagination?

Because pagination should operate on the filtered results so that the page count and displayed records match the search criteria.

Q6. Why use the GET method for search forms?

GET places search parameters in the URL, making results easier to bookmark and share.

Q7. How can you preserve search filters across pages?

Include the active filter parameters in each pagination link.

Q8. Does pagination always improve database performance?

Not necessarily. It limits the records displayed per page, but query counts, sorting, indexes, and database size also affect performance.

Interview Questions and Answers

Q1. Which Django class is used for pagination?

The Paginator class from django.core.paginator.

Q2. What is a Page object?

A Page object represents one page of results and provides methods and properties for accessing its records and navigation information.

Q3. What does has_next() do?

It checks whether a subsequent page exists.

Q4. How do you implement search and pagination together?

Apply the search filters to the QuerySet, create a paginator from the filtered QuerySet, and retrieve the requested page.

Q5. What happens when a user requests a page number that does not exist?

With get_page(), Django returns the last page for an out-of-range page number, or the first page for an invalid page value, provided pages exist.

Q6. How can you search multiple fields using one input?

Use Q objects with OR conditions, such as matching either name or email.

Q7. Why is stable ordering important for pagination?

Stable ordering helps ensure that records appear in a predictable sequence as users move between pages.

Practical Assignment

Build a paginated Student Management listing page.

Requirements:

  1. Display 10 students per page.

  2. Add Previous, Next, First, and Last links.

  3. Display the current page number and total pages.

  4. Search students by name.

  5. Filter students by city and course.

  6. Search by either name or email using Q objects.

  7. Preserve active search filters during pagination.

  8. Display a friendly message when no results are found.

  9. Add a Reset button to clear all filters.

  10. Test the page with multiple records and different search conditions.

Expected Outcome: You will be able to build a practical Django listing interface with pagination, dynamic search, multiple filters, and reliable page navigation.

Conclusion

Django Pagination and Search Filters are essential for creating scalable and user-friendly applications. By combining Paginator, QuerySets, GET parameters, and Q objects, developers can build efficient listing pages that allow users to find information quickly.

These concepts are widely used in admin panels, student management systems, employee directories, e-commerce websites, and other database-driven applications.

In the next module, you will learn about Django Forms and ModelForms, including form validation, handling user input, and saving data securely to the database.

←
Post 15 of 29
→