Wagtail CMS: Adding Custom StreamField Logic

This video is from Wagtail CMS 2023 .

Wagtail CMS: Adding Custom StreamField Logic
0:14:42
Published November 29, 2023
6,855 views
154 likes

There will be times when you need to provide multiple optional fields in a StreamField, and naturally we lean towards using logic in the template. In this video we're going to learn how to pull out template logic and use Python instead. In this example we'll be using a StructValue and StructBlock to return a single URL in the template even though the StreamField has a PageChooserBlock and a URLBlock (pick one and return it).

Tutorial: https://learnwagtail.com/tutorials/adding-custom-streamfield-logic/

GitHub Commit: https://github.com/CodingForEverybody/learn-wagtail/commit/4218b53508dc2697dcf032c0f5c56dce38cb929a

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

The speaker shows how to move conditional link handling out of Wagtail templates and into Python. They build a `ButtonBlock` as a `StructBlock` with optional page and external-URL fields, then attach a custom `StructValue` through `value_class`; its `url` property returns the selected page’s URL first, the external URL second, or `None` if neither is set. The template can then use `self.url` without branching. They also introduce `get_context` as another way to add computed data to a block, illustrating it with a possible latest-posts query, while noting that this approach may not be stable and the example is incomplete.

Key takeaways

  • A custom `StructValue` can add computed properties and logic to a Wagtail `StructBlock`.
  • Set the block’s `Meta.value_class` to connect it to the custom struct value.
  • The example’s `url` property prioritizes a selected Wagtail page, then an external URL, and otherwise returns `None`.
  • Templates can use `self.url` directly instead of checking for page and URL fields themselves.
  • `get_context` can add computed context such as recent posts, but the speaker cautions that this pattern may not be future-proof.

Summarised automatically from the transcript.

Transcript

2,481 words · auto-generated Show

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

0:00

Hello, welcome to another lesson on Wow Learning Wagtail. In this lesson, we're going to go over some stream field logic. How do we apply some logic inside of our stream field so that we don't necessarily have to have logic inside of our templates? Now a good example of this is uh can I open up blocks. py and Actually, it's already on it. Is when we have a a page chooser block and we also have a URL block And then in our template, we are going to have something that's like if there is a button page or if there's a button URL. What if we didn't want to do that in the template? What if we simply just wanted to give a URL? We could limit the number of lines in our template.

0:46

We can simplify the front end a lot more by simply providing a URL. In this lesson, that's exactly what we're going to do. We're going to create a brand new stream field. It's going to be a button stream field, so it will just have one button on it. Maybe a title or something like that. But yeah, it's gonna have a Wagtail page to choose from, or it's going to have an external URL. And either way, we're going to return whichever one is output using Python logic. So let's dive into that. First things first, let's open up blocks. py and let's create a brand new stream field. So let's do class. button block and it's going to be a struct block. So struct block an external or internal

1:33

URL Now at this point if you're thinking, Caleb, why why would we ever run into this? How how is this even a thing Well that's a good question. Wagtill provides us with a page chooser block, as we can see up here on line 77, and that allows people who are less technically savvy to grab pages that are already inside of the CMS. They don't have to find URLs, they don't have to learn about slogs. It just makes their job a lot easier. This is great for account management people. This is great for clients. This is great for content entry people who just know where all the pages are. It's great for a lot of people, but sometimes you need to be able to link to an external page. And sometimes that page needs to be, for example, Google or Facebook or something. Because sometimes we just have a link that says

2:21

join us on Facebook or follow us on Twitter or something like that. And we need that external URL. So this is a very common situation. So what I'm going to do here is I'm actually going to copy and paste this button page and button URL. There's nothing special going on here. This is simply adding a page chooser block. This is not required. And a button URL. Also not required. I'm going to copy the metadata in here because I am being lazy and let's change this to button block and let's call this single button And I'm going to open up this page and simply resave this as buttonblock. html And uh you know what I do actually want some of that styling and that markup in here, so let's do this.

3:10

Text center. And what we're going to do is simply add a tag in here and it's going to be the URL. That's all it's going to be. And it's going to be learn more as a default text. So we're not good, we're not going to get overly complicated with how many fields are in the stream field. We want to focus on which one do we show first. So let's add some help text in here. Help text if selected this button And this URL will be used first. And for the button page, if added, this URL will be used. Secondarily

3:57

to the button page. Something like that. I'm not a copyright expert, so try not to judge me on that one. Now all I'm going to do is open up my flex models because I know this is already somewhat full So this is our flex page and I'm just going to add a button block in here and CTA is going to be button block And so if I open up Firefox and I just refresh our page here, it's gonna say that it's not running, obviously. Obviously. We run our server, refresh And we have a single button block that allows us to choose a page and choose a button URL.

4:45

Both of these are optional. But we want to specify if the button page is used, provide that as the URL. If the button URL is used, provide that as the button URL. So no more template logic. All we're going to do is provide one particular value to the template and use that. Okay, jumping back over to blocks. py. We want to create another class. And yes, this gets a little bit weird because we're not using getContext, although that is an option if you wanted it to be. Maybe that's something we go over a little bit later in this video. But we definitely want to create a new class here, and this class needs to be a struct value. So let's create a new class, make that larger And let's just call this link struct value. And let's inherit from

5:30

blocks. structValue. So it's not a struct block, it's a struct value. So it's a value, basically, for a struct block. And doc string additional logic for our URLs. Something like that. That's kind of vague. Not great, but not terrible. And then let's add a function in here. So defurl. And that's where we're going to get URL from. It's going to match this function name. We're going to pass in self. And right now this is still unconnected. So if you're wondering well where does this connect to our button block, I'll show you when we're done writing this out. Let's uh let's figure out the page. So we have a page and that page needs to be self.

6:16

get and that's going to be our button page. That's right here And then let's also have an external URL. So let's call that external URL. And da da da da da da. Nope, wrong one. And that one is equal to self dot get buttonURL. Now all we have to do is return these values. We can say If which one did we want to show first? Well, we wanted to show our button page, so maybe Let's name those so that they're the the same. So button page is equal to self. get button page and button URL is equal to self. get button URL. So if button page, so if there is a page selected, simply return.

7:04

Button page dot URL El if button URL. Return button URL because that's just a string. Otherwise, return none. In fact, we don't even need an else in there. We can just do return none. Now at this point our logic is it's implemented. It says if there's a button page, use it. If there's a button URL, use it. Which is exactly what we saw in the template. We're just doing this in Python land now. Now the last thing we need to do is we actually need to attach these together. And we do that in our button block by specifying a meta value. Called value class and we say that's equal to link struct value.

7:49

And I got that name from the class up here. So now I'm just gonna take a look at my console. Life looks good in there, there's no errors I'm going to refresh my about page, my flex page, and let's add a single button And this is going to go to https learnwagtail. com slash course. Publish the page, and then I'm going to V O Live. So this simply says learn more. And in fact, what I want to do is I want to make that way bigger because this is going to be hard to see. I apologize to people who are on very small screens. If you're on a phone, this is not the greatest experience. I know that, and I apologize just because we're working with something that is so small.

8:37

Uh so instead of URL, in fact this is going to be self. url because it's referencing itself, much like we would use self. buttonpage. And now when I refresh our page, it says learn more, but at the bottom left it says wagtail. com slash course. Now you can't actually see that, so let's go ahead and change this learn more in here to. self. url learnwagtail. com slash course. But what happens if I go back into the page and I actually select a page from Doo doo doo doo doo from Wagtail. So these are both going to be filled out and I want to go to blog. I'm going to publish that and I'm going to refresh our page. And it just goes straight to blog.

9:22

So that is how we add some additional logic to our stream fields. Now this is actually really, really useful if you want to simplify your template, which I would highly recommend doing. But there is one other way, which I mentioned a little bit earlier, and we can actually add context to a stream field. Now let us be known at this point in time I honestly don't know if this is the right way or not the right way, and I don't know if this is going to be supported in the future or not. I just know that this exists. So that is my little disclaimer here. So I'm going to add a function in here. Let me move that up and make it bigger. I'm going to add a function in here called. Get context. It's going to take self because it's an object, it takes request, args

10:09

and quargs are necessary, and just like grabbing context from a page, we do context is equal to Super dot get context pass in request args and quargs And return context. Now we can do anything else we want in here. Now where is this a good idea? Well this is This is also a good idea if, for example, we're not using a button block, but instead if we had a latest blog post, we could do something along the lines of context Let's call that latest posts is equal to blog detail page dot objects dot live

10:55

dot public and then we'll just get the latest three something like that we'll just grab three of them And that will be our latest posts. And then inside of our template, what we can do, and I'm not actually going to do this, but what we could do is for post in latest posts, and then we could say post. title. add a line break in there and four so we could do that we could also instead of having this in here We could also put it in here as deflatest posts And all it's going to do is return blog detail. That's far too down page. Blog detail page.

11:40

objects. live dot public Something like that, or all pages, even if it doesn't matter if it's public, and that's just going to return the latest three blog posts that are live. And then we could do the same thing, however, instead of using Latest posts, it is self. latest post because it is a struct value. That value has been added to our struct block. Now that's all there is really to adding additional logic to your stream field. In fact, I'm going to leave this in here. And you can do what you want with it. This code is actually going to break because this is not imported, and that's fine. I'm going to leave this in here too. But that's also going to break for you. So should you want to do that, you're going to have to do a little extra work.

12:25

However, the URL example is absolutely going to work for you here. Now again, that's all there is to adding additional logic or additional values to really any stream field. And the really nice thing about all of this is that now we can just say self. url instead of card blocks, which had If card. button page lf card. button URL, we just threw that in here. If button page lf button URL. That's all we did, so we just turned template logic into Python logic. And in my opinion, that's actually so much better because in a template, your HTML needs to be parsed.

13:10

So Python has to look at this. Django templates has to look at this. If you're using Jinja, it has to look at this and say, oh, okay, so this is logic. I understand that I now have to underst I now have to break it apart, I have to parse it, and I have to figure out what that logic is actually trying to do. Oh, and there's an L if? Okay, so I have to add a little additional logic. Whereas in here It doesn't have to do that. It doesn't have to do that parsing from HTML to Python. It just has straight Python. So you're skipping that step. So technically this is more efficient So in summary, in this lesson you learned what a struct value is. We learned how to attach it using a value class in a struct block. We also learned that we can use getContext And we created in our struct value a value called URL, which is accessible through self. url

13:56

And we added our logic in here based on Stream Field Fields. So that's all I have for you in this lesson. If you like this lesson, if you learned something valuable. Feel free to hit subscribe or like or thumbs up or you can share it in Slack channels or Facebook groups or really wherever. If you want more tutorials on Wagtail CMS, you can find them at learnwagtail. com And if you think I could have gone a little bit further into how all of this works and uh you want to dive in a little bit more, feel free to hit up the documentation. Docs. wagtail. io is a great place to start, and if that doesn't get you what you need, you can always hit up github. com slash wagtail slash wagtail and look at the source code, and that will give you everything you need.

Questions this talk answers

Why use a Wagtail page chooser and an external URL in the same button field?

A page chooser lets less technical editors link to pages already in the CMS, while an external URL is needed for destinations such as Facebook, Twitter, or Google. Supporting both covers common internal and external linking needs.

Discussed at 1:33

How can I make a Wagtail StreamField button return either an internal page URL or an external URL?

Create a `StructValue` with a URL method that returns the selected page’s URL first, then the external URL, and returns `None` if neither is present. Attach it to the `StructBlock` with its `value_class`, making the template use one value such as `self.url` instead of conditional logic.

Discussed at 5:30

What is a StructValue in Wagtail, and how do I connect it to a StructBlock?

A `StructValue` is the value object behind a `StructBlock`, where you can add methods and other custom logic. Define the value class and assign it in the block’s Meta class with `value_class = ...`.

Discussed at 5:30

Can I add extra context or computed values to a Wagtail StreamField?

Yes. A custom value can override `get_context`, call the parent implementation, and add values such as recent blog posts; it can also expose computed data through methods on the `StructValue`. The speaker notes that this approach may not be supported in the future, unlike the URL example.

Discussed at 9:22

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