mirror of
https://github.com/Hopiu/wagtail.git
synced 2026-05-12 01:03:11 +00:00
move ListBlock into wagtailcore.blocks.list_block
This commit is contained in:
parent
ad76273b54
commit
5936326726
2 changed files with 157 additions and 144 deletions
|
|
@ -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 <li> 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(
|
||||
'<script type="text/template" id="{0}-newmember">{1}</script>',
|
||||
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', '<li>{0}</li>',
|
||||
[(self.child_block.render(child_value),) for child_value in value]
|
||||
)
|
||||
return format_html("<ul>{0}</ul>", 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
|
||||
|
|
|
|||
156
wagtail/wagtailcore/blocks/list_block.py
Normal file
156
wagtail/wagtailcore/blocks/list_block.py
Normal file
|
|
@ -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 <li> 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(
|
||||
'<script type="text/template" id="{0}-newmember">{1}</script>',
|
||||
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', '<li>{0}</li>',
|
||||
[(self.child_block.render(child_value),) for child_value in value]
|
||||
)
|
||||
return format_html("<ul>{0}</ul>", 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',
|
||||
}
|
||||
Loading…
Reference in a new issue