Wagtail CMS: How to subclass Wagtail Pages

This video is from Wagtail CMS 2023 .

Wagtail CMS: How to subclass Wagtail Pages
0:27:44
Published November 29, 2023
5,090 views
103 likes

Subclassing is having a class (in this case it's a Wagtail Page) that can be used for other classes (Wagtail Page's). The parent class has all the common attributes for the child pages, and every child page will inherit everything from it's parent. In this lesson we'll explore that by creating a subclassed Article and Video Blog Page that share a common parent, and then we'll extend the functionality of both subclassed pages by adding new fields.

In this video we'll be subclassing a Blog page into 2 child Blog Pages with different templates. And we'll touch on the subject of .specific briefly, although the next video will be entirely about using .specific because it's a worthy subject on it's own.

Tutorial: https://learnwagtail.com/tutorials/how-to-subclass-wagtail-pages/

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

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 page subclassing lets one parent page model hold shared fields and behavior while specialized child pages add their own fields and templates. The speaker builds an article page with optional subtitle and intro image, then a video page with a required YouTube video ID, showing the model, migration, admin, and template changes needed for each. They also explain that querying the parent model returns parent-typed objects, so child-specific fields are not directly available; the next step is to use Wagtail’s `specific` property to retrieve the concrete child page.

Key takeaways

  • A parent Wagtail page can centralize shared fields such as titles, images, authors, categories, and StreamField content.
  • Child page models inherit the parent’s fields and can add specialized fields, such as an article subtitle and intro image or a video’s YouTube ID.
  • Each subclass can define its own template and conditionally render its additional fields.
  • After changing models, migrations must be created and applied before the new page types appear in the Wagtail admin.
  • Queries against the parent model include child pages but return them as the parent type, so `.specific` is needed to access subclass-specific properties.

Summarised automatically from the transcript.

Transcript

4,648 words · auto-generated Show

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

0:00

Hello, welcome to another lesson on learning Wagtail. Today we're going to be talking about subclassing. Subclassing is the idea or the concept that there is a definitive source of information about your data. Basically, it's a model that contains all the essential fields and and behaviors that you want to store and then you're going to clone them into other types of models. So a good example of this is different types of blog posts. And this is what we're going to do in this video is we're actually going to uh we're going to rework some of our code and we're going to subclass a blog post page so that we can have multiple types of blog post pages. We'll get into that in just a little bit.

0:45

But the idea is that there will be one model that will have all of the information that you need. So it could be a custom title, could be a banner image, could be something else. And then any other class that subclasses this. This particular model will also have all of those fields, but they can they can go and modify it a little bit, so they can add additional properties. Let's think of it this way. You have a family, and in this family, you have uh you've got a dad, you've got a mom, you've got a son, and a daughter. So you have a family of four. The son and daughter, the two children, will inevitably inherit personality properties from their parents. They're going to be like their parents in some way, shape, or form.

1:32

But the son and daughter, even though they are a lot like their parents, they are also their own people. So they have their own personality traits, things that they've picked up on from other people, things that they've just decided that they want to start doing. Things that differentiate them from their parents. So even though they have a lot of common properties from their mom and dad, that doesn't necessarily mean that they are the exact same. It means they have common properties, and they all share similarities, such as in a family. However, they aren't necessarily clones because they have other properties that make them them. Alright, so enough of that, let's just sort of jump into this. I'm going to go into my website and pip env shell

2:18

python3 manage. py run server. Alright, I'm gonna open up Visual Studio Code and the example that we're going to work on today is in our blog models. And so in our blog models here we have we have a blog detail page and this is the page that I want to rework. And the idea is that we're going to have a blog parent page, so sort of a blog detail parent page, and that's going to be the definitive source of data. It's going to have all the essential fields and behaviors that all of our blog post pages need. And then we're going to create maybe an article page and maybe just a video blog post page. So what I'm going to do here is I'm going to rename this instead of blog detail page.

3:03

Actually, you know what? No, that's that's a pretty good name already. Blog detail page, and I'm just gonna change the doc string Blog detail page doc string is going to change from blog detail page to uh blog detail page no let's do this parental blog detail page. It's going to have a custom title. It's going to have a blog image. So a banner of some type. Maybe let's change that name from blog image to banner image. Every blog post is going to have a category, every blog post is going to have default content, and every blog post is going to have content panels with our custom title, our banner image, our blog authors. categories and our content. Those are our stream fields.

3:49

So I'm just going to save that and I want to make migrations here just because I'm changing this model right now. So let's go ahead and make those migrations. So let's do Python 3 manage. py make migrations. Did you rename blog detail page dot blog image to blog detail page banner image? Yep I did. Python 3 manager. py migrate. Let's go ahead and run that server. Awesome, awesome. Things are looking okay. So let's go back into our editor and let's create a new class. This is going to be our first subclass. So first sub subclassed blog post page and this one's just going to be something quite generic so let's make this one

4:37

uh oh geez what should we make this one let's make it an article page so we have class, article, blog page, something like that. And this is going to inherit the blog detail page. So if I scroll up here. And we see class blog detail page is inheriting from Wagtail's core page. And where I got Wagtail Core from was WagtailCore. models imports page. So that is our default Wagtail page We are simply extending it. So we're saying that every one of these pages has to have a custom title, a banner image, categories, blog authors, and stream fields. And because we're extending that, we can now add additional properties. So this is going to be an article blog page, and what would an article possibly need?

5:25

Maybe an intro? image so not just a banner image but also an intro image and maybe it wants some sort of subtitle and all the stream fields could probably just stay the same so a subclast That 's not how you type it. Subclassed blog post page for articles. That's it Now in this page we need to explicitly define some sort of template. Well I guess we don't need to, but we probably should. And this one's going to go into blog slash article. blogpage. html, which is where it would go by default anyways, but for anyone who's just joining in, you can now understand exactly where the template's coming from.

6:11

So it pays to be explicit. We also want a subtitle, so let's add a subtitle in here. I'll just move this down. Is equal to models. charfield. Its max length is going to be something Something normal, I guess, maybe like 100 and no hundred characters. That should be more than enough. Uh default is going to be just a blank title, I guess. Uh blank is equal to no let's make it optional. Blank is equal to true, null is equal to true, and let's get rid of default. We don't need that. And let's go ahead and actually throw this on a few different lines here so that you can see this all in one screen.

6:59

So there we have a subtitle on our article blog page. Uh next we need Maybe an intro image. So let's add an intro image and this is going to be a Wagtail image. So we want this to be a foreign key. So let's go ahead and add that models dot foreign key and that's going to go straight to WagtailImages. image. That is the WagtailImages is the app name. Image is the model name Blank is equal to true. Null is equal to true. On delete. What do we do on delete? On delete is equal to models. Do we want to cascade? No, maybe not. Maybe we want to keep the image. So let's just do set null with an underscore.

7:46

And there we have an intro image. And of course you can add some help text in here. So help text, uh let's say best size. Let's tell the content editor what the best size for this image is. Best Size for this image will be 1400 by 400. I don't know, I made those dimensions up, so you know if you clone this repo down and you're like, Caleb, that's actually not the best size. Don't blame me for that. That's just an example. And so this is your blog article detail page essentially. So when we save this and we open up terminal, again we're going to go ahead, make migrations, make Migrations and I'm gonna do this all in one command python3

8:32

manage. py migrate and python three manage. py run server. This is just going to add another table So you can see that it created a model for article blog page, uh da threw it into a Python file, and then that was applied to our database. So if we open up localhost 8000, that's our port slash admin, and we open up our pages, we have a home page, we've got an about, we've got a blog page, and under the blog page we have blog post one and two. So those are regular blog detail pages. Let's open up blog and let's add a new child page and we're going to see a new option here. The new option is a

9:18

article blog page And this one is going to simply be an article blog page. A custom title So before we actually get into this, there's going to be a custom title, a banner image, we're going to be able to choose our authors, categories, and content. So you can see that it's actually the exact same as its parent class right now. That is simply because where we have content panels, it's not redefining the content panels down here where it should. So let's go ahead and let's grab all of that content And let's just paste that in here and do a little bit of modifying.

10:03

Now there is a better way to do this, but uh for anyone who is just walking into this and just learning how to subclass, this is the easiest way to sort of uh get your hands dirty with subclassing. So in our article blog page we have content panels is equal to, and this is the exact same as the uh blog detail page, the parent class, the source of truth. And all we're going to do is add our two fields. So we have our subtitle and our intro image. So let's go ahead and add a subtitle. Subtitle. And let's add our intro image. Intro image. By the way, I'm not really focusing on the order of any of these. If you want to, you can definitely order these. It's probably a good idea to throw these into a multi-field panel with some sort of heading. So like you can have all of your images together and all of your titles together.

10:50

So I am just gonna save that and reopen up Firefox here and uh what I'm going to do is just refresh this page. Yes, leave page. So we have title, we've got a custom title, we have a subtitle, we have a banner image, and an intro image. And the subtitle and intro image are both optional. And we have our help text over here. Best size for this image will be 1400 by 400. And along with that, we have our authors and our categories and all of our content and all that good stuff. So we have everything from our parent page. Do do do do do do do. So we already have our content, categories, blog authors, that's a from the related name. We have a banner image and we have a custom title. But we didn't have to go ahead and

11:36

restate all of those. We didn't have to rewrite or copy and paste all of those. The only thing we really had to do here was change our content panels So let's go ahead and create this page. Let's create this page. So we've got an article blog page, custom article blog page title, just so that we can use that in our template, a subtitle. Welcome to my subtitle. Banner image is going to be that place, wherever that is. An intro image is going to be that lady. And the author is going to be Jacob Tavlian. Categories, coding, people skills, sure. And I'm just throwing some stuff in here. I'm just really filling this out. It doesn't really matter what goes in here at this point. We just need to make sure that there is some content.

12:21

Title in here, text in here, publish And by now you should be familiar enough that when we click view live, we're actually going to get a template error. That there is no template in at all. So let's do this. Yep, there we go. blog slash article blog page. html does not exist, so let's go make that exist. So let's go open up our templates and in our blog. We just want a new file in here called Article Blog Page Now this article blog page. html can be essentially the exact same right now as our blog detail

13:07

page. html or we can extend it and we could say extends is equal to instead of base. html, we can use blog detailpage. html. So we can extend it from its parent as well if we wanted to do that. In this video we're not going to do that because I want some things to be different. And personally, I also like a flat structure. So I'm going to change that back to base. html and we just have to add a couple things in here. So I'm going to copy paste this whole thing over because I'm going to be a little bit lazy here. And let's close that. And so we have our blog image, which is no longer called blog image. This is called banner image

13:52

We have a custom title in here, so this should actually be a conditional, so it should be if self. customtitle. So if there is a custom title, use that. Otherwise, let's go ahead and Use the regular title. Self. title. And if. Let's go ahead and put these on separate lines. Because it makes it easier to read. We have categories, da da da da da da. We have blog authors. That's all good. So this is all standard stuff. We have a list of categories in here. And we have our stream fields. Now what we're missing in here is uh

14:39

simply Subtitle and intro image. So let's go up here where the subtitle could have been, where the title is. So we've got an H1 here. Let's just do another conditional. So if there is a subtitle, let's display it. So if self dotsubtitle, and let's make that an h3, self. subtitle. And if. And we have an intro image as well. So if there is an intro image, where are we going to put this? Well, let's go ahead and see what we currently have. So we have a title, we have a subtitle, we have some categories and stuff, we've got an author, so maybe just underneath Right under

15:25

underneath here we put an intro image. So let's go ahead and do that now. Also, I don't believe we need those categories anymore. That was a demonstration that I simply just Did not take the code out for. And so let's go ahead and add a container. Let's add a row. Let's make it uh column large eight, offset large two. And in here is going to be our image. And we're going to wrap this in a conditional in just a little bit. Uh but for now let's do a Wagtail image. Self. intro image is what it was called and let's fill this. Fill, Uncle Phil, 1400 by 400 as intro image.

16:12

By the way, you see me doing this uh this this type of image all the time. So there is another way to make images in Wagtail and it will take care of the whole image property for you. There the image HTML property that is. But I like to do this a little more explicitly. The reason for that is because sometimes you have a background image. So it's like style is equal to background. image is equal to some sort of URL and you want that URL on there. And you can't do that easily with Wagtail. So what you do is you assign it to a variable and then you can simply put Curly braces, intro, image. url, something like that. We're not doing that right now, but I just wanted to explain that because I feel like I've done that a lot and I haven't explained why So let's go ahead and create an image.

16:58

So image intro image dot url and the alt could be intro image. alt And let's go ahead and wrap this in a conditional. So let's do if self. intro image. So if there is one in here, let's go ahead and dent this. And if. Ta -da! Intro image. There it is. So we have our banner image. We've got a title, we've got a subtitle. Categories, authors, and an intro image plus all of our stream fields in here. So at this point you have successfully subclassed a page. Now, should you ever want to extend this, you can also add another subclass page.

17:44

So I'm going to write one more in here. Let's do this. Class video blog page, and this is going to inherit the blog detail page. And this one is going to be second subclassed page. A video subclassed page And this one is simply going to take a video ID. So like a YouTube video ID. So let's call that YouTube video ID is equal to models. charfield. Max length. I don't know how long a YouTube video ID usually is. Let's let's guess that it's 30. I hope that's long enough. That's going to be a mandatory field. And I'm gonna take all of these content panels again

18:29

and I'm gonna throw it in here. But there's one little caveat because I copied from another subclass page. This one does not have a subtitle, and this one does not have a banner image. Nope, that was a lie. This one does not have an intro image. It does have a custom title, banner image, all that stuff comes from blog detail page. But we need one more in here. We need custom YouTube video ID field. And maybe let's not put that above the title because that one is really awkward. Uh so let's put that above our stream fields and let's run migrations again. Make migrations. This is going to make migrations, run migrations, and then rerun my server. Life looks good in here. And let's go back to our admin and add another child page.

19:15

And now we can see that we have Not just an article blog page and a blog detail page, but we have a video blog page as well. So let's open this up and we will see if I scroll down here, custom YouTube ID. Okay, so let's go ahead and let's create this video page. So video blog article. Uh custom title. Custom video blog title. I spelled that wrong. Banner image, I don't really care what the banner image is. Blog author. It's going to be me this time, I guess. Uh this one's gonna be websites, YouTube video ID. I'm just gonna put this in here. Uh so this is a video ID from another Learned Wagtail video, I guess. I just sort of have that in my clipboard ready for this video.

20:03

And title and text. Title here. Text here. I'm going to publish this. And when I view live, it's gonna say the same thing about the templates. Eh, template doesn't exist. Yeah, yeah, we know, we know. So let's go ahead and take our article blog page and let's resave this as a video blog page. Now you can see that I didn't explicitly name the template in here, but I'm going to anyways, just because uh when someone's coming in, I always like them to know exactly where they can find the template. So blog slash. Videoblog page. html. That's going to be our template. That matches this name, video blog page. html. Uh does this one have a subtitle?

20:49

Nope. This one does not. This one also does not have an intro image. However, it does have a YouTube video. And so we don't need a conditional in here because it is a mandatory field. So we've got a container. And let's just get rid of this. We don't need that either. Let's add our embed code here. So I'm going to open up YouTube. YouTube. com. And it's right there. That's nice. Pause that. And da da da da da da da da da. Share. Embed. And I'm just gonna copy all of this in here. So, copy all of that. It's going to give me all sorts of stuff in here, but most of this we can ignore to the exception of our ID.

21:35

We need that YouTube ID. So we do self. youtube video video id and that matches this name in here. In fact I'm going to copy and paste that just in case there was any sort of typo in there Let's go ahead and refresh our page. Oh, would you look at that? Look, there is our video So that video is always going to come before our stream fields and there will be stream fields below it so that you can add anything you like and our video is in there. Now this video does not come with responsive styling using CSS or anything like that. I'm gonna leave that in your hands. You can do that with Bootstrap if you want to. But for now, we have two subclass pages. Now there's an issue. There's a problem and we're going to talk about this in the next lesson.

22:24

But that issue is what happens when we try to get all of our blog pages. What I want to do is I want to rerun my server and actually visually demonstrate an issue here. So we have our blog post one, two, custom article page, uh, and a custom video page. However, If we wanted to get the subtitle from one of these pages, well that's not really going to work anymore. So let's do this. Let's open up our blog listing page. And do do do do do do do where is our title? If post dot subtitle post dot subtitle And

23:09

so that's just saying if there is a subtitle, please show it. And let's go ahead and refresh our page. Now we know one of these pages has a subtitle We know that the custom article page has a subtitle. In fact, I'll prove it. Article blog page right here has a subtitle. Subtitle. Welcome to my subtitle. But this does not show up. So what's the deal here with this? Well, this is because at this point we have what's called a query set, and in this query set we have a blog detail page, blog detail page, an article blog page and a video blog page. So it has essentially a ton of different pages. And in that last video, I actually showed you the Django Shell a little bit. So I'm just gonna make this a tiny bit smaller so that we can see what's going on here.

23:56

And let's do Python 3 manage. py shell plus dash dash i python. And let's get all of our blog pages. So here's the thing with subclassing. With subclassing Because you have this blog detail page, which is technically renamed to a video blog page with one extra field on it. We have the same thing happening with this one. blog article page, and we have our parent class called blog detail page. So when we query for this blog detail page, what it's actually going to do, it's going to grab all of the child pages that it inherits as well. Or in or that they inherit rather. So the article blog page inherits from blog detail page, and also the video blog page inherits from blog detail page.

24:42

So when we open up our shell, and again this is why we were learning about this. uh not that long ago was if we go ahead and we get let's say posts is equal to blog detail page dot objects dot live We have several different posts in here and we can actually see we can actually see that we've got a blog detail page in here, blog detail page in here Another blog detail page and another blog detail page. But we know that in fact these are not blog detail pages anymore. That this one Could be a blog detail page. It's at some point inheriting from a blog detail page. However, the video page, the video blog article is actually a video blog page, and this one is an article blog page. And so in the next lesson we're going to learn how to tackle this problem.

25:29

How do we get specific properties? Now because there's a problem here, because all of these are blog detail pages. The article page, or this one that you would think would be an article page, does not have a subtitle. And this one currently does not have a YouTube video ID. So how do you go ahead and get those? How do you grab those if all it is doing is grabbing that parent class that doesn't have those properties yet? Well in the next video we're going to talk about a thing called specific And this dot specific will actually allow us to grab some of those. We're going to go into more detail about how all of that works in the next lesson. Alright, so that's all there is to really know about subclassing for now. Specific is going to get into the nitty-gritty details of grabbing child page or

26:15

or child class properties. And at this point in time, you know how to grab all of the pages, you know how to subclass, you know how to add additional properties. So that's all there is to really know about subclassing. Essentially, again, you can think of it like a family where subclassing has a set of parents and a set of children, and the children often They act like their parents, because who else are they gonna model after? In fact that's sort of an an accurate pun. Uh who else are they going to model after other than their parents? And so even though they act and sometimes look a lot like their parents, they are not necessarily their parents. They share a lot of common attributes. However, they are also their own people with their own properties and methods

27:01

or personality traits, if you will. So that's it for this lesson. If you liked this lesson, if you learned something from it, don't forget you can always hit subscribe, you can share, you can comment My name is Caleb Tallinn. I'm the voice behind the video. If you want more videos like this, you can always head on over to learnwagtail. com. And if you want more specifics on subclassing, you can always hit up the Django Docs. The Django Docs are really, really well written and definitely covers subclassing. So if you want more information, maybe I didn't cover something, maybe you've got a question that I I can't answer because I'm just a video, you can always head over to the Django docs and they will have you covered.

Questions this talk answers

How do I subclass a Wagtail Page to create different types of blog posts?

Create a parent blog detail page containing the fields and behavior shared by all posts, then define child page models such as article and video pages that inherit from it and add their own fields.

Discussed at 2:18

How do Wagtail page subclasses inherit fields and add extra fields?

A subclass automatically gets the parent page’s model fields, such as the custom title, banner image, authors, categories, and content. It can then add fields of its own, such as an article subtitle and intro image or a video’s YouTube ID.

Discussed at 4:37

How do I add an optional subtitle and intro image to a Wagtail article page?

Define a nullable, optional `CharField` for the subtitle and a nullable, optional foreign key to Wagtail’s image model for the intro image. The example also adds help text describing the recommended image size.

Discussed at 5:25

How do I set up a template for a Wagtail page subclass?

Give the subclass a template path, such as `blog/article_blog_page.html`, and create that file in the templates directory. The template can copy the parent page’s layout, extend another template, or add subclass-specific fields and conditionals.

Discussed at 6:11

How do I create a Wagtail video blog page with a YouTube video?

Create a subclass of the common blog detail page with a required `youtube_video_id` field, then use that ID in the video page template’s embed code. The video is rendered before the page’s stream fields.

Discussed at 17:44

Why don’t child-specific fields appear when I query a Wagtail parent page model?

A query for the parent page model returns the parent representation of both ordinary pages and child pages, so fields such as an article subtitle or video ID are not available directly. The talk identifies `.specific` as the mechanism for retrieving the child page properties, but defers the detailed explanation to the next lesson.

Discussed at 23:36

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