Video Tour of Wagtail 8.0
Published September 19, 2026
This video is from Wagtail CMS 2023 .
In this video we'll learn how to create a brand new StreamField using a StructBlock to create a Call to Action section on your page. And then we'll leverage the power of StreamFields by placing this code on a second page so we can re-use all our template and logic in two (or more) places.
Tutorial Page: https://learnwagtail.com/tutorials/creating-call-action-streamfield/
GitHub Commit: https://github.com/CodingForEverybody/learn-wagtail/commit/1f1c3fc2d9f62ccddb651d1081d21df69ff638e6
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
A reusable Wagtail call-to-action StructBlock can contain a required title, limited-feature rich text, button text, and either an internal page chooser or an external URL. The template checks which destination is present, uses the selected page’s generated URL when available, and omits the button if neither option is set. The block is added to both a flexible page and the homepage by defining it once, including its template in each page’s StreamField rendering, and running migrations after changing the models.
Summarised automatically from the transcript.
Automatically transcribed, so expect mistakes in names and technical terms.
Hello, in this lesson we're going to be creating one more stream field. It's just going to be a regular struct block. So by this point in the video lessons. you should be fairly familiar with struct block. We are going to add a title, a rich text, a couple button options, button text, and then we're going to apply it to two different pages. So before anything, let's open up our terminal and we need to get this thing started. So I'm going to CD into my new website, pipenv shell, and python3 manage. py run server And that will run my server. And then open up my browser to localhost 8000. Everything is good there. And go to my admin. Now because everything we've been working on so far
in regards to stream fields has been in the about page or a flex page, we're going to continue to work here So if you open up your editor, open up flex and then models. py, you will see that we have stream fields in here. So let's Let's add one more. And I'm not going to necessarily save this just yet, but I'm going to call this one CTA and I'm going to call this one CTA block. Now we also need to open up our streams, blocks. py, and create a new well, a new stream field. So let's go ahead and create that from scratch. And this is actually quite simple. Again, you should be quite familiar with this by now. So class
CTA block, and this is going to be inheriting blocks dot struct block A simple call to action section. That's all this is. Now some quick little pseudocode. I mentioned that we are going to be adding a title. We're going to be adding some sort of text. It's going to be rich text. We need button options. So is it going to be a Wagtail page or is it going to be an external URL? So let's do button page and let's also do button URL. So this one is going to be external and let's add a note. External. And I'll move that up. And this one is internal. And then button text. And that's going to have some sort of default
Lastly, let's add our metadata in there, so don't QA that please. Template is equal to streams slash cta block. html icon is equal to I don't know, edit? No. Placeholder. By the way, you can change those icons at any time. I've just been using edit or placeholder or something generic, but uh you can change it to whatever suits your needs. And the label for this is going to be call to action. That's it. Now when I save this, Flake 8 is complaining. It's going to say. Undefined name, title, text, button page, just because these are not actually doing anything. We we wrote some pseudocode here, so let's go ahead and add some proper code.
Now because this is a stream field, again, we don't use Django models, we use Wagtail blocks. So instead of typing models. charfield, what we're going to type is blocks. charblock and required Required is equal to false. That is false. Required is equal to true. The section always needs a title. And let's add A max length for this title to be 50 or 60 characters, something like that. Get rid of that one. Text is going to be blocks. Rich text block. There it is. Required is equal to true.
And let's limit the features on this one. Now we've learned how to limit features in other stream fields that are using rich text. Such as well, we have a full rich text block here and we've got a simple one here where we can really get into some of the nitty-gritty behind the scenes with uh with a function called init But this is actually a shorter way. The shorter way is you just write features, features, and you give it a list. So we're gonna say bold is okay, and italic is okay. And naturally paragraph comes with a rich text A button page. We're going to leave that one for now because I would like to give you a better explanation of that one in just a second. And the button URL, blocks. url block. Required
is equal to false. We're gonna do some template logic there. This one is going to be blocks. Char block required is equal to false. No, let's do true. It always has to be something. So default is equal to learn more and max. Maximum length is going to be let's make this a fairly long-ish button and say max length is 40. Now, lastly, we have the button page in here. Now I wanted to give a little bit of an explanation on this. So to To be able to select another Wagtail page is really easy from a string field. It's blocks. page chooser block. And we're going to say required is equal to false. And that's it. But the thing that I wanted to explain here is why do we have two button URLs in here?
So we've got a button page that is an internal URL, so another Wagtail page that we have created. Or a button URL, which can be an external URL, and we'll simply check in the template to see which one we should be using Now the reason that we do this is because Wagtail does not give us the option to choose an external page. It wouldn't make sense because we'd have the entire internet to choose from. The button URL is simply a full URL. So it will look something like https website. com slash sumpage. html. Something like that. And how Wagtail stores these are very, very different. So I'm just gonna save that, head on over to my models. py, save this one,
open up my terminal. Everything is okay. And let's refresh our page here. Yep, leave the page. We didn't do anything. And at the very bottom, where we want some sort of call to action, look at that, we have a call to action in here. And this title is going to be a call to action title, some rich text. Rich text in here and it's gonna fill this up a little bit Maybe put a little bit of bold in there, maybe put a little bit of italic in there. It's just going to be totally random. Choose a page. Uh let's choose a page. Let's say this page has to go back to home page.
Button URL. Let's also add this. Remember this, these are both optional. So the button page and the button URL are optional. And what we're going to say in the template is if the button page has been selected, use that. Otherwise use the external URL if that is also set. If neither of those are set, do not show the button. Quite simple. Codingforeverybody. com And simply learn more for the button text. Alright, let's publish that and then let's go give that a view. Now this is automatically going to complain because we don't have a template. If it ever loads. And there it is. We are missing a template. Naturally, that makes sense. We've run into this quite a few times.
And that's because where we put it in our blocks. py streams Ctablock. html does not exist. So let's open up my site, where is that folder? And then templates and then streams and let's add a new file in there. called cta block. html and in here we're just going to have some really basic print things to the page kind of thing for now So let's add a container in here. This is just bootstrap four. You don't have to worry about that stuff too much right now. Let's add a row. And then let's add coal large six. So it's always on the left. And then let's add a title in here. So we've got. Self.
title. But Caleb, where'd you get self. title from? Great question. Title. Because in cta block. html, self is You can think of it as this class. So self. title is getting this property. Now we know we've got some text in here, so we've got a rich text block and it can be bold and italic, so let's go ahead and add that. So let's go ahead and add self. text. Rich text. Now that's not going to load, and I know this one from a lot of practice is when we want to load something, all we do is type load, and if it's rich text, we know that this is going to be a wagtill. core feature. So we add Wagtail Core underscore tags. That's going to load the Wagtail Core tags
file. Now when we head on over back to our block we have button page, button URL and button text. Okay. At this point, this is all very regular Streamfield stuff. So I'm just gonna check to see if this page has a value, if it is set. So if self. buttonpage, and I'm just writing some logic in here. If button URL, so L if button self dot uh button URL and if So now we have some template logic in here. So if that button page exists, show it. If that button URL exists, show it. And we're always going to going to use the button text. So let's go ahead and create a very ugly link And this is going to be self.
buttonpage. url because when When when when just a moment please, and we will get to an example in just a moment. Okay, so in our page chooser block, what this is saying is it has chosen the home page and it has given us the entire homepage model In the sense that if we open homemodels. py, it's actually giving us this whole thing. So now we have access to banner title, banner subtitle, banner image, banner CTA, all that good stuff. That's all available to us. One of the things we don't see in here is the URL, which is what Wagtail sets for us. And that's the nice thing behind using a content management system is it will generate a URL for us.
And then self. button text. That's it. I'm going to copy, paste that in there. And all I need to do is Changes to button URL. And now when we refresh the page, this will work for us. Call to action. We've got some rich text in here. You can actually see that some of it is italicized, some of it is bold, and we have a ugly little button at the very bottom left, which is probably hard to see in this video. So I'm going to touch this up after the video so you don't have to watch me write a bunch of bootstrap. But essentially that's all there is to creating a call to action stream field. Now the next thing we're actually going to want to do is
we want to add this particular stream field to the homepage as well. So the homepage may have a different type of call to action. Maybe the homepage says sign up now and maybe the about page has a call to action to read a blog post or something It could be a different circumstance. The content may be a little bit different, but essentially the template and the layout of the template is going to be the exact same. So why not use it twice? So essentially all we're going to do is if we look at our flex page, we have our content in here, and we can literally copy and paste this, simply run some migrations And life gets easier for us in just a second. So we have our content in here.
We've got our stream field, title, and text. Full rich text, simple rich text, cards, and a CTA. Now the homepage, in my experience, generally has less stream fields. It's less customized and more of a wow factor. So So on the homepage we use these things called orderables, which allow us to create content that can move up and down, but not necessarily like a stream field. It's a lot more structured. We're going to go over that in a couple of lessons from now. Now the only other thing we have to add is our Streamfield panel into our content panels. So again, this is really just copy and paste. And because I don't want all of these, because homepage At this point, I'm in home models. py. I just want to get rid of these.
And that's because again, the homepage usually has less customized content on it Or at least it has less customized content in the sense that we're not going to be using as many stream fields on the homepage Or at least for the moment that holds true. So I'm just going to save that and it's going to complain that StreamField Panel is not defined, blocks is not defined. So let's import blocks from Streams import blocks. And oh, we also need stream field in there. So we need stream field and stream field panel. And if you're ever like, oh, where do I get either of those? Well you can always cross-reference your own code. Or you can always look at wag till demo code, or you can always reference this code. Either option, any option. It's totally fine.
So I'm just gonna be lazy and I'm gonna copy and paste this. And you can actually see that we have Wagtail Core fields already in here. So this is one of the downsides to copying and pasting. And let's see if there's any complaints. Everything looks a-okay. Flake 8 is complaining about a couple things in here. Uh but you know what? Again, I can touch those up at the end of the video when I wouldn't be wasting your time. Let's open up our terminal. Things are looking okay. But we're gonna have one problem. We have a server error. Why are we getting a server error? And this is actually super, super straightforward. And actually in the next lesson, what I'm going to do is I'm going to show you how the database is sort of structured a little bit using a database explorer.
tool that you can get available for free on Mac or pretty much any operating system. So what this is saying is no such column homepage. content This is saying your SQL table does not exist and your column does not exist. Actually it's saying it in tandem. So it's saying that your table called homepage And the column does not exist. So all we have to do is run a migration. Anytime you see something along these lines that's saying basically, your database doesn't line up with what we're expecting. So maybe go and fix that up. So let's go ahead and do that. So I open up my terminal and all I do is run Python 3 managed. py make migrations And it made a migration, it altered the field content on flexpage, and it also added the field content to homepage.
So now we do python3 manage. py. Run server. Which is actually incorrect. I got a little bit ahead of myself there. We don't want to make migrations. We already ran that. We want to migrate those migrations. So Django said, oh, here's a bunch of Python files. Now you actually have to go and apply that to your database. So Python3 managed. py run server. And when we load up our page again, this will no longer complain. Go into our homepage So we've got our banner title, banner subtitle, banner image, banner CTA, and we also have content in here. So now we have a call to action from homepage
Blah blah blah. We're not going to have any buttons in there. It doesn't matter if there's button text because neither of the buttons are going to be in there. We're going to publish this. And that's viewed live. And you're going to notice that there are no stream fields in there. Again, the reason for that is because in the land of Django and Wagtail, things have to be quite explicit. All we have to do is we know that this works on our flex page, so let's open up our flexpage template. So we go into templates, flexpage. html And all we have in here is this beautiful little loop. And all this does is loop through all of our stream fields and say, oh, okay, you got a stream field, include it. Another stream field, include it. Oh, you got three stream fields? Okay, include it. So if you open up home, home page, and let's go in here, paste that in there.
We already have Wakedel core tags being loaded. That's good. We save our page. And if you refresh Our call to action from homepage shows up. Beautiful. So now in this video what we've done is we've created a brand new stream field, a struct block stream field. with a title, a rich text where we've limited features very easily with a parameter. We've added a page chooser block, some template logic to use the button page or button URL Once we've got that working on one page, we actually extended it to work on two pages. Now at this point, you're probably thinking, or you have at least thought this, probably once or more. Why am I writing stream fields for one particular page? This is a lot of work for just a little bit of content.
And sometimes you're right. But the power comes in when we want to use that same block on another page. On properly designed websites, you will be using the same stream field, ideally on more than one page. So you can only ever define it once or you only have to define it once, you only ever have to create that template once, and then you can include it in StreamFields on other pages by simply doing This, you just add more and more and more. Although they won't all be called CTA block, they will be called something else Hi, I'm Caleb Tullin. If you like this video, feel free to subscribe, thumbs up, share it, leave a comment below, check out learnwagtail. com. Or if you are so inclined, you can always check out the Wagtail docs
for more clarity on a lot of the technicalities behind everything that we're learning. And the docs are available at docs. wagtail. io. And Wagtail also has a Slack channel, so if you go to Wagtail. io, I'm sure you can find that link on there. And you can come join us on Slack.
Define a Wagtail StructBlock with a required title and rich-text body, optional internal page and external URL buttons, and required button text. Add the block’s template, icon, and label metadata.
Discussed at 3:05The page chooser handles links to pages inside the Wagtail site, while the URL field handles external websites. Wagtail does not provide an external-page chooser, so the template can check which option was supplied.
Discussed at 5:25Add the StreamField and its panel to the homepage model, run the required migrations, and include the same StreamField-rendering loop in the homepage template. The CTA block’s existing template can then be used on both the original page and the homepage.
Discussed at 11:29Run `makemigrations` to create migration files and then run `migrate` to apply them to the database. The error means the database schema does not yet match the updated Django/Wagtail models.
Discussed at 14:33Note: 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.
Published September 19, 2026
Published July 9, 2026
Published May 20, 2026
Published April 16, 2026
Published April 1, 2026
Published March 10, 2026