Add Bootstrap Theme to Wagtail


Michael Yin

Full Stack Developer

Last updated on Jul 01 2021


Table of Contents

Objectives

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

  1. Import Bootstrap theme to Wagtail project
  2. Build custom Django template tag

Bootstrap

Bootstrap: The most popular HTML, CSS, and JavaScript framework for developing responsive, mobile first projects on the web.

Design

BlogPage

PostPage

Base Template

Let's update wagtail_bootstrap_blog/templates/base.html to import Bootstrap theme files.

{% load static wagtailuserbar %}

<!DOCTYPE html>
<html class="no-js" lang="en">
    <head>
        <meta charset="utf-8" />
        <title>
            {% block title %}
                {% if self.seo_title %}{{ self.seo_title }}{% else %}{{ self.title }}{% endif %}
            {% endblock %}
            {% block title_suffix %}
                {% with self.get_site.site_name as site_name %}
                    {% if site_name %}- {{ site_name }}{% endif %}
                {% endwith %}
            {% endblock %}
        </title>
        <meta name="description" content="" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />

        {# Global stylesheets #}
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css" integrity="sha512-+4zCK9k+qNFUR5X+cKL9EIR+ZOhtIloNl9GIKS57V1MyNsYpYcUrUeQc9vNfzsWfV28IaLL3i96P9sdNyeRssA==" crossorigin="anonymous" />
        <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.3/css/bootstrap.min.css" integrity="sha512-oc9+XSs1H243/FRN9Rw62Fn8EtxjEYWHXRvjS43YtueEewbS6ObfXcJNyohjHqVKFPoXXUxwc+q1K7Dee6vv9g==" crossorigin="anonymous" />
        {% block extra_css %}
            {# Override this in templates to add extra stylesheets #}
        {% endblock %}
    </head>

    <body class="{% block body_class %}{% endblock %}">
        {% wagtailuserbar %}

        {% include 'blog/components/navbar.html' %}

        <div class="container">
          <div class="row">
              <div class="col-md-8">
                {% block content %}{% endblock %}
              </div>
              {% include 'blog/components/sidebar.html' %}
          </div>
        </div>

        {% include 'blog/components/footer.html' %}

        {# Global javascript #}
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.slim.min.js" integrity="sha512-/DXTXr6nQodMUiq+IUJYCt2PPOUjrHJ9wFrqpJ3XkgPNOZVfMok7cRw6CSxyCQxXn6ozlESsSh1/sMCTF1rL/g==" crossorigin="anonymous"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.3/js/bootstrap.bundle.min.js" integrity="sha512-iceXjjbmB2rwoX93Ka6HAHP+B76IY1z0o3h+N1PeDtRSsyeetU3/0QKJqGyPJcX63zysNehggFwMC/bi7dvMig==" crossorigin="anonymous"></script>
        {% block extra_js %}
            {# Override this in templates to add extra javascript #}
        {% endblock %}
    </body>
</html>

Notes:

  1. Template inheritance allows you to build a base “skeleton” template that contains all the common elements of your site and defines blocks that child templates can override
  2. Here we import jQuery, bootstrap and font-awesome from cdnjs, which is a free CDN service provided by CloudFlare.
  3. Then we created a container div which has content block and sidebar.
  4. With Django include template tag, we can loads a template and renders it with the current context. Django doc

Let's create wagtail_bootstrap_blog/templates/blog/components/navbar.html, which is loaded by {% include 'blog/components/navbar.html' %} in the wagtail_bootstrap_blog/templates/base.html

<nav class="mb-2 navbar navbar-expand-lg navbar-dark bg-dark">
  <div class="container">
    <a class="navbar-brand" href="/">Wagtail Blog Demo</a>
    <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarResponsive">
    </div>
  </div>
</nav>

Create wagtail_bootstrap_blog/templates/blog/components/footer.html which is loaded by {% include 'blog/components/footer.html' %} in the wagtail_bootstrap_blog/templates/base.html

<footer class="mt-4 py-5 bg-dark">
  <div class="container">
    <p class="m-0 text-center text-white">
      Built by <a href="https://www.accordbox.com/">[email protected]</a>
    </p>
  </div>
</footer>

Create wagtail_bootstrap_blog/templates/blog/components/sidebar.html which is loaded by {% include 'blog/components/sidebar.html' %} in the wagtail_bootstrap_blog/templates/base.html

<div class="col-md-4">

  <h3>Sidebar</h3>

</div>

BlogPage

Update wagtail_bootstrap_blog/templates/blog/blog_page.html

{% extends "base.html" %}

{% load wagtailcore_tags wagtailimages_tags %}

{% block content %}

    {% for post in page.get_children.specific %}
        <div class="card mb-4">
          {% if post.header_image %}
            {% image post.header_image original as header_image %}
            <a href="{% pageurl post %}">
              <img src="{{ header_image.url }}" class="card-img-top">
            </a>
          {% endif %}

          <div class="card-body">
            <h2 class="card-title">
              <a href="{% pageurl post %}">{{ post.title }}</a>
            </h2>
            <p class="card-text">
              {{ post.description }}
            </p>
            <a href="{% pageurl post %}" class="btn btn-primary">Read More &rarr;</a>

          </div>

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

        </div>
    {% endfor %}

{% endblock %}

Notes:

  1. With Django extends template tag, we start using Django template-inheritance.
  2. The HTML in {% block content %} would override {% block content %} in wagtail_bootstrap_blog/templates/base.html, you can check Django doc: Template inheritance to learn more

PostPage

Update wagtail_bootstrap_blog/templates/blog/post_page.html

{% extends "base.html" %}

{% load wagtailcore_tags wagtailimages_tags %}

{% block content %}
    {% image page.header_image original as header_image %}
    <img src="{{ header_image.url }}" class="img-fluid">

    <h1>{{ page.title }}</h1>
    <hr>

    <div class="tags">
      <h3>Tags</h3>
      {% for tag in page.tags.all %}
        <button type="button">{{ tag }}</button>
      {% endfor %}
    </div>

    <h3>Categories</h3>
    <ul>
      {% for postpage_category in page.categories.all %}
        <li>
          {{ postpage_category.blog_category.name }}
        </li>
      {% endfor %}
    </ul>

    <p><a href="{{ page.get_parent.url }}">Return to blog</a></p>

{% endblock %}

Category Widget

Next, let's add Category widget to the sidebar.

To keep the template clean, we will create a custom Django template tag to do this.

Create blog/templatetags/blogapp_tags.py

from blog.models import BlogCategory as Category, Tag
from django.template import Library, loader

register = Library()


@register.inclusion_tag('blog/components/categories_list.html',
                        takes_context=True)
def categories_list(context):
    categories = Category.objects.all()
    return {
        'request': context['request'],
        'categories': categories
    }

Notes:

  1. The logic is very simple, we get all Category instances from the DB and display it in template.
  2. The blog/components/categories_list.html is the template which will be used to render HTML
  3. You can check Custom template tags and filters

Create wagtail_bootstrap_blog/templates/blog/components/categories_list.html

<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 %}
            <li>
              <a href="#">
                {{ category.name }}
              </a>
            </li>
          {% empty %}
            'No categories yet'
          {% endfor %}
        </ul>
      </div>
    </div>
  </div>
</div>

Tag Widget

Update blog/templatetags/blogapp_tags.py

@register.inclusion_tag('blog/components/tags_list.html',
                        takes_context=True)
def tags_list(context):
    tags = Tag.objects.all()
    return {
        'request': context['request'],
        'tags': tags
    }


@register.inclusion_tag('blog/components/categories_list.html',
                        takes_context=True)
def categories_list(context):
    categories = Category.objects.all()
    return {
        'request': context['request'],
        'categories': categories
    }

Create wagtail_bootstrap_blog/templates/blog/components/tags_list.html

<div class="card my-4">
  <h5 class="card-header">Tags</h5>
  <div class="card-body">
    {% for tag in tags %}
      <a href="#">
        <span class="badge badge-secondary">{{ tag }}</span>
      </a>
    {% empty %}
      No tags yet
    {% endfor %}
  </div>
</div>

After we build tags_list and categories_list, let's update wagtail_bootstrap_blog/templates/blog/components/sidebar.html

{% load blogapp_tags %}

<div class="col-md-4">
  {% if blog_page %}

  {% categories_list %}

  {% tags_list %}

  {% endif %}
</div>

Notes:

  1. At the top, we load blogapp_tags so the above template tags would be available in the sidebar.html (Like import statement in Python)
  2. We call categories_list and tags_list to render the category list and tag list on the sidebar.

As you can see, now the Category widget and Tag widget is working in the sidebar.

PostPage

Let's keep adding template tags to display category and tag info for specific PostPage

Update blog/templatetags/blogapp_tags.py

@register.inclusion_tag("blog/components/post_categories_list.html", takes_context=True)
def post_categories_list(context):
    page = context["page"]
    post_categories = page.categories.all()
    return {
        "request": context["request"],
        "post_categories": post_categories,
    }


@register.inclusion_tag("blog/components/post_tags_list.html", takes_context=True)
def post_tags_list(context):
    page = context["page"]
    post_tags = page.tags.all()
    return {
        "request": context["request"],
        "post_tags": post_tags,
    }

Notes:

  1. Here we added two template tags, the post prefix tell us they are for post_page

Create wagtail_bootstrap_blog/templates/blog/components/post_categories_list.html

{% if post_categories %}
    <i class="fas fa-tag"></i>
    {% for postpage_category in post_categories %}
        {{ postpage_category.blog_category.name }}
    {% endfor %}
    &nbsp;
{% endif %}

Create wagtail_bootstrap_blog/templates/blog/components/post_tags_list.html

<div>
    {% for tag in post_tags %}
        <span class="badge badge-secondary">{{ tag }}</span>
    {% endfor %}
</div>

Now if you check the post content, you will see the category and tag info is also displayed.


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 blog with React, Wagtail CMS and SSR (Server-Side Rendering), which has good performance and good SEO.

Read More

Subscribe

Get notified about new great Web Development Tutorial