Merge branch 'feature/streamfield' of github.com:torchbox/wagtail into feature/streamfield

This commit is contained in:
Matt Westcott 2015-02-10 15:52:38 +00:00
commit 3129d4e029
15 changed files with 475 additions and 124 deletions

View file

@ -45,10 +45,29 @@ def js_dict(d):
# Top-level superclasses and helper objects
# =========================================
class BaseBlock(type):
def __new__(mcs, name, bases, attrs):
meta_class = attrs.pop('Meta', None)
cls = super(BaseBlock, mcs).__new__(mcs, name, bases, attrs)
base_meta_class = getattr(cls, '_meta_class', None)
bases = tuple(cls for cls in [meta_class, base_meta_class] if cls) or ()
cls._meta_class = type(str(name + 'Meta'), bases + (object, ), {})
return cls
@deconstructible
class Block(object):
class Block(six.with_metaclass(BaseBlock, object)):
name = ''
creation_counter = 0
icon = "streamfield-block-placeholder"
class Meta:
label = None
icon = "streamfield-block-placeholder"
classname = None
"""
Setting a 'dependencies' list serves as a shortcut for the common case where a complex block type
@ -81,11 +100,10 @@ class Block(object):
return mark_safe('\n'.join(declarations))
def __init__(self, **kwargs):
if 'default' in kwargs:
self.default = kwargs['default'] # if not specified, leave as the class-level default
if 'icon' in kwargs:
self.icon = kwargs['icon'] # if not specified, leave as the class-level default
self.label = kwargs.get('label', None)
self.meta = self._meta_class()
for attr, value in kwargs.items():
setattr(self.meta, attr, value)
# Increase the creation counter, and save our local copy.
self.creation_counter = Block.creation_counter
@ -95,9 +113,9 @@ class Block(object):
def set_name(self, name):
self.name = name
# if we don't have a label already, generate one from name
if self.label is None:
self.label = capfirst(name.replace('_', ' '))
@property
def label(self):
return self.meta.label or self.name
@property
def media(self):
@ -156,7 +174,7 @@ class Block(object):
(new list items, for example). This will have a prefix of '__PREFIX__' (to be dynamically replaced with
a real prefix when it's inserted into the page) and a value equal to the block's default value.
"""
return self.bind(self.default, '__PREFIX__')
return self.bind(self.meta.default, '__PREFIX__')
def clean(self, value):
"""
@ -194,7 +212,7 @@ class Block(object):
use a template if a 'template' property is specified on the block, and fall back on render_basic
otherwise.
"""
template = getattr(self, 'template', None)
template = getattr(self.meta, 'template', None)
if template:
return render_to_string(template, {'self': value})
else:
@ -227,7 +245,8 @@ class BoundBlock(object):
# ==========
class TextInputBlock(Block):
default = ''
class Meta:
default = ''
def render_form(self, value, prefix='', error=None):
if self.label:
@ -255,7 +274,8 @@ class TextInputBlock(Block):
# would affect anything you're doing in migrations)
class FieldBlock(Block):
default = None
class Meta:
default = None
def __init__(self, field=None, **kwargs):
super(FieldBlock, self).__init__(**kwargs)
@ -273,8 +293,6 @@ class FieldBlock(Block):
def render_form(self, value, prefix='', error=None):
widget = self.field.widget
widget_html = widget.render(prefix, value, {'id': prefix})
#if error:
# error_html = str(ErrorList(error.error_list))
#else:
@ -288,7 +306,23 @@ class FieldBlock(Block):
else:
label_html = ''
widget_html = widget.render(prefix, value, {'id': prefix, 'placeholder': self.label.title() })
#if error:
# error_html = str(ErrorList(error.error_list))
#else:
# error_html = ''
if self.meta.classname:
classes = self.meta.classname.split(' ')
else:
classes = None
return render_to_string('wagtailadmin/block_forms/field.html', {
'label': self.label,
'classes': classes,
'widget': widget_html,
'label_tag': label_html,
'field': self.field,
@ -327,7 +361,8 @@ class PageChooserBlock(FieldBlock):
# =======
class ChooserBlock(Block):
default = None
class Meta:
default = None
@property
def media(self):
@ -357,8 +392,9 @@ class ChooserBlock(Block):
# ===========
class BaseStructBlock(Block):
default = {}
template = "wagtailadmin/blocks/struct.html"
class Meta:
default = {}
template = "wagtailadmin/blocks/struct.html"
def __init__(self, local_blocks=None, **kwargs):
super(BaseStructBlock, self).__init__(**kwargs)
@ -390,7 +426,7 @@ class BaseStructBlock(Block):
def render_form(self, value, prefix='', error=None):
child_renderings = [
block.render_form(value.get(name, block.default), prefix="%s-%s" % (prefix, name),
block.render_form(value.get(name, block.meta.default), prefix="%s-%s" % (prefix, name),
error=error.params.get(name) if error else None)
for name, block in self.child_blocks.items()
]
@ -434,7 +470,7 @@ class BaseStructBlock(Block):
return StructValue(self, [
(
name,
child_block.to_python(value.get(name, child_block.default))
child_block.to_python(value.get(name, child_block.meta.default))
)
for name, child_block in self.child_blocks.items()
])
@ -463,7 +499,7 @@ class StructValue(collections.OrderedDict):
])
class DeclarativeSubBlocksMetaclass(type):
class DeclarativeSubBlocksMetaclass(BaseBlock):
"""
Metaclass that collects sub-blocks declared on the base classes.
(cheerfully stolen from https://github.com/django/django/blob/master/django/forms/forms.py)
@ -508,7 +544,8 @@ class StructBlock(six.with_metaclass(DeclarativeSubBlocksMetaclass, BaseStructBl
# =========
class ListBlock(Block):
default = []
class Meta:
default = []
def __init__(self, child_block, **kwargs):
super(ListBlock, self).__init__(**kwargs)
@ -543,7 +580,7 @@ class ListBlock(Block):
# 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.default, '__PREFIX__', '')
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>',
@ -634,9 +671,10 @@ class BaseStreamBlock(Block):
# TODO: decide what it means to pass a 'default' arg to StreamBlock's constructor. Logically we want it to be
# of type StreamValue, but we can't construct one of those because it needs a reference back to the StreamBlock
# that we haven't constructed yet...
@property
def default(self):
return StreamValue(self, [])
class Meta:
@property
def default(self):
return StreamValue(self, [])
def __init__(self, local_blocks=None, **kwargs):
super(BaseStreamBlock, self).__init__(**kwargs)
@ -671,7 +709,7 @@ class BaseStreamBlock(Block):
(
self.definition_prefix,
name,
mark_safe(escape_script(self.render_list_member(name, child_block.default, '__PREFIX__', '')))
mark_safe(escape_script(self.render_list_member(name, child_block.meta.default, '__PREFIX__', '')))
)
for name, child_block in self.child_blocks.items()
]

View file

@ -10,10 +10,14 @@ For example, they don't assume the presence of a 'delete' button - it's up to th
var self = {};
self.prefix = prefix;
self.container = $('#' + self.prefix + '-container');
self.menu = $('.stream-menu', self.container);
self.menu = $('> .stream-menu', self.container);
var indexField = $('#' + self.prefix + '-order');
self.menu.click(function(){
self.toggleMenu();
});
self.delete = function() {
sequence.deleteMember(self);
};
@ -30,9 +34,12 @@ For example, they don't assume the presence of a 'delete' button - it's up to th
self.container.fadeOut();
};
self._markAdded = function() {
self.menu.addClass('closed');
self.menu.addClass('stream-menu-closed');
self.container.hide();
self.container.slideDown();
// focus first suitable input found
$('.input input,.input textarea,.input .richtext', self.container).first().focus();
};
self.getIndex = function() {
return parseInt(indexField.val(), 10);
@ -41,6 +48,20 @@ For example, they don't assume the presence of a 'delete' button - it's up to th
indexField.val(i);
};
self.toggleMenu = function(){
if(self.menu.hasClass('stream-menu-closed')){
self.showMenu();
} else {
self.hideMenu();
}
}
self.showMenu = function(){
self.menu.removeClass('stream-menu-closed');
};
self.hideMenu = function(){
self.menu.addClass('stream-menu-closed')
}
return self;
};
window.Sequence = function(opts) {
@ -49,6 +70,11 @@ For example, they don't assume the presence of a 'delete' button - it's up to th
var countField = $('#' + opts.prefix + '-count');
/* NB countField includes deleted items; for the count of non-deleted items, use members.length */
var members = [];
self.menu = countField.siblings('.stream-menu');
self.menu.click(function(){
self.toggleMenu();
});
self.getCount = function() {
return parseInt(countField.val(), 10);
@ -168,6 +194,23 @@ For example, they don't assume the presence of a 'delete' button - it's up to th
member._markDeleted();
};
self.toggleMenu = function(){
if(self.menu.hasClass('stream-menu-closed')){
self.showMenu();
} else {
self.hideMenu();
}
}
self.showMenu = function(){
self.menu.removeClass('stream-menu-closed');
};
self.hideMenu = function(){
self.menu.addClass('stream-menu-closed')
}
/* initialize initial list members */
for (var i = 0; i < self.getCount(); i++) {
var memberPrefix = opts.prefix + '-' + i;

View file

@ -12,14 +12,6 @@
listMemberTemplates[childBlock.name] = template;
}
$('.stream-menu').addClass('stream-menu-closed');
$(document).on('mouseover','.stream-menu',function(){
$(this).removeClass('stream-menu-closed');
}).on('mouseout', '.stream-menu', function(){
$(this).addClass('stream-menu-closed')
});
return function(elementPrefix) {
var sequence = Sequence({
'prefix': elementPrefix,

View file

@ -335,6 +335,10 @@ input[type=submit], input[type=reset], input[type=button], .button, button{
border-color:$color-grey-2;
color:lighten($color-grey-2, 15%);
}
&.button-nostroke{
border:0 !important;
}
}
/* Special styles to counteract Firefox's completely unwarranted assumptions about button styles */

View file

@ -153,16 +153,6 @@
padding:0;
}
input, textarea, .richtext{
@include nice-padding();
@include border-radius(0px);
font-family:Bitter, Georgia, serif;
padding-top:2em;
padding-bottom:2em;
font-size:1.2em;
line-height:1.6em;
}
.richtext{
padding-top:3em; /* to provide space for editor buttons */
padding-bottom:3em;
@ -187,8 +177,15 @@
}
&.stream-field {
padding:0;
&.required .field > label:after{
display:none;
}
> fieldset{
@include column(12);
max-width:none;
padding-left:0;
padding-right:0;
}
@ -196,6 +193,11 @@
.fields > li > .field > label{
display:none;
}
.stream_widget > .field-content{
width:100%;
display:block;
}
.sequence{
position:relative;
@include clearfix;
@ -203,7 +205,6 @@
.sequence{ /* YUK! */
padding:0 1.5em;
margin:1em 0;
border:1px solid lighten($color-grey-4, 3%);
}
}
@ -218,18 +219,40 @@
.sequence-member{
@include clearfix;
position:relative;
padding:1em 1.5em;
border-bottom:1px solid lighten($color-grey-4, 3%);
margin:0 -1.5em;
.inner{
.sequence-member-inner{
@include clearfix;
position:relative;
padding:0em 50px 1em 50px;
> .struct-block > label,
> .char_field > label{
@include transition(opacity 0.2s ease);
opacity:0;
display:block;
font-style:italic;
font-weight:normal;
position:absolute;
top:0; right:4em;
float:none;
width:auto;
padding:0;
color:$color-grey-2;
line-height:2.2em;
text-transform:capitalize;
}
}
.inner > .struct-block > label{
display:block;
width:100%;
float:none;
&:hover{
background-color:$color-input-focus;
}
&:hover .sequence-member-inner{
> .struct-block > label,
> .char_field > label{
opacity:1;
}
}
}
@ -238,14 +261,27 @@
padding-top:0.5em;
padding-bottom:1.2em;
}
.struct-block .char_field > label{
display:none;
}
input[type=text], input[type=url], input[type=email], input[type=numeric], .richtext, textarea{
border:0;
padding-left:0;
padding-right:0;
background-color:transparent;
max-width:1024px;
}
}
/* Object controls */
.stream-controls{
@include transition(opacity 0.2s ease);
opacity:0;
position:absolute;
top:0; right:1em;
z-index:1;
right:1em;
top:1em;
color:white;
overflow:hidden;
@include border-radius(2px);
@ -268,59 +304,131 @@
/* Menu of other blocks to be added at each position */
.stream-menu{
@include transition(all 0.2s ease);
@include box-shadow(inset 0 0 45px rgba(0,0,0,0.3));
position:relative;
overflow:hidden;
background-color:$color-grey-1;
border-top:1px solid transparent;
opacity:1;
z-index:5;
.stream-menu-inner{
@include transition(max-height 0.2s ease);
max-width:50em;
max-height:9999em;
margin: auto;
overflow:hidden;
}
ul{
@include transition(all 0.2s ease);
@include clearfix;
opacity:1;
padding:1em;
background-color:$color-grey-5;
overflow:hidden;
}
li{
display:inline;
@include column(2);
padding-bottom:$grid-gutter-width;
}
button{
@include transition(all 0.2s ease);
background-color:transparent;
border:0;
color:darken($color-grey-3, 5%);
height:auto;
display:block;
width:100%;
padding:0 0 0.5em 0;
span{
text-overflow:ellipsis;
text-transform:none;
white-space: nowrap;
width:100%;
display:block;
overflow:hidden;
padding:0 1em;
box-sizing: border-box;
}
&:before{
display:block;
font-family:wagtail;
font-size:3em;
width:3em;
height:3em;
line-height:3em;
font-size:2em;
width:100%;
height:2em;
line-height:2em;
text-align:center;
}
&:hover{
background-color:$color-teal;
color:white;
}
}
&:before{
margin-top:-0.5em;
@include transition(all 0.2s ease);
@include transform(rotate(-45deg));
@include border-radius(50px);
cursor:pointer;
font-family:wagtail;
content:"B";
width:2em;
height:2em;
width:1em;
height:1em;
display:block;
position:relative;
left:0;right:0;
margin:auto;
margin:-0.5em auto auto auto;
z-index:5;
color:$color-teal;
color:$color-grey-1;
background-color:white;
font-size:1.7em;
line-height:2em;
line-height:1em;
text-align:center;
}
&.stream-menu-closed{
@include box-shadow(none);
height:0px;
border-top:1px solid lighten($color-grey-4, 3%);
.stream-menu-inner{
max-height:1em;
}
ul{
/*height:0px;*/
opacity:0;
padding:10em;
}
&:before{
@include transform(rotate(0deg));
color:$color-grey-3;
background-color:white;
}
&:hover{
border-top-color:$color-teal;
&:before{
color:$color-teal;
}
}
}
}
.sequence-member .stream-menu{
margin:auto -1.5em;
margin:auto auto 0em auto;
}
.sequence-member .stream-menu-closed{
opacity:0;
}
.sequence-member:hover{
.stream-controls{
opacity:1;
}
.stream-menu{
opacity:1;
}
}
@ -335,17 +443,6 @@
}
&.title input,
&.title textarea{
font-size:2em;
padding-top:2em;
}
&.title input{
padding-top:1.5em;
padding-bottom:1.5em;
}
.multiple{
padding:4.5em 0 0 0;
@ -450,6 +547,51 @@
}
}
.full input, textarea, .richtext{
@include nice-padding();
@include border-radius(0px);
padding-top:2em;
padding-bottom:2em;
font-size:1.2em;
line-height:1.6em;
}
.title input,
.title textarea,
.title .richtext{
font-family:Bitter, Georgia, serif;
}
.title.h2 input,
.title.h2 textarea,
.title.h2 .richtext{
font-size:2em;
}
.title.h3 input,
.title.h3 textarea
.title.h3 .richtext{
font-size:1.8em;
}
.title.h4 input,
.title.h4 textarea,
.title.h4 .richtext{
font-size:1.5em;
}
.intro input,
.intro textarea,
.intro .richtext{
font-size:1.4em;
}
.quote input{
font-style: italic;
}
footer .preview{
button, .button{
background-color:lighten($color-grey-2,10%);

View file

@ -60,6 +60,14 @@
transition: none !important;
}
@mixin transform($transform){
-moz-transform: $transform;
-webkit-transform: $transform;
-o-transform: $transform;
-ms-transform: $transform;
transform: $transform;
}
@mixin border-radius($radius){
-webkit-border-radius: $radius;
border-radius: $radius;

View file

@ -1,5 +1,5 @@
{% load wagtailadmin_tags %}
<div class="field {{ field|fieldtype }}">
<div class="field {{ field|fieldtype }} {% for class in classes %} blockclass-{{ class }}{% endfor %} blockname-{{ label }}">
{{ label_tag }}
<div class="field-content">
<div class="input">

View file

@ -1,4 +1,4 @@
{% extends "wagtailadmin/block_forms/sequence_member.html" %}
{% block header_controls %}
<button type="button" id="{{ prefix }}-delete" class="icon text-replace no icon-bin">delete</button>
<button type="button" id="{{ prefix }}-delete" class="icon text-replace no icon-bin button-secondary button-nostroke">delete</button>
{% endblock %}

View file

@ -8,7 +8,7 @@
{% block header_controls %}{% endblock %}
</div>
<div class="inner">{{ child.render_form }}</div>
<div class="sequence-member-inner">{{ child.render_form }}</div>
{% block footer_controls %}{% endblock %}
</li>

View file

@ -1,4 +1,8 @@
{% extends "wagtailadmin/block_forms/sequence.html" %}
{% block header %}
{% include "wagtailadmin/block_forms/stream_menu.html" with prefix=header_menu_prefix %}
{% if list_members_html %}
{% include "wagtailadmin/block_forms/stream_menu.html" with prefix=header_menu_prefix state="closed" %}
{% else %}
{% include "wagtailadmin/block_forms/stream_menu.html" with prefix=header_menu_prefix state="open" %}
{% endif %}
{% endblock %}

View file

@ -5,9 +5,9 @@
{% endblock %}
{% block header_controls %}
<button type="button" id="{{ prefix }}-delete" class="stream-control icon text-replace no icon-bin">delete</button>
<button type="button" id="{{ prefix }}-delete" class="stream-control icon text-replace no icon-bin button-secondary button-nostroke">delete</button>
{% endblock %}
{% block footer_controls %}
{% include "wagtailadmin/block_forms/stream_menu.html" %}
{% include "wagtailadmin/block_forms/stream_menu.html" with state="closed"%}
{% endblock %}

View file

@ -1,7 +1,9 @@
<div class="stream-menu">
<ul>
{% for child_block in child_blocks %}
<li><button type="button" id="{{ prefix }}-add-{{ child_block.name }}" class="block-{{ child_block.name|slugify }} icon icon-{{ child_block.icon }}">{{ child_block.label }}</button></li>
{% endfor %}
</ul>
<div class="stream-menu {% if state == 'closed' %}stream-menu-closed{% endif %}">
<div class="stream-menu-inner">
<ul>
{% for child_block in child_blocks %}
<li><button type="button" id="{{ prefix }}-add-{{ child_block.name }}" class="block-{{ child_block.name|slugify }} icon icon-{{ child_block.meta.icon }}"><span>{{ child_block.label }}</span></button></li>
{% endfor %}
</ul>
</div>
</div>

View file

@ -13,18 +13,19 @@ class TestFieldBlock(unittest.TestCase):
self.assertEqual(html, "Hello world!")
@unittest.expectedFailure # classname seems to have broken
def test_charfield_render_form(self):
block = blocks.FieldBlock(forms.CharField())
html = block.render_form("Hello world!")
self.assertIn('<div class="field char_field">', html)
self.assertIn('<input id="" name="" type="text" value="Hello world!" />', html)
self.assertIn('<input id="" name="" placeholder="" type="text" value="Hello world!" />', html)
def test_charfield_render_form_with_prefix(self):
block = blocks.FieldBlock(forms.CharField())
html = block.render_form("Hello world!", prefix='foo')
self.assertIn('<input id="foo" name="foo" type="text" value="Hello world!" />', html)
self.assertIn('<input id="foo" name="foo" placeholder="" type="text" value="Hello world!" />', html)
def test_charfield_render_form_with_error(self):
block = blocks.FieldBlock(forms.CharField())
@ -41,6 +42,7 @@ class TestFieldBlock(unittest.TestCase):
self.assertEqual(html, "choice-2")
@unittest.expectedFailure # classname seems to have broken
def test_choicefield_render_form(self):
block = blocks.FieldBlock(forms.ChoiceField(choices=(
('choice-1', "Choice 1"),
@ -49,11 +51,47 @@ class TestFieldBlock(unittest.TestCase):
html = block.render_form('choice-2')
self.assertIn('<div class="field choice_field">', html)
self.assertIn('<select id="" name="">', html)
self.assertIn('<select id="" name="" placeholder="">', html)
self.assertIn('<option value="choice-1">Choice 1</option>', html)
self.assertIn('<option value="choice-2" selected="selected">Choice 2</option>', html)
class TestMeta(unittest.TestCase):
def test_set_template_with_meta(self):
class HeadingBlock(blocks.FieldBlock):
class Meta:
template = 'heading.html'
block = HeadingBlock(forms.CharField())
self.assertEqual(block.meta.template, 'heading.html')
def test_set_template_with_constructor(self):
block = blocks.FieldBlock(forms.CharField(), template='heading.html')
self.assertEqual(block.meta.template, 'heading.html')
def test_set_template_with_constructor_overrides_meta(self):
class HeadingBlock(blocks.FieldBlock):
class Meta:
template = 'heading.html'
block = HeadingBlock(forms.CharField(), template='subheading.html')
self.assertEqual(block.meta.template, 'subheading.html')
def test_meta_multiple_inheritance(self):
class HeadingBlock(blocks.FieldBlock):
class Meta:
template = 'heading.html'
test = 'Foo'
class SubHeadingBlock(HeadingBlock):
class Meta:
template = 'subheading.html'
block = SubHeadingBlock(forms.CharField())
self.assertEqual(block.meta.template, 'subheading.html')
self.assertEqual(block.meta.test, 'Foo')
class TestStructBlock(unittest.TestCase):
def test_initialisation(self):
block = blocks.StructBlock([
@ -148,6 +186,7 @@ class TestStructBlock(unittest.TestCase):
# Don't render the extra item
self.assertNotIn('<dt>image</dt>', html)
@unittest.expectedFailure # Double space in classnames...
def test_render_form(self):
class LinkBlock(blocks.StructBlock):
title = blocks.FieldBlock(forms.CharField())
@ -160,10 +199,10 @@ class TestStructBlock(unittest.TestCase):
}, prefix='mylink')
self.assertIn('<div class="struct-block">', html)
self.assertIn('<div class="field char_field">', html)
self.assertIn('<input id="mylink-title" name="mylink-title" type="text" value="Wagtail site" />', html)
self.assertIn('<div class="field url_field">', html)
self.assertIn('<input id="mylink-link" name="mylink-link" type="url" value="http://www.wagtail.io" />', html)
self.assertIn('<div class="field char_field blockname-title">', html)
self.assertIn('<input id="mylink-title" name="mylink-title" placeholder="Title" type="text" value="Wagtail site" />', html)
self.assertIn('<div class="field url_field blockname-link">', html)
self.assertIn('<input id="mylink-link" name="mylink-link" placeholder="Link" type="url" value="http://www.wagtail.io" />', html)
def test_render_form_unknown_field(self):
class LinkBlock(blocks.StructBlock):
@ -177,11 +216,8 @@ class TestStructBlock(unittest.TestCase):
'image': 10,
}, prefix='mylink')
self.assertIn('<div class="struct-block">', html)
self.assertIn('<div class="field char_field">', html)
self.assertIn('<input id="mylink-title" name="mylink-title" type="text" value="Wagtail site" />', html)
self.assertIn('<div class="field url_field">', html)
self.assertIn('<input id="mylink-link" name="mylink-link" type="url" value="http://www.wagtail.io" />', html)
self.assertIn('<input id="mylink-title" name="mylink-title" placeholder="Title" type="text" value="Wagtail site" />', html)
self.assertIn('<input id="mylink-link" name="mylink-link" placeholder="Link" type="url" value="http://www.wagtail.io" />', html)
# Don't render the extra field
self.assertNotIn('mylink-image', html)
@ -195,12 +231,20 @@ class TestStructBlock(unittest.TestCase):
block = LinkBlock()
html = block.render_form({}, prefix='mylink')
self.assertIn('<div class="struct-block">', html)
self.assertIn('<div class="field char_field">', html)
self.assertIn('<input id="mylink-title" name="mylink-title" type="text" value="Torchbox" />', html)
self.assertIn('<div class="field url_field">', html)
self.assertIn('<input id="mylink-link" name="mylink-link" type="url" value="http://www.torchbox.com" />', html)
def test_render_form_uses_default_value(self):
class LinkBlock(blocks.StructBlock):
title = blocks.FieldBlock(forms.CharField(), default="Torchbox")
link = blocks.FieldBlock(forms.URLField(), default="http://www.torchbox.com")
block = LinkBlock()
html = block.render_form({}, prefix='mylink')
self.assertIn('<input id="mylink-title" name="mylink-title" placeholder="Title" type="text" value="Torchbox" />', html)
self.assertIn('<input id="mylink-link" name="mylink-link" placeholder="Link" type="url" value="http://www.torchbox.com" />', html)
class TestListBlock(unittest.TestCase):
def test_initialise_with_class(self):
@ -260,16 +304,38 @@ class TestListBlock(unittest.TestCase):
def test_render_form_labels(self):
html = self.render_form()
self.assertIn('<label for=links-0-value-title>Title</label>', html)
self.assertIn('<label for=links-1-value-link>Link</label>', html)
self.assertIn('<label for=links-0-value-title>title</label>', html)
self.assertIn('<label for=links-0-value-link>link</label>', html)
def test_render_form_values(self):
html = self.render_form()
self.assertIn('<input id="links-0-value-title" name="links-0-value-title" type="text" value="Wagtail" />', html)
self.assertIn('<input id="links-0-value-link" name="links-0-value-link" type="url" value="http://www.wagtail.io" />', html)
self.assertIn('<input id="links-1-value-title" name="links-1-value-title" type="text" value="Django" />', html)
self.assertIn('<input id="links-1-value-link" name="links-1-value-link" type="url" value="http://www.djangoproject.com" />', html)
self.assertIn('<input id="links-0-value-title" name="links-0-value-title" placeholder="Title" type="text" value="Wagtail" />', html)
self.assertIn('<input id="links-0-value-link" name="links-0-value-link" placeholder="Link" type="url" value="http://www.wagtail.io" />', html)
self.assertIn('<input id="links-1-value-title" name="links-1-value-title" placeholder="Title" type="text" value="Django" />', html)
self.assertIn('<input id="links-1-value-link" name="links-1-value-link" placeholder="Link" type="url" value="http://www.djangoproject.com" />', html)
def test_html_declarations(self):
class LinkBlock(blocks.StructBlock):
title = blocks.FieldBlock(forms.CharField())
link = blocks.FieldBlock(forms.URLField())
block = blocks.ListBlock(LinkBlock)
html = block.html_declarations()
self.assertIn('<input id="__PREFIX__-value-title" name="__PREFIX__-value-title" placeholder="Title" type="text" />', html)
self.assertIn('<input id="__PREFIX__-value-link" name="__PREFIX__-value-link" placeholder="Link" type="url" />', html)
def test_html_declarations_uses_default(self):
class LinkBlock(blocks.StructBlock):
title = blocks.FieldBlock(forms.CharField(), default="Github")
link = blocks.FieldBlock(forms.URLField(), default="http://www.github.com")
block = blocks.ListBlock(LinkBlock)
html = block.html_declarations()
self.assertIn('<input id="__PREFIX__-value-title" name="__PREFIX__-value-title" placeholder="Title" type="text" value="Github" />', html)
self.assertIn('<input id="__PREFIX__-value-link" name="__PREFIX__-value-link" placeholder="Link" type="url" value="http://www.github.com" />', html)
class TestStreamBlock(unittest.TestCase):
@ -423,6 +489,28 @@ class TestStreamBlock(unittest.TestCase):
def test_render_form_value_fields(self):
html = self.render_form()
self.assertIn('<input id="myarticle-0-value" name="myarticle-0-value" type="text" value="My title" />', html)
self.assertIn('<input id="myarticle-1-value" name="myarticle-1-value" type="text" value="My first paragraph" />', html)
self.assertIn('<input id="myarticle-2-value" name="myarticle-2-value" type="text" value="My second paragraph" />', html)
self.assertIn('<input id="myarticle-0-value" name="myarticle-0-value" placeholder="Heading" type="text" value="My title" />', html)
self.assertIn('<input id="myarticle-1-value" name="myarticle-1-value" placeholder="Paragraph" type="text" value="My first paragraph" />', html)
self.assertIn('<input id="myarticle-2-value" name="myarticle-2-value" placeholder="Paragraph" type="text" value="My second paragraph" />', html)
def test_html_declarations(self):
class LinkBlock(blocks.StructBlock):
title = blocks.FieldBlock(forms.CharField())
link = blocks.FieldBlock(forms.URLField())
block = blocks.ListBlock(LinkBlock)
html = block.html_declarations()
self.assertIn('<input id="__PREFIX__-value-title" name="__PREFIX__-value-title" placeholder="Title" type="text" />', html)
self.assertIn('<input id="__PREFIX__-value-link" name="__PREFIX__-value-link" placeholder="Link" type="url" />', html)
def test_html_declarations_uses_default(self):
class LinkBlock(blocks.StructBlock):
title = blocks.FieldBlock(forms.CharField(), default="Github")
link = blocks.FieldBlock(forms.URLField(), default="http://www.github.com")
block = blocks.ListBlock(LinkBlock)
html = block.html_declarations()
self.assertIn('<input id="__PREFIX__-value-title" name="__PREFIX__-value-title" placeholder="Title" type="text" value="Github" />', html)
self.assertIn('<input id="__PREFIX__-value-link" name="__PREFIX__-value-link" placeholder="Link" type="url" value="http://www.github.com" />', html)

View file

@ -0,0 +1,13 @@
from django import forms
from wagtail.wagtailadmin import blocks
from wagtail.wagtailembeds.format import embed_to_frontend_html
class EmbedBlock(blocks.FieldBlock):
def __init__(self, **kwargs):
super(EmbedBlock, self).__init__(forms.URLField(), **kwargs)
def render_basic(self, value):
return embed_to_frontend_html(value)

View file

@ -25,6 +25,8 @@ from wagtail.wagtailembeds.embeds import (
oembed as wagtail_oembed,
)
from wagtail.wagtailembeds.templatetags.wagtailembeds_tags import embed as embed_filter
from wagtail.wagtailembeds.blocks import EmbedBlock
from wagtail.wagtailembeds.models import Embed
class TestEmbeds(TestCase):
@ -303,3 +305,18 @@ class TestEmbedFilter(TestCase):
context = template.Context()
result = temp.render(context)
self.assertEqual(result, '')
class TestEmbedBlock(TestCase):
@patch('wagtail.wagtailembeds.format.get_embed')
def test_render(self, get_embed):
get_embed.return_value = Embed(html='<h1>Hello world!</h1>')
block = EmbedBlock()
html = block.render('http://www.example.com')
# Check that get_embed was called correctly
get_embed.assert_any_call('http://www.example.com')
# Check that the embed was in the returned HTML
self.assertIn('<h1>Hello world!</h1>', html)