update upload and copy

adds 'notebook.ipynb/copy' url handler

because we were putting too many things in a single POST handler.
This commit is contained in:
MinRK 2013-10-07 16:32:38 -07:00
parent 75e14424c6
commit b33ffa7068
4 changed files with 71 additions and 31 deletions

View File

@ -71,8 +71,7 @@ class NotebookHandler(IPythonHandler):
@web.authenticated @web.authenticated
@json_errors @json_errors
def patch(self, path='', name=None): def patch(self, path='', name=None):
"""patch is currently used strictly for notebook renaming. """PATCH renames a notebook without re-uploading content."""
Changes the notebook name to the name given in data."""
nbm = self.notebook_manager nbm = self.notebook_manager
if name is None: if name is None:
raise web.HTTPError(400, u'Notebook name missing') raise web.HTTPError(400, u'Notebook name missing')
@ -90,10 +89,31 @@ class NotebookHandler(IPythonHandler):
@web.authenticated @web.authenticated
@json_errors @json_errors
def post(self, path='', name=None): def post(self, path='', name=None):
"""Create a new notebook in the location given by 'notebook_path'.""" """Create a new notebook in the specified path.
POST creates new notebooks.
POST /api/notebooks/path : new untitled notebook in path
POST /api/notebooks/path/notebook.ipynb : new notebook with name in path
If content specified upload notebook, otherwise start empty.
"""
nbm = self.notebook_manager nbm = self.notebook_manager
model = self.get_json_body() model = self.get_json_body()
model = nbm.create_notebook_model(model, path) if name is None:
# creating new notebook, model doesn't make sense
if model is not None:
raise web.HTTPError(400, "Model not valid when creating untitled notebooks.")
model = nbm.create_notebook_model(path=path)
else:
if model is None:
self.log.info("Creating new Notebook at %s/%s", path, name)
model = {}
else:
self.log.info("Uploading Notebook to %s/%s", path, name)
# set the model name from the URL
model['name'] = name
model = nbm.create_notebook_model(model, path)
location = self.notebook_location(model[u'name'], model[u'path']) location = self.notebook_location(model[u'name'], model[u'path'])
self.set_header(u'Location', location) self.set_header(u'Location', location)
self.set_header(u'Last-Modified', model[u'last_modified']) self.set_header(u'Last-Modified', model[u'last_modified'])
@ -120,6 +140,29 @@ class NotebookHandler(IPythonHandler):
self.set_status(204) self.set_status(204)
self.finish() self.finish()
class NotebookCopyHandler(IPythonHandler):
SUPPORTED_METHODS = ('POST')
@web.authenticated
@json_errors
def post(self, path='', name=None):
"""Copy an existing notebook."""
nbm = self.notebook_manager
model = self.get_json_body()
if name is None:
raise web.HTTPError(400, "Notebook name required")
self.log.info("Copying Notebook %s/%s", path, name)
model = nbm.copy_notebook(name, path)
location = url_path_join(
self.base_project_url, 'api', 'notebooks',
model['path'], model['name'],
)
self.set_header(u'Location', location)
self.set_header(u'Last-Modified', model[u'last_modified'])
self.set_status(201)
self.finish(json.dumps(model, default=date_default))
class NotebookCheckpointsHandler(IPythonHandler): class NotebookCheckpointsHandler(IPythonHandler):
@ -180,6 +223,7 @@ _notebook_name_regex = r"(?P<name>[^/]+\.ipynb)"
_notebook_path_regex = "%s/%s" % (_path_regex, _notebook_name_regex) _notebook_path_regex = "%s/%s" % (_path_regex, _notebook_name_regex)
default_handlers = [ default_handlers = [
(r"/api/notebooks/?%s/copy" % _notebook_path_regex, NotebookCopyHandler),
(r"/api/notebooks/?%s/checkpoints" % _notebook_path_regex, NotebookCheckpointsHandler), (r"/api/notebooks/?%s/checkpoints" % _notebook_path_regex, NotebookCheckpointsHandler),
(r"/api/notebooks/?%s/checkpoints/%s" % (_notebook_path_regex, _checkpoint_id_regex), (r"/api/notebooks/?%s/checkpoints/%s" % (_notebook_path_regex, _checkpoint_id_regex),
ModifyNotebookCheckpointsHandler), ModifyNotebookCheckpointsHandler),

View File

@ -87,7 +87,7 @@ class NotebookManager(LoggingConfigurable):
""" """
return basename return basename
def list_notebooks(self): def list_notebooks(self, path=''):
"""Return a list of notebook dicts without content. """Return a list of notebook dicts without content.
This returns a list of dicts, each of the form:: This returns a list of dicts, each of the form::
@ -112,53 +112,51 @@ class NotebookManager(LoggingConfigurable):
"""Update the notebook model and return the model with no content.""" """Update the notebook model and return the model with no content."""
raise NotImplementedError('must be implemented in a subclass') raise NotImplementedError('must be implemented in a subclass')
def delete_notebook_model(self, name, path): def delete_notebook_model(self, name, path=''):
"""Delete notebook by name and path.""" """Delete notebook by name and path."""
raise NotImplementedError('must be implemented in a subclass') raise NotImplementedError('must be implemented in a subclass')
def create_notebook_model(self, model=None, path=''): def create_notebook_model(self, model=None, path=''):
"""Create a new untitled notebook and return its model with no content.""" """Create a new untitled notebook and return its model with no content."""
untitled = self.increment_filename('Untitled', path)
if model is None: if model is None:
model = {} model = {}
if 'content' not in model:
metadata = current.new_metadata(name=u'') metadata = current.new_metadata(name=u'')
nb = current.new_notebook(metadata=metadata) model['content'] = current.new_notebook(metadata=metadata)
model['content'] = nb if 'name' not in model:
model['name'] = name = untitled model['name'] = self.increment_filename('Untitled', path)
model['path'] = path
else: model['path'] = path
name = model.setdefault('name', untitled) model = self.save_notebook_model(model, model['name'], model['path'])
model['path'] = path
model = self.save_notebook_model(model, name, path)
return model return model
def copy_notebook(self, name, path='/', content=False): def copy_notebook(self, name, path=''):
"""Copy an existing notebook and return its new model.""" """Copy an existing notebook and return its new model."""
model = self.get_notebook_model(name, path) model = self.get_notebook_model(name, path)
name = os.path.splitext(name)[0] + '-Copy' name = os.path.splitext(name)[0] + '-Copy'
name = self.increment_filename(name, path) + self.filename_ext name = self.increment_filename(name, path) + self.filename_ext
model['name'] = name model['name'] = name
model = self.save_notebook_model(model, name, path, content=content) model = self.save_notebook_model(model, name, path)
return model return model
# Checkpoint-related # Checkpoint-related
def create_checkpoint(self, name, path='/'): def create_checkpoint(self, name, path=''):
"""Create a checkpoint of the current state of a notebook """Create a checkpoint of the current state of a notebook
Returns a checkpoint_id for the new checkpoint. Returns a checkpoint_id for the new checkpoint.
""" """
raise NotImplementedError("must be implemented in a subclass") raise NotImplementedError("must be implemented in a subclass")
def list_checkpoints(self, name, path='/'): def list_checkpoints(self, name, path=''):
"""Return a list of checkpoints for a given notebook""" """Return a list of checkpoints for a given notebook"""
return [] return []
def restore_checkpoint(self, checkpoint_id, name, path='/'): def restore_checkpoint(self, checkpoint_id, name, path=''):
"""Restore a notebook from one of its checkpoints""" """Restore a notebook from one of its checkpoints"""
raise NotImplementedError("must be implemented in a subclass") raise NotImplementedError("must be implemented in a subclass")
def delete_checkpoint(self, checkpoint_id, name, path='/'): def delete_checkpoint(self, checkpoint_id, name, path=''):
"""delete a checkpoint for a notebook""" """delete a checkpoint for a notebook"""
raise NotImplementedError("must be implemented in a subclass") raise NotImplementedError("must be implemented in a subclass")

View File

@ -1768,15 +1768,12 @@ var IPython = (function (IPython) {
Notebook.prototype.copy_notebook = function(){ Notebook.prototype.copy_notebook = function(){
var path = this.notebookPath(); var path = this.notebookPath();
var name = {'name': this.notebook_name}
var settings = { var settings = {
processData : false, processData : false,
cache : false, cache : false,
type : "POST", type : "POST",
data: JSON.stringify(name),
dataType : "json",
success:$.proxy(function (data, status, xhr){ success:$.proxy(function (data, status, xhr){
notebook_name = data.name; var notebook_name = data.name;
window.open(utils.url_path_join( window.open(utils.url_path_join(
this._baseProjectUrl, this._baseProjectUrl,
'notebooks', 'notebooks',
@ -1787,8 +1784,10 @@ var IPython = (function (IPython) {
}; };
var url = utils.url_path_join( var url = utils.url_path_join(
this._baseProjectUrl, this._baseProjectUrl,
'notebooks', 'api/notebooks',
path path,
this.notebook_name,
'copy'
); );
$.ajax(url,settings); $.ajax(url,settings);
}; };

View File

@ -312,7 +312,7 @@ var IPython = (function (IPython) {
var upload_button = $('<button/>').text("Upload") var upload_button = $('<button/>').text("Upload")
.addClass('btn btn-primary btn-mini upload_button') .addClass('btn btn-primary btn-mini upload_button')
.click(function (e) { .click(function (e) {
var nbname = item.find('.item_name > input').attr('value'); var nbname = item.find('.item_name > input').val();
var nbformat = item.data('nbformat'); var nbformat = item.data('nbformat');
var nbdata = item.data('nbdata'); var nbdata = item.data('nbdata');
var content_type = 'application/json'; var content_type = 'application/json';
@ -323,8 +323,6 @@ var IPython = (function (IPython) {
} }
var model = { var model = {
content : JSON.parse(nbdata), content : JSON.parse(nbdata),
name : nbname,
path : that.notebookPath()
}; };
var settings = { var settings = {
processData : false, processData : false,
@ -345,7 +343,8 @@ var IPython = (function (IPython) {
var url = utils.url_path_join( var url = utils.url_path_join(
that.baseProjectUrl(), that.baseProjectUrl(),
'api/notebooks', 'api/notebooks',
that.notebookPath() that.notebookPath(),
nbname + '.ipynb'
); );
$.ajax(url, settings); $.ajax(url, settings);
return false; return false;