【问题标题】:Child widget creation in ipywidgets produces an error using ViewList and create_child_view在 ipywidgets 中创建子小部件会使用 ViewList 和 create_child_view 产生错误
【发布时间】:2019-10-21 23:46:10
【问题描述】:

总结

请参阅玩具示例 Azure 笔记本 hosted at this link。笔记本可以从那里克隆并运行,或下载并在本地运行,但为方便起见,所有代码也在下面。

当所有单元格运行时,javascript 控制台在最后一个单元格中报告这些错误(缩写),并且最终预期的输出行没有呈现:

Error: Could not create a view for model id 91700d0eb745433eaee98bca2d9f3fc8
    at promiseRejection (utils.js:119)
Error: Could not create view
    at promiseRejection (utils.js:119)
Uncaught (in promise) TypeError: Cannot read property 'then' of undefined
Uncaught (in promise) TypeError: Cannot read property 'then' of undefined

不知道哪里出错了。

更新:

由于它当前存在,代码将字符串实例(而不是DOMWidgetModel 实例)发送到create_child_view 方法。该字符串包含"IPY_MODEL_" 附加模型ID。这似乎可能是问题的根源。客户端正在从服务器端 Backbone children 模型数组项 (this.model.get('children')) 接收该字符串实例。

我想知道这个问题是否与low-level widget tutorial 中讨论的小部件的[反]序列化有关。但我不确定如何使用它来解决这个问题,因为我需要访问子小部件模型本身而不仅仅是属性。而且我相信我正确地传递了教程指定的**widgets.widget_serialization


详情

笔记本包含 python 和 javascript 代码,并利用严重依赖 Backbone 的 ipywidgets 库。后端代码(python,cell #1)创建了一个ipywidgets.DOMWidget 子类小部件,Test(一个在前端镜像的 Backbone 模型)。前端代码(javascript,单元格#2)创建了一个ipywidgets.DOMWidgetView 子类TestView,它在呈现到页面时由小部件实例化。

Test 模型小部件有一个 children 成员,该成员由多个“子小部件”(也是模型)组成。这些小部件是 python 类Sub 的实例。当Test 的视图被渲染时,我想实例化和渲染子小部件的视图并将它们附加到父Test 小部件的视图(注意:最后部分尚未在下面实现)。

问题是当我尝试按照ipywidgets API 创建子视图时,通过在每个子模型上使用create_child_view 方法实例化子视图来填充ViewList 数组不起作用。

这类事情的 API 没有特别详细的文档,所以我尽我所能遵循各种类似的示例,说明如何使用父视图中的子模型(例如父小部件)实例化子视图in ipywidgets itselfin ipyleaflet。但我所做的一切似乎都无法让儿童视图的创建发挥作用。

请注意,我能够单独呈现每个Sub 小部件的视图,而不会出现任何问题。只有当我尝试使用create_child_view 方法从父Test 小部件中创建视图时,我们才会遇到问题。


代码

单元 1(服务器端 jupyter python 内核)

import ipywidgets.widgets as widgets
from traitlets import Unicode, List, Instance
from IPython.display import display


class Sub(widgets.DOMWidget):
    """Widget intended to be part of the view of another widget."""
    _view_name = Unicode('SubView').tag(sync=True)
    _view_module = Unicode('test').tag(sync=True)
    _view_module_version = Unicode('0.1.0').tag(sync=True)


class Test(widgets.DOMWidget):
    """A parent widget intended to be made up of child widgets."""
    _view_name = Unicode('TestView').tag(sync=True)
    _view_module = Unicode('test').tag(sync=True)
    _view_module_version = Unicode('0.1.0').tag(sync=True)
    children = List(Instance(widgets.Widget)).tag(sync=True, 
                                        **widgets.widget_serialization)

    def __init__(self, subs):
        super().__init__()
        self.children = list(subs)

Cell 2(前端 jupyter notebook 代码)

%%javascript

require.undef('test');

define('test', ["@jupyter-widgets/base"], function(widgets) {

    var SubView = widgets.DOMWidgetView.extend({

        initialize: function() {
            console.log('init SubView');
            SubView.__super__.initialize.apply(this, arguments);
        },

        render: function() {
            this.el.textContent = "subview rendering";
        },

    });

    var TestView = widgets.DOMWidgetView.extend({

        initialize: function() {
            console.log('init TestView');
            TestView.__super__.initialize.apply(this, arguments);
            this.views = new widgets.ViewList(this.add_view, null, this);
            this.listenTo(this.model, 'change:children', function(model, value) {
                this.views.update(value);
            }, this);
            console.log('init TestView complete');
        },

        add_view: function (child_model) {
            // error occurs on this line:
            return this.create_child_view(child_model);
        },

        render: function() {
            this.views.update(this.model.get('children'));
            this.el.textContent = 'rendered test_view';
        },
    });

    return {
        SubView : SubView,
        TestView : TestView,
    };

});

Cell 3(用于测试的python代码)

models=[Sub() for _ in range(4)]
for m in models:
    # view each Sub object individually
    display(m)  # output: 'subview rendering'
t=Test(models)
t  # output: 'rendered test_view'  <-- broken; see console log

输出

当前输出:

subview rendering  

subview rendering  

subview rendering  

subview rendering

预期输出:

subview rendering  

subview rendering  

subview rendering  

subview rendering  

rendered test_view

如果有人有兴趣,可以在this github issue 获取有关我正在从事的实际项目的更多具体信息。

【问题讨论】:

  • 你有任何图像,输出应该是什么样子?
  • @ASHu2 我在笔记本中添加了预期的输出(和一些注释),希望对您有所帮助。
  • @ASHu2 每个单独的子视图本身的“子视图渲染”是预期的并且可以正常工作。来自:for m in models: display(m)。在 parent 对象中添加新视图时会发生错误,使用 this.create_child_view(child_model).
  • 谢谢。如果大多数输出​​都是none,您能否检查t.get_manager_state()
  • @ASHu2 Link

标签: javascript python backbone.js jupyter-notebook ipywidgets


【解决方案1】:

您需要明确告诉前端如何反序列化小部件,即如何将字符串 "IPY_MODEL_*" 转换为实际模型。

您可以通过在前端显式定义模型并为children 属性设置自定义反序列化器来实现此目的。这是您在 Python 端添加到 children traitlet 的 **widgets.widget_serialization 序列化程序的对应物。

笔记本

这是一个修改后的笔记本,用于渲染孩子:

https://gist.github.com/pbugnion/63cf43b41ec0eed2d0b7e7426d1c67d2

全部更改

内核端,保持对JS模型类的显式引用:

class Test(widgets.DOMWidget):
    _model_name = Unicode('TestModel').tag(sync=True)  # reference to JS model class
    _model_module = Unicode('test').tag(sync=True)  # reference to JS model module

    # all the rest is unchanged
    _view_name = Unicode('TestView').tag(sync=True)
    _view_module = Unicode('test').tag(sync=True)
    _view_module_version = Unicode('0.1.0').tag(sync=True)
    children = List(Instance(widgets.Widget)).tag(sync=True, **widgets.widget_serialization)

    def __init__(self, subs):
        super().__init__()
        self.children = subs

那么,在 JS 方面,

  1. 导入下划线以便我们可以扩展对象:
require.undef('test');

define('test', ["@jupyter-widgets/base", "underscore"], function(widgets, _) {

  1. 定义您的模型模块:
    var TestModel = widgets.DOMWidgetModel.extend({}, {
        serializers: _.extend({
            children: { deserialize: widgets.unpack_models }
        }, widgets.WidgetModel.serializers)
    })

这告诉小部件管理器在反序列化children 属性时使用widgets.unpack_models 函数。我们或许可以在这里使用Object.assign 代替下划线,这样可以消除下划线的依赖性。

  1. 导出您的模型:
    return {
        SubView : SubView,
        TestView : TestView,
        TestModel : TestModel
    };

野外示例

我可以在 IPyleaflet 代码库here 中找到与此匹配的模式。请特别注意LeafletLayerModel 类。

对于使用更现代(babelified)语法的示例,我的 gmaps 包使用小部件反序列化 here

【讨论】:

  • 这听起来可能是正确的答案,但我昨天尝试了这个确切的修复,但我相信我无法让它工作——我会找到笔记本并发布一个链接,也许我是做错事。
  • 啊哈!有用!我错过了 widgets.DOMWidgetModel.extend() 的第一个空 Object 参数。如果您不介意,我想在 GitHub gist 上问一些问题。
  • 我直接在gist回答了你的问题。
猜你喜欢
  • 1970-01-01
  • 2011-03-01
  • 1970-01-01
  • 2011-08-24
  • 1970-01-01
  • 2020-12-30
  • 1970-01-01
  • 1970-01-01
  • 2021-01-14
相关资源
最近更新 更多