Wagtail Unboxed episode 05 7:56
StreamField, properly
We replace the rich-text blob with real content blocks — and hit the migration error that stops most people on their first try.
What this episode covers
If you have ever seen CHECK constraint failed: JSON_VALID and had no idea why, that is in here, with the fix and the reason: why the migration fails on any page that already has content, and how to write a data migration that runs before the column changes type.
The blocks themselves: what StreamField actually is, a JSON column holding an ordered list of typed blocks; the four kinds of block and when to reach for each; StructBlock against ListBlock, which is record against repeat and the one people mix up; and Meta — icon, label and template, and what the editor sees.
Then the template side: the one-line change on the page template, how tiny a block template really is, and block_counts on a StreamBlock against max_num on a ListBlock.
Two gotchas, both hit while building the episode: the migration failure, and the reason a call to action rendered as a teal box inside a teal box. Also: stop passing use_json_field=True — Wagtail 7.4 accepts it and ignores it.
Chapters
- 0:00 One field, one blob
- 0:20 What StreamField actually is
- 0:38 The four kinds of block
- 1:01 StructBlock vs ListBlock
- 1:21 Writing a StructBlock
- 1:40 A struct containing a list
- 1:57 The top-level StreamBlock
- 2:14 The model change
- 2:27 use_json_field is dead
- 2:48 The migration blows up
- 3:01 What that error actually means
- 3:24 Nothing is broken
- 3:47 The fix: convert the data first
- 4:07 Writing the data migration
- 4:33 It applies
- 4:42 That unreadable block_lookup
- 5:04 One line in the page template
- 5:17 Inside a block template
- 5:33 A teal box inside a teal box
- 5:47 Wagtail already wrapped your block
- 6:06 Style the wrapper instead
- 6:19 What the client sees
- 6:41 Two ways to say “max”
- 7:01 The finished home page
- 7:17 The migrated About page
- 7:31 Commit and tag
- 7:34 Next episode
Versions on screen
Wagtail 7.4.3 · Django 6.1.1 · Python 3.12. Recorded on Windows; macOS and Linux commands are in the repository for every episode.