Wagtail StreamField erweitern: eigene Blocks nachrüsten
Die meisten Wagtail-Erweiterungen, die ich für Kunden baue, sind neue StreamField-Blöcke — ein Call-to-Action, eine Bildergalerie, ein Preistabellen-Block. Hier die Muster, die sich bewährt haben.
Warum eigene Blocks?
Redakteure sollen Inhalte zusammenstellen können, ohne HTML anzufassen — und ohne Dinge kaputt machen zu können. Ein gut gebauter Block ist selbsterklärend, validiert Eingaben und rendert konsistent.
StructBlock: der Arbeitspferd-Baustein
from wagtail import blocks
from wagtail.images.blocks import ImageChooserBlock
class CallToActionBlock(blocks.StructBlock):
heading = blocks.CharBlock(required=True, max_length=80, label="Überschrift")
text = blocks.TextBlock(required=False, label="Text")
button_text = blocks.CharBlock(required=True, max_length=40)
button_url = blocks.URLBlock(required=True)
image = ImageChooserBlock(required=False)
class Meta:
icon = "pick"
label = "Call to Action"
template = "blocks/cta_block.html"
help_text = "Ein Hinweisblock mit Button."Diesen Block in ein bestehendes StreamField aufzunehmen ist unkompliziert — danach eine Migration erzeugen:
class BlogPage(Page):
body = StreamField([
("paragraph", blocks.RichTextBlock()),
("cta", CallToActionBlock()), # neuer Block
], use_json_field=True)
# python manage.py makemigrations && migrateBlock-Templates & Kontext
{# blocks/cta_block.html #}
{% load wagtailcore_tags wagtailimages_tags %}
{% if value.image %}
{% image value.image width-400 %}
{% endif %}
{{ value.heading }}
{% if value.text %}{{ value.text }}
{% endif %}
{{ value.button_text }}
Brauchst du zusätzlichen Kontext im Template, überschreibe get_context:
class LatestPostsBlock(blocks.StructBlock):
count = blocks.IntegerBlock(default=3, min_value=1, max_value=10)
def get_context(self, value, parent_context=None):
ctx = super().get_context(value, parent_context)
from blog.models import BlogPage
ctx["posts"] = BlogPage.objects.live().order_by("-first_published_at")[:value["count"]]
return ctx
class Meta:
template = "blocks/latest_posts.html"Validierung eigener Blocks
Blöcke können ihre Eingaben validieren — wichtig, damit Redakteure sinnvolle Fehlermeldungen bekommen statt kaputter Seiten:
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
class CTABlock(blocks.StructBlock):
button_text = blocks.CharBlock(required=False)
button_url = blocks.URLBlock(required=False)
def clean(self, value):
result = super().clean(value)
# Button-Text und URL nur gemeinsam sinnvoll
if result["button_text"] and not result["button_url"]:
raise ValidationError(_("Button-Text ohne URL angegeben."))
return resultWiederverwendbare Block-Bibliothek
In größeren Projekten lohnt sich eine zentrale blocks.py, aus der alle Seitentypen schöpfen. So bleiben Blöcke konsistent und Wartung zentral:
# common/blocks.py
class BodyStreamBlock(blocks.StreamBlock):
paragraph = blocks.RichTextBlock()
heading = blocks.CharBlock(form_classname="title")
cta = CallToActionBlock()
image = ImageChooserBlock()
class Meta:
block_counts = {"cta": {"max_num": 3}} # Grenzen setzen
# In den Seiten:
body = StreamField(BodyStreamBlock(), use_json_field=True)Fazit
Gute StreamField-Blöcke sind das Herz einer wartbaren Wagtail-Site: StructBlock für Struktur, saubere Templates, Validierung für Redakteurssicherheit und eine zentrale Bibliothek für Konsistenz. Genau hier entscheidet sich, ob Redakteure gern mit der Site arbeiten.
