##// END OF EJS Templates
updates per review...
MinRK -
Show More
@@ -54,7 +54,7 b' class FileContentsManager(ContentsManager):'
54 self.log.debug("copystat on %s failed", dest, exc_info=True)
54 self.log.debug("copystat on %s failed", dest, exc_info=True)
55
55
56 def _get_os_path(self, name=None, path=''):
56 def _get_os_path(self, name=None, path=''):
57 """Given a filename and a URL path, return its file system
57 """Given a filename and API path, return its file system
58 path.
58 path.
59
59
60 Parameters
60 Parameters
@@ -62,8 +62,7 b' class FileContentsManager(ContentsManager):'
62 name : string
62 name : string
63 A filename
63 A filename
64 path : string
64 path : string
65 The relative URL path (with '/' as separator) to the named
65 The relative API path to the named file.
66 file.
67
66
68 Returns
67 Returns
69 -------
68 -------
@@ -77,6 +76,8 b' class FileContentsManager(ContentsManager):'
77 def path_exists(self, path):
76 def path_exists(self, path):
78 """Does the API-style path refer to an extant directory?
77 """Does the API-style path refer to an extant directory?
79
78
79 API-style wrapper for os.path.isdir
80
80 Parameters
81 Parameters
81 ----------
82 ----------
82 path : string
83 path : string
@@ -114,6 +115,8 b' class FileContentsManager(ContentsManager):'
114 def file_exists(self, name, path=''):
115 def file_exists(self, name, path=''):
115 """Returns True if the file exists, else returns False.
116 """Returns True if the file exists, else returns False.
116
117
118 API-style wrapper for os.path.isfile
119
117 Parameters
120 Parameters
118 ----------
121 ----------
119 name : string
122 name : string
@@ -123,7 +126,8 b' class FileContentsManager(ContentsManager):'
123
126
124 Returns
127 Returns
125 -------
128 -------
126 bool
129 exists : bool
130 Whether the file exists.
127 """
131 """
128 path = path.strip('/')
132 path = path.strip('/')
129 nbpath = self._get_os_path(name, path=path)
133 nbpath = self._get_os_path(name, path=path)
@@ -132,6 +136,8 b' class FileContentsManager(ContentsManager):'
132 def exists(self, name=None, path=''):
136 def exists(self, name=None, path=''):
133 """Returns True if the path [and name] exists, else returns False.
137 """Returns True if the path [and name] exists, else returns False.
134
138
139 API-style wrapper for os.path.exists
140
135 Parameters
141 Parameters
136 ----------
142 ----------
137 name : string
143 name : string
@@ -141,7 +147,8 b' class FileContentsManager(ContentsManager):'
141
147
142 Returns
148 Returns
143 -------
149 -------
144 bool
150 exists : bool
151 Whether the target exists.
145 """
152 """
146 path = path.strip('/')
153 path = path.strip('/')
147 os_path = self._get_os_path(name, path=path)
154 os_path = self._get_os_path(name, path=path)
@@ -246,7 +253,7 b' class FileContentsManager(ContentsManager):'
246 name : str
253 name : str
247 the name of the target
254 the name of the target
248 path : str
255 path : str
249 the URL path that describes the relative path for the target
256 the API path that describes the relative path for the target
250
257
251 Returns
258 Returns
252 -------
259 -------
@@ -344,7 +351,11 b' class FileContentsManager(ContentsManager):'
344 return model
351 return model
345
352
346 def update(self, model, name, path=''):
353 def update(self, model, name, path=''):
347 """Update the file's path and/or name"""
354 """Update the file's path and/or name
355
356 For use in PATCH requests, to enable renaming a file without
357 re-uploading its contents. Only used for renaming at the moment.
358 """
348 path = path.strip('/')
359 path = path.strip('/')
349 new_name = model.get('name', name)
360 new_name = model.get('name', name)
350 new_path = model.get('path', path).strip('/')
361 new_path = model.get('path', path).strip('/')
@@ -393,7 +404,7 b' class FileContentsManager(ContentsManager):'
393
404
394 # Should we proceed with the move?
405 # Should we proceed with the move?
395 if os.path.isfile(new_os_path):
406 if os.path.isfile(new_os_path):
396 raise web.HTTPError(409, u'Notebook with name already exists: %s' % new_os_path)
407 raise web.HTTPError(409, u'File with name already exists: %s' % new_os_path)
397
408
398 # Move the file
409 # Move the file
399 try:
410 try:
@@ -54,16 +54,16 b' class ContentsHandler(IPythonHandler):'
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 file or list of files.
57 """Return a model for a file or directory.
58
58
59 * GET with path and no filename lists files in a directory
59 A directory model contains a list of models (without content)
60 * GET with path and filename returns file contents model
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: front-ends shouldn't rely on this sorting
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
@@ -81,22 +81,22 b' class ContentsHandler(IPythonHandler):'
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 in path, optionally specifying the new name.
84 """Copy a file, optionally specifying the new name.
85
86 Only support copying within the same directory.
87 """
85 """
88 self.log.info(u"Copying from %s/%s to %s/%s",
86 self.log.info(u"Copying {copy_from} to {path}/{copy_to}".format(
89 path, copy_from,
87 copy_from=copy_from,
90 path, copy_to or '',
88 path=path,
91 )
89 copy_to=copy_to or '',
90 ))
92 model = self.contents_manager.copy(copy_from, copy_to, path)
91 model = self.contents_manager.copy(copy_from, copy_to, path)
93 self.set_status(201)
92 self.set_status(201)
94 self._finish_model(model)
93 self._finish_model(model)
95
94
96 def _upload(self, model, path, name=None):
95 def _upload(self, model, path, name=None):
97 """Upload a file
96 """Handle upload of a new file
98
97
99 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.
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:
@@ -151,7 +151,7 b' class ContentsHandler(IPythonHandler):'
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, "Only POST to directories. Use PUT for full names.")
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)
@@ -184,11 +184,17 b' class ContentsHandler(IPythonHandler):'
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?copy=OtherNotebook.ipynb
187 PUT /api/contents/path/Name.ipynb
188 with JSON body::
189
190 {
191 "copy_from" : "[path/to/]OtherNotebook.ipynb"
192 }
193
188 Copy OtherNotebook to Name
194 Copy OtherNotebook to Name
189 """
195 """
190 if name is None:
196 if name is None:
191 raise web.HTTPError(400, "Only PUT to full names. Use POST for directories.")
197 raise web.HTTPError(400, "name must be specified with PUT.")
192
198
193 model = self.get_json_body()
199 model = self.get_json_body()
194 if model:
200 if model:
@@ -15,6 +15,31 b' from IPython.utils.traitlets import Instance, Unicode, List'
15
15
16
16
17 class ContentsManager(LoggingConfigurable):
17 class ContentsManager(LoggingConfigurable):
18 """Base class for serving files and directories.
19
20 This serves any text or binary file,
21 as well as directories,
22 with special handling for JSON notebook documents.
23
24 Most APIs take a path argument,
25 which is always an API-style unicode path,
26 and always refers to a directory.
27
28 - unicode, not url-escaped
29 - '/'-separated
30 - leading and trailing '/' will be stripped
31 - if unspecified, path defaults to '',
32 indicating the root path.
33
34 name is also unicode, and refers to a specfic target:
35
36 - unicode, not url-escaped
37 - must not contain '/'
38 - It refers to an individual filename
39 - It may refer to a directory name,
40 in the case of listing or creating directories.
41
42 """
18
43
19 notary = Instance(sign.NotebookNotary)
44 notary = Instance(sign.NotebookNotary)
20 def _notary_default(self):
45 def _notary_default(self):
@@ -27,12 +52,26 b' class ContentsManager(LoggingConfigurable):'
27 Glob patterns to hide in file and directory listings.
52 Glob patterns to hide in file and directory listings.
28 """)
53 """)
29
54
55 untitled_notebook = Unicode("Untitled", config=True,
56 help="The base name used when creating untitled notebooks."
57 )
58
59 untitled_file = Unicode("untitled", config=True,
60 help="The base name used when creating untitled files."
61 )
62
63 untitled_directory = Unicode("Untitled Folder", config=True,
64 help="The base name used when creating untitled directories."
65 )
66
30 # ContentsManager API part 1: methods that must be
67 # ContentsManager API part 1: methods that must be
31 # implemented in subclasses.
68 # implemented in subclasses.
32
69
33 def path_exists(self, path):
70 def path_exists(self, path):
34 """Does the API-style path (directory) actually exist?
71 """Does the API-style path (directory) actually exist?
35
72
73 Like os.path.isdir
74
36 Override this method in subclasses.
75 Override this method in subclasses.
37
76
38 Parameters
77 Parameters
@@ -58,14 +97,18 b' class ContentsManager(LoggingConfigurable):'
58
97
59 Returns
98 Returns
60 -------
99 -------
61 exists : bool
100 hidden : bool
62 Whether the path is hidden.
101 Whether the path is hidden.
63
102
64 """
103 """
65 raise NotImplementedError
104 raise NotImplementedError
66
105
67 def file_exists(self, name, path=''):
106 def file_exists(self, name, path=''):
68 """Returns a True if the file exists. Else, returns False.
107 """Does a file exist at the given name and path?
108
109 Like os.path.isfile
110
111 Override this method in subclasses.
69
112
70 Parameters
113 Parameters
71 ----------
114 ----------
@@ -76,20 +119,29 b' class ContentsManager(LoggingConfigurable):'
76
119
77 Returns
120 Returns
78 -------
121 -------
79 bool
122 exists : bool
123 Whether the file exists.
80 """
124 """
81 raise NotImplementedError('must be implemented in a subclass')
125 raise NotImplementedError('must be implemented in a subclass')
82
126
83 def list(self, path=''):
127 def exists(self, name, path=''):
84 """Return a list of contents dicts without content.
128 """Does a file or directory exist at the given name and path?
85
129
86 This returns a list of dicts
130 Like os.path.exists
87
131
88 This list of dicts should be sorted by name::
132 Parameters
133 ----------
134 name : string
135 The name of the file you are checking.
136 path : string
137 The relative path to the file's directory (with '/' as separator)
89
138
90 data = sorted(data, key=lambda item: item['name'])
139 Returns
140 -------
141 exists : bool
142 Whether the target exists.
91 """
143 """
92 raise NotImplementedError('must be implemented in a subclass')
144 return self.file_exists(name, path) or self.path_exists("%s/%s" % (path, name))
93
145
94 def get_model(self, name, path='', content=True):
146 def get_model(self, name, path='', content=True):
95 """Get the model of a file or directory with or without content."""
147 """Get the model of a file or directory with or without content."""
@@ -100,7 +152,11 b' class ContentsManager(LoggingConfigurable):'
100 raise NotImplementedError('must be implemented in a subclass')
152 raise NotImplementedError('must be implemented in a subclass')
101
153
102 def update(self, model, name, path=''):
154 def update(self, model, name, path=''):
103 """Update the file or directory and return the model with no content."""
155 """Update the file or directory and return the model with no content.
156
157 For use in PATCH requests, to enable renaming a file without
158 re-uploading its contents. Only used for renaming at the moment.
159 """
104 raise NotImplementedError('must be implemented in a subclass')
160 raise NotImplementedError('must be implemented in a subclass')
105
161
106 def delete(self, name, path=''):
162 def delete(self, name, path=''):
@@ -126,12 +182,12 b' class ContentsManager(LoggingConfigurable):'
126 """delete a checkpoint for a file"""
182 """delete a checkpoint for a file"""
127 raise NotImplementedError("must be implemented in a subclass")
183 raise NotImplementedError("must be implemented in a subclass")
128
184
129 def info_string(self):
130 return "Serving notebooks"
131
132 # ContentsManager API part 2: methods that have useable default
185 # ContentsManager API part 2: methods that have useable default
133 # implementations, but can be overridden in subclasses.
186 # implementations, but can be overridden in subclasses.
134
187
188 def info_string(self):
189 return "Serving contents"
190
135 def get_kernel_path(self, name, path='', model=None):
191 def get_kernel_path(self, name, path='', model=None):
136 """ Return the path to start kernel in """
192 """ Return the path to start kernel in """
137 return path
193 return path
@@ -144,7 +200,7 b' class ContentsManager(LoggingConfigurable):'
144 filename : unicode
200 filename : unicode
145 The name of a file, including extension
201 The name of a file, including extension
146 path : unicode
202 path : unicode
147 The URL path of the target's directory
203 The API path of the target's directory
148
204
149 Returns
205 Returns
150 -------
206 -------
@@ -176,7 +232,15 b' class ContentsManager(LoggingConfigurable):'
176 model['type'] = 'file'
232 model['type'] = 'file'
177 model['format'] = 'text'
233 model['format'] = 'text'
178 if 'name' not in model:
234 if 'name' not in model:
179 model['name'] = self.increment_filename('Untitled' + ext, path)
235 if model['type'] == 'directory':
236 untitled = self.untitled_directory
237 elif model['type'] == 'notebook':
238 untitled = self.untitled_notebook
239 elif model['type'] == 'file':
240 untitled = self.untitled_file
241 else:
242 raise HTTPError(400, "Unexpected model type: %r" % model['type'])
243 model['name'] = self.increment_filename(untitled + ext, path)
180
244
181 model['path'] = path
245 model['path'] = path
182 model = self.save(model, model['name'], model['path'])
246 model = self.save(model, model['name'], model['path'])
@@ -186,9 +250,16 b' class ContentsManager(LoggingConfigurable):'
186 """Copy an existing file and return its new model.
250 """Copy an existing file and return its new model.
187
251
188 If to_name not specified, increment `from_name-Copy#.ext`.
252 If to_name not specified, increment `from_name-Copy#.ext`.
253
254 copy_from can be a full path to a file,
255 or just a base name. If a base name, `path` is used.
189 """
256 """
190 path = path.strip('/')
257 path = path.strip('/')
191 model = self.get_model(from_name, path)
258 if '/' in from_name:
259 from_path, from_name = from_name.rsplit('/', 1)
260 else:
261 from_path = path
262 model = self.get_model(from_name, from_path)
192 if model['type'] == 'directory':
263 if model['type'] == 'directory':
193 raise HTTPError(400, "Can't copy directories")
264 raise HTTPError(400, "Can't copy directories")
194 if not to_name:
265 if not to_name:
@@ -196,6 +267,7 b' class ContentsManager(LoggingConfigurable):'
196 copy_name = u'{0}-Copy{1}'.format(base, ext)
267 copy_name = u'{0}-Copy{1}'.format(base, ext)
197 to_name = self.increment_filename(copy_name, path)
268 to_name = self.increment_filename(copy_name, path)
198 model['name'] = to_name
269 model['name'] = to_name
270 model['path'] = path
199 model = self.save(model, to_name, path)
271 model = self.save(model, to_name, path)
200 return model
272 return model
201
273
@@ -218,7 +290,7 b' class ContentsManager(LoggingConfigurable):'
218 self.notary.mark_cells(nb, True)
290 self.notary.mark_cells(nb, True)
219 self.save(model, name, path)
291 self.save(model, name, path)
220
292
221 def check_and_sign(self, nb, name, path=''):
293 def check_and_sign(self, nb, name='', path=''):
222 """Check for trusted cells, and sign the notebook.
294 """Check for trusted cells, and sign the notebook.
223
295
224 Called as a part of saving notebooks.
296 Called as a part of saving notebooks.
@@ -226,18 +298,18 b' class ContentsManager(LoggingConfigurable):'
226 Parameters
298 Parameters
227 ----------
299 ----------
228 nb : dict
300 nb : dict
229 The notebook structure
301 The notebook object (in nbformat.current format)
230 name : string
302 name : string
231 The filename of the notebook
303 The filename of the notebook (for logging)
232 path : string
304 path : string
233 The notebook's directory
305 The notebook's directory (for logging)
234 """
306 """
235 if self.notary.check_cells(nb):
307 if self.notary.check_cells(nb):
236 self.notary.sign(nb)
308 self.notary.sign(nb)
237 else:
309 else:
238 self.log.warn("Saving untrusted notebook %s/%s", path, name)
310 self.log.warn("Saving untrusted notebook %s/%s", path, name)
239
311
240 def mark_trusted_cells(self, nb, name, path=''):
312 def mark_trusted_cells(self, nb, name='', path=''):
241 """Mark cells as trusted if the notebook signature matches.
313 """Mark cells as trusted if the notebook signature matches.
242
314
243 Called as a part of loading notebooks.
315 Called as a part of loading notebooks.
@@ -245,11 +317,11 b' class ContentsManager(LoggingConfigurable):'
245 Parameters
317 Parameters
246 ----------
318 ----------
247 nb : dict
319 nb : dict
248 The notebook structure
320 The notebook object (in nbformat.current format)
249 name : string
321 name : string
250 The filename of the notebook
322 The filename of the notebook (for logging)
251 path : string
323 path : string
252 The notebook's directory
324 The notebook's directory (for logging)
253 """
325 """
254 trusted = self.notary.check_signature(nb)
326 trusted = self.notary.check_signature(nb)
255 if not trusted:
327 if not trusted:
@@ -22,8 +22,6 b' from IPython.utils import py3compat'
22 from IPython.utils.data import uniq_stable
22 from IPython.utils.data import uniq_stable
23
23
24
24
25 # TODO: Remove this after we create the contents web service and directories are
26 # no longer listed by the notebook web service.
27 def notebooks_only(dir_model):
25 def notebooks_only(dir_model):
28 return [nb for nb in dir_model['content'] if nb['type']=='notebook']
26 return [nb for nb in dir_model['content'] if nb['type']=='notebook']
29
27
@@ -279,9 +277,9 b' class APITest(NotebookTestBase):'
279
277
280 def test_create_untitled_txt(self):
278 def test_create_untitled_txt(self):
281 resp = self.api.create_untitled(path='foo/bar', ext='.txt')
279 resp = self.api.create_untitled(path='foo/bar', ext='.txt')
282 self._check_created(resp, 'Untitled0.txt', 'foo/bar', type='file')
280 self._check_created(resp, 'untitled0.txt', 'foo/bar', type='file')
283
281
284 resp = self.api.read(path='foo/bar', name='Untitled0.txt')
282 resp = self.api.read(path='foo/bar', name='untitled0.txt')
285 model = resp.json()
283 model = resp.json()
286 self.assertEqual(model['type'], 'file')
284 self.assertEqual(model['type'], 'file')
287 self.assertEqual(model['format'], 'text')
285 self.assertEqual(model['format'], 'text')
@@ -363,6 +361,10 b' class APITest(NotebookTestBase):'
363 resp = self.api.copy(u'Γ§ d.ipynb', u'cΓΈpy.ipynb', path=u'Γ₯ b')
361 resp = self.api.copy(u'Γ§ d.ipynb', u'cΓΈpy.ipynb', path=u'Γ₯ b')
364 self._check_created(resp, u'cΓΈpy.ipynb', u'Γ₯ b')
362 self._check_created(resp, u'cΓΈpy.ipynb', u'Γ₯ b')
365
363
364 def test_copy_path(self):
365 resp = self.api.copy(u'foo/a.ipynb', u'cΓΈpyfoo.ipynb', path=u'Γ₯ b')
366 self._check_created(resp, u'cΓΈpyfoo.ipynb', u'Γ₯ b')
367
366 def test_copy_dir_400(self):
368 def test_copy_dir_400(self):
367 # can't copy directories
369 # can't copy directories
368 with assert_http_error(400):
370 with assert_http_error(400):
@@ -19,7 +19,7 b' define(['
19 // base_url: string
19 // base_url: string
20 // notebook_path: string
20 // notebook_path: string
21 notebooklist.NotebookList.call(this, selector, $.extend({
21 notebooklist.NotebookList.call(this, selector, $.extend({
22 element_name: 'running'},
22 element_name: 'running'},
23 options));
23 options));
24 };
24 };
25
25
@@ -28,13 +28,20 b' define(['
28 KernelList.prototype.sessions_loaded = function (d) {
28 KernelList.prototype.sessions_loaded = function (d) {
29 this.sessions = d;
29 this.sessions = d;
30 this.clear_list();
30 this.clear_list();
31 var item;
31 var item, path_name;
32 for (var path in d) {
32 for (path_name in d) {
33 item = this.new_notebook_item(-1);
33 if (!d.hasOwnProperty(path_name)) {
34 this.add_link('', path, item);
34 // nothing is safe in javascript
35 this.add_shutdown_button(item, this.sessions[path]);
35 continue;
36 }
37 item = this.new_item(-1);
38 this.add_link({
39 name: path_name,
40 path: '',
41 type: 'notebook',
42 }, item);
43 this.add_shutdown_button(item, this.sessions[path_name]);
36 }
44 }
37
38 $('#running_list_header').toggle($.isEmptyObject(d));
45 $('#running_list_header').toggle($.isEmptyObject(d));
39 };
46 };
40
47
@@ -80,7 +80,7 b' define(['
80 var name_and_ext = utils.splitext(f.name);
80 var name_and_ext = utils.splitext(f.name);
81 var file_ext = name_and_ext[1];
81 var file_ext = name_and_ext[1];
82 if (file_ext === '.ipynb') {
82 if (file_ext === '.ipynb') {
83 var item = that.new_notebook_item(0);
83 var item = that.new_item(0);
84 item.addClass('new-file');
84 item.addClass('new-file');
85 that.add_name_input(f.name, item);
85 that.add_name_input(f.name, item);
86 // Store the notebook item in the reader so we can use it later
86 // Store the notebook item in the reader so we can use it later
@@ -236,7 +236,7 b' define(['
236 var icon = NotebookList.icons[model.type];
236 var icon = NotebookList.icons[model.type];
237 var uri_prefix = NotebookList.uri_prefixes[model.type];
237 var uri_prefix = NotebookList.uri_prefixes[model.type];
238 item.find(".item_icon").addClass(icon).addClass('icon-fixed-width');
238 item.find(".item_icon").addClass(icon).addClass('icon-fixed-width');
239 item.find("a.item_link")
239 var link = item.find("a.item_link")
240 .attr('href',
240 .attr('href',
241 utils.url_join_encode(
241 utils.url_join_encode(
242 this.base_url,
242 this.base_url,
@@ -245,6 +245,11 b' define(['
245 name
245 name
246 )
246 )
247 );
247 );
248 // directory nav doesn't open new tabs
249 // files, notebooks do
250 if (model.type !== "directory") {
251 link.attr('target','_blank');
252 }
248 var path_name = utils.url_path_join(path, name);
253 var path_name = utils.url_path_join(path, name);
249 if (model.type == 'file') {
254 if (model.type == 'file') {
250 this.add_delete_button(item);
255 this.add_delete_button(item);
@@ -362,7 +367,10 b' define(['
362 var nbdata = item.data('nbdata');
367 var nbdata = item.data('nbdata');
363 var content_type = 'application/json';
368 var content_type = 'application/json';
364 var model = {
369 var model = {
370 path: path,
371 name: nbname,
365 content : JSON.parse(nbdata),
372 content : JSON.parse(nbdata),
373 type : 'notebook'
366 };
374 };
367 var settings = {
375 var settings = {
368 processData : false,
376 processData : false,
@@ -372,7 +380,7 b' define(['
372 data : JSON.stringify(model),
380 data : JSON.stringify(model),
373 headers : {'Content-Type': content_type},
381 headers : {'Content-Type': content_type},
374 success : function (data, status, xhr) {
382 success : function (data, status, xhr) {
375 that.add_link(path, nbname, item);
383 that.add_link(model, item);
376 that.add_delete_button(item);
384 that.add_delete_button(item);
377 },
385 },
378 error : utils.log_ajax_error,
386 error : utils.log_ajax_error,
General Comments 0
You need to be logged in to leave comments. Login now