Refactor edit handler JavaScript integration

Edit handler panels which required JavaScript provided the JavaScript in
a `render_js` function, the output of which was collected together and
inserted after the `<script>` tags in the footer. Now that the
`<script>` tags are at the top of the `<body>`, these collected
initialisations were failing when they could not find the elements.

A new set of widgets have been created: `AdminPageChooser`,
`AdminImageChooser`, `AdminSnippetChooser`, `AdminDocumentChooser`.
These widgets output the initialisation JavaScript in a `<script>` tag
immediately following the widget itself, assuming that the required
external libraries and scripts are now included further up in the page.
This is how the GeoDjango fields from Django work, as well as many other
third party widgets. It makes writing integrations for arbitary widgets
easy, as they do not have to know anything about Wagtail edit handlers.

This also removes the need for the `fixPrefix()` JavaScript function
wrapping all field initialisers. As the fields are initialised as they
are inserted in to the page, any scripts included with them are inserted
at the same time. Additionally, the `__prefix__` placeholder in empty
form fields is also present in the initialisation scripts, and is fixed
at the same time. A template tag `escapescript` has been added to help
with this, to prevent `<script>` initialisation blocks from prematurely
closing the `<script type='text/django-form-template'>` tags.
This commit is contained in:
Tim Heap 2014-09-09 15:28:41 +10:00
parent f2b9615ac8
commit 385564afe5
24 changed files with 364 additions and 230 deletions

View file

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('wagtailcore', '0006_auto_20141008_1706'),
('tests', '0003_auto_20140905_0634'),
]
operations = [
migrations.CreateModel(
name='PageChooserModel',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('page', models.ForeignKey(help_text=b'help text', to='wagtailcore.Page')),
],
options={
},
bases=(models.Model,),
),
migrations.CreateModel(
name='SnippetChooserModel',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('advert', models.ForeignKey(help_text=b'help text', to='tests.Advert')),
],
options={
},
bases=(models.Model,),
),
]

View file

@ -17,6 +17,7 @@ from wagtail.wagtailimages.edit_handlers import ImageChooserPanel
from wagtail.wagtaildocs.edit_handlers import DocumentChooserPanel
from wagtail.wagtailforms.models import AbstractEmailForm, AbstractFormField
from wagtail.wagtailsnippets.models import register_snippet
from wagtail.wagtailsnippets.edit_handlers import SnippetChooserPanel
from wagtail.wagtailsearch import index
from wagtail.contrib.wagtailroutablepage.models import RoutablePage
@ -463,3 +464,15 @@ class TaggedPageTag(TaggedItemBase):
class TaggedPage(Page):
tags = ClusterTaggableManager(through=TaggedPageTag, blank=True)
class PageChooserModel(models.Model):
page = models.ForeignKey('wagtailcore.Page', help_text='help text')
class SnippetChooserModel(models.Model):
advert = models.ForeignKey(Advert, help_text='help text')
panels = [
SnippetChooserPanel('advert', Advert),
]

19
wagtail/utils/widgets.py Normal file
View file

@ -0,0 +1,19 @@
from django.forms.widgets import Widget
from django.utils.safestring import mark_safe
class WidgetWithScript(Widget):
def render(self, name, value, attrs=None):
widget = super(WidgetWithScript, self).render(name, value, attrs)
final_attrs = self.build_attrs(attrs, name=name)
id_ = final_attrs.get('id', None)
if 'id_' is None:
return widget
js = self.render_js_init(id_, name, value)
out = '{0}<script>{1}</script>'.format(widget, js)
return mark_safe(out)
def render_js_init(self, id_, name, value):
return ''

View file

@ -2,39 +2,32 @@ from __future__ import unicode_literals
import copy
from six import string_types
from six import text_type
from taggit.forms import TagWidget
from modelcluster.forms import ClusterForm, ClusterFormMetaclass
from django.db import models
from django.template.loader import render_to_string
from django.template.defaultfilters import addslashes
from django.utils.safestring import mark_safe
from django import forms
from django.forms.models import fields_for_model
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy
from taggit.managers import TaggableManager
from wagtail.wagtailadmin import widgets
from wagtail.wagtailcore.models import Page
from wagtail.wagtailcore.fields import RichTextArea
from wagtail.wagtailcore.utils import camelcase_to_underscore, resolve_model_string
FORM_FIELD_OVERRIDES = {}
WIDGET_JS = {
forms.DateInput: (lambda id: "initDateChooser(fixPrefix('%s'));" % id),
forms.TimeInput: (lambda id: "initTimeChooser(fixPrefix('%s'));" % id),
forms.DateTimeInput: (lambda id: "initDateTimeChooser(fixPrefix('%s'));" % id),
RichTextArea: (lambda id: "makeRichTextEditable(fixPrefix('%s'));" % id),
TagWidget: (
lambda id: "initTagField(fixPrefix('%s'), '%s');" % (
id, addslashes(reverse('wagtailadmin_tag_autocomplete'))
)
),
FORM_FIELD_OVERRIDES = {
models.DateField: {'widget': widgets.AdminDateInput},
models.TimeField: {'widget': widgets.AdminTimeInput},
models.DateTimeField: {'widget': widgets.AdminDateTimeInput},
TaggableManager: {'widget': widgets.AdminTagWidget}
}
@ -53,6 +46,10 @@ def formfield_for_dbfield(db_field, **kwargs):
return db_field.formfield(**kwargs)
def widget_with_script(widget, script):
return mark_safe('{0}<script>{1}</script>'.format(widget, script))
class WagtailAdminModelFormMetaclass(ClusterFormMetaclass):
# Override the behaviour of the regular ModelForm metaclass -
# which handles the translation of model fields to form fields -
@ -189,7 +186,7 @@ class EditHandler(object):
def classes(self):
"""
Additional CSS classnames to add to whatever kind of object this is at output.
Additional CSS classnames to add to whatever kind of object this is at output.
Subclasses of EditHandler should override this, invoking super(B, self).classes() to
append more classes specific to the situation.
"""
@ -224,13 +221,6 @@ class EditHandler(object):
# by default, assume that the subclass provides a catch-all render() method
return self.render()
def render_js(self):
"""
Render a snippet of Javascript code to be executed when this object's rendered
HTML is inserted into the DOM. (This won't necessarily happen on page load...)
"""
return ""
def rendered_fields(self):
"""
return a list of the fields of the passed form which are rendered by this
@ -303,9 +293,6 @@ class BaseCompositeEditHandler(EditHandler):
'self': self
}))
def render_js(self):
return mark_safe('\n'.join([handler.render_js() for handler in self.children]))
def rendered_fields(self):
result = []
for handler in self.children:
@ -337,21 +324,24 @@ def ObjectList(children, heading="", classname=""):
class BaseFieldRowPanel(BaseCompositeEditHandler):
template = "wagtailadmin/edit_handlers/field_row_panel.html"
def FieldRowPanel(children, classname=""):
return type(str('_FieldRowPanel'), (BaseFieldRowPanel,), {
'children': children,
'classname': classname,
})
class BaseMultiFieldPanel(BaseCompositeEditHandler):
template = "wagtailadmin/edit_handlers/multi_field_panel.html"
def classes(self):
classes = super(BaseMultiFieldPanel, self).classes()
classes.append("multi-field")
return classes
def MultiFieldPanel(children, heading="", classname=""):
return type(str('_MultiFieldPanel'), (BaseMultiFieldPanel,), {
'children': children,
@ -369,16 +359,16 @@ class BaseFieldPanel(EditHandler):
self.help_text = self.bound_field.help_text
def classes(self):
classes = super(BaseFieldPanel, self).classes();
classes = super(BaseFieldPanel, self).classes()
if self.bound_field.field.required:
classes.append("required")
if self.bound_field.errors:
classes.append("error")
classes.append(self.field_type())
classes.append("single-field")
return classes
def field_type(self):
@ -392,23 +382,16 @@ class BaseFieldPanel(EditHandler):
'field_content': self.render_as_field(show_help_text=False),
}))
def render_js(self):
try:
# see if there's an entry for this widget type in WIDGET_JS
js_func = WIDGET_JS[self.bound_field.field.widget.__class__]
except KeyError:
return ''
return mark_safe(js_func(self.bound_field.id_for_label))
field_template = "wagtailadmin/edit_handlers/field_panel_field.html"
def render_as_field(self, show_help_text=True):
return mark_safe(render_to_string(self.field_template, {
def render_as_field(self, show_help_text=True, extra_context={}):
context = {
'field': self.bound_field,
'field_type': self.field_type(),
'show_help_text': show_help_text,
}))
}
context.update(extra_context)
return mark_safe(render_to_string(self.field_template, context))
def rendered_fields(self):
return [self.field_name]
@ -422,8 +405,7 @@ def FieldPanel(field_name, classname=""):
class BaseRichTextFieldPanel(BaseFieldPanel):
def render_js(self):
return mark_safe("makeRichTextEditable(fixPrefix('%s'));" % self.bound_field.id_for_label)
pass
def RichTextFieldPanel(field_name):
@ -442,14 +424,7 @@ class BaseChooserPanel(BaseFieldPanel):
* field_template
* object_type_name - something like 'image' which will be used as the var name
for the object instance in the field_template
* js_function_name - a JS function responsible for the modal workflow; this receives
the ID of the hidden field as a parameter, and should ultimately populate that field
with the appropriate object ID. If the function requires any other parameters, the
subclass will need to override render_js instead.
"""
@classmethod
def widget_overrides(cls):
return {cls.field_name: forms.HiddenInput}
def get_chosen_item(self):
try:
@ -460,17 +435,16 @@ class BaseChooserPanel(BaseFieldPanel):
# like every other unpopulated field type. Yay consistency!
return None
def render_as_field(self, show_help_text=True):
def render_as_field(self, show_help_text=True, extra_context={}):
instance_obj = self.get_chosen_item()
return mark_safe(render_to_string(self.field_template, {
context = {
'field': self.bound_field,
self.object_type_name: instance_obj,
'is_chosen': bool(instance_obj),
'show_help_text': show_help_text,
}))
def render_js(self):
return mark_safe("%s(fixPrefix('%s'));" % (self.js_function_name, self.bound_field.id_for_label))
}
context.update(extra_context)
return mark_safe(render_to_string(self.field_template, context))
class BasePageChooserPanel(BaseChooserPanel):
@ -479,6 +453,11 @@ class BasePageChooserPanel(BaseChooserPanel):
_target_content_type = None
@classmethod
def widget_overrides(cls):
return {cls.field_name: widgets.AdminPageChooser(
content_type=cls.target_content_type())}
@classmethod
def target_content_type(cls):
if cls._target_content_type is None:
@ -499,28 +478,13 @@ class BasePageChooserPanel(BaseChooserPanel):
return cls._target_content_type
def render_as_field(self, show_help_text=True):
instance_obj = self.get_chosen_item()
return mark_safe(render_to_string(self.field_template, {
'field': self.bound_field,
self.object_type_name: instance_obj,
'is_chosen': bool(instance_obj),
'show_help_text': show_help_text,
def render_as_field(self, show_help_text=True, extra_context={}):
context = {
'choose_another_text_str': ugettext_lazy("Choose another page"),
'choose_one_text_str': ugettext_lazy("Choose a page"),
}))
def render_js(self):
page = self.get_chosen_item()
parent = page.get_parent() if page else None
content_type = self.__class__.target_content_type()
return mark_safe("createPageChooser(fixPrefix('%s'), '%s.%s', %s);" % (
self.bound_field.id_for_label,
content_type.app_label,
content_type.model,
(parent.id if parent else 'null'),
))
}
context.update(extra_context)
return super(BasePageChooserPanel, self).render_as_field(show_help_text, context)
def PageChooserPanel(field_name, page_type=None):
@ -596,14 +560,16 @@ class BaseInlinePanel(EditHandler):
template = "wagtailadmin/edit_handlers/inline_panel.html"
def render(self):
return mark_safe(render_to_string(self.template, {
formset = render_to_string(self.template, {
'self': self,
'can_order': self.formset.can_order,
}))
})
js = self.render_js_init()
return widget_with_script(formset, js)
js_template = "wagtailadmin/edit_handlers/inline_panel.js"
def render_js(self):
def render_js_init(self):
return mark_safe(render_to_string(self.js_template, {
'self': self,
'can_order': self.formset.can_order,
@ -622,14 +588,14 @@ def InlinePanel(base_model, relation_name, panels=None, label='', help_text=''):
# This allows users to include the publishing panel in their own per-model override
# without having to write these fields out by hand, potentially losing 'classname'
# without having to write these fields out by hand, potentially losing 'classname'
# and therefore the associated styling of the publishing panel
def PublishingPanel():
return MultiFieldPanel([
FieldRowPanel([
FieldPanel('go_live_at'),
FieldPanel('expire_at'),
], classname="label-above"),
], classname="label-above"),
], ugettext_lazy('Scheduled publishing'), classname="publishing")

View file

@ -16,12 +16,15 @@ function buildExpandingFormset(prefix, opts) {
}
addButton.click(function() {
var newFormHtml = emptyFormTemplate.replace(/__prefix__/g, formCount);
var newFormHtml = emptyFormTemplate
.replace(/__prefix__/g, formCount)
.replace(/<-(-*)\/script>/g, '<$1/script>');
formContainer.append(newFormHtml);
if (opts.onAdd) {
opts.onAdd(formCount);
}
formCount++;
totalFormsInput.val(formCount);
});

View file

@ -254,7 +254,7 @@ function InlinePanel(opts) {
}
self.updateMoveButtonDisabledStates();
opts.onAdd(fixPrefix);
opts.onAdd();
}
});

View file

@ -1,3 +1,5 @@
{% load wagtailadmin_tags %}
{{ self.formset.management_form }}
<ul class="multiple" id="id_{{ self.formset.prefix }}-FORMS">
{% for child in self.children %}
@ -6,7 +8,9 @@
</ul>
<script type="text/django-form-template" id="id_{{ self.formset.prefix }}-EMPTY_FORM_TEMPLATE">
{% include "wagtailadmin/edit_handlers/inline_panel_child.html" with child=self.empty_child %}
{% escapescript %}
{% include "wagtailadmin/edit_handlers/inline_panel_child.html" with child=self.empty_child %}
{% endescapescript %}
</script>
<p class="add">

View file

@ -1,17 +1,14 @@
(function() {
var panel = InlinePanel({
formsetPrefix: fixPrefix("id_{{ self.formset.prefix }}"),
emptyChildFormPrefix: fixPrefix("{{ self.empty_child.form.prefix }}"),
formsetPrefix: "id_{{ self.formset.prefix }}",
emptyChildFormPrefix: "{{ self.empty_child.form.prefix }}",
canOrder: {% if can_order %}true{% else %}false{% endif %},
onAdd: function(fixPrefix) {
{{ self.empty_child.render_js }}
}
onAdd: function() { }
});
{% for child in self.children %}
{{ child.render_js }}
panel.initChildControls(fixPrefix("{{ child.form.prefix }}"));
panel.initChildControls("{{ child.form.prefix }}");
{% endfor %}
panel.setHasContent();
panel.updateMoveButtonDisabledStates();

View file

@ -32,11 +32,3 @@
Additional js from widgets media. Allows for custom widgets in admin panel.
{% endcomment %}
{{ edit_handler.form.media.js }}
<script>
(function() {
function fixPrefix(str) {return str;}
{{ edit_handler.render_js }}
})();
</script>

View file

@ -1,5 +1,7 @@
from __future__ import unicode_literals
import re
from django.conf import settings
from django import template
@ -120,3 +122,25 @@ def hook_output(hook_name):
@register.assignment_tag
def usage_count_enabled():
return getattr(settings, 'WAGTAIL_USAGE_COUNT_ENABLED', False)
class EscapeScriptNode(template.Node):
TAG_NAME = 'escapescript'
SCRIPT_RE = re.compile(r'<(-*)/script>')
def __init__(self, nodelist):
super(EscapeScriptNode, self).__init__()
self.nodelist = nodelist
def render(self, context):
out = self.nodelist.render(context)
escaped_out = self.SCRIPT_RE.sub(r'<-\1/script>', out)
return escaped_out
@classmethod
def handle(cls, parser, token):
nodelist = parser.parse(('end' + EscapeScriptNode.TAG_NAME,))
parser.delete_first_token()
return cls(nodelist)
register.tag(EscapeScriptNode.TAG_NAME, EscapeScriptNode.handle)

View file

@ -1,25 +1,25 @@
from mock import MagicMock
from django.test import TestCase
from django.core.exceptions import ImproperlyConfigured
from django.forms.widgets import HiddenInput
from django.test import TestCase
from wagtail.wagtailadmin.edit_handlers import (
get_form_for_model,
extract_panel_definitions_from_model_class,
BaseFieldPanel,
FieldPanel,
RichTextFieldPanel,
EditHandler,
WagtailAdminModelForm,
BaseTabbedInterface,
TabbedInterface,
BaseObjectList,
ObjectList,
PageChooserPanel,
InlinePanel
InlinePanel,
)
from wagtail.wagtailadmin.widgets import AdminPageChooser
from wagtail.wagtailcore.models import Page, Site
from wagtail.tests.models import PageChooserModel
class TestGetFormForModel(TestCase):
@ -63,9 +63,6 @@ class TestExtractPanelDefinitionsFromModelClass(TestCase):
class TestTabbedInterface(TestCase):
class FakeChild(object):
class FakeGrandchild(object):
def render_js(self):
return "rendered js"
def rendered_fields(self):
return ["rendered fields"]
@ -101,10 +98,6 @@ class TestTabbedInterface(TestCase):
result = self.tabbed_interface.render()
self.assertIn('<div class="tab-content">', result)
def test_render_js(self):
result = self.tabbed_interface.render_js()
self.assertEqual(result, 'rendered js')
def test_rendered_fields(self):
result = self.tabbed_interface.rendered_fields()
self.assertEqual(result, ["rendered fields"])
@ -149,17 +142,6 @@ class TestFieldPanel(TestCase):
self.assertIn('<p class="error-message">',
result)
def test_render_js_unknown_widget(self):
field = self.FakeField()
bound_field = self.FakeField()
widget = self.FakeField()
field.widget = widget
bound_field.field = field
self.field_panel.bound_field = bound_field
result = self.field_panel.render_js()
self.assertEqual(result,
'')
def test_render_as_field(self):
field = self.FakeField()
bound_field = self.FakeField()
@ -194,10 +176,6 @@ class TestFieldPanel(TestCase):
result = FieldPanel('barbecue', 'snowman').get_form_class(Page)
self.assertTrue(issubclass(result, WagtailAdminModelForm))
def test_render_js(self):
result = self.field_panel.render_js()
self.assertEqual(result, "")
def test_render_missing_fields(self):
fake_form = self.FakeForm()
fake_form["foo"] = "bar"
@ -211,59 +189,33 @@ class TestFieldPanel(TestCase):
self.assertIn("bar", self.field_panel.render_form_content())
class TestRichTextFieldPanel(TestCase):
class FakeField(object):
label = 'label'
help_text = 'help text'
errors = ['errors']
id_for_label = 'id for label'
def test_render_js(self):
fake_field = self.FakeField()
rich_text_field_panel = RichTextFieldPanel('barbecue')(
instance=True,
form={'barbecue': fake_field})
result = rich_text_field_panel.render_js()
self.assertEqual(result,
"makeRichTextEditable(fixPrefix('id for label'));")
class TestPageChooserPanel(TestCase):
class FakeField(object):
label = 'label'
help_text = 'help text'
errors = ['errors']
id_for_label = 'id for label'
class FakeInstance(object):
class FakePage(object):
class FakeParent(object):
id = 1
name = 'fake page'
def get_parent(self):
return self.FakeParent()
def __init__(self):
fake_page = self.FakePage()
self.barbecue = fake_page
def setUp(self):
fake_field = self.FakeField()
fake_instance = self.FakeInstance()
self.page_chooser_panel = PageChooserPanel('barbecue')(
instance=fake_instance,
form={'barbecue': fake_field})
model = PageChooserModel
self.chosen_page = Page.objects.get(pk=2)
test_instance = model.objects.create(page=self.chosen_page)
self.dotted_model = model._meta.app_label + '.' + model._meta.model_name
def test_render_js(self):
result = self.page_chooser_panel.render_js()
self.assertEqual(result,
"createPageChooser(fixPrefix('id for label'), 'wagtailcore.page', 1);")
self.page_chooser_panel_class = PageChooserPanel('page', model)
form_class = get_form_for_model(model, widgets=self.page_chooser_panel_class.widget_overrides())
form = form_class(instance=test_instance)
form.errors['page'] = form.error_class(['errors'])
self.page_chooser_panel = self.page_chooser_panel_class(instance=test_instance,
form=form)
def test_render_js_init(self):
result = self.page_chooser_panel.render_as_field()
self.assertIn(
'createPageChooser("{id}", "{model}", {parent});'.format(
id="id_page", model=self.dotted_model, parent=self.chosen_page.get_parent().id),
result)
def test_get_chosen_item(self):
result = self.page_chooser_panel.get_chosen_item()
self.assertEqual(result.name, 'fake page')
self.assertEqual(result, self.chosen_page)
def test_render_as_field(self):
result = self.page_chooser_panel.render_as_field()
@ -272,7 +224,7 @@ class TestPageChooserPanel(TestCase):
def test_widget_overrides(self):
result = self.page_chooser_panel.widget_overrides()
self.assertEqual(result, {'barbecue': HiddenInput})
self.assertIsInstance(result['page'], AdminPageChooser)
def test_target_content_type(self):
result = PageChooserPanel(
@ -347,9 +299,6 @@ class TestInlinePanel(TestCase):
name = 'mock panel'
class FakeChild(object):
def render_js(self):
return "rendered js"
def rendered_fields(self):
return ["rendered fields"]
@ -407,10 +356,10 @@ class TestInlinePanel(TestCase):
form=self.fake_field)
self.assertIn('Add foo', inline_panel.render())
def test_render_js(self):
def test_render_js_init(self):
inline_panel = InlinePanel(self.mock_model,
'formset')(
instance=self.fake_instance,
form=self.fake_field)
self.assertIn('var panel = InlinePanel({',
inline_panel.render_js())
inline_panel.render_js_init())

View file

@ -0,0 +1,55 @@
from __future__ import absolute_import, unicode_literals
import json
from django.core.urlresolvers import reverse
from django.forms import widgets
from wagtail.utils.widgets import WidgetWithScript
from wagtail.wagtailcore.models import Page
from taggit.forms import TagWidget
class AdminDateInput(WidgetWithScript, widgets.DateInput):
def render_js_init(self, id_, name, value):
return 'initDateChooser({});'.format(json.dumps(id_))
class AdminTimeInput(WidgetWithScript, widgets.TimeInput):
def render_js_init(self, id_, name, value):
return 'initTimeChooser({});'.format(json.dumps(id_))
class AdminDateTimeInput(WidgetWithScript, widgets.DateTimeInput):
def render_js_init(self, id_, name, value):
return 'initDateTimeChooser({});'.format(json.dumps(id_))
class AdminTagWidget(WidgetWithScript, TagWidget):
def render_js_init(self, id_, name, value):
return "initTagField({0}, {1});".format(
json.dumps(id_),
json.dumps(reverse('wagtailadmin_tag_autocomplete')))
class AdminPageChooser(WidgetWithScript, widgets.Input):
input_type = 'hidden'
target_content_type = None
def __init__(self, content_type=None, **kwargs):
super(AdminPageChooser, self).__init__(**kwargs)
if content_type is not None:
self.target_content_type = content_type
def render_js_init(self, id_, name, value):
page = Page.objects.get(pk=value) if value else None
parent = page.get_parent() if page else None
content_type = self.target_content_type
return "createPageChooser({id}, {content_type}, {parent});".format(
id=json.dumps(id_),
content_type=json.dumps('{app}.{model}'.format(
app=content_type.app_label,
model=content_type.model)),
parent=json.dumps(parent.id if parent else None))

View file

@ -1,4 +1,8 @@
from __future__ import absolute_import, unicode_literals
import json
import django
from django.db import models
from django.forms import Textarea
@ -7,9 +11,10 @@ if django.VERSION < (1, 7):
add_introspection_rules([], ["^wagtail\.wagtailcore\.fields\.RichTextField"])
from wagtail.wagtailcore.rich_text import DbWhitelister, expand_db_html
from wagtail.utils.widgets import WidgetWithScript
class RichTextArea(Textarea):
class RichTextArea(WidgetWithScript, Textarea):
def get_panel(self):
from wagtail.wagtailadmin.edit_handlers import RichTextFieldPanel
return RichTextFieldPanel
@ -21,6 +26,9 @@ class RichTextArea(Textarea):
translated_value = expand_db_html(value, for_editor=True)
return super(RichTextArea, self).render(name, translated_value, attrs)
def render_js_init(self, id_, name, value):
return "makeRichTextEditable({0});".format(json.dumps(id_))
def value_from_datadict(self, data, files, name):
original_value = super(RichTextArea, self).value_from_datadict(data, files, name)
if original_value is None:

View file

@ -1,13 +1,19 @@
from __future__ import absolute_import, unicode_literals
from wagtail.wagtailadmin.edit_handlers import BaseChooserPanel
from .widgets import AdminDocumentChooser
class BaseDocumentChooserPanel(BaseChooserPanel):
field_template = "wagtaildocs/edit_handlers/document_chooser_panel.html"
object_type_name = "document"
js_function_name = "createDocumentChooser"
@classmethod
def widget_overrides(cls):
return {cls.field_name: AdminDocumentChooser}
def DocumentChooserPanel(field_name):
return type('_DocumentChooserPanel', (BaseDocumentChooserPanel,), {
return type(str('_DocumentChooserPanel'), (BaseDocumentChooserPanel,), {
'field_name': field_name,
})

View file

@ -0,0 +1,14 @@
from __future__ import absolute_import, unicode_literals
import json
from django.forms import widgets
from wagtail.utils.widgets import WidgetWithScript
class AdminDocumentChooser(WidgetWithScript, widgets.Input):
input_type = 'hidden'
def render_js_init(self, id_, name, value):
return "createDocumentChooser({0});".format(json.dumps(id_))

View file

@ -1,13 +1,19 @@
from __future__ import absolute_import, unicode_literals
from wagtail.wagtailadmin.edit_handlers import BaseChooserPanel
from .widgets import AdminImageChooser
class BaseImageChooserPanel(BaseChooserPanel):
field_template = "wagtailimages/edit_handlers/image_chooser_panel.html"
object_type_name = "image"
js_function_name = "createImageChooser"
@classmethod
def widget_overrides(cls):
return {cls.field_name: AdminImageChooser}
def ImageChooserPanel(field_name):
return type('_ImageChooserPanel', (BaseImageChooserPanel,), {
return type(str('_ImageChooserPanel'), (BaseImageChooserPanel,), {
'field_name': field_name,
})

View file

@ -0,0 +1,14 @@
from __future__ import absolute_import, unicode_literals
import json
from django.forms import widgets
from wagtail.utils.widgets import WidgetWithScript
class AdminImageChooser(WidgetWithScript, widgets.Input):
input_type = 'hidden'
def render_js_init(self, id_, name, value):
return "createImageChooser({0});".format(json.dumps(id_))

View file

@ -1,7 +1,10 @@
from django import forms
from django.contrib.contenttypes.models import ContentType
from django.forms.models import inlineformset_factory
from django.utils.translation import ugettext_lazy as _
from wagtail.wagtailadmin.widgets import AdminPageChooser
from wagtail.wagtailcore.models import Page
from wagtail.wagtailsearch import models
@ -18,7 +21,7 @@ class EditorsPickForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(EditorsPickForm, self).__init__(*args, **kwargs)
self.fields['page'].widget = forms.HiddenInput()
self.fields['page'].widget = AdminPageChooser(ContentType.objects.get_for_model(Page))
class Meta:
model = models.EditorsPick
@ -35,6 +38,7 @@ EditorsPickFormSetBase = inlineformset_factory(models.Query, models.EditorsPick,
class EditorsPickFormSet(EditorsPickFormSetBase):
minimum_forms = 1
minimum_forms_message = _("Please specify at least one recommendation for this search term.")
def add_fields(self, form, *args, **kwargs):
super(EditorsPickFormSet, self).add_fields(form, *args, **kwargs)

View file

@ -29,13 +29,6 @@
</ul>
</form>
</div>
{% endblock %}
{% block extra_css %}
{% include "wagtailadmin/pages/_editor_css.html" %}
{% endblock %}
{% block extra_js %}
{% include "wagtailadmin/pages/_editor_js.html" %}
<script type="text/javascript">
{% include "wagtailsearch/editorspicks/includes/editorspicks_formset.js" with formset=editors_pick_formset only %}
@ -45,4 +38,11 @@
createQueryChooser('{{ query_form.query_string.auto_id }}');
})();
</script>
{% endblock %}
{% endblock %}
{% block extra_css %}
{% include "wagtailadmin/pages/_editor_css.html" %}
{% endblock %}
{% block extra_js %}
{% include "wagtailadmin/pages/_editor_js.html" %}
{% endblock %}

View file

@ -1,4 +1,4 @@
{% load i18n %}
{% load i18n wagtailadmin_tags %}
{{ formset.management_form }}
<ul class="multiple" id="id_{{ formset.prefix }}-FORMS">
{% for form in formset.forms %}
@ -7,9 +7,11 @@
</ul>
<script type="text/django-form-template" id="id_{{ formset.prefix }}-EMPTY_FORM_TEMPLATE">
{% include "wagtailsearch/editorspicks/includes/editorspicks_form.html" with form=formset.empty_form only %}
{% escapescript %}
{% include "wagtailsearch/editorspicks/includes/editorspicks_form.html" with form=formset.empty_form only %}
{% endescapescript %}
</script>
<p class="add">
<a class="button bicolor icon icon-plus" id="id_{{ formset.prefix }}-ADD" value="Add">{% trans "Add recommended page" %}</a>
</p>
</p>

View file

@ -1,20 +1,15 @@
(function() {
function fixPrefix(str) {return str;}
var panel = InlinePanel({
formsetPrefix: fixPrefix("id_{{ formset.prefix }}"),
emptyChildFormPrefix: fixPrefix("{{ formset.empty_form.prefix }}"),
formsetPrefix: "id_{{ formset.prefix }}",
emptyChildFormPrefix: "{{ formset.empty_form.prefix }}",
canOrder: true,
onAdd: function(fixPrefix) {
createPageChooser(fixPrefix('id_{{ formset.prefix }}-__prefix__-page'), 'wagtailcore.page', null);
}
onAdd: function() { }
});
{% for form in formset.forms %}
createPageChooser(fixPrefix('id_{{ formset.prefix }}-{{ forloop.counter0 }}-page'), 'wagtailcore.page', null);
panel.initChildControls('{{ formset.prefix }}-{{ forloop.counter0 }}');
{% endfor %}
panel.updateMoveButtonDisabledStates();
})();
})();

View file

@ -1,9 +1,12 @@
from __future__ import absolute_import, unicode_literals
from django.template.loader import render_to_string
from django.contrib.contenttypes.models import ContentType
from django.utils.safestring import mark_safe
from django.utils.encoding import force_text
from wagtail.wagtailadmin.edit_handlers import BaseChooserPanel
from .widgets import AdminSnippetChooser
class BaseSnippetChooserPanel(BaseChooserPanel):
@ -12,6 +15,11 @@ class BaseSnippetChooserPanel(BaseChooserPanel):
_content_type = None
@classmethod
def widget_overrides(cls):
return {cls.field_name: AdminSnippetChooser(
content_type=cls.content_type())}
@classmethod
def content_type(cls):
if cls._content_type is None:
@ -30,18 +38,9 @@ class BaseSnippetChooserPanel(BaseChooserPanel):
'show_help_text': show_help_text,
}))
def render_js(self):
content_type = self.__class__.content_type()
return mark_safe("createSnippetChooser(fixPrefix('%s'), '%s/%s');" % (
self.bound_field.id_for_label,
content_type.app_label,
content_type.model,
))
def SnippetChooserPanel(field_name, snippet_type):
return type('_SnippetChooserPanel', (BaseSnippetChooserPanel,), {
return type(str('_SnippetChooserPanel'), (BaseSnippetChooserPanel,), {
'field_name': field_name,
'snippet_type_name': force_text(snippet_type._meta.verbose_name),
'snippet_type': snippet_type,

View file

@ -4,13 +4,12 @@ from django.db import models
from wagtail.tests.utils import WagtailTestUtils
from django.test.utils import override_settings
from wagtail.tests.models import Advert, AlphaSnippet, ZuluSnippet
from wagtail.tests.models import Advert, AlphaSnippet, ZuluSnippet, SnippetChooserModel
from wagtail.wagtailsnippets.models import register_snippet, SNIPPET_MODELS
from wagtail.wagtailsnippets.views.snippets import (
get_snippet_edit_handler
)
from wagtail.wagtailsnippets.edit_handlers import SnippetChooserPanel
from wagtail.wagtailcore.models import Page
@ -155,26 +154,30 @@ class TestSnippetChooserPanel(TestCase):
fixtures = ['wagtail/tests/fixtures/test.json']
def setUp(self):
content_type = Advert
test_snippet = Advert.objects.get(id=1)
model = SnippetChooserModel
self.advert_text = 'Test advert text'
test_snippet = model.objects.create(
advert=Advert.objects.create(text=self.advert_text))
edit_handler_class = get_snippet_edit_handler(Advert)
form_class = edit_handler_class.get_form_class(Advert)
edit_handler_class = get_snippet_edit_handler(model)
form_class = edit_handler_class.get_form_class(model)
form = form_class(instance=test_snippet)
edit_handler = edit_handler_class(instance=test_snippet, form=form)
self.snippet_chooser_panel_class = SnippetChooserPanel('text', content_type)
self.snippet_chooser_panel = self.snippet_chooser_panel_class(instance=test_snippet,
form=form)
self.snippet_chooser_panel = [
panel for panel in edit_handler.children
if getattr(panel, 'field_name', None) == 'advert'][0]
def test_create_snippet_chooser_panel_class(self):
self.assertEqual(self.snippet_chooser_panel_class.__name__, '_SnippetChooserPanel')
self.assertEqual(type(self.snippet_chooser_panel).__name__,
'_SnippetChooserPanel')
def test_render_as_field(self):
self.assertTrue('test_advert' in self.snippet_chooser_panel.render_as_field())
self.assertTrue(self.advert_text in self.snippet_chooser_panel.render_as_field())
def test_render_js(self):
self.assertTrue("createSnippetChooser(fixPrefix('id_text'), 'tests/advert');"
in self.snippet_chooser_panel.render_js())
self.assertIn('createSnippetChooser("id_advert", "tests/advert");',
self.snippet_chooser_panel.render_as_field())
class TestSnippetRegistering(TestCase):

View file

@ -0,0 +1,26 @@
from __future__ import absolute_import, unicode_literals
import json
from django.forms import widgets
from wagtail.utils.widgets import WidgetWithScript
class AdminSnippetChooser(WidgetWithScript, widgets.Input):
input_type = 'hidden'
target_content_type = None
def __init__(self, content_type=None, **kwargs):
super(AdminSnippetChooser, self).__init__(**kwargs)
if content_type is not None:
self.target_content_type = content_type
def render_js_init(self, id_, name, value):
content_type = self.target_content_type
return "createSnippetChooser({id}, {content_type});".format(
id=json.dumps(id_),
content_type=json.dumps('{app}/{model}'.format(
app=content_type.app_label,
model=content_type.model)))