Wagtail Tutorials #6: Import Bootstrap Theme Into Wagtail Blog

Michael Yin

Full Stack Developer

Last updated on Jan 02 2021

Table of Contents


In this wagtail tutorial, I will teach you how to quickly import an existing theme into Wagtail blog to make our blog look more decent, it really makes sense especially when you want to import theme from WordPress or themeforest, and I will talk about how to use django-el-pagination to add pagination to our blog app.

Import bootstrap theme

To make the reader easy to understand CSS, HTML during this process, the theme I will import is bootstrap blog from startbootstrap.com. This theme is a very clean theme and supports mobile device very well. Here is the screenshot from the preview website, it is based on Bootstrap4 now.

Let's download the theme file and take a look at what is in the file, here is the project structure.

├── README.md
├── css
│   ├── blog-home.css
│   ├── bootstrap.css
│   └── bootstrap.min.css
├── index.html
└── js
    ├── bootstrap.js
    ├── bootstrap.min.js
    └── jquery.js

As you can see, the index.html is the html source of blog page, css, js, fonts have static files to make the page display as expect.

Now we start to import these files into our wagtail blog project, to make our blog app to a reusable Django app, I prefer to put these files in the blog app directory.

Create static directory in blog app, to make Django can point to the right static file, remember to create blog directory and then put the fils in it, this method called Static file namespacing.

├── blog  ------------------->  This is the blog app directory
    ├── static
    │   └── blog
    │       ├── LICENSE
    │       ├── README.md
    │       ├── css
    │       │   ├── blog-home.css
    │       │   ├── bootstrap.css
    │       │   └── bootstrap.min.css
    │       ├── index.html
    │       └── js
    │           ├── bootstrap.js
    │           ├── bootstrap.min.js
    │           └── jquery.js
    ├── templatetags
    │   ├── __init__.py
    │   └── blogapp_tags.py
    ├── tests.py
    ├── urls.py
    └── views.py

Modify template

Now the static files are ready to go, next step is to modify the blog page templates. Since it is a tedious job, I will write some points you should keep in mind here.

  • Finish first, improve second.
  • Copy the html from theme file to blog page template, change static path to make it work with django static files.
{% load static wagtailcore_tags wagtailimages_tags blogapp_tags %}
<link href="{% static 'blog/css/bootstrap.min.css' %}" rel="stylesheet">
  • Modify code to enable it to show the contents from blog posts as expected.
{% for post in posts %}
  <div class="card mb-4">

    {% if post.header_image %}
        {% image post.header_image original as header_image %}
        <a href="{% post_date_url post blog_page %}">
          <img src="{{ header_image.url }}" class="card-img-top" ></img>
    {% endif %}

    <div class="card-body">
      <h2 class="card-title">
        <a href="{% post_date_url post blog_page %}">{{ post.title }}</a>
      <p class="card-text">
        {% if post.excerpt %}
            {{ post.excerpt|markdown|safe }}
        {% else %}
            {{ post.body|markdown|safe|truncatewords_html:70 }}
        {% endif %}
      <a href="{% post_date_url post blog_page %}" class="btn btn-primary">Read More &rarr;</a>

    <div class="card-footer text-muted">
        Posted on {{ post.date }}

{% endfor %}
  • If needed, create components to make the html structure easy to manage, for example, base.html, sidebar.html, header.html etc..
<!-- Page Content -->
<div class="container">
  <div class="row">
        {% block maincontent %}
            <div class="col-md-8">
                {% block content %}

                {% endblock %}
            {% include 'blog/components/sidebar.html' %}
        {% endblock %}
  <!-- /.row -->
<!-- /.container -->

If you want to get the full source code of blog_page.html, check the code at the end of this tutorial.

Template tags

For some reason, we need do some extra job to make the category and tags work as we expect since there is no direct way to display it in template. Do you remember I use blogapp_tags to extend the function of Django template in the previous chapter? We use the same way to create some custom Django template tags.

Edit blog/templatetags/blogapp_tags.py

from ..models import BlogCategory as Category, Tag

def tags_list(context, limit=None):
    blog_page = context['blog_page']
    tags = Tag.objects.all()
    if limit:
        tags = tags[:limit]
    return {
        'blog_page': blog_page, 'request': context['request'], 'tags': tags}

def categories_list(context):
    blog_page = context['blog_page']
    categories = Category.objects.all()
    return {
        'blog_page': blog_page, 'request': context['request'], 
        'categories': categories}

From the code above, you can see I create two template tags, one is to show all tags of blog app, the other is to show all category links of blog app. The data returned from the function will be used to render html in template included in decorator.

I will paste the code of blog/components/categories_list.html here so you can better understand the workflow.

{% load i18n wagtailroutablepage_tags %}

<div class="card my-4">
<h5 class="card-header">Categories</h5>
<div class="card-body">
  <div class="row">
    <div class="col-lg-12">
      <ul class="list-unstyled mb-0">

        {% for category in categories %}
                <a href="{% routablepageurl blog_page "post_by_category" category.slug %}">
                    {{ category.name }}
        {% empty %}
            {% trans 'No categories yet' %}
        {% endfor %}


Variable categories here is the value returned by categories_list function, the returned dict can be seen as a context, which would be used to render HTML with blog/components/categories_list.html. After all stuff setup, we can just show the category links in blog_page.html by writing {% categories_list %}. which is very clean and DRY.

Use Django-el-pagination to provide pagination in wagtail

To make our blog support pagination, we use django-el-pagination to provide this fucntion, the reason I choose django-el-pagination here is it is easy to customize it and the repo on Github is activity maintained.

pip install django-el-pagination
# add 'el_pagination' in the INSTALLED_APPS of settings.py to activate it

Now el_pagination is ready to be called, first we try to use in in our template, edit blog/templates/blog/blog_page.html

{% load static wagtailcore_tags wagtailimages_tags blogapp_tags el_pagination_tags %}

{% paginate 5 posts %}

{% for post in posts %}
{% endfor %}

<!-- Pagination -->
<ul class="pagination justify-content-center mb-4">
  {% show_pages %}

There are some points you should notice from the code above:

  1. Remember to load el_pagination_tags in template before use it
  2. For collection you want to paginate, call {% paginate 5 posts %} to paginate, the 5 here is the number posts displayed per page, change it if needed.
  3. Call show_pages to generate the pagination HTML in template

It is so easy! Right? But wait, how to change the pagination HTML generated because it seems so ugly? We can override the template of el_pagination_tags to change the html generated.

To make blog app more reusable, we create the template in blog/templates/el_pagination

├── README.md
├── blog
│   ├── templates
│   │   ├── blog  ---------> Theme file location, created above
│   │   └── el_pagination
│   │       ├── current_link.html
│   │       ├── page_link.html
│   │       └── show_pages.html

Now let me explain what is the meaning of the template here. current_link.html it use to show the current link, page_link.html is used to show links other than current link, show_pages.html can be ignored here.

Edit current_link.html

<li class="page-item disabled">
    <a href="{{ page.path }}"
        rel="{{ querystring_key }}{% if add_nofollow %} nofollow{% endif %}"
        class="page-link">{{ page.label|safe }}

Edit page_link.html

<li class="page-item">
    <a href="{{ page.path }}"
        rel="{{ querystring_key }}{% if add_nofollow %} nofollow{% endif %}"
        class="page-link">{{ page.label|safe }}

Now the pagination block looks more decent. Below is the screenshot of our blog page.


In this Wagtail tutorial, I talked about how to import an existing theme into Wagtail project, mentioned some rules you should follow to make your Django app reusable, and explained how to add pagination to our Wagtail Blog app.

The source code of this Wagtail tutorial is available on Github, you can get it here wagtail-bootstrap-blog, and I would appreciate that if you could star my repo.

You can also check the full list of my wagtail tutorial here wagtail tutorial series

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

This book will teach you how to build a modern blog with Wagtail CMS

Read More


Get notified about new great Web Development Tutorial