Skip to content

Instantly share code, notes, and snippets.

@zerolab
Last active September 7, 2025 21:13
Show Gist options
  • Save zerolab/cbd19becd21a5ab12a711674d5979157 to your computer and use it in GitHub Desktop.
Save zerolab/cbd19becd21a5ab12a711674d5979157 to your computer and use it in GitHub Desktop.
Wagtail: Ignore StreamFields in migrations

Changes to Wagtail StreamField definitions will generate new migrations. This is in line with Django's philosophy and helps with data migrations. For complex definitions, the generated migrations were quite big. Wagtail 6.2 improved the generated migrations, making them much smaller, but they are still required. Reference: wagtail/wagtail#4298

However, for cases where data migrations are not necessary and are in active development, the need for the Django migration files become an unnecessary burden. To disable that you can use a modified version of StreamField as seen in fields.py.

Dev notes:

  • Add fields.py to your app.
  • Replace usages of from wagtail.fields import StreamField with from your_app.fields import StreamField

Credit: Andy Babic

import json
from wagtail.fields import StreamField as WagtailStreamfield
class StreamField(WagtailStreamfield):
def __init__(self, *args, **kwargs):
"""
Overrides StreamField.__init__() to account for `block_types` no longer
being received as an arg when migrating (because there is no longer a
`block_types` value in the migration to provide).
Usage:
import this StreamField instead of `from wagtail.fields import StreamField` for usage in your models
"""
if args:
block_types = args[0] or []
args = args[1:]
else:
block_types = kwargs.pop("block_types", [])
super().__init__(block_types, *args, **kwargs)
def deconstruct(self):
"""
Overrides StreamField.deconstruct() to remove `block_types` and
`verbose_name` values so that migrations remain smaller in size,
and changes to those attributes do not require a new migration.
"""
name, path, args, kwargs = super().deconstruct()
if args:
args = args[1:]
else:
kwargs.pop("block_types", None)
kwargs.pop("verbose_name", None)
return name, path, args, kwargs
def to_python(self, value):
"""
Overrides StreamField.to_python() to make the return value
(a `StreamValue`) more useful when migrating. When migrating, block
definitions are unavailable to the field's underlying StreamBlock,
causing self.stream_block.to_python() to not recognise any of the
blocks in the stored value.
"""
stream_value = super().to_python(value)
# There is no way to be absolutely sure this is a migration,
# but the combination of factors below is a pretty decent indicator
if not self.stream_block.child_blocks and value and not stream_value._raw_data:
stream_data = None
if isinstance(value, list):
stream_data = value
elif isinstance(value, str):
try:
stream_data = json.loads(value)
except ValueError:
stream_value.raw_text = value
if stream_data:
return type(stream_value)(self, stream_data, is_lazy=True)
return stream_value
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment