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:
- Add
Date
to the PostPage URL - Understand what is
cached_property
and the benefit
Models
Update wagtail_app/blog/models.py
import datetime
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 wagtail_app/blog/models.py
from django.http import Http404
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. - Do not forget to add
from django.http import Http404
at the top
Now the post page should be accessible on url like http://127.0.0.1:8000/2022/10/20/postpage1/
Next, let's display date_slug_url
on the index page.
Template
Update wagtail_app/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_app/templates/blog/blog_page.html
{% extends "base.html" %}
{% load wagtailcore_tags wagtailimages_tags blogapp_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="{% post_page_date_slug_url post blog_page %}">
<img src="{{ header_image.url }}">
</a>
{% endif %}
<div class="p-6">
<h2 class="title-font text-3xl text-blue-900 mb-6">
<a href="{% post_page_date_slug_url post blog_page %}">{{ post.title }}</a>
</h2>
<p>
{{ post.search_description }}
</p>
<a href="{% post_page_date_slug_url post blog_page %}" 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:
- 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 }}
Now if we test on the blog page, the post page URL would have date info.
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/2022/08/11/postpage1/
http://127.0.0.1:8000/postpage1/
For better SEO, we will add canonical
link.
Update wagtail_app/blog/models.py
class PostPage(Page):
# code omitted for brevity
def canonical_url(self):
# we should import here to avoid circular import
from wagtail_app.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_app/templates/base.html
<head>
// code omitted for brevity
{% 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="/2022/10/20/postpage1/"/>
. Let's change the relative url to absolute.
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( # new
"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 might need to config the site in Wagtail admin.. Because default value is
localhost:80
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:
- code in
{% if page.canonical_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 wagtail_app/blog/models.py
from django.utils.functional import cached_property
class PostPage(Page):
def get_context(self, request, *args, **kwargs):
context = super().get_context(request, *args, **kwargs)
context['blog_page'] = self.blog_page # new
return context
@cached_property
def blog_page(self): # new
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 # new
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 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: