Add Bootstrap Theme 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. 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/">MichaelYin@Accordbox</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.

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

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

Read More
© 2018 - 2024 AccordBox