move StructBlock into wagtailcore.blocks.struct_block

This commit is contained in:
Matt Westcott 2015-02-19 19:29:00 +00:00
parent 162f430b35
commit 76656e4735
2 changed files with 168 additions and 156 deletions

View file

@ -6,20 +6,15 @@ from __future__ import absolute_import, unicode_literals
import collections
from importlib import import_module
from django.core.exceptions import ValidationError, ImproperlyConfigured
from django.utils.html import format_html, format_html_join
from django.core.exceptions import ImproperlyConfigured
from django.utils.safestring import mark_safe
from django.utils.text import capfirst
from django.utils.encoding import force_text, python_2_unicode_compatible
from django.utils.functional import cached_property
from django.utils.encoding import force_text
from django.template.loader import render_to_string
from django import forms
from django.forms.utils import ErrorList
import six
from .utils import js_dict
# =========================================
# Top-level superclasses and helper objects
@ -318,152 +313,6 @@ class BoundBlock(object):
return self.block.render(self.value)
# ===========
# StructBlock
# ===========
class BaseStructBlock(Block):
class Meta:
default = {}
template = "wagtailadmin/blocks/struct.html"
def __init__(self, local_blocks=None, **kwargs):
self._constructor_kwargs = kwargs
super(BaseStructBlock, self).__init__(**kwargs)
self.child_blocks = self.base_blocks.copy() # create a local (shallow) copy of base_blocks so that it can be supplemented by local_blocks
if local_blocks:
for name, block in local_blocks:
block.set_name(name)
self.child_blocks[name] = block
self.child_js_initializers = {}
for name, block in self.child_blocks.items():
js_initializer = block.js_initializer()
if js_initializer is not None:
self.child_js_initializers[name] = js_initializer
self.dependencies = self.child_blocks.values()
def js_initializer(self):
# skip JS setup entirely if no children have js_initializers
if not self.child_js_initializers:
return None
return "StructBlock(%s)" % js_dict(self.child_js_initializers)
@property
def media(self):
return forms.Media(js=['wagtailadmin/js/blocks/struct.js'])
def render_form(self, value, prefix='', errors=None):
if errors:
if len(errors) > 1:
# We rely on StructBlock.clean throwing a single ValidationError with a specially crafted
# 'params' attribute that we can pull apart and distribute to the child blocks
raise TypeError('StructBlock.render_form unexpectedly received multiple errors')
error_dict = errors.as_data()[0].params
else:
error_dict = {}
child_renderings = [
block.render_form(value.get(name, block.meta.default), prefix="%s-%s" % (prefix, name),
errors=error_dict.get(name))
for name, block in self.child_blocks.items()
]
list_items = format_html_join('\n', "<li>{0}</li>", [
[child_rendering]
for child_rendering in child_renderings
])
# Can these be rendered with a template?
if self.label:
return format_html('<div class="struct-block"><label>{0}</label> <ul>{1}</ul></div>', self.label, list_items)
else:
return format_html('<div class="struct-block"><ul>{0}</ul></div>', list_items)
def value_from_datadict(self, data, files, prefix):
return dict([
(name, block.value_from_datadict(data, files, '%s-%s' % (prefix, name)))
for name, block in self.child_blocks.items()
])
def clean(self, value):
result = {}
errors = {}
for name, val in value.items():
try:
result[name] = self.child_blocks[name].clean(val)
except ValidationError as e:
errors[name] = ErrorList([e])
if errors:
# The message here is arbitrary - StructBlock.render_form will suppress it
# and delegate the errors contained in the 'params' dict to the child blocks instead
raise ValidationError('Validation error in StructBlock', params=errors)
return result
def to_python(self, value):
# recursively call to_python on children and return as a StructValue
return StructValue(self, [
(
name,
child_block.to_python(value.get(name, child_block.meta.default))
)
for name, child_block in self.child_blocks.items()
])
def get_prep_value(self, value):
# recursively call get_prep_value on children and return as a plain dict
return dict([
(name, self.child_blocks[name].get_prep_value(val))
for name, val in value.items()
])
def get_searchable_content(self, value):
content = []
for name, block in self.child_blocks.items():
content.extend(block.get_searchable_content(value.get(name, block.meta.default)))
return content
def deconstruct(self):
"""
Always deconstruct StructBlock instances as if they were plain StructBlocks with all of the
field definitions passed to the constructor - even if in reality this is a subclass of StructBlock
with the fields defined declaratively, or some combination of the two.
This ensures that the field definitions get frozen into migrations, rather than leaving a reference
to a custom subclass in the user's models.py that may or may not stick around.
"""
path = 'wagtail.wagtailcore.blocks.StructBlock'
args = [self.child_blocks.items()]
kwargs = self._constructor_kwargs
return (path, args, kwargs)
@python_2_unicode_compatible # provide equivalent __unicode__ and __str__ methods on Py2
class StructValue(collections.OrderedDict):
def __init__(self, block, *args):
super(StructValue, self).__init__(*args)
self.block = block
def __str__(self):
return self.block.render(self)
@cached_property
def bound_blocks(self):
return collections.OrderedDict([
(name, block.bind(self.get(name)))
for name, block in self.block.child_blocks.items()
])
class DeclarativeSubBlocksMetaclass(BaseBlock):
"""
Metaclass that collects sub-blocks declared on the base classes.
@ -500,9 +349,6 @@ class DeclarativeSubBlocksMetaclass(BaseBlock):
return new_class
class StructBlock(six.with_metaclass(DeclarativeSubBlocksMetaclass, BaseStructBlock)):
pass
# ========================
# django.forms integration
@ -559,5 +405,6 @@ class BlockField(forms.Field):
# Import block types defined in submodules into the wagtail.wagtailcore.blocks namespace
from .field_block import * # NOQA
from .struct_block import * # NOQA
from .list_block import * # NOQA
from .stream_block import * # NOQA

View file

@ -0,0 +1,165 @@
from __future__ import absolute_import, unicode_literals
import collections
from django import forms
from django.core.exceptions import ValidationError
from django.forms.utils import ErrorList
from django.utils.encoding import python_2_unicode_compatible
from django.utils.functional import cached_property
from django.utils.html import format_html, format_html_join
import six
from wagtail.wagtailcore.blocks import Block, DeclarativeSubBlocksMetaclass
from .utils import js_dict
__all__ = ['BaseStructBlock', 'StructBlock', 'StructValue']
class BaseStructBlock(Block):
class Meta:
default = {}
template = "wagtailadmin/blocks/struct.html"
def __init__(self, local_blocks=None, **kwargs):
self._constructor_kwargs = kwargs
super(BaseStructBlock, self).__init__(**kwargs)
self.child_blocks = self.base_blocks.copy() # create a local (shallow) copy of base_blocks so that it can be supplemented by local_blocks
if local_blocks:
for name, block in local_blocks:
block.set_name(name)
self.child_blocks[name] = block
self.child_js_initializers = {}
for name, block in self.child_blocks.items():
js_initializer = block.js_initializer()
if js_initializer is not None:
self.child_js_initializers[name] = js_initializer
self.dependencies = self.child_blocks.values()
def js_initializer(self):
# skip JS setup entirely if no children have js_initializers
if not self.child_js_initializers:
return None
return "StructBlock(%s)" % js_dict(self.child_js_initializers)
@property
def media(self):
return forms.Media(js=['wagtailadmin/js/blocks/struct.js'])
def render_form(self, value, prefix='', errors=None):
if errors:
if len(errors) > 1:
# We rely on StructBlock.clean throwing a single ValidationError with a specially crafted
# 'params' attribute that we can pull apart and distribute to the child blocks
raise TypeError('StructBlock.render_form unexpectedly received multiple errors')
error_dict = errors.as_data()[0].params
else:
error_dict = {}
child_renderings = [
block.render_form(value.get(name, block.meta.default), prefix="%s-%s" % (prefix, name),
errors=error_dict.get(name))
for name, block in self.child_blocks.items()
]
list_items = format_html_join('\n', "<li>{0}</li>", [
[child_rendering]
for child_rendering in child_renderings
])
# Can these be rendered with a template?
if self.label:
return format_html('<div class="struct-block"><label>{0}</label> <ul>{1}</ul></div>', self.label, list_items)
else:
return format_html('<div class="struct-block"><ul>{0}</ul></div>', list_items)
def value_from_datadict(self, data, files, prefix):
return dict([
(name, block.value_from_datadict(data, files, '%s-%s' % (prefix, name)))
for name, block in self.child_blocks.items()
])
def clean(self, value):
result = {}
errors = {}
for name, val in value.items():
try:
result[name] = self.child_blocks[name].clean(val)
except ValidationError as e:
errors[name] = ErrorList([e])
if errors:
# The message here is arbitrary - StructBlock.render_form will suppress it
# and delegate the errors contained in the 'params' dict to the child blocks instead
raise ValidationError('Validation error in StructBlock', params=errors)
return result
def to_python(self, value):
# recursively call to_python on children and return as a StructValue
return StructValue(self, [
(
name,
child_block.to_python(value.get(name, child_block.meta.default))
)
for name, child_block in self.child_blocks.items()
])
def get_prep_value(self, value):
# recursively call get_prep_value on children and return as a plain dict
return dict([
(name, self.child_blocks[name].get_prep_value(val))
for name, val in value.items()
])
def get_searchable_content(self, value):
content = []
for name, block in self.child_blocks.items():
content.extend(block.get_searchable_content(value.get(name, block.meta.default)))
return content
def deconstruct(self):
"""
Always deconstruct StructBlock instances as if they were plain StructBlocks with all of the
field definitions passed to the constructor - even if in reality this is a subclass of StructBlock
with the fields defined declaratively, or some combination of the two.
This ensures that the field definitions get frozen into migrations, rather than leaving a reference
to a custom subclass in the user's models.py that may or may not stick around.
"""
path = 'wagtail.wagtailcore.blocks.StructBlock'
args = [self.child_blocks.items()]
kwargs = self._constructor_kwargs
return (path, args, kwargs)
class StructBlock(six.with_metaclass(DeclarativeSubBlocksMetaclass, BaseStructBlock)):
pass
@python_2_unicode_compatible # provide equivalent __unicode__ and __str__ methods on Py2
class StructValue(collections.OrderedDict):
def __init__(self, block, *args):
super(StructValue, self).__init__(*args)
self.block = block
def __str__(self):
return self.block.render(self)
@cached_property
def bound_blocks(self):
return collections.OrderedDict([
(name, block.bind(self.get(name)))
for name, block in self.block.child_blocks.items()
])