Learn how to render a bootstrap 5 based paginator widget in a django class based list view.

In this example, I'm going to use a very simple implementation of a list view in django, where i'm going to simply render a view with 10 items in the database, so I'm going to paginate by 1, so I can see a single item rendered in the view on every page. All you need to do to paginate your model in a ListView is to declare and assign a value to paginate_by

from django.views.generic import ListView
from .models import Items

class ItemsListView(ListView):
    model = Items
    context_object_name = 'items'
    template_name = 'items/index.html'
    # Set the paginate_by parameter to the number of items that you want to 
    # appear on every page of the view
    paginate_by = 1

Then in your template, you can simply iterate over every item in the context_object_name, only the items of the page will be rendered:

<div class="container">
    <div class="row">
        {% comment %}
            In this block we will display every single item in a 4 columns grid
        {% endcomment %}
        {% for item in items %}
            <div class="col-lg-3">
                {{ item.id }}). {{ item.name }}
            </div>
        {% endfor %}
    </div>
</div>

Now, to render the pagination widget, you can use the following code that will render a Bootstrap 5 pagination widget. It will render 4 interaction buttons when there are multiple pages to navigate (First page, Previous page, Next Page and Last Page), as well as up to 7 buttons containing the number of the pages of the paginator:

{% if page_obj.has_other_pages %}
    <nav>
        <ul class="pagination justify-content-center">
            {% if page_obj.has_previous %}
                <li class="page-item">
                    <a class="page-link" href="?page=1">
                        First
                    </a>
                </li>
                <li class="page-item">
                    <a class="page-link" href="?page={{ page_obj.previous_page_number }}">
                        Previous
                    </a>
                </li>
            {% endif %}

            {% for page_number in page_obj.paginator.page_range %}
                {% comment %}
                    This conditional allows us to display up to 3 pages before and after the current page
                    If you decide to remove this conditional, all the pages will be displayed

                    You can change the 3 to any number you want e.g
                    To display only 5 pagination items, change the 3 to 2 (2 before and 2 after the current page)
                {% endcomment %}
                {% if page_number <= page_obj.number|add:3 and page_number >= page_obj.number|add:-3 %}
                    {% if page_obj.number == page_number %}
                        <li class="page-item active">
                            <a class="page-link" href="?page={{ page_number }}">
                                {{ page_number }}
                            </a>
                        </li>
                    {% else %}
                        <li class="page-item">
                            <a class="page-link" href="?page={{ page_number }}">
                                {{ page_number }}
                            </a>
                        </li>
                    {% endif %}
                {% endif %}
            {% endfor %}

            {% if page_obj.has_next %}
                <li class="page-item">
                    <a class="page-link" href="?page={{ page_obj.next_page_number }}">
                        Next
                    </a>
                </li>
                <li class="page-item">
                    <a class="page-link" href="?page={{ page_obj.paginator.num_pages }}">
                        Last
                    </a>
                </li>
            {% endif %}
        </ul>
    </nav>
{% endif %}

At the end, your template file will look something like this:

{% extends 'base.html' %}

{% block content %}    
    <div class="container">
        <div class="row">
            {% comment %}
                In this block we will display every single item in a 4 columns grid
            {% endcomment %}
            {% for item in items %}
                <div class="col-lg-3">
                    {{ item.id }}). {{ item.name }}
                </div>
            {% endfor %}
        </div>
    </div>

    {% comment %}
        Render a Bootstrap 5 based pagination element with Django default's pagination
    {% endcomment %}
    <div class="row">
        <div class="col-lg-12">
            {% if page_obj.has_other_pages %}
                <nav>
                    <ul class="pagination justify-content-center">
                        {% if page_obj.has_previous %}
                            <li class="page-item">
                                <a class="page-link" href="?page=1">
                                    First
                                </a>
                            </li>
                            <li class="page-item">
                                <a class="page-link" href="?page={{ page_obj.previous_page_number }}">
                                    Previous
                                </a>
                            </li>
                        {% endif %}

                        {% for page_number in page_obj.paginator.page_range %}
                            {% comment %}
                                This conditional allows us to display up to 3 pages before and after the current page
                                If you decide to remove this conditional, all the pages will be displayed

                                You can change the 3 to any number you want e.g
                                To display only 5 pagination items, change the 3 to 2 (2 before and 2 after the current page)
                            {% endcomment %}
                            {% if page_number <= page_obj.number|add:3 and page_number >= page_obj.number|add:-3 %}
                                {% if page_obj.number == page_number %}
                                    <li class="page-item active">
                                        <a class="page-link" href="?page={{ page_number }}">
                                            {{ page_number }}
                                        </a>
                                    </li>
                                {% else %}
                                    <li class="page-item">
                                        <a class="page-link" href="?page={{ page_number }}">
                                            {{ page_number }}
                                        </a>
                                    </li>
                                {% endif %}
                            {% endif %}
                        {% endfor %}

                        {% if page_obj.has_next %}
                            <li class="page-item">
                                <a class="page-link" href="?page={{ page_obj.next_page_number }}">
                                    Next
                                </a>
                            </li>
                            <li class="page-item">
                                <a class="page-link" href="?page={{ page_obj.paginator.num_pages }}">
                                    Last
                                </a>
                            </li>
                        {% endif %}
                    </ul>
                </nav>
            {% endif %}
        </div>
    </div>
{% endblock %}

In my template (a customized template based on Bootstrap 5), once this page is rendered the paginator looks like this:

Django Bootstrap 5 Paginator

Of course this is the default pagination widget of Bootstrap, however this is a nice start point if you're looking for extra customization of the widget in your Django project.

Including all GET parameters of the request in the pages buttons

In the previous implementation, we didn't handle what happens with the other GET parameters of the URL, we forced a single parameter namely page. In case that you need to include as well the other GET parameters from the request on every link of every page, you need to include the following piece of template when the URL is generated for every button:

{% comment %}
    This renders the rest of the query string like this (excluding the page number):
    &param2=search+query&param3=some+text
{% endcomment %}
{% for key, value in request.GET.items %}
    {% if key != 'page' %}
        &{{ key }}={{ value }}
    {% endif %}
{% endfor %}

For example, in our implementation you can replace the page range block with the following one to include all the GET parameters (don't forget to do the same with the Next and Last button):

{% if page_obj.has_other_pages %}
    <nav>
        <ul class="pagination justify-content-center">
            {% if page_obj.has_previous %}
                <li class="page-item">
                    <a class="page-link" href="?page=1{% for key, value in request.GET.items %}{% if key != 'page' %}&{{ key }}={{ value }}{% endif %}{% endfor %}">First</a>
                </li>
                <li class="page-item">
                    <a class="page-link" href="?page={{ page_obj.previous_page_number }}{% for key, value in request.GET.items %}{% if key != 'page' %}&{{ key }}={{ value }}{% endif %}{% endfor %}">
                        Previous
                    </a>
                </li>
            {% endif %}

            {% for page_number in page_obj.paginator.page_range %}
                {% if page_number <= page_obj.number|add:3 and page_number >= page_obj.number|add:-3 %}
                    {% if page_obj.number == page_number %}
                        <li class="page-item active">
                            <a class="page-link" href="?page={{ page_number }}{% for key, value in request.GET.items %}{% if key != 'page' %}&{{ key }}={{ value }}{% endif %}{% endfor %}">{{ page_number }}</a>
                        </li>
                    {% else %}
                        <li class="page-item">
                            <a class="page-link" href="?page={{ page_number }}{% for key, value in request.GET.items %}{% if key != 'page' %}&{{ key }}={{ value }}{% endif %}{% endfor %}">{{ page_number }}</a>
                        </li>
                    {% endif %}
                {% endif %}
            {% endfor %}

            {% if page_obj.has_next %}
                <li class="page-item">
                    <a class="page-link" href="?page={{ page_obj.next_page_number }}{% for key, value in request.GET.items %}{% if key != 'page' %}&{{ key }}={{ value }}{% endif %}{% endfor %}">
                        Next
                    </a>
                </li>
                <li class="page-item">
                    <a class="page-link" href="?page={{ page_obj.paginator.num_pages }}{% for key, value in request.GET.items %}{% if key != 'page' %}&{{ key }}={{ value }}{% endif %}{% endfor %}">
                        Last
                    </a>
                </li>
            {% endif %}
        </ul>
    </nav>
{% endif %}

Happy coding ❤️!


Senior Software Engineer at Software Medico. Interested in programming since he was 14 years old, Carlos is a self-taught programmer and founder and author of most of the articles at Our Code World.

Sponsors