0
0
mirror of https://github.com/wagtail/wagtail.git synced 2024-12-01 11:41:20 +01:00

Teach StreamBlocks about NON_FIELD_ERRORS

A StreamBlock can now have NON_FIELD_ERRORS as part of its error dict.
These will be displayed as error messages above the StreamBlock in the
admin upon form submission.

No code in Wagtail currently makes use of this feautre. It is available
for developers of sites to use in their custom StreamField blocks.
This commit is contained in:
Tim Heap 2016-01-22 16:18:01 +11:00 committed by Matt Westcott
parent e7ace5c181
commit 9ae69ed8f2
3 changed files with 107 additions and 4 deletions

View File

@ -14,8 +14,15 @@
{% endcomment %} {% endcomment %}
<div class="sequence-container sequence-type-{% block sequence_type_class %}{% endblock %}"> <div class="sequence-container sequence-type-{% block sequence_type_class %}{% endblock %}">
<input type="hidden" name="{{ prefix }}-count" id="{{ prefix }}-count" value="{{ list_members_html|length }}"> <input type="hidden" name="{{ prefix }}-count" id="{{ prefix }}-count" value="{{ list_members_html|length }}">
{% block header %}{% endblock %} {% block header %}{% endblock %}
{% if block_errors %}
{% for error in block_errors %}
<div class="help-block help-critical">{{ error }}</div>
{% endfor %}
{% endif %}
<div class="sequence-container-inner"> <div class="sequence-container-inner">
<ul id="{{ prefix }}-list" class="sequence"> <ul id="{{ prefix }}-list" class="sequence">
{% for list_member_html in list_members_html %} {% for list_member_html in list_members_html %}

View File

@ -3,7 +3,7 @@ from __future__ import absolute_import, unicode_literals
import collections import collections
from django import forms from django import forms
from django.core.exceptions import ValidationError from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
from django.forms.utils import ErrorList from django.forms.utils import ErrorList
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils.encoding import python_2_unicode_compatible, force_text from django.utils.encoding import python_2_unicode_compatible, force_text
@ -130,6 +130,7 @@ class BaseStreamBlock(Block):
'list_members_html': list_members_html, 'list_members_html': list_members_html,
'child_blocks': self.child_blocks.values(), 'child_blocks': self.child_blocks.values(),
'header_menu_prefix': '%s-before' % prefix, 'header_menu_prefix': '%s-before' % prefix,
'block_errors': error_dict.get(NON_FIELD_ERRORS),
}) })
def value_from_datadict(self, data, files, prefix): def value_from_datadict(self, data, files, prefix):

View File

@ -5,7 +5,7 @@ import unittest
from django import forms from django import forms
from django.forms.utils import ErrorList from django.forms.utils import ErrorList
from django.core.exceptions import ValidationError from django.core.exceptions import NON_FIELD_ERRORS, ValidationError
from django.test import TestCase, SimpleTestCase from django.test import TestCase, SimpleTestCase
from django.utils.safestring import mark_safe, SafeData from django.utils.safestring import mark_safe, SafeData
@ -18,6 +18,36 @@ from wagtail.tests.testapp.blocks import SectionBlock
import base64 import base64
class IntegerBlock(blocks.CharBlock):
def clean(self, value):
out = super(IntegerBlock, self).clean(value)
try:
return int(out)
except ValueError:
raise ValidationError('Value must be an integer')
class ColumnBlock(blocks.StructBlock):
width = IntegerBlock()
content = blocks.TextBlock()
class ValidatedBlock(blocks.StreamBlock):
column = ColumnBlock()
required_width = 6
def clean(self, value):
blocks = super(ValidatedBlock, self).clean(value)
width = sum(block.value['width'] for block in blocks)
if width != self.required_width:
error = 'You must have exactly %d columns, not %d' % (
self.required_width, width)
raise ValidationError('Validation error in StreamBlock', params={
NON_FIELD_ERRORS: [error],
})
return blocks
class TestFieldBlock(unittest.TestCase): class TestFieldBlock(unittest.TestCase):
def test_charfield_render(self): def test_charfield_render(self):
block = blocks.CharBlock() block = blocks.CharBlock()
@ -1039,7 +1069,7 @@ class TestListBlock(unittest.TestCase):
self.assertIn('value="chocolate"', form_html) self.assertIn('value="chocolate"', form_html)
class TestStreamBlock(unittest.TestCase): class TestStreamBlock(SimpleTestCase):
def test_initialisation(self): def test_initialisation(self):
block = blocks.StreamBlock([ block = blocks.StreamBlock([
('heading', blocks.CharBlock()), ('heading', blocks.CharBlock()),
@ -1271,6 +1301,71 @@ class TestStreamBlock(unittest.TestCase):
3: ['Enter a valid URL.'], 3: ['Enter a valid URL.'],
}) })
def test_block_level_validation_no_errors(self):
block = ValidatedBlock()
post_data = {'columns-count': '3'}
for i in range(3):
post_data.update({
'columns-%d-deleted' % i: '',
'columns-%d-order' % i: str(i),
'columns-%d-type' % i: 'column',
'columns-%d-value-width' % i: str(i + 1),
'columns-%d-value-content' % i: 'hello %d' % (i,),
})
block_value = block.value_from_datadict(post_data, {}, 'columns')
try:
block.clean(block_value)
except ValidationError:
self.fail('Should have passed validation')
def test_block_level_validation_throws_errors(self):
block = ValidatedBlock()
post_data = {'columns-count': '4'}
for i in range(4):
post_data.update({
'columns-%d-deleted' % i: '',
'columns-%d-order' % i: str(i),
'columns-%d-type' % i: 'column',
'columns-%d-value-width' % i: '2',
'columns-%d-value-content' % i: 'hello %d' % (i,),
})
block_value = block.value_from_datadict(post_data, {}, 'columns')
with self.assertRaises(ValidationError) as catcher:
block.clean(block_value)
self.assertEqual(catcher.exception.params, {
NON_FIELD_ERRORS: ['You must have exactly 6 columns, not 8'],
})
def test_block_level_validation_renders_errors(self):
block = ValidatedBlock()
post_data = {'columns-count': '2'}
for i in range(2):
post_data.update({
'columns-%d-deleted' % i: '',
'columns-%d-order' % i: str(i),
'columns-%d-type' % i: 'column',
'columns-%d-value-width' % i: str(i + 2),
'columns-%d-value-content' % i: 'hello %d' % (i,),
})
block_value = block.value_from_datadict(post_data, {}, 'columns')
error_message = 'You must have exactly 6 columns, not 5'
errors = ErrorList([
ValidationError('Validation error in StructBlock', params={
NON_FIELD_ERRORS: [error_message]
})
])
self.assertInHTML(
'<div class="help-block help-critical">%s</div>' % (error_message,),
block.render_form(block_value, prefix='columns', errors=errors))
def test_html_declarations(self): def test_html_declarations(self):
class ArticleBlock(blocks.StreamBlock): class ArticleBlock(blocks.StreamBlock):
heading = blocks.CharBlock() heading = blocks.CharBlock()