Permalink
Browse files

Support building a custom hallo plugins list from a 'features' kwarg …

…on RichTextField
  • Loading branch information...
gasman committed Jul 13, 2017
1 parent 31cf4b6 commit 3532f8662aef9fcc169da44edd934ebecf232077
@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.1 on 2017-07-13 22:20
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import wagtail.wagtailcore.fields
class Migration(migrations.Migration):
dependencies = [
('wagtailcore', '0040_page_draft_title'),
('tests', '0018_multiselect_form_field'),
]
operations = [
migrations.CreateModel(
name='RichTextFieldWithFeaturesPage',
fields=[
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.Page')),
('body', wagtail.wagtailcore.fields.RichTextField()),
],
options={
'abstract': False,
},
bases=('wagtailcore.page',),
),
]
@@ -911,6 +911,15 @@ class CustomRichBlockFieldPage(Page):
]
class RichTextFieldWithFeaturesPage(Page):
body = RichTextField(features=['blockquote', 'embed', 'made-up-feature'])
content_panels = [
FieldPanel('title', classname="full title"),
FieldPanel('body'),
]
# a page that only contains RichTextField within an InlinePanel,
# to test that the inline child's form media gets pulled through
class SectionedRichTextPageSection(Orderable):
@@ -5,6 +5,7 @@
from django.http import HttpResponse
from wagtail.wagtailadmin.menu import MenuItem
from wagtail.wagtailadmin.rich_text import HalloPlugin
from wagtail.wagtailadmin.search import SearchArea
from wagtail.wagtailcore import hooks
from wagtail.wagtailcore.whitelist import allow_without_attributes, attribute_rule, check_url
@@ -96,5 +97,5 @@ def polite_pages_only(parent_page, pages, request):
@hooks.register('register_rich_text_features')
def register_blockquote_feature(features):
features.register_editor_plugin(
'hallo', 'blockquote', {'plugin_name': 'halloblockquote'}
'hallo', 'blockquote', HalloPlugin(name='halloblockquote')
)
@@ -9,15 +9,30 @@
from wagtail.utils.widgets import WidgetWithScript
from wagtail.wagtailadmin.edit_handlers import RichTextFieldPanel
from wagtail.wagtailcore.rich_text import DbWhitelister, expand_db_html
from wagtail.wagtailcore.rich_text import DbWhitelister, expand_db_html, features
class HalloRichTextArea(WidgetWithScript, widgets.Textarea):
# this class's constructor accepts a 'features' kwarg
accepts_features = True
def get_panel(self):
return RichTextFieldPanel
def __init__(self, *args, **kwargs):
self.options = kwargs.pop('options', None)
self.features = kwargs.pop('features', None)
if self.features is None:
self.plugins = None
else:
# construct a list of plugin objects, by querying the feature registry
# and keeping the non-null responses from get_editor_plugin
self.plugins = filter(None, [
features.get_editor_plugin('hallo', feature_name)
for feature_name in self.features
])
super(HalloRichTextArea, self).__init__(*args, **kwargs)
def render(self, name, value, attrs=None):
@@ -28,15 +43,19 @@ def render(self, name, value, attrs=None):
return super(HalloRichTextArea, self).render(name, translated_value, attrs)
def render_js_init(self, id_, name, value):
try:
plugins = self.options['plugins']
except (TypeError, KeyError):
if self.options is not None and 'plugins' in self.options:
plugin_data = self.options['plugins']
elif self.plugins is not None:
plugin_data = {}
for plugin in self.plugins:
plugin.construct_plugins_list(plugin_data)
else:
# no plugin list specified, so initialise without a plugins arg
# (so that it'll pick up the globally-defined halloPlugins list instead)
return "makeHalloRichTextEditable({0});".format(json.dumps(id_))
return "makeHalloRichTextEditable({0}, {1});".format(
json.dumps(id_), json.dumps(plugins)
json.dumps(id_), json.dumps(plugin_data)
)
def value_from_datadict(self, data, files, name):
@@ -56,20 +75,36 @@ def media(self):
])
class HalloPlugin(object):
def __init__(self, **kwargs):
self.name = kwargs.pop('name', None)
self.options = kwargs.pop('options', {})
def construct_plugins_list(self, plugins):
if self.name is not None:
plugins[self.name] = self.options
DEFAULT_RICH_TEXT_EDITORS = {
'default': {
'WIDGET': 'wagtail.wagtailadmin.rich_text.HalloRichTextArea'
}
}
def get_rich_text_editor_widget(name='default'):
def get_rich_text_editor_widget(name='default', features=None):
editor_settings = getattr(settings, 'WAGTAILADMIN_RICH_TEXT_EDITORS', DEFAULT_RICH_TEXT_EDITORS)
editor = editor_settings[name]
options = editor.get('OPTIONS', None)
cls = import_string(editor['WIDGET'])
kwargs = {}
if options is not None:
kwargs['options'] = options
if options is None:
return import_string(editor['WIDGET'])()
if getattr(cls, 'accepts_features', False):
kwargs['features'] = features
return import_string(editor['WIDGET'])(options=options)
return cls(**kwargs)
@@ -265,3 +265,39 @@ def test_custom_editor_in_rich_text_block(self):
# Check that the custom plugin options are being passed in the hallo initialiser
self.assertIn('makeHalloRichTextEditable("body", {"halloheadings": {"formatBlocks": ["p"]}});', form_html)
class TestHalloJsWithFeaturesKwarg(BaseRichTextEditHandlerTestCase, WagtailTestUtils):
def setUp(self):
super(TestHalloJsWithFeaturesKwarg, self).setUp()
# Find root page
self.root_page = Page.objects.get(id=2)
self.login()
def test_features_list_on_rich_text_field(self):
response = self.client.get(reverse(
'wagtailadmin_pages:add', args=('tests', 'richtextfieldwithfeaturespage', self.root_page.id)
))
# Check status code
self.assertEqual(response.status_code, 200)
# Check that the custom plugin options are being passed in the hallo initialiser
self.assertContains(response, '"halloblockquote":')
self.assertContains(response, '"hallowagtailembeds":')
self.assertNotContains(response, '"halloheadings":')
self.assertNotContains(response, '"hallowagtailimage":')
def test_features_list_on_rich_text_block(self):
block = RichTextBlock(features=['blockquote', 'embed', 'made-up-feature'])
form_html = block.render_form(block.to_python("<p>hello</p>"), 'body')
# Check that the custom plugin options are being passed in the hallo initialiser
self.assertIn('"halloblockquote":', form_html)
self.assertIn('"hallowagtailembeds":', form_html)
self.assertNotIn('"halloheadings":', form_html)
self.assertNotIn('"hallowagtailimage":', form_html)
@@ -450,9 +450,10 @@ class Meta:
class RichTextBlock(FieldBlock):
def __init__(self, required=True, help_text=None, editor='default', **kwargs):
def __init__(self, required=True, help_text=None, editor='default', features=None, **kwargs):
self.field_options = {'required': required, 'help_text': help_text}
self.editor = editor
self.features = features
super(RichTextBlock, self).__init__(**kwargs)
def get_default(self):
@@ -474,7 +475,10 @@ def get_prep_value(self, value):
@cached_property
def field(self):
from wagtail.wagtailadmin.rich_text import get_rich_text_editor_widget
return forms.CharField(widget=get_rich_text_editor_widget(self.editor), **self.field_options)
return forms.CharField(
widget=get_rich_text_editor_widget(self.editor, features=self.features),
**self.field_options
)
def value_for_form(self, value):
# Rich text editors take the source-HTML string as input (and takes care
@@ -12,11 +12,13 @@
class RichTextField(models.TextField):
def __init__(self, *args, **kwargs):
self.editor = kwargs.pop('editor', 'default')
self.features = kwargs.pop('features', None)
# TODO: preserve 'editor' and 'features' when deconstructing for migrations
super(RichTextField, self).__init__(*args, **kwargs)
def formfield(self, **kwargs):
from wagtail.wagtailadmin.rich_text import get_rich_text_editor_widget
defaults = {'widget': get_rich_text_editor_widget(self.editor)}
defaults = {'widget': get_rich_text_editor_widget(self.editor, features=self.features)}
defaults.update(kwargs)
return super(RichTextField, self).formfield(**defaults)
@@ -152,7 +152,7 @@ def test_register_rich_text_features_hook(self):
# plugin, via the register_rich_text_features hook; test that we can retrieve it here
features = FeatureRegistry()
blockquote = features.get_editor_plugin('hallo', 'blockquote')
self.assertEqual(blockquote['plugin_name'], 'halloblockquote')
self.assertEqual(blockquote.name, 'halloblockquote')
def test_missing_editor_plugin_returns_none(self):
features = FeatureRegistry()
@@ -10,6 +10,7 @@
from django.utils.translation import ungettext
from wagtail.wagtailadmin.menu import MenuItem
from wagtail.wagtailadmin.rich_text import HalloPlugin
from wagtail.wagtailadmin.search import SearchArea
from wagtail.wagtailadmin.site_summary import SummaryItem
from wagtail.wagtailcore import hooks
@@ -74,6 +75,14 @@ def editor_js():
)
@hooks.register('register_rich_text_features')
def register_embed_feature(features):
features.register_editor_plugin(
'hallo', 'document-link',
HalloPlugin(name='hallowagtaildoclink')
)
@hooks.register('register_rich_text_link_handler')
def register_document_link_handler():
return ('document', DocumentLinkHandler)
@@ -5,6 +5,7 @@
from django.core import urlresolvers
from django.utils.html import format_html
from wagtail.wagtailadmin.rich_text import HalloPlugin
from wagtail.wagtailcore import hooks
from wagtail.wagtailembeds import urls
from wagtail.wagtailembeds.rich_text import MediaEmbedHandler
@@ -32,6 +33,14 @@ def editor_js():
)
@hooks.register('register_rich_text_features')
def register_embed_feature(features):
features.register_editor_plugin(
'hallo', 'embed',
HalloPlugin(name='hallowagtailembeds')
)
@hooks.register('register_rich_text_embed_handler')
def register_media_embed_handler():
return ('media', MediaEmbedHandler)
@@ -8,6 +8,7 @@
from django.utils.translation import ungettext
from wagtail.wagtailadmin.menu import MenuItem
from wagtail.wagtailadmin.rich_text import HalloPlugin
from wagtail.wagtailadmin.search import SearchArea
from wagtail.wagtailadmin.site_summary import SummaryItem
from wagtail.wagtailcore import hooks
@@ -66,6 +67,14 @@ def editor_js():
)
@hooks.register('register_rich_text_features')
def register_image_feature(features):
features.register_editor_plugin(
'hallo', 'image',
HalloPlugin(name='hallowagtailimage')
)
@hooks.register('register_image_operations')
def register_image_operations():
return [

0 comments on commit 3532f86

Please sign in to comment.