Add Pagination Component to Wagtail

Table of Contents

Wagtail Tutorial Series:

To learn more about Wagtail CMS, please check Build Blog With Wagtail CMS (4.0.0)

  1. Create Wagtail Project
  2. Modern Frontend Techs for Wagtail
  3. Dockerizing Wagtail App
  4. Add Blog Models to Wagtail
  5. How to write Wagtail page template
  6. Create Stylish Wagtail Pages with Tailwind CSS
  7. How to use StreamField in Wagtail
  8. Wagtail Routable Page
  9. Add Pagination Component to Wagtail
  10. Customize Wagtail Page URL
  11. Add Full Text Search to Wagtail
  12. Add Markdown Support to Wagtail
  13. Add LaTeX Support & Code Highlight In Wagtail
  14. How to Build Form Page in Wagtail
  15. How to Create and Manage Menus in Wagtail
  16. Wagtail SEO Guide
  17. Online Demo http://wagtail-blog.accordbox.com/
  18. Source code: https://github.com/AccordBox/wagtail-tailwind-blog

Wagtail Tips:

  1. Wagtail Tip #1: How to replace ParentalManyToManyField with InlinePanel
  2. Wagtail Tip #2: How to Export & Restore Wagtail Site

Write style in Wagtail:

  1. How to use SCSS/SASS in your Django project (Python Way)
  2. How to use SCSS/SASS in your Django project (NPM Way)

Other Wagtail Topics:

  1. How to make Wagtail project have good coding style
  2. How to do A/B Testing in Wagtail CMS 
  3. How to build a landing page using Wagtail CMS 
  4. How to support multi-language in Wagtail CMS 
  5. Add Bootstrap Theme to Wagtail

More Wagtail articles and eBooks written by me

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 wagtail_app/blog/models.py

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

class BlogPage(RoutablePageMixin, Page):

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

    def get_posts(self):
        return PostPage.objects.descendant_of(self).live()

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

        return posts

Notes:

  1. In get_paginated_posts, we use Django Paginator to create paginator instance from the self.posts, the number 2 here is the number of item per page.
  2. The page number is from the URl querystring ?page=

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

{# Pagination #}
{# https://docs.djangoproject.com/en/4.0/topics/pagination/#paginating-a-listview #}

<nav aria-label="Page navigation">
  <ul class="flex pl-0 rounded list-none flex-wrap">

    {% if posts.has_previous %}
      <li>
        <a href="?page={{ posts.previous_page_number }}"
           class="text-blue-500 dark:text-white inline-block py-2 px-4 bg-white  border border-gray-300 border-r-0 rounded-l-lg hover:bg-blue-500 hover:text-white">
          Previous
        </a>
      </li>
    {% else %}
      <li>
        <a href="#" class="pointer-events-none text-gray-300 inline-block py-2 px-4 bg-white  border border-gray-300 border-r-0 rounded-l-lg hover:bg-blue-500 hover:text-white">
          Previous
        </a>
      </li>
    {% endif %}

    {% if posts.has_next %}
      <li>
        <a href="?page={{ posts.next_page_number }}"
           class="text-blue-500 dark:text-white inline-block py-2 px-4 bg-white  border border-gray-300 rounded-r-lg hover:bg-blue-500 hover:text-white">
          Next
        </a>
      </li>
    {% else %}
      <li>
        <a href="#" class="pointer-events-none text-gray-300 inline-block py-2 px-4 bg-white  border border-gray-300 rounded-r-lg hover:bg-blue-500 hover:text-white">
          Next
        </a>
      </li>
    {% endif %}

  </ul>
</nav>

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 generate the next page url, it would be http://127.0.0.1:8000/?q=wagtail?page=2, the q=wagtail is dropped by the browser, and we would visit http://127.0.0.1:8000/?page=2

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

Update wagtail_app/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_app/templates/blog/blog_page.html

{% load wagtailcore_tags wagtailimages_tags blogapp_tags %}

...

<nav aria-label="Page navigation">
  <ul class="flex pl-0 rounded list-none flex-wrap">

    {% if posts.has_previous %}
      <li>
        <a href="{% url_replace request page=posts.previous_page_number %}"
           class="text-blue-500 dark:text-white inline-block py-2 px-4 bg-white  border border-gray-300 border-r-0 rounded-l-lg hover:bg-blue-500 hover:text-white">
          Previous
        </a>
      </li>
    {% else %}
      <li>
        <a href="#" class="pointer-events-none text-gray-300 inline-block py-2 px-4 bg-white  border border-gray-300 border-r-0 rounded-l-lg hover:bg-blue-500 hover:text-white">
          Previous
        </a>
      </li>
    {% endif %}

    {% if posts.has_next %}
      <li>
        <a href="{% url_replace request page=posts.next_page_number %}"
           class="text-blue-500 dark:text-white inline-block py-2 px-4 bg-white  border border-gray-300 rounded-r-lg hover:bg-blue-500 hover:text-white">
          Next
        </a>
      </li>
    {% else %}
      <li>
        <a href="#" class="pointer-events-none text-gray-300 inline-block py-2 px-4 bg-white  border border-gray-300 rounded-r-lg hover:bg-blue-500 hover:text-white">
          Next
        </a>
      </li>
    {% endif %}

  </ul>
</nav>

Notes:

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

Now if we visit http://127.0.0.1:8000/?q=wagtail the generated next page url will be http://127.0.0.1:8000/?q=wagtail&page=2

Wagtail Tutorial Series:

To learn more about Wagtail CMS, please check Build Blog With Wagtail CMS (4.0.0)

  1. Create Wagtail Project
  2. Modern Frontend Techs for Wagtail
  3. Dockerizing Wagtail App
  4. Add Blog Models to Wagtail
  5. How to write Wagtail page template
  6. Create Stylish Wagtail Pages with Tailwind CSS
  7. How to use StreamField in Wagtail
  8. Wagtail Routable Page
  9. Add Pagination Component to Wagtail
  10. Customize Wagtail Page URL
  11. Add Full Text Search to Wagtail
  12. Add Markdown Support to Wagtail
  13. Add LaTeX Support & Code Highlight In Wagtail
  14. How to Build Form Page in Wagtail
  15. How to Create and Manage Menus in Wagtail
  16. Wagtail SEO Guide
  17. Online Demo http://wagtail-blog.accordbox.com/
  18. Source code: https://github.com/AccordBox/wagtail-tailwind-blog

Wagtail Tips:

  1. Wagtail Tip #1: How to replace ParentalManyToManyField with InlinePanel
  2. Wagtail Tip #2: How to Export & Restore Wagtail Site

Write style in Wagtail:

  1. How to use SCSS/SASS in your Django project (Python Way)
  2. How to use SCSS/SASS in your Django project (NPM Way)

Other Wagtail Topics:

  1. How to make Wagtail project have good coding style
  2. How to do A/B Testing in Wagtail CMS 
  3. How to build a landing page using Wagtail CMS 
  4. How to support multi-language in Wagtail CMS 
  5. Add Bootstrap Theme to Wagtail

More Wagtail articles and eBooks written by me

Launch Products Faster with Django

SaaS Hammer helps you launch products in faster way. It contains all the foundations you need so you can focus on your product.

Michael Yin

Michael is a Full Stack Developer from China who loves writing code, tutorials about Django, and modern frontend tech.

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.

Django SaaS Template

It aims to save your time and money building your product

Learn More

Build Jamstack web app with Next.js and Wagtail CMS.

Read More
© 2018 - 2024 AccordBox