How to Use ListBlocks in Wagtail CMS to Create Repeating StreamField Content

This video is from Wagtail CMS 2023 .

How to Use ListBlocks in Wagtail CMS to Create Repeating StreamField Content
0:19:52
Published November 29, 2023
19,670 views
342 likes

Occasionally you'll want a StreamField that can have multiple repeating content areas. A good example is the design component known as a Card. In this lesson we'll explore a ListBlock to enable us to create unlimited cards with custom data, ImageChooserBlock, PageChooserBlock and how to loop through a ListBlock in your Wagtail CMS template.

Tutorial Page: https://learnwagtail.com/tutorials/how-use-listblocks-create-repeating-streamfield-content

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

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

Stock photos provided by Pexels.com.

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

Summary

Wagtail’s ListBlock can repeat a nested StructBlock inside a StreamField, making it suitable for dynamic groups of content such as Bootstrap cards. The speaker builds a card block containing an image, title, description, and optional internal-page or external-URL button, then adds it to a page model and renders it with a custom template. The template loops over the cards, creates image renditions with Wagtail image tags, and uses conditional logic to prefer an internal page link, fall back to an external URL, or hide the button when neither is provided.

Key takeaways

  • A ListBlock repeats a nested StructBlock as many times as the editor needs.
  • The example card contains an image chooser, required title and text fields, and optional page and URL links.
  • Custom block templates are placed in the templates directory and can loop through self.cards.
  • Wagtail image tags can generate a 300-by-200 rendition and expose its URL and alt text.
  • Template conditionals can prioritize an internal page link, use an external URL as a fallback, and omit the button otherwise.

Summarised automatically from the transcript.

Transcript

3,180 words · auto-generated Show

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

0:00

Hello and welcome back. In a previous lesson, in a previous video, we were talking about creating stream fields. And just as a quick little recap, in our blocks. py we have Title and Text Block, Rich Text Block, and Simple Rich Text Block. But now let's say there is an instance where you want repeating content inside of a stream field And that repeating content needs to be dynamic. How do you go about doing that? Well in Wagtail there's this thing called a list block, and in this video, we're going to dive right into list blocks and repeating content inside of a stream field. Now a good example of this is if we go to get bootstrap bootstrap. com

0:46

and let's look at some documentation. Let's quickly skip to cards Now let's say you have this on your page. And this is called a card, by the way, if you didn't know that. And basically it's an image on the top, a little bit of Text for a title, a little bit of text for a description, and maybe an optional call to action button that can either be an internal page or an external page. Now, on most sites, you don't see one card by itself. You actually see, I think it's near the bottom here. Card deck, where are you? Something like this. But more commonly something like this. So you have cards that are side by side. This is what we're going to create actually

1:33

is side-by-side cards. Now this is really great for listing people on your team uh features for a product or service that you're creating. Really anything with uh an image and a little bit of text as a description and maybe a call to action. So let's get started with that. First thing I do is I open up Visual Visual Studio Code. That's just my editor of choice right now. For you, that might be Sublime, that might be Vim, that might be Adam, it doesn't really matter. Just open up your favorite editor and let's open up blocks. py and create a brand new stream field. Now this one is actually quite simple. And it's going to look, actually, maybe if I do this up more, this one's going to look a lot

2:19

like this, our title and text block. So if I type class card block and it's going to be a struct block And give us a little doc string in here. So let's call this cards with image and text and buttons. Plural, maybe. Now instead of having It look like this. What we actually want to do is we do want that title. So in fact, we can just copy and paste that exactly because we want a title of our section. So we're going to create a brand new section with a title on top and some cards underneath. And then underneath, let's go ahead and create our list

3:05

block. Now this list block is actually quite simple, and it's actually going to look like a mix of everything we've already done and a mix of this sort of styling. So we've got a list with tuples in it. So first we need to figure out what this is going to be called. So let's assign a name called cards Because they're going to be cards and it's going to be blocks dot listblock. And inside of this list block, it will always be a blocks dot struct block because there's going to be a structure of blocks inside of our list. So basically all this is saying is Our list block is going to take a struct block and it's going to repeat it as many times as the user wants.

3:52

And at this point, it's going to look like a list. And now we're going to have some sort of uh da-da-da, tuple name and uh blocks dot char block something like that. And so now you can already see that this is starting to look a lot like this. But this is not real, so let's make something that is real. Let's let's add an image. We haven't done this yet. So to add an image, we're just gonna call it image. And we're going to give this image Chooser block and required is equal to true. Now if I save this, this is going to complain that image chooser block is not defined Or at least I thought it was going to, but it didn't, and I am uncertain as to why.

4:41

Regardless, we need to import that. So this one comes from Wagtail, images, blocks, because images are their own beast in Wagtail. They get their own blocks. But we don't want to import blocks because then we'll have blocks and blocks twice, and that's going to well that's just not going to work. So we're going to import it directly from blocks and we are going to import image chooser block And now we have direct access to that fella right there. Next we need a title, so let's add a title And remember, because it's not a Django model, this is a complex data type, and in a stream field, we don't use models per se, we use blocks.

5:26

So instead of Models. char field is going to be blocks. charblock. Required is equal to true. Max length is equal to uh let's give this a short title, maximum 40 characters. So now we've got a title for our card. Next we need some text. Again, very simple. Blocks dot text block. Required. Yes, we're gonna say this is required as well. Max length, this one can be longer, 200 characters. We need two buttons, I think. This doesn't have a button in here, but at the very top it did So this is only going to have one button per card, but that button can be an internal URL, so a link to another Wagtail page, or it can have a link to an external URL, which would use

6:15

a URL field in Django or a URL block in Wagtail Stream Fields. So we need to be able to give the user the option of both. So let's call this one button page. blocks dot page chooser block and this allows us to select a page required is equal to false and let's create another tuple we'll call this one button URL and this one's going to be blocks. url block and required is also false Now, because these are both false and we ideally want a button to show up in one of these cards, in the template we're going to use a little logic. In the template we're going to say, is there a button page?

7:01

No. Okay, well is there a button URL? Yes. Well then use that URL. So that's essentially what it boils down to. And in fact, this is a good idea to add some help text in here. Help text is equal to, if the button page above is selected. That will be used first. That is the longest line ever. And I'm going to use a formatter called black later to make that nice and pretty Now just to check in, let's open up our terminal. Things look good. Let's open up our flex page, models. py, so that is flex slash models. py. That's the file we're in. And let's add a new one in here called cards. And this is a card block.

7:47

Now for us, this name does not matter. Wagtail uses that internally. This name, however, the name of the class must match the name of the class we just created. And that's because at the top here we're importing from streams and we're importing the blocks file. And so this is. streams slash blocks. py, this is our file, and this is our class name. So that's that's how we got that. So let's go ahead and give that a save and let 's open up localhost 8000 slash admin, edit our about page, and at the very bottom, oh we have cards. So we have a title in here. Uh

8:33

about us cards? No, let's maybe do staff cards And I need three pictures of people because I want three cards. So what I'm gonna do is I'm gonna go to pexels. com, grab some free stock photos. Alright, there we are. So let's grab this guy, download. We just need a small one. Let's grab this lady. Small And let's also grab sure. Do do do to do do download. Okay, there we go. I got three images. And now I want to create three cards. So I'm gonna upload a card here. Or upload an image rather, and I'm gonna upload the first one.

9:26

Title, uh Mark Smith. Nope. Let's do Matt Smith. Button page? Uh yeah, sure. Let's use a button page and let's go to the home page. But there's not going to be a button URL. This is an external URL. So when this form validates, when this page validates, this is going to check for HTTP or HTTPS. And if that is not in there, then this field is not going to work. But because this is optional, as long as that field validates as empty, we can still save this. Let's add another image. Adding another image. Okay. Clara Oswald.

10:13

And let's go to https google. com. And let's just add one more. Oh, by the way, that's what this list block did. It gives us this button that says add another. So we can just keep adding these over and over and over again. And because it's a stream field manner, we can move this up and down. So if we wanted to put Clara before Matt Smith, we could move her up like I just did. Matt Smith is now in the middle. And lastly, let's add our third person. And let's call her Jane Doe. And this will this one will link to about Okay, I'm gonna save this page now. I have three cards in there.

10:58

And when I refresh my home page, nothing happens because it's the wrong page, but if I go to the about page. I have a stream field in here with uh title with text. Whoops. Title with text. And you can actually see that I've got a bunch of image data in here. I've got button, button URL, it's got struct values. So this is how This is how Wagtail sees a list block. Now we actually don't want that at all, because that is no way to live your life. So what we need to do is back in our blocks. py. You may have noticed that we did not add any metadata, so let's go ahead and do that. And in fact, I'm going to copy and paste because I am

11:44

being lazy. Placeholder, we'll call this. Uh let's call this one staff cards and cards. Uh card block, I guess. That's good enough. I'm gonna take this name and I'm going to create a new file inside of my templates. So Close that up, close that up, go into my site, templates, streams, and let's create a new file in there We'll call that one card block. html. Okay, so we have our card underscore block. html. There's nothing in it. If we save that, And we refresh our page, we see that nothing shows up now. So now Wagtail is saying, oh, okay, you have a template, it's empty. We're just not going to show anything because that template is empty.

12:33

Now if I did something like this, I could do an h1 self. title, and where I got self from is this title here. It's not part of our cards loop. Refresh and look at that, it's got our title in there, our staff cards, that's what we called it, which is reflected right there Okay, so that's all cool, but we need to be able to loop through all of our cards. So how do we do that? Well, this is a simple Django for loop. So for card in self. cards. And four, make that slightly larger. And let's change this to a Django template for better syntax highlighting.

13:19

Now what I'm gonna do here is I'm just going to quickly rough out some card stuff. Just some HTML. It has a little bit of CSS or basic classes in it. Nothing fancy. And when we refresh our page, there it is. We have three cards. Okay, so we probably also want to wrap that in a quick little container. So div class container. Grab all that stuff, out dent, close our div. Looking a little better. Also put our title inside of that container And let's throw this a text center

14:05

Hey, look at that. So we have staff cards and we have three cards because we told it to have three cards, but there's nothing in there yet. So let's go and customize some of this stuff. So we have our card loop in here. So what we're saying is for each card in self. cards or for each one of these inside of self. cards, that's this. We now have access to the image, title, text, button page, and button URL. So let's do the quick ones first, the quick wins Card dot title. Make that a little smaller. This one will be card. text And now we have two trickier parts in here. We now have an image. We need to get an image rendition.

14:51

So we need to load Wagtail Images Tags. And inside of our loop, let's create a new image. So we're going to do image card. image. So where I get that card. image from is card. image, this title. So if this was called Images with a bunch of Z. This would be images with a bunch of Z's. Phil, I don't know what I want this to be, so I'm just gonna honestly guess at like 300 by maybe 200 as img. And now we have access to img all over the place, so img. url. And in the alt we can do img. alt.

15:37

And let's see what this is looking like so far. Woohoo, look at that. Okay, so we now have people in here with Well their title I guess is their first name and the text is their last name, so that was bad content entry on my behalf. So shame Caleb. I know better. Now lastly we have to check the button page and the button URL. So because we have help text in here, it says if the button page above is selected, that will be used first. So what we want to say is if there is a button page. So let's add some template logic if card dot button page. If that's filled out, then we are going to use this. In fact, I'm going to copy this whole thing right now And we can do card.

16:22

buttonpage. url. And that's because Wagtail is going to return the entire page that we've selected. As in when we use the choose a page feature here, it's going to give us the entire page. So we got the URL, we got the title, we got all sorts of good stuff in there. The only thing we want in this particular instance is the URL. And in fact, let's just fill this with some default text for now. Uh read more. Or Learn more. Google's SEO machine is not going to like that, but this is not an SEO course. Let's do an LF in here and let's do card. buttonURL, and this is an external URL. And we're gonna do an end if

17:08

throw our link in there and da da da da da da da And so because this is a full URL, this is just a string, we can just throw that whole thing in there and we can say learn more. And just to demonstrate that this is actually working, external. internal and when I refresh that page we're going to see different links. Learn more external. You can see at that bottom left I hope hope you can see that. It says google. com. In fact, let's just open it to really show you. Goes to Google. This one is in an internal URL, so this one is supposed to go to homepage In fact, it did go to homepage. Perfect. And this one is an internal URL, and this is going to go to the about page.

17:54

And that's exactly where it brought us to. Now I'm just going to clean that up quickly. That's about it. I might do a little extra cleanup behind the scenes just to add some extra some extra spacing in our section here, some extra padding or margin between the title and the cards Nothing major, nothing you have to worry about there. You should also know that if there is no button page and if there is no button URL, that this button is not going to show up at all because we said If there's a button page, show it. If there's a button URL, show it. If there's nothing else, well there's nothing else to show. So in this lesson, you had learned about list blocks. And how you can nest a struct block inside of it. You've also learned about an image chooser block and a page chooser block. And this is the first time we also saw the URL block used inside of a Wagtail site as well, which is pretty familiar to Django's URL field.

18:44

And we used Bootstrap to make a couple of cards here. So these are a few cards and these are looking pretty good. And we also learned how to load up Wagtail Images tags and how to use an image tag inside of a loop to create a 300 by 200 image rendition, store that as an image variable, and then use that image variable. url and image. alt in the image tag itself And for the first time in this course, we've actually seen some proper template logic implemented. So we actually covered a lot of ground in this lesson. I hope you've learned a lot. If you have any questions, leave them down below. Don't forget, there are tons of more tutorials and videos on learnwagtail. com. The Wagtail docs are fantastic as well, docs.

19:31

wagtail. io. And don't forget, if this video was helpful to you, hit that subscribe button, uh, maybe a thumbs up, give it a share with your friends on Slack or in Messenger or WhatsApp or Whatever people are using these days, make sure you give that a share because that helps the Wagtail community grow. And as always, I'm Caleb Tollin and I will see you in the next lesson.

Questions this talk answers

How do I create repeating content inside a Wagtail StreamField?

Use a `ListBlock` containing a `StructBlock`. The struct block defines one item’s fields, and the list block lets editors repeat that item as many times as needed.

Discussed at 3:05

How do I render a Wagtail ListBlock in a template?

Create a template for the block and loop through the items with Django template syntax such as `{% for card in self.cards %}`. Each repeated item then exposes its fields, including the image, title, text, and button values.

Discussed at 12:33

How do I display an image from an ImageChooserBlock in a Wagtail template?

Load Wagtail’s image template tags, create a rendition with the image tag, and use the rendition’s URL and alt text in the HTML image element.

Discussed at 14:51

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