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',
+}