Headless Wagtail CMS: Serializing Child Pages (Serializing a QuerySet)

This video features Kalob Taulien at Wagtail CMS 2023 .

Headless Wagtail CMS: Serializing Child Pages (Serializing a QuerySet)
0:12:54
Published November 29, 2023
2,547 views

Tutorial: https://learnwagtail.com/tutorials/headless-cms-serializing-child-pages-and-querysets/
Wagtail for Beginners Course: https://learnwagtail.com/wagtail-for-beginners/

In this video we'll explore serializing a Wagtail Page QuerySet. Or in other words.. we'll add all the blog pages to the blog listing page API response. This can be very helpful for Listing pages in your Headless Wagtail website.

There are a several different ways to get a listing view of your blog detail pages, but in this tutorial we'll dig into creating a custom serializer to display child page data automatically.

For more free tutorials: https://learnwagtail.com/tutorials/
Don't forget to subscribe to this channel and follow me on Twitter at https://twitter.com/kalobtaulien
Git Commit: https://github.com/CodingForEverybody/learn-wagtail/commit/d7a14bd1a7456fe6049a3b4848a4bd752cd46b0e

#Wagtail #Django #Python

Summary

The speaker shows how to expose a Wagtail page’s child pages through the Wagtail v2 API. A custom API field calls a method that retrieves public, live children, while a Django REST framework `Field` serializer converts the resulting QuerySet into a JSON-safe list of dictionaries containing selected values such as ID, title, slug, and URL. They also demonstrate the shorter `values()` approach for database fields, explain why computed properties such as `url` require custom serialization, and show how to rename the API output with `source`.

Key takeaways

  • Enable the Wagtail v2 API before adding custom page fields.
  • Retrieve only public, live child pages with `get_children().public().live()`.
  • A QuerySet of Wagtail page objects is not JSON serializable, so a custom REST framework field can turn it into a list of dictionaries.
  • Use `values()` to return database fields directly when no computed properties are needed.
  • Computed attributes such as a page’s URL require custom serialization, and `source` can give the API field a different name from the underlying method.

Summarised automatically from the transcript.

Transcript

2,404 words · auto-generated Show

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

0:00

Hello, hello, hello, and welcome back to another lesson on Learn Wagtail. In this video, we are going to take a look at all of our blog pages and throwing them into an API response. So like Basically just getting a list of all of our pages in here. Now there's a few different ways we can do this, but the way we're going to do it is if we open up our API, and uh which one is our blog page here? Our blog listing page is page ID number four So if we were to look at page number four, maybe we want to see our blog pages in here. So our blog detail pages. Maybe it's just the ID, title, slug, and the page URL, but we actually want to show some of the child pages in here. Now before we get started, you are going to need to have the Wagtail V2 API installed in your

0:46

in your project Or enabled rather, since it always comes with Wagtail. You need it enabled in your project. If you don't already have that enabled, you're going to want to watch that video first and get it up and running. If you already have your API up and running, let's continue. Now how that's reflected in the Wagtail Admin is if I go to pages, I've got a home page here, I have a blog page here, and I have an article blog page and a second article blog page. Now currently neither of these show up in here. Now there are other methods of getting the child pages, but in this particular video we're going to create a list of items So it's gonna look sorta like this. And it's just going to be child pages. So that whenever there's an API response to page ID number four, well

1:33

not necessarily page ID number four, but whenever there's an API response to basically query for our blog page, we're also going to be able to get our child pages in here. So I just have the Learn Wagtail repo open and I'm going to open up the blogmodels. py and I want to modify the blog listing page. So not the blog detail page. I'm in my blog app models. py and this is the class that I'm currently on, but I want to go to the blog listing page. And do we have any API fields in here? Where are you? API fields, there you are. Nope, that's the blog detail page. We don't have any of them in there. Okay. So let's look again for that blog listing page and let's uh let's put some custom API fields in here.

2:18

So first things first we need to put an API field. API fields. is equal to and then just a list of fields. And it's always going to be an API field class. And then we just need to fill it with something. So what I'm going to do is I'm going to create a new property in here. Add property def get child pages, something like that. And in here all I'm going to return is self. getchildren . public because I only want to expose public pages. And . live because I only want to show live pages, not drafts. And then I can take this get child page

3:03

's name and just throw it in here. Now, this is basically what we did in our last video. Where we threw in a custom page property into the API. And while that works for basic data, it may not work the way that you're thinking. So this is actually going to be a little bit more advanced. Okay, so now if we go back into our API here, and this is our blog listing page, yeah, we're gonna see that object of page type is not JSON serializable. That basically means we have a query set a bunch of pages, uh, where it can't figure out how to serialize it. Now again in Django Rest framework, there's a few different ways of doing this. But we're going to use a more Wagtail way, and I think this is actually a pretty clean way of serializing our data.

3:48

And we're only going to return the data that we actually want. So we're going to need to use a custom serializer here. So the serializer is going to be something, I don't know, blog child pages serializer. I don't know. It's a terrible name. But again, it's verbose. So we are going to take this serializer and let's just go and create that. So class And this is going to be a rest framework field. And is this already in here? Rest framework. It is. We already have that in there. If you don't have that, you're going to need to from rest framework. fields import field. And so I just skipped back on down to the bottom. And then we need to do def to representation. It's going to take self and a value. Now this value that we're passing in here.

4:36

Is going to be whatever comes from here. Now, there's a little code traversing you have to do in your mind. And I'll just make that one step smaller here. So in our API field, we're saying, hey. You have this property or a function or whatever, and it's called getChildPages. Now that's cool, and that function is going to return this, but we're getting a JSON serializer error. With a custom serializer, whatever comes back from this function, which matches the name up here, is going to be thrown into this particular serializer right away. And that's our quote unquote value. So our value, for simplicity's sake, is going to be self dot get children. public. live. Whatever that's going to be, that's what the value is.

5:22

Now just as an example in here we can do return a thing. And let's make that one step bigger there. And let's go ahead and open this up. Now this is not going to err on us anymore because we've overwritten the serializer with our own custom serializer. And get child pages is just a string called a thing. So You know, it's working. It's not working the way we want, but it is working. So now in here, we have a few different things we can do. We can try just returning the value, but that's really just returning the entire query string. We can return a list, so one, two, three, but that's really no different than get child pages is equal to a hard-coded string. This is no different than get child pages as a hard is equal to a hard coded list.

6:08

Again, not very useful. Now just as an example to show you that this actually is not going to work, even if we return this value, which makes this whole serializer totally redundant, but if we did return that value, we're going to see this same issue. object of type pages, not JSON serializable. So what do we do? Well we can just pick and choose what we want to return. So there's a few different ways we can do this and I'm going to go through Sort of the longer way it's not super pythonic, but it makes a little more sense to people who are a little more new to Python, Django, and Wagtail So I'm going to create a variable called return posts, and it's just going to be an empty list. And at the end, I'm going to return the return posts. So this is always going to return a list, even if there's nothing in there.

6:55

Next I'm going to loop through every page. So instead of calling this value, I'm going to call this Child pages, because it just makes a little more sense of the context. And for child in child pages, what I can do is simply print child. title. Now this isn't going to do anything for our serializer. This is just going to return an empty list. But once this loads, I'll show you. So, empty list, but in here in our terminal it says article blog page one and two. So now we actually have some data that we can work with. So let's go ahead and create a dictionary that we're going to put into this list. So let's call this post dictionary, and this is just going to be whatever data we want to throw back into the API response.

7:42

So the child page dot ID, let's maybe give it a title as well or pass in the title. We're not giving it a title, we're just passing in the title. A slug, because a slug might be useful. Uh let's also give the direct URL, child. url, and pretty much anything else that comes with a Wagtail page we could throw in there. Now that's nice, but all we did was create a dictionary and we didn't actually do anything with this return posts list. So now we have to add this dictionary to the list. So return posts. append and we're just going to add post dictionary or the child page dictionary to the return post. Let's go ahead and save that. And refresh our page here.

8:28

And look at that. Our get child pages is a list with the two blog posts that we have, number five and number six. We can also, if we wanted to. Where are you? Uh we could do other logic in here, we could sort, we could order by, we could filter, we could do anything we want in there. Uh we could also, if we wanted to, do the whole thing in here because we've got child pages as a query set, so we have access to this entire Django query set. So we could perform the log it logic in here. We could do anything else we wanted to. Now this is one way of doing it. If we wanted to do the more Pythonic way, we could simply return a list with a dictionary in it. And that dictionary is going to basically just be this.

9:14

And we're going to say for child in child pages. So now. We're returning a list and for every child in child pages we're creating a dictionary. And this is the more pythonic way. Looks a little weirder, but it's the more pythonic way of writing all that. Let's go ahead and refresh and you can see absolutely nothing has changed, so we've just written it in a more pythonic way. Now what if we didn't want to do any of that? What if we just wanted the ID, the title, and let's log some basic data where there's no extra processing. We just want data from the database. What we could do is instead of passing in a custom serializer, so I'm just going to disable this real quick by commenting that out, we could, and I'm going to comment that one out too, we could just say values.

10:00

And just return these values. And it won't need a custom serializer because it's just returning database values. That's all it's doing. So let's return the page ID the Wagtail page title, and the Wagtail page slug. And so now we don't need a serializer because we're just using regular database values. And when I refresh the page, you can see that I've got ID, title, and slug. I don't have the URL because that requires a little extra processing, so it's not quite the same. And in fact, as a demonstration, if I pass in URL into values, because it's technically not a model value, it's going to complain that it doesn't exist. And sure enough, can't resolve keyword URL because URL is a function. It's not a model field.

10:46

So this is a good way to bypass using a serializer, if you don't really need to, why use one? And if you want to use a serializer, let's just return that and undo this. And you could do anything you want in here. So what I'm going to do just to keep this code a little more simple is I'm just going to comment the bottom return out and enable the top return. And this one's going to be called Pythonic Comprehensions So you can use either method that you like here. Okay, so I refresh the page, we have one more thing. Get child pages doesn't really sound great And depending on how you code, you might want something a little more verbose. So it may not always be possible to change the name of your function.

11:31

But what you can do is change the name that is returned in your API. So we could say this is called blog post or even easier, just posts And in here we can give this a source. The source is going to be getChild Pages. And we pass that source directly into our serializer. So let's refresh our API page and we can see we renamed it to posts, but the data didn't change. So there's that as well. So that's really all there is to adding a custom serializer forgetting. child pages. It doesn't necessarily have to be child pages either. It could literally be any other query set that you can think of. You could filter through all of your Wagtail pages if you wanted to. You can literally do any sort of logic that you want in this function, or in any function that's similar to this.

12:16

There's the. values method, where you don't need a serializer at all, and then there's the serializer method with a custom source that matches over here. And then we just pass in the child pages and we did a little extra logic in there just to create our own list of dictionaries with just the data that we want. And with that said, we are done. You can find the commit link down below to all this source code as well. My name is Caleb Tallin. Thank you for watching this video. If you liked it, don't forget you can subscribe, and if you want to, only if you want to, you can come follow me on Twitter at CalebTollen.

Questions this talk answers

How do I return only public, live child pages in a Wagtail API response?

Define a property such as `get_child_pages` that returns `self.get_children().public().live()`, then expose that property through an API field.

Discussed at 2:18

How do I serialize a Wagtail child-page QuerySet in the API?

Add a custom Wagtail REST framework field serializer to the API field. The serializer loops over the child pages and returns JSON-safe dictionaries instead of the page objects themselves.

Discussed at 3:48

What data can I include when serializing Wagtail child pages?

You can choose the fields to put in each returned dictionary, such as the page ID, title, slug, and URL. Other logic, including filtering, sorting, or ordering, can also be applied to the QuerySet before building the response.

Discussed at 7:42

Can I serialize Wagtail child pages without writing a custom serializer?

Yes. If you only need database fields, use the QuerySet’s `values()` method and return fields such as the page ID, title, and slug. Computed properties such as `url` are not model fields and require extra processing through a serializer.

Discussed at 10:00

How do I rename a custom API field in Wagtail while keeping the existing method name?

Give the API field the desired name and set its `source` to the existing method, such as using `posts` with `source='get_child_pages'`. The response data stays the same while the API field name changes.

Discussed at 11:31

Presenters

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 by Kalob Taulien

More videos from Wagtail CMS