Looking to build a modern, flexible blog with Python? Wagtail CMS offers a powerful, developer‑friendly solution that sits on top of Django, giving you the best of both worlds: a clean admin interface and a robust content‑management backbone. In this guide we’ll walk through every step of creating a Python Wagtail CMS blog project, from environment setup to deployment, while sprinkling in SEO‑friendly tips that help your posts rank higher in search results.
Why Choose Wagtail for a Blog Project?
Before diving into code, it’s worth understanding what makes Wagtail a top choice for bloggers and developers alike.
- Streamlined editor experience – Inline rich‑text editing, drag‑and‑drop page ordering, and image handling built right into the admin.
- Extensible data models – Leverage Django’s ORM to create custom fields, tags, and relationships without fighting the framework.
- SEO‑ready out of the box – Built‑in support for meta tags, sitemap generation, and clean URL routing.
- Scalable architecture – Perfect for small personal blogs and large editorial sites alike.
Setting Up the Development Environment
A clean environment ensures reproducible builds and smooth collaboration. Follow these steps to get started:
- Install
Python 3.11or later. - Create a virtual environment:
python -m venv .venv
source .venv/bin/activate # On Windows use .venv\Scripts\activate
- Upgrade
pipand install required packages:
pip install --upgrade pip
pip install Django==5.0 wagtail==5.2
With Django and Wagtail installed, you’re ready to scaffold the project.
Creating the Wagtail Project
Run the Wagtail starter command to generate a base project structure:
wagtail start myblog
cd myblog
python manage.py migrate
python manage.py createsuperuser # Follow prompts to set admin credentials
Start the development server to verify everything works:
python manage.py runserver
Visit http://localhost:8000 for the front‑end and http://localhost:8000/admin/ for the admin dashboard.
Building the Blog Application
Now we’ll add a reusable blog app that holds our article models, templates, and routing logic.
python manage.py startapp blog
Register the app in myblog/settings/base.py (or settings.py if you’re using a single‑file config):
INSTALLED_APPS = [
# default Django apps …
'wagtail.contrib.forms',
'wagtail.contrib.redirects',
'wagtail.embeds',
'wagtail.sites',
'wagtail.users',
'wagtail.images',
'wagtail.documents',
'wagtail.snippets',
'wagtail.search',
'wagtail.admin',
'wagtail.core',
'modelcluster',
'taggit',
'blog', # ← our new app
]
Defining the Blog Page Model
Wagtail treats each piece of content as a Page subclass. Create blog/models.py with the following:
from django.db import models
from wagtail.models import Page
from wagtail.fields import RichTextField
from wagtail.admin.edit_handlers import FieldPanel, StreamFieldPanel
from wagtail.images.edit_handlers import ImageChooserPanel
from wagtail.search import index
from taggit.models import TaggedItemBase
from modelcluster.fields import ParentalKey
from modelcluster.tags import ClusterTaggableManager
class BlogIndexPage(Page):
"""Container for blog posts – appears at /blog/."""
intro = RichTextField(blank=True)
content_panels = Page.content_panels + [
FieldPanel('intro'),
]
class BlogPageTag(TaggedItemBase):
content_object = ParentalKey('BlogPage', related_name='tagged_items', on_delete=models.CASCADE)
class BlogPage(Page):
date = models.DateField("Post date")
author = models.CharField(max_length=255)
body = RichTextField()
tags = ClusterTaggableManager(through=BlogPageTag, blank=True)
header_image = models.ForeignKey(
'wagtailimages.Image',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+'
)
search_fields = Page.search_fields + [
index.SearchField('body'),
index.FilterField('date'),
index.FilterField('tags'),
]
content_panels = Page.content_panels + [
FieldPanel('date'),
FieldPanel('author'),
ImageChooserPanel('header_image'),
FieldPanel('body', classname="full"),
FieldPanel('tags'),
]
Run migrations to create the new tables:
python manage.py makemigrations blog
python manage.py migrate
Adding the Blog to the Site Tree
Log into the Wagtail admin, navigate to the root page, and click “Add child page”. Choose Blog index page, give it a title like “Blog”, and publish. Inside this index you can now add individual Blog page entries.
Customizing Templates for SEO and Design
Wagtail looks for templates in templates/ that match the page class name. Create the following files:
templates/blog/blog_index_page.htmltemplates/blog/blog_page.html
Here’s a minimal blog_index_page.html that includes meta tags and a list of posts:
{% load wagtailcore_tags wagtailimages_tags %}
{{ page.title }} – {{ site.site_name }}
{% include "wagtailseo/meta.html" %}
{{ page.title }}
{{ page.intro|richtext }}
{% for post in page.get_children.live().specific %}
-
{{ post.title }}
by {{ post.author }} on {{ post.date }}
{% empty %}
- No posts yet.
{% endfor %}
And a simple blog_page.html that renders the article with Open Graph tags:
{% load wagtailcore_tags wagtailimages_tags %}
{{ page.title }} – {{ site.site_name }}
{% if page.header_image %}
{% image page.header_image fill-1200x630 as og_image %}
{% endif %}
{{ page.title }}
Published: {{ page.date }} | Author: {{ page.author }}
{% if page.header_image %}
{% image page.header_image fill-800x400 as header %}
{% endif %}
{{ page.body|richtext }}
Tags:
{% for tag in page.tags.all %}
{{ tag.name }}{% if not forloop.last %}, {% endif %}
{% empty %}
No tags.
{% endfor %}
Managing Content in the Wagtail Admin
The real power of Wagtail shines in its admin UI. Here are a few best‑practice tips for editors:
- Use the “Schedule” feature
Leave a Reply