Learn Wagtail CMS Course

Headless CMS: Exposing Orderable Data and StreamFields

In previous tutorials we've learned how to enable the Wagtail v2 API so we can create a headless CMS, we learned how to expose our custom model fields, and we learned how to fetch specific fields from the API using only our URL bar.

But we didn't learn how to expose StreamFields or custom model fields from inside an Orderable.

In this video we'll learn how to do both of these.

StreamFields in your API

Enabling StreamField API output is super simple. It's a subject I forgot to cover in an early lesson, but it's so simple we can cover this at any point in time because it's the exact same as exposing a custom model field, which we have explored in depth already.

from wagtail.api import APIField
from wagtail.core.fields import StreamField
from wagtail.core.models import Page

from streams import blocks


class YourWagtailPage(Page):

    # These are your StreamFields. Adjust this as field as needed.
    content = StreamField(
        [
            ("title_and_text", blocks.TitleAndTextBlock()),
            ("full_richtext", blocks.RichtextBlock()),
            ("simple_richtext", blocks.SimpleRichtextBlock()),
            ("cards", blocks.CardBlock()),
            ("cta", blocks.CTABlock()),
        ],
        null=True,
        blank=True,
    )
    # Exposing your StreamField()
    api_fields = [
        APIField("content"),
    ]

Orderables in your API

Adding an Orderable to your API output is a bit more work, but honestly it's not very much work either. The main thing to note is that you need to add api_fields to your main Page model, and also your Orderable.

By adding api_fields to your Page model, you're telling the API to link to the Orderable. But by default the Orderable does not have custom fields exposed, so we need to expose them by adding api_fields to the Orderable. A blog-based example can be found below (or view the gist for a quick overview)

from django.db import models 

from modelcluster.fields import ParentalKey

from wagtail.api import APIField
from wagtail.admin.edit_handlers import FieldPanel, StreamFieldPanel
from wagtail.core.fields import StreamField
from wagtail.core.models import Page, Orderable
from wagtail.snippets.edit_handlers import SnippetChooserPanel
from wagtail.snippets.models import register_snippet

from streams import blocks


class BlogAuthorsOrderable(Orderable):
    """This allows us to select one or more blog authors from Snippets."""

    page = ParentalKey("blog.BlogDetailPage", related_name="blog_authors")
    author = models.ForeignKey(
        "blog.BlogAuthor",
        on_delete=models.CASCADE,
    )

    panels = [
        SnippetChooserPanel("author"),
    ]

    @property
    # Traverse through the `author` ForeignKey to return the `name`
    def author_name(self):
        return self.author.name

    @property
    # Traverse through the `author` ForeignKey to return the `website`
    def author_website(self):
        return self.author.website

    # Expose author_name and author_website fields
    # These are now properties in our Orderable. 
    api_fields = [
        APIField("author_name"),
        APIField("author_website"),
    ]


@register_snippet
class BlogAuthor(models.Model):
    """Blog author snippets."""

    name = models.CharField(max_length=100)
    website = models.URLField(blank=True, null=True)

    panels = [
        FieldPanel("name"),
        FieldPanel("website"),
    ]

    def __str__(self):
        """String repr of this class."""
        return self.name

    class Meta:  # noqa
        verbose_name = "Blog Author"
        verbose_name_plural = "Blog Authors"


class BlogDetailPage(Page):
    """Blog detail page."""

    # StreamFields. Adjust this field as needed.
    content = StreamField(
        [
            ("title_and_text", blocks.TitleAndTextBlock()),
            ("full_richtext", blocks.RichtextBlock()),
            ("simple_richtext", blocks.SimpleRichtextBlock()),
            ("cards", blocks.CardBlock()),
            ("cta", blocks.CTABlock()),
        ],
        null=True,
        blank=True,
    )

    content_panels = Page.content_panels + [
        StreamFieldPanel("content"),
    ]

    api_fields = [
        # Exposed the related_name from the BlogAuthorsOrderable
        APIField("blog_authors"),
        # Exposed StreamFields
        APIField("content"),
    ]

The Git Commit