From 5936326726feb64245b1cba732783d03b60cb9bb Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Thu, 19 Feb 2015 19:18:22 +0000 Subject: [PATCH] move ListBlock into wagtailcore.blocks.list_block --- wagtail/wagtailcore/blocks/__init__.py | 145 +-------------------- wagtail/wagtailcore/blocks/list_block.py | 156 +++++++++++++++++++++++ 2 files changed, 157 insertions(+), 144 deletions(-) create mode 100644 wagtail/wagtailcore/blocks/list_block.py diff --git a/wagtail/wagtailcore/blocks/__init__.py b/wagtail/wagtailcore/blocks/__init__.py index 6df3b79fc..f58951961 100644 --- a/wagtail/wagtailcore/blocks/__init__.py +++ b/wagtail/wagtailcore/blocks/__init__.py @@ -18,8 +18,6 @@ from django.forms.utils import ErrorList import six -from wagtail.wagtailcore.utils import escape_script - from .utils import js_dict @@ -506,148 +504,6 @@ class StructBlock(six.with_metaclass(DeclarativeSubBlocksMetaclass, BaseStructBl pass -# ========= -# ListBlock -# ========= - -class ListBlock(Block): - class Meta: - # Default to a list consisting of one empty child item (using None to trigger the child's empty / default rendering) - default = [None] - - def __init__(self, child_block, **kwargs): - super(ListBlock, self).__init__(**kwargs) - - if isinstance(child_block, type): - # child_block was passed as a class, so convert it to a block instance - self.child_block = child_block() - else: - self.child_block = child_block - - self.dependencies = [self.child_block] - self.child_js_initializer = self.child_block.js_initializer() - - @property - def media(self): - return forms.Media(js=['wagtailadmin/js/blocks/sequence.js', 'wagtailadmin/js/blocks/list.js']) - - def render_list_member(self, value, prefix, index, errors=None): - """ - Render the HTML for a single list item in the form. This consists of an
  • wrapper, hidden fields - to manage ID/deleted state, delete/reorder buttons, and the child block's own form HTML. - """ - child = self.child_block.bind(value, prefix="%s-value" % prefix, errors=errors) - return render_to_string('wagtailadmin/block_forms/list_member.html', { - 'prefix': prefix, - 'child': child, - 'index': index, - }) - - def html_declarations(self): - # generate the HTML to be used when adding a new item to the list; - # this is the output of render_list_member as rendered with the prefix '__PREFIX__' - # (to be replaced dynamically when adding the new item) and the child block's default value - # as its value. - list_member_html = self.render_list_member(self.child_block.meta.default, '__PREFIX__', '') - - return format_html( - '', - self.definition_prefix, mark_safe(escape_script(list_member_html)) - ) - - def js_initializer(self): - opts = {'definitionPrefix': "'%s'" % self.definition_prefix} - - if self.child_js_initializer: - opts['childInitializer'] = self.child_js_initializer - - return "ListBlock(%s)" % js_dict(opts) - - def render_form(self, value, prefix='', errors=None): - if errors: - if len(errors) > 1: - # We rely on ListBlock.clean throwing a single ValidationError with a specially crafted - # 'params' attribute that we can pull apart and distribute to the child blocks - raise TypeError('ListBlock.render_form unexpectedly received multiple errors') - error_list = errors.as_data()[0].params - else: - error_list = None - - list_members_html = [ - self.render_list_member(child_val, "%s-%d" % (prefix, i), i, - errors=error_list[i] if error_list else None) - for (i, child_val) in enumerate(value) - ] - - return render_to_string('wagtailadmin/block_forms/list.html', { - 'label': self.label, - 'prefix': prefix, - 'list_members_html': list_members_html, - }) - - def value_from_datadict(self, data, files, prefix): - count = int(data['%s-count' % prefix]) - values_with_indexes = [] - for i in range(0, count): - if data['%s-%d-deleted' % (prefix, i)]: - continue - values_with_indexes.append( - ( - data['%s-%d-order' % (prefix, i)], - self.child_block.value_from_datadict(data, files, '%s-%d-value' % (prefix, i)) - ) - ) - - values_with_indexes.sort() - return [v for (i, v) in values_with_indexes] - - def clean(self, value): - result = [] - errors = [] - for child_val in value: - try: - result.append(self.child_block.clean(child_val)) - except ValidationError as e: - errors.append(ErrorList([e])) - else: - errors.append(None) - - if any(errors): - # The message here is arbitrary - outputting error messages is delegated to the child blocks, - # which only involves the 'params' list - raise ValidationError('Validation error in ListBlock', params=errors) - - return result - - def to_python(self, value): - # recursively call to_python on children and return as a list - return [ - self.child_block.to_python(item) - for item in value - ] - - def get_prep_value(self, value): - # recursively call get_prep_value on children and return as a list - return [ - self.child_block.get_prep_value(item) - for item in value - ] - - def render_basic(self, value): - children = format_html_join('\n', '
  • {0}
  • ', - [(self.child_block.render(child_value),) for child_value in value] - ) - return format_html("", children) - - def get_searchable_content(self, value): - content = [] - - for child_value in value: - content.extend(self.child_block.get_searchable_content(child_value)) - - return content - - # ======================== # django.forms integration # ======================== @@ -703,4 +559,5 @@ class BlockField(forms.Field): # Import block types defined in submodules into the wagtail.wagtailcore.blocks namespace from .field import * # NOQA +from .list_block import * # NOQA from .stream import * # NOQA diff --git a/wagtail/wagtailcore/blocks/list_block.py b/wagtail/wagtailcore/blocks/list_block.py new file mode 100644 index 000000000..57778e2f7 --- /dev/null +++ b/wagtail/wagtailcore/blocks/list_block.py @@ -0,0 +1,156 @@ +from __future__ import absolute_import, unicode_literals + +from django import forms +from django.core.exceptions import ValidationError +from django.forms.utils import ErrorList +from django.template.loader import render_to_string +from django.utils.html import format_html, format_html_join +from django.utils.safestring import mark_safe + +from wagtail.wagtailcore.blocks import Block +from wagtail.wagtailcore.utils import escape_script + +from .utils import js_dict + +__all__ = ['ListBlock'] + +class ListBlock(Block): + class Meta: + # Default to a list consisting of one empty child item (using None to trigger the child's empty / default rendering) + default = [None] + + def __init__(self, child_block, **kwargs): + super(ListBlock, self).__init__(**kwargs) + + if isinstance(child_block, type): + # child_block was passed as a class, so convert it to a block instance + self.child_block = child_block() + else: + self.child_block = child_block + + self.dependencies = [self.child_block] + self.child_js_initializer = self.child_block.js_initializer() + + @property + def media(self): + return forms.Media(js=['wagtailadmin/js/blocks/sequence.js', 'wagtailadmin/js/blocks/list.js']) + + def render_list_member(self, value, prefix, index, errors=None): + """ + Render the HTML for a single list item in the form. This consists of an
  • wrapper, hidden fields + to manage ID/deleted state, delete/reorder buttons, and the child block's own form HTML. + """ + child = self.child_block.bind(value, prefix="%s-value" % prefix, errors=errors) + return render_to_string('wagtailadmin/block_forms/list_member.html', { + 'prefix': prefix, + 'child': child, + 'index': index, + }) + + def html_declarations(self): + # generate the HTML to be used when adding a new item to the list; + # this is the output of render_list_member as rendered with the prefix '__PREFIX__' + # (to be replaced dynamically when adding the new item) and the child block's default value + # as its value. + list_member_html = self.render_list_member(self.child_block.meta.default, '__PREFIX__', '') + + return format_html( + '', + self.definition_prefix, mark_safe(escape_script(list_member_html)) + ) + + def js_initializer(self): + opts = {'definitionPrefix': "'%s'" % self.definition_prefix} + + if self.child_js_initializer: + opts['childInitializer'] = self.child_js_initializer + + return "ListBlock(%s)" % js_dict(opts) + + def render_form(self, value, prefix='', errors=None): + if errors: + if len(errors) > 1: + # We rely on ListBlock.clean throwing a single ValidationError with a specially crafted + # 'params' attribute that we can pull apart and distribute to the child blocks + raise TypeError('ListBlock.render_form unexpectedly received multiple errors') + error_list = errors.as_data()[0].params + else: + error_list = None + + list_members_html = [ + self.render_list_member(child_val, "%s-%d" % (prefix, i), i, + errors=error_list[i] if error_list else None) + for (i, child_val) in enumerate(value) + ] + + return render_to_string('wagtailadmin/block_forms/list.html', { + 'label': self.label, + 'prefix': prefix, + 'list_members_html': list_members_html, + }) + + def value_from_datadict(self, data, files, prefix): + count = int(data['%s-count' % prefix]) + values_with_indexes = [] + for i in range(0, count): + if data['%s-%d-deleted' % (prefix, i)]: + continue + values_with_indexes.append( + ( + data['%s-%d-order' % (prefix, i)], + self.child_block.value_from_datadict(data, files, '%s-%d-value' % (prefix, i)) + ) + ) + + values_with_indexes.sort() + return [v for (i, v) in values_with_indexes] + + def clean(self, value): + result = [] + errors = [] + for child_val in value: + try: + result.append(self.child_block.clean(child_val)) + except ValidationError as e: + errors.append(ErrorList([e])) + else: + errors.append(None) + + if any(errors): + # The message here is arbitrary - outputting error messages is delegated to the child blocks, + # which only involves the 'params' list + raise ValidationError('Validation error in ListBlock', params=errors) + + return result + + def to_python(self, value): + # recursively call to_python on children and return as a list + return [ + self.child_block.to_python(item) + for item in value + ] + + def get_prep_value(self, value): + # recursively call get_prep_value on children and return as a list + return [ + self.child_block.get_prep_value(item) + for item in value + ] + + def render_basic(self, value): + children = format_html_join('\n', '
  • {0}
  • ', + [(self.child_block.render(child_value),) for child_value in value] + ) + return format_html("", children) + + def get_searchable_content(self, value): + content = [] + + for child_value in value: + content.extend(self.child_block.get_searchable_content(child_value)) + + return content + +DECONSTRUCT_ALIASES = { + ListBlock: 'wagtail.wagtailcore.blocks.ListBlock', +}