From a1a065ffce6b78a56aef21e9dec2bd6b8ce12a6b Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Thu, 26 Feb 2015 11:37:17 +0000 Subject: [PATCH 1/6] implement a basic ChoiceBlock --- wagtail/wagtailcore/blocks/field_block.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/wagtail/wagtailcore/blocks/field_block.py b/wagtail/wagtailcore/blocks/field_block.py index 586d03fae..333f0cbea 100644 --- a/wagtail/wagtailcore/blocks/field_block.py +++ b/wagtail/wagtailcore/blocks/field_block.py @@ -142,6 +142,12 @@ class DateTimeBlock(FieldBlock): return parse_datetime(value) +class ChoiceBlock(FieldBlock): + def __init__(self, choices=(), required=True, help_text=None, **kwargs): + self.field = forms.ChoiceField(choices=choices, required=required, help_text=help_text) + super(ChoiceBlock, self).__init__(**kwargs) + + class RichTextBlock(FieldBlock): @cached_property def field(self): @@ -228,7 +234,7 @@ class PageChooserBlock(ChooserBlock): # rather than wagtailcore.blocks.field.FooBlock block_classes = [ FieldBlock, CharBlock, URLBlock, RichTextBlock, RawHTMLBlock, ChooserBlock, PageChooserBlock, - BooleanBlock, DateBlock, TimeBlock, DateTimeBlock, + BooleanBlock, DateBlock, TimeBlock, DateTimeBlock, ChoiceBlock, ] DECONSTRUCT_ALIASES = { cls: 'wagtail.wagtailcore.blocks.%s' % cls.__name__ From c2836382d5af76586defcff152bc66713070c533 Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Thu, 26 Feb 2015 11:48:04 +0000 Subject: [PATCH 2/6] tests for ChoiceBlock with required=True --- wagtail/wagtailcore/tests/test_blocks.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/wagtail/wagtailcore/tests/test_blocks.py b/wagtail/wagtailcore/tests/test_blocks.py index 17bf1b81e..de688c62a 100644 --- a/wagtail/wagtailcore/tests/test_blocks.py +++ b/wagtail/wagtailcore/tests/test_blocks.py @@ -82,6 +82,28 @@ class TestFieldBlock(unittest.TestCase): self.assertEqual(content, ["Choice 1"]) +class TestChoiceBlock(unittest.TestCase): + def test_render_required_choice_block(self): + block = blocks.ChoiceBlock(choices=[('tea', 'Tea'), ('coffee', 'Coffee')]) + html = block.render_form('coffee', prefix='beverage') + self.assertIn('', html) + self.assertIn('' % blank_choice_dash_label, html) + self.assertIn('', html) + self.assertIn('', html) + + def test_validate_non_required_choice_block(self): + block = blocks.ChoiceBlock(choices=[('tea', 'Tea'), ('coffee', 'Coffee')], required=False) + self.assertEqual(block.clean('coffee'), 'coffee') + + with self.assertRaises(ValidationError): + block.clean('whisky') + + self.assertEqual(block.clean(''), '') + self.assertEqual(block.clean(None), '') + + def test_render_non_required_choice_block_with_existing_blank_choice(self): + from django.db.models.fields import BLANK_CHOICE_DASH + blank_choice_dash_label = BLANK_CHOICE_DASH[0][1] + + block = blocks.ChoiceBlock( + choices=[('tea', 'Tea'), ('coffee', 'Coffee'), ('', 'No thanks')], + required=False) + html = block.render_form(None, prefix='beverage') + self.assertIn('', html) + # blank option should still be rendered for required fields + # (we may want it as an initial value) + self.assertIn('' % self.blank_choice_dash_label, html) self.assertIn('', html) self.assertIn('', html) @@ -104,13 +111,10 @@ class TestChoiceBlock(unittest.TestCase): block.clean(None) def test_render_non_required_choice_block(self): - from django.db.models.fields import BLANK_CHOICE_DASH - blank_choice_dash_label = BLANK_CHOICE_DASH[0][1] - block = blocks.ChoiceBlock(choices=[('tea', 'Tea'), ('coffee', 'Coffee')], required=False) html = block.render_form('coffee', prefix='beverage') self.assertIn('', html) - self.assertNotIn('' % blank_choice_dash_label, html) + self.assertNotIn('' % self.blank_choice_dash_label, html) self.assertIn('', html) self.assertIn('', html) self.assertIn('', html) + def test_named_groups_without_blank_option(self): + block = blocks.ChoiceBlock( + choices=[ + ('Alcoholic', [ + ('gin', 'Gin'), + ('whisky', 'Whisky'), + ]), + ('Non-alcoholic', [ + ('tea', 'Tea'), + ('coffee', 'Coffee'), + ]), + ]) + + # test rendering with the blank option selected + html = block.render_form(None, prefix='beverage') + self.assertIn('', html) + self.assertIn('' % self.blank_choice_dash_label, html) + self.assertIn('', html) + self.assertIn('', html) + + def test_named_groups_with_blank_option(self): + block = blocks.ChoiceBlock( + choices=[ + ('Alcoholic', [ + ('gin', 'Gin'), + ('whisky', 'Whisky'), + ]), + ('Non-alcoholic', [ + ('tea', 'Tea'), + ('coffee', 'Coffee'), + ]), + ('Not thirsty', [ + ('', 'No thanks') + ]), + ], + required=False) + + # test rendering with the blank option selected + html = block.render_form(None, prefix='beverage') + self.assertIn('', html) + self.assertNotIn('' % self.blank_choice_dash_label, html) + self.assertNotIn('' % self.blank_choice_dash_label, html) + self.assertIn('', html) + self.assertIn('', html) + class TestMeta(unittest.TestCase): def test_set_template_with_meta(self): From 1191643ac6c56619ed89e48b5ebb423af62b5ddf Mon Sep 17 00:00:00 2001 From: Matt Westcott Date: Thu, 26 Feb 2015 13:18:41 +0000 Subject: [PATCH 5/6] support choice lists via subclassing ChoiceBlock --- wagtail/wagtailcore/blocks/field_block.py | 10 ++++++++-- wagtail/wagtailcore/tests/test_blocks.py | 12 ++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/wagtail/wagtailcore/blocks/field_block.py b/wagtail/wagtailcore/blocks/field_block.py index 8168b74f7..10c14e242 100644 --- a/wagtail/wagtailcore/blocks/field_block.py +++ b/wagtail/wagtailcore/blocks/field_block.py @@ -144,8 +144,14 @@ class DateTimeBlock(FieldBlock): class ChoiceBlock(FieldBlock): - def __init__(self, choices=(), required=True, help_text=None, **kwargs): - choices = list(choices) if choices else [] + choices = () + + def __init__(self, choices=None, required=True, help_text=None, **kwargs): + if choices is None: + # no choices specified, so pick up the choice list defined at the class level + choices = list(self.choices) + else: + choices = list(choices) # If choices does not already contain a blank option, insert one # (to match Django's own behaviour for modelfields: https://github.com/django/django/blob/1.7.5/django/db/models/fields/__init__.py#L732-744) diff --git a/wagtail/wagtailcore/tests/test_blocks.py b/wagtail/wagtailcore/tests/test_blocks.py index 42d3ccdc7..029c8509f 100644 --- a/wagtail/wagtailcore/tests/test_blocks.py +++ b/wagtail/wagtailcore/tests/test_blocks.py @@ -200,6 +200,18 @@ class TestChoiceBlock(unittest.TestCase): self.assertIn('', html) self.assertIn('', html) + def test_subclassing(self): + class BeverageChoiceBlock(blocks.ChoiceBlock): + choices = [ + ('tea', 'Tea'), + ('coffee', 'Coffee'), + ] + + block = BeverageChoiceBlock() + html = block.render_form('tea', prefix='beverage') + self.assertIn('', html) self.assertIn('', html) + # subclasses of ChoiceBlock should deconstruct to a basic ChoiceBlock for migrations + self.assertEqual( + block.deconstruct(), + ( + 'wagtail.wagtailcore.blocks.ChoiceBlock', + [], + { + 'choices': [('tea', 'Tea'), ('coffee', 'Coffee')], + 'required': False, + }, + ) + ) + class TestMeta(unittest.TestCase): def test_set_template_with_meta(self):