mirror of
https://github.com/Hopiu/wagtail.git
synced 2026-04-30 03:24:55 +00:00
2471 lines
93 KiB
Python
2471 lines
93 KiB
Python
# -*- coding: utf-8 -*
|
|
from __future__ import absolute_import, unicode_literals
|
|
|
|
import base64
|
|
import collections
|
|
import json
|
|
import unittest
|
|
from decimal import Decimal
|
|
|
|
# non-standard import name for ugettext_lazy, to prevent strings from being picked up for translation
|
|
import django
|
|
from django import forms
|
|
from django.core.exceptions import ValidationError
|
|
from django.forms.utils import ErrorList
|
|
from django.template.loader import render_to_string
|
|
from django.test import SimpleTestCase, TestCase
|
|
from django.utils.html import format_html
|
|
from django.utils.safestring import SafeData, mark_safe
|
|
from django.utils.translation import ugettext_lazy as __
|
|
|
|
from wagtail.tests.testapp.blocks import LinkBlock as CustomLinkBlock
|
|
from wagtail.tests.testapp.blocks import SectionBlock
|
|
from wagtail.tests.testapp.models import SimplePage
|
|
from wagtail.wagtailcore import blocks
|
|
from wagtail.wagtailcore.models import Page
|
|
from wagtail.wagtailcore.rich_text import RichText
|
|
|
|
|
|
class FooStreamBlock(blocks.StreamBlock):
|
|
text = blocks.CharBlock()
|
|
error = 'At least one block must say "foo"'
|
|
|
|
def clean(self, value):
|
|
value = super(FooStreamBlock, self).clean(value)
|
|
if not any(block.value == 'foo' for block in value):
|
|
raise blocks.StreamBlockValidationError(non_block_errors=ErrorList([self.error]))
|
|
return value
|
|
|
|
|
|
class TestFieldBlock(unittest.TestCase):
|
|
def test_charfield_render(self):
|
|
block = blocks.CharBlock()
|
|
html = block.render("Hello world!")
|
|
|
|
self.assertEqual(html, "Hello world!")
|
|
|
|
def test_charfield_render_with_template(self):
|
|
block = blocks.CharBlock(template='tests/blocks/heading_block.html')
|
|
html = block.render("Hello world!")
|
|
|
|
self.assertEqual(html, '<h1>Hello world!</h1>')
|
|
|
|
def test_charfield_render_with_template_with_extra_context(self):
|
|
block = blocks.CharBlock(template='tests/blocks/heading_block.html')
|
|
html = block.render("Bonjour le monde!", context={
|
|
'language': 'fr',
|
|
})
|
|
|
|
self.assertEqual(html, '<h1 lang="fr">Bonjour le monde!</h1>')
|
|
|
|
def test_charfield_render_form(self):
|
|
block = blocks.CharBlock()
|
|
html = block.render_form("Hello world!")
|
|
|
|
self.assertIn('<div class="field char_field widget-text_input">', html)
|
|
self.assertIn('<input id="" name="" placeholder="" type="text" value="Hello world!" />', html)
|
|
|
|
def test_charfield_render_form_with_prefix(self):
|
|
block = blocks.CharBlock()
|
|
html = block.render_form("Hello world!", prefix='foo')
|
|
|
|
self.assertIn('<input id="foo" name="foo" placeholder="" type="text" value="Hello world!" />', html)
|
|
|
|
def test_charfield_render_form_with_error(self):
|
|
block = blocks.CharBlock()
|
|
html = block.render_form(
|
|
"Hello world!",
|
|
errors=ErrorList([ValidationError("This field is required.")]))
|
|
|
|
self.assertIn('This field is required.', html)
|
|
|
|
def test_charfield_searchable_content(self):
|
|
block = blocks.CharBlock()
|
|
content = block.get_searchable_content("Hello world!")
|
|
|
|
self.assertEqual(content, ["Hello world!"])
|
|
|
|
def test_choicefield_render(self):
|
|
class ChoiceBlock(blocks.FieldBlock):
|
|
field = forms.ChoiceField(choices=(
|
|
('choice-1', "Choice 1"),
|
|
('choice-2', "Choice 2"),
|
|
))
|
|
|
|
block = ChoiceBlock()
|
|
html = block.render('choice-2')
|
|
|
|
self.assertEqual(html, "choice-2")
|
|
|
|
def test_choicefield_render_form(self):
|
|
class ChoiceBlock(blocks.FieldBlock):
|
|
field = forms.ChoiceField(choices=(
|
|
('choice-1', "Choice 1"),
|
|
('choice-2', "Choice 2"),
|
|
))
|
|
|
|
block = ChoiceBlock()
|
|
html = block.render_form('choice-2')
|
|
|
|
self.assertIn('<div class="field choice_field widget-select">', html)
|
|
self.assertIn('<select id="" name="" placeholder="">', html)
|
|
self.assertIn('<option value="choice-1">Choice 1</option>', html)
|
|
self.assertIn('<option value="choice-2" selected="selected">Choice 2</option>', html)
|
|
|
|
def test_searchable_content(self):
|
|
"""
|
|
FieldBlock should not return anything for `get_searchable_content` by
|
|
default. Subclasses are free to override it and provide relevant
|
|
content.
|
|
"""
|
|
class CustomBlock(blocks.FieldBlock):
|
|
field = forms.CharField(required=True)
|
|
block = CustomBlock()
|
|
self.assertEqual(block.get_searchable_content("foo bar"), [])
|
|
|
|
def test_form_handling_is_independent_of_serialisation(self):
|
|
class Base64EncodingCharBlock(blocks.CharBlock):
|
|
"""A CharBlock with a deliberately perverse JSON (de)serialisation format
|
|
so that it visibly blows up if we call to_python / get_prep_value where we shouldn't"""
|
|
|
|
def to_python(self, jsonish_value):
|
|
# decode as base64 on the way out of the JSON serialisation
|
|
return base64.b64decode(jsonish_value)
|
|
|
|
def get_prep_value(self, native_value):
|
|
# encode as base64 on the way into the JSON serialisation
|
|
return base64.b64encode(native_value)
|
|
|
|
block = Base64EncodingCharBlock()
|
|
form_html = block.render_form('hello world', 'title')
|
|
self.assertIn('value="hello world"', form_html)
|
|
|
|
value_from_form = block.value_from_datadict({'title': 'hello world'}, {}, 'title')
|
|
self.assertEqual('hello world', value_from_form)
|
|
|
|
def test_widget_media(self):
|
|
class CalendarWidget(forms.TextInput):
|
|
@property
|
|
def media(self):
|
|
return forms.Media(
|
|
css={'all': ('pretty.css',)},
|
|
js=('animations.js', 'actions.js')
|
|
)
|
|
|
|
class CalenderBlock(blocks.FieldBlock):
|
|
def __init__(self, required=True, help_text=None, max_length=None, min_length=None, **kwargs):
|
|
# Set widget to CalenderWidget
|
|
self.field = forms.CharField(
|
|
required=required,
|
|
help_text=help_text,
|
|
max_length=max_length,
|
|
min_length=min_length,
|
|
widget=CalendarWidget(),
|
|
)
|
|
super(blocks.FieldBlock, self).__init__(**kwargs)
|
|
|
|
block = CalenderBlock()
|
|
self.assertIn('pretty.css', ''.join(block.all_media().render_css()))
|
|
self.assertIn('animations.js', ''.join(block.all_media().render_js()))
|
|
|
|
|
|
class TestIntegerBlock(unittest.TestCase):
|
|
def test_type(self):
|
|
block = blocks.IntegerBlock()
|
|
digit = block.value_from_form(1234)
|
|
|
|
self.assertEqual(type(digit), int)
|
|
|
|
def test_render(self):
|
|
block = blocks.IntegerBlock()
|
|
digit = block.value_from_form(1234)
|
|
|
|
self.assertEqual(digit, 1234)
|
|
|
|
def test_render_required_error(self):
|
|
block = blocks.IntegerBlock()
|
|
|
|
with self.assertRaises(ValidationError):
|
|
block.clean("")
|
|
|
|
def test_render_max_value_validation(self):
|
|
block = blocks.IntegerBlock(max_value=20)
|
|
|
|
with self.assertRaises(ValidationError):
|
|
block.clean(25)
|
|
|
|
def test_render_min_value_validation(self):
|
|
block = blocks.IntegerBlock(min_value=20)
|
|
|
|
with self.assertRaises(ValidationError):
|
|
block.clean(10)
|
|
|
|
|
|
class TestEmailBlock(unittest.TestCase):
|
|
def test_render(self):
|
|
block = blocks.EmailBlock()
|
|
email = block.render("example@email.com")
|
|
|
|
self.assertEqual(email, "example@email.com")
|
|
|
|
def test_render_required_error(self):
|
|
block = blocks.EmailBlock()
|
|
|
|
with self.assertRaises(ValidationError):
|
|
block.clean("")
|
|
|
|
def test_format_validation(self):
|
|
block = blocks.EmailBlock()
|
|
|
|
with self.assertRaises(ValidationError):
|
|
block.clean("example.email.com")
|
|
|
|
|
|
class TestFloatBlock(TestCase):
|
|
def test_type(self):
|
|
block = blocks.FloatBlock()
|
|
block_val = block.value_from_form(float(1.63))
|
|
self.assertEqual(type(block_val), float)
|
|
|
|
def test_render(self):
|
|
block = blocks.FloatBlock()
|
|
test_val = float(1.63)
|
|
block_val = block.value_from_form(test_val)
|
|
self.assertEqual(block_val, test_val)
|
|
|
|
def test_raises_required_error(self):
|
|
block = blocks.FloatBlock()
|
|
|
|
with self.assertRaises(ValidationError):
|
|
block.clean("")
|
|
|
|
def test_raises_max_value_validation_error(self):
|
|
block = blocks.FloatBlock(max_value=20)
|
|
|
|
with self.assertRaises(ValidationError):
|
|
block.clean('20.01')
|
|
|
|
def test_raises_min_value_validation_error(self):
|
|
block = blocks.FloatBlock(min_value=20)
|
|
|
|
with self.assertRaises(ValidationError):
|
|
block.clean('19.99')
|
|
|
|
|
|
class TestDecimalBlock(TestCase):
|
|
def test_type(self):
|
|
block = blocks.DecimalBlock()
|
|
block_val = block.value_from_form(Decimal('1.63'))
|
|
self.assertEqual(type(block_val), Decimal)
|
|
|
|
def test_render(self):
|
|
block = blocks.DecimalBlock()
|
|
test_val = Decimal(1.63)
|
|
block_val = block.value_from_form(test_val)
|
|
|
|
self.assertEqual(block_val, test_val)
|
|
|
|
def test_raises_required_error(self):
|
|
block = blocks.DecimalBlock()
|
|
|
|
with self.assertRaises(ValidationError):
|
|
block.clean("")
|
|
|
|
def test_raises_max_value_validation_error(self):
|
|
block = blocks.DecimalBlock(max_value=20)
|
|
|
|
with self.assertRaises(ValidationError):
|
|
block.clean('20.01')
|
|
|
|
def test_raises_min_value_validation_error(self):
|
|
block = blocks.DecimalBlock(min_value=20)
|
|
|
|
with self.assertRaises(ValidationError):
|
|
block.clean('19.99')
|
|
|
|
|
|
class TestRegexBlock(TestCase):
|
|
|
|
def test_render(self):
|
|
block = blocks.RegexBlock(regex=r'^[0-9]{3}$')
|
|
test_val = '123'
|
|
block_val = block.value_from_form(test_val)
|
|
|
|
self.assertEqual(block_val, test_val)
|
|
|
|
def test_raises_required_error(self):
|
|
block = blocks.RegexBlock(regex=r'^[0-9]{3}$')
|
|
|
|
with self.assertRaises(ValidationError) as context:
|
|
block.clean("")
|
|
|
|
self.assertIn('This field is required.', context.exception.messages)
|
|
|
|
def test_raises_custom_required_error(self):
|
|
test_message = 'Oops, you missed a bit.'
|
|
block = blocks.RegexBlock(regex=r'^[0-9]{3}$', error_messages={
|
|
'required': test_message,
|
|
})
|
|
|
|
with self.assertRaises(ValidationError) as context:
|
|
block.clean("")
|
|
|
|
self.assertIn(test_message, context.exception.messages)
|
|
|
|
def test_raises_validation_error(self):
|
|
block = blocks.RegexBlock(regex=r'^[0-9]{3}$')
|
|
|
|
with self.assertRaises(ValidationError) as context:
|
|
block.clean("[/]")
|
|
|
|
self.assertIn('Enter a valid value.', context.exception.messages)
|
|
|
|
def test_raises_custom_error_message(self):
|
|
test_message = 'Not a valid library card number.'
|
|
block = blocks.RegexBlock(regex=r'^[0-9]{3}$', error_messages={
|
|
'invalid': test_message
|
|
})
|
|
|
|
with self.assertRaises(ValidationError) as context:
|
|
block.clean("[/]")
|
|
|
|
self.assertIn(test_message, context.exception.messages)
|
|
|
|
html = block.render_form(
|
|
"[/]",
|
|
errors=ErrorList([ValidationError(test_message)]))
|
|
|
|
self.assertIn(test_message, html)
|
|
|
|
|
|
class TestRichTextBlock(TestCase):
|
|
fixtures = ['test.json']
|
|
|
|
def test_get_default_with_fallback_value(self):
|
|
default_value = blocks.RichTextBlock().get_default()
|
|
self.assertIsInstance(default_value, RichText)
|
|
self.assertEqual(default_value.source, '')
|
|
|
|
def test_get_default_with_default_none(self):
|
|
default_value = blocks.RichTextBlock(default=None).get_default()
|
|
self.assertIsInstance(default_value, RichText)
|
|
self.assertEqual(default_value.source, '')
|
|
|
|
def test_get_default_with_empty_string(self):
|
|
default_value = blocks.RichTextBlock(default='').get_default()
|
|
self.assertIsInstance(default_value, RichText)
|
|
self.assertEqual(default_value.source, '')
|
|
|
|
def test_get_default_with_nonempty_string(self):
|
|
default_value = blocks.RichTextBlock(default='<p>foo</p>').get_default()
|
|
self.assertIsInstance(default_value, RichText)
|
|
self.assertEqual(default_value.source, '<p>foo</p>')
|
|
|
|
def test_get_default_with_richtext_value(self):
|
|
default_value = blocks.RichTextBlock(default=RichText('<p>foo</p>')).get_default()
|
|
self.assertIsInstance(default_value, RichText)
|
|
self.assertEqual(default_value.source, '<p>foo</p>')
|
|
|
|
def test_render(self):
|
|
block = blocks.RichTextBlock()
|
|
value = RichText('<p>Merry <a linktype="page" id="4">Christmas</a>!</p>')
|
|
result = block.render(value)
|
|
self.assertEqual(
|
|
result, '<div class="rich-text"><p>Merry <a href="/events/christmas/">Christmas</a>!</p></div>'
|
|
)
|
|
|
|
def test_render_form(self):
|
|
"""
|
|
render_form should produce the editor-specific rendition of the rich text value
|
|
(which includes e.g. 'data-linktype' attributes on <a> elements)
|
|
"""
|
|
block = blocks.RichTextBlock()
|
|
value = RichText('<p>Merry <a linktype="page" id="4">Christmas</a>!</p>')
|
|
result = block.render_form(value, prefix='richtext')
|
|
self.assertIn(
|
|
(
|
|
'<p>Merry <a data-linktype="page" data-id="4"'
|
|
' data-parent-id="3" href="/events/christmas/">Christmas</a>!</p>'
|
|
),
|
|
result
|
|
)
|
|
|
|
def test_validate_required_richtext_block(self):
|
|
block = blocks.RichTextBlock()
|
|
|
|
with self.assertRaises(ValidationError):
|
|
block.clean(RichText(''))
|
|
|
|
def test_validate_non_required_richtext_block(self):
|
|
block = blocks.RichTextBlock(required=False)
|
|
result = block.clean(RichText(''))
|
|
self.assertIsInstance(result, RichText)
|
|
self.assertEqual(result.source, '')
|
|
|
|
|
|
class TestChoiceBlock(unittest.TestCase):
|
|
def setUp(self):
|
|
from django.db.models.fields import BLANK_CHOICE_DASH
|
|
self.blank_choice_dash_label = BLANK_CHOICE_DASH[0][1]
|
|
|
|
def test_render_required_choice_block(self):
|
|
block = blocks.ChoiceBlock(choices=[('tea', 'Tea'), ('coffee', 'Coffee')])
|
|
html = block.render_form('coffee', prefix='beverage')
|
|
self.assertIn('<select id="beverage" name="beverage" placeholder="">', html)
|
|
# blank option should still be rendered for required fields
|
|
# (we may want it as an initial value)
|
|
self.assertIn('<option value="">%s</option>' % self.blank_choice_dash_label, html)
|
|
self.assertIn('<option value="tea">Tea</option>', html)
|
|
self.assertIn('<option value="coffee" selected="selected">Coffee</option>', html)
|
|
|
|
def test_render_required_choice_block_with_callable_choices(self):
|
|
def callable_choices():
|
|
return [('tea', 'Tea'), ('coffee', 'Coffee')]
|
|
|
|
block = blocks.ChoiceBlock(choices=callable_choices)
|
|
html = block.render_form('coffee', prefix='beverage')
|
|
self.assertIn('<select id="beverage" name="beverage" placeholder="">', html)
|
|
# blank option should still be rendered for required fields
|
|
# (we may want it as an initial value)
|
|
self.assertIn('<option value="">%s</option>' % self.blank_choice_dash_label, html)
|
|
self.assertIn('<option value="tea">Tea</option>', html)
|
|
self.assertIn('<option value="coffee" selected="selected">Coffee</option>', html)
|
|
|
|
def test_validate_required_choice_block(self):
|
|
block = blocks.ChoiceBlock(choices=[('tea', 'Tea'), ('coffee', 'Coffee')])
|
|
self.assertEqual(block.clean('coffee'), 'coffee')
|
|
|
|
with self.assertRaises(ValidationError):
|
|
block.clean('whisky')
|
|
|
|
with self.assertRaises(ValidationError):
|
|
block.clean('')
|
|
|
|
with self.assertRaises(ValidationError):
|
|
block.clean(None)
|
|
|
|
def test_render_non_required_choice_block(self):
|
|
block = blocks.ChoiceBlock(choices=[('tea', 'Tea'), ('coffee', 'Coffee')], required=False)
|
|
html = block.render_form('coffee', prefix='beverage')
|
|
self.assertIn('<select id="beverage" name="beverage" placeholder="">', html)
|
|
self.assertIn('<option value="">%s</option>' % self.blank_choice_dash_label, html)
|
|
self.assertIn('<option value="tea">Tea</option>', html)
|
|
self.assertIn('<option value="coffee" selected="selected">Coffee</option>', html)
|
|
|
|
def test_render_non_required_choice_block_with_callable_choices(self):
|
|
def callable_choices():
|
|
return [('tea', 'Tea'), ('coffee', 'Coffee')]
|
|
|
|
block = blocks.ChoiceBlock(choices=callable_choices, required=False)
|
|
html = block.render_form('coffee', prefix='beverage')
|
|
self.assertIn('<select id="beverage" name="beverage" placeholder="">', html)
|
|
self.assertIn('<option value="">%s</option>' % self.blank_choice_dash_label, html)
|
|
self.assertIn('<option value="tea">Tea</option>', html)
|
|
self.assertIn('<option value="coffee" selected="selected">Coffee</option>', 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_choice_block_with_existing_blank_choice(self):
|
|
block = blocks.ChoiceBlock(
|
|
choices=[('tea', 'Tea'), ('coffee', 'Coffee'), ('', 'No thanks')],
|
|
required=False)
|
|
html = block.render_form(None, prefix='beverage')
|
|
self.assertIn('<select id="beverage" name="beverage" placeholder="">', html)
|
|
self.assertNotIn('<option value="">%s</option>' % self.blank_choice_dash_label, html)
|
|
self.assertIn('<option value="" selected="selected">No thanks</option>', html)
|
|
self.assertIn('<option value="tea">Tea</option>', html)
|
|
self.assertIn('<option value="coffee">Coffee</option>', html)
|
|
|
|
def test_render_choice_block_with_existing_blank_choice_and_with_callable_choices(self):
|
|
def callable_choices():
|
|
return [('tea', 'Tea'), ('coffee', 'Coffee'), ('', 'No thanks')]
|
|
|
|
block = blocks.ChoiceBlock(
|
|
choices=callable_choices,
|
|
required=False)
|
|
html = block.render_form(None, prefix='beverage')
|
|
self.assertIn('<select id="beverage" name="beverage" placeholder="">', html)
|
|
self.assertNotIn('<option value="">%s</option>' % self.blank_choice_dash_label, html)
|
|
self.assertIn('<option value="" selected="selected">No thanks</option>', html)
|
|
self.assertIn('<option value="tea">Tea</option>', html)
|
|
self.assertIn('<option value="coffee">Coffee</option>', 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('<select id="beverage" name="beverage" placeholder="">', html)
|
|
self.assertIn('<option value="" selected="selected">%s</option>' % self.blank_choice_dash_label, html)
|
|
self.assertIn('<optgroup label="Alcoholic">', html)
|
|
self.assertIn('<option value="tea">Tea</option>', html)
|
|
|
|
# test rendering with a non-blank option selected
|
|
html = block.render_form('tea', prefix='beverage')
|
|
self.assertIn('<select id="beverage" name="beverage" placeholder="">', html)
|
|
self.assertIn('<option value="">%s</option>' % self.blank_choice_dash_label, html)
|
|
self.assertIn('<optgroup label="Alcoholic">', html)
|
|
self.assertIn('<option value="tea" selected="selected">Tea</option>', 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('<select id="beverage" name="beverage" placeholder="">', html)
|
|
self.assertNotIn('<option value="">%s</option>' % self.blank_choice_dash_label, html)
|
|
self.assertNotIn('<option value="" selected="selected">%s</option>' % self.blank_choice_dash_label, html)
|
|
self.assertIn('<optgroup label="Alcoholic">', html)
|
|
self.assertIn('<option value="tea">Tea</option>', html)
|
|
self.assertIn('<option value="" selected="selected">No thanks</option>', html)
|
|
|
|
# test rendering with a non-blank option selected
|
|
html = block.render_form('tea', prefix='beverage')
|
|
self.assertIn('<select id="beverage" name="beverage" placeholder="">', html)
|
|
self.assertNotIn('<option value="">%s</option>' % self.blank_choice_dash_label, html)
|
|
self.assertNotIn('<option value="" selected="selected">%s</option>' % self.blank_choice_dash_label, html)
|
|
self.assertIn('<optgroup label="Alcoholic">', html)
|
|
self.assertIn('<option value="tea" selected="selected">Tea</option>', html)
|
|
|
|
def test_subclassing(self):
|
|
class BeverageChoiceBlock(blocks.ChoiceBlock):
|
|
choices = [
|
|
('tea', 'Tea'),
|
|
('coffee', 'Coffee'),
|
|
]
|
|
|
|
block = BeverageChoiceBlock(required=False)
|
|
html = block.render_form('tea', prefix='beverage')
|
|
self.assertIn('<select id="beverage" name="beverage" placeholder="">', html)
|
|
self.assertIn('<option value="tea" selected="selected">Tea</option>', 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,
|
|
},
|
|
)
|
|
)
|
|
|
|
def test_searchable_content(self):
|
|
block = blocks.ChoiceBlock(choices=[
|
|
('choice-1', "Choice 1"),
|
|
('choice-2', "Choice 2"),
|
|
])
|
|
self.assertEqual(block.get_searchable_content("choice-1"),
|
|
["Choice 1"])
|
|
|
|
def test_searchable_content_with_callable_choices(self):
|
|
def callable_choices():
|
|
return [
|
|
('choice-1', "Choice 1"),
|
|
('choice-2', "Choice 2"),
|
|
]
|
|
|
|
block = blocks.ChoiceBlock(choices=callable_choices)
|
|
self.assertEqual(block.get_searchable_content("choice-1"),
|
|
["Choice 1"])
|
|
|
|
def test_optgroup_searchable_content(self):
|
|
block = blocks.ChoiceBlock(choices=[
|
|
('Section 1', [
|
|
('1-1', "Block 1"),
|
|
('1-2', "Block 2"),
|
|
]),
|
|
('Section 2', [
|
|
('2-1', "Block 1"),
|
|
('2-2', "Block 2"),
|
|
]),
|
|
])
|
|
self.assertEqual(block.get_searchable_content("2-2"),
|
|
["Section 2", "Block 2"])
|
|
|
|
def test_invalid_searchable_content(self):
|
|
block = blocks.ChoiceBlock(choices=[
|
|
('one', 'One'),
|
|
('two', 'Two'),
|
|
])
|
|
self.assertEqual(block.get_searchable_content('three'), [])
|
|
|
|
def test_searchable_content_with_lazy_translation(self):
|
|
block = blocks.ChoiceBlock(choices=[
|
|
('choice-1', __("Choice 1")),
|
|
('choice-2', __("Choice 2")),
|
|
])
|
|
result = block.get_searchable_content("choice-1")
|
|
# result must survive JSON (de)serialisation, which is not the case for
|
|
# lazy translation objects
|
|
result = json.loads(json.dumps(result))
|
|
self.assertEqual(result, ["Choice 1"])
|
|
|
|
def test_optgroup_searchable_content_with_lazy_translation(self):
|
|
block = blocks.ChoiceBlock(choices=[
|
|
(__('Section 1'), [
|
|
('1-1', __("Block 1")),
|
|
('1-2', __("Block 2")),
|
|
]),
|
|
(__('Section 2'), [
|
|
('2-1', __("Block 1")),
|
|
('2-2', __("Block 2")),
|
|
]),
|
|
])
|
|
result = block.get_searchable_content("2-2")
|
|
# result must survive JSON (de)serialisation, which is not the case for
|
|
# lazy translation objects
|
|
result = json.loads(json.dumps(result))
|
|
self.assertEqual(result, ["Section 2", "Block 2"])
|
|
|
|
def test_deconstruct_with_callable_choices(self):
|
|
def callable_choices():
|
|
return [
|
|
('tea', 'Tea'),
|
|
('coffee', 'Coffee'),
|
|
]
|
|
|
|
block = blocks.ChoiceBlock(choices=callable_choices, required=False)
|
|
html = block.render_form('tea', prefix='beverage')
|
|
self.assertIn('<select id="beverage" name="beverage" placeholder="">', html)
|
|
self.assertIn('<option value="tea" selected="selected">Tea</option>', html)
|
|
|
|
self.assertEqual(
|
|
block.deconstruct(),
|
|
(
|
|
'wagtail.wagtailcore.blocks.ChoiceBlock',
|
|
[],
|
|
{
|
|
'choices': callable_choices,
|
|
'required': False,
|
|
},
|
|
)
|
|
)
|
|
|
|
|
|
class TestRawHTMLBlock(unittest.TestCase):
|
|
def test_get_default_with_fallback_value(self):
|
|
default_value = blocks.RawHTMLBlock().get_default()
|
|
self.assertEqual(default_value, '')
|
|
self.assertIsInstance(default_value, SafeData)
|
|
|
|
def test_get_default_with_none(self):
|
|
default_value = blocks.RawHTMLBlock(default=None).get_default()
|
|
self.assertEqual(default_value, '')
|
|
self.assertIsInstance(default_value, SafeData)
|
|
|
|
def test_get_default_with_empty_string(self):
|
|
default_value = blocks.RawHTMLBlock(default='').get_default()
|
|
self.assertEqual(default_value, '')
|
|
self.assertIsInstance(default_value, SafeData)
|
|
|
|
def test_get_default_with_nonempty_string(self):
|
|
default_value = blocks.RawHTMLBlock(default='<blink>BÖÖM</blink>').get_default()
|
|
self.assertEqual(default_value, '<blink>BÖÖM</blink>')
|
|
self.assertIsInstance(default_value, SafeData)
|
|
|
|
def test_serialize(self):
|
|
block = blocks.RawHTMLBlock()
|
|
result = block.get_prep_value(mark_safe('<blink>BÖÖM</blink>'))
|
|
self.assertEqual(result, '<blink>BÖÖM</blink>')
|
|
self.assertNotIsInstance(result, SafeData)
|
|
|
|
def test_deserialize(self):
|
|
block = blocks.RawHTMLBlock()
|
|
result = block.to_python('<blink>BÖÖM</blink>')
|
|
self.assertEqual(result, '<blink>BÖÖM</blink>')
|
|
self.assertIsInstance(result, SafeData)
|
|
|
|
def test_render(self):
|
|
block = blocks.RawHTMLBlock()
|
|
result = block.render(mark_safe('<blink>BÖÖM</blink>'))
|
|
self.assertEqual(result, '<blink>BÖÖM</blink>')
|
|
self.assertIsInstance(result, SafeData)
|
|
|
|
def test_render_form(self):
|
|
block = blocks.RawHTMLBlock()
|
|
result = block.render_form(mark_safe('<blink>BÖÖM</blink>'), prefix='rawhtml')
|
|
self.assertIn('<textarea ', result)
|
|
self.assertIn('name="rawhtml"', result)
|
|
self.assertIn('<blink>BÖÖM</blink>', result)
|
|
|
|
def test_form_response(self):
|
|
block = blocks.RawHTMLBlock()
|
|
result = block.value_from_datadict({'rawhtml': '<blink>BÖÖM</blink>'}, {}, prefix='rawhtml')
|
|
self.assertEqual(result, '<blink>BÖÖM</blink>')
|
|
self.assertIsInstance(result, SafeData)
|
|
|
|
@unittest.skipIf(django.VERSION < (1, 10, 2), "value_omitted_from_data is not available")
|
|
def test_value_omitted_from_data(self):
|
|
block = blocks.RawHTMLBlock()
|
|
self.assertFalse(block.value_omitted_from_data({'rawhtml': 'ohai'}, {}, 'rawhtml'))
|
|
self.assertFalse(block.value_omitted_from_data({'rawhtml': ''}, {}, 'rawhtml'))
|
|
self.assertTrue(block.value_omitted_from_data({'nothing-here': 'nope'}, {}, 'rawhtml'))
|
|
|
|
def test_clean_required_field(self):
|
|
block = blocks.RawHTMLBlock()
|
|
result = block.clean(mark_safe('<blink>BÖÖM</blink>'))
|
|
self.assertEqual(result, '<blink>BÖÖM</blink>')
|
|
self.assertIsInstance(result, SafeData)
|
|
|
|
with self.assertRaises(ValidationError):
|
|
block.clean(mark_safe(''))
|
|
|
|
def test_clean_nonrequired_field(self):
|
|
block = blocks.RawHTMLBlock(required=False)
|
|
result = block.clean(mark_safe('<blink>BÖÖM</blink>'))
|
|
self.assertEqual(result, '<blink>BÖÖM</blink>')
|
|
self.assertIsInstance(result, SafeData)
|
|
|
|
result = block.clean(mark_safe(''))
|
|
self.assertEqual(result, '')
|
|
self.assertIsInstance(result, SafeData)
|
|
|
|
|
|
class TestMeta(unittest.TestCase):
|
|
def test_set_template_with_meta(self):
|
|
class HeadingBlock(blocks.CharBlock):
|
|
class Meta:
|
|
template = 'heading.html'
|
|
|
|
block = HeadingBlock()
|
|
self.assertEqual(block.meta.template, 'heading.html')
|
|
|
|
def test_set_template_with_constructor(self):
|
|
block = blocks.CharBlock(template='heading.html')
|
|
self.assertEqual(block.meta.template, 'heading.html')
|
|
|
|
def test_set_template_with_constructor_overrides_meta(self):
|
|
class HeadingBlock(blocks.CharBlock):
|
|
class Meta:
|
|
template = 'heading.html'
|
|
|
|
block = HeadingBlock(template='subheading.html')
|
|
self.assertEqual(block.meta.template, 'subheading.html')
|
|
|
|
def test_meta_nested_inheritance(self):
|
|
"""
|
|
Check that having a multi-level inheritance chain works
|
|
"""
|
|
class HeadingBlock(blocks.CharBlock):
|
|
class Meta:
|
|
template = 'heading.html'
|
|
test = 'Foo'
|
|
|
|
class SubHeadingBlock(HeadingBlock):
|
|
class Meta:
|
|
template = 'subheading.html'
|
|
|
|
block = SubHeadingBlock()
|
|
self.assertEqual(block.meta.template, 'subheading.html')
|
|
self.assertEqual(block.meta.test, 'Foo')
|
|
|
|
def test_meta_multi_inheritance(self):
|
|
"""
|
|
Check that multi-inheritance and Meta classes work together
|
|
"""
|
|
class LeftBlock(blocks.CharBlock):
|
|
class Meta:
|
|
template = 'template.html'
|
|
clash = 'the band'
|
|
label = 'Left block'
|
|
|
|
class RightBlock(blocks.CharBlock):
|
|
class Meta:
|
|
default = 'hello'
|
|
clash = 'the album'
|
|
label = 'Right block'
|
|
|
|
class ChildBlock(LeftBlock, RightBlock):
|
|
class Meta:
|
|
label = 'Child block'
|
|
|
|
block = ChildBlock()
|
|
# These should be directly inherited from the LeftBlock/RightBlock
|
|
self.assertEqual(block.meta.template, 'template.html')
|
|
self.assertEqual(block.meta.default, 'hello')
|
|
|
|
# This should be inherited from the LeftBlock, solving the collision,
|
|
# as LeftBlock comes first
|
|
self.assertEqual(block.meta.clash, 'the band')
|
|
|
|
# This should come from ChildBlock itself, ignoring the label on
|
|
# LeftBlock/RightBlock
|
|
self.assertEqual(block.meta.label, 'Child block')
|
|
|
|
|
|
class TestStructBlock(SimpleTestCase):
|
|
def test_initialisation(self):
|
|
block = blocks.StructBlock([
|
|
('title', blocks.CharBlock()),
|
|
('link', blocks.URLBlock()),
|
|
])
|
|
|
|
self.assertEqual(list(block.child_blocks.keys()), ['title', 'link'])
|
|
|
|
def test_initialisation_from_subclass(self):
|
|
class LinkBlock(blocks.StructBlock):
|
|
title = blocks.CharBlock()
|
|
link = blocks.URLBlock()
|
|
|
|
block = LinkBlock()
|
|
|
|
self.assertEqual(list(block.child_blocks.keys()), ['title', 'link'])
|
|
|
|
def test_initialisation_from_subclass_with_extra(self):
|
|
class LinkBlock(blocks.StructBlock):
|
|
title = blocks.CharBlock()
|
|
link = blocks.URLBlock()
|
|
|
|
block = LinkBlock([
|
|
('classname', blocks.CharBlock())
|
|
])
|
|
|
|
self.assertEqual(list(block.child_blocks.keys()), ['title', 'link', 'classname'])
|
|
|
|
def test_initialisation_with_multiple_subclassses(self):
|
|
class LinkBlock(blocks.StructBlock):
|
|
title = blocks.CharBlock()
|
|
link = blocks.URLBlock()
|
|
|
|
class StyledLinkBlock(LinkBlock):
|
|
classname = blocks.CharBlock()
|
|
|
|
block = StyledLinkBlock()
|
|
|
|
self.assertEqual(list(block.child_blocks.keys()), ['title', 'link', 'classname'])
|
|
|
|
def test_initialisation_with_mixins(self):
|
|
"""
|
|
The order of fields of classes with multiple parent classes is slightly
|
|
surprising at first. Child fields are inherited in a bottom-up order,
|
|
by traversing the MRO in reverse. In the example below,
|
|
``StyledLinkBlock`` will have an MRO of::
|
|
|
|
[StyledLinkBlock, StylingMixin, LinkBlock, StructBlock, ...]
|
|
|
|
This will result in ``classname`` appearing *after* ``title`` and
|
|
``link`` in ``StyleLinkBlock`.child_blocks`, even though
|
|
``StylingMixin`` appeared before ``LinkBlock``.
|
|
"""
|
|
class LinkBlock(blocks.StructBlock):
|
|
title = blocks.CharBlock()
|
|
link = blocks.URLBlock()
|
|
|
|
class StylingMixin(blocks.StructBlock):
|
|
classname = blocks.CharBlock()
|
|
|
|
class StyledLinkBlock(StylingMixin, LinkBlock):
|
|
source = blocks.CharBlock()
|
|
|
|
block = StyledLinkBlock()
|
|
|
|
self.assertEqual(list(block.child_blocks.keys()),
|
|
['title', 'link', 'classname', 'source'])
|
|
|
|
def test_render(self):
|
|
class LinkBlock(blocks.StructBlock):
|
|
title = blocks.CharBlock()
|
|
link = blocks.URLBlock()
|
|
|
|
block = LinkBlock()
|
|
html = block.render(block.to_python({
|
|
'title': "Wagtail site",
|
|
'link': 'http://www.wagtail.io',
|
|
}))
|
|
expected_html = '\n'.join([
|
|
'<dl>',
|
|
'<dt>title</dt>',
|
|
'<dd>Wagtail site</dd>',
|
|
'<dt>link</dt>',
|
|
'<dd>http://www.wagtail.io</dd>',
|
|
'</dl>',
|
|
])
|
|
|
|
self.assertHTMLEqual(html, expected_html)
|
|
|
|
def test_render_unknown_field(self):
|
|
class LinkBlock(blocks.StructBlock):
|
|
title = blocks.CharBlock()
|
|
link = blocks.URLBlock()
|
|
|
|
block = LinkBlock()
|
|
html = block.render(block.to_python({
|
|
'title': "Wagtail site",
|
|
'link': 'http://www.wagtail.io',
|
|
'image': 10,
|
|
}))
|
|
|
|
self.assertIn('<dt>title</dt>', html)
|
|
self.assertIn('<dd>Wagtail site</dd>', html)
|
|
self.assertIn('<dt>link</dt>', html)
|
|
self.assertIn('<dd>http://www.wagtail.io</dd>', html)
|
|
|
|
# Don't render the extra item
|
|
self.assertNotIn('<dt>image</dt>', html)
|
|
|
|
def test_render_bound_block(self):
|
|
# the string representation of a bound block should be the value as rendered by
|
|
# the associated block
|
|
class SectionBlock(blocks.StructBlock):
|
|
title = blocks.CharBlock()
|
|
body = blocks.RichTextBlock()
|
|
|
|
block = SectionBlock()
|
|
struct_value = block.to_python({
|
|
'title': 'hello',
|
|
'body': '<b>world</b>',
|
|
})
|
|
body_bound_block = struct_value.bound_blocks['body']
|
|
expected = '<div class="rich-text"><b>world</b></div>'
|
|
self.assertEqual(str(body_bound_block), expected)
|
|
|
|
def test_get_form_context(self):
|
|
class LinkBlock(blocks.StructBlock):
|
|
title = blocks.CharBlock()
|
|
link = blocks.URLBlock()
|
|
|
|
block = LinkBlock()
|
|
context = block.get_form_context(block.to_python({
|
|
'title': "Wagtail site",
|
|
'link': 'http://www.wagtail.io',
|
|
}), prefix='mylink')
|
|
|
|
self.assertTrue(isinstance(context['children'], collections.OrderedDict))
|
|
self.assertEqual(len(context['children']), 2)
|
|
self.assertTrue(isinstance(context['children']['title'], blocks.BoundBlock))
|
|
self.assertEqual(context['children']['title'].value, "Wagtail site")
|
|
self.assertTrue(isinstance(context['children']['link'], blocks.BoundBlock))
|
|
self.assertEqual(context['children']['link'].value, 'http://www.wagtail.io')
|
|
self.assertEqual(context['block_definition'], block)
|
|
self.assertEqual(context['prefix'], 'mylink')
|
|
|
|
def test_render_form(self):
|
|
class LinkBlock(blocks.StructBlock):
|
|
title = blocks.CharBlock(required=False)
|
|
link = blocks.URLBlock(required=False)
|
|
|
|
block = LinkBlock()
|
|
html = block.render_form(block.to_python({
|
|
'title': "Wagtail site",
|
|
'link': 'http://www.wagtail.io',
|
|
}), prefix='mylink')
|
|
|
|
self.assertIn('<div class="struct-block">', html)
|
|
self.assertIn('<div class="field char_field widget-text_input fieldname-title">', html)
|
|
self.assertIn('<label for="mylink-title">Title:</label>', html)
|
|
self.assertIn(
|
|
'<input id="mylink-title" name="mylink-title" placeholder="Title" type="text" value="Wagtail site" />', html
|
|
)
|
|
self.assertIn('<div class="field url_field widget-url_input fieldname-link">', html)
|
|
self.assertIn(
|
|
(
|
|
'<input id="mylink-link" name="mylink-link" placeholder="Link"'
|
|
' type="url" value="http://www.wagtail.io" />'
|
|
),
|
|
html
|
|
)
|
|
self.assertNotIn('<li class="required">', html)
|
|
|
|
def test_render_required_field_indicator(self):
|
|
class LinkBlock(blocks.StructBlock):
|
|
title = blocks.CharBlock()
|
|
link = blocks.URLBlock(required=True)
|
|
|
|
block = LinkBlock()
|
|
html = block.render_form(block.to_python({
|
|
'title': "Wagtail site",
|
|
'link': 'http://www.wagtail.io',
|
|
}), prefix='mylink')
|
|
|
|
self.assertIn('<li class="required">', html)
|
|
|
|
def test_render_form_unknown_field(self):
|
|
class LinkBlock(blocks.StructBlock):
|
|
title = blocks.CharBlock()
|
|
link = blocks.URLBlock()
|
|
|
|
block = LinkBlock()
|
|
html = block.render_form(block.to_python({
|
|
'title': "Wagtail site",
|
|
'link': 'http://www.wagtail.io',
|
|
'image': 10,
|
|
}), prefix='mylink')
|
|
|
|
self.assertIn(
|
|
(
|
|
'<input id="mylink-title" name="mylink-title" placeholder="Title"'
|
|
' type="text" value="Wagtail site" />'
|
|
),
|
|
html
|
|
)
|
|
self.assertIn(
|
|
(
|
|
'<input id="mylink-link" name="mylink-link" placeholder="Link" type="url"'
|
|
' value="http://www.wagtail.io" />'
|
|
),
|
|
html
|
|
)
|
|
|
|
# Don't render the extra field
|
|
self.assertNotIn('mylink-image', html)
|
|
|
|
def test_render_form_uses_default_value(self):
|
|
class LinkBlock(blocks.StructBlock):
|
|
title = blocks.CharBlock(default="Torchbox")
|
|
link = blocks.URLBlock(default="http://www.torchbox.com")
|
|
|
|
block = LinkBlock()
|
|
html = block.render_form(block.to_python({}), prefix='mylink')
|
|
|
|
self.assertIn(
|
|
'<input id="mylink-title" name="mylink-title" placeholder="Title" type="text" value="Torchbox" />', html
|
|
)
|
|
self.assertIn(
|
|
(
|
|
'<input id="mylink-link" name="mylink-link" placeholder="Link"'
|
|
' type="url" value="http://www.torchbox.com" />'
|
|
),
|
|
html
|
|
)
|
|
|
|
def test_render_form_with_help_text(self):
|
|
class LinkBlock(blocks.StructBlock):
|
|
title = blocks.CharBlock()
|
|
link = blocks.URLBlock()
|
|
|
|
class Meta:
|
|
help_text = "Self-promotion is encouraged"
|
|
|
|
block = LinkBlock()
|
|
html = block.render_form(block.to_python({
|
|
'title': "Wagtail site",
|
|
'link': 'http://www.wagtail.io',
|
|
}), prefix='mylink')
|
|
|
|
self.assertIn('<div class="object-help help">Self-promotion is encouraged</div>', html)
|
|
|
|
# check it can be overridden in the block constructor
|
|
block = LinkBlock(help_text="Self-promotion is discouraged")
|
|
html = block.render_form(block.to_python({
|
|
'title': "Wagtail site",
|
|
'link': 'http://www.wagtail.io',
|
|
}), prefix='mylink')
|
|
|
|
self.assertIn('<div class="object-help help">Self-promotion is discouraged</div>', html)
|
|
|
|
def test_media_inheritance(self):
|
|
class ScriptedCharBlock(blocks.CharBlock):
|
|
media = forms.Media(js=['scripted_char_block.js'])
|
|
|
|
class LinkBlock(blocks.StructBlock):
|
|
title = ScriptedCharBlock(default="Torchbox")
|
|
link = blocks.URLBlock(default="http://www.torchbox.com")
|
|
|
|
block = LinkBlock()
|
|
self.assertIn('scripted_char_block.js', ''.join(block.all_media().render_js()))
|
|
|
|
def test_html_declaration_inheritance(self):
|
|
class CharBlockWithDeclarations(blocks.CharBlock):
|
|
def html_declarations(self):
|
|
return '<script type="text/x-html-template">hello world</script>'
|
|
|
|
class LinkBlock(blocks.StructBlock):
|
|
title = CharBlockWithDeclarations(default="Torchbox")
|
|
link = blocks.URLBlock(default="http://www.torchbox.com")
|
|
|
|
block = LinkBlock()
|
|
self.assertIn('<script type="text/x-html-template">hello world</script>', block.all_html_declarations())
|
|
|
|
def test_searchable_content(self):
|
|
class LinkBlock(blocks.StructBlock):
|
|
title = blocks.CharBlock()
|
|
link = blocks.URLBlock()
|
|
|
|
block = LinkBlock()
|
|
content = block.get_searchable_content(block.to_python({
|
|
'title': "Wagtail site",
|
|
'link': 'http://www.wagtail.io',
|
|
}))
|
|
|
|
self.assertEqual(content, ["Wagtail site"])
|
|
|
|
def test_value_from_datadict(self):
|
|
block = blocks.StructBlock([
|
|
('title', blocks.CharBlock()),
|
|
('link', blocks.URLBlock()),
|
|
])
|
|
|
|
struct_val = block.value_from_datadict({
|
|
'mylink-title': "Torchbox",
|
|
'mylink-link': "http://www.torchbox.com"
|
|
}, {}, 'mylink')
|
|
|
|
self.assertEqual(struct_val['title'], "Torchbox")
|
|
self.assertEqual(struct_val['link'], "http://www.torchbox.com")
|
|
self.assertTrue(isinstance(struct_val, blocks.StructValue))
|
|
self.assertTrue(isinstance(struct_val.bound_blocks['link'].block, blocks.URLBlock))
|
|
|
|
@unittest.skipIf(django.VERSION < (1, 10, 2), "value_omitted_from_data is not available")
|
|
def test_value_omitted_from_data(self):
|
|
block = blocks.StructBlock([
|
|
('title', blocks.CharBlock()),
|
|
('link', blocks.URLBlock()),
|
|
])
|
|
|
|
# overall value is considered present in the form if any sub-field is present
|
|
self.assertFalse(block.value_omitted_from_data({'mylink-title': 'Torchbox'}, {}, 'mylink'))
|
|
self.assertTrue(block.value_omitted_from_data({'nothing-here': 'nope'}, {}, 'mylink'))
|
|
|
|
def test_default_is_returned_as_structvalue(self):
|
|
"""When returning the default value of a StructBlock (e.g. because it's
|
|
a child of another StructBlock, and the outer value is missing that key)
|
|
we should receive it as a StructValue, not just a plain dict"""
|
|
class PersonBlock(blocks.StructBlock):
|
|
first_name = blocks.CharBlock()
|
|
surname = blocks.CharBlock()
|
|
|
|
class EventBlock(blocks.StructBlock):
|
|
title = blocks.CharBlock()
|
|
guest_speaker = PersonBlock(default={'first_name': 'Ed', 'surname': 'Balls'})
|
|
|
|
event_block = EventBlock()
|
|
|
|
event = event_block.to_python({'title': 'Birthday party'})
|
|
|
|
self.assertEqual(event['guest_speaker']['first_name'], 'Ed')
|
|
self.assertTrue(isinstance(event['guest_speaker'], blocks.StructValue))
|
|
|
|
def test_clean(self):
|
|
block = blocks.StructBlock([
|
|
('title', blocks.CharBlock()),
|
|
('link', blocks.URLBlock()),
|
|
])
|
|
|
|
value = block.to_python({'title': 'Torchbox', 'link': 'http://www.torchbox.com/'})
|
|
clean_value = block.clean(value)
|
|
self.assertTrue(isinstance(clean_value, blocks.StructValue))
|
|
self.assertEqual(clean_value['title'], 'Torchbox')
|
|
|
|
value = block.to_python({'title': 'Torchbox', 'link': 'not a url'})
|
|
with self.assertRaises(ValidationError):
|
|
block.clean(value)
|
|
|
|
def test_bound_blocks_are_available_on_template(self):
|
|
"""
|
|
Test that we are able to use value.bound_blocks within templates
|
|
to access a child block's own HTML rendering
|
|
"""
|
|
block = SectionBlock()
|
|
value = block.to_python({'title': 'Hello', 'body': '<i>italic</i> world'})
|
|
result = block.render(value)
|
|
self.assertEqual(result, """<h1>Hello</h1><div class="rich-text"><i>italic</i> world</div>""")
|
|
|
|
def test_render_block_with_extra_context(self):
|
|
block = SectionBlock()
|
|
value = block.to_python({'title': 'Bonjour', 'body': 'monde <i>italique</i>'})
|
|
result = block.render(value, context={'language': 'fr'})
|
|
self.assertEqual(result, """<h1 lang="fr">Bonjour</h1><div class="rich-text">monde <i>italique</i></div>""")
|
|
|
|
def test_render_structvalue(self):
|
|
"""
|
|
The string representation of a StructValue should use the block's template
|
|
"""
|
|
block = SectionBlock()
|
|
value = block.to_python({'title': 'Hello', 'body': '<i>italic</i> world'})
|
|
result = str(value)
|
|
self.assertEqual(result, """<h1>Hello</h1><div class="rich-text"><i>italic</i> world</div>""")
|
|
|
|
# value.render_as_block() should be equivalent to str(value)
|
|
result = value.render_as_block()
|
|
self.assertEqual(result, """<h1>Hello</h1><div class="rich-text"><i>italic</i> world</div>""")
|
|
|
|
def test_render_structvalue_with_extra_context(self):
|
|
block = SectionBlock()
|
|
value = block.to_python({'title': 'Bonjour', 'body': 'monde <i>italique</i>'})
|
|
result = value.render_as_block(context={'language': 'fr'})
|
|
self.assertEqual(result, """<h1 lang="fr">Bonjour</h1><div class="rich-text">monde <i>italique</i></div>""")
|
|
|
|
|
|
class TestListBlock(unittest.TestCase):
|
|
def test_initialise_with_class(self):
|
|
block = blocks.ListBlock(blocks.CharBlock)
|
|
|
|
# Child block should be initialised for us
|
|
self.assertIsInstance(block.child_block, blocks.CharBlock)
|
|
|
|
def test_initialise_with_instance(self):
|
|
child_block = blocks.CharBlock()
|
|
block = blocks.ListBlock(child_block)
|
|
|
|
self.assertEqual(block.child_block, child_block)
|
|
|
|
def render(self):
|
|
class LinkBlock(blocks.StructBlock):
|
|
title = blocks.CharBlock()
|
|
link = blocks.URLBlock()
|
|
|
|
block = blocks.ListBlock(LinkBlock())
|
|
return block.render([
|
|
{
|
|
'title': "Wagtail",
|
|
'link': 'http://www.wagtail.io',
|
|
},
|
|
{
|
|
'title': "Django",
|
|
'link': 'http://www.djangoproject.com',
|
|
},
|
|
])
|
|
|
|
def test_render_uses_ul(self):
|
|
html = self.render()
|
|
|
|
self.assertIn('<ul>', html)
|
|
self.assertIn('</ul>', html)
|
|
|
|
def test_render_uses_li(self):
|
|
html = self.render()
|
|
|
|
self.assertIn('<li>', html)
|
|
self.assertIn('</li>', html)
|
|
|
|
def test_render_calls_block_render_on_children(self):
|
|
"""
|
|
The default rendering of a ListBlock should invoke the block's render method
|
|
on each child, rather than just outputting the child value as a string.
|
|
"""
|
|
block = blocks.ListBlock(
|
|
blocks.CharBlock(template='tests/blocks/heading_block.html')
|
|
)
|
|
html = block.render(["Hello world!", "Goodbye world!"])
|
|
|
|
self.assertIn('<h1>Hello world!</h1>', html)
|
|
self.assertIn('<h1>Goodbye world!</h1>', html)
|
|
|
|
def test_render_passes_context_to_children(self):
|
|
"""
|
|
Template context passed to the render method should be passed on
|
|
to the render method of the child block.
|
|
"""
|
|
block = blocks.ListBlock(
|
|
blocks.CharBlock(template='tests/blocks/heading_block.html')
|
|
)
|
|
html = block.render(["Bonjour le monde!", "Au revoir le monde!"], context={
|
|
'language': 'fr',
|
|
})
|
|
|
|
self.assertIn('<h1 lang="fr">Bonjour le monde!</h1>', html)
|
|
self.assertIn('<h1 lang="fr">Au revoir le monde!</h1>', html)
|
|
|
|
def render_form(self):
|
|
class LinkBlock(blocks.StructBlock):
|
|
title = blocks.CharBlock()
|
|
link = blocks.URLBlock()
|
|
|
|
block = blocks.ListBlock(LinkBlock)
|
|
|
|
html = block.render_form([
|
|
{
|
|
'title': "Wagtail",
|
|
'link': 'http://www.wagtail.io',
|
|
},
|
|
{
|
|
'title': "Django",
|
|
'link': 'http://www.djangoproject.com',
|
|
},
|
|
], prefix='links')
|
|
|
|
return html
|
|
|
|
def test_render_form_wrapper_class(self):
|
|
html = self.render_form()
|
|
|
|
self.assertIn('<div class="sequence-container sequence-type-list">', html)
|
|
|
|
def test_render_form_count_field(self):
|
|
html = self.render_form()
|
|
|
|
self.assertIn('<input type="hidden" name="links-count" id="links-count" value="2">', html)
|
|
|
|
def test_render_form_delete_field(self):
|
|
html = self.render_form()
|
|
|
|
self.assertIn('<input type="hidden" id="links-0-deleted" name="links-0-deleted" value="">', html)
|
|
|
|
def test_render_form_order_fields(self):
|
|
html = self.render_form()
|
|
|
|
self.assertIn('<input type="hidden" id="links-0-order" name="links-0-order" value="0">', html)
|
|
self.assertIn('<input type="hidden" id="links-1-order" name="links-1-order" value="1">', html)
|
|
|
|
def test_render_form_labels(self):
|
|
html = self.render_form()
|
|
|
|
self.assertIn('<label for="links-0-value-title">Title:</label>', html)
|
|
self.assertIn('<label for="links-0-value-link">Link:</label>', html)
|
|
|
|
def test_render_form_values(self):
|
|
html = self.render_form()
|
|
|
|
self.assertIn(
|
|
(
|
|
'<input id="links-0-value-title" name="links-0-value-title" placeholder="Title"'
|
|
' type="text" value="Wagtail" />'
|
|
),
|
|
html
|
|
)
|
|
self.assertIn(
|
|
(
|
|
'<input id="links-0-value-link" name="links-0-value-link" placeholder="Link" type="url"'
|
|
' value="http://www.wagtail.io" />'
|
|
),
|
|
html
|
|
)
|
|
self.assertIn(
|
|
(
|
|
'<input id="links-1-value-title" name="links-1-value-title" placeholder="Title" type="text"'
|
|
' value="Django" />'
|
|
),
|
|
html
|
|
)
|
|
self.assertIn(
|
|
(
|
|
'<input id="links-1-value-link" name="links-1-value-link" placeholder="Link"'
|
|
' type="url" value="http://www.djangoproject.com" />'
|
|
),
|
|
html
|
|
)
|
|
|
|
def test_html_declarations(self):
|
|
class LinkBlock(blocks.StructBlock):
|
|
title = blocks.CharBlock()
|
|
link = blocks.URLBlock()
|
|
|
|
block = blocks.ListBlock(LinkBlock)
|
|
html = block.html_declarations()
|
|
|
|
self.assertIn(
|
|
'<input id="__PREFIX__-value-title" name="__PREFIX__-value-title" placeholder="Title" type="text" />',
|
|
html
|
|
)
|
|
self.assertIn(
|
|
'<input id="__PREFIX__-value-link" name="__PREFIX__-value-link" placeholder="Link" type="url" />',
|
|
html
|
|
)
|
|
|
|
def test_html_declarations_uses_default(self):
|
|
class LinkBlock(blocks.StructBlock):
|
|
title = blocks.CharBlock(default="Github")
|
|
link = blocks.URLBlock(default="http://www.github.com")
|
|
|
|
block = blocks.ListBlock(LinkBlock)
|
|
html = block.html_declarations()
|
|
|
|
self.assertIn(
|
|
(
|
|
'<input id="__PREFIX__-value-title" name="__PREFIX__-value-title" placeholder="Title"'
|
|
' type="text" value="Github" />'
|
|
),
|
|
html
|
|
)
|
|
self.assertIn(
|
|
(
|
|
'<input id="__PREFIX__-value-link" name="__PREFIX__-value-link" placeholder="Link"'
|
|
' type="url" value="http://www.github.com" />'
|
|
),
|
|
html
|
|
)
|
|
|
|
def test_media_inheritance(self):
|
|
class ScriptedCharBlock(blocks.CharBlock):
|
|
media = forms.Media(js=['scripted_char_block.js'])
|
|
|
|
block = blocks.ListBlock(ScriptedCharBlock())
|
|
self.assertIn('scripted_char_block.js', ''.join(block.all_media().render_js()))
|
|
|
|
def test_html_declaration_inheritance(self):
|
|
class CharBlockWithDeclarations(blocks.CharBlock):
|
|
def html_declarations(self):
|
|
return '<script type="text/x-html-template">hello world</script>'
|
|
|
|
block = blocks.ListBlock(CharBlockWithDeclarations())
|
|
self.assertIn('<script type="text/x-html-template">hello world</script>', block.all_html_declarations())
|
|
|
|
def test_searchable_content(self):
|
|
class LinkBlock(blocks.StructBlock):
|
|
title = blocks.CharBlock()
|
|
link = blocks.URLBlock()
|
|
|
|
block = blocks.ListBlock(LinkBlock())
|
|
content = block.get_searchable_content([
|
|
{
|
|
'title': "Wagtail",
|
|
'link': 'http://www.wagtail.io',
|
|
},
|
|
{
|
|
'title': "Django",
|
|
'link': 'http://www.djangoproject.com',
|
|
},
|
|
])
|
|
|
|
self.assertEqual(content, ["Wagtail", "Django"])
|
|
|
|
@unittest.skipIf(django.VERSION < (1, 10, 2), "value_omitted_from_data is not available")
|
|
def test_value_omitted_from_data(self):
|
|
block = blocks.ListBlock(blocks.CharBlock())
|
|
|
|
# overall value is considered present in the form if the 'count' field is present
|
|
self.assertFalse(block.value_omitted_from_data({'mylist-count': '0'}, {}, 'mylist'))
|
|
self.assertFalse(block.value_omitted_from_data({
|
|
'mylist-count': '1',
|
|
'mylist-0-value': 'hello', 'mylist-0-deleted': '', 'mylist-0-order': '0'
|
|
}, {}, 'mylist'))
|
|
self.assertTrue(block.value_omitted_from_data({'nothing-here': 'nope'}, {}, 'mylist'))
|
|
|
|
def test_ordering_in_form_submission_uses_order_field(self):
|
|
block = blocks.ListBlock(blocks.CharBlock())
|
|
|
|
# check that items are ordered by the 'order' field, not the order they appear in the form
|
|
post_data = {'shoppinglist-count': '3'}
|
|
for i in range(0, 3):
|
|
post_data.update({
|
|
'shoppinglist-%d-deleted' % i: '',
|
|
'shoppinglist-%d-order' % i: str(2 - i),
|
|
'shoppinglist-%d-value' % i: "item %d" % i
|
|
})
|
|
|
|
block_value = block.value_from_datadict(post_data, {}, 'shoppinglist')
|
|
self.assertEqual(block_value[2], "item 0")
|
|
|
|
def test_ordering_in_form_submission_is_numeric(self):
|
|
block = blocks.ListBlock(blocks.CharBlock())
|
|
|
|
# check that items are ordered by 'order' numerically, not alphabetically
|
|
post_data = {'shoppinglist-count': '12'}
|
|
for i in range(0, 12):
|
|
post_data.update({
|
|
'shoppinglist-%d-deleted' % i: '',
|
|
'shoppinglist-%d-order' % i: str(i),
|
|
'shoppinglist-%d-value' % i: "item %d" % i
|
|
})
|
|
|
|
block_value = block.value_from_datadict(post_data, {}, 'shoppinglist')
|
|
self.assertEqual(block_value[2], "item 2")
|
|
|
|
def test_can_specify_default(self):
|
|
class ShoppingListBlock(blocks.StructBlock):
|
|
shop = blocks.CharBlock()
|
|
items = blocks.ListBlock(blocks.CharBlock(), default=['peas', 'beans', 'carrots'])
|
|
|
|
block = ShoppingListBlock()
|
|
# the value here does not specify an 'items' field, so this should revert to the ListBlock's default
|
|
form_html = block.render_form(block.to_python({'shop': 'Tesco'}), prefix='shoppinglist')
|
|
|
|
self.assertIn(
|
|
'<input type="hidden" name="shoppinglist-items-count" id="shoppinglist-items-count" value="3">',
|
|
form_html
|
|
)
|
|
self.assertIn('value="peas"', form_html)
|
|
|
|
def test_default_default(self):
|
|
"""
|
|
if no explicit 'default' is set on the ListBlock, it should fall back on
|
|
a single instance of the child block in its default state.
|
|
"""
|
|
class ShoppingListBlock(blocks.StructBlock):
|
|
shop = blocks.CharBlock()
|
|
items = blocks.ListBlock(blocks.CharBlock(default='chocolate'))
|
|
|
|
block = ShoppingListBlock()
|
|
# the value here does not specify an 'items' field, so this should revert to the ListBlock's default
|
|
form_html = block.render_form(block.to_python({'shop': 'Tesco'}), prefix='shoppinglist')
|
|
|
|
self.assertIn(
|
|
'<input type="hidden" name="shoppinglist-items-count" id="shoppinglist-items-count" value="1">',
|
|
form_html
|
|
)
|
|
self.assertIn('value="chocolate"', form_html)
|
|
|
|
|
|
class TestStreamBlock(SimpleTestCase):
|
|
def test_initialisation(self):
|
|
block = blocks.StreamBlock([
|
|
('heading', blocks.CharBlock()),
|
|
('paragraph', blocks.CharBlock()),
|
|
])
|
|
|
|
self.assertEqual(list(block.child_blocks.keys()), ['heading', 'paragraph'])
|
|
|
|
def test_initialisation_with_binary_string_names(self):
|
|
# migrations will sometimes write out names as binary strings, just to keep us on our toes
|
|
block = blocks.StreamBlock([
|
|
(b'heading', blocks.CharBlock()),
|
|
(b'paragraph', blocks.CharBlock()),
|
|
])
|
|
|
|
self.assertEqual(list(block.child_blocks.keys()), [b'heading', b'paragraph'])
|
|
|
|
def test_initialisation_from_subclass(self):
|
|
class ArticleBlock(blocks.StreamBlock):
|
|
heading = blocks.CharBlock()
|
|
paragraph = blocks.CharBlock()
|
|
|
|
block = ArticleBlock()
|
|
|
|
self.assertEqual(list(block.child_blocks.keys()), ['heading', 'paragraph'])
|
|
|
|
def test_initialisation_from_subclass_with_extra(self):
|
|
class ArticleBlock(blocks.StreamBlock):
|
|
heading = blocks.CharBlock()
|
|
paragraph = blocks.CharBlock()
|
|
|
|
block = ArticleBlock([
|
|
('intro', blocks.CharBlock())
|
|
])
|
|
|
|
self.assertEqual(list(block.child_blocks.keys()), ['heading', 'paragraph', 'intro'])
|
|
|
|
def test_initialisation_with_multiple_subclassses(self):
|
|
class ArticleBlock(blocks.StreamBlock):
|
|
heading = blocks.CharBlock()
|
|
paragraph = blocks.CharBlock()
|
|
|
|
class ArticleWithIntroBlock(ArticleBlock):
|
|
intro = blocks.CharBlock()
|
|
|
|
block = ArticleWithIntroBlock()
|
|
|
|
self.assertEqual(list(block.child_blocks.keys()), ['heading', 'paragraph', 'intro'])
|
|
|
|
def test_initialisation_with_mixins(self):
|
|
"""
|
|
The order of child blocks of ``StreamBlock``\s with multiple parent
|
|
classes is slightly surprising at first. Child blocks are inherited in
|
|
a bottom-up order, by traversing the MRO in reverse. In the example
|
|
below, ``ArticleWithIntroBlock`` will have an MRO of::
|
|
|
|
[ArticleWithIntroBlock, IntroMixin, ArticleBlock, StreamBlock, ...]
|
|
|
|
This will result in ``intro`` appearing *after* ``heading`` and
|
|
``paragraph`` in ``ArticleWithIntroBlock.child_blocks``, even though
|
|
``IntroMixin`` appeared before ``ArticleBlock``.
|
|
"""
|
|
class ArticleBlock(blocks.StreamBlock):
|
|
heading = blocks.CharBlock()
|
|
paragraph = blocks.CharBlock()
|
|
|
|
class IntroMixin(blocks.StreamBlock):
|
|
intro = blocks.CharBlock()
|
|
|
|
class ArticleWithIntroBlock(IntroMixin, ArticleBlock):
|
|
by_line = blocks.CharBlock()
|
|
|
|
block = ArticleWithIntroBlock()
|
|
|
|
self.assertEqual(list(block.child_blocks.keys()),
|
|
['heading', 'paragraph', 'intro', 'by_line'])
|
|
|
|
def render_article(self, data):
|
|
class ArticleBlock(blocks.StreamBlock):
|
|
heading = blocks.CharBlock()
|
|
paragraph = blocks.RichTextBlock()
|
|
|
|
block = ArticleBlock()
|
|
value = block.to_python(data)
|
|
|
|
return block.render(value)
|
|
|
|
def test_render(self):
|
|
html = self.render_article([
|
|
{
|
|
'type': 'heading',
|
|
'value': "My title",
|
|
},
|
|
{
|
|
'type': 'paragraph',
|
|
'value': 'My <i>first</i> paragraph',
|
|
},
|
|
{
|
|
'type': 'paragraph',
|
|
'value': 'My second paragraph',
|
|
},
|
|
])
|
|
|
|
self.assertIn('<div class="block-heading">My title</div>', html)
|
|
self.assertIn('<div class="block-paragraph"><div class="rich-text">My <i>first</i> paragraph</div></div>', html)
|
|
self.assertIn('<div class="block-paragraph"><div class="rich-text">My second paragraph</div></div>', html)
|
|
|
|
def test_render_unknown_type(self):
|
|
# This can happen if a developer removes a type from their StreamBlock
|
|
html = self.render_article([
|
|
{
|
|
'type': 'foo',
|
|
'value': "Hello",
|
|
},
|
|
{
|
|
'type': 'paragraph',
|
|
'value': 'My first paragraph',
|
|
},
|
|
])
|
|
self.assertNotIn('foo', html)
|
|
self.assertNotIn('Hello', html)
|
|
self.assertIn('<div class="block-paragraph"><div class="rich-text">My first paragraph</div></div>', html)
|
|
|
|
def test_render_calls_block_render_on_children(self):
|
|
"""
|
|
The default rendering of a StreamBlock should invoke the block's render method
|
|
on each child, rather than just outputting the child value as a string.
|
|
"""
|
|
block = blocks.StreamBlock([
|
|
('heading', blocks.CharBlock(template='tests/blocks/heading_block.html')),
|
|
('paragraph', blocks.CharBlock()),
|
|
])
|
|
value = block.to_python([
|
|
{'type': 'heading', 'value': 'Hello'}
|
|
])
|
|
html = block.render(value)
|
|
self.assertIn('<div class="block-heading"><h1>Hello</h1></div>', html)
|
|
|
|
# calling render_as_block() on value (a StreamValue instance)
|
|
# should be equivalent to block.render(value)
|
|
html = value.render_as_block()
|
|
self.assertIn('<div class="block-heading"><h1>Hello</h1></div>', html)
|
|
|
|
def test_render_passes_context_to_children(self):
|
|
block = blocks.StreamBlock([
|
|
('heading', blocks.CharBlock(template='tests/blocks/heading_block.html')),
|
|
('paragraph', blocks.CharBlock()),
|
|
])
|
|
value = block.to_python([
|
|
{'type': 'heading', 'value': 'Bonjour'}
|
|
])
|
|
html = block.render(value, context={
|
|
'language': 'fr',
|
|
})
|
|
self.assertIn('<div class="block-heading"><h1 lang="fr">Bonjour</h1></div>', html)
|
|
|
|
# calling render_as_block(context=foo) on value (a StreamValue instance)
|
|
# should be equivalent to block.render(value, context=foo)
|
|
html = value.render_as_block(context={
|
|
'language': 'fr',
|
|
})
|
|
self.assertIn('<div class="block-heading"><h1 lang="fr">Bonjour</h1></div>', html)
|
|
|
|
def test_render_on_stream_child_uses_child_template(self):
|
|
"""
|
|
Accessing a child element of the stream (giving a StreamChild object) and rendering it
|
|
should use the block template, not just render the value's string representation
|
|
"""
|
|
block = blocks.StreamBlock([
|
|
('heading', blocks.CharBlock(template='tests/blocks/heading_block.html')),
|
|
('paragraph', blocks.CharBlock()),
|
|
])
|
|
value = block.to_python([
|
|
{'type': 'heading', 'value': 'Hello'}
|
|
])
|
|
html = value[0].render()
|
|
self.assertEqual('<h1>Hello</h1>', html)
|
|
|
|
# StreamChild.__str__ should do the same
|
|
html = str(value[0])
|
|
self.assertEqual('<h1>Hello</h1>', html)
|
|
|
|
# and so should StreamChild.render_as_block
|
|
html = value[0].render_as_block()
|
|
self.assertEqual('<h1>Hello</h1>', html)
|
|
|
|
def test_can_pass_context_to_stream_child_template(self):
|
|
block = blocks.StreamBlock([
|
|
('heading', blocks.CharBlock(template='tests/blocks/heading_block.html')),
|
|
('paragraph', blocks.CharBlock()),
|
|
])
|
|
value = block.to_python([
|
|
{'type': 'heading', 'value': 'Bonjour'}
|
|
])
|
|
html = value[0].render(context={'language': 'fr'})
|
|
self.assertEqual('<h1 lang="fr">Bonjour</h1>', html)
|
|
|
|
# the same functionality should be available through the alias `render_as_block`
|
|
html = value[0].render_as_block(context={'language': 'fr'})
|
|
self.assertEqual('<h1 lang="fr">Bonjour</h1>', html)
|
|
|
|
def render_form(self):
|
|
class ArticleBlock(blocks.StreamBlock):
|
|
heading = blocks.CharBlock()
|
|
paragraph = blocks.CharBlock()
|
|
|
|
block = ArticleBlock()
|
|
value = block.to_python([
|
|
{
|
|
'type': 'heading',
|
|
'value': "My title",
|
|
},
|
|
{
|
|
'type': 'paragraph',
|
|
'value': 'My first paragraph',
|
|
},
|
|
{
|
|
'type': 'paragraph',
|
|
'value': 'My second paragraph',
|
|
},
|
|
])
|
|
return block.render_form(value, prefix='myarticle')
|
|
|
|
def test_render_form_wrapper_class(self):
|
|
html = self.render_form()
|
|
|
|
self.assertIn('<div class="sequence-container sequence-type-stream">', html)
|
|
|
|
def test_render_form_count_field(self):
|
|
html = self.render_form()
|
|
|
|
self.assertIn('<input type="hidden" name="myarticle-count" id="myarticle-count" value="3">', html)
|
|
|
|
def test_render_form_delete_field(self):
|
|
html = self.render_form()
|
|
|
|
self.assertIn('<input type="hidden" id="myarticle-0-deleted" name="myarticle-0-deleted" value="">', html)
|
|
|
|
def test_render_form_order_fields(self):
|
|
html = self.render_form()
|
|
|
|
self.assertIn('<input type="hidden" id="myarticle-0-order" name="myarticle-0-order" value="0">', html)
|
|
self.assertIn('<input type="hidden" id="myarticle-1-order" name="myarticle-1-order" value="1">', html)
|
|
self.assertIn('<input type="hidden" id="myarticle-2-order" name="myarticle-2-order" value="2">', html)
|
|
|
|
def test_render_form_type_fields(self):
|
|
html = self.render_form()
|
|
|
|
self.assertIn('<input type="hidden" id="myarticle-0-type" name="myarticle-0-type" value="heading">', html)
|
|
self.assertIn('<input type="hidden" id="myarticle-1-type" name="myarticle-1-type" value="paragraph">', html)
|
|
self.assertIn('<input type="hidden" id="myarticle-2-type" name="myarticle-2-type" value="paragraph">', html)
|
|
|
|
def test_render_form_value_fields(self):
|
|
html = self.render_form()
|
|
|
|
self.assertIn(
|
|
(
|
|
'<input id="myarticle-0-value" name="myarticle-0-value" placeholder="Heading"'
|
|
' type="text" value="My title" />'
|
|
),
|
|
html
|
|
)
|
|
self.assertIn(
|
|
(
|
|
'<input id="myarticle-1-value" name="myarticle-1-value" placeholder="Paragraph"'
|
|
' type="text" value="My first paragraph" />'
|
|
),
|
|
html
|
|
)
|
|
self.assertIn(
|
|
(
|
|
'<input id="myarticle-2-value" name="myarticle-2-value" placeholder="Paragraph"'
|
|
' type="text" value="My second paragraph" />'
|
|
),
|
|
html
|
|
)
|
|
|
|
@unittest.skipIf(django.VERSION < (1, 10, 2), "value_omitted_from_data is not available")
|
|
def test_value_omitted_from_data(self):
|
|
block = blocks.StreamBlock([
|
|
('heading', blocks.CharBlock()),
|
|
])
|
|
|
|
# overall value is considered present in the form if the 'count' field is present
|
|
self.assertFalse(block.value_omitted_from_data({'mystream-count': '0'}, {}, 'mystream'))
|
|
self.assertFalse(block.value_omitted_from_data({
|
|
'mystream-count': '1',
|
|
'mystream-0-type': 'heading', 'mystream-0-value': 'hello',
|
|
'mystream-0-deleted': '', 'mystream-0-order': '0'
|
|
}, {}, 'mystream'))
|
|
self.assertTrue(block.value_omitted_from_data({'nothing-here': 'nope'}, {}, 'mystream'))
|
|
|
|
def test_validation_errors(self):
|
|
class ValidatedBlock(blocks.StreamBlock):
|
|
char = blocks.CharBlock()
|
|
url = blocks.URLBlock()
|
|
block = ValidatedBlock()
|
|
|
|
value = [
|
|
blocks.BoundBlock(
|
|
block=block.child_blocks['char'],
|
|
value='',
|
|
),
|
|
blocks.BoundBlock(
|
|
block=block.child_blocks['char'],
|
|
value='foo',
|
|
),
|
|
blocks.BoundBlock(
|
|
block=block.child_blocks['url'],
|
|
value='http://example.com/',
|
|
),
|
|
blocks.BoundBlock(
|
|
block=block.child_blocks['url'],
|
|
value='not a url',
|
|
),
|
|
]
|
|
|
|
with self.assertRaises(ValidationError) as catcher:
|
|
block.clean(value)
|
|
self.assertEqual(catcher.exception.params, {
|
|
0: ['This field is required.'],
|
|
3: ['Enter a valid URL.'],
|
|
})
|
|
|
|
def test_block_level_validation_renders_errors(self):
|
|
block = FooStreamBlock()
|
|
|
|
post_data = {'stream-count': '2'}
|
|
for i, value in enumerate(['bar', 'baz']):
|
|
post_data.update({
|
|
'stream-%d-deleted' % i: '',
|
|
'stream-%d-order' % i: str(i),
|
|
'stream-%d-type' % i: 'text',
|
|
'stream-%d-value' % i: value,
|
|
})
|
|
|
|
block_value = block.value_from_datadict(post_data, {}, 'stream')
|
|
with self.assertRaises(ValidationError) as catcher:
|
|
block.clean(block_value)
|
|
|
|
errors = ErrorList([
|
|
catcher.exception
|
|
])
|
|
|
|
self.assertInHTML(
|
|
format_html('<div class="help-block help-critical">{}</div>', FooStreamBlock.error),
|
|
block.render_form(block_value, prefix='stream', errors=errors))
|
|
|
|
def test_block_level_validation_render_no_errors(self):
|
|
block = FooStreamBlock()
|
|
|
|
post_data = {'stream-count': '3'}
|
|
for i, value in enumerate(['foo', 'bar', 'baz']):
|
|
post_data.update({
|
|
'stream-%d-deleted' % i: '',
|
|
'stream-%d-order' % i: str(i),
|
|
'stream-%d-type' % i: 'text',
|
|
'stream-%d-value' % i: value,
|
|
})
|
|
|
|
block_value = block.value_from_datadict(post_data, {}, 'stream')
|
|
|
|
try:
|
|
block.clean(block_value)
|
|
except ValidationError:
|
|
self.fail('Should have passed validation')
|
|
|
|
self.assertInHTML(
|
|
format_html('<div class="help-block help-critical">{}</div>', FooStreamBlock.error),
|
|
block.render_form(block_value, prefix='stream'),
|
|
count=0)
|
|
|
|
def test_html_declarations(self):
|
|
class ArticleBlock(blocks.StreamBlock):
|
|
heading = blocks.CharBlock()
|
|
paragraph = blocks.CharBlock()
|
|
|
|
block = ArticleBlock()
|
|
html = block.html_declarations()
|
|
|
|
self.assertIn('<input id="__PREFIX__-value" name="__PREFIX__-value" placeholder="Heading" type="text" />', html)
|
|
self.assertIn(
|
|
'<input id="__PREFIX__-value" name="__PREFIX__-value" placeholder="Paragraph" type="text" />',
|
|
html
|
|
)
|
|
|
|
def test_html_declarations_uses_default(self):
|
|
class ArticleBlock(blocks.StreamBlock):
|
|
heading = blocks.CharBlock(default="Fish found on moon")
|
|
paragraph = blocks.CharBlock(default="Lorem ipsum dolor sit amet")
|
|
|
|
block = ArticleBlock()
|
|
html = block.html_declarations()
|
|
|
|
self.assertIn(
|
|
(
|
|
'<input id="__PREFIX__-value" name="__PREFIX__-value" placeholder="Heading"'
|
|
' type="text" value="Fish found on moon" />'
|
|
),
|
|
html
|
|
)
|
|
self.assertIn(
|
|
(
|
|
'<input id="__PREFIX__-value" name="__PREFIX__-value" placeholder="Paragraph" type="text"'
|
|
' value="Lorem ipsum dolor sit amet" />'
|
|
),
|
|
html
|
|
)
|
|
|
|
def test_media_inheritance(self):
|
|
class ScriptedCharBlock(blocks.CharBlock):
|
|
media = forms.Media(js=['scripted_char_block.js'])
|
|
|
|
class ArticleBlock(blocks.StreamBlock):
|
|
heading = ScriptedCharBlock()
|
|
paragraph = blocks.CharBlock()
|
|
|
|
block = ArticleBlock()
|
|
self.assertIn('scripted_char_block.js', ''.join(block.all_media().render_js()))
|
|
|
|
def test_html_declaration_inheritance(self):
|
|
class CharBlockWithDeclarations(blocks.CharBlock):
|
|
def html_declarations(self):
|
|
return '<script type="text/x-html-template">hello world</script>'
|
|
|
|
class ArticleBlock(blocks.StreamBlock):
|
|
heading = CharBlockWithDeclarations(default="Torchbox")
|
|
paragraph = blocks.CharBlock()
|
|
|
|
block = ArticleBlock()
|
|
self.assertIn('<script type="text/x-html-template">hello world</script>', block.all_html_declarations())
|
|
|
|
def test_ordering_in_form_submission_uses_order_field(self):
|
|
class ArticleBlock(blocks.StreamBlock):
|
|
heading = blocks.CharBlock()
|
|
paragraph = blocks.CharBlock()
|
|
|
|
block = ArticleBlock()
|
|
|
|
# check that items are ordered by the 'order' field, not the order they appear in the form
|
|
post_data = {'article-count': '3'}
|
|
for i in range(0, 3):
|
|
post_data.update({
|
|
'article-%d-deleted' % i: '',
|
|
'article-%d-order' % i: str(2 - i),
|
|
'article-%d-type' % i: 'heading',
|
|
'article-%d-value' % i: "heading %d" % i
|
|
})
|
|
|
|
block_value = block.value_from_datadict(post_data, {}, 'article')
|
|
self.assertEqual(block_value[2].value, "heading 0")
|
|
|
|
def test_ordering_in_form_submission_is_numeric(self):
|
|
class ArticleBlock(blocks.StreamBlock):
|
|
heading = blocks.CharBlock()
|
|
paragraph = blocks.CharBlock()
|
|
|
|
block = ArticleBlock()
|
|
|
|
# check that items are ordered by 'order' numerically, not alphabetically
|
|
post_data = {'article-count': '12'}
|
|
for i in range(0, 12):
|
|
post_data.update({
|
|
'article-%d-deleted' % i: '',
|
|
'article-%d-order' % i: str(i),
|
|
'article-%d-type' % i: 'heading',
|
|
'article-%d-value' % i: "heading %d" % i
|
|
})
|
|
|
|
block_value = block.value_from_datadict(post_data, {}, 'article')
|
|
self.assertEqual(block_value[2].value, "heading 2")
|
|
|
|
def test_searchable_content(self):
|
|
class ArticleBlock(blocks.StreamBlock):
|
|
heading = blocks.CharBlock()
|
|
paragraph = blocks.CharBlock()
|
|
|
|
block = ArticleBlock()
|
|
value = block.to_python([
|
|
{
|
|
'type': 'heading',
|
|
'value': "My title",
|
|
},
|
|
{
|
|
'type': 'paragraph',
|
|
'value': 'My first paragraph',
|
|
},
|
|
{
|
|
'type': 'paragraph',
|
|
'value': 'My second paragraph',
|
|
},
|
|
])
|
|
|
|
content = block.get_searchable_content(value)
|
|
|
|
self.assertEqual(content, [
|
|
"My title",
|
|
"My first paragraph",
|
|
"My second paragraph",
|
|
])
|
|
|
|
def test_meta_default(self):
|
|
"""Test that we can specify a default value in the Meta of a StreamBlock"""
|
|
|
|
class ArticleBlock(blocks.StreamBlock):
|
|
heading = blocks.CharBlock()
|
|
paragraph = blocks.CharBlock()
|
|
|
|
class Meta:
|
|
default = [('heading', 'A default heading')]
|
|
|
|
# to access the default value, we retrieve it through a StructBlock
|
|
# from a struct value that's missing that key
|
|
class ArticleContainerBlock(blocks.StructBlock):
|
|
author = blocks.CharBlock()
|
|
article = ArticleBlock()
|
|
|
|
block = ArticleContainerBlock()
|
|
struct_value = block.to_python({'author': 'Bob'})
|
|
stream_value = struct_value['article']
|
|
|
|
self.assertTrue(isinstance(stream_value, blocks.StreamValue))
|
|
self.assertEqual(len(stream_value), 1)
|
|
self.assertEqual(stream_value[0].block_type, 'heading')
|
|
self.assertEqual(stream_value[0].value, 'A default heading')
|
|
|
|
def test_constructor_default(self):
|
|
"""Test that we can specify a default value in the constructor of a StreamBlock"""
|
|
|
|
class ArticleBlock(blocks.StreamBlock):
|
|
heading = blocks.CharBlock()
|
|
paragraph = blocks.CharBlock()
|
|
|
|
class Meta:
|
|
default = [('heading', 'A default heading')]
|
|
|
|
# to access the default value, we retrieve it through a StructBlock
|
|
# from a struct value that's missing that key
|
|
class ArticleContainerBlock(blocks.StructBlock):
|
|
author = blocks.CharBlock()
|
|
article = ArticleBlock(default=[('heading', 'A different default heading')])
|
|
|
|
block = ArticleContainerBlock()
|
|
struct_value = block.to_python({'author': 'Bob'})
|
|
stream_value = struct_value['article']
|
|
|
|
self.assertTrue(isinstance(stream_value, blocks.StreamValue))
|
|
self.assertEqual(len(stream_value), 1)
|
|
self.assertEqual(stream_value[0].block_type, 'heading')
|
|
self.assertEqual(stream_value[0].value, 'A different default heading')
|
|
|
|
|
|
class TestPageChooserBlock(TestCase):
|
|
fixtures = ['test.json']
|
|
|
|
def test_serialize(self):
|
|
"""The value of a PageChooserBlock (a Page object) should serialize to an ID"""
|
|
block = blocks.PageChooserBlock()
|
|
christmas_page = Page.objects.get(slug='christmas')
|
|
|
|
self.assertEqual(block.get_prep_value(christmas_page), christmas_page.id)
|
|
|
|
# None should serialize to None
|
|
self.assertEqual(block.get_prep_value(None), None)
|
|
|
|
def test_deserialize(self):
|
|
"""The serialized value of a PageChooserBlock (an ID) should deserialize to a Page object"""
|
|
block = blocks.PageChooserBlock()
|
|
christmas_page = Page.objects.get(slug='christmas')
|
|
|
|
self.assertEqual(block.to_python(christmas_page.id), christmas_page)
|
|
|
|
# None should deserialize to None
|
|
self.assertEqual(block.to_python(None), None)
|
|
|
|
def test_form_render(self):
|
|
block = blocks.PageChooserBlock(help_text="pick a page, any page")
|
|
|
|
empty_form_html = block.render_form(None, 'page')
|
|
self.assertIn('<input id="page" name="page" placeholder="" type="hidden" />', empty_form_html)
|
|
self.assertIn('createPageChooser("page", ["wagtailcore.page"], null, false);', empty_form_html)
|
|
|
|
christmas_page = Page.objects.get(slug='christmas')
|
|
christmas_form_html = block.render_form(christmas_page, 'page')
|
|
expected_html = '<input id="page" name="page" placeholder="" type="hidden" value="%d" />' % christmas_page.id
|
|
self.assertIn(expected_html, christmas_form_html)
|
|
self.assertIn("pick a page, any page", christmas_form_html)
|
|
|
|
def test_form_render_with_target_model(self):
|
|
block = blocks.PageChooserBlock(help_text="pick a page, any page", target_model='tests.SimplePage')
|
|
empty_form_html = block.render_form(None, 'page')
|
|
self.assertIn('createPageChooser("page", ["tests.simplepage"], null, false);', empty_form_html)
|
|
|
|
def test_form_render_with_can_choose_root(self):
|
|
block = blocks.PageChooserBlock(help_text="pick a page, any page", can_choose_root=True)
|
|
empty_form_html = block.render_form(None, 'page')
|
|
self.assertIn('createPageChooser("page", ["wagtailcore.page"], null, true);', empty_form_html)
|
|
|
|
def test_form_response(self):
|
|
block = blocks.PageChooserBlock()
|
|
christmas_page = Page.objects.get(slug='christmas')
|
|
|
|
value = block.value_from_datadict({'page': str(christmas_page.id)}, {}, 'page')
|
|
self.assertEqual(value, christmas_page)
|
|
|
|
empty_value = block.value_from_datadict({'page': ''}, {}, 'page')
|
|
self.assertEqual(empty_value, None)
|
|
|
|
def test_clean(self):
|
|
required_block = blocks.PageChooserBlock()
|
|
nonrequired_block = blocks.PageChooserBlock(required=False)
|
|
christmas_page = Page.objects.get(slug='christmas')
|
|
|
|
self.assertEqual(required_block.clean(christmas_page), christmas_page)
|
|
with self.assertRaises(ValidationError):
|
|
required_block.clean(None)
|
|
|
|
self.assertEqual(nonrequired_block.clean(christmas_page), christmas_page)
|
|
self.assertEqual(nonrequired_block.clean(None), None)
|
|
|
|
def test_target_model_string(self):
|
|
block = blocks.PageChooserBlock(target_model='tests.SimplePage')
|
|
self.assertEqual(block.target_model, SimplePage)
|
|
|
|
def test_target_model_literal(self):
|
|
block = blocks.PageChooserBlock(target_model=SimplePage)
|
|
self.assertEqual(block.target_model, SimplePage)
|
|
|
|
def test_deconstruct_target_model_string(self):
|
|
block = blocks.PageChooserBlock(target_model='tests.SimplePage')
|
|
self.assertEqual(block.deconstruct(), (
|
|
'wagtail.wagtailcore.blocks.PageChooserBlock',
|
|
(), {'target_model': 'tests.SimplePage'}))
|
|
|
|
def test_deconstruct_target_model_literal(self):
|
|
block = blocks.PageChooserBlock(target_model=SimplePage)
|
|
self.assertEqual(block.deconstruct(), (
|
|
'wagtail.wagtailcore.blocks.PageChooserBlock',
|
|
(), {'target_model': 'tests.SimplePage'}))
|
|
|
|
|
|
class TestStaticBlock(unittest.TestCase):
|
|
def test_render_form_with_constructor(self):
|
|
block = blocks.StaticBlock(
|
|
admin_text="Latest posts - This block doesn't need to be configured, it will be displayed automatically",
|
|
template='tests/blocks/posts_static_block.html')
|
|
rendered_html = block.render_form(None)
|
|
|
|
self.assertEqual(rendered_html, "Latest posts - This block doesn't need to be configured, it will be displayed automatically")
|
|
|
|
def test_render_form_with_subclass(self):
|
|
class PostsStaticBlock(blocks.StaticBlock):
|
|
class Meta:
|
|
admin_text = "Latest posts - This block doesn't need to be configured, it will be displayed automatically"
|
|
template = "tests/blocks/posts_static_block.html"
|
|
|
|
block = PostsStaticBlock()
|
|
rendered_html = block.render_form(None)
|
|
|
|
self.assertEqual(rendered_html, "Latest posts - This block doesn't need to be configured, it will be displayed automatically")
|
|
|
|
def test_render_form_with_subclass_displays_default_text_if_no_admin_text(self):
|
|
class LabelOnlyStaticBlock(blocks.StaticBlock):
|
|
class Meta:
|
|
label = "Latest posts"
|
|
|
|
block = LabelOnlyStaticBlock()
|
|
rendered_html = block.render_form(None)
|
|
|
|
self.assertEqual(rendered_html, "Latest posts: this block has no options.")
|
|
|
|
def test_render_form_with_subclass_displays_default_text_if_no_admin_text_and_no_label(self):
|
|
class NoMetaStaticBlock(blocks.StaticBlock):
|
|
pass
|
|
|
|
block = NoMetaStaticBlock()
|
|
rendered_html = block.render_form(None)
|
|
|
|
self.assertEqual(rendered_html, "This block has no options.")
|
|
|
|
def test_render_form_works_with_mark_safe(self):
|
|
block = blocks.StaticBlock(
|
|
admin_text=mark_safe("<b>Latest posts</b> - This block doesn't need to be configured, it will be displayed automatically"),
|
|
template='tests/blocks/posts_static_block.html')
|
|
rendered_html = block.render_form(None)
|
|
|
|
self.assertEqual(rendered_html, "<b>Latest posts</b> - This block doesn't need to be configured, it will be displayed automatically")
|
|
|
|
def test_get_default(self):
|
|
block = blocks.StaticBlock()
|
|
default_value = block.get_default()
|
|
self.assertEqual(default_value, None)
|
|
|
|
def test_render(self):
|
|
block = blocks.StaticBlock(template='tests/blocks/posts_static_block.html')
|
|
result = block.render(None)
|
|
self.assertEqual(result, '<p>PostsStaticBlock template</p>')
|
|
|
|
def test_serialize(self):
|
|
block = blocks.StaticBlock()
|
|
result = block.get_prep_value(None)
|
|
self.assertEqual(result, None)
|
|
|
|
def test_deserialize(self):
|
|
block = blocks.StaticBlock()
|
|
result = block.to_python(None)
|
|
self.assertEqual(result, 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")
|
|
self.assertEqual(errors[0].obj, block.child_blocks[''])
|
|
|
|
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")
|
|
self.assertEqual(errors[0].obj, block.child_blocks['rich text'])
|
|
|
|
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")
|
|
self.assertEqual(errors[0].obj, block.child_blocks['rich-text'])
|
|
|
|
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")
|
|
self.assertEqual(errors[0].obj, block.child_blocks['99richtext'])
|
|
|
|
def test_system_checks_recurse_into_lists(self):
|
|
failing_block = blocks.RichTextBlock()
|
|
block = blocks.StreamBlock([
|
|
('paragraph_list', blocks.ListBlock(
|
|
blocks.StructBlock([
|
|
('heading', blocks.CharBlock()),
|
|
('rich text', failing_block),
|
|
])
|
|
))
|
|
])
|
|
|
|
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")
|
|
self.assertEqual(errors[0].obj, failing_block)
|
|
|
|
def test_system_checks_recurse_into_streams(self):
|
|
failing_block = blocks.RichTextBlock()
|
|
block = blocks.StreamBlock([
|
|
('carousel', blocks.StreamBlock([
|
|
('text', blocks.StructBlock([
|
|
('heading', blocks.CharBlock()),
|
|
('rich text', failing_block),
|
|
]))
|
|
]))
|
|
])
|
|
|
|
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")
|
|
self.assertEqual(errors[0].obj, failing_block)
|
|
|
|
def test_system_checks_recurse_into_structs(self):
|
|
failing_block_1 = blocks.RichTextBlock()
|
|
failing_block_2 = blocks.RichTextBlock()
|
|
block = blocks.StreamBlock([
|
|
('two_column', blocks.StructBlock([
|
|
('left', blocks.StructBlock([
|
|
('heading', blocks.CharBlock()),
|
|
('rich text', failing_block_1),
|
|
])),
|
|
('right', blocks.StructBlock([
|
|
('heading', blocks.CharBlock()),
|
|
('rich text', failing_block_2),
|
|
]))
|
|
]))
|
|
])
|
|
|
|
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[0].obj, failing_block_1)
|
|
self.assertEqual(errors[1].id, 'wagtailcore.E001')
|
|
self.assertEqual(errors[1].hint, "Block names cannot contain spaces")
|
|
self.assertEqual(errors[0].obj, failing_block_2)
|
|
|
|
|
|
class TestTemplateRendering(TestCase):
|
|
def test_render_with_custom_context(self):
|
|
block = CustomLinkBlock()
|
|
value = block.to_python({'title': 'Torchbox', 'url': 'http://torchbox.com/'})
|
|
result = block.render(value)
|
|
|
|
self.assertEqual(result, '<a href="http://torchbox.com/" class="important">Torchbox</a>')
|
|
|
|
def test_render_with_custom_form_context(self):
|
|
block = CustomLinkBlock()
|
|
value = block.to_python({'title': 'Torchbox', 'url': 'http://torchbox.com/'})
|
|
result = block.render_form(value, prefix='my-link-block')
|
|
|
|
self.assertIn('data-prefix="my-link-block"', result)
|
|
self.assertIn('<p>Hello from get_form_context!</p>', result)
|
|
|
|
|
|
class TestIncludeBlockTag(TestCase):
|
|
def test_include_block_tag_with_boundblock(self):
|
|
"""
|
|
The include_block tag should be able to render a BoundBlock's template
|
|
while keeping the parent template's context
|
|
"""
|
|
block = blocks.CharBlock(template='tests/blocks/heading_block.html')
|
|
bound_block = block.bind('bonjour')
|
|
|
|
result = render_to_string('tests/blocks/include_block_test.html', {
|
|
'test_block': bound_block,
|
|
'language': 'fr',
|
|
})
|
|
self.assertIn('<body><h1 lang="fr">bonjour</h1></body>', result)
|
|
|
|
def test_include_block_tag_with_structvalue(self):
|
|
"""
|
|
The include_block tag should be able to render a StructValue's template
|
|
while keeping the parent template's context
|
|
"""
|
|
block = SectionBlock()
|
|
struct_value = block.to_python({'title': 'Bonjour', 'body': 'monde <i>italique</i>'})
|
|
|
|
result = render_to_string('tests/blocks/include_block_test.html', {
|
|
'test_block': struct_value,
|
|
'language': 'fr',
|
|
})
|
|
|
|
self.assertIn(
|
|
"""<body><h1 lang="fr">Bonjour</h1><div class="rich-text">monde <i>italique</i></div></body>""",
|
|
result
|
|
)
|
|
|
|
def test_include_block_tag_with_streamvalue(self):
|
|
"""
|
|
The include_block tag should be able to render a StreamValue's template
|
|
while keeping the parent template's context
|
|
"""
|
|
block = blocks.StreamBlock([
|
|
('heading', blocks.CharBlock(template='tests/blocks/heading_block.html')),
|
|
('paragraph', blocks.CharBlock()),
|
|
], template='tests/blocks/stream_with_language.html')
|
|
|
|
stream_value = block.to_python([
|
|
{'type': 'heading', 'value': 'Bonjour'}
|
|
])
|
|
|
|
result = render_to_string('tests/blocks/include_block_test.html', {
|
|
'test_block': stream_value,
|
|
'language': 'fr',
|
|
})
|
|
|
|
self.assertIn('<div class="heading" lang="fr"><h1 lang="fr">Bonjour</h1></div>', result)
|
|
|
|
def test_include_block_tag_with_plain_value(self):
|
|
"""
|
|
The include_block tag should be able to render a value without a render_as_block method
|
|
by just rendering it as a string
|
|
"""
|
|
result = render_to_string('tests/blocks/include_block_test.html', {
|
|
'test_block': 42,
|
|
})
|
|
|
|
self.assertIn('<body>42</body>', result)
|
|
|
|
def test_include_block_tag_with_filtered_value(self):
|
|
"""
|
|
The block parameter on include_block tag should support complex values including filters,
|
|
e.g. {% include_block foo|default:123 %}
|
|
"""
|
|
block = blocks.CharBlock(template='tests/blocks/heading_block.html')
|
|
bound_block = block.bind('bonjour')
|
|
|
|
result = render_to_string('tests/blocks/include_block_test_with_filter.html', {
|
|
'test_block': bound_block,
|
|
'language': 'fr',
|
|
})
|
|
self.assertIn('<body><h1 lang="fr">bonjour</h1></body>', result)
|
|
|
|
result = render_to_string('tests/blocks/include_block_test_with_filter.html', {
|
|
'test_block': None,
|
|
'language': 'fr',
|
|
})
|
|
self.assertIn('<body>999</body>', result)
|
|
|
|
def test_include_block_tag_with_extra_context(self):
|
|
"""
|
|
Test that it's possible to pass extra context on an include_block tag using
|
|
{% include_block foo with classname="bar" %}
|
|
"""
|
|
block = blocks.CharBlock(template='tests/blocks/heading_block.html')
|
|
bound_block = block.bind('bonjour')
|
|
|
|
result = render_to_string('tests/blocks/include_block_with_test.html', {
|
|
'test_block': bound_block,
|
|
'language': 'fr',
|
|
})
|
|
self.assertIn('<body><h1 lang="fr" class="important">bonjour</h1></body>', result)
|
|
|
|
def test_include_block_tag_with_only_flag(self):
|
|
"""
|
|
A tag such as {% include_block foo with classname="bar" only %}
|
|
should not inherit the parent context
|
|
"""
|
|
block = blocks.CharBlock(template='tests/blocks/heading_block.html')
|
|
bound_block = block.bind('bonjour')
|
|
|
|
result = render_to_string('tests/blocks/include_block_only_test.html', {
|
|
'test_block': bound_block,
|
|
'language': 'fr',
|
|
})
|
|
self.assertIn('<body><h1 class="important">bonjour</h1></body>', result)
|