Wagtail Tutorials #5: Customize Blog Post URL

Michael Yin

Full Stack Developer

Last updated on Jan 02 2021

Table of Contents


In this chapter, I will talk about how to customize the permanent link of our post page. As you know, RoutablePageMixin can be used to add route function to blog page, so here we continue to dive into RoutablePageMixin to get this job done. For example, we can add publish date of the post to URL so the permanent link would seem like this /blog/2017/06/27/post-page-1/. Which means user can visit the same page from /blog/2017/06/27/post-page-1/ or /blog/post-page-1/

Add date info into blog post url

In the last post, we talked about how to use RoutablePageMixin and route decorator to make our blog page routable, now we keep moving to make the blog have more control of post permanent link.

class BlogPage(RoutablePageMixin, Page):

    def get_posts(self):
        return PostPage.objects.descendant_of(self).live().order_by('-date')

    def post_by_date(self, request, year, month=None, day=None, *args, **kwargs):
        self.posts = self.get_posts().filter(date__year=year)
        if month:
            self.posts = self.posts.filter(date__month=month)
            df = DateFormat(date(int(year), int(month), 1))
            self.search_term = df.format('F Y')
        if day:
            self.posts = self.posts.filter(date__day=day)
            self.search_term = date_format(date(int(year), int(month), int(day)))
        return Page.serve(self, request, *args, **kwargs)

    def post_by_date_slug(self, request, year, month, day, slug, *args, **kwargs):
        post_page = self.get_posts().filter(slug=slug).first()
        if not post_page:
            raise Http404
        return Page.serve(post_page, request, *args, **kwargs)

There are some points you should notice from the code above.

First, many people have no idea if Wagtail support multiple routes, the answer is yes, you can add more than one decorator to the view function to make it handle different url patterns. Here we add multiple routes to post_by_date method, make it can handle different patterns of urls. The view function can filter the posts by year, month and day of publishing date.

Second, you should understand what Page.serve do here.

All page classes have a serve() method that internally calls the get_context and get_template methods and renders the template. This method is similar to a Django view function, taking a Django Request object and returning a Django Response object

Every page has serve method, so if we want to render some specific blog post, we can just call post.serve and return the response object back. This method sounds very simple here but almost all repos on GitHub about Wagtail blog use a very complex solution by hacking the urls.py. Do not use urls.py if you can do it with RoutablePageMixin

As you can see, in post_by_date_slug we first get the slug from the URL, then find the post which have the slug, if not found, we return 404 HTTP error, if found, we call Page.serve to render the post, the first parameters passed in is the post object instead of blog page object itself, post_page.serve(request, *args, **kwargs) should also work here too

Next, We need to add a new date field to our PostPage

class PostPage(Page):
    body = RichTextField(blank=True)
    date = models.DateTimeField(verbose_name="Post date", default=datetime.datetime.today)
    categories = ParentalManyToManyField('blog.BlogCategory', blank=True)
    tags = ClusterTaggableManager(through='blog.BlogPageTag', blank=True)

    content_panels = Page.content_panels + [
        FieldPanel('body', classname="full"),
        FieldPanel('categories', widget=forms.CheckboxSelectMultiple),

    settings_panels = Page.settings_panels + [

    def blog_page(self):
        return self.get_parent().specific

    def get_context(self, request, *args, **kwargs):
        context = super(PostPage, self).get_context(request, *args, **kwargs)
        context['blog_page'] = self.blog_page
        return context

As we can see, we add a new field date to our PostPage, the value will be set when page instance is created. To make user can set it in edit page, we also add it to Page.settings_panels

The categories and tags are implemented in another Wagtail blog tutorial Category And Tag Support. After you are done with the model, remember to migrate your databae.

python manage.py makemigrations blog
python manage.py migrate blog

Reversing post urls

Now our blog application can handle url which contains the date info of blog post, and would return the same HTML. What should I do if we want the links in blog page also have date info?

Since there is no direct way to generate the link, we need to create our own Django template tags now. It is not a big problem if you have no idea what is Django template tags, just follow this article step by step.

Create directory blog/templatetags, create blog/templatetags/__init__.py in directory to make it treated as packages in python, and create blog/templatetags/blogapp_tags.py to edit.

# -*- coding: utf-8 -*-
from django.template import Library, loader
from django.core.urlresolvers import resolve

register = Library()

def post_date_url(post, blog_page):
    post_date = post.date
    url = blog_page.url + blog_page.reverse_subpage(
    return url

The logic here is very simple, but you should know the basic points here. register.simple_tag is used to create custom template tags in Django, the variable passed to the function should be post object, and blog page object. Because we did not set name in the route decorator, so the name passed in blog_page.reverse_subpage is the method name.

Now edit blog/templates/blog/blog_page.html to call the template tags in our template.

{% load wagtailcore_tags blogapp_tags %}

{% block content %}
  <h1>{{ blog_page.title }}</h1>

  <div class="intro">{{ blog_page.description }}</div>

  {% for post in posts %}
    <h2><a href="{% post_date_url post blog_page %}">{{ post.title }}</a></h2>
  {% endfor %}

{% endblock %}

In template we first load blogapp_tags to make them available in this template, then we use post_date_url custom tag to help us generate post urls. After work, we can see the post link in the blog page now have publish date info and we can click the post link to ask wagtai to handle the post url for us, which is awesome!

If you do not know what is blog_page, it is a value set in get_context method of BlogPage.


In this chapter, we successfully customized the permanent link of post page, and we also learned how to create Django template tags to help us keep the template code clean and easy to manage.

The source code of this Wagtail tutorial is available on Github, you can get it here wagtail-bootstrap-blog, and I would appreciate that if you could star my repo.

You can also check the full list of my wagtail tutorial here wagtail tutorial series

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 SPA (single-page application) with React and Wagtail CMS

Read More


Get notified about new great Web Development Tutorial