From 458d473d556a2a4334cc355b734f3da0e94b4eb3 2014-10-08 19:33:32 From: MinRK Date: 2014-10-08 19:33:32 Subject: [PATCH] add dialogs for failed save/load - failed validation doesn't prevent save/load, but the user is now warned about it. - there is now a warning dialog if fromJSON fails, rather than the usual silent failure. --- diff --git a/IPython/html/services/contents/filemanager.py b/IPython/html/services/contents/filemanager.py index 1329319..2c3f9fc 100644 --- a/IPython/html/services/contents/filemanager.py +++ b/IPython/html/services/contents/filemanager.py @@ -176,6 +176,7 @@ class FileContentsManager(ContentsManager): model['created'] = created model['content'] = None model['format'] = None + model['message'] = None return model def _dir_model(self, name, path='', content=True): @@ -258,6 +259,7 @@ class FileContentsManager(ContentsManager): self.mark_trusted_cells(nb, name, path) model['content'] = nb model['format'] = 'json' + self.validate_notebook_model(model) return model def get_model(self, name, path='', content=True): @@ -366,7 +368,14 @@ class FileContentsManager(ContentsManager): except Exception as e: raise web.HTTPError(400, u'Unexpected error while saving file: %s %s' % (os_path, e)) + validation_message = None + if model['type'] == 'notebook': + self.validate_notebook_model(model) + validation_message = model.get('message', None) + model = self.get_model(new_name, new_path, content=False) + if validation_message: + model['message'] = validation_message return model def update(self, model, name, path=''): diff --git a/IPython/html/services/contents/manager.py b/IPython/html/services/contents/manager.py index e6a11ed..f260135 100644 --- a/IPython/html/services/contents/manager.py +++ b/IPython/html/services/contents/manager.py @@ -5,6 +5,7 @@ from fnmatch import fnmatch import itertools +import json import os from tornado.web import HTTPError @@ -216,6 +217,16 @@ class ContentsManager(LoggingConfigurable): break return name + def validate_notebook_model(self, model): + """Add failed-validation message to model""" + try: + current.validate(model['content']) + except current.ValidationError as e: + model['message'] = 'Notebook Validation failed: {}:\n{}'.format( + e.message, json.dumps(e.instance, indent=1, default=lambda obj: ''), + ) + return model + def create_file(self, model=None, path='', ext='.ipynb'): """Create a new file or directory and return its model with no content.""" path = path.strip('/') diff --git a/IPython/html/static/notebook/js/notebook.js b/IPython/html/static/notebook/js/notebook.js index fa1b9ab..ffbb7fe 100644 --- a/IPython/html/static/notebook/js/notebook.js +++ b/IPython/html/static/notebook/js/notebook.js @@ -1826,6 +1826,7 @@ define([ * @param {Object} data JSON representation of a notebook */ Notebook.prototype.fromJSON = function (data) { + var content = data.content; var ncells = this.ncells(); var i; @@ -1975,6 +1976,7 @@ define([ type : "PUT", data : JSON.stringify(model), headers : {'Content-Type': 'application/json'}, + dataType : "json", success : $.proxy(this.save_notebook_success, this, start), error : $.proxy(this.save_notebook_error, this) }; @@ -2004,6 +2006,30 @@ define([ */ Notebook.prototype.save_notebook_success = function (start, data, status, xhr) { this.set_dirty(false); + if (data.message) { + // save succeeded, but validation failed. + var body = $("
"); + var title = "Notebook validation failed"; + + body.append($("

").text( + "The save operation succeeded," + + " but the notebook does not appear to be valid." + + " The validation error was:" + )).append($("

").addClass("validation-error").append( + $("
").text(data.message)
+            ));
+            dialog.modal({
+                notebook: this,
+                keyboard_manager: this.keyboard_manager,
+                title: title,
+                body: body,
+                buttons : {
+                    OK : {
+                        "class" : "btn-primary"
+                    }
+                }
+            });
+        }
         this.events.trigger('notebook_saved.Notebook');
         this._update_autosave_interval(start);
         if (this._checkpoint_after_save) {
@@ -2278,7 +2304,57 @@ define([
      * @param {jqXHR} xhr jQuery Ajax object
      */
     Notebook.prototype.load_notebook_success = function (data, status, xhr) {
-        this.fromJSON(data);
+        var failed;
+        try {
+            this.fromJSON(data);
+        } catch (e) {
+            failed = e;
+            console.log("Notebook failed to load from JSON:", e);
+        }
+        if (failed || data.message) {
+            // *either* fromJSON failed or validation failed
+            var body = $("
"); + var title; + if (failed) { + title = "Notebook failed to load"; + body.append($("

").text( + "The error was: " + )).append($("

").addClass("js-error").text( + failed.toString() + )).append($("

").text( + "See the error console for details." + )); + } else { + title = "Notebook validation failed"; + } + + if (data.message) { + var msg; + if (failed) { + msg = "The notebook also failed validation:" + } else { + msg = "An invalid notebook may not function properly." + + " The validation error was:" + } + body.append($("

").text( + msg + )).append($("

").addClass("validation-error").append( + $("
").text(data.message)
+                ));
+            }
+
+            dialog.modal({
+                notebook: this,
+                keyboard_manager: this.keyboard_manager,
+                title: title,
+                body: body,
+                buttons : {
+                    OK : {
+                        "class" : "btn-primary"
+                    }
+                }
+            });
+        }
         if (this.ncells() === 0) {
             this.insert_cell_below('code');
             this.edit_mode(0);