3 min read
Recently I built a headless CMS using Wagtail's API as a backend with NextJS/React/Redux as a frontend. Building the API I ran into some small issues with Image URL data, the API representation of snippets and creating a fully customized page representation. I'll show some simple recipes which will hopefully simplify it for anyone encountering these issues.
I create a ImageChooserBlock with a custom API representation. You can use the function get_rendition()
to fetch all attributes as e.g the full URL to the image. It's possible to pass arguments suiting your needs as you can when using Wagtail images with Django templates, meaning you can pass max,min,original,fill
etc.
from wagtail.images.blocks import ImageChooserBlock as DefaultImageChooserBlock
class ImageChooserBlock(DefaultImageChooserBlock):
def get_api_representation(self, value, context=None):
if value:
return {
"id": value.id,
"title": value.title,
"original": value.get_rendition("original").attrs_dict,
"thumbnail": value.get_rendition("fill-120x120").attrs_dict,
}
Then in your StreamBlock
just reference the new ImageChooserBlock.
from .common_blocks import ImageChooserBlock
class MyBlock(StructBlock):
image = ImageChooserBlock()
I use a PageChooserPanel
in my header snippet to make it easy for the user to add links to other pages in the header.
@register_snippet
class Header(models.Model):
links = StreamField(
[("link", PageChooserBlock(page_type="api.PortfolioPage"))],
null=True,
blank=True,
)
Since the snippet extends the django.db model
I won't be able to customize the get_api_representation()
method so I use a custom SnippetChooserBlock with a custom api representation.
class HeaderChooserBlock(DefaultSnippetChooserBlock):
def get_api_representation(self, value, context=None):
if value:
links = []
for page in value.links:
links.append({"url": page.value.url, "title": page.value.title})
return {
"logo": value.logo.get_rendition("original").attrs_dict,
"links": links,
"header_links_color": value.header_links_color,
}
I loop through all the links in the StreamField
and then I can access all the Page
fields and add any fields to the API that I would like. Then you can reference your own HeaderChooserBlock
when you want a page to have an specific header and you can customize all the fields you'd like it to return from the chosen page from the PageChooserBlock
.
If we continue from the above example of a HeaderChooserBlock
, we will add a similar FooterChooserBlock
. We use different blocks due to the varying API representation (reach out to me if you have a less repetitive solution).
class FooterChooserBlock(DefaultSnippetChooserBlock):
def get_api_representation(self, value, context=None):
if value:
return {
"copyright": value.copyright,
"social_links": value.social_links.get_prep_value(),
}
Remember you can use get_prep_value()
to fetch all the fields for nested blocks within Streamfields
. Remember that you will not receive any URLs but an ID of the image or page(when using e.g PageChooserBlock
or ImageChooserBlock
) which you can then use to make a second API call. I do not prefer to make several API calls therefore I customize the exact values returned to include page/image URLs and not IDs.
class LayoutBlock(StreamBlock):
header = HeaderChooserBlock("api.Header")
footer = FooterChooserBlock("api.Footer")
class Meta:
icon = "snippet"
I wrap then both layout fields (header, footer) in a custom LayoutBlock
and then you can use it in any page you'd like.
snippets = StreamField(
[
(
"layout",
LayoutBlock(
block_counts={"header": {"max_num": 1}, "footer": {"max_num": 1}}
),
)
]
)
content_panels = Page.content_panels + [
StreamFieldPanel("snippets"),
...
]
I played around first with Wagtail GraphQL as a headless CMS with Gatsby as a frontend, but I did not enjoy the query language therefore I opted for NextJS+Wagtail's API. I found it great, I can customize all API calls however I want and more so it has great defaults for almost all blocks/fields so you do not have to tinker with the data returned.
If you have any questions or better solutions, feel free to get in touch. I had some issues as I did not find a large community behind the Wagtail API, thus answers to beginner questions were hard to find, hope this helps.
8 min read
When creating Django applications or using cookiecutters as Django Cookiecutter you will have by default a number of dependencies that will be needed to be created as a S3 bucket, a Postgres Database and a …
8 min read
Struggling to determine your product’s future path? A/B testing helps you decide if you should take the road less traveled by. To understand your users and their needs, start by creating two product variations and …
6 min read
As good as a framework that Django is the default method of sending an email when getting an error leave much to be desired. The database or cache acting flaky? Expect 1000s of emails depicting …