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

Add system check to reject invalid block names in StreamField - fixes #1154

This commit is contained in:
Matt Westcott 2015-06-10 17:21:17 +01:00
parent c3c92ebb10
commit 0143787fab
7 changed files with 187 additions and 0 deletions

View File

@ -6,6 +6,7 @@ from __future__ import absolute_import, unicode_literals
import collections import collections
from importlib import import_module from importlib import import_module
from django.core import checks
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.text import capfirst from django.utils.text import capfirst
@ -223,6 +224,54 @@ class Block(six.with_metaclass(BaseBlock, object)):
""" """
return [] return []
def check(self, **kwargs):
"""
Hook for the Django system checks framework -
returns a list of django.core.checks.Error objects indicating validity errors in the block
"""
return []
def _check_name(self, **kwargs):
"""
Helper method called by container blocks as part of the system checks framework,
to validate that this block's name is a valid identifier.
(Not called universally, because not all blocks need names)
"""
errors = []
if not self.name:
errors.append(checks.Error(
"Block name %r is invalid" % self.name,
hint="Block name cannot be empty",
obj=kwargs.get('field', self),
id='wagtailcore.E001',
))
if ' ' in self.name:
errors.append(checks.Error(
"Block name %r is invalid" % self.name,
hint="Block names cannot contain spaces",
obj=kwargs.get('field', self),
id='wagtailcore.E001',
))
if '-' in self.name:
errors.append(checks.Error(
"Block name %r is invalid" % self.name,
"Block names cannot contain dashes",
obj=kwargs.get('field', self),
id='wagtailcore.E001',
))
if self.name and self.name[0].isdigit():
errors.append(checks.Error(
"Block name %r is invalid" % self.name,
"Block names cannot begin with a digit",
obj=kwargs.get('field', self),
id='wagtailcore.E001',
))
return errors
def deconstruct(self): def deconstruct(self):
# adapted from django.utils.deconstruct.deconstructible # adapted from django.utils.deconstruct.deconstructible
module_name = self.__module__ module_name = self.__module__

View File

@ -153,6 +153,11 @@ class ListBlock(Block):
return content return content
def check(self, **kwargs):
errors = super(ListBlock, self).check(**kwargs)
errors.extend(self.child_block.check(**kwargs))
return errors
DECONSTRUCT_ALIASES = { DECONSTRUCT_ALIASES = {
ListBlock: 'wagtail.wagtailcore.blocks.ListBlock', ListBlock: 'wagtail.wagtailcore.blocks.ListBlock',
} }

View File

@ -223,6 +223,14 @@ class BaseStreamBlock(Block):
kwargs = self._constructor_kwargs kwargs = self._constructor_kwargs
return (path, args, kwargs) return (path, args, kwargs)
def check(self, **kwargs):
errors = super(BaseStreamBlock, self).check(**kwargs)
for name, child_block in self.child_blocks.items():
errors.extend(child_block.check(**kwargs))
errors.extend(child_block._check_name(**kwargs))
return errors
class StreamBlock(six.with_metaclass(DeclarativeSubBlocksMetaclass, BaseStreamBlock)): class StreamBlock(six.with_metaclass(DeclarativeSubBlocksMetaclass, BaseStreamBlock)):
pass pass

View File

@ -150,6 +150,14 @@ class BaseStructBlock(Block):
kwargs = self._constructor_kwargs kwargs = self._constructor_kwargs
return (path, args, kwargs) return (path, args, kwargs)
def check(self, **kwargs):
errors = super(BaseStructBlock, self).check(**kwargs)
for name, child_block in self.child_blocks.items():
errors.extend(child_block.check(**kwargs))
errors.extend(child_block._check_name(**kwargs))
return errors
class StructBlock(six.with_metaclass(DeclarativeSubBlocksMetaclass, BaseStructBlock)): class StructBlock(six.with_metaclass(DeclarativeSubBlocksMetaclass, BaseStructBlock)):
pass pass

View File

@ -107,3 +107,8 @@ class StreamField(with_metaclass(models.SubfieldBase, models.Field)):
def get_searchable_content(self, value): def get_searchable_content(self, value):
return self.stream_block.get_searchable_content(value) return self.stream_block.get_searchable_content(value)
def check(self, **kwargs):
errors = super(StreamField, self).check(**kwargs)
errors.extend(self.stream_block.check(field=self, **kwargs))
return errors

View File

@ -1249,3 +1249,100 @@ class TestPageChooserBlock(TestCase):
self.assertEqual(nonrequired_block.clean(christmas_page), christmas_page) self.assertEqual(nonrequired_block.clean(christmas_page), christmas_page)
self.assertEqual(nonrequired_block.clean(None), None) self.assertEqual(nonrequired_block.clean(None), None)
class TestSystemCheck(TestCase):
def test_name_must_be_nonempty(self):
block = blocks.StreamBlock([
('heading', blocks.CharBlock()),
('', blocks.RichTextBlock()),
])
errors = block.check()
self.assertEqual(len(errors), 1)
self.assertEqual(errors[0].id, 'wagtailcore.E001')
self.assertEqual(errors[0].hint, "Block name cannot be empty")
def test_name_cannot_contain_spaces(self):
block = blocks.StreamBlock([
('heading', blocks.CharBlock()),
('rich text', blocks.RichTextBlock()),
])
errors = block.check()
self.assertEqual(len(errors), 1)
self.assertEqual(errors[0].id, 'wagtailcore.E001')
self.assertEqual(errors[0].hint, "Block names cannot contain spaces")
def test_name_cannot_contain_dashes(self):
block = blocks.StreamBlock([
('heading', blocks.CharBlock()),
('rich-text', blocks.RichTextBlock()),
])
errors = block.check()
self.assertEqual(len(errors), 1)
self.assertEqual(errors[0].id, 'wagtailcore.E001')
self.assertEqual(errors[0].hint, "Block names cannot contain dashes")
def test_name_cannot_begin_with_digit(self):
block = blocks.StreamBlock([
('heading', blocks.CharBlock()),
('99richtext', blocks.RichTextBlock()),
])
errors = block.check()
self.assertEqual(len(errors), 1)
self.assertEqual(errors[0].id, 'wagtailcore.E001')
self.assertEqual(errors[0].hint, "Block names cannot begin with a digit")
def test_system_checks_recurse_into_lists(self):
block = blocks.StreamBlock([
('paragraph_list', blocks.ListBlock(
blocks.StructBlock([
('heading', blocks.CharBlock()),
('rich text', blocks.RichTextBlock()),
])
))
])
errors = block.check()
self.assertEqual(len(errors), 1)
self.assertEqual(errors[0].id, 'wagtailcore.E001')
self.assertEqual(errors[0].hint, "Block names cannot contain spaces")
def test_system_checks_recurse_into_streams(self):
block = blocks.StreamBlock([
('carousel', blocks.StreamBlock([
('text', blocks.StructBlock([
('heading', blocks.CharBlock()),
('rich text', blocks.RichTextBlock()),
]))
]))
])
errors = block.check()
self.assertEqual(len(errors), 1)
self.assertEqual(errors[0].id, 'wagtailcore.E001')
self.assertEqual(errors[0].hint, "Block names cannot contain spaces")
def test_system_checks_recurse_into_structs(self):
block = blocks.StreamBlock([
('two_column', blocks.StructBlock([
('left', blocks.StructBlock([
('heading', blocks.CharBlock()),
('rich text', blocks.RichTextBlock()),
])),
('right', blocks.StructBlock([
('heading', blocks.CharBlock()),
('rich text', blocks.RichTextBlock()),
]))
]))
])
errors = block.check()
self.assertEqual(len(errors), 2)
self.assertEqual(errors[0].id, 'wagtailcore.E001')
self.assertEqual(errors[0].hint, "Block names cannot contain spaces")
self.assertEqual(errors[1].id, 'wagtailcore.E001')
self.assertEqual(errors[1].hint, "Block names cannot contain spaces")

View File

@ -1,8 +1,11 @@
import json import json
from django.test import TestCase from django.test import TestCase
from django.db import models
from wagtail.tests.testapp.models import StreamModel from wagtail.tests.testapp.models import StreamModel
from wagtail.wagtailcore import blocks
from wagtail.wagtailcore.fields import StreamField
from wagtail.wagtailimages.models import Image from wagtail.wagtailimages.models import Image
from wagtail.wagtailimages.tests.utils import get_test_image_file from wagtail.wagtailimages.tests.utils import get_test_image_file
@ -70,3 +73,15 @@ class TestLazyStreamField(TestCase):
with self.assertNumQueries(0): with self.assertNumQueries(0):
instances_lookup[self.no_image.pk].body[0] instances_lookup[self.no_image.pk].body[0]
def test_system_check_validates_block(self):
class InvalidStreamModel(models.Model):
body = StreamField([
('heading', blocks.CharBlock()),
('rich text', blocks.RichTextBlock()),
])
errors = InvalidStreamModel.check()
self.assertEqual(len(errors), 1)
self.assertEqual(errors[0].id, 'wagtailcore.E001')
self.assertEqual(errors[0].hint, "Block names cannot contain spaces")