Leaving RoutablePageMixin
aside for a moment, something to understand with Wagtail is that you shouldn't think about URLs the same way as you do with a regular Django website (or Rails or whatever framework which maps routes to views/controllers). With Wagtail, the path to a page is built from the page tree. Therefore, if you want the category to appear in the path, it needs to be part of the tree. In your case, it would be something like this:
HomePage (title: My Website, path: /)
|- CategoryPage (title: Health, path: /health/)
| |- PostPage (title: Post 1, path: /health/post-1/)
| |- PostPage (title: Post 2, path: /health/post-2/)
| \- PostPage (title: Post 3, path: /health/post-3/)
\- CategoryPage (title: Diet, path: /diet/)
|- ...
\- ...
In this scenario, you wouldn't need any further mangling. It's simple and you get category pages for free which can list all the posts within that category. Obviously, the downside of this solution is that a post can only belong to a single category this way.
Now let's see how RoutablePageMixin
s work and what they can offer. They allow you to create virtual pages (pages that don't exist in the tree). They achieve this by suffixing the path, e.g. you have a BlogIndexPage
at /blog/
but you also want to provide yearly archives at /blog/archives/<the-year>/
and tags filtering at /blog/tags/<the-tag>
. The way you can think of it (disclaimer: it's not how it actually works) is that when Wagtail receives a request for /blog/archives/<the-year>/
, this path won't exist in the tree (nor will /blog/archives/
), but /blog/
does, so Wagtail loads you're routable page and checks for a route that would match /archives/<the-year>
(because it has already resolved /blog/
).
So if you wanted to modify what's before the path of your current page, the RoutablePageMixin
must be a part of the declaration of the parent page in the tree. Assuming your PostPage
es are direct children of your HomePage like so:
HomePage (title: My Website, path: /)
|- PostPage (title: Post 1, path: /health/post-1/)
|- PostPage (title: Post 2, path: /health/post-2/)
\- PostPage (title: Post 3, path: /health/post-3/)
Then you would include RoutablePageMixin
in the declaration of HomePage
:
form django.shortcuts import get_object_or_404
from wagtail.contrib.routable_page.models import RoutablePageMixin, route
from wagtail.core.models import Page
class HomePage(RoutablePageMixin, Page):
# Some fields
@route(r'^(?P<category_slug>[\w-]+)/(?P<page_slug>[\w-]+)')
def post_page(self, requets, category_slug, page_slug):
post = get_object_or_404(PostPage, slug=page_slug, category_slug=category_slug)
context = {
'post': post
}
return render(request, 'posts/post_page.html', context)
But now you have an issue because your posts technically exist at 2 different URLs, i.e. /<category_slug>/<page_slug>/
(because of the RoutablePageMixin) and /<page_slug>/
(because of its place in the tree). You could live with that and simply set the canonical URL or make your posts redirect to the correct URL:
form django.shortcuts import redirect
from wagtail.core.models import Page
class PostPage(Page):
# Some fields
def serve(self, request, *args, **kwargs):
homepage = self.get_parent().specific
url = homepage.url + homepage.reverse_subpage('post_page', category_slug=self.category_slug, page_slug=self.page_slug)
return redirect(url)
That being said, I would advise against using redirect
. This is really bending Wagtail against its core principles.
If you did change the URL structure so it doesn't match Wagtail's tree path, you'll also want to consider changing the sitemap urls.
class PostPage(Page):
def serve(..):
....
def get_sitemap_urls(self, request):
"""Overwrite the url."""
return [
{
"location": '', # Reverse subpage url
"lastmod": (self.last_published_at or self.latest_revision_created_at),
}
]