move FieldBlock and its subclasses into wagtailcore.blocks.field

This commit is contained in:
Matt Westcott 2015-02-19 18:47:18 +00:00
parent bd1a6699b4
commit 8df5f8a200
2 changed files with 164 additions and 145 deletions

View file

@ -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 for={label_id}>{label}</label> """,
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('<div class="rich-text">' + expand_db_html(value) + '</div>')
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('<a href="{0}">{1}</a>', 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

View file

@ -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 for={label_id}>{label}</label> """,
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('<div class="rich-text">' + expand_db_html(value) + '</div>')
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('<a href="{0}">{1}</a>', 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]