Bootstrap4-based Pagination Buttons for Django ListView

 May 11, 2018     0 comments

Often, when displaying multi-page lists in html pages rendered from Django templates, we need to include a pagination control, usually a set of navigation buttons, that is both functional and good looking. In this post I'll describe the solution used in this blog to paginate the list of search results. To be honest, I borrowed the whole idea from here and adapted to the current Django paginator API and Bootstrap 4 design with Font Awesome icons. This pagination buttons template is meant for views based on ListView CBV. However, it can also be used with function-based views if you add page_obj and paginator template variables that are instances of Page and Paginator objects respectively. The pagination buttons template is rendered via a template tag.

paginator.html template based on Bootstrap 4 with Font Awesome icons for "previous" and "next" buttons:

 Click to show
<div class="btn-group" role="group">
  {% if has_previous %}
     <a class="btn btn-secondary" href="{{ request.path }}?page={{ previous }}"><i class="fas fa-angle-left"></i></a>
  {% else %}
     <button type="button" class="btn btn-secondary" disabled="disabled"><i class="fas fa-angle-left"></i></button>
  {% endif %}

  {% if show_first %}
     <a class="btn btn-secondary" href="{{ request.path }}?page=1">1</a>
     <button type="button" class="btn btn-secondary" disabled="disabled">...</button>  
  {% endif %}

  {% for linkpage in page_numbers %}
    {% ifequal linkpage page %}
       <button type="button" class="btn btn-secondary active" >{{ page }}</button>
    {% else %}
       <a class="btn btn-secondary" href="{{ request.path }}?page={{ linkpage }}">{{ linkpage }}</a>
    {% endifequal %}
  {% endfor %}

  {% if show_last %}
     <button type="button" class="btn btn-secondary" disabled="disabled">...</button>
     <a class="btn btn-secondary" href="{{ request.path }}?page=last">{{ pages }}</a>
  {% endif %}

  {% if has_next %}
     <a class="btn btn-secondary" href="{{ request.path }}?page={{ next }}"><i class="fas fa-angle-right"></i></a>
  {% else %}
     <button type="button" class="btn btn-secondary" disabled="disabled"><i class="fas fa-angle-right"></i></button>
  {% endif %}
</div>

render_paginator template tag used for rendering the preceding template:

 Click to show
from django import template
from django.core.paginator import EmptyPage

register = template.Library()


@register.inclusion_tag('myapp/paginator.html', takes_context=True)
def render_paginator(context, adjacent_pages=3):
    """
    Inclusion tag

    Renders paginator for multi-page lists.

    Adds pagination context variables for use in displaying first, adjacent and
    last page links in addition to those created by the object_list generic
    view.

    :param context: parent template context
    :param adjacent_pages: the number of pages adjacent to the current
    :return: context for rendering paginator html code
    """
    start_page = max(context['page_obj'].number - adjacent_pages, 1)
    if start_page <= 3:
        start_page = 1
    end_page = context['page_obj'].number + adjacent_pages + 1
    if end_page >= context['paginator'].num_pages - 1:
        end_page = context['paginator'].num_pages + 1
    page_numbers = [n for n in range(start_page, end_page) if n in range(1, context['paginator'].num_pages + 1)]
    page_obj = context['page_obj']
    paginator = context['paginator']
    try:
        next_ = context['page_obj'].next_page_number()
    except EmptyPage:
        next_ = None
    try:
        previous = context['page_obj'].previous_page_number()
    except EmptyPage:
        previous = None
    return {
        'page_obj': page_obj,
        'paginator': paginator,
        'page': context['page_obj'].number,
        'pages': context['paginator'].num_pages,
        'page_numbers': page_numbers,
        'next': next_,
        'previous': previous,
        'has_next': context['page_obj'].has_next(),
        'has_previous': context['page_obj'].has_previous(),
        'show_first': 1 not in page_numbers,
        'show_last': context['paginator'].num_pages not in page_numbers,
        'request': context['request']
    }

The template tag takes one argument that is the number of buttons adjacent to the current page button.

To render a paginator simply add the render_paginator template tag to your template that displays a list of some objects. Again, to display your list you need to use either ListView CBV or a function-based view with the necessary context variables as described above.

...
{% render_paginator 3 %}
...

Result:

The first page: 

A page in the middle:

The last page: 

  DjangoHTMLPython