diff --git a/IPython/html/services/notebooks/handlers.py b/IPython/html/services/notebooks/handlers.py
index 3a7d9d7..351c053 100644
--- a/IPython/html/services/notebooks/handlers.py
+++ b/IPython/html/services/notebooks/handlers.py
@@ -71,8 +71,7 @@ class NotebookHandler(IPythonHandler):
@web.authenticated
@json_errors
def patch(self, path='', name=None):
- """patch is currently used strictly for notebook renaming.
- Changes the notebook name to the name given in data."""
+ """PATCH renames a notebook without re-uploading content."""
nbm = self.notebook_manager
if name is None:
raise web.HTTPError(400, u'Notebook name missing')
@@ -90,10 +89,31 @@ class NotebookHandler(IPythonHandler):
@web.authenticated
@json_errors
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
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'])
self.set_header(u'Location', location)
self.set_header(u'Last-Modified', model[u'last_modified'])
@@ -120,6 +140,29 @@ class NotebookHandler(IPythonHandler):
self.set_status(204)
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):
@@ -180,6 +223,7 @@ _notebook_name_regex = r"(?P[^/]+\.ipynb)"
_notebook_path_regex = "%s/%s" % (_path_regex, _notebook_name_regex)
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/%s" % (_notebook_path_regex, _checkpoint_id_regex),
ModifyNotebookCheckpointsHandler),
diff --git a/IPython/html/services/notebooks/nbmanager.py b/IPython/html/services/notebooks/nbmanager.py
index 2acbd57..ed01f43 100644
--- a/IPython/html/services/notebooks/nbmanager.py
+++ b/IPython/html/services/notebooks/nbmanager.py
@@ -87,7 +87,7 @@ class NotebookManager(LoggingConfigurable):
"""
return basename
- def list_notebooks(self):
+ def list_notebooks(self, path=''):
"""Return a list of notebook dicts without content.
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."""
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."""
raise NotImplementedError('must be implemented in a subclass')
def create_notebook_model(self, model=None, path=''):
"""Create a new untitled notebook and return its model with no content."""
- untitled = self.increment_filename('Untitled', path)
if model is None:
model = {}
+ if 'content' not in model:
metadata = current.new_metadata(name=u'')
- nb = current.new_notebook(metadata=metadata)
- model['content'] = nb
- model['name'] = name = untitled
- model['path'] = path
- else:
- name = model.setdefault('name', untitled)
- model['path'] = path
- model = self.save_notebook_model(model, name, path)
+ model['content'] = current.new_notebook(metadata=metadata)
+ if 'name' not in model:
+ model['name'] = self.increment_filename('Untitled', path)
+
+ model['path'] = path
+ model = self.save_notebook_model(model, model['name'], model['path'])
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."""
model = self.get_notebook_model(name, path)
name = os.path.splitext(name)[0] + '-Copy'
name = self.increment_filename(name, path) + self.filename_ext
model['name'] = name
- model = self.save_notebook_model(model, name, path, content=content)
+ model = self.save_notebook_model(model, name, path)
return model
# Checkpoint-related
- def create_checkpoint(self, name, path='/'):
+ def create_checkpoint(self, name, path=''):
"""Create a checkpoint of the current state of a notebook
Returns a checkpoint_id for the new checkpoint.
"""
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 []
- def restore_checkpoint(self, checkpoint_id, name, path='/'):
+ def restore_checkpoint(self, checkpoint_id, name, path=''):
"""Restore a notebook from one of its checkpoints"""
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"""
raise NotImplementedError("must be implemented in a subclass")
diff --git a/IPython/html/static/notebook/js/notebook.js b/IPython/html/static/notebook/js/notebook.js
index 3fd4f50..e7a2b40 100644
--- a/IPython/html/static/notebook/js/notebook.js
+++ b/IPython/html/static/notebook/js/notebook.js
@@ -1768,15 +1768,12 @@ var IPython = (function (IPython) {
Notebook.prototype.copy_notebook = function(){
var path = this.notebookPath();
- var name = {'name': this.notebook_name}
var settings = {
processData : false,
cache : false,
type : "POST",
- data: JSON.stringify(name),
- dataType : "json",
success:$.proxy(function (data, status, xhr){
- notebook_name = data.name;
+ var notebook_name = data.name;
window.open(utils.url_path_join(
this._baseProjectUrl,
'notebooks',
@@ -1787,8 +1784,10 @@ var IPython = (function (IPython) {
};
var url = utils.url_path_join(
this._baseProjectUrl,
- 'notebooks',
- path
+ 'api/notebooks',
+ path,
+ this.notebook_name,
+ 'copy'
);
$.ajax(url,settings);
};
diff --git a/IPython/html/static/tree/js/notebooklist.js b/IPython/html/static/tree/js/notebooklist.js
index b10395f..69957eb 100644
--- a/IPython/html/static/tree/js/notebooklist.js
+++ b/IPython/html/static/tree/js/notebooklist.js
@@ -312,7 +312,7 @@ var IPython = (function (IPython) {
var upload_button = $('').text("Upload")
.addClass('btn btn-primary btn-mini upload_button')
.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 nbdata = item.data('nbdata');
var content_type = 'application/json';
@@ -323,8 +323,6 @@ var IPython = (function (IPython) {
}
var model = {
content : JSON.parse(nbdata),
- name : nbname,
- path : that.notebookPath()
};
var settings = {
processData : false,
@@ -345,7 +343,8 @@ var IPython = (function (IPython) {
var url = utils.url_path_join(
that.baseProjectUrl(),
'api/notebooks',
- that.notebookPath()
+ that.notebookPath(),
+ nbname + '.ipynb'
);
$.ajax(url, settings);
return false;