Skip to content

Instantly share code, notes, and snippets.



Created Apr 26, 2018
What would you like to do?
Splitting multi-paragraph StreamField blocks into individual blocks
from __future__ import absolute_import, unicode_literals
from bs4 import BeautifulSoup, Tag
from django.db import models
from wagtail.wagtailadmin.edit_handlers import StreamFieldPanel
from wagtail.wagtailcore import blocks
from wagtail.wagtailcore.fields import StreamField
from wagtail.wagtailcore.models import Page
from wagtail.wagtailcore.rich_text import RichText
class HomePage(Page):
body = StreamField([
('heading', blocks.CharBlock()),
('paragraph', blocks.RichTextBlock()),
('multi_paragraph_import', blocks.RichTextBlock()),
content_panels = Page.content_panels + [
def clean(self):
is_replacing_body = False # becomes true if there are multi_paragraph_import blocks to rewrite
new_body = [] # a list of (block_type, value, id) tuples
for block in self.body:
if block.block_type == 'multi_paragraph_import':
is_replacing_body = True
soup = BeautifulSoup(block.value.source, 'html5lib')
# create a paragraph block for each top-level element in the rich text
for element in soup.body.children:
# ignore whitespace between elements
if isinstance(element, str) and not element.strip():
# ignore empty paragraphs
if isinstance(element, Tag) and == 'p' and not element.text:
('paragraph', RichText(str(element)), None)
# keep all other block types intact
new_body.append((block.block_type, block.value,
if is_replacing_body:
# need to construct a StreamValue to write back to body; writing the list back
# will fail because StreamField only recognises (block_type, value) tuples,
# not (block_type, value, id)
body_block = self._meta.get_field('body').stream_block
self.body = blocks.StreamValue(body_block, new_body)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment