diff --git a/wagtail/wagtailcore/blocks/__init__.py b/wagtail/wagtailcore/blocks/__init__.py index 302d8583d..4b4cb288d 100644 --- a/wagtail/wagtailcore/blocks/__init__.py +++ b/wagtail/wagtailcore/blocks/__init__.py @@ -1,5 +1,5 @@ from __future__ import absolute_import, unicode_literals -# this ensures that any render / __str__ methods returning HTML via calls to mark_safe / format_html +# unicode_literals ensures that any render / __str__ methods returning HTML via calls to mark_safe / format_html # return a SafeText, not SafeBytes; necessary so that it doesn't get re-encoded when the template engine # calls force_text, which would cause it to lose its 'safe' flag @@ -19,7 +19,6 @@ from django.forms.utils import ErrorList import six from wagtail.wagtailcore.utils import escape_script -from wagtail.wagtailcore.rich_text import expand_db_html from .utils import indent, js_dict @@ -321,149 +320,6 @@ class BoundBlock(object): return self.block.render(self.value) -# =========== -# Field block -# =========== - -class FieldBlock(Block): - class Meta: - default = None - - def render_form(self, value, prefix='', errors=None): - widget = self.field.widget - - if self.label: - label_html = format_html( - """ """, - label_id=widget.id_for_label(prefix), label=self.label - ) - else: - label_html = '' - - widget_attrs = {'id': prefix, 'placeholder': self.label} - - if hasattr(widget, 'render_with_errors'): - widget_html = widget.render_with_errors(prefix, value, attrs=widget_attrs, errors=errors) - widget_has_rendered_errors = True - else: - widget_html = widget.render(prefix, value, attrs=widget_attrs) - widget_has_rendered_errors = False - - return render_to_string('wagtailadmin/block_forms/field.html', { - 'name': self.name, - 'label': self.label, - 'classes': self.meta.classname, - 'widget': widget_html, - 'label_tag': label_html, - 'field': self.field, - 'errors': errors if (not widget_has_rendered_errors) else None - }) - - def value_from_datadict(self, data, files, prefix): - return self.to_python(self.field.widget.value_from_datadict(data, files, prefix)) - - def clean(self, value): - return self.field.clean(value) - - -class CharBlock(FieldBlock): - def __init__(self, required=True, help_text=None, max_length=None, min_length=None, **kwargs): - # CharField's 'label' and 'initial' parameters are not exposed, as Block handles that functionality natively (via 'label' and 'default') - self.field = forms.CharField(required=required, help_text=help_text, max_length=max_length, min_length=min_length) - super(CharBlock, self).__init__(**kwargs) - - def get_searchable_content(self, value): - return [force_text(value)] - - -class URLBlock(FieldBlock): - def __init__(self, required=True, help_text=None, max_length=None, min_length=None, **kwargs): - self.field = forms.URLField(required=required, help_text=help_text, max_length=max_length, min_length=min_length) - super(URLBlock, self).__init__(**kwargs) - - -class RichTextBlock(FieldBlock): - @cached_property - def field(self): - from wagtail.wagtailcore.fields import RichTextArea - return forms.CharField(widget=RichTextArea) - - def render_basic(self, value): - return mark_safe('
' + expand_db_html(value) + '
') - - def get_searchable_content(self, value): - return [force_text(value)] - - -class RawHTMLBlock(FieldBlock): - def __init__(self, required=True, help_text=None, max_length=None, min_length=None, **kwargs): - self.field = forms.CharField( - required=required, help_text=help_text, max_length=max_length, min_length=min_length, - widget = forms.Textarea) - super(RawHTMLBlock, self).__init__(**kwargs) - - def render_basic(self, value): - return mark_safe(value) # if it isn't safe, that's the site admin's problem for allowing raw HTML blocks in the first place... - - class Meta: - icon = 'code' - - -class ChooserBlock(FieldBlock): - def __init__(self, required=True, **kwargs): - self.required=required - super(ChooserBlock, self).__init__(**kwargs) - - """Abstract superclass for fields that implement a chooser interface (page, image, snippet etc)""" - @cached_property - def field(self): - return forms.ModelChoiceField(queryset=self.target_model.objects.all(), widget=self.widget, required=self.required) - - def to_python(self, value): - if value is None or isinstance(value, self.target_model): - return value - else: - try: - return self.target_model.objects.get(pk=value) - except self.target_model.DoesNotExist: - return None - - def get_prep_value(self, value): - if isinstance(value, self.target_model): - return value.id - else: - return value - - def clean(self, value): - # ChooserBlock works natively with model instances as its 'value' type (because that's what you - # want to work with when doing front-end templating), but ModelChoiceField.clean expects an ID - # as the input value (and returns a model instance as the result). We don't want to bypass - # ModelChoiceField.clean entirely (it might be doing relevant validation, such as checking page - # type) so we convert our instance back to an ID here. It means we have a wasted round-trip to - # the database when ModelChoiceField.clean promptly does its own lookup, but there's no easy way - # around that... - if isinstance(value, self.target_model): - value = value.pk - return super(ChooserBlock, self).clean(value) - -class PageChooserBlock(ChooserBlock): - @cached_property - def target_model(self): - from wagtail.wagtailcore.models import Page # TODO: allow limiting to specific page types - return Page - - @cached_property - def widget(self): - from wagtail.wagtailadmin.widgets import AdminPageChooser - return AdminPageChooser - - def render_basic(self, value): - if value: - return format_html('{1}', value.url, value.title) - else: - return '' - - # =========== # StructBlock # =========== @@ -1094,3 +950,7 @@ class BlockField(forms.Field): def clean(self, value): return self.block.clean(value) + + +# Import block types defined in wagtail.wagtailcore.blocks.field into the wagtail.wagtailcore.blocks namespace +from .field import * # NOQA diff --git a/wagtail/wagtailcore/blocks/field.py b/wagtail/wagtailcore/blocks/field.py new file mode 100644 index 000000000..2cf01eea0 --- /dev/null +++ b/wagtail/wagtailcore/blocks/field.py @@ -0,0 +1,159 @@ +from __future__ import absolute_import, unicode_literals + +from django import forms +from django.template.loader import render_to_string +from django.utils.encoding import force_text +from django.utils.functional import cached_property +from django.utils.html import format_html +from django.utils.safestring import mark_safe + +from wagtail.wagtailcore.blocks import Block +from wagtail.wagtailcore.rich_text import expand_db_html + +class FieldBlock(Block): + class Meta: + default = None + + def render_form(self, value, prefix='', errors=None): + widget = self.field.widget + + if self.label: + label_html = format_html( + """ """, + label_id=widget.id_for_label(prefix), label=self.label + ) + else: + label_html = '' + + widget_attrs = {'id': prefix, 'placeholder': self.label} + + if hasattr(widget, 'render_with_errors'): + widget_html = widget.render_with_errors(prefix, value, attrs=widget_attrs, errors=errors) + widget_has_rendered_errors = True + else: + widget_html = widget.render(prefix, value, attrs=widget_attrs) + widget_has_rendered_errors = False + + return render_to_string('wagtailadmin/block_forms/field.html', { + 'name': self.name, + 'label': self.label, + 'classes': self.meta.classname, + 'widget': widget_html, + 'label_tag': label_html, + 'field': self.field, + 'errors': errors if (not widget_has_rendered_errors) else None + }) + + def value_from_datadict(self, data, files, prefix): + return self.to_python(self.field.widget.value_from_datadict(data, files, prefix)) + + def clean(self, value): + return self.field.clean(value) + + +class CharBlock(FieldBlock): + def __init__(self, required=True, help_text=None, max_length=None, min_length=None, **kwargs): + # CharField's 'label' and 'initial' parameters are not exposed, as Block handles that functionality natively (via 'label' and 'default') + self.field = forms.CharField(required=required, help_text=help_text, max_length=max_length, min_length=min_length) + super(CharBlock, self).__init__(**kwargs) + + def get_searchable_content(self, value): + return [force_text(value)] + + +class URLBlock(FieldBlock): + def __init__(self, required=True, help_text=None, max_length=None, min_length=None, **kwargs): + self.field = forms.URLField(required=required, help_text=help_text, max_length=max_length, min_length=min_length) + super(URLBlock, self).__init__(**kwargs) + + +class RichTextBlock(FieldBlock): + @cached_property + def field(self): + from wagtail.wagtailcore.fields import RichTextArea + return forms.CharField(widget=RichTextArea) + + def render_basic(self, value): + return mark_safe('
' + expand_db_html(value) + '
') + + def get_searchable_content(self, value): + return [force_text(value)] + + +class RawHTMLBlock(FieldBlock): + def __init__(self, required=True, help_text=None, max_length=None, min_length=None, **kwargs): + self.field = forms.CharField( + required=required, help_text=help_text, max_length=max_length, min_length=min_length, + widget = forms.Textarea) + super(RawHTMLBlock, self).__init__(**kwargs) + + def render_basic(self, value): + return mark_safe(value) # if it isn't safe, that's the site admin's problem for allowing raw HTML blocks in the first place... + + class Meta: + icon = 'code' + + +class ChooserBlock(FieldBlock): + def __init__(self, required=True, **kwargs): + self.required=required + super(ChooserBlock, self).__init__(**kwargs) + + """Abstract superclass for fields that implement a chooser interface (page, image, snippet etc)""" + @cached_property + def field(self): + return forms.ModelChoiceField(queryset=self.target_model.objects.all(), widget=self.widget, required=self.required) + + def to_python(self, value): + if value is None or isinstance(value, self.target_model): + return value + else: + try: + return self.target_model.objects.get(pk=value) + except self.target_model.DoesNotExist: + return None + + def get_prep_value(self, value): + if isinstance(value, self.target_model): + return value.id + else: + return value + + def clean(self, value): + # ChooserBlock works natively with model instances as its 'value' type (because that's what you + # want to work with when doing front-end templating), but ModelChoiceField.clean expects an ID + # as the input value (and returns a model instance as the result). We don't want to bypass + # ModelChoiceField.clean entirely (it might be doing relevant validation, such as checking page + # type) so we convert our instance back to an ID here. It means we have a wasted round-trip to + # the database when ModelChoiceField.clean promptly does its own lookup, but there's no easy way + # around that... + if isinstance(value, self.target_model): + value = value.pk + return super(ChooserBlock, self).clean(value) + +class PageChooserBlock(ChooserBlock): + @cached_property + def target_model(self): + from wagtail.wagtailcore.models import Page # TODO: allow limiting to specific page types + return Page + + @cached_property + def widget(self): + from wagtail.wagtailadmin.widgets import AdminPageChooser + return AdminPageChooser + + def render_basic(self, value): + if value: + return format_html('{1}', value.url, value.title) + else: + return '' + + +# Ensure that the blocks defined here get deconstructed as wagtailcore.blocks.FooBlock +# rather than wagtailcore.blocks.field.FooBlock +block_classes = [FieldBlock, CharBlock, URLBlock, RichTextBlock, RawHTMLBlock, ChooserBlock, PageChooserBlock] +DECONSTRUCT_ALIASES = { + cls: 'wagtail.wagtailcore.blocks.%s' % cls.__name__ + for cls in block_classes +} +__all__ = [cls.__name__ for cls in block_classes]