##// END OF EJS Templates
ContentManager function signatures updated
jhemmelg -
Show More
@@ -1,303 +1,306 b''
1 """Tornado handlers for the contents web service."""
1 """Tornado handlers for the contents web service."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 import json
6 import json
7
7
8 from tornado import web
8 from tornado import web
9
9
10 from IPython.html.utils import url_path_join, url_escape
10 from IPython.html.utils import url_path_join, url_escape
11 from IPython.utils.jsonutil import date_default
11 from IPython.utils.jsonutil import date_default
12
12
13 from IPython.html.base.handlers import (IPythonHandler, json_errors,
13 from IPython.html.base.handlers import (IPythonHandler, json_errors,
14 file_path_regex, path_regex,
14 file_path_regex, path_regex,
15 file_name_regex)
15 file_name_regex)
16
16
17
17
18 def sort_key(model):
18 def sort_key(model):
19 """key function for case-insensitive sort by name and type"""
19 """key function for case-insensitive sort by name and type"""
20 iname = model['name'].lower()
20 iname = model['name'].lower()
21 type_key = {
21 type_key = {
22 'directory' : '0',
22 'directory' : '0',
23 'notebook' : '1',
23 'notebook' : '1',
24 'file' : '2',
24 'file' : '2',
25 }.get(model['type'], '9')
25 }.get(model['type'], '9')
26 return u'%s%s' % (type_key, iname)
26 return u'%s%s' % (type_key, iname)
27
27
28 class ContentsHandler(IPythonHandler):
28 class ContentsHandler(IPythonHandler):
29
29
30 SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE')
30 SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE')
31
31
32 def location_url(self, name, path):
32 def location_url(self, name, path):
33 """Return the full URL location of a file.
33 """Return the full URL location of a file.
34
34
35 Parameters
35 Parameters
36 ----------
36 ----------
37 name : unicode
37 name : unicode
38 The base name of the file, such as "foo.ipynb".
38 The base name of the file, such as "foo.ipynb".
39 path : unicode
39 path : unicode
40 The API path of the file, such as "foo/bar".
40 The API path of the file, such as "foo/bar".
41 """
41 """
42 return url_escape(url_path_join(
42 return url_escape(url_path_join(
43 self.base_url, 'api', 'contents', path, name
43 self.base_url, 'api', 'contents', path, name
44 ))
44 ))
45
45
46 def _finish_model(self, model, location=True):
46 def _finish_model(self, model, location=True):
47 """Finish a JSON request with a model, setting relevant headers, etc."""
47 """Finish a JSON request with a model, setting relevant headers, etc."""
48 if location:
48 if location:
49 location = self.location_url(model['name'], model['path'])
49 location = self.location_url(model['name'], model['path'])
50 self.set_header('Location', location)
50 self.set_header('Location', location)
51 self.set_header('Last-Modified', model['last_modified'])
51 self.set_header('Last-Modified', model['last_modified'])
52 self.finish(json.dumps(model, default=date_default))
52 self.finish(json.dumps(model, default=date_default))
53
53
54 @web.authenticated
54 @web.authenticated
55 @json_errors
55 @json_errors
56 def get(self, path='', name=None):
56 def get(self, path='', name=None):
57 """Return a model for a file or directory.
57 """Return a model for a file or directory.
58
58
59 A directory model contains a list of models (without content)
59 A directory model contains a list of models (without content)
60 of the files and directories it contains.
60 of the files and directories it contains.
61 """
61 """
62 path = path or ''
62 path = path or ''
63 model = self.contents_manager.get_model(name=name, path=path)
63 model = self.contents_manager.get_model(name=name, path=path)
64 if model['type'] == 'directory':
64 if model['type'] == 'directory':
65 # group listing by type, then by name (case-insensitive)
65 # group listing by type, then by name (case-insensitive)
66 # FIXME: sorting should be done in the frontends
66 # FIXME: sorting should be done in the frontends
67 model['content'].sort(key=sort_key)
67 model['content'].sort(key=sort_key)
68 self._finish_model(model, location=False)
68 self._finish_model(model, location=False)
69
69
70 @web.authenticated
70 @web.authenticated
71 @json_errors
71 @json_errors
72 def patch(self, path='', name=None):
72 def patch(self, path='', name=None):
73 """PATCH renames a notebook without re-uploading content."""
73 """PATCH renames a notebook without re-uploading content."""
74 cm = self.contents_manager
74 cm = self.contents_manager
75 if name is None:
75 if name is None:
76 raise web.HTTPError(400, u'Filename missing')
76 raise web.HTTPError(400, u'Filename missing')
77 model = self.get_json_body()
77 model = self.get_json_body()
78 if model is None:
78 if model is None:
79 raise web.HTTPError(400, u'JSON body missing')
79 raise web.HTTPError(400, u'JSON body missing')
80 model = cm.update(model, name, path)
80 model = cm.update(model, name, path)
81 self._finish_model(model)
81 self._finish_model(model)
82
82
83 def _copy(self, copy_from, path, copy_to=None):
83 def _copy(self, copy_from, path, copy_to=None):
84 """Copy a file, optionally specifying the new name.
84 """Copy a file, optionally specifying the new name.
85 """
85 """
86 self.log.info(u"Copying {copy_from} to {path}/{copy_to}".format(
86 self.log.info(u"Copying {copy_from} to {path}/{copy_to}".format(
87 copy_from=copy_from,
87 copy_from=copy_from,
88 path=path,
88 path=path,
89 copy_to=copy_to or '',
89 copy_to=copy_to or '',
90 ))
90 ))
91 model = self.contents_manager.copy(copy_from, copy_to, path)
91 model = self.contents_manager.copy(copy_from, copy_to, path)
92 self.set_status(201)
92 self.set_status(201)
93 self._finish_model(model)
93 self._finish_model(model)
94
94
95 def _upload(self, model, path, name=None):
95 def _upload(self, model, path, name=None):
96 """Handle upload of a new file
96 """Handle upload of a new file
97
97
98 If name specified, create it in path/name,
98 If name specified, create it in path/name,
99 otherwise create a new untitled file in path.
99 otherwise create a new untitled file in path.
100 """
100 """
101 self.log.info(u"Uploading file to %s/%s", path, name or '')
101 self.log.info(u"Uploading file to %s/%s", path, name or '')
102 if name:
102 if name:
103 model['name'] = name
103 model['name'] = name
104
104
105 model = self.contents_manager.create_file(model, path)
105 model = self.contents_manager.create_file(model, path)
106 self.set_status(201)
106 self.set_status(201)
107 self._finish_model(model)
107 self._finish_model(model)
108
108
109 def _create_empty_file(self, path, name=None, ext='.ipynb'):
109 def _create_empty_file(self, path, name=None, ext='.ipynb'):
110 """Create an empty file in path
110 """Create an empty file in path
111
111
112 If name specified, create it in path/name.
112 If name specified, create it in path/name.
113 """
113 """
114 self.log.info(u"Creating new file in %s/%s", path, name or '')
114 self.log.info(u"Creating new file in %s/%s", path, name or '')
115 model = {}
115 model = {}
116 if name:
116 if name:
117 model['name'] = name
117 model['name'] = name
118 model = self.contents_manager.create_file(model, path=path, ext=ext)
118 model = self.contents_manager.create_file(model, path=path, ext=ext)
119 self.set_status(201)
119 self.set_status(201)
120 self._finish_model(model)
120 self._finish_model(model)
121
121
122 def _save(self, model, path, name):
122 def _save(self, model, path, name):
123 """Save an existing file."""
123 """Save an existing file."""
124 self.log.info(u"Saving file at %s/%s", path, name)
124 self.log.info(u"Saving file at %s/%s", path, name)
125 model = self.contents_manager.save(model, name, path)
125 model = self.contents_manager.save(model, name, path)
126 if model['path'] != path.strip('/') or model['name'] != name:
126 if model['path'] != path.strip('/') or model['name'] != name:
127 # a rename happened, set Location header
127 # a rename happened, set Location header
128 location = True
128 location = True
129 else:
129 else:
130 location = False
130 location = False
131 self._finish_model(model, location)
131 self._finish_model(model, location)
132
132
133 @web.authenticated
133 @web.authenticated
134 @json_errors
134 @json_errors
135 def post(self, path='', name=None):
135 def post(self, path='', name=None):
136 """Create a new file or directory in the specified path.
136 """Create a new file or directory in the specified path.
137
137
138 POST creates new files or directories. The server always decides on the name.
138 POST creates new files or directories. The server always decides on the name.
139
139
140 POST /api/contents/path
140 POST /api/contents/path
141 New untitled notebook in path. If content specified, upload a
141 New untitled notebook in path. If content specified, upload a
142 notebook, otherwise start empty.
142 notebook, otherwise start empty.
143 POST /api/contents/path
143 POST /api/contents/path
144 with body {"copy_from" : "OtherNotebook.ipynb"}
144 with body {"copy_from" : "OtherNotebook.ipynb"}
145 New copy of OtherNotebook in path
145 New copy of OtherNotebook in path
146 """
146 """
147
147
148 if name is not None:
148 if name is not None:
149 path = u'{}/{}'.format(path, name)
149 path = u'{}/{}'.format(path, name)
150
150
151 cm = self.contents_manager
151 cm = self.contents_manager
152
152
153 if cm.file_exists(path):
153 if cm.file_exists(path):
154 raise web.HTTPError(400, "Cannot POST to existing files, use PUT instead.")
154 raise web.HTTPError(400, "Cannot POST to existing files, use PUT instead.")
155
155
156 if not cm.path_exists(path):
156 if not cm.path_exists(path):
157 raise web.HTTPError(404, "No such directory: %s" % path)
157 raise web.HTTPError(404, "No such directory: %s" % path)
158
158
159 model = self.get_json_body()
159 model = self.get_json_body()
160
160
161 if model is not None:
161 if model is not None:
162 copy_from = model.get('copy_from')
162 copy_from = model.get('copy_from')
163 ext = model.get('ext', '.ipynb')
163 ext = model.get('ext', '.ipynb')
164 if model.get('content') is not None:
164 if model.get('content') is not None:
165 if copy_from:
165 if copy_from:
166 raise web.HTTPError(400, "Can't upload and copy at the same time.")
166 raise web.HTTPError(400, "Can't upload and copy at the same time.")
167 self._upload(model, path)
167 self._upload(model, path)
168 elif copy_from:
168 elif copy_from:
169 self._copy(copy_from, path)
169 self._copy(copy_from, path)
170 else:
170 else:
171 self._create_empty_file(path, ext=ext)
171 self._create_empty_file(path, ext=ext)
172 else:
172 else:
173 self._create_empty_file(path)
173 self._create_empty_file(path)
174
174
175 @web.authenticated
175 @web.authenticated
176 @json_errors
176 @json_errors
177 def put(self, path='', name=None):
177 def put(self, path='', name=None):
178 """Saves the file in the location specified by name and path.
178 """Saves the file in the location specified by name and path.
179
179
180 PUT is very similar to POST, but the requester specifies the name,
180 PUT is very similar to POST, but the requester specifies the name,
181 whereas with POST, the server picks the name.
181 whereas with POST, the server picks the name.
182
182
183 PUT /api/contents/path/Name.ipynb
183 PUT /api/contents/path/Name.ipynb
184 Save notebook at ``path/Name.ipynb``. Notebook structure is specified
184 Save notebook at ``path/Name.ipynb``. Notebook structure is specified
185 in `content` key of JSON request body. If content is not specified,
185 in `content` key of JSON request body. If content is not specified,
186 create a new empty notebook.
186 create a new empty notebook.
187 PUT /api/contents/path/Name.ipynb
187 PUT /api/contents/path/Name.ipynb
188 with JSON body::
188 with JSON body::
189
189
190 {
190 {
191 "copy_from" : "[path/to/]OtherNotebook.ipynb"
191 "copy_from" : "[path/to/]OtherNotebook.ipynb"
192 }
192 }
193
193
194 Copy OtherNotebook to Name
194 Copy OtherNotebook to Name
195 """
195 """
196 if name is None:
196 if name is None:
197 raise web.HTTPError(400, "name must be specified with PUT.")
197 raise web.HTTPError(400, "name must be specified with PUT.")
198
198
199 model = self.get_json_body()
199 model = self.get_json_body()
200 if model:
200 if model:
201 copy_from = model.get('copy_from')
201 copy_from = model.get('copy_from')
202 if copy_from:
202 if copy_from:
203 if model.get('content'):
203 if model.get('content'):
204 raise web.HTTPError(400, "Can't upload and copy at the same time.")
204 raise web.HTTPError(400, "Can't upload and copy at the same time.")
205 self._copy(copy_from, path, name)
205 self._copy(copy_from, path, name)
206 elif self.contents_manager.file_exists(name, path):
206 elif self.contents_manager.file_exists(name, path):
207 self._save(model, path, name)
207 self._save(model, path, name)
208 checkpoint = model.get('_checkpoint_after_save')
209 if checkpoint:
210 nbm.create_checkpoint(path, name)
208 else:
211 else:
209 self._upload(model, path, name)
212 self._upload(model, path, name)
210 else:
213 else:
211 self._create_empty_file(path, name)
214 self._create_empty_file(path, name)
212
215
213 @web.authenticated
216 @web.authenticated
214 @json_errors
217 @json_errors
215 def delete(self, path='', name=None):
218 def delete(self, path='', name=None):
216 """delete a file in the given path"""
219 """delete a file in the given path"""
217 cm = self.contents_manager
220 cm = self.contents_manager
218 self.log.warn('delete %s:%s', path, name)
221 self.log.warn('delete %s:%s', path, name)
219 cm.delete(name, path)
222 cm.delete(name, path)
220 self.set_status(204)
223 self.set_status(204)
221 self.finish()
224 self.finish()
222
225
223
226
224 class CheckpointsHandler(IPythonHandler):
227 class CheckpointsHandler(IPythonHandler):
225
228
226 SUPPORTED_METHODS = ('GET', 'POST')
229 SUPPORTED_METHODS = ('GET', 'POST')
227
230
228 @web.authenticated
231 @web.authenticated
229 @json_errors
232 @json_errors
230 def get(self, path='', name=None):
233 def get(self, path='', name=None):
231 """get lists checkpoints for a file"""
234 """get lists checkpoints for a file"""
232 cm = self.contents_manager
235 cm = self.contents_manager
233 checkpoints = cm.list_checkpoints(name, path)
236 checkpoints = cm.list_checkpoints(name, path)
234 data = json.dumps(checkpoints, default=date_default)
237 data = json.dumps(checkpoints, default=date_default)
235 self.finish(data)
238 self.finish(data)
236
239
237 @web.authenticated
240 @web.authenticated
238 @json_errors
241 @json_errors
239 def post(self, path='', name=None):
242 def post(self, path='', name=None):
240 """post creates a new checkpoint"""
243 """post creates a new checkpoint"""
241 cm = self.contents_manager
244 cm = self.contents_manager
242 checkpoint = cm.create_checkpoint(name, path)
245 checkpoint = cm.create_checkpoint(name, path)
243 data = json.dumps(checkpoint, default=date_default)
246 data = json.dumps(checkpoint, default=date_default)
244 location = url_path_join(self.base_url, 'api/contents',
247 location = url_path_join(self.base_url, 'api/contents',
245 path, name, 'checkpoints', checkpoint['id'])
248 path, name, 'checkpoints', checkpoint['id'])
246 self.set_header('Location', url_escape(location))
249 self.set_header('Location', url_escape(location))
247 self.set_status(201)
250 self.set_status(201)
248 self.finish(data)
251 self.finish(data)
249
252
250
253
251 class ModifyCheckpointsHandler(IPythonHandler):
254 class ModifyCheckpointsHandler(IPythonHandler):
252
255
253 SUPPORTED_METHODS = ('POST', 'DELETE')
256 SUPPORTED_METHODS = ('POST', 'DELETE')
254
257
255 @web.authenticated
258 @web.authenticated
256 @json_errors
259 @json_errors
257 def post(self, path, name, checkpoint_id):
260 def post(self, path, name, checkpoint_id):
258 """post restores a file from a checkpoint"""
261 """post restores a file from a checkpoint"""
259 cm = self.contents_manager
262 cm = self.contents_manager
260 cm.restore_checkpoint(checkpoint_id, name, path)
263 cm.restore_checkpoint(checkpoint_id, name, path)
261 self.set_status(204)
264 self.set_status(204)
262 self.finish()
265 self.finish()
263
266
264 @web.authenticated
267 @web.authenticated
265 @json_errors
268 @json_errors
266 def delete(self, path, name, checkpoint_id):
269 def delete(self, path, name, checkpoint_id):
267 """delete clears a checkpoint for a given file"""
270 """delete clears a checkpoint for a given file"""
268 cm = self.contents_manager
271 cm = self.contents_manager
269 cm.delete_checkpoint(checkpoint_id, name, path)
272 cm.delete_checkpoint(checkpoint_id, name, path)
270 self.set_status(204)
273 self.set_status(204)
271 self.finish()
274 self.finish()
272
275
273
276
274 class NotebooksRedirectHandler(IPythonHandler):
277 class NotebooksRedirectHandler(IPythonHandler):
275 """Redirect /api/notebooks to /api/contents"""
278 """Redirect /api/notebooks to /api/contents"""
276 SUPPORTED_METHODS = ('GET', 'PUT', 'PATCH', 'POST', 'DELETE')
279 SUPPORTED_METHODS = ('GET', 'PUT', 'PATCH', 'POST', 'DELETE')
277
280
278 def get(self, path):
281 def get(self, path):
279 self.log.warn("/api/notebooks is deprecated, use /api/contents")
282 self.log.warn("/api/notebooks is deprecated, use /api/contents")
280 self.redirect(url_path_join(
283 self.redirect(url_path_join(
281 self.base_url,
284 self.base_url,
282 'api/contents',
285 'api/contents',
283 path
286 path
284 ))
287 ))
285
288
286 put = patch = post = delete = get
289 put = patch = post = delete = get
287
290
288
291
289 #-----------------------------------------------------------------------------
292 #-----------------------------------------------------------------------------
290 # URL to handler mappings
293 # URL to handler mappings
291 #-----------------------------------------------------------------------------
294 #-----------------------------------------------------------------------------
292
295
293
296
294 _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
297 _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
295
298
296 default_handlers = [
299 default_handlers = [
297 (r"/api/contents%s/checkpoints" % file_path_regex, CheckpointsHandler),
300 (r"/api/contents%s/checkpoints" % file_path_regex, CheckpointsHandler),
298 (r"/api/contents%s/checkpoints/%s" % (file_path_regex, _checkpoint_id_regex),
301 (r"/api/contents%s/checkpoints/%s" % (file_path_regex, _checkpoint_id_regex),
299 ModifyCheckpointsHandler),
302 ModifyCheckpointsHandler),
300 (r"/api/contents%s" % file_path_regex, ContentsHandler),
303 (r"/api/contents%s" % file_path_regex, ContentsHandler),
301 (r"/api/contents%s" % path_regex, ContentsHandler),
304 (r"/api/contents%s" % path_regex, ContentsHandler),
302 (r"/api/notebooks/?(.*)", NotebooksRedirectHandler),
305 (r"/api/notebooks/?(.*)", NotebooksRedirectHandler),
303 ]
306 ]
@@ -1,42 +1,173 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jquery',
6 'jquery',
7 ], function(IPython, $) {
7 ], function(IPython, $) {
8 var ContentManager = function() {
8 var ContentManager = function(notebook_path, base_url) {
9 // Constructor
9 // Constructor
10 //
10 //
11 // A contentmanager handles passing file operations
11 // A contentmanager handles passing file operations
12 // to the back-end. This includes checkpointing
12 // to the back-end. This includes checkpointing
13 // with the normal file operations.
13 // with the normal file operations.
14 //
14 //
15 // Parameters:
15 // Parameters:
16 // None
16 // notebook_path
17 // base_url
17 this.version = 0.1;
18 this.version = 0.1;
19 this.notebook_path = notebook_path;
20 this.base_url = base_url;
18 }
21 }
19
22
20 ContentManager.prototype.new_notebook = function() {
23 ContentManager.prototype.new_notebook = function() {
24 var path = this.notebook_path;
25 var base_url = this.base_url;
26 var settings = {
27 processData : false,
28 cache : false,
29 type : "POST",
30 dataType : "json",
31 async : false,
32 success : function (data, status, xhr){
33 var notebook_name = data.name;
34 window.open(
35 utils.url_join_encode(
36 base_url,
37 'notebooks',
38 path,
39 notebook_name
40 ),
41 '_blank'
42 );
43 },
44 error : utils.log_ajax_error,
45 };
46 var url = utils.url_join_encode(
47 base_url,
48 'api/notebooks',
49 path
50 );
51 $.ajax(url,settings);
21 }
52 }
22
53
23 ContentManager.prototype.delete_notebook = function(name, path) {
54 ContentManager.prototype.delete_notebook = function(notebook) {
55 var that = notebook;
56 var settings = {
57 processData : false,
58 cache : false,
59 type : "DELETE",
60 dataType: "json",
61 error : utils.log_ajax_error,
62 };
63 var url = utils.url_join_encode(
64 that.base_url,
65 'api/notebooks',
66 that.notebook_path,
67 that.notebook_name
68 );
69 $.ajax(url, settings);
24 }
70 }
25
71
26 ContentManager.prototype.rename_notebook = function(new_name, new_path, old_name, old_path) {
72 ContentManager.prototype.rename_notebook = function(notebook, nbname) {
73 var that = notebook;
74 if (!nbname.match(/\.ipynb$/)) {
75 nbname = nbname + ".ipynb";
76 }
77 var data = {name: nbname};
78 var settings = {
79 processData : false,
80 cache : false,
81 type : "PATCH",
82 data : JSON.stringify(data),
83 dataType: "json",
84 headers : {'Content-Type': 'application/json'},
85 success : $.proxy(that.rename_success, this),
86 error : $.proxy(that.rename_error, this)
87 };
88 this.events.trigger('rename_notebook.Notebook', data);
89 var url = utils.url_join_encode(
90 this.base_url,
91 'api/notebooks',
92 this.notebook_path,
93 this.notebook_name
94 );
95 $.ajax(url, settings);
27 }
96 }
28
97
29 ContentManager.prototype.save_notebook = function(notebook, extra_settings) {
98 ContentManager.prototype.save_notebook = function(notebook, extra_settings) {
30 }
99 // Create a JSON model to be sent to the server.
100 var model = {};
101 model.name = notebook.notebook_name;
102 model.path = notebook.notebook_path;
103 model.content = notebook.toJSON();
104 model.content.nbformat = notebook.nbformat;
105 model.content.nbformat_minor = notebook.nbformat_minor;
106 // time the ajax call for autosave tuning purposes.
107 var start = new Date().getTime();
108 // We do the call with settings so we can set cache to false.
109 var settings = {
110 processData : false,
111 cache : false,
112 type : "PUT",
113 data : JSON.stringify(model),
114 headers : {'Content-Type': 'application/json'},
115 success : $.proxy(notebook.save_notebook_success, this, start),
116 error : $.proxy(notebook.save_notebook_error, this)
117 };
118 if (extra_settings) {
119 for (var key in extra_settings) {
120 settings[key] = extra_settings[key];
121 }
122 }
123 notebook.events.trigger('notebook_saving.Notebook');
124 var url = utils.url_join_encode(
125 notebook.base_url,
126 'api/notebooks',
127 notebook.notebook_path,
128 notebook.notebook_name
129 );
130 $.ajax(url, settings);
131 };
132 }
31
133
32 ContentManager.prototype.save_checkpoint = function() {
134 ContentManager.prototype.save_checkpoint = function() {
135 // This is not necessary - integrated into save
33 }
136 }
34
137
35 ContentManager.prototype.restore_checkpoint = function(id) {
138 ContentManager.prototype.restore_checkpoint = function(notebook, id) {
139 that = notebook;
140 this.events.trigger('notebook_restoring.Notebook', checkpoint);
141 var url = utils.url_join_encode(
142 this.base_url,
143 'api/notebooks',
144 this.notebook_path,
145 this.notebook_name,
146 'checkpoints',
147 checkpoint
148 );
149 $.post(url).done(
150 $.proxy(that.restore_checkpoint_success, that)
151 ).fail(
152 $.proxy(that.restore_checkpoint_error, that)
153 );
36 }
154 }
37
155
38 ContentManager.prototype.list_checkpoints = function() {
156 ContentManager.prototype.list_checkpoints = function(notebook) {
157 that = notebook;
158 var url = utils.url_join_encode(
159 that.base_url,
160 'api/notebooks',
161 that.notebook_path,
162 that.notebook_name,
163 'checkpoints'
164 );
165 $.get(url).done(
166 $.proxy(that.list_checkpoints_success, that)
167 ).fail(
168 $.proxy(that.list_checkpoints_error, that)
169 );
39 }
170 }
40
171
41 return ContentManager;
172 return ContentManager;
42 });
173 });
General Comments 0
You need to be logged in to leave comments. Login now