Appending Wagtail StructBlock to Streamfield
is there a good way to append a StructBlock into an existing StreamField? I'm building a links page where non admin users can add their own links. I would like to be able to append a LinkBlock using form data. I'm attaching what I have right now and currently stepping through the Wagtail source code to better understand how blocks are created.
class LinkBlock(blocks.StructBlock):
title = blocks.CharBlock(required=True, help_text="Link title")
url = blocks.URLBlock(
required=True,
help_text="Link URL",
)
class LinksPage(Page):
links = StreamField(
[
("link", LinkBlock()),
],
blank=True,
use_json_field=True,
max_length=20
)
content_panels = Page.content_panels + [
FieldPanel("links"),
]
# Adding dynamic links with form data
links_page.links.append(
(
"link",
{
"title": form.cleaned_data["title"],
"url": form.cleaned_data["url"],
"link_type": form.cleaned_data["link_type"],
"color_scheme": form.cleaned_data["color_scheme"],
},
)
)
try:
# TODO: Investigate why this is necessary
# links_page.links._raw_data = [lnk for lnk in links_page.links._raw_data if lnk is not None]
# without the filter above, we get a TypeError when trying to save the page
links_page.save()
except TypeError as e:
logger.error(f"Error saving link: {e}")
raise e
"""
Example stack trace:
File "../.venv/lib/python3.13/site-packages/wagtail/blocks/stream_block.py", line 680, in __getitem__
self._prefetch_blocks(raw_value["type"])
~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
File "../.venv/lib/python3.13/site-packages/wagtail/blocks/stream_block.py", line 710, in _prefetch_blocks
raw_values = OrderedDict(
File "../.venv/lib/python3.13/site-packages/wagtail/blocks/stream_block.py", line 713, in <genexpr>
if raw_item["type"] == type_name and self._bound_blocks[i] is None
~~~~~~~~^^^^^^^^
TypeError: 'NoneType' object is not subscriptable
"""
After messing around, it looks like this method works to append a StructBlock:
links = [(lnk.block_type, lnk.value) for lnk in links_page.links]
links.append(
(
"link",
{
"title": form.cleaned_data["title"],
"url": form.cleaned_data["url"],
"link_type": form.cleaned_data["link_type"],
"color_scheme": form.cleaned_data["color_scheme"],
},
)
)
links_page.links = links
However, the Wagtail docs seem to indicate that a simple append
with the (block type, data) tuple should work. Would be grateful for some insight.
The TypeError
you encountered suggests that after appending, some blocks may have ended up as None
or improperly formatted. This could happen if the data isn't passed correctly into the block.
instead of manually filtering _raw_data
or reassigning links_page.links
directly with a list of tuples, you should leverage the StreamField's
method for updating its data. Try to go from this approach
# firstly prepare the form data for the new link
link_data = {
"title": form.cleaned_data["title"],
"url": form.cleaned_data["url"],
"link_type": form.cleaned_data["link_type"],
"color_scheme": form.cleaned_data["color_scheme"],
}
# now append the new link as a tuple (type, data)
links_page.links.append(
("link", link_data)
)
# save the page
try:
links_page.save()
except TypeError as e:
logger.error(f"Error saving link: {e}")
raise e
if the error persists, consider checking the StreamField's
internal representation by inspecting links_page.links
before and after appending, to debug any issues with the data format.