Wagtail Tutorial Series:
To learn more about Wagtail CMS, please check Build Blog With Wagtail CMS (4.0.0)
- Create Wagtail Project
- Modern Frontend Techs for Wagtail
- Dockerizing Wagtail App
- Add Blog Models to Wagtail
- How to write Wagtail page template
- Create Stylish Wagtail Pages with Tailwind CSS
- 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
- Online Demo http://wagtail-blog.accordbox.com/
- 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
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_app/settings.py
INSTALLED_APPS = [
# code omitted for brevity
'wagtail.contrib.routable_page', # new
# code omitted for brevity
]
Update wagtail_app/blog/models.py
from wagtail.contrib.routable_page.models import RoutablePageMixin, route
class BlogPage(RoutablePageMixin, Page):
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 wagtail_app/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_app/templates/blog/blog_page.html
{% extends "base.html" %}
{% load wagtailcore_tags wagtailimages_tags %}
{% block content %}
{% for post in posts %}
<div class="mb-4 rounded-lg border border-opacity-75 border-gray-300 shadow-xl overflow-hidden">
{% if post.header_image %}
{% image post.header_image original as header_image %}
<a href="{% pageurl post %}">
<img src="{{ header_image.url }}">
</a>
{% endif %}
<div class="p-6">
<h2 class="title-font text-3xl text-blue-900 mb-6">
<a href="{% pageurl post %}">{{ post.title }}</a>
</h2>
<p>
{{ post.search_description }}
</p>
<a href="{% pageurl post %}" class="px-4 py-3 text-white bg-blue-500 border border-blue-500 hover:bg-blue-600 rounded-lg">Read More →</a>
</div>
<div class="bg-gray-100 px-6 py-4">
<h4 class="text-base text-gray-900">Posted on {{ post.last_published_at }}</h4>
</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 wagtail_app/blog/templatetags/blogapp_tags.py and add blog_page
to the context.
@register.inclusion_tag('blog/components/categories_list.html',
takes_context=True)
def categories_list(context):
categories = BlogCategory.objects.all()
return {
'request': context['request'],
'blog_page': context['blog_page'], # new
'categories': categories
}
@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'], # new
'tags': tags
}
Update wagtail_app/templates/blog/components/categories_list.html
{% load wagtailroutablepage_tags %}
<div class="mb-4 border rounded-lg border-opacity-75 border-gray-300 shadow-xl overflow-hidden ">
<div class="bg-gray-100 text-gray-900 px-6 py-4 ">
<h4 class="text-base font-medium">Categories</h4>
</div>
<div class="px-6 py-4">
<nav class="list-none">
{% for category in categories %}
<li>
<a href="{% routablepageurl blog_page "post_by_category" category.slug %}" class="text-gray-600 hover:text-gray-800 no-underline hover:underline ">
{{ category.name }}
</a>
</li>
{% empty %}
'No categories yet'
{% endfor %}
</nav>
</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_app/templates/blog/components/tags_list.html
{% load wagtailroutablepage_tags %}
<div class="mb-4 border rounded-lg border-opacity-75 border-gray-300 shadow-xl overflow-hidden">
<div class="bg-gray-100 text-gray-900 px-6 py-4">
<h4 class="text-base font-medium">Tags</h4>
</div>
<div class="px-6 py-4">
{% for tag in tags %}
<a href="{% routablepageurl blog_page "post_by_tag" tag.slug %}" class="text-gray-600 hover:text-gray-800">
<span class="inline-flex items-center justify-center px-2 py-1 mr-2 text-xs font-bold leading-none text-white bg-gray-600 hover:bg-gray-500 focus:bg-gray-700 rounded-full">{{ 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 learn more about Wagtail CMS, please check Build Blog With Wagtail CMS (4.0.0)
- Create Wagtail Project
- Modern Frontend Techs for Wagtail
- Dockerizing Wagtail App
- Add Blog Models to Wagtail
- How to write Wagtail page template
- Create Stylish Wagtail Pages with Tailwind CSS
- 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
- Online Demo http://wagtail-blog.accordbox.com/
- 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: