Add validations for min_max_fields on StreamBlock

pull/3401/merge
ebar0n 2017-05-30 08:59:12 -05:00 zatwierdzone przez Matt Westcott
rodzic 58ad6545be
commit 993cff3e22
3 zmienionych plików z 71 dodań i 2 usunięć

Wyświetl plik

@ -441,7 +441,7 @@ Since ``StreamField`` accepts an instance of ``StreamBlock`` as a parameter, in
.. code-block:: python
class HomePage(Page):
carousel = StreamField(CarouselBlock(max_num=10))
carousel = StreamField(CarouselBlock(max_num=10, min_max_fields={'video': {'max_num': 2}}))
``StreamBlock`` accepts the following options as either keyword arguments or ``Meta`` properties:
@ -454,6 +454,9 @@ Since ``StreamField`` accepts an instance of ``StreamBlock`` as a parameter, in
``max_num``
Maximum number of sub-blocks that the stream may have.
``min_max_fields``
Specifies the minimum and maximum number of each block type, as a dictionary mapping block names to dicts with (optional) ``min_num`` and ``max_num`` fields.
.. _streamfield_personblock_example:

Wyświetl plik

@ -36,12 +36,15 @@ class StreamBlockValidationError(ValidationError):
class BaseStreamBlock(Block):
def __init__(self, local_blocks=None, min_num=None, max_num=None, **kwargs):
def __init__(self, local_blocks=None, min_num=None, max_num=None, min_max_fields=None, **kwargs):
self._constructor_kwargs = kwargs
# Used to validate the minimum and maximum number of elements in the block
self.min_num = min_num
self.max_num = max_num
if min_max_fields is None:
min_max_fields = {}
self.min_max_fields = min_max_fields
super(BaseStreamBlock, self).__init__(**kwargs)
@ -212,6 +215,33 @@ class BaseStreamBlock(Block):
[_('The maximum number of items is %s' % self.max_num)]
))
if self.min_max_fields:
fields = {}
for item in value:
if item.block_type not in self.min_max_fields:
continue
if item.block_type not in fields:
fields[item.block_type] = 0
fields[item.block_type] += 1
for field in self.min_max_fields:
field_title = field.replace('_', ' ').title()
max_num = self.min_max_fields[field].get('max_num', None)
min_num = self.min_max_fields[field].get('min_num', None)
if field in fields:
if min_num and min_num > fields[field]:
non_block_errors.append(ErrorList(
['{}: {}'.format(field_title, _('The minimum number of items is %s' % min_num))]
))
if max_num and max_num < fields[field]:
non_block_errors.append(ErrorList(
['{}: {}'.format(field_title, _('The maximum number of items is %s' % max_num))]
))
elif min_num:
non_block_errors.append(ErrorList(
['{}: {}'.format(field_title, _('The minimum number of items is %s' % min_num))]
))
if errors or non_block_errors:
# The message here is arbitrary - outputting error messages is delegated to the child blocks,
# which only involves the 'params' list

Wyświetl plik

@ -2073,6 +2073,42 @@ class TestStreamBlock(WagtailTestUtils, SimpleTestCase):
'__all__': [['The maximum number of items is 1']]
})
def test_min_max_fields_min_validation_errors(self):
class ValidatedBlock(blocks.StreamBlock):
char = blocks.CharBlock()
url = blocks.URLBlock()
block = ValidatedBlock(min_max_fields={'char': {'min_num': 1}})
value = blocks.StreamValue(block, [
('url', 'http://example.com/'),
('url', 'http://example.com/'),
])
with self.assertRaises(ValidationError) as catcher:
block.clean(value)
self.assertEqual(catcher.exception.params, {
'__all__': [['Char: The minimum number of items is 1']]
})
def test_min_max_fields_max_validation_errors(self):
class ValidatedBlock(blocks.StreamBlock):
char = blocks.CharBlock()
url = blocks.URLBlock()
block = ValidatedBlock(min_max_fields={'char': {'max_num': 1}})
value = blocks.StreamValue(block, [
('char', 'foo'),
('char', 'foo'),
('url', 'http://example.com/'),
('url', 'http://example.com/'),
])
with self.assertRaises(ValidationError) as catcher:
block.clean(value)
self.assertEqual(catcher.exception.params, {
'__all__': [['Char: The maximum number of items is 1']]
})
def test_block_level_validation_renders_errors(self):
block = FooStreamBlock()