Wagtail Tutorial Series:
To get the latest learning resource for Wagtail 4, please check Build Blog With Wagtail CMS (4.0.0)
- Create Wagtail Project
- Dockerizing Wagtail App
- Add Blog Models to Wagtail
- How to write Wagtail page template
- Add Bootstrap Theme to Wagtail
- How to use StreamField in Wagtail
- Wagtail Routable Page
- Add pagination component to Wagtail
- Customize Wagtail Page URL
- Add Full Text Search to Wagtail
- Add Markdown Support to Wagtail
- Add LaTeX Support & Code Highlight In Wagtail
- How to Build Form Page in Wagtail
- How to Create and Manage Menus in Wagtail
- Wagtail SEO Guide
- Source code: https://github.com/AccordBox/wagtail-tailwind-blog
Wagtail Tips:
- Wagtail Tip #1: How to replace ParentalManyToManyField with InlinePanel
- Wagtail Tip #2: How to Export & Restore Wagtail Site
Write style in Wagtail:
- How to use SCSS/SASS in your Django project (Python Way)
- How to use SCSS/SASS in your Django project (NPM Way)
Other Wagtail Topics:
Objective
By the end of this chapter, you should be able to:
- Understand how to create
Routable page
in Wagtail - Make
Caregory
andTag
work withRoutable page
- Implement
Pagination
in Wagtail.
Router
Wagtail pages are organized following tree structure, as each page in the tree has its own URL path, like so:
/
people/
nien-nunb/ (http://www.example.com/people/nien-nunb)
laura-roslin/
blog/
post-page-1/
post-page-2/
You can check more on Wagtail doc: Introduction to Trees
The RoutablePageMixin mixin provides a convenient way for a page to respond on multiple sub-URLs with different views. For example, a blog section on a site might provide several different types of index page at URLs like /blog/2013/06/, /blog/authors/bob/, /blog/tagged/python/, all served by the same page instance.
So here we will make our blog page can handle custom url like http://127.0.0.1:8000/category/slug/
and http://127.0.0.1:8000/tag/slug/
Add wagtail.contrib.routable_page
to the INSTALLED_APPS
of wagtail_bootstrap_blog/settings/base.py
INSTALLED_APPS = [
'home',
'search',
'blog',
'wagtail.contrib.forms',
'wagtail.contrib.redirects',
'wagtail.embeds',
'wagtail.sites',
'wagtail.users',
'wagtail.snippets',
'wagtail.documents',
'wagtail.images',
'wagtail.search',
'wagtail.admin',
'wagtail.core',
'wagtail.contrib.routable_page',
# code omitted for brevity
]
Update blog/models.py
from wagtail.contrib.routable_page.models import RoutablePageMixin, route
class BlogPage(RoutablePageMixin, Page):
description = models.CharField(max_length=255, blank=True,)
content_panels = Page.content_panels + [FieldPanel("description", classname="full")]
def get_context(self, request, *args, **kwargs):
context = super(BlogPage, self).get_context(request, *args, **kwargs)
context['blog_page'] = self
context['posts'] = self.posts
return context
def get_posts(self):
return PostPage.objects.descendant_of(self).live()
@route(r'^tag/(?P<tag>[-\w]+)/$')
def post_by_tag(self, request, tag, *args, **kwargs):
self.posts = self.get_posts().filter(tags__slug=tag)
return self.render(request)
@route(r'^category/(?P<category>[-\w]+)/$')
def post_by_category(self, request, category, *args, **kwargs):
self.posts = self.get_posts().filter(categories__blog_category__slug=category)
return self.render(request)
@route(r'^$')
def post_list(self, request, *args, **kwargs):
self.posts = self.get_posts()
return self.render(request)
Notes:
- Update
blog.BlogPage
to make it inherit from bothwagtail.contrib.routable_page.models.RoutablePageMixin
and WagtailPage
- Please make sure the
RoutablePageMixin
is before thePage
, if not, the router function would fail. - We added three routes, the parameters passed in the
route
decorator is a regex expression. If you are new to this, please check Django doc: regular expressions get_posts
is a common method which return the publicPostPage
of theBlogPage
. The routes would then filter and set the value toself.posts
.- The
route
works similar with Django view, here we usereturn self.render(request)
to return the Response back to visitor. - I will talk about
get_context
method in a bit so let's ignore it for now.
Context
Sometimes, if we want to make some variables available in the template
, we need to overwrite the get_context
method.
All pages have a
get_context
method that is called whenever the template is rendered and returns a dictionary of variables to bind into the template
Update blog/models.py
class BlogPage(RoutablePageMixin, Page):
# code omitted for brevity
def get_context(self, request, *args, **kwargs):
context = super().get_context(request, *args, **kwargs)
context['blog_page'] = self
context['posts'] = self.posts
return context
class PostPage(Page):
# code omitted for brevity
def get_context(self, request, *args, **kwargs):
context = super().get_context(request, *args, **kwargs)
context['blog_page'] = self.get_parent().specific
return context
Notes:
- Now
blog_page
would be available when renderingblog_page.html
andpost_page.html
posts
would be available inblog_page.html
Template
Update wagtail_bootstrap_blog/templates/blog/blog_page.html
{% extends "base.html" %}
{% load wagtailcore_tags wagtailimages_tags %}
{% block content %}
{% for post in posts %}
<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 →</a>
</div>
<div class="card-footer text-muted">
Posted on {{ post.last_published_at }}
</div>
</div>
{% endfor %}
{% endblock %}
Notes:
- We changed
{% for post in page.get_children.specific %}
to{% for post in posts %}
.posts
is now available inblog_page
because ofBlogPage.get_context
method. - After
BlogPage
handle the HTTP request,posts
in context is the collection of the filtered posts, we add it to context object to make the templates can directly iterate it.
Let's run our project
$ docker-compose up -d --build
$ docker-compose logs -f
- Visit http://127.0.0.1:8000
- Visit http://127.0.0.1:8000/category/programming/
- Visit http://127.0.0.1:8000/category/test/
- Visit http://127.0.0.1:8000/tag/django/
- Visit http://127.0.0.1:8000/tag/test/
You might need to change the url a little bit, after the test, you will see the route is working.
Reversing route urls
Next, let's try to update the Category widget and Tag widget in the sidebar to make the URL work with route of the BlogPage
Update blog/templatetags/blogapp_tags.py and add blog_page
to the context.
from blog.models import BlogCategory as Category, Tag
from django.template import Library, loader
register = Library()
@register.inclusion_tag('blog/components/tags_list.html',
takes_context=True)
def tags_list(context):
tags = Tag.objects.all()
return {
'request': context['request'],
'blog_page': context['blog_page'],
'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'],
'blog_page': context['blog_page'],
'categories': categories
}
Update wagtail_bootstrap_blog/templates/blog/components/categories_list.html
{% load wagtailroutablepage_tags %}
<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="{% routablepageurl blog_page "post_by_category" category.slug %}">
{{ category.name }}
</a>
</li>
{% empty %}
'No categories yet'
{% endfor %}
</ul>
</div>
</div>
</div>
</div>
Notes:
- At the top, we
{% load wagtailroutablepage_tags %}
{% routablepageurl blog_page "post_by_category" category.slug %}
would help us generate the url of the category. It is very similar with Django reverse
Update wagtail_bootstrap_blog/templates/blog/components/tags_list.html
{% load wagtailroutablepage_tags %}
<div class="card my-4">
<h5 class="card-header">Tags</h5>
<div class="card-body">
{% for tag in tags %}
<a href="{% routablepageurl blog_page "post_by_tag" tag.slug %}">
<span class="badge badge-secondary">{{ tag }}</span>
</a>
{% empty %}
No tags yet
{% endfor %}
</div>
</div>
Notes:
{% routablepageurl blog_page "post_by_tag" tag.slug %}
would help us generate the url of the tag.
Now the category and tag link in the sidebar would seem like this.
Wagtail Tutorial Series:
To get the latest learning resource for Wagtail 4, please check Build Blog With Wagtail CMS (4.0.0)
- Create Wagtail Project
- Dockerizing Wagtail App
- Add Blog Models to Wagtail
- How to write Wagtail page template
- Add Bootstrap Theme to Wagtail
- How to use StreamField in Wagtail
- Wagtail Routable Page
- Add pagination component to Wagtail
- Customize Wagtail Page URL
- Add Full Text Search to Wagtail
- Add Markdown Support to Wagtail
- Add LaTeX Support & Code Highlight In Wagtail
- How to Build Form Page in Wagtail
- How to Create and Manage Menus in Wagtail
- Wagtail SEO Guide
- Source code: https://github.com/AccordBox/wagtail-tailwind-blog
Wagtail Tips:
- Wagtail Tip #1: How to replace ParentalManyToManyField with InlinePanel
- Wagtail Tip #2: How to Export & Restore Wagtail Site
Write style in Wagtail:
- How to use SCSS/SASS in your Django project (Python Way)
- How to use SCSS/SASS in your Django project (NPM Way)
Other Wagtail Topics: