5 votes

La création de widgets enfants dans ipywidgets produit une erreur en utilisant ViewList et create_child_view

Résumé

Voir l'exemple de l'Azure notebook hébergé à ce lien . Le bloc-notes peut être cloné et exécuté, ou téléchargé et exécuté localement, à partir de là, mais tout le code est également ci-dessous pour plus de commodité.

Lorsque toutes les cellules sont exécutées, la console javascript signale ces erreurs (abrégées) dans la dernière cellule, et la dernière ligne de sortie attendue ne s'affiche pas :

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

Je ne sais pas où je me trompe.

UPDATE :

Tel qu'il existe actuellement, le code envoie une instance de chaîne (plutôt qu'une DOMWidgetModel ) à la create_child_view méthode. La chaîne de caractères contient "IPY_MODEL_" complété par l'identifiant du modèle. Cela semble être la racine du problème. Cette instance de chaîne est reçue par le client depuis le Backbone côté serveur. children éléments du tableau du modèle ( this.model.get('children') ).

Je me demande si le problème n'est pas lié à la [dé]sérialisation des widgets dont il est question dans l'avis de la Commission européenne. tutoriel sur les widgets de bas niveau . Mais je ne suis pas sûr de savoir comment l'utiliser pour résoudre ce problème, puisque j'ai besoin d'accéder au modèle du sous-widget lui-même et pas seulement à un attribut. Et je pense que je passe correctement le **widgets.widget_serialization comme le précise le tutoriel.


Détails

Le carnet de notes contient du code python et javascript, et utilise l'application ipywidgets qui s'appuie largement sur Backbone. Le code dorsal (python, cellule #1) crée un fichier ipywidgets.DOMWidget sous-classe widget, Test (un modèle Backbone reflété dans le front-end). Le code frontal (javascript, cellule #2) crée un fichier ipywidgets.DOMWidgetView sous-classe, TestView qui est instancié par le widget lorsqu'il est rendu dans la page.

El Test Le widget de modèle a un children composé de plusieurs "sous-widgets" (qui sont aussi des modèles). Ces widgets sont des instances de la classe python Sub . Lorsqu'une vue de Test est rendu, je veux instancier et rendre les vues des widgets enfants et les attacher à la vue du parent Test (note : cette dernière partie n'a pas encore été implémentée ci-dessous).

Le problème est que lorsque j'essaye de suivre les ipywidgets pour créer des vues enfants, en alimentant le ViewList en instanciant les vues enfants à l'aide de la fonction create_child_view sur chaque modèle enfant ne fonctionne pas.

L'API pour ce genre de choses n'est pas particulièrement bien documentée, donc je fais de mon mieux pour suivre divers exemples similaires sur la façon d'instancier des sous-vues en utilisant des modèles enfants à partir d'une vue parent, comme les widgets parents. sur ipywidgets lui-même et sur ipyleaflet . Mais rien ne me permet de faire fonctionner la création de vues d'enfants.

Notez que je suis en mesure de rendre une vue de chaque Sub widget individuellement sans aucun problème. Ce n'est que lorsque j'essaie d'utiliser le create_child_view pour créer une vue à partir du parent Test widget que nous rencontrons des problèmes.


Code

Cellule 1 (côté serveur jupyter python noyau)

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)

Cellule 2 (code frontal du carnet de notes de Jupyter)

%%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,
    };

});

Cellule 3 (code python pour les tests)

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

Sortie

Sortie de courant :

subview rendering  

subview rendering  

subview rendering  

subview rendering

Sortie attendue :

subview rendering  

subview rendering  

subview rendering  

subview rendering  

rendered test_view

Des informations plus spécifiques sur le projet sur lequel je travaille sont disponibles à l'adresse suivante ce problème github si quelqu'un est intéressé.

3voto

Pascal Bugnion Points 3323

Vous devez indiquer explicitement au frontal comment désérialiser les widgets, c'est-à-dire comment transformer la chaîne de caractères "IPY_MODEL_*" en un modèle réel.

Pour ce faire, il faut définir explicitement un modèle du côté frontal et définir un dé-sérialiseur personnalisé pour l'objet children attribut. Il s'agit de la contrepartie de l'attribut **widgets.widget_serialization que vous avez ajoutés au fichier children traitlet du côté Python.

Carnet de notes

Voici une version modifiée du carnet qui rend les enfants :

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

Changements complets

Côté noyau, maintenez une référence explicite à la classe de modèle 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

Ensuite, du côté de JS,

  1. Importez l'underscore pour pouvoir étendre les objets :

    require.undef('test');

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

  2. Définissez votre module de modèle :

    var TestModel = widgets.DOMWidgetModel.extend({}, {
        serializers: _.extend({
            children: { deserialize: widgets.unpack_models }
        }, widgets.WidgetModel.serializers)
    })

Cela indique au gestionnaire de widgets d'utiliser l'option widgets.unpack_models lors de la désérialisation de la children attribut. Nous pourrions utiliser Object.assign au lieu de l'underscore ici, ce qui supprimerait la dépendance à l'underscore.

  1. Exportez votre modèle :

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

Exemples dans la nature

J'ai pu trouver un modèle qui correspond à ceci dans la base de code de l'IPyleaflet. aquí . Regardez spécialement dans le LeafletLayerModel classe.

Pour un exemple qui utilise une syntaxe plus moderne (babélifiée), mes gmaps Le paquet utilise la désérialisation des widgets aquí .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X