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:
parent
c3c92ebb10
commit
0143787fab
@ -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__
|
||||||
|
@ -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',
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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")
|
||||||
|
@ -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")
|
||||||
|
Loading…
Reference in New Issue
Block a user