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:
- Generate meta tag with
wagtail-metadata
package. - Generate sitemap, and robots.txt for Wagtail project.
- Check 404, 500 page on local env.
Meta Tag
Setup
Add wagtail-metadata
to the requirements.txt
wagtail-metadata==4.0.2
Add wagtailmetadata
to the INSTALLED_APPS
in wagtail_app/settings.py
INSTALLED_APPS = [
# code omitted for brevity
'wagtailmetadata', # new
# code omitted for brevity
]
$ docker-compose up -d --build
$ docker-compose logs -f
Update wagtail_app/blog/models.py
from wagtailmetadata.models import MetadataPageMixin
class PostPage(MetadataPageMixin, Page):
# code omitted for brevity
Notes:
- We make
PostPage
inherit fromMetadataPageMixin
(please be careful about the order)
Let's migrate db
$ docker-compose run --rm web python manage.py makemigrations
$ docker-compose run --rm web python manage.py migrate
Add meta data in Wagtail admin
Now we can add meta data in promote
tab for the PostPage
slug
,page_title
,search_description
come from WagtailPage
model- The
search_image
come from bywagtailmetadata.models.MetadataPageMixin
Template
Update wagtail_app/templates/base.html
<head>
<meta charset="utf-8" />
{% block meta_tag %}
<title>
{% block title %}
{% if self.seo_title %}{{ self.seo_title }}{% else %}{{ self.title }}{% endif %}
{% endblock %}
{% block title_suffix %}
{% with self.get_site.site_name as site_name %}
{% if site_name %}- {{ site_name }}{% endif %}
{% endwith %}
{% endblock %}
</title>
<meta name="description" content="{{ page.search_description }}" />
{% endblock %}
<meta name="viewport" content="width=device-width, initial-scale=1" />
...
</head>
Notes:
- We created a
meta_tag
block - If child template does not fill the
meta_tag
block,meta_tag
ofbase.html
would be used instead
Update wagtail_app/templates/blog/post_page.html
{% extends "base.html" %}
{% load wagtailcore_tags wagtailimages_tags wagtailmetadata_tags %}
{% block meta_tag %}
{% meta_tags %}
{% endblock %}
Notes:
- In
post_page.html
, we fill themeta_tag
block with django template tagmeta_tags
fromwagtailmetadata_tags
- When we render HTML for
PostPage
, it would generate HTML usingmeta_tags
and fill themeta_tag
block.
If we check the HTML source code, we will see something like this
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="PostPage1">
<meta name="twitter:description" content="search description test">
<meta name="twitter:image" content="http://localhost:8000/media/images/image_3.original.jpg">
<meta property="og:url" content="http://localhost:8000/postpage1/" />
<meta property="og:title" content="PostPage1" />
<meta property="og:description" content="search description test" />
<meta property="og:site_name" content="" />
<meta property="og:image" content="http://localhost:8000/media/images/image_3.original.jpg" />
<meta property="og:image:width" content="1280" />
<meta property="og:image:height" content="853" />
<meta itemprop="url" content="http://localhost:8000/postpage1/"/>
<meta itemprop="name" content="PostPage1">
<meta itemprop="description" content="search description test" />
<meta itemprop="image" content="http://localhost:8000/media/images/image_3.original.jpg" />
Sitemap
Please add django.contrib.sitemaps
to INSTALLED_APPS
in wagtail_app/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.sitemaps', # new
]
Update wagtail_app/urls.py
from django.conf import settings
from django.urls import include, path
from django.contrib import admin
from wagtail.admin import urls as wagtailadmin_urls
from wagtail.core import urls as wagtail_urls
from wagtail.documents import urls as wagtaildocs_urls
from wagtail.contrib.sitemaps.views import sitemap
urlpatterns = [
...
path('sitemap.xml', sitemap),
]
- We added
path('sitemap.xml', sitemap)
, the sitemap view come from Wagtail. - If we visit http://127.0.0.1:8000/sitemap.xml, we will see sitemap is generated.
Notes:
- The domain and port are generated from
wagtail sites setting
, so remember to modify it when deploying. - In Wagtail Page model, there is
get_sitemap_urls
method and Wagtail would call this method to get info for sitemap item. - For
RoutablePageMixin
, we can overwrite theget_sitemap_urls
method.
Update wagtail_app/blog/models.py
class BlogPage(RoutablePageMixin, Page):
def get_sitemap_urls(self, request=None):
output = []
posts = self.get_posts()
for post in posts:
post_date = post.post_date
url = self.get_full_url(request) + self.reverse_subpage(
'post_by_date_slug',
args=(
post_date.year,
'{0:02}'.format(post_date.month),
'{0:02}'.format(post_date.day),
post.slug,
)
)
output.append({
'location': url,
'lastmod': post.last_published_at
})
return output
class PostPage(MetadataPageMixin, Page):
def get_sitemap_urls(self, request=None):
return []
Notes:
- Here we use
BlogPage.get_sitemap_urls
to generate thedate_slug_url
for thePostPage
- To avoid
http://127.0.0.1:8000/postpage4/
, we return empty list inPostPage.get_sitemap_urls
The sitemap would seem like this
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">
<url>
<loc>http://localhost:8000/2022/10/20/postpage3/</loc>
<lastmod>2022-10-20</lastmod>
</url>
<url>
<loc>http://localhost:8000/2022/10/20/postpage2/</loc>
<lastmod>2022-10-20</lastmod>
</url>
<url>
<loc>http://localhost:8000/2022/10/20/postpage1/</loc>
<lastmod>2022-10-23</lastmod>
</url>
<url>
<loc>http://localhost:8000/contact/</loc>
<lastmod>2022-10-21</lastmod>
</url>
</urlset>
Robots.txt
Update wagtail_app/urls.py
import wagtail_app.blog.views
urlpatterns = [
path('robots.txt', blog.views.RobotsView.as_view()),
]
Update wagtail_app/blog/views.py
from django.views.generic import TemplateView
from wagtail.core.models import Site
class RobotsView(TemplateView):
content_type = 'text/plain'
template_name = 'robots.txt'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
request = context['view'].request
context['wagtail_site'] = Site.find_for_request(request)
return context
Create wagtail_app/templates/robots.txt
User-Agent: *
Disallow: /admin/
# Sitemap files
Sitemap: {{ wagtail_site.root_url }}/sitemap.xml
Notes:
- Since the Sitemap should be fully-qualified URL so here we use this way to generate proper robots
- If you site does not support multi-site feature, you can write static URL and delete the
RobotsView.get_context_data
method.
Context
Let's review our code before going to the next section.
- We already add
blog_page
in theget_context
method ofBlogPage
,PostPage
, andFormPage
- We need
blog_page
because we need it soroutablepageurl blog_page xxxx
in sidebar can work. - But this is tedious because if we have many page types in our project, then each page model would have code like this.
There are some solutions here:
- Create a
BasePage
class, setblog_page
in theget_context
, then other pages which inheritBasePage
do not need to to that again. - Or we can build a custom context processor, Django doc here
A context processor has a simple interface: It’s a Python function that takes one argument, an HttpRequest object, and returns a dictionary that gets added to the template context.
Here let's choose the latter solution.
Create wagtail_app/blog/context_processors.py
from wagtail.core.models import Site
from wagtail_app.blog.models import BlogPage
def blog_page(request):
wagtail_site = Site.find_for_request(request)
context = {
'blog_page': BlogPage.objects.in_site(wagtail_site).first()
}
return context
The logic is simple here, in the blog_page
function, we return the correct blog_page
according to the request
.
Update TEMPLATES
in wagtail_app/settings.py to add the blog.context_processors.blog_page
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': ['wagtail_app/templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'wagtailmenus.context_processors.wagtailmenus',
'wagtail_app.blog.context_processors.blog_page' # new
],
},
},
]
Now remove code context["blog_page"]
in get_context
method from (BlogPage
, PostPage
, FormPage
)
We will see everything can still work like a charm.
Custom 404 500 page
It is always better to have custom 404
, 500
page to tell user what happened, but how to test the page on local?
Update wagtail_app/urls.py
from django.views import defaults as default_views
if settings.DEBUG:
from django.conf.urls.static import static
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
# Serve static and media files from development server
urlpatterns += staticfiles_urlpatterns()
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns = [
path('404/', default_views.page_not_found, kwargs={'exception': Exception("Page not Found")}),
path('500/', default_views.server_error),
] + urlpatterns
Create wagtail_app/templates/404.html
{% extends "base.html" %}
{% block title %}Page not found{% endblock %}
{% block body_class %}template-404{% endblock %}
{% block content %}
<div class="p-4 mb-4 text-sm text-red-700 bg-red-100 rounded-lg dark:bg-red-200 dark:text-red-800" role="alert">
Sorry, this page could not be found.
</div>
{% endblock %}
Now we can visit http://127.0.0.1:8000/404/ to test
Notes:
- For
404
page, the sidebar widget can still work - When we visits
http://127.0.0.1:8000/404/
, Django view is used to return response (not Wagtail) blog.context_processors.blog_page
helps us makeblog_page
available in the template context so widgets in the sidebar can still work.
3-party tool
There are some great 3-party tools to help us and I'd like to share with you here. I would also appreciate that if you can tell me your tool!
- uptimerobot would send notification when our site is down, it has free plan.
- deadlinkchecker I'd like to use it to help check broken links for my projects
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: