-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Description
Is your proposal related to a problem?
When writing to a StreamField block, e.g. page.body = [('heading', "Hello world!")]
, or setting a default
value, we expect the block value to be passed in the block's native data type. This data type is often undocumented and non-obvious:
- for RichTextBlock, it's a
wagtail.rich_text.RichText
object, not a string - for EmbedBlock, it's an EmbedValue object
- for StreamBlock, it's a
StreamValue
instance. (The list-of-tuples representation is only recognised byStreamField.to_python
, not StreamBlock, which means it's only valid for the top level of the stream, not nested StreamBlocks.) - For ListBlock, it used to be a Python list, but is now a
ListValue
instance as of Wagtail 4.1.
StreamValue (and possibly ListValue too, now) is especially troublesome, as the constructor needs to be passed a reference to the StreamBlock, which is hard to get hold of, and impossible in the case of setting a default
(because it would have to be a reference to the StreamBlock that you're still in the middle of defining).
In these situations, we should follow Postel's Law and accept "close enough" data types whenever a block value is being supplied from user code: a string for RichTextBlock, a list-of-tuples for StreamBlock and so on.
Describe the solution you'd like
The Block
class should implement a normalize(value)
method that accepts an input value in a range of allowable types, and returns that value in the block's native type. This differs from existing methods such as to_python
in that it's not aiming to convert data between representations - just standardising data that "should" be in the native type. We would then run any user-supplied data through normalize
before using it in the block's internal logic.
The default implementation in Block
would simply return value
unchanged, while blocks that act as containers for child blocks will recursively call normalize
on the child values along with doing whatever conversion they require for themselves.
Additional context
If we were to move the "sniffing various types to decide what to do" logic from StreamField.to_python to StreamBlock.normalize, I believe this would remove the remaining blocker to supporting other kinds of block besides StreamBlock as the top-level block of a StreamField, providing a solution to #2048.