Rewrite wagtail.core.telepath to recursively pack constructor args

js_args and get_media methods no longer receive the context object, since they are not responsible for packing their own arguments.
pull/6931/head
Matt Westcott 2021-01-12 14:39:47 +00:00
rodzic ac0c5d7ef5
commit d4dc02ad0f
11 zmienionych plików z 100 dodań i 28 usunięć

Wyświetl plik

@ -86,7 +86,7 @@ class DraftailRichTextArea(widgets.HiddenInput):
class DraftailRichTextAreaAdapter(WidgetAdapter):
js_constructor = 'wagtail.widgets.DraftailRichTextArea'
def js_args(self, widget, context):
def js_args(self, widget):
return [
widget.options,
]

Wyświetl plik

@ -173,7 +173,7 @@ class AdminPageChooser(AdminChooser):
class PageChooserAdapter(WidgetAdapter):
js_constructor = 'wagtail.widgets.PageChooser'
def js_args(self, widget, context):
def js_args(self, widget):
return [
widget.render_html('__NAME__', None, attrs={'id': '__ID__'}),
widget.id_for_label('__ID__'),

Wyświetl plik

@ -74,7 +74,7 @@ class FieldBlock(Block):
class FieldBlockAdapter(Adapter):
js_constructor = 'wagtail.blocks.FieldBlock'
def js_args(self, block, context):
def js_args(self, block):
classname = [
'field',
camelcase_to_underscore(block.field.__class__.__name__),
@ -85,7 +85,7 @@ class FieldBlockAdapter(Adapter):
return [
block.name,
context.pack(block.field.widget),
block.field.widget,
{
'label': block.label,
'required': block.required,

Wyświetl plik

@ -143,10 +143,10 @@ class ListBlock(Block):
class ListBlockAdapter(Adapter):
js_constructor = 'wagtail.blocks.ListBlock'
def js_args(self, block, context):
def js_args(self, block):
return [
block.name,
context.pack(block.child_block),
block.child_block,
{
'label': block.label, 'icon': block.meta.icon, 'classname': block.meta.form_classname,
'helpText': getattr(block.meta, 'help_text', None),

Wyświetl plik

@ -611,10 +611,10 @@ class StreamValue(MutableSequence):
class StreamBlockAdapter(Adapter):
js_constructor = 'wagtail.blocks.StreamBlock'
def js_args(self, block, context):
def js_args(self, block):
return [
block.name,
[context.pack(child) for child in block.child_blocks.values()],
block.child_blocks.values(),
{
'label': block.label, 'required': block.required, 'icon': block.meta.icon,
'classname': block.meta.form_classname, 'helpText': getattr(block.meta, 'help_text', None),

Wyświetl plik

@ -214,10 +214,10 @@ class StructBlock(BaseStructBlock, metaclass=DeclarativeSubBlocksMetaclass):
class StructBlockAdapter(Adapter):
js_constructor = 'wagtail.blocks.StructBlock'
def js_args(self, block, context):
def js_args(self, block):
return [
block.name,
[context.pack(child) for child in block.child_blocks.values()],
block.child_blocks.values(),
{
'label': block.label, 'required': block.required, 'icon': block.meta.icon,
'classname': block.meta.form_classname, 'helpText': getattr(block.meta, 'help_text', None),

Wyświetl plik

@ -4,7 +4,73 @@ from django.forms import MediaDefiningClass
from wagtail.admin.staticfiles import versioned_static
adapters = {}
DICT_RESERVED_KEYS = ['_type', '_args', '_dict']
class BaseAdapter:
"""Handles serialisation of a specific object type"""
def pack(self, obj, context):
"""
Translates obj into serialisable form. Any media declarations that will be required for
deserialisation of the object should be passed to context.add_media().
This base implementation handles simple JSON-serialisable values such as strings, and
returns them unchanged.
"""
return obj
class DictAdapter(BaseAdapter):
"""Handles serialisation of dicts"""
def pack(self, obj, context):
packed_obj = {
str(key): context.pack(val)
for key, val in obj.items()
}
if any(reserved_key in packed_obj for reserved_key in DICT_RESERVED_KEYS):
# this dict contains keys such as _type that would collide with our object notation,
# so wrap it in an explicit _dict to disambiguate
return {'_dict': packed_obj}
else:
return packed_obj
class Adapter(BaseAdapter, metaclass=MediaDefiningClass):
"""
Handles serialisation of custom types.
Subclasses should define:
- js_constructor: namespaced identifier for the JS constructor function that will unpack this
object
- js_args(obj): returns a list of (telepath-packable) arguments to be passed to the constructor
- get_media(obj) or class Media: media definitions necessary for unpacking
The adapter should then be registered with register(adapter, cls).
"""
def get_media(self, obj):
return self.media
def pack(self, obj, context):
context.add_media(self.get_media(obj))
return {
'_type': self.js_constructor,
'_args': [context.pack(arg) for arg in self.js_args(obj)]
}
adapters = {
# Primitive value types that are unchanged on serialisation
type(None): BaseAdapter(),
bool: BaseAdapter(),
int: BaseAdapter(),
float: BaseAdapter(),
str: BaseAdapter(),
# Container types to be serialised recursively
dict: DictAdapter(),
# Iterable types (list, tuple, odict_values...) do not have a reliably recognisable
# superclass, so will be handled as a special case
}
def register(adapter, cls):
@ -16,21 +82,27 @@ class JSContext:
self.media = forms.Media(js=[
versioned_static('wagtailadmin/js/telepath/telepath.js')
])
self.objects = {}
# Keep track of media declarations that have already added to self.media - ones that
# exactly match a previous one can be ignored, as they will not affect the result
self.media_fragments = set([str(self.media)])
def add_media(self, media):
media_str = str(media)
if media_str not in self.media_fragments:
self.media += media
self.media_fragments.add(media_str)
def pack(self, obj):
for cls in type(obj).__mro__:
adapter = adapters.get(cls)
if adapter:
break
return adapter.pack(obj, self)
if adapter is None:
raise Exception("don't know how to add object to JS context: %r" % obj)
# as fallback, try handling as an iterable
try:
return [self.pack(item) for item in obj]
except TypeError:
pass
self.media += adapter.get_media(obj, self)
return [adapter.js_constructor, *adapter.js_args(obj, self)]
class Adapter(metaclass=MediaDefiningClass):
def get_media(self, obj, context):
return self.media
raise Exception("don't know how to pack object: %r" % obj)

Wyświetl plik

@ -13,14 +13,14 @@ from wagtail.core.telepath import Adapter, register
class WidgetAdapter(Adapter):
js_constructor = 'wagtail.widgets.Widget'
def js_args(self, widget, context):
def js_args(self, widget):
return [
widget.render('__NAME__', None, attrs={'id': '__ID__'}),
widget.id_for_label('__ID__'),
]
def get_media(self, widget, context):
media = super().get_media(widget, context)
def get_media(self, widget):
media = super().get_media(widget)
return media + widget.media
class Media:

Wyświetl plik

@ -62,7 +62,7 @@ class AdminDocumentChooser(AdminChooser):
class DocumentChooserAdapter(WidgetAdapter):
js_constructor = 'wagtail.documents.widgets.DocumentChooser'
def js_args(self, widget, context):
def js_args(self, widget):
return [
widget.render_html('__NAME__', None, attrs={'id': '__ID__'}),
widget.id_for_label('__ID__'),

Wyświetl plik

@ -71,7 +71,7 @@ class AdminImageChooser(AdminChooser):
class ImageChooserAdapter(WidgetAdapter):
js_constructor = 'wagtail.images.widgets.ImageChooser'
def js_args(self, widget, context):
def js_args(self, widget):
return [
widget.render_html('__NAME__', None, attrs={'id': '__ID__'}),
widget.id_for_label('__ID__'),

Wyświetl plik

@ -79,7 +79,7 @@ class AdminSnippetChooser(AdminChooser):
class SnippetChooserAdapter(WidgetAdapter):
js_constructor = 'wagtail.snippets.widgets.SnippetChooser'
def js_args(self, widget, context):
def js_args(self, widget):
return [
widget.render_html('__NAME__', None, attrs={'id': '__ID__'}),
widget.id_for_label('__ID__'), widget.model_string