From 36ed0ec99d49b97384ebaa4ded6911e3104d3f0b Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Thu, 5 Feb 2015 11:46:44 +0000 Subject: [PATCH 1/8] Added tests for FieldBlock --- wagtail/wagtailadmin/tests/test_blocks.py | 55 +++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 wagtail/wagtailadmin/tests/test_blocks.py diff --git a/wagtail/wagtailadmin/tests/test_blocks.py b/wagtail/wagtailadmin/tests/test_blocks.py new file mode 100644 index 000000000..eb9c74a1f --- /dev/null +++ b/wagtail/wagtailadmin/tests/test_blocks.py @@ -0,0 +1,55 @@ +import unittest + +from django import forms +from django.core.exceptions import ValidationError + +from wagtail.wagtailadmin import blocks + + +class TestFieldBlock(unittest.TestCase): + def test_charfield_render(self): + block = blocks.FieldBlock(forms.CharField()) + html = block.render("Hello world!") + + self.assertEqual(html, "Hello world!") + + def test_charfield_render_form(self): + block = blocks.FieldBlock(forms.CharField()) + html = block.render_form("Hello world!") + + self.assertIn('
', html) + self.assertIn('', html) + + def test_charfield_render_form_with_prefix(self): + block = blocks.FieldBlock(forms.CharField()) + html = block.render_form("Hello world!", prefix='foo') + + self.assertIn('', html) + + def test_charfield_render_form_with_error(self): + block = blocks.FieldBlock(forms.CharField()) + html = block.render_form("Hello world!", error=ValidationError("This field is required.")) + + self.assertIn('This field is required.', html) + + @unittest.expectedFailure + def test_choicefield_render(self): + block = blocks.FieldBlock(forms.ChoiceField(choices=( + ('choice-1', "Choice 1"), + ('choice-2', "Choice 2"), + ))) + html = block.render('choice-2') + + self.assertEqual(html, "Choice 2") + + def test_choicefield_render_form(self): + block = blocks.FieldBlock(forms.ChoiceField(choices=( + ('choice-1', "Choice 1"), + ('choice-2', "Choice 2"), + ))) + html = block.render_form('choice-2') + + self.assertIn('
', html) + self.assertIn('', html) self.assertIn('', html) self.assertIn('', html) + + +class TestStructBlock(unittest.TestCase): + def test_initialisation(self): + block = blocks.StructBlock([ + ('title', blocks.FieldBlock(forms.CharField())), + ('link', blocks.FieldBlock(forms.URLField())), + ]) + + self.assertEqual(list(block.child_blocks.keys()), ['title', 'link']) + + def test_initialisation_from_subclass(self): + class LinkBlock(blocks.StructBlock): + title = blocks.FieldBlock(forms.CharField()) + link = blocks.FieldBlock(forms.URLField()) + + block = LinkBlock() + + self.assertEqual(list(block.child_blocks.keys()), ['title', 'link']) + + def test_initialisation_from_subclass_with_extra(self): + class LinkBlock(blocks.StructBlock): + title = blocks.FieldBlock(forms.CharField()) + link = blocks.FieldBlock(forms.URLField()) + + block = LinkBlock([ + ('classname', blocks.FieldBlock(forms.CharField())) + ]) + + self.assertEqual(list(block.child_blocks.keys()), ['title', 'link', 'classname']) + + def test_initialisation_with_multiple_subclassses(self): + class LinkBlock(blocks.StructBlock): + title = blocks.FieldBlock(forms.CharField()) + link = blocks.FieldBlock(forms.URLField()) + + class StyledLinkBlock(LinkBlock): + classname = blocks.FieldBlock(forms.CharField()) + + block = StyledLinkBlock() + + self.assertEqual(list(block.child_blocks.keys()), ['title', 'link', 'classname']) + + @unittest.expectedFailure # Field order doesn't match inheritance order + def test_initialisation_with_mixins(self): + class LinkBlock(blocks.StructBlock): + title = blocks.FieldBlock(forms.CharField()) + link = blocks.FieldBlock(forms.URLField()) + + class StylingMixin(blocks.StructBlock): + classname = blocks.FieldBlock(forms.CharField()) + + class StyledLinkBlock(LinkBlock, StylingMixin): + pass + + block = StyledLinkBlock() + + 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")) + + block = LinkBlock() + html = block.render({ + 'title': "Wagtail site", + 'link': 'http://www.wagtail.io', + }) + + self.assertIn('
Title
', html) + self.assertIn('
Wagtail site
', html) + self.assertIn('
Link
', html) + self.assertIn('
http://www.wagtail.io
', html) + + def test_render_form(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', + }, prefix='mylink') + + self.assertIn('
', html) + self.assertIn('
', html) + self.assertIn('', html) + self.assertIn('
', html) + self.assertIn('', html) From ac1bee89c992c2952a0f6fb8128490e344eb7245 Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Thu, 5 Feb 2015 12:31:24 +0000 Subject: [PATCH 5/8] Have StreamChild.__str__ return the child's native rendering so that it's usable on templates. (StreamChild isn't just syntactic sugar after all, then...) --- wagtail/wagtailadmin/blocks.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/wagtail/wagtailadmin/blocks.py b/wagtail/wagtailadmin/blocks.py index af2d24e52..e3dd620e5 100644 --- a/wagtail/wagtailadmin/blocks.py +++ b/wagtail/wagtailadmin/blocks.py @@ -735,7 +735,7 @@ class BaseStreamBlock(Block): def render(self, value): return format_html_join('\n', '
{0}
', - [(child.render(), child.block_type) for child in value] + [(child, child.block_type) for child in value] ) @@ -750,15 +750,21 @@ class StreamValue(collections.Sequence): (which keep track of block types in a way that the values alone wouldn't). """ + @python_2_unicode_compatible class StreamChild(BoundBlock): - """ - Syntactic sugar so that we can say child.block_type instead of child.block.name. - (This doesn't belong on BoundBlock itself because the idea of block.name denoting - the child's "type" ('heading', 'paragraph' etc) is unique to StreamBlock, and in the - wider context people are liable to confuse it with the block class (CharBlock etc). - """ + """Provides some extensions to BoundBlock to make it more natural to work with on front-end templates""" + def __str__(self): + """Render the value according to the block's native rendering""" + return self.block.render(self.value) + @property def block_type(self): + """ + Syntactic sugar so that we can say child.block_type instead of child.block.name. + (This doesn't belong on BoundBlock itself because the idea of block.name denoting + the child's "type" ('heading', 'paragraph' etc) is unique to StreamBlock, and in the + wider context people are liable to confuse it with the block class (CharBlock etc). + """ return self.block.name def __init__(self, stream_block, stream_data): From c115addf1aa1d47da855bafc6469dfca769a6e3d Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Thu, 5 Feb 2015 13:09:34 +0000 Subject: [PATCH 6/8] Added tests for ListBlock --- wagtail/wagtailadmin/tests/test_blocks.py | 70 +++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/wagtail/wagtailadmin/tests/test_blocks.py b/wagtail/wagtailadmin/tests/test_blocks.py index b37541ba7..4600793a2 100644 --- a/wagtail/wagtailadmin/tests/test_blocks.py +++ b/wagtail/wagtailadmin/tests/test_blocks.py @@ -145,3 +145,73 @@ class TestStructBlock(unittest.TestCase): self.assertIn('', html) self.assertIn('
', html) self.assertIn('', html) + + +class TestListBlock(unittest.TestCase): + def test_initialise_with_class(self): + block = blocks.ListBlock(blocks.Block) + + # Child block should be initialised for us + self.assertIsInstance(block.child_block, blocks.Block) + + def test_initialise_with_instance(self): + child_block = blocks.Block() + block = blocks.ListBlock(child_block) + + self.assertEqual(block.child_block, child_block) + + def render_form(self): + class LinkBlock(blocks.StructBlock): + title = blocks.FieldBlock(forms.CharField()) + link = blocks.FieldBlock(forms.URLField()) + + block = blocks.ListBlock(LinkBlock) + + html = block.render_form([ + { + 'title': "Wagtail", + 'link': 'http://www.wagtail.io', + }, + { + 'title': "Django", + 'link': 'http://www.djangoproject.com', + }, + ] + , prefix='links') + + return html + + def test_render_form_wrapper_class(self): + html = self.render_form() + + self.assertIn('
', html) + + def test_render_form_count_field(self): + html = self.render_form() + + self.assertIn('', html) + + def test_render_form_delete_field(self): + html = self.render_form() + + self.assertIn('', html) + + def test_render_form_order_fields(self): + html = self.render_form() + + self.assertIn('', html) + self.assertIn('', html) + + def test_render_form_labels(self): + html = self.render_form() + + self.assertIn('', html) + self.assertIn('', html) + + def test_render_form_values(self): + html = self.render_form() + + self.assertIn('', html) + self.assertIn('', html) + self.assertIn('', html) + self.assertIn('', html) From 2ad0366fa0a87c43f68d4d96c784611195ff7d7f Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Thu, 5 Feb 2015 15:00:56 +0000 Subject: [PATCH 7/8] make the base Block.render template-aware, so that any subclass can define a 'template' property to override the default rendering (which is now provided by block.render_basic()) --- wagtail/wagtailadmin/blocks.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/wagtail/wagtailadmin/blocks.py b/wagtail/wagtailadmin/blocks.py index e3dd620e5..ea008f1c4 100644 --- a/wagtail/wagtailadmin/blocks.py +++ b/wagtail/wagtailadmin/blocks.py @@ -184,7 +184,20 @@ class Block(object): def render(self, value): """ - Return a text rendering of 'value', suitable for display on templates. + Return a text rendering of 'value', suitable for display on templates. By default, this will + use a template if a 'template' property is specified on the block, and fall back on render_basic + otherwise. + """ + template = getattr(self, 'template', None) + if template: + return render_to_string(template, {'self': value}) + else: + return self.render_basic(value) + + def render_basic(self, value): + """ + Return a text rendering of 'value', suitable for display on templates. render() will fall back on + this if the block does not define a 'template' property. """ return force_text(value) @@ -403,9 +416,6 @@ class BaseStructBlock(Block): for name, val in value.items() ]) - def render(self, value): - return render_to_string(self.template, {'self': value}) - @python_2_unicode_compatible # provide equivalent __unicode__ and __str__ methods on Py2 class StructValue(collections.OrderedDict): def __init__(self, block, *args): @@ -579,6 +589,12 @@ class ListBlock(Block): for item in value ] + def render_basic(self, value): + children = format_html_join('\n', '
  • {0}
  • ', + [(self.child_block.render(child_value),) for child_value in value] + ) + return format_html("
      {0}
    ", children) + # =========== # StreamBlock @@ -733,7 +749,7 @@ class BaseStreamBlock(Block): for child in value # child is a BoundBlock instance ] - def render(self, value): + def render_basic(self, value): return format_html_join('\n', '
    {0}
    ', [(child, child.block_type) for child in value] ) From 161aa50a9ef158e5b399af8d0e36f33ef6c0a47b Mon Sep 17 00:00:00 2001 From: Karl Hobley Date: Thu, 5 Feb 2015 15:33:42 +0000 Subject: [PATCH 8/8] Test tweaks --- wagtail/wagtailadmin/tests/test_blocks.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/wagtail/wagtailadmin/tests/test_blocks.py b/wagtail/wagtailadmin/tests/test_blocks.py index 4600793a2..78c94bea9 100644 --- a/wagtail/wagtailadmin/tests/test_blocks.py +++ b/wagtail/wagtailadmin/tests/test_blocks.py @@ -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,7 +111,6 @@ 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")) @@ -124,9 +122,9 @@ class TestStructBlock(unittest.TestCase): 'link': 'http://www.wagtail.io', }) - self.assertIn('
    Title
    ', html) + self.assertIn('
    title
    ', html) self.assertIn('
    Wagtail site
    ', html) - self.assertIn('
    Link
    ', html) + self.assertIn('
    link
    ', html) self.assertIn('
    http://www.wagtail.io
    ', html) def test_render_form(self):