diff --git a/wagtail/wagtailcore/blocks/field_block.py b/wagtail/wagtailcore/blocks/field_block.py index ddc740a21..d01141d46 100644 --- a/wagtail/wagtailcore/blocks/field_block.py +++ b/wagtail/wagtailcore/blocks/field_block.py @@ -11,7 +11,7 @@ from django.utils.functional import cached_property from django.utils.html import format_html from django.utils.safestring import mark_safe -from wagtail.wagtailcore.rich_text import expand_db_html +from wagtail.wagtailcore.rich_text import RichText from .base import Block @@ -235,23 +235,44 @@ class ChoiceBlock(FieldBlock): return ('wagtail.wagtailcore.blocks.ChoiceBlock', [], self._constructor_kwargs) - class RichTextBlock(FieldBlock): def __init__(self, required=True, help_text=None, **kwargs): self.field_options = {'required': required, 'help_text': help_text} super(RichTextBlock, self).__init__(**kwargs) + def get_default(self): + if isinstance(self.meta.default, RichText): + return self.meta.default + else: + return RichText(self.meta.default) + + def to_python(self, value): + # convert a source-HTML string from the JSONish representation + # to a RichText object + return RichText(value) + + def get_prep_value(self, value): + # convert a RichText object back to a source-HTML string to go into + # the JSONish representation + return value.source + @cached_property def field(self): from wagtail.wagtailcore.fields import RichTextArea return forms.CharField(widget=RichTextArea, **self.field_options) - def render_basic(self, value): - return mark_safe('
' + expand_db_html(value) + '
') + def value_for_form(self, value): + # RichTextArea takes the source-HTML string as input (and takes care + # of expanding it for the purposes of the editor) + return value.source + + def value_from_form(self, value): + # RichTextArea returns a source-HTML string; concert to a RichText object + return RichText(value) def get_searchable_content(self, value): - return [force_text(value)] + return [force_text(value.source)] class RawHTMLBlock(FieldBlock): diff --git a/wagtail/wagtailcore/rich_text.py b/wagtail/wagtailcore/rich_text.py index 60159ca5c..2ba1a52ed 100644 --- a/wagtail/wagtailcore/rich_text.py +++ b/wagtail/wagtailcore/rich_text.py @@ -1,6 +1,7 @@ import re # parsing HTML with regexes LIKE A BOSS. from django.utils.html import escape +from django.utils.safestring import mark_safe from wagtail.wagtailcore.whitelist import Whitelister from wagtail.wagtailcore.models import Page @@ -170,3 +171,17 @@ def expand_db_html(html, for_editor=False): html = FIND_A_TAG.sub(replace_a_tag, html) html = FIND_EMBED_TAG.sub(replace_embed_tag, html) return html + + +class RichText(object): + """ + A custom object used to represent a renderable rich text value. + Provides a 'source' property to access the original source code, + and renders to the front-end HTML rendering. + Used as the native value of a wagtailcore.blocks.field_block.RichTextBlock. + """ + def __init__(self, source): + self.source = (source or '') + + def __str__(self): + return mark_safe('
' + expand_db_html(self.source) + '
') diff --git a/wagtail/wagtailcore/templatetags/wagtailcore_tags.py b/wagtail/wagtailcore/templatetags/wagtailcore_tags.py index c0cf66e29..40d5db87f 100644 --- a/wagtail/wagtailcore/templatetags/wagtailcore_tags.py +++ b/wagtail/wagtailcore/templatetags/wagtailcore_tags.py @@ -2,7 +2,7 @@ from django import template from django.utils.safestring import mark_safe from wagtail.wagtailcore.models import Page -from wagtail.wagtailcore.rich_text import expand_db_html +from wagtail.wagtailcore.rich_text import expand_db_html, RichText from wagtail.wagtailcore import __version__ register = template.Library() @@ -35,9 +35,12 @@ def wagtail_version(): @register.filter def richtext(value): - if value is not None: - html = expand_db_html(value) - else: + if isinstance(value, RichText): + # passing a RichText value through the |richtext filter should have no effect + return value + elif value is None: html = '' + else: + html = expand_db_html(value) return mark_safe('
' + html + '
') diff --git a/wagtail/wagtailcore/tests/test_blocks.py b/wagtail/wagtailcore/tests/test_blocks.py index a4d38ce2e..ca9af3c35 100644 --- a/wagtail/wagtailcore/tests/test_blocks.py +++ b/wagtail/wagtailcore/tests/test_blocks.py @@ -6,6 +6,7 @@ from django.core.exceptions import ValidationError from django.test import TestCase from wagtail.wagtailcore import blocks +from wagtail.wagtailcore.rich_text import RichText from wagtail.wagtailcore.models import Page import base64 @@ -106,21 +107,57 @@ class TestFieldBlock(unittest.TestCase): self.assertEqual('hello world', value_from_form) -class TestRichTextBlock(unittest.TestCase): +class TestRichTextBlock(TestCase): + fixtures = ['test.json'] + + def test_get_default(self): + default_value = blocks.RichTextBlock().get_default() + self.assertTrue(isinstance(default_value, RichText)) + self.assertEqual(default_value.source, '') + + default_value = blocks.RichTextBlock(default=None).get_default() + self.assertTrue(isinstance(default_value, RichText)) + self.assertEqual(default_value.source, '') + + default_value = blocks.RichTextBlock(default='').get_default() + self.assertTrue(isinstance(default_value, RichText)) + self.assertEqual(default_value.source, '') + + default_value = blocks.RichTextBlock(default='

foo

').get_default() + self.assertTrue(isinstance(default_value, RichText)) + self.assertEqual(default_value.source, '

foo

') + + default_value = blocks.RichTextBlock(default=blocks.RichText('

foo

')).get_default() + self.assertTrue(isinstance(default_value, RichText)) + self.assertEqual(default_value.source, '

foo

') + + def test_render(self): + block = blocks.RichTextBlock() + value = RichText('

Merry Christmas!

') + result = block.render(value) + self.assertEqual(result, '

Merry Christmas!

') + + 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 elements) + """ + block = blocks.RichTextBlock() + value = RichText('

Merry Christmas!

') + result = block.render_form(value, prefix='richtext') + self.assertIn('<p>Merry <a data-linktype="page" data-id="4" href="/events/christmas/">Christmas</a>!</p>', result) def test_validate_required_richtext_block(self): block = blocks.RichTextBlock() with self.assertRaises(ValidationError): - block.clean('') - - with self.assertRaises(ValidationError): - block.clean(None) + block.clean(blocks.RichText('')) def test_validate_non_required_richtext_block(self): block = blocks.RichTextBlock(required=False) - self.assertEqual(block.clean(''), '') - self.assertEqual(block.clean(None), '') + result = block.clean(RichText('')) + self.assertTrue(isinstance(result, RichText)) + self.assertEqual(result.source, '') class TestChoiceBlock(unittest.TestCase): diff --git a/wagtail/wagtailcore/tests/test_rich_text.py b/wagtail/wagtailcore/tests/test_rich_text.py index bc12b4fd7..2afd991c2 100644 --- a/wagtail/wagtailcore/tests/test_rich_text.py +++ b/wagtail/wagtailcore/tests/test_rich_text.py @@ -6,7 +6,8 @@ from wagtail.wagtailcore.rich_text import ( PageLinkHandler, DbWhitelister, extract_attrs, - expand_db_html + expand_db_html, + RichText ) from bs4 import BeautifulSoup @@ -115,3 +116,22 @@ class TestExpandDbHtml(TestCase): html = '' result = expand_db_html(html) self.assertIn('test html', result) + + +class TestRichTextValue(TestCase): + fixtures = ['test.json'] + + def test_construct(self): + value = RichText(None) + self.assertEqual(value.source, '') + + value = RichText('') + self.assertEqual(value.source, '') + + value = RichText('

hello world

') + self.assertEqual(value.source, '

hello world

') + + def test_render(self): + value = RichText('

Merry Christmas!

') + result = str(value) + self.assertEqual(result, '

Merry Christmas!

')