mirror of
https://github.com/Hopiu/wagtail.git
synced 2026-04-12 11:00:59 +00:00
Add a 'RichText' value type, and use it as the native value of RichTextBlock
This means that they consistently render as rich text regardless of how they are output onto the template (as a child of StreamBlock or StructBlock; with or without a |richtext filter).
This commit is contained in:
parent
eec194f154
commit
4da28acb2a
5 changed files with 113 additions and 17 deletions
|
|
@ -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('<div class="rich-text">' + expand_db_html(value) + '</div>')
|
||||
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):
|
||||
|
|
|
|||
|
|
@ -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('<div class="rich-text">' + expand_db_html(self.source) + '</div>')
|
||||
|
|
|
|||
|
|
@ -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('<div class="rich-text">' + html + '</div>')
|
||||
|
|
|
|||
|
|
@ -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='<p>foo</p>').get_default()
|
||||
self.assertTrue(isinstance(default_value, RichText))
|
||||
self.assertEqual(default_value.source, '<p>foo</p>')
|
||||
|
||||
default_value = blocks.RichTextBlock(default=blocks.RichText('<p>foo</p>')).get_default()
|
||||
self.assertTrue(isinstance(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" 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):
|
||||
|
|
|
|||
|
|
@ -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 = '<embed embedtype="media" url="http://www.youtube.com/watch" />'
|
||||
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('<p>hello world</p>')
|
||||
self.assertEqual(value.source, '<p>hello world</p>')
|
||||
|
||||
def test_render(self):
|
||||
value = RichText('<p>Merry <a linktype="page" id="4">Christmas</a>!</p>')
|
||||
result = str(value)
|
||||
self.assertEqual(result, '<div class="rich-text"><p>Merry <a href="/events/christmas/">Christmas</a>!</p></div>')
|
||||
|
|
|
|||
Loading…
Reference in a new issue