Skip to content

Commit

Permalink
Adapt code injection for telepath
Browse files Browse the repository at this point in the history
Wagtail released 2.13 while I was in the middle of this work and it
breaks using templates to insert JS code.  Instead, we now use
a telepath adapter.
  • Loading branch information
aaronhaslett committed Jun 2, 2021
1 parent 12911f2 commit 8bcb6c6
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 54 deletions.
35 changes: 8 additions & 27 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,40 +51,21 @@ Usage
To select a model from a remote API, respectively use ``RemoteModelChooserBlock`` and ``RemoteModelChooserPanel`` instead.

If you have `WagtailDraftail <https://github.com/springload/wagtaildraftail>`_ installed, it will automatically register the ``ModelSource`` and ``RemoteModelSource`` to the JS. Refer to ``WagtailDraftail``'s `documentation <https://github.com/springload/wagtaildraftail#configuration>`_ to hook it up properly.

If you want your choosers to be available in draftail, put a unique 'draftail_type' into each chooser configuration, then put the DraftailJSRenderMixin into the Draftail rich text area widget class in your project. If you're on default wagtail, you can do that in settings/base.py like this:
If you want a chooser to be available in draftail, put a unique 'draftail_type' into its configuration dict in ``MODEL_CHOOSER_OPTIONS``, then add the chooser name to rich text features. You can do the latter either by adding it explicitly in a feature list like this:

.. code:: python
WAGTAILADMIN_RICH_TEXT_EDITORS = {
"default": {
"WIDGET": "myproject.widgets.MyNewDraftailRichTextArea"
}
}
rich_text_block = RichTextBlock(features=["custom_model"])
Then in myproject.widgets do this:

.. code:: python
from wagtailmodelchoosers.widgets import DraftailJSRenderMixin
class MyNewDraftailRichTextArea(DraftailJSRenderMixin, DraftailRichTextArea):
pass
Now the modelchoosers you define in ``MODEL_CHOOSER_OPTIONS`` (format described in next section) in your project settings are available as features in your rich text blocks, and you can pass them like this:

.. code:: python
rich_text_block = RichTextBlock(features="mymodel")
If you want some of your models to be available in your rich text toolbar by default (so you don't have to construct feature lists), go to myproject.wagtail_hooks.py and add this for each model you want to be available:
Or adding it to the list of default features for all rich text blocks like this:

.. code:: python
@hooks.register("register_rich_text_features")
def register_rich_text_features(features):
features.default_features.append("mymodel")
features.default_features.append("custom_model")
Note: It's easy to confuse draftail_type with the chooser's name. See the Configuration section if you're not sure which is which. The only place you need to put the draftail_type is in settings. The value you put there gets passed into draftail as an entity type, so the blocks created by that modelchooser toolbar option have the block type equal to the draftail_type you declared. This is useful if you're doing something custom with the contentstate.

Configuration
~~~~~~~~~~~~~
Expand All @@ -96,7 +77,7 @@ The ModelChooser and RemoteModelChooser share a similar base configuration and o
.. code:: python
MODEL_CHOOSERS_OPTIONS = {
'navigation': {
'navigation': { # The chooser name
'label': 'Navigation', # The label to use for buttons or modal title
'display': 'name', # The field to display when selecting an object
'list_display': [ # The fields to display in the chooser
Expand All @@ -108,7 +89,7 @@ The ModelChooser and RemoteModelChooser share a similar base configuration and o
'fields_to_save': ['id'] + RATE_CHOOSER_DISPLAY_FIELDS, # ONLY FOR REMOTE: The remote objects fields to save to the DB. Leave empty to save the whole object.
'remote_endpoint': 'http://...' # ONLY FOR REMOTE: The remote API endpoint.
'pk_name': 'uuid', # The primary key name of the model
'draftail_type': 'NAV', # Only add this if you want the model available in draftail. Must be unique.
'draftail_type': 'NAV', # Only add this if you want the model available in draftail. Must be unique and can't clash with any default wagtail types.
}
}
Expand Down
6 changes: 3 additions & 3 deletions wagtailmodelchoosers/client/draftailmodelchoosers.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import DefaultDecorator from './DefaultDecorator';

const WrapDecorator = (entityData, decorator) => (...args) => decorator(entityData, ...args);

const modelChooserDraftailInit = (modelChooserEntityTypes, draftailOptions, widgetAttrIds) => {
const modelChooserDraftailInit = (draftailOptions, modelChooserEntityTypes, constructor) => {
// Save entities for decorators to use. Doing this because I can't find a way to get entity type
// data from within a decorator without saving it to contentstate, which ends up getting saved
// to DB, which is unnecessary and breaks historical data.
Expand All @@ -25,7 +25,7 @@ const modelChooserDraftailInit = (modelChooserEntityTypes, draftailOptions, widg
window.draftail.registerPlugin(plugin);
});

window.draftail.initEditor(widgetAttrIds, draftailOptions, document.currentScript);
return new window.telepath.constructors[constructor](draftailOptions);
};

window.modelChooserDraftailInit = modelChooserDraftailInit;
window.telepath.register('modelChooserDraftailInit', modelChooserDraftailInit);

This file was deleted.

29 changes: 28 additions & 1 deletion wagtailmodelchoosers/wagtail_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@
from django.utils.html import format_html, format_html_join
from draftjs_exporter.dom import DOM
from wagtail.admin.rich_text.converters.html_to_contentstate import InlineEntityElementHandler
from wagtail.core import hooks
from wagtail.admin.rich_text.editors.draftail import (
DraftailRichTextArea,
DraftailRichTextAreaAdapter,
)
from wagtail.core import hooks, telepath
from wagtail.core.rich_text import LinkHandler
from wagtail.core.widget_adapters import WidgetAdapter

from wagtailmodelchoosers.views import ModelView, RemoteResourceView

Expand Down Expand Up @@ -94,3 +99,25 @@ def register_rich_text_features(features):
"to_database_format": {"entity_decorators": {draftail_type: _to}},
},
)


class ModelChooserDraftailRichTextAreaAdapter(DraftailRichTextAreaAdapter):
js_constructor = "modelChooserDraftailInit"

class Media:
js = ["wagtailmodelchoosers/draftailmodelchoosers.js"]

def js_args(self, *args, **kwargs):
js_args = super().js_args(*args, **kwargs)

# Give it all draftail types to register
f = "draftail_type"
js_args.append([c[f] for c in get_all_chooser_options().values() if f in c])

# Upstream's JS constructor to call with options
js_args.append(super().js_constructor)

return js_args


telepath.register(ModelChooserDraftailRichTextAreaAdapter(), DraftailRichTextArea)
19 changes: 0 additions & 19 deletions wagtailmodelchoosers/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
from django.forms import Media, widgets
from django.template.loader import render_to_string
from django.utils.functional import cached_property
from wagtail.admin.rich_text.editors.draftail import DraftailRichTextArea
from wagtail.admin.staticfiles import versioned_static
from wagtail.utils.widgets import WidgetWithScript

from .utils import first_non_empty, get_all_chooser_options
Expand Down Expand Up @@ -209,20 +207,3 @@ def render_html(self, name, value, attrs):
}

return render_to_string(self.template_name, context)


class DraftailJSRenderMixin(DraftailRichTextArea):
template_name = "wagtailmodelchoosers/widgets/draftail_rich_text_area.html"

@cached_property
def media(self):
return super().media + Media(
js=[versioned_static("wagtailmodelchoosers/draftailmodelchoosers.js")]
)

def get_context(self, *args, **kwargs):
context = super().get_context(*args, **kwargs)
f = "draftail_type"
met = [c[f] for c in get_all_chooser_options().values() if f in c]
context["modelchooser_entity_types"] = json.dumps(met)
return context

0 comments on commit 8bcb6c6

Please sign in to comment.