Python Wagtail Cms Blog Project

Written by

in

Looking to build a modern, scalable blog with a clean editorial experience? Python Wagtail CMS offers a powerful, developer‑friendly framework that blends the flexibility of Django with a sleek, drag‑and‑drop admin interface. In this guide we’ll walk you through creating a full‑featured blog project from scratch, covering everything from initial setup and content models to SEO optimization and deployment. By the end, you’ll have a production‑ready Wagtail blog that’s easy to maintain and primed for search engine visibility.

Why Choose Wagtail for a Python Blog Project?

Wagtail has quickly become a favorite among developers and content editors alike. Here are the key reasons it stands out for a blog project:

  • Built on Django – Leverages the robustness, security, and extensive ecosystem of the world’s most popular Python web framework.
  • Intuitive admin UI – Content creators can compose, reorder, and schedule posts without touching code.
  • Streamlined routing – Powerful page hierarchy and URL handling out of the box.
  • Rich content blocks – Reusable StreamField blocks let you embed images, videos, and custom widgets.
  • SEO‑ready – Built‑in meta tags, sitemap generation, and easy integration with third‑party SEO tools.

Project Prerequisites

Before diving into code, make sure you have the following installed on your development machine:

  1. Python 3.10 or newer
  2. Virtualenv or venv for isolated environments
  3. Git (optional but recommended)
  4. PostgreSQL (recommended for production; SQLite works for quick prototyping)

Step 1: Setting Up the Wagtail Project

Start by creating a fresh virtual environment and installing Wagtail via pip:

python -m venv env
source env/bin/activate  # On Windows use `env\Scripts\activate`
pip install wagtail
wagtail start myblog
cd myblog
pip install -r requirements.txt
python manage.py migrate
python manage.py createsuperuser
python manage.py runserver

Visit http://127.0.0.1:8000/ in your browser and log in with the superuser credentials you just created. You’ll see the default Wagtail admin dashboard – the perfect launchpad for your blog.

Step 2: Designing the Blog Content Model

In Wagtail, each piece of content is a Page subclass. For a blog, we’ll create two page types:

  • BlogIndexPage – Serves as the container for all blog posts and handles pagination.
  • BlogPostPage – Represents an individual article with title, author, publish date, body, and SEO fields.

Open home/models.py (or create a new app called blog) and add the following code:

from django.db import models
from wagtail.admin.edit_handlers import FieldPanel, StreamFieldPanel, MultiFieldPanel
from wagtail.core.models import Page
from wagtail.core.fields import StreamField
from wagtail.images.edit_handlers import ImageChooserPanel
from wagtail.core import blocks
from wagtail.contrib.routable_page.models import RoutablePageMixin, route

class BlogIndexPage(Page):
    intro = models.TextField(blank=True)

    content_panels = Page.content_panels + [
        FieldPanel('intro')
    ]

    subpage_types = ['blog.BlogPostPage']

    def get_context(self, request):
        # Add blog posts to the template context
        context = super().get_context(request)
        posts = BlogPostPage.objects.live().descendant_of(self).order_by('-first_published_at')
        context['posts'] = posts
        return context


class BlogPostPage(Page):
    date = models.DateField("Post date")
    author = models.CharField(max_length=255)
    body = StreamField([
        ('paragraph', blocks.RichTextBlock()),
        ('image', blocks.ImageChooserBlock()),
        ('quote', blocks.BlockQuoteBlock()),
    ], null=True, blank=True)

    # SEO fields
    meta_description = models.CharField(max_length=160, blank=True)

    content_panels = Page.content_panels + [
        MultiFieldPanel([
            FieldPanel('date'),
            FieldPanel('author'),
        ], heading="Post Details"),
        StreamFieldPanel('body'),
        FieldPanel('meta_description', classname="full"),
    ]

    parent_page_types = ['blog.BlogIndexPage']
    subpage_types = []

Run migrations to apply the new models:

python manage.py makemigrations blog
python manage.py migrate

Step 3: Creating Templates for Index and Detail Pages

Wagtail looks for templates named after the page class. Create two files inside blog/templates/blog/:

  • blog_index_page.html
  • blog_post_page.html

blog_index_page.html – displays a list of posts with pagination:

{% extends "base.html" %}
{% load wagtailcore_tags %}

{% block content %}
  

{{ page.title }}

{% if page.intro %}

{{ page.intro }}

{% endif %}
    {% for post in posts %}
  • {{ post.title }}

    By {{ post.author }} on {{ post.date|date:"F j, Y" }}

    {% if post.search_description %}

    {{ post.search_description }}

    {% endif %}
  • {% empty %}
  • No posts yet.
  • {% endfor %}
{% endblock %}

blog_post_page.html – renders the full article with SEO meta tags:

{% extends "base.html" %}
{% load wagtailcore_tags wagtailimages_tags %}

{% block head %}
  {{ block.super }}
  {% if page.meta_description %}
    
  {% endif %}
{% endblock %}

{% block content %}
  

{{ page.title }}

By {{ page.author }} on {{ page.date|date:"F j, Y" }}

{% for block in page.body %} {% include_block block %} {% endfor %}
{% endblock %}

Step 4: Adding Rich Features with StreamField

StreamField makes it simple to let editors embed complex content without developers writing extra code each time. Below are a few popular block types you might want to add:

  • Image Gallery – a collection of images displayed as a carousel.
  • Embedded Video – YouTube or Vimeo embeds with responsive sizing.
  • Call‑to‑Action (CTA) – highlighted sections that drive newsletter sign‑ups or product links.

Extend the body definition in BlogPostPage like so:

body = StreamField([
    ('paragraph', blocks.RichTextBlock()),
    ('image', blocks.ImageChooserBlock()),
    ('quote', blocks.BlockQuoteBlock()),
    ('gallery', blocks.ListBlock(blocks.ImageChooserBlock())),
    ('video', blocks.StructBlock([
        ('url', blocks.URLBlock()),
        ('caption', blocks.CharBlock(required=False)),
    ])),
    ('cta', blocks.StructBlock([
        ('text', blocks.CharBlock()),
        ('button_text', blocks.CharBlock()),
        ('button_link', blocks.URLBlock()),
    ])),
], null=True, blank=True)

Remember to run makemigrations again after updating the model.

Step 5: SEO Best Practices for a Wagtail Blog

Search engine visibility is crucial for any blog. Wagtail provides built‑in tools, but you should also implement a few extra steps:

1. Enable the Sitemap Generator

Add 'wagtail.contrib.sitemaps' to INSTALLED_APPS and include the sitemap URL in urls.py:

from wagtail.contrib.sitemaps.views import sitemap

urlpatterns = [
    # … other patterns …
    path('sitemap.xml', sitemap),
]

2. Configure Meta Tags and Open Graph

Use the meta_description field you added and create a reusable snippet for Open Graph data. Example snippet model:

class SocialMetaSnippet(models.Model):
    page = ParentalKey('wagtailcore.Page', related_name='social_meta')
    og_title = models.CharField(max_length=60, blank=True)
    og_description = models.CharField(max_length=155, blank=True)
    og_image = models.ForeignKey(
        'wagtailimages.Image',
        null=True, blank=True,
        on_delete=models.SET_NULL,
        related_name='+'
    )
    panels = [
        FieldPanel('og_title'),
        FieldPanel('og_description'),
        ImageChooserPanel('og_image'),
    ]

Then render these tags in the head block of your base template.

3. Optimize Images

Leverage Wagtail’s built‑in image rendition system to serve appropriately sized images:

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *