mirror of
https://github.com/wagtail/wagtail.git
synced 2024-12-01 11:41:20 +01:00
Provide a raw_data accessor on StreamValue to allow accessing and modifying the raw JSON representation
This matches the old behaviour of stream_data for lazy StreamValues. Given that a lazy StreamValue is what you get as standard when retrieving a model instance from the database (or from a PageRevision), any existing user code that casually tinkered with stream_data unaware of the lazy versus non-lazy gotcha is almost certainly expecting it to behave this way - therefore in 99% of cases those users should be able to replace `stream_data` with `raw_data` and have their code work as before, with the added bonus that it won't fall over on page previews (which get their data from a form submission in Python format rather than JSON, and are thus NOT lazy).
This commit is contained in:
parent
aab619550f
commit
37d4e841d7
@ -7,6 +7,7 @@ from django import forms
|
|||||||
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
|
from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
|
||||||
from django.forms.utils import ErrorList
|
from django.forms.utils import ErrorList
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
|
from django.utils.functional import cached_property
|
||||||
from django.utils.html import format_html_join
|
from django.utils.html import format_html_join
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
@ -413,6 +414,49 @@ class StreamValue(MutableSequence):
|
|||||||
"""
|
"""
|
||||||
return self.block.name
|
return self.block.name
|
||||||
|
|
||||||
|
def get_prep_value(self):
|
||||||
|
return {
|
||||||
|
'type': self.block_type,
|
||||||
|
'value': self.block.get_prep_value(self.value),
|
||||||
|
'id': self.id,
|
||||||
|
}
|
||||||
|
|
||||||
|
class RawDataView(MutableSequence):
|
||||||
|
"""
|
||||||
|
Internal helper class to present the stream data in raw JSONish format. For backwards
|
||||||
|
compatibility with old code that manipulated StreamValue.stream_data, this is considered
|
||||||
|
mutable to some extent, with the proviso that once the BoundBlock representation has been
|
||||||
|
accessed, any changes to fields within raw data will not propagate back to the BoundBlock
|
||||||
|
and will not be saved back when calling get_prep_value.
|
||||||
|
"""
|
||||||
|
def __init__(self, stream_value):
|
||||||
|
self.stream_value = stream_value
|
||||||
|
|
||||||
|
def __getitem__(self, i):
|
||||||
|
item = self.stream_value._raw_data[i]
|
||||||
|
if item is None:
|
||||||
|
# reconstruct raw data from the bound block
|
||||||
|
item = self.stream_value._bound_blocks[i].get_prep_value()
|
||||||
|
self.stream_value._raw_data[i] = item
|
||||||
|
|
||||||
|
return item
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.stream_value._raw_data)
|
||||||
|
|
||||||
|
def __setitem__(self, i, item):
|
||||||
|
self.stream_value._raw_data[i] = item
|
||||||
|
# clear the cached bound_block for this item
|
||||||
|
self.stream_value._bound_blocks[i] = None
|
||||||
|
|
||||||
|
def __delitem__(self, i):
|
||||||
|
# same as deletion on the stream itself - delete both the raw and bound_block data
|
||||||
|
del self.stream_value[i]
|
||||||
|
|
||||||
|
def insert(self, i, item):
|
||||||
|
self.stream_value._raw_data.insert(i, item)
|
||||||
|
self.stream_value._bound_blocks.insert(i, None)
|
||||||
|
|
||||||
def __init__(self, stream_block, stream_data, is_lazy=False, raw_text=None):
|
def __init__(self, stream_block, stream_data, is_lazy=False, raw_text=None):
|
||||||
"""
|
"""
|
||||||
Construct a StreamValue linked to the given StreamBlock,
|
Construct a StreamValue linked to the given StreamBlock,
|
||||||
@ -485,6 +529,10 @@ class StreamValue(MutableSequence):
|
|||||||
self._bound_blocks.insert(i, self._construct_stream_child(item))
|
self._bound_blocks.insert(i, self._construct_stream_child(item))
|
||||||
self._raw_data.insert(i, None)
|
self._raw_data.insert(i, None)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def raw_data(self):
|
||||||
|
return StreamValue.RawDataView(self)
|
||||||
|
|
||||||
def _prefetch_blocks(self, type_name):
|
def _prefetch_blocks(self, type_name):
|
||||||
"""
|
"""
|
||||||
Populate _bound_blocks with all items in this stream of type `type_name` that exist in
|
Populate _bound_blocks with all items in this stream of type `type_name` that exist in
|
||||||
@ -519,11 +567,7 @@ class StreamValue(MutableSequence):
|
|||||||
if not item.id:
|
if not item.id:
|
||||||
item.id = str(uuid.uuid4())
|
item.id = str(uuid.uuid4())
|
||||||
|
|
||||||
prep_value.append({
|
prep_value.append(item.get_prep_value())
|
||||||
'type': item.block_type,
|
|
||||||
'value': item.block.get_prep_value(item.value),
|
|
||||||
'id': item.id,
|
|
||||||
})
|
|
||||||
else:
|
else:
|
||||||
# item has not been converted to a BoundBlock, so its _raw_data entry is
|
# item has not been converted to a BoundBlock, so its _raw_data entry is
|
||||||
# still usable (but ensure it has an ID before returning it)
|
# still usable (but ensure it has an ID before returning it)
|
||||||
|
@ -3436,6 +3436,57 @@ class TestStreamBlock(WagtailTestUtils, SimpleTestCase):
|
|||||||
{'type': 'paragraph', 'value': 'of warcraft', 'id': '0003'},
|
{'type': 'paragraph', 'value': 'of warcraft', 'id': '0003'},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
def test_streamvalue_raw_data(self):
|
||||||
|
class ArticleBlock(blocks.StreamBlock):
|
||||||
|
heading = blocks.CharBlock()
|
||||||
|
paragraph = blocks.CharBlock()
|
||||||
|
|
||||||
|
block = ArticleBlock()
|
||||||
|
stream = block.to_python([
|
||||||
|
{'type': 'heading', 'value': 'hello', 'id': '0001'},
|
||||||
|
{'type': 'paragraph', 'value': 'world', 'id': '0002'},
|
||||||
|
])
|
||||||
|
|
||||||
|
self.assertEqual(stream.raw_data[0], {'type': 'heading', 'value': 'hello', 'id': '0001'})
|
||||||
|
stream.raw_data[0]['value'] = 'bonjour'
|
||||||
|
self.assertEqual(stream.raw_data[0], {'type': 'heading', 'value': 'bonjour', 'id': '0001'})
|
||||||
|
|
||||||
|
# changes to raw_data will be written back via get_prep_value...
|
||||||
|
raw_data = block.get_prep_value(stream)
|
||||||
|
self.assertEqual(raw_data, [
|
||||||
|
{'type': 'heading', 'value': 'bonjour', 'id': '0001'},
|
||||||
|
{'type': 'paragraph', 'value': 'world', 'id': '0002'},
|
||||||
|
])
|
||||||
|
|
||||||
|
# ...but once the bound-block representation has been accessed, that takes precedence
|
||||||
|
self.assertEqual(stream[0].value, 'bonjour')
|
||||||
|
stream.raw_data[0]['value'] = 'guten tag'
|
||||||
|
self.assertEqual(stream.raw_data[0]['value'], 'guten tag')
|
||||||
|
self.assertEqual(stream[0].value, 'bonjour')
|
||||||
|
raw_data = block.get_prep_value(stream)
|
||||||
|
self.assertEqual(raw_data, [
|
||||||
|
{'type': 'heading', 'value': 'bonjour', 'id': '0001'},
|
||||||
|
{'type': 'paragraph', 'value': 'world', 'id': '0002'},
|
||||||
|
])
|
||||||
|
|
||||||
|
# Replacing a raw_data entry outright will propagate to the bound block, though
|
||||||
|
stream.raw_data[0] = {'type': 'heading', 'value': 'konnichiwa', 'id': '0003'}
|
||||||
|
raw_data = block.get_prep_value(stream)
|
||||||
|
self.assertEqual(raw_data, [
|
||||||
|
{'type': 'heading', 'value': 'konnichiwa', 'id': '0003'},
|
||||||
|
{'type': 'paragraph', 'value': 'world', 'id': '0002'},
|
||||||
|
])
|
||||||
|
self.assertEqual(stream[0].value, 'konnichiwa')
|
||||||
|
|
||||||
|
# deletions / insertions on raw_data will also propagate to the bound block representation
|
||||||
|
del stream.raw_data[1]
|
||||||
|
stream.raw_data.insert(0, {'type': 'paragraph', 'value': 'hello kitty says', 'id': '0004'})
|
||||||
|
raw_data = block.get_prep_value(stream)
|
||||||
|
self.assertEqual(raw_data, [
|
||||||
|
{'type': 'paragraph', 'value': 'hello kitty says', 'id': '0004'},
|
||||||
|
{'type': 'heading', 'value': 'konnichiwa', 'id': '0003'},
|
||||||
|
])
|
||||||
|
|
||||||
def test_render_with_classname_via_kwarg(self):
|
def test_render_with_classname_via_kwarg(self):
|
||||||
"""form_classname from kwargs to be used as an additional class when rendering stream block"""
|
"""form_classname from kwargs to be used as an additional class when rendering stream block"""
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user