mirror of
https://github.com/Hopiu/wagtail.git
synced 2026-05-12 09:13:14 +00:00
Merge branch 'feature/streamfield' of github.com:torchbox/wagtail into feature/streamfield
This commit is contained in:
commit
91dd196a24
10 changed files with 1731 additions and 8573 deletions
|
|
@ -48,6 +48,7 @@ def js_dict(d):
|
|||
@deconstructible
|
||||
class Block(object):
|
||||
creation_counter = 0
|
||||
icon = "streamfield-block-placeholder"
|
||||
|
||||
"""
|
||||
Setting a 'dependencies' list serves as a shortcut for the common case where a complex block type
|
||||
|
|
@ -82,6 +83,8 @@ class Block(object):
|
|||
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)
|
||||
|
||||
# Increase the creation counter, and save our local copy.
|
||||
|
|
|
|||
|
|
@ -258,6 +258,22 @@
|
|||
.icon-site:before{
|
||||
content:"@";
|
||||
}
|
||||
.icon-larger:before{
|
||||
font-size:1.5em;
|
||||
}
|
||||
.icon-streamfield-block-placeholder:before{
|
||||
content:"{";
|
||||
}
|
||||
.icon-pilcrow:before {
|
||||
content: "\e600";
|
||||
}
|
||||
.icon-title:before {
|
||||
content: "\f034";
|
||||
}
|
||||
.icon-code:before{
|
||||
content:"\e601"
|
||||
}
|
||||
|
||||
|
||||
.icon.text-replace{
|
||||
font-size:0em;
|
||||
|
|
@ -275,9 +291,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.icon.icon-larger:before{
|
||||
font-size:1.5em;
|
||||
}
|
||||
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
|
|
|
|||
9994
wagtail/wagtailadmin/static/wagtailadmin/scss/fonts/wagtail-icomoon.json
Normal file → Executable file
9994
wagtail/wagtailadmin/static/wagtailadmin/scss/fonts/wagtail-icomoon.json
Normal file → Executable file
File diff suppressed because one or more lines are too long
Binary file not shown.
|
|
@ -74,4 +74,8 @@
|
|||
<glyph unicode="x" d="M954 668c0-14-4-28-16-38l-490-492c-12-10-24-16-40-16-14 0-28 6-38 16l-284 284c-12 12-16 24-16 40 0 14 4 28 16 38l76 78c12 10 24 16 40 16 14 0 28-6 38-16l168-168 376 376c10 10 24 16 38 16 16 0 28-6 40-16l76-78c12-12 16-24 16-40z" />
|
||||
<glyph unicode="y" d="M115.076 175.322q0 28.393 1.875 55.447t7.5 58.393 14.197 58.125 23.035 52.231 33.215 43.393 45.803 28.66 59.732 10.715q4.822 0 22.5-11.518t39.909-25.715 57.857-25.715 71.519-11.518 71.517 11.518 57.857 25.715 39.909 25.715 22.5 11.518q32.678 0 59.732-10.715t45.803-28.66 33.215-43.393 23.035-52.231 14.197-58.125 7.5-58.393 1.875-55.447q0-64.285-39.107-101.518t-103.929-37.232h-468.214q-64.822 0-103.929 37.232t-39.107 101.518zM286.506 653.715q0 85.178 60.268 145.447t145.447 60.268 145.447-60.268 60.268-145.447-60.268-145.447-145.447-60.268-145.447 60.268-60.268 145.447z" horiz-adv-x="1029" />
|
||||
<glyph unicode="z" d="M688 682c0-6-2-10-6-14l-224-224 224-226c4-2 6-8 6-12 0-6-2-10-6-14l-28-28c-4-4-10-6-14-6s-10 2-14 6l-266 266c-4 4-6 8-6 14 0 4 2 8 6 12l266 266c4 4 10 6 14 6s10-2 14-6l28-28c4-4 6-8 6-12z" />
|
||||
<glyph unicode="{" d="M128 640v-128h-128v128h128zM0 832c0 70.656 57.344 128 128 128h64v-128c-35.392 0-64-28.608-64-64h-128v64zM320 832v128h128v-128h-128zM128 128c0-35.392 28.608-64 64-64v-128h-64c-70.656 0-128 57.344-128 128v64h128zM128 384v-128h-128v128h128zM320-64v128h128v-128h-128zM896 512v128h128v-128h-128zM576-64v128h128v-128h-128zM896 960c70.656 0 128-57.344 128-128v-64h-128c0 35.392-28.608 64-64 64v128h64zM896 256v128h128v-128h-128zM832 64c35.392 0 64 28.608 64 64h128v-64c0-70.656-57.344-128-128-128h-64v128zM576 832v128h128v-128h-128z" />
|
||||
<glyph unicode="" d="M384 960h512v-128h-128v-896h-128v896h-128v-896h-128v512c-141.384 0-256 114.616-256 256s114.616 256 256 256z" />
|
||||
<glyph unicode="" d="M832 224l96-96 320 320-320 320-96-96 224-224zM448 672l-96 96-320-320 320-320 96 96-224 224zM701.298 809.481l69.468-18.944-191.987-704.026-69.468 18.944 191.987 704.026z" horiz-adv-x="1280" />
|
||||
<glyph unicode="" d="M0 657.714v218.857l46.286 0.571 30.857-15.429q6.857-2.857 120.571-2.857 25.143 0 75.429 1.143t75.429 1.143q20.571 0 61.429-0.286t61.429-0.286h167.429q3.429 0 12-0.286t11.714 0 9.143 1.714 10 5.143 8.571 10l24 0.571q2.286 0 8-0.286t8-0.286q1.143-64 1.143-192 0-45.714-2.857-62.286-22.286-8-38.857-10.286-14.286 25.143-30.857 73.143-1.714 5.143-6.286 27.429t-8.286 42-4.286 20.286q-3.429 4.571-6.857 7.143t-8.857 3.429-7.429 1.429-10.286 0.286-9.429-0.286q-9.714 0-38 0.286t-42.571 0.286-36.571-1.143-40.571-3.429q-5.143-46.286-4.571-77.714 0-53.714 1.143-221.714t1.143-260q0-9.143-1.429-40.857t0-52.286 7.143-39.429q22.857-12 70.857-24.286t68.571-21.429q2.857-22.857 2.857-28.571 0-8-1.714-16.571l-19.429-0.571q-43.429-1.143-124.571 4.571t-118.286 5.714q-28.571 0-86.286-5.143t-86.857-5.143q-1.714 29.143-1.714 29.714v5.143q9.714 15.429 35.143 24.571t56.286 16.571 44.571 15.429q10.857 24 10.857 218.857 0 57.714-1.714 173.143t-1.714 173.143v66.857q0 1.143 0.286 8.857t0.286 14.286-0.571 14.571-1.714 13.714-2.857 8q-6.286 6.857-92.571 6.857-18.857 0-53.143-6.857t-45.714-14.857q-10.857-7.429-19.429-41.429t-18-63.429-24.286-30.571q-24 14.857-32 25.143zM808 135.714q5.143 10.571 24 10.571h45.714v585.143h-45.714q-18.857 0-24 10.571t6.286 25.429l72 92.571q11.429 14.857 28 14.857t28-14.857l72-92.571q11.429-14.857 6.286-25.429t-24-10.571h-45.714v-585.143h45.714q18.857 0 24-10.571t-6.286-25.429l-72-92.571q-11.429-14.857-28-14.857t-28 14.857l-72 92.571q-11.429 14.857-6.286 25.429z" />
|
||||
</font></defs></svg>
|
||||
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 64 KiB |
Binary file not shown.
Binary file not shown.
|
|
@ -276,29 +276,51 @@
|
|||
padding:1em;
|
||||
background-color:$color-grey-5;
|
||||
}
|
||||
li{
|
||||
display:inline;
|
||||
}
|
||||
|
||||
&.stream-menu-closed{
|
||||
ul{
|
||||
height:0px;
|
||||
}
|
||||
|
||||
button{
|
||||
height:auto;
|
||||
&:before{
|
||||
margin-top:-0.5em;
|
||||
font-family:wagtail;
|
||||
content:"B";
|
||||
width:2em;
|
||||
height:2em;
|
||||
display:block;
|
||||
position:relative;
|
||||
left:0;right:0;
|
||||
margin:auto;
|
||||
z-index:5;
|
||||
color:$color-teal;
|
||||
font-size:1.7em;
|
||||
line-height:2em;
|
||||
font-family:wagtail;
|
||||
font-size:3em;
|
||||
width:3em;
|
||||
height:3em;
|
||||
line-height:3em;
|
||||
text-align:center;
|
||||
}
|
||||
}
|
||||
|
||||
&:before{
|
||||
margin-top:-0.5em;
|
||||
font-family:wagtail;
|
||||
content:"B";
|
||||
width:2em;
|
||||
height:2em;
|
||||
display:block;
|
||||
position:relative;
|
||||
left:0;right:0;
|
||||
margin:auto;
|
||||
z-index:5;
|
||||
color:$color-teal;
|
||||
font-size:1.7em;
|
||||
line-height:2em;
|
||||
text-align:center;
|
||||
}
|
||||
|
||||
|
||||
&.stream-menu-closed{
|
||||
ul{
|
||||
/*height:0px;*/
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
.sequence-member .stream-menu{
|
||||
margin:auto -1.5em;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<div class="stream-menu">
|
||||
<ul>
|
||||
{% for child_block in child_blocks %}
|
||||
<li style="display: inline;"><button type="button" id="{{ prefix }}-add-{{ child_block.name }}">{{ child_block.name }}</button></li>
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ class TestFieldBlock(unittest.TestCase):
|
|||
|
||||
self.assertIn('This field is required.', html)
|
||||
|
||||
@unittest.expectedFailure
|
||||
def test_choicefield_render(self):
|
||||
block = blocks.FieldBlock(forms.ChoiceField(choices=(
|
||||
('choice-1', "Choice 1"),
|
||||
|
|
@ -40,7 +39,7 @@ class TestFieldBlock(unittest.TestCase):
|
|||
)))
|
||||
html = block.render('choice-2')
|
||||
|
||||
self.assertEqual(html, "Choice 2")
|
||||
self.assertEqual(html, "choice-2")
|
||||
|
||||
def test_choicefield_render_form(self):
|
||||
block = blocks.FieldBlock(forms.ChoiceField(choices=(
|
||||
|
|
@ -112,11 +111,10 @@ class TestStructBlock(unittest.TestCase):
|
|||
|
||||
self.assertEqual(list(block.child_blocks.keys()), ['title', 'link', 'classname'])
|
||||
|
||||
@unittest.expectedFailure # Field label not being used in HTML
|
||||
def test_render(self):
|
||||
class LinkBlock(blocks.StructBlock):
|
||||
title = blocks.FieldBlock(forms.CharField(label="Title"))
|
||||
link = blocks.FieldBlock(forms.URLField(label="Link"))
|
||||
title = blocks.FieldBlock(forms.CharField())
|
||||
link = blocks.FieldBlock(forms.URLField())
|
||||
|
||||
block = LinkBlock()
|
||||
html = block.render({
|
||||
|
|
@ -124,11 +122,32 @@ class TestStructBlock(unittest.TestCase):
|
|||
'link': 'http://www.wagtail.io',
|
||||
})
|
||||
|
||||
self.assertIn('<dt>Title</dt>', html)
|
||||
self.assertIn('<dt>title</dt>', html)
|
||||
self.assertIn('<dd>Wagtail site</dd>', html)
|
||||
self.assertIn('<dt>Link</dt>', html)
|
||||
self.assertIn('<dt>link</dt>', html)
|
||||
self.assertIn('<dd>http://www.wagtail.io</dd>', html)
|
||||
|
||||
@unittest.expectedFailure
|
||||
def test_render_unknown_field(self):
|
||||
class LinkBlock(blocks.StructBlock):
|
||||
title = blocks.FieldBlock(forms.CharField())
|
||||
link = blocks.FieldBlock(forms.URLField())
|
||||
|
||||
block = LinkBlock()
|
||||
html = block.render({
|
||||
'title': "Wagtail site",
|
||||
'link': 'http://www.wagtail.io',
|
||||
'image': 10,
|
||||
})
|
||||
|
||||
self.assertIn('<dt>title</dt>', html)
|
||||
self.assertIn('<dd>Wagtail site</dd>', html)
|
||||
self.assertIn('<dt>link</dt>', html)
|
||||
self.assertIn('<dd>http://www.wagtail.io</dd>', html)
|
||||
|
||||
# Don't render the extra item
|
||||
self.assertNotIn('<dt>image</dt>', html)
|
||||
|
||||
def test_render_form(self):
|
||||
class LinkBlock(blocks.StructBlock):
|
||||
title = blocks.FieldBlock(forms.CharField())
|
||||
|
|
@ -146,6 +165,42 @@ class TestStructBlock(unittest.TestCase):
|
|||
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)
|
||||
|
||||
def test_render_form_unknown_field(self):
|
||||
class LinkBlock(blocks.StructBlock):
|
||||
title = blocks.FieldBlock(forms.CharField())
|
||||
link = blocks.FieldBlock(forms.URLField())
|
||||
|
||||
block = LinkBlock()
|
||||
html = block.render_form({
|
||||
'title': "Wagtail site",
|
||||
'link': 'http://www.wagtail.io',
|
||||
'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)
|
||||
|
||||
# Don't render the extra field
|
||||
self.assertNotIn('mylink-image', html)
|
||||
|
||||
@unittest.expectedFailure
|
||||
def test_render_form_uses_initial_values(self):
|
||||
class LinkBlock(blocks.StructBlock):
|
||||
title = blocks.FieldBlock(forms.CharField(initial="Torchbox"))
|
||||
link = blocks.FieldBlock(forms.URLField(initial="http://www.torchbox.com"))
|
||||
|
||||
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)
|
||||
|
||||
|
||||
class TestListBlock(unittest.TestCase):
|
||||
def test_initialise_with_class(self):
|
||||
|
|
@ -215,3 +270,159 @@ class TestListBlock(unittest.TestCase):
|
|||
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)
|
||||
|
||||
|
||||
class TestStreamBlock(unittest.TestCase):
|
||||
def test_initialisation(self):
|
||||
block = blocks.StreamBlock([
|
||||
('heading', blocks.FieldBlock(forms.CharField())),
|
||||
('paragraph', blocks.FieldBlock(forms.CharField())),
|
||||
])
|
||||
|
||||
self.assertEqual(list(block.child_blocks.keys()), ['heading', 'paragraph'])
|
||||
|
||||
def test_initialisation_from_subclass(self):
|
||||
class ArticleBlock(blocks.StreamBlock):
|
||||
heading = blocks.FieldBlock(forms.CharField())
|
||||
paragraph = blocks.FieldBlock(forms.CharField())
|
||||
|
||||
block = ArticleBlock()
|
||||
|
||||
self.assertEqual(list(block.child_blocks.keys()), ['heading', 'paragraph'])
|
||||
|
||||
def test_initialisation_from_subclass_with_extra(self):
|
||||
class ArticleBlock(blocks.StreamBlock):
|
||||
heading = blocks.FieldBlock(forms.CharField())
|
||||
paragraph = blocks.FieldBlock(forms.CharField())
|
||||
|
||||
block = ArticleBlock([
|
||||
('intro', blocks.FieldBlock(forms.CharField()))
|
||||
])
|
||||
|
||||
self.assertEqual(list(block.child_blocks.keys()), ['heading', 'paragraph', 'intro'])
|
||||
|
||||
def test_initialisation_with_multiple_subclassses(self):
|
||||
class ArticleBlock(blocks.StreamBlock):
|
||||
heading = blocks.FieldBlock(forms.CharField())
|
||||
paragraph = blocks.FieldBlock(forms.CharField())
|
||||
|
||||
class ArticleWithIntroBlock(ArticleBlock):
|
||||
intro = blocks.FieldBlock(forms.CharField())
|
||||
|
||||
block = ArticleWithIntroBlock()
|
||||
|
||||
self.assertEqual(list(block.child_blocks.keys()), ['heading', 'paragraph', 'intro'])
|
||||
|
||||
@unittest.expectedFailure # Field order doesn't match inheritance order
|
||||
def test_initialisation_with_mixins(self):
|
||||
class ArticleBlock(blocks.StreamBlock):
|
||||
heading = blocks.FieldBlock(forms.CharField())
|
||||
paragraph = blocks.FieldBlock(forms.CharField())
|
||||
|
||||
class IntroMixin(blocks.StreamBlock):
|
||||
intro = blocks.FieldBlock(forms.CharField())
|
||||
|
||||
class ArticleWithIntroBlock(ArticleBlock, IntroMixin):
|
||||
pass
|
||||
|
||||
block = ArticleWithIntroBlock()
|
||||
|
||||
self.assertEqual(list(block.child_blocks.keys()), ['heading', 'paragraph', 'intro'])
|
||||
|
||||
def render_article(self, data):
|
||||
class ArticleBlock(blocks.StreamBlock):
|
||||
heading = blocks.FieldBlock(forms.CharField())
|
||||
paragraph = blocks.FieldBlock(forms.CharField())
|
||||
|
||||
block = ArticleBlock()
|
||||
value = block.to_python(data)
|
||||
|
||||
return block.render(value)
|
||||
|
||||
def test_render(self):
|
||||
html = self.render_article([
|
||||
{
|
||||
'type': 'heading',
|
||||
'value': "My title",
|
||||
},
|
||||
{
|
||||
'type': 'paragraph',
|
||||
'value': 'My first paragraph',
|
||||
},
|
||||
{
|
||||
'type': 'paragraph',
|
||||
'value': 'My second paragraph',
|
||||
},
|
||||
])
|
||||
|
||||
self.assertIn('<div class="block-heading">My title</div>', html)
|
||||
self.assertIn('<div class="block-paragraph">My first paragraph</div>', html)
|
||||
self.assertIn('<div class="block-paragraph">My second paragraph</div>', html)
|
||||
|
||||
@unittest.expectedFailure
|
||||
def test_render_unknown_type(self):
|
||||
# This can happen if a developer removes a type from their StreamBlock
|
||||
html = self.render_article([
|
||||
{
|
||||
'type': 'foo',
|
||||
'value': "Hello",
|
||||
},
|
||||
])
|
||||
|
||||
def render_form(self):
|
||||
class ArticleBlock(blocks.StreamBlock):
|
||||
heading = blocks.FieldBlock(forms.CharField())
|
||||
paragraph = blocks.FieldBlock(forms.CharField())
|
||||
|
||||
block = ArticleBlock()
|
||||
value = block.to_python([
|
||||
{
|
||||
'type': 'heading',
|
||||
'value': "My title",
|
||||
},
|
||||
{
|
||||
'type': 'paragraph',
|
||||
'value': 'My first paragraph',
|
||||
},
|
||||
{
|
||||
'type': 'paragraph',
|
||||
'value': 'My second paragraph',
|
||||
},
|
||||
])
|
||||
return block.render_form(value, prefix='myarticle')
|
||||
|
||||
def test_render_form_wrapper_class(self):
|
||||
html = self.render_form()
|
||||
|
||||
self.assertIn('<div class="sequence">', html)
|
||||
|
||||
def test_render_form_count_field(self):
|
||||
html = self.render_form()
|
||||
|
||||
self.assertIn('<input type="hidden" name="myarticle-count" id="myarticle-count" value="3">', html)
|
||||
|
||||
def test_render_form_delete_field(self):
|
||||
html = self.render_form()
|
||||
|
||||
self.assertIn('<input type="hidden" id="myarticle-0-deleted" name="myarticle-0-deleted" value="">', html)
|
||||
|
||||
def test_render_form_order_fields(self):
|
||||
html = self.render_form()
|
||||
|
||||
self.assertIn('<input type="hidden" id="myarticle-0-order" name="myarticle-0-order" value="0">', html)
|
||||
self.assertIn('<input type="hidden" id="myarticle-1-order" name="myarticle-1-order" value="1">', html)
|
||||
self.assertIn('<input type="hidden" id="myarticle-2-order" name="myarticle-2-order" value="2">', html)
|
||||
|
||||
def test_render_form_type_fields(self):
|
||||
html = self.render_form()
|
||||
|
||||
self.assertIn('<input type="hidden" id="myarticle-0-type" name="myarticle-0-type" value="heading">', html)
|
||||
self.assertIn('<input type="hidden" id="myarticle-1-type" name="myarticle-1-type" value="paragraph">', html)
|
||||
self.assertIn('<input type="hidden" id="myarticle-2-type" name="myarticle-2-type" value="paragraph">', html)
|
||||
|
||||
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)
|
||||
|
|
|
|||
Loading…
Reference in a new issue