Add pagination component to Wagtail

Michael Yin

Last updated on October 07 2021

Table of Contents

Wagtail Tutorial Series:

  1. Create Wagtail Project
  2. Dockerizing Wagtail App
  3. Add Blog Models to Wagtail
  4. How to write Wagtail page template
  5. Add Bootstrap Theme to Wagtail
  6. How to use StreamField in Wagtail
  7. Wagtail Routable Page
  8. Add pagination component to Wagtail
  9. Customize Wagtail Page URL

The source code is available on https://github.com/AccordBox/wagtail-bootstrap-blog

Objectives

By the end of this chapter, you should be able to:

  1. Learn how to build pagination with Django Paginator
  2. Handle the querystring with custom Django template tag

Pagination

Let's add pagination to our BlotPage

Update BlogPage.get_context in blog/models.py

from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator

class BlogPage(RoutablePageMixin, Page):

    def get_context(self, request, *args, **kwargs):
        context = super().get_context(request, *args, **kwargs)
        context['blog_page'] = self

        # https://docs.djangoproject.com/en/3.1/topics/pagination/#using-paginator-in-a-view-function
        paginator = Paginator(self.posts, 2)
        page = request.GET.get("page")
        try:
            posts = paginator.page(page)
        except PageNotAnInteger:
            posts = paginator.page(1)
        except EmptyPage:
            posts = paginator.object_list.none()

        context["posts"] = posts
        return context

Notes:

  1. We removed the context['posts'] = self.posts
  2. Use Django Paginator to create paginator instance from the self.posts, the number 2 here is the number of item per page.
  3. The page number is from the URl querystring ?page=

Next, let's add add pagination component to the wagtail_bootstrap_blog/templates/blog/blog_page.html

{# Pagination #}
{# https://docs.djangoproject.com/en/3.1/topics/pagination/#paginating-a-listview #}
<ul class="pagination">
  {% if posts.has_previous %}
    <li class="page-item">
      <a href="?page={{ posts.previous_page_number }}" class="page-link">
        Previous
      </a>
    </li>
  {% else %}
    <li class="page-item disabled">
      <a href="#" class="page-link">
        Previous
      </a>
    </li>
  {% endif %}

  {% if posts.has_next %}
    <li class="page-item">
      <a href="?page={{ posts.next_page_number }}" class="page-link">
        Next
      </a>
    </li>
  {% else %}
    <li class="page-item disabled">
      <a href="#" class="page-link">
        Next
      </a>
    </li>
  {% endif %}
</ul>

Notes:

  1. We use ?page={{ posts.previous_page_number }} to append the page querystring to the url to make pagination work.
  2. As for the number of item per page, you can get it from the page field, or even Django global settings.

URL which already has querystring

Let's take a look at this URL http://www.example.com/?q=wagtail

If we use code above to generat the next url, it would be http://www.example.com/?q=wagtail&page=2

This is not correct and we should use a better way to handle the querystring in the URL.

Update blog/templatetags/blogapp_tags.py

from urllib.parse import urlparse, urlunparse
from django.http import QueryDict

@register.simple_tag
def url_replace(request, **kwargs):
    """
    This tag can help us replace or add querystring

    TO replace the page field in URL
    {% url_replace request page=page_num %}
    """
    (scheme, netloc, path, params, query, fragment) = urlparse(request.get_full_path())
    query_dict = QueryDict(query, mutable=True)
    for key, value in kwargs.items():
        query_dict[key] = value
    query = query_dict.urlencode()
    return urlunparse((scheme, netloc, path, params, query, fragment))

Notes:

  1. Here we added a django template tag to help us solve the problem.
  2. It would get the query_dict from the current request url, and then use the kwargs to generate the new url.

Update pagination in wagtail_bootstrap_blog/templates/blog/blog_page.html

<ul class="pagination">
  {% if posts.has_previous %}
    <li class="page-item">
      <a href="{% url_replace request page=posts.previous_page_number %}" class="page-link">
        Previous
      </a>
    </li>
  {% else %}
    <li class="page-item disabled">
      <a href="#" class="page-link">
        Previous
      </a>
    </li>
  {% endif %}

  {% if posts.has_next %}
    <li class="page-item">
      <a href="{% url_replace request page=posts.next_page_number %}" class="page-link">
        Next
      </a>
    </li>
  {% else %}
    <li class="page-item disabled">
      <a href="#" class="page-link">
        Next
      </a>
    </li>
  {% endif %}
</ul>

Notes:

  1. We replaced ?page={{ posts.previous_page_number }} with {% url_replace request page=posts.previous_page_number %}
  2. The url_replace would help us keep existing querystring in the url when adding page querystring.

Wagtail Tutorial Series:

  1. Create Wagtail Project
  2. Dockerizing Wagtail App
  3. Add Blog Models to Wagtail
  4. How to write Wagtail page template
  5. Add Bootstrap Theme to Wagtail
  6. How to use StreamField in Wagtail
  7. Wagtail Routable Page
  8. Add pagination component to Wagtail
  9. Customize Wagtail Page URL

The source code is available on https://github.com/AccordBox/wagtail-bootstrap-blog


Michael Yin

Michael is a Full Stack Developer from China who loves writing code, tutorials about Django, Wagtail CMS and React.

He has published some ebooks on leanpub and tech course on testdriven.io.

He is also the founder of the AccordBox which provides the web development services.


Table of Contents

Search

This book will teach you how to build a SPA (single-page application) with React and Wagtail CMS

Read More

Subscribe

Get notified about new great Web Development Tutorial