How to Add Routable Pages to your Wagtail CMS Website

This video is from Wagtail CMS 2023 .

How to Add Routable Pages to your Wagtail CMS Website
0:24:27
Published November 29, 2023
13,282 views
218 likes

Routable Pages allow us to create "subpages" under any regular Wagtail Page. Essentially, we can create pages with urls that aren't accessible through the Wagtail CMS admin. We'll learn how to implement routable pages, how to add additional context to the new page, how to render a new page template, how to reverse the routable page url in the template and how to reverse the routable page url in the Wagtail model.

Tutorial: https://learnwagtail.com/tutorials/routable-pages/

The Git Commit: https://github.com/CodingForEverybody/learn-wagtail/commit/d215c36a8aa5094d53e5b83e7b0cc4a3d05e04d5

Part 2 of this video can be found here https://www.youtube.com/watch?v=g_csyhvWrBg

Learn Wagtail from scratch with the official Wagtail for Beginners Course
https://learnwagtail.com/wagtail-for-beginners/

Used in this video: Wagtail 2.4, Python 3.7, Django 2.1.5 #Wagtail #Django #Python

Summary

Wagtail routable pages add extra URLs and views beneath an existing page without creating separate pages in the Wagtail admin. The speaker shows how to enable the routable pages app, mix `RoutablePageMixin` into a page model, define routes with the `route` decorator, render custom templates, and pass both existing and route-specific context. Using a blog listing page, they add a “latest posts” route, query or limit posts, generate links with Wagtail’s routable-page template tags, and reverse route URLs from Python. Routable pages can also support optional trailing slashes and URL parameters, making them useful for subscription forms, product actions, author pages, categories, tags, and date-based filtering.

Key takeaways

  • Enable `wagtail.contrib.routable_page` and use `RoutablePageMixin` with the `route` decorator to define sub-URLs on a Wagtail page.
  • A route can render its own Django template while reusing the page’s existing context and adding route-specific values.
  • The example adds a `/latest/` route to a blog listing page and limits the posts shown there.
  • Use Wagtail’s routable-page URL template tag or `reverse_subpage` instead of hard-coding route paths.
  • Routable pages can be extended with optional trailing slashes and URL parameters for categories, tags, authors, or date-based filters.

Summarised automatically from the transcript.

Transcript

4,014 words · auto-generated Show

Automatically transcribed, so expect mistakes in names and technical terms.

0:00

Hello, welcome back to another lesson on LearnWagtail. In this video, we're going to be learning about reputable pages. Now a routable page is basically a page that Wagtail doesn't have too much control over, so in the admin we can't go and add extra stuff necessarily. But we can create some sort of additional URL. For example, if you have a product and then you have a buy page, you may not want to create a separate Wagtail page that is just a buy page. It might have the same layout, might be grabbing the same data, it might always be the same. Doesn't really matter what the product is. Well I mean the product information is going to change, but like everything else on the page is going to be the exact same, even the logic is going to be the exact same. So why create a page in Wagtail where the data can vary like that

0:48

if it's all in an enclosed system already And this is where routable pages come in. Now I want to quickly show you what a routable page is, and then we're going to use routable pages as an actual real-life thing in a blog But first we need to create a demonstration. So this demonstration is going to take place in our home page. Now before we do that, we actually have to go and enable this. So we open up my site and go into your base. py settings and we are looking for. . our installed apps here. So I just found my installed apps and I'm going to install routable pages by adding Wagtail.

1:34

contrib routepage. And that is it. Now routable pages are enabled. Now to make a quick demonstration, let's open up homemodels. py And we need to do a couple imports here. We're going to add a readable page mix in, and then we're actually going to overwrite the home page, and then we're going to change that. to an actual page. Just as a quick demonstration here. My imports need to be from Wagtail. Contrib dot routable page dot models import and I want to import the routable page mixin and I also want to import a decorator called route And next I'm going to put a routable page

2:19

mixin in here. And at this point, this currently does absolutely nothing And at the bottom, I'm just going to leave some space there. And I'm going to create my first route And all this one is going to do is it's going to override the homepage. So I'm going to use a decorator inside of the class called homepage, called route, and I'm going to give it some regex parameter. So let's just make this uh blank for now. Starts with, ends with, and give it a function. So def Let's call this uh let's call this the subscribe page the subscribe page because that's what we're going to end up turning it into

3:06

going to take self and request And essentially, that is it. Now we do need a couple other things in here just to make this actually work because if we save this. This is not going to work the way that we expect it to work. Just because we're saying, oh, here's a routable page, but technically at this point, Wagtail doesn't know what template to render, so we need to go and render. a page. Now I'm just going to get this set up right now and then we're going to go and do our imports. So what I want to do here is I want to actually add args and quargs just in case there's anything else coming in. And because I have existing context that I might want to pass into this page, I'm going to grab that existing context and then I'm going to render a new page. So I'm going to do this with context is equal to self dot

3:51

getContext. request, args, and quargs. And we've learned about get context a little bit already, so we should be somewhat familiar with that. And simply return context is what we would generally do in the getContext function or the getContext method. Instead what we're going to do is we're going to return render we're going to pass in the request we're going to pass in a path to our new URL so let's do subscribes uh home slash subscribe subscribe. html and throw in the context as well. Now you can see that we don't have render in here, but let's just get ahead of ourselves for a sec and let's do a special

4:37

Test is equal to hello world123123. Something like that. Now you can see that Render does not exist. I pass that in as arg. It should be args. And let's go to the very top and let's import from Django. From Django dot shortcuts. render. Now in my terminal, pipenv shell. Python 3 manage. py run server. I'll make that slightly bigger. I just totally ruined that text there too. And when I open up localhost 8000, it says template does not exist, home slash subscribe. html. That's because it's trying to overwrite our current template. So this is what a routable page does, is it's trying to overwrite everything

5:26

Now we can at any point in time also access all the other properties that are inside of this But while of this class, because it's object-oriented programming and because our method isn't still inside of this class, it's totally acceptable to grab self. banner title for instance if we needed just that. Now let's go ahead and create the subscribe page. So let's open up our where are we? My site. Templates Let's close a couple of these and let's create a new file in here called subscribe. html. And I'm going to be lazy. I'm going to copy and paste all this and then I'm going to delete all this because. Laziness.

6:11

This is a test page and let's also see what we got from our special context called a special test. I will click save, I will refresh our page. This is a test page and hello 123123. So this is a test page, is what I wrote in the template, and this is what I passed in from the context. So at this point, this is basically a routable page. There's nothing more we need to do except actually make this page look nice. So let's go ahead and actually make this somewhat useful because right now this is overriding the homepage and that is terrible. So let's go ahead and create a page called subscribe. It has to end with a slash because our application ends in slashes.

6:59

I'll refresh the page, it will show us the homepage as expected, and let's go to subscribe. There we go, we have a custom routable page. And this lives off of the homepage. So anytime you create a home page, it will also have a subscribe page on it. Or in the event of creating a blog detail page, if you wanted to add an authors subpage to it, you could do that as well. And that page could just loop through all the authors and and show the author information. Which actually is not a bad idea. It's probably pretty good for S SEO, I imagine. Now as an idea to extend this page, what we could do is We could have a Django form on this page, and if you remember from a couple lessons ago, if you were with me back then, let's make this smaller.

7:47

A couple lessons ago, we made a model called subscribers. Now if you were with me back then, thank you for joining me. If you are just stepping in now, we created a brand new Django model and we registered it inside of Wagtail so that Wagtail could Edit, delete, and create, and also update, uh, basically a subscriber model that has an email and a full name in it Now what we could do if I just click back hard enough is on this page we could have a form where people could actually subscribe and they could create their own row in that table. We're not going to get into that yet. And in fact, what I'm going to do is I'm going to add a to-do in here. Add a Django form that lets people self-subscribe to

8:33

this website using subscribers model Just like that. Alright, so we are gonna close this down, and I think at this point what we should be doing is well let's also get rid of that. Let's go and add this to our blog listing page. So we have a blog listing page that lists all of our existing blog posts. But let's see, let's say we only wanted to create a page where the top five posts existed or the latest two posts or the latest one post existed or something like that. Your use case is going to be very different. I'm trying to be purposely very vague here While being specific enough about routable page URLs, so that you can actually make use of these in your own projects as well without having to actually use all of my existing code and then trying to figure out what it does.

9:26

So let's open up blog. We have blog models and let's do the same thing here. So our blog detail page is not the one we want. Our blog listing page is the one we want. Roadable page, I spell that wrong literally every time. Uh roadable page mixin. Page mixin. Nailed it. Uh we need to import that. Now where did we get that from? Well again we can just go back here. I'm gonna be lazy, I'm gonna copy that because that's exactly what we need. And we also need from Django Diet Shortcuts Import Render. That's it

10:12

Now we have a blog listing page, and on this blog listing page, let's go ahead and create a new route. And this route is going to be for uh latest posts and it's going to show the Well it depends on how many blog posts we have in this example. If we've got three, maybe we'll show two. If we've got two, we'll just show one, something like that. So this is gonna be regex, and this is going to be latest. Latest Starts with, ends with, Def, latest blog posts. Self request args quargs Context is equal to self. getContext request

10:58

args quargs Return, render, and remember we always want to throw the request back into the render so that we have access to that in our template. We also want this to be using the blog slash latest posts. html file as our template and throw in the context. Now let's see if I am missing anything That looks okay. And let's go to our blog. Now we're not gonna see a link to there, but if we go to slash latest There we go. It says blog latestposts. html does not exist. Let's go and create that now. So in our templates folder under blog, let's just create a new file. We'll call that latestposts. html And I'm going to grab all that because I want to be lazy and type less.

11:48

And actually, I was onto something there. So let's grab that blog listing page. Let's grab the whole thing. Copy and paste, and in fact what we're going to do is we're going to reuse this entire template, which we could cheat, and we could also reuse this template, but this one might be a little bit different in the future. So the latest blog post is going to have some context in here called posts, and it's just going to be the latest one, two, or three posts, something like that. But it's also going to render everything the same. But to make sure that this is different, let's do H1 latest posts. So we have that in there, uh, but let's let's do a little bit of a thought experiment here. So let's say regular context variable

12:33

Here's what we're gonna call this. And let's just see what happens when we do a thing. So open up your blog models. py again. And in our regular context, let's just add regular context in here. Let's see what happens. And the idea is the idea is, hold wait, hold on, just one sec here. Hello world, bunch of numbers. The idea is that because this context is already grabbing all of this context, we should have access to it in here, shouldn't we? Now when I refresh my page it says latest post, hello world123123123123. That is really good news. That means that we are getting all of our regular contacts so that Posts is also going to show up, which as you can see, posts is going to show up. Now if we wanted to limit these posts, what we could do is we could say

13:23

context posts. Which is right here is equal to context posts. But then limit it to Well, we could limit to like two or one or whatever. Let's just do one. And so basically we're overwriting our post context here. This is sort of a funny way of doing this. And it only shows the latest post. So now we have latest posts, and we're actually reusing a query from the database so that we're not having to re-query the database. Now I'm going to clean this up and I'm going to show you one more thing. Let's do latest posts is equal to

14:10

blog detail page dot objects dot live dot public and let's limit that to one as well And let's open up latest post. html and just swap out our variable inside of our. Let's see if I can make that bigger Swap out posts for latest posts. Now when I refresh our page, we're going to see the exact same thing happens. Ta -da! I also got rid of that variable in there, so that's not gonna show up anymore. And it still only shows the one post, so that's perfect. Now if you wanted to add anything else, you could absolutely do that. So let me scroll up here. Context. is equal to Caleb No. Let's do name

14:56

is equal to Caleb Tallin And let's do context website is equal to learnwagtail. com And when I put this in here, let's do do an H2. Let's show the name and let's show the website. There it is, Caleb Tollin, LearnWagtail. com. But if we were to take this exact same thing and show it on the blog listing page, because again this is living under the same class. So, just to show you what I mean, blog listing page is our class name in here. And we have latest blog posts as a routable page.

15:42

Will we have access to name and website Now to clarify this, I've added context just in this one routable page, not in the context of the entire page, just this one routable page. So theoretically, I will not have access to name and website. And when I save and refresh that page, nope, that's the wrong page because I'm on latest. It only shows that colon because, well, that wasn't part of the context. That was just something I threw into the template. So it does not have the name and the website So that is living proof that when you have a rotable URL like this, you can at any time add additional context. So you can have existing context, but you can also have additional context.

16:30

Now I'm just gonna clean this up real quick, get rid of that stuff, get rid of that. In fact, I'm gonna undo this one because that is A little less efficient than I like, so I'm gonna Alright, so everything is back to normal. What if in my main blog listing page I wanted to link to the latest pages? How would I get that? Now that's a good question. We can hard code it, and we can always do slash latest, knowing that that's not really going to change once we write it as a routable URL, but there is still that possibility that another back-end developer is going to see that and go, hmm. Maybe we want to change it from latest to top five or something like that. Well, that decision is sometimes made without a developer, and when that happens, the developer basically just goes and does it.

17:18

And we have to live with the consequences of a broken page or a 404. So we don't want to use that. In fact, there's actually even a better way of doing this. And all we want to do here is we want to load beside our Wagtail images tags, we want to load Wagtail routable page underscore tags. And let's create a link in here. So we've got A and in here it's going to look not like a Django URL. So a Django URL looks like this. So some page Looks like that and that will go to some page. That's not what we're looking for. Because this is a Wagtail routable page, basically think of it as a subpage, we're going to have to write something a little bit different.

18:03

So we write rotable Routable, should I be able to ever type that right? Routable page URL page, which is the equivalent to self. In this case we're gonna say page. And then we need to give it one parameter in here. And that one parameter needs to be a string, which needs to be some way of telling what this is. Now, by default, we can use the method name. So let's go ahead and throw that method name in here, which is latest blog post. I'll make that a little smaller. Latest blog post, and let's say View latest posts only. And now we have a link that goes to view latest posts only. And when I click it. It does in fact show up with the latest posts.

18:49

So that's pretty good. Now another place where you would use this is categories or tags or something like that. So you could have blog slash category name, for instance, environment, something like that. preferably spelt properly. Or it could be uh politics or anything like that. And then it would simply look up the category from the slug in the URL and Well return what you need to return. And return posts that have that particular category. Now we don't we don't have that set up yet. So we're not going to do that. But that is how you get the latest blog post. Now what what happens if you have some standard at your company where all of your method names are very explicitly named? Latest blog posts.

19:34

only shows last five, something like that. Well you may not want to use that name. So what you can do is you can actually overwrite the name in the route decorator and say latest posts. Grab that name, head on over back to your blog listing page. html. In just a moment, I'm going to close up some stuff here. And we can use latest posts instead of using the method name, which was latest blog posts only show last five We use latest posts instead. And when we refresh our page, it still works beautifully. Now there are a couple more things that we need to go over. One is reversing a page inside of our view, because that's going to be very common

20:24

So let's say in our context here, we wanted a URL. For whatever reason, we didn't want to use it in our template tags. We wanted to be able to get this URL from inside of this and pass it directly into the context. Well we could do that as well. That's absolutely doable. And all we would do here is contacts a special link and I'm just gonna call it something crazy like that just to really demonstrate that this is going to work for you and we're going to do self dot reverse subpage And we give it the subpage name, so latest posts. And I'm going to save, grab that link, throw this in here, and let's do

21:09

an H2. Special link is a special link. And when I refresh our page, we're going to see that it shows up with latest slash. Now as a cool little trick I want to show you something here. This says latest slash, but if someone goes to just latest, that should work as well Should that ever give you a problem, you can always add a question mark after your slash in your route. And this basically just says that the slash is optional, and when it is optional and you have a typical Wagtail and Django website running, it will automatically append slashes to your URL. So it will say, oh, it's actually missing that slash. Okay, I'm going to add that slash back in and it's going to match again. Should you ever run into that?

21:55

That's just a funny one I've seen a few times over the last couple of years. But that's how you would get around that. Okay, so that is all for this lesson. There is a little bit more that we could learn, but we're running out of time for this video. So uh if you really want to, what you could do is you could also add parameters into your your URL. You can always check out the docs for that. The docs are uh they're pretty good on this one. They're not great, but they're not terrible either. So for example, if you're on your blog and it's like your website. com slash blog slash year slash month. You could grab those and then you could filter your blog posts by that. Again, not something we're going to cover because we're just going to cover the base of a readdown page right now.

22:43

So in summary, here's what we've learned. We have learned about routable pages. We have learnt about reversing subpages. We learnt how to render custom templates using the render function from Django. We learned how to overwrite templates. And we learned how to overwrite our existing context from within a Wagtail page. So we've learned quite a bit in this lesson. Routable pages are a really, really good way to add uh additional functionality to your website. For example, uh sub pages like a buy page from a product page or an author's page off of a detail page, a blog detail page that is. or in this case, a latest post page or a category listing page for your blogs as well.

23:30

Or a tag listing page or something like that. There are a lot of uses for routable pages, and now you are somewhat familiar with them. As always, I am your instructor. My name is Caleb Tallinn. Thank you for joining me today on a lesson about Wagtail routable pages. You can find more tutorials and videos like this on learnwagtail. com. They're also all available on YouTube for free. If you feel like I didn't cover enough, you can always explore the Wagtail documentation at docs. wagtail. io. Don't forget, you can always subscribe, and if you're feeling Very generous. You can also share this video with your friends in Slack, in Facebook groups, in Python groups, and Django groups.

24:15

Or if sharing isn't your thing and you like this video, you can simply hit that thumbs up button, that like button, and again, thank you for joining me today. I will see you in the next lesson.

Questions this talk answers

What are Wagtail routable pages useful for?

They let you add extra URLs or subpages without creating separate Wagtail pages for content that uses the same layout and logic. Examples include a product buy page, an author page, a latest-posts page, or category and tag listings.

Discussed at 0:00

How do I enable routable pages in Wagtail?

Add `wagtail.contrib.routable_page` to `INSTALLED_APPS` in `base.py` settings.

Discussed at 0:48

How do I create a custom routable page and render its template in Wagtail?

Add `RoutablePageMixin` to the page model, define a method decorated with `@route(...)`, build any needed context, and return Django’s `render()` with the request, template path, and context. The route can then render a template such as `home/subscribe.html`.

Discussed at 3:06

How do I add a latest-posts route to a Wagtail blog listing page?

Add `RoutablePageMixin` to the blog listing page, define a route such as `latest`, and render a separate template. You can reuse the page’s existing context or query live, public posts and limit the result to the number you want.

Discussed at 10:12

How can a Wagtail routable page add or override template context?

A routable method can start with the page’s existing context from `get_context()`, then add new values or replace existing ones before rendering. This allows a route to provide special data without changing the context for the entire page.

Discussed at 15:42

How do I reverse a Wagtail routable-page URL inside a view?

Call `self.reverse_subpage()` with the route name, such as `latest-posts`, and put the returned URL into the context for use in the template.

Discussed at 20:24

How can I make the trailing slash optional on a Wagtail route?

Add a question mark after the slash in the route pattern. With the usual Wagtail and Django configuration, the framework can then append the slash automatically when needed.

Discussed at 21:09

Note: We understand that names change, people change, and bodies change. We respect each individual's journey and privacy. If you have any concerns about a video or need us to remove content, please don't hesitate to contact us. We will handle your request with care and promptly address any issues.

More videos from Wagtail CMS