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:
- Add
Date
to the PostPage URL - Understand what is
cached_property
and the benefit
Models
Update blog/models.py
class PostPage(Page):
# code omitted for brevity
post_date = models.DateTimeField(
verbose_name="Post date", default=datetime.datetime.today
)
settings_panels = Page.settings_panels + [
FieldPanel("post_date"),
]
- We created a
post_date
field, which store the Post date. - To make it editable in Wagtail admin, we also add it to the
settings_panels
- Do not forget to add
import datetime
to the top of the file
Migrate the db
$ docker-compose run --rm web python manage.py makemigrations
$ docker-compose run --rm web python manage.py migrate
Update blog/models.py
class BlogPage(RoutablePageMixin, Page):
# code omitted for brevity
def get_posts(self):
return PostPage.objects.descendant_of(self).live().order_by("-post_date")
@route(r"^(\d{4})/$")
@route(r"^(\d{4})/(\d{2})/$")
@route(r"^(\d{4})/(\d{2})/(\d{2})/$")
def post_by_date(self, request, year, month=None, day=None, *args, **kwargs):
self.posts = self.get_posts().filter(post_date__year=year)
if month:
self.posts = self.posts.filter(post_date__month=month)
if day:
self.posts = self.posts.filter(post_date__day=day)
return self.render(request)
@route(r"^(\d{4})/(\d{2})/(\d{2})/(.+)/$")
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
# here we render another page, so we call the serve method of the page instance
return post_page.serve(request)
Notes:
- Here we added two routes to the
BlogPage
post_by_date
would make us can check post pages which are published in specific year, month or day.post_by_date_slug
would make us check post page on URL with this pattern/year/month/date/slug
.- In
post_by_date_slug
, please note that because we need to render thePostPage
in theBlogPage
route, we need to callpost_page.serve(request)
instead ofself.render
- We added
order_by("-post_date")
to the query inget_posts
to make it have descending order. Please check Django doc: order-by for more details.
Now the post page should be accessible on url like http://127.0.0.1:8000/2020/12/20/postpage1/
Next, let's display date_slug_url
on the index page.
Template
Update blog/templatetags/blogapp_tags.py
@register.simple_tag()
def post_page_date_slug_url(post_page, blog_page):
post_date = post_page.post_date
url = blog_page.url + blog_page.reverse_subpage(
"post_by_date_slug",
args=(
post_date.year,
"{0:02}".format(post_date.month),
"{0:02}".format(post_date.day),
post_page.slug,
),
)
return url
Notes:
- Here we add a template tag
post_page_date_slug_url
, we use it to help us generate the date_slug_url of the post page. - Considering we only return url instead of HTML, so we use
@register.simple_tag
instead of@register.inclusion_tag
Update wagtail_bootstrap_blog/templates/blog/blog_page.html
{% extends "base.html" %}
{% load wagtailcore_tags wagtailimages_tags blogapp_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="{% post_page_date_slug_url post blog_page %}">
<img src="{{ header_image.url }}" class="card-img-top">
</a>
{% endif %}
<div class="card-body">
<h2 class="card-title">
<a href="{% post_page_date_slug_url post blog_page %}">{{ post.title }}</a>
</h2>
<p class="card-text">
{{ post.description }}
</p>
<a href="{% post_page_date_slug_url post blog_page %}" class="btn btn-primary">Read More →</a>
</div>
<div class="card-footer text-muted">
Posted on {{ post.post_date }}
</div>
</div>
{% endfor %}
{% endblock %}
Notes:
- We
load blogapp_tags
at the top of the template file. - Replace
posturl post
with{% post_page_date_slug_url post blog_page %}
, so it would run custom template tag we just build - Replace
{{ post.last_published_at }}
with{{ post.post_date }}
Canonical URL
A canonical URL is the URL of the page that Google thinks is most representative from a set of duplicate pages on your site.
Now the post page can be visited in two patterns.
http://127.0.0.1:8000/2020/12/20/postpage1/
http://127.0.0.1:8000/postpage1/
For better SEO, we will add canonical
link.
Update blog/models.py
class PostPage(Page):
# code omitted for brevity
def canonical_url(self):
# we should import here to avoid circular import
from blog.templatetags.blogapp_tags import post_page_date_slug_url
blog_page = self.get_parent().specific
return post_page_date_slug_url(self, blog_page)
Notes:
- We added a
canonical_url
method to thePostPage
, which would return thedate_slug
url of the post page. - Please note that we put the import statement inside the method, to avoid circular import.
Update wagtail_bootstrap_blog/templates/base.html
// code omitted for brevity
<head>
{% if page.canonical_url %}
<link rel="canonical" href="{{ page.canonical_url }}"/>
{% endif %}
</head>
Notes:
- If page has
canonical_url
, thencanonical
link would be added to the htmlhead
If you check the HTML source code in your browser, you will find HTML like this <link rel="canonical" href="/2020/12/20/postpage1/"/>
. Let's change the relative url to absolute url.
Update templatetags/blogapp_tags.py
@register.simple_tag()
def post_page_date_slug_url(post_page, blog_page):
post_date = post_page.post_date
url = blog_page.full_url + blog_page.reverse_subpage(
"post_by_date_slug",
args=(
post_date.year,
"{0:02}".format(post_date.month),
"{0:02}".format(post_date.day),
post_page.slug,
),
)
return url
Notes:
- In
post_page_date_slug_url
we changedblog_page.url
toblog_page.full_url
, which contains the protocol, domain - To make the domain have correct value, we need to config the site in Wagtail admin.
Cached property
Let's review our Django template of the canonical_url
{% if page.canonical_url %}
<link rel="canonical" href="{{ page.canonical_url }}"/>
{% endif %}
Notes:
page.canonical_url
, code incanonical_url
would run for the first time.href="{{ page.canonical_url }}"/>
, code incanonical_url
would run for the second time.
As you can see, the code in the template caused redundant db query, we can optimize our code here.
Django has provided django.utils.functional.cached_property
to help us Django doc: cached_property
The @cached_property decorator caches the result of a method with a single self argument as a property. The cached result will persist as long as the instance does, so if the instance is passed around and the function subsequently invoked, the cached result will be returned.
Update blog/models.py
class PostPage(Page):
def get_context(self, request, *args, **kwargs):
context = super().get_context(request, *args, **kwargs)
context['blog_page'] = self.blog_page
return context
@cached_property
def blog_page(self):
return self.get_parent().specific
@cached_property
def canonical_url(self):
# we should import here to avoid circular import
from blog.templatetags.blogapp_tags import post_page_date_slug_url
blog_page = self.blog_page
return post_page_date_slug_url(self, blog_page)
Notes:
- We create a
cached_property
blog_page
- We create a
cached_property
canonical_url
- In
get_context
, we setcontext['blog_page']
with theself.blog_page
property
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: