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, '')
+
+ 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, '')