A Story of How I Built This Website in Wagtail

A little background...

Disclaimer: I'm not a professional web developer. You only need to peek at how untidy my CSS is to see that. I don't expect any of my solutions to have the elegance that comes with having habitualised best practices from the beginning under good mentoring. I'm just not very good. Having said that, I've been "code-adjacent" for as long as I can remember.

Old webcam photos from 2001

In 2001, I built a Buffy-themed static HTML website with just Notepad, WS_FTP, and a free hosting site. It was glorious. There were scrolling marquees, animated banner GIFs that I made myself in Paintshop Pro 6, and potato-quality webcam photos of myself that I added borders and frames to. (The whole thing was pretty cringe tbh.)

Web technology has evolved rapidly over the last 20 years. As an infrequent hobbyist, it can be a struggle to keep up when you're forced to re-familiarise yourself with a whole new landscape everytime you creep out of your cave.

But I think I've finally found a stack that'll stick. For me, at least.

This website is built in Django (a Python web framework) with Wagtail as a CMS; powered by Nginx + Gunicorn; running on an Ubuntu 18.04 instance of AWS Lightsail.

Why Django?

Homepage code screenshot

I first learnt very basic Python nearly a decade ago - just to build little scraper tools for myself and, eventually years later, play around with Twitter bots. With a low barrier to entry, it felt like a very accessible programming language for a beginner. And, at the time, I was running Linux as my main operating system and so much was Python-based anyway. It wasn't until 2016-ish that I thought I'd give Django a try. I'd churned and burned a few get-rich-quick schemes by that point in Blogger, Wordpress, and Opencart, and thought it'd be fun to build my own CMS. It was like re-inventing a worse version of the wheel. Despite the objective being a CMS, I ended up hard-coding so much just to make it work. So I got bored, and put the project down.

Until last year at a Wellington Python Meetup, when Tom Eastman asked me, "Have you ever tried Wagtail?". The answer was no, and I completely forgot about it.

Until Wednesday 8th July 2020. When I realised that, if I'm serious about making the move from full-time employment to freelancing, I'm going to need a website.

What was that CMS that Tom told me about again?


I love Wagtail. For so many reasons, and probably many more that I'm not even aware of yet. I know for a fact that I have barely scratched the surface of its capability.

It was easy to get started building things in my dev environment. Django as a framework is pretty intuitive anyway. You create your data/logic models in Python, and then map it to your pages. Django, like many MVC (model-view-controller) frameworks, has a simple templating language so you can render your fun logic stuff in HTML templates. To make sure everything's talking to each other, I have my project-wide settings to handle things like environment variables, database, directory paths, middleware, etc. Most of which I don't understand beyond "I need to add this to that array, if I want it work".

Wagtail blog directory structure

I've split this website project into different apps:

  • blog - the structure of which you can see in this screenshot as an example
  • contact - the contact form page, and success page
  • home - the homepage
  • page - my default page set-up, currently only used for Services

Each app is organised with its own templates directory, models, etc.

The blog app encompasses the entire blog section of this website - including the blog index page, the post pages, the categories...

I can even register rich text hooks in wagtail_hooks.py to add new options (or limit them) for the Wagtail rich text editor.

For example, see the </> button?

Wagtail Rich Text Editor

This is the code in my wagtail_hooks.py that creates that little option to wrap the text I select with a <code> tag. (Yeah, this one styled right here!)

But, as I type this, I can see the problem with this solution in my rich-text editor. As it doesn't preserve my whitespace, so it breaks my indentation without <pre>.

This is where StreamFields become ✨~magical~✨...

def register_code_styling(features):
"""Add the <code> to the richtext editor and page."""
feature_name = "code"
type_ = "CODE"
tag = "code"

control = {
"type": type_,
"label": "</>",
"description": "Code"

"draftail", feature_name, draftail_features.InlineStyleFeature(control)

db_conversion = {
"from_database_format": {tag: InlineStyleElementHandler(type_)},
"to_database_format": {"style_map": {type_: {"element": tag}}}

features.register_converter_rule("contentstate", feature_name, db_conversion)


What the hell is a StreamField?

Blog Page Model

On the left-side panel, you can see my models.py where I have my BlogPage class. My content panels define the structure of my blog post editor. The content of this whole blog post here is built as a StreamField.

Blog posts are (currently) composed of six blocks or modules in my admin:

StreamField StreamBlocks

(To solve my <pre><code></code></pre> issue, I could always create it as its own StreamBlock instead of a rich text hook.)

On the right-side panel, you can see how each one is constructed. Then it's just a matter of templating and styling each block.

For example, the dark module above is my ImageTextBlock:

{% load wagtailimages_tags %}
<div class="row">
<div class="col-md-6">
{% image self.left_column original class="img-column" %}
<div class="col-md-6">
{{ self.right_column }}

Wagtail has just given me so much flexibility when it comes to content presentation. This has always been achievable through Django but with Wagtail, it's just such a smooth process.

Blog Page Wagtail Admin Screenshot

As a non-developer / barely-part-time-coder, it's a pretty painless experience that I'd recommend for anyone who was interested in playing around with web development for funsies.

From building the admin, to testing on localhost, all the way to production.... kinda.

Challenges deploying to production

Hello PuTTY, my old friend.

My previous experiences with VPS hosting was always with Digital Ocean. Never had an issue. That was back in the UK though, and the nearest servers were in Amsterdam. And then they opened up a data centre in London and it was even better. The closest data centre to New Zealand though would've been Singapore. Like 9000 km away. (That's over 5000 miles.) I've yet to delete my old droplet, but it's pretty noticeably laggy from here, to be honest. After some research, it seemed that AWS Lightsail was the closest comparison in terms of both service and cost. And location. Sydney FYI.

So I fired up an "instance", and tried to remember how I do this again...

It took me 3 days and 3 instances. I know it's not Amazon's fault, but configuring servers is hard. DevOps stuff is not my forté.

My first mistake was looking at the Lightsail options with pre-installed apps + OS (primary tab btw), and immediately being like "Django! That's what I want!" ~ *click*. It took me way too long to try and just make it fit with my stuff. I git cloned my site over, pip installed my requirements.txt, and just tried to put all the pieces together. Enlisting the help from John - my old friend from my SoSLUG (Southend-on-Sea Linux User Group) days back in the UK. He was my shoulder to cry on, and my Twitter cheerleader throughout.

My second attempt, I realised that I was working way harder than I needed to. Why don't I just install Ubuntu 18.04 without the bloatware? I have my requirements.txt... I opened up tabs upon tabs of tutorials, and flitted backwards and forwards between them, blindly bruteforcing my way to anything resembling progress.

I was on the verge of a breakdown, ready to hurl myself at the throes of Heroku.

Burn it. Start again.

One. Last. Time.

Fresh instance. This time I'll do it properly. I won't just blindly open up tabs of tutorial articles. I found this 7-part playlist on YouTube, titled "Deploy Django - From Zero to Hero", and followed along carefully, taking the time to understand each step and why I was (or wasn't, in some cases) doing it. Every error that popped up, I'd take my time attending to that singular issue instead of just throwing spaghetti at the wall and seeing what sticks. Diagnosing things one at a time, and really looking at the error logs. If you've ever had to untangle a sticky pile of electric guitar cords, it felt a bit like that. And the satisfaction at the end was soooo much sweeter.

There are still some kinks to work out. I still need to install a Let's Encrypt SSL certificate. But I'm learning to be patient and take things one tangle at a time.

Amateur wireframing

This website is a work in progress. I managed to get out a "first release" within 2 weeks of starting on it, and I'm pretty happy with what I've achieved so far. But I have ideas, crudely wireframed onto random post-it notes, for how to improve the design, the user experience, the back-end functionality.... My Trello board is bursting with inspiration, and I'm excited to make stuff do stuff.

You're more than welcome to have a nose around my code: https://github.com/RiaLolwut/mysite

And if there's a better way that I can be doing something, you're welcome to leave your suggestions below!

What else is new?