Refactor StreamValue so that it behaves as a sequence of BoundBlocks rather than a sequence of values

This commit is contained in:
Matt Westcott 2015-02-05 12:08:33 +00:00
parent 2a485e499e
commit 94a248055b

View file

@ -660,9 +660,9 @@ class BaseStreamBlock(Block):
def render_form(self, value, prefix='', error=None):
list_members_html = [
self.render_list_member(block_type, child_value, "%s-%d" % (prefix, i), i,
self.render_list_member(child.block.name, child.value, "%s-%d" % (prefix, i), i,
error=error.params[i] if error else None)
for (i, (child_value, block_type)) in enumerate(value.values_with_types)
for (i, child) in enumerate(value)
]
return render_to_string('wagtailadmin/block_forms/stream.html', {
@ -685,22 +685,24 @@ class BaseStreamBlock(Block):
values_with_indexes.append(
(
data['%s-%d-order' % (prefix, i)],
child_block.value_from_datadict(data, files, '%s-%d-value' % (prefix, i)),
block_type_name,
child_block.value_from_datadict(data, files, '%s-%d-value' % (prefix, i)),
)
)
values_with_indexes.sort()
return StreamValue(self, [(val, typ) for (index, val, typ) in values_with_indexes])
return StreamValue(self, [
(block_type_name, value)
for (index, block_type_name, value) in values_with_indexes
])
def clean(self, value):
result = []
cleaned_data = []
errors = []
for child_type, child_val in value.values_with_types:
child_block = self.child_blocks[child_type]
for child in value: # child is a BoundBlock instance
try:
result.append(
(child_block.clean(child_val), child_type)
cleaned_data.append(
(child.block.clean(child.value), child.block.name)
)
except ValidationError as e:
errors.append(e)
@ -712,14 +714,14 @@ class BaseStreamBlock(Block):
# which only involves the 'params' list
raise ValidationError('Validation error in StreamBlock', params=errors)
return StreamValue(self, result)
return StreamValue(self, cleaned_data)
def to_python(self, value):
# the incoming JSONish representation is a list of dicts, each with a 'type' and 'value' field.
# Convert this to a StreamValue backed by a list of (value, type) tuples
# Convert this to a StreamValue backed by a list of (type, value) tuples
return StreamValue(self, [
(self.child_blocks[item['type']].to_python(item['value']), item['type'])
for item in value
(child_data['type'], self.child_blocks[child_data['type']].to_python(child_data['value']))
for child_data in value
])
def get_prep_value(self, value):
@ -727,13 +729,13 @@ class BaseStreamBlock(Block):
return None
return [
{'type': bound_block.block.name, 'value': bound_block.block.get_prep_value(bound_block.value)}
for bound_block in value.bound_blocks
{'type': child.block.name, 'value': child.block.get_prep_value(child.value)}
for child in value # child is a BoundBlock instance
]
def render(self, value):
return format_html_join('\n', '<div class="block-{1}">{0}</div>',
[(bound_block.render(), bound_block.block.name) for bound_block in value.bound_blocks]
[(child.render(), child.block.name) for child in value]
)
@ -744,29 +746,27 @@ class StreamBlock(six.with_metaclass(DeclarativeSubBlocksMetaclass, BaseStreamBl
@python_2_unicode_compatible # provide equivalent __unicode__ and __str__ methods on Py2
class StreamValue(collections.Sequence):
"""
Custom type used to represent the value of a StreamBlock; behaves as a sequence of block values
so that we can naturally iterate over it in template code, but also allows retrieval of
(value, type) tuples as required for the form (and other complex rendering logic) to work.
Custom type used to represent the value of a StreamBlock; behaves as a sequence of BoundBlocks
(which keep track of block types in a way that the values alone wouldn't).
"""
def __init__(self, stream_block, values_with_types):
self.stream_block = stream_block
self.values_with_types = values_with_types
def __init__(self, stream_block, stream_data):
self.stream_block = stream_block # the StreamBlock object that handles this value
self.stream_data = stream_data # a list of (type_name, value) tuples
self._bound_blocks = {} # populated lazily from stream_data as we access items through __getitem__
def __getitem__(self, i):
return self.values_with_types[i][0]
if i not in self._bound_blocks:
type_name, value = self.stream_data[i]
child_block = self.stream_block.child_blocks[type_name]
self._bound_blocks[i] = child_block.bind(value)
return self._bound_blocks[i]
def __len__(self):
return len(self.values_with_types)
return len(self.stream_data)
def __repr__(self):
return repr(list(self))
def __str__(self):
return self.stream_block.render(self)
@cached_property
def bound_blocks(self):
return [
BoundBlock(self.stream_block.child_blocks[block_type], value)
for value, block_type in self.values_with_types
]