Show More
@@ -17,7 +17,7 from ..utils import url_escape | |||||
17 | class NotebookHandler(IPythonHandler): |
|
17 | class NotebookHandler(IPythonHandler): | |
18 |
|
18 | |||
19 | @web.authenticated |
|
19 | @web.authenticated | |
20 |
def get(self, path |
|
20 | def get(self, path): | |
21 | """get renders the notebook template if a name is given, or |
|
21 | """get renders the notebook template if a name is given, or | |
22 | redirects to the '/files/' handler if the name is not given.""" |
|
22 | redirects to the '/files/' handler if the name is not given.""" | |
23 | path = path.strip('/') |
|
23 | path = path.strip('/') |
@@ -187,7 +187,9 class NotebookWebApplication(web.Application): | |||||
187 | return settings |
|
187 | return settings | |
188 |
|
188 | |||
189 | def init_handlers(self, settings): |
|
189 | def init_handlers(self, settings): | |
190 |
|
|
190 | """Load the (URL pattern, handler) tuples for each component.""" | |
|
191 | ||||
|
192 | # Order matters. The first handler to match the URL will handle the request. | |||
191 | handlers = [] |
|
193 | handlers = [] | |
192 | handlers.extend(load_handlers('tree.handlers')) |
|
194 | handlers.extend(load_handlers('tree.handlers')) | |
193 | handlers.extend(load_handlers('auth.login')) |
|
195 | handlers.extend(load_handlers('auth.login')) | |
@@ -205,6 +207,7 class NotebookWebApplication(web.Application): | |||||
205 | handlers.append( |
|
207 | handlers.append( | |
206 | (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}), |
|
208 | (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}), | |
207 | ) |
|
209 | ) | |
|
210 | # register base handlers last | |||
208 | handlers.extend(load_handlers('base.handlers')) |
|
211 | handlers.extend(load_handlers('base.handlers')) | |
209 | # set the URL that will be redirected from `/` |
|
212 | # set the URL that will be redirected from `/` | |
210 | handlers.append( |
|
213 | handlers.append( |
@@ -61,9 +61,8 class FileContentsManager(ContentsManager): | |||||
61 | except OSError as e: |
|
61 | except OSError as e: | |
62 | self.log.debug("copystat on %s failed", dest, exc_info=True) |
|
62 | self.log.debug("copystat on %s failed", dest, exc_info=True) | |
63 |
|
63 | |||
64 |
def _get_os_path(self, path |
|
64 | def _get_os_path(self, path): | |
65 |
"""Given a |
|
65 | """Given an API path, return its file system path. | |
66 | path. |
|
|||
67 |
|
66 | |||
68 | Parameters |
|
67 | Parameters | |
69 | ---------- |
|
68 | ---------- | |
@@ -131,8 +130,8 class FileContentsManager(ContentsManager): | |||||
131 | Whether the file exists. |
|
130 | Whether the file exists. | |
132 | """ |
|
131 | """ | |
133 | path = path.strip('/') |
|
132 | path = path.strip('/') | |
134 |
|
|
133 | os_path = self._get_os_path(path) | |
135 |
return os.path.isfile( |
|
134 | return os.path.isfile(os_path) | |
136 |
|
135 | |||
137 | def exists(self, path): |
|
136 | def exists(self, path): | |
138 | """Returns True if the path exists, else returns False. |
|
137 | """Returns True if the path exists, else returns False. | |
@@ -167,7 +166,6 class FileContentsManager(ContentsManager): | |||||
167 | model['created'] = created |
|
166 | model['created'] = created | |
168 | model['content'] = None |
|
167 | model['content'] = None | |
169 | model['format'] = None |
|
168 | model['format'] = None | |
170 | model['message'] = None |
|
|||
171 | return model |
|
169 | return model | |
172 |
|
170 | |||
173 | def _dir_model(self, path, content=True): |
|
171 | def _dir_model(self, path, content=True): | |
@@ -191,12 +189,16 class FileContentsManager(ContentsManager): | |||||
191 | model['type'] = 'directory' |
|
189 | model['type'] = 'directory' | |
192 | if content: |
|
190 | if content: | |
193 | model['content'] = contents = [] |
|
191 | model['content'] = contents = [] | |
194 |
|
|
192 | os_dir = self._get_os_path(path) | |
195 |
|
|
193 | for name in os.listdir(os_dir): | |
|
194 | os_path = os.path.join(os_dir, name) | |||
196 | # skip over broken symlinks in listing |
|
195 | # skip over broken symlinks in listing | |
197 | if not os.path.exists(os_path): |
|
196 | if not os.path.exists(os_path): | |
198 | self.log.warn("%s doesn't exist", os_path) |
|
197 | self.log.warn("%s doesn't exist", os_path) | |
199 | continue |
|
198 | continue | |
|
199 | elif not os.path.isfile(os_path) and not os.path.isdir(os_path): | |||
|
200 | self.log.debug("%s not a regular file", os_path) | |||
|
201 | continue | |||
200 | if self.should_list(name) and not is_hidden(os_path, self.root_dir): |
|
202 | if self.should_list(name) and not is_hidden(os_path, self.root_dir): | |
201 | contents.append(self.get_model( |
|
203 | contents.append(self.get_model( | |
202 | path='%s/%s' % (path, name), |
|
204 | path='%s/%s' % (path, name), | |
@@ -217,6 +219,9 class FileContentsManager(ContentsManager): | |||||
217 | model['type'] = 'file' |
|
219 | model['type'] = 'file' | |
218 | if content: |
|
220 | if content: | |
219 | os_path = self._get_os_path(path) |
|
221 | os_path = self._get_os_path(path) | |
|
222 | if not os.path.isfile(os_path): | |||
|
223 | # could be FIFO | |||
|
224 | raise web.HTTPError(400, "Cannot get content of non-file %s" % os_path) | |||
220 | with io.open(os_path, 'rb') as f: |
|
225 | with io.open(os_path, 'rb') as f: | |
221 | bcontent = f.read() |
|
226 | bcontent = f.read() | |
222 | try: |
|
227 | try: | |
@@ -407,14 +412,14 class FileContentsManager(ContentsManager): | |||||
407 | old_os_path = self._get_os_path(old_path) |
|
412 | old_os_path = self._get_os_path(old_path) | |
408 |
|
413 | |||
409 | # Should we proceed with the move? |
|
414 | # Should we proceed with the move? | |
410 |
if os.path. |
|
415 | if os.path.exists(new_os_path): | |
411 |
raise web.HTTPError(409, u'File already exists: %s' % new_ |
|
416 | raise web.HTTPError(409, u'File already exists: %s' % new_path) | |
412 |
|
417 | |||
413 | # Move the file |
|
418 | # Move the file | |
414 | try: |
|
419 | try: | |
415 | shutil.move(old_os_path, new_os_path) |
|
420 | shutil.move(old_os_path, new_os_path) | |
416 | except Exception as e: |
|
421 | except Exception as e: | |
417 |
raise web.HTTPError(500, u'Unknown error renaming file: %s %s' % (old_ |
|
422 | raise web.HTTPError(500, u'Unknown error renaming file: %s %s' % (old_path, e)) | |
418 |
|
423 | |||
419 | # Move the checkpoints |
|
424 | # Move the checkpoints | |
420 | old_checkpoints = self.list_checkpoints(old_path) |
|
425 | old_checkpoints = self.list_checkpoints(old_path) | |
@@ -462,6 +467,8 class FileContentsManager(ContentsManager): | |||||
462 | def create_checkpoint(self, path): |
|
467 | def create_checkpoint(self, path): | |
463 | """Create a checkpoint from the current state of a file""" |
|
468 | """Create a checkpoint from the current state of a file""" | |
464 | path = path.strip('/') |
|
469 | path = path.strip('/') | |
|
470 | if not self.file_exists(path): | |||
|
471 | raise web.HTTPError(404) | |||
465 | src_path = self._get_os_path(path) |
|
472 | src_path = self._get_os_path(path) | |
466 | # only the one checkpoint ID: |
|
473 | # only the one checkpoint ID: | |
467 | checkpoint_id = u"checkpoint" |
|
474 | checkpoint_id = u"checkpoint" | |
@@ -521,7 +528,7 class FileContentsManager(ContentsManager): | |||||
521 | def get_kernel_path(self, path, model=None): |
|
528 | def get_kernel_path(self, path, model=None): | |
522 | """Return the initial working dir a kernel associated with a given notebook""" |
|
529 | """Return the initial working dir a kernel associated with a given notebook""" | |
523 | if '/' in path: |
|
530 | if '/' in path: | |
524 |
|
|
531 | parent_dir = path.rsplit('/', 1)[0] | |
525 | else: |
|
532 | else: | |
526 |
|
|
533 | parent_dir = '' | |
527 |
return self._get_os_path( |
|
534 | return self._get_os_path(parent_dir) |
@@ -73,14 +73,11 class ContentsHandler(IPythonHandler): | |||||
73 | model = self.get_json_body() |
|
73 | model = self.get_json_body() | |
74 | if model is None: |
|
74 | if model is None: | |
75 | raise web.HTTPError(400, u'JSON body missing') |
|
75 | raise web.HTTPError(400, u'JSON body missing') | |
76 | print('before', model) |
|
|||
77 | model = cm.update(model, path) |
|
76 | model = cm.update(model, path) | |
78 | print('after', model) |
|
|||
79 | self._finish_model(model) |
|
77 | self._finish_model(model) | |
80 |
|
78 | |||
81 | def _copy(self, copy_from, copy_to=None): |
|
79 | def _copy(self, copy_from, copy_to=None): | |
82 |
"""Copy a file, optionally specifying |
|
80 | """Copy a file, optionally specifying a target directory.""" | |
83 | """ |
|
|||
84 | self.log.info(u"Copying {copy_from} to {copy_to}".format( |
|
81 | self.log.info(u"Copying {copy_from} to {copy_to}".format( | |
85 | copy_from=copy_from, |
|
82 | copy_from=copy_from, | |
86 | copy_to=copy_to or '', |
|
83 | copy_to=copy_to or '', | |
@@ -96,13 +93,17 class ContentsHandler(IPythonHandler): | |||||
96 | self.set_status(201) |
|
93 | self.set_status(201) | |
97 | self._finish_model(model) |
|
94 | self._finish_model(model) | |
98 |
|
95 | |||
99 |
def _ |
|
96 | def _new(self, path, type='notebook', ext=''): | |
100 | """Create an empty file in path |
|
97 | """Create an empty file or directory in directory specified by path | |
101 |
|
98 | |||
102 | If name specified, create it in path. |
|
99 | ContentsManager picks the filename. | |
103 | """ |
|
100 | """ | |
104 |
self.log.info(u"Creating new |
|
101 | self.log.info(u"Creating new %s in %s", type or 'file', path) | |
105 | model = self.contents_manager.new(path=path, ext=ext) |
|
102 | if type: | |
|
103 | model = {'type': type} | |||
|
104 | else: | |||
|
105 | model = None | |||
|
106 | model = self.contents_manager.new(model, path=path, ext=ext) | |||
106 | self.set_status(201) |
|
107 | self.set_status(201) | |
107 | self._finish_model(model) |
|
108 | self._finish_model(model) | |
108 |
|
109 | |||
@@ -115,9 +116,9 class ContentsHandler(IPythonHandler): | |||||
115 | @web.authenticated |
|
116 | @web.authenticated | |
116 | @json_errors |
|
117 | @json_errors | |
117 | def post(self, path=''): |
|
118 | def post(self, path=''): | |
118 |
"""Create a new file |
|
119 | """Create a new file in the specified path. | |
119 |
|
120 | |||
120 |
POST creates new files |
|
121 | POST creates new files. The server always decides on the name. | |
121 |
|
122 | |||
122 | POST /api/contents/path |
|
123 | POST /api/contents/path | |
123 | New untitled, empty file or directory. |
|
124 | New untitled, empty file or directory. | |
@@ -129,7 +130,7 class ContentsHandler(IPythonHandler): | |||||
129 | cm = self.contents_manager |
|
130 | cm = self.contents_manager | |
130 |
|
131 | |||
131 | if cm.file_exists(path): |
|
132 | if cm.file_exists(path): | |
132 |
raise web.HTTPError(400, "Cannot POST to |
|
133 | raise web.HTTPError(400, "Cannot POST to files, use PUT instead.") | |
133 |
|
134 | |||
134 | if not cm.dir_exists(path): |
|
135 | if not cm.dir_exists(path): | |
135 | raise web.HTTPError(404, "No such directory: %s" % path) |
|
136 | raise web.HTTPError(404, "No such directory: %s" % path) | |
@@ -138,13 +139,14 class ContentsHandler(IPythonHandler): | |||||
138 |
|
139 | |||
139 | if model is not None: |
|
140 | if model is not None: | |
140 | copy_from = model.get('copy_from') |
|
141 | copy_from = model.get('copy_from') | |
141 |
ext = model.get('ext', ' |
|
142 | ext = model.get('ext', '') | |
|
143 | type = model.get('type') | |||
142 | if copy_from: |
|
144 | if copy_from: | |
143 | self._copy(copy_from, path) |
|
145 | self._copy(copy_from, path) | |
144 | else: |
|
146 | else: | |
145 |
self._ |
|
147 | self._new(path, type=type, ext=ext) | |
146 | else: |
|
148 | else: | |
147 |
self._ |
|
149 | self._new(path) | |
148 |
|
150 | |||
149 | @web.authenticated |
|
151 | @web.authenticated | |
150 | @json_errors |
|
152 | @json_errors | |
@@ -168,7 +170,7 class ContentsHandler(IPythonHandler): | |||||
168 | else: |
|
170 | else: | |
169 | self._upload(model, path) |
|
171 | self._upload(model, path) | |
170 | else: |
|
172 | else: | |
171 |
self._ |
|
173 | self._new(path) | |
172 |
|
174 | |||
173 | @web.authenticated |
|
175 | @web.authenticated | |
174 | @json_errors |
|
176 | @json_errors |
@@ -119,7 +119,7 class ContentsManager(LoggingConfigurable): | |||||
119 | raise NotImplementedError('must be implemented in a subclass') |
|
119 | raise NotImplementedError('must be implemented in a subclass') | |
120 |
|
120 | |||
121 | def exists(self, path): |
|
121 | def exists(self, path): | |
122 |
"""Does a file or directory exist at the given |
|
122 | """Does a file or directory exist at the given path? | |
123 |
|
123 | |||
124 | Like os.path.exists |
|
124 | Like os.path.exists | |
125 |
|
125 | |||
@@ -181,7 +181,11 class ContentsManager(LoggingConfigurable): | |||||
181 | return "Serving contents" |
|
181 | return "Serving contents" | |
182 |
|
182 | |||
183 | def get_kernel_path(self, path, model=None): |
|
183 | def get_kernel_path(self, path, model=None): | |
184 |
""" |
|
184 | """Return the API path for the kernel | |
|
185 | ||||
|
186 | KernelManagers can turn this value into a filesystem path, | |||
|
187 | or ignore it altogether. | |||
|
188 | """ | |||
185 | return path |
|
189 | return path | |
186 |
|
190 | |||
187 | def increment_filename(self, filename, path=''): |
|
191 | def increment_filename(self, filename, path=''): | |
@@ -204,7 +208,7 class ContentsManager(LoggingConfigurable): | |||||
204 | for i in itertools.count(): |
|
208 | for i in itertools.count(): | |
205 | name = u'{basename}{i}{ext}'.format(basename=basename, i=i, |
|
209 | name = u'{basename}{i}{ext}'.format(basename=basename, i=i, | |
206 | ext=ext) |
|
210 | ext=ext) | |
207 |
if not self. |
|
211 | if not self.exists(u'{}/{}'.format(path, name)): | |
208 | break |
|
212 | break | |
209 | return name |
|
213 | return name | |
210 |
|
214 | |||
@@ -218,22 +222,35 class ContentsManager(LoggingConfigurable): | |||||
218 | ) |
|
222 | ) | |
219 | return model |
|
223 | return model | |
220 |
|
224 | |||
221 |
def new(self, model=None, path='', ext=' |
|
225 | def new(self, model=None, path='', ext=''): | |
222 |
"""Create a new file or directory and return its model with no content. |
|
226 | """Create a new file or directory and return its model with no content. | |
|
227 | ||||
|
228 | If path is a directory, a new untitled file/directory is created in path. | |||
|
229 | Otherwise, a new file/directory is created exactly at path. | |||
|
230 | """ | |||
223 | path = path.strip('/') |
|
231 | path = path.strip('/') | |
224 | if model is None: |
|
232 | if model is None: | |
225 | model = {} |
|
233 | model = {} | |
226 | else: |
|
234 | else: | |
227 | model.pop('path', None) |
|
235 | model.pop('path', None) | |
228 | if 'content' not in model and model.get('type', None) != 'directory': |
|
236 | ||
229 |
|
|
237 | if ext and ext != '.ipynb': | |
|
238 | model.setdefault('type', 'file') | |||
|
239 | else: | |||
|
240 | model.setdefault('type', 'notebook') | |||
|
241 | ||||
|
242 | # no content, not a directory, so fill out new-file model | |||
|
243 | if 'content' not in model and model['type'] != 'directory': | |||
|
244 | if model['type'] == 'notebook': | |||
|
245 | ext = '.ipynb' | |||
230 | model['content'] = new_notebook() |
|
246 | model['content'] = new_notebook() | |
231 | model['type'] = 'notebook' |
|
|||
232 | model['format'] = 'json' |
|
247 | model['format'] = 'json' | |
233 | else: |
|
248 | else: | |
234 | model['content'] = '' |
|
249 | model['content'] = '' | |
235 | model['type'] = 'file' |
|
250 | model['type'] = 'file' | |
236 | model['format'] = 'text' |
|
251 | model['format'] = 'text' | |
|
252 | ||||
|
253 | # if path is a directory, create an untitled file or directory | |||
237 | if self.dir_exists(path): |
|
254 | if self.dir_exists(path): | |
238 | if model['type'] == 'directory': |
|
255 | if model['type'] == 'directory': | |
239 | untitled = self.untitled_directory |
|
256 | untitled = self.untitled_directory | |
@@ -252,10 +269,10 class ContentsManager(LoggingConfigurable): | |||||
252 | def copy(self, from_path, to_path=None): |
|
269 | def copy(self, from_path, to_path=None): | |
253 | """Copy an existing file and return its new model. |
|
270 | """Copy an existing file and return its new model. | |
254 |
|
271 | |||
255 |
If to_ |
|
272 | If to_path not specified, it will be the parent directory of from_path. | |
|
273 | If to_path is a directory, filename will increment `from_path-Copy#.ext`. | |||
256 |
|
274 | |||
257 |
|
|
275 | from_path must be a full path to a file. | |
258 | or just a base name. If a base name, `path` is used. |
|
|||
259 | """ |
|
276 | """ | |
260 | path = from_path.strip('/') |
|
277 | path = from_path.strip('/') | |
261 | if '/' in path: |
|
278 | if '/' in path: | |
@@ -325,7 +342,7 class ContentsManager(LoggingConfigurable): | |||||
325 | nb : dict |
|
342 | nb : dict | |
326 | The notebook object (in current nbformat) |
|
343 | The notebook object (in current nbformat) | |
327 | path : string |
|
344 | path : string | |
328 |
The notebook's |
|
345 | The notebook's path (for logging) | |
329 | """ |
|
346 | """ | |
330 | trusted = self.notary.check_signature(nb) |
|
347 | trusted = self.notary.check_signature(nb) | |
331 | if not trusted: |
|
348 | if not trusted: |
@@ -55,6 +55,9 class API(object): | |||||
55 | body = json.dumps({'ext': ext}) |
|
55 | body = json.dumps({'ext': ext}) | |
56 | return self._req('POST', path, body) |
|
56 | return self._req('POST', path, body) | |
57 |
|
57 | |||
|
58 | def mkdir_untitled(self, path='/'): | |||
|
59 | return self._req('POST', path, json.dumps({'type': 'directory'})) | |||
|
60 | ||||
58 | def copy(self, copy_from, path='/'): |
|
61 | def copy(self, copy_from, path='/'): | |
59 | body = json.dumps({'copy_from':copy_from}) |
|
62 | body = json.dumps({'copy_from':copy_from}) | |
60 | return self._req('POST', path, body) |
|
63 | return self._req('POST', path, body) | |
@@ -65,6 +68,9 class API(object): | |||||
65 | def upload(self, path, body): |
|
68 | def upload(self, path, body): | |
66 | return self._req('PUT', path, body) |
|
69 | return self._req('PUT', path, body) | |
67 |
|
70 | |||
|
71 | def mkdir_untitled(self, path='/'): | |||
|
72 | return self._req('POST', path, json.dumps({'type': 'directory'})) | |||
|
73 | ||||
68 | def mkdir(self, path='/'): |
|
74 | def mkdir(self, path='/'): | |
69 | return self._req('PUT', path, json.dumps({'type': 'directory'})) |
|
75 | return self._req('PUT', path, json.dumps({'type': 'directory'})) | |
70 |
|
76 | |||
@@ -193,8 +199,11 class APITest(NotebookTestBase): | |||||
193 | self.assertEqual(nbnames, expected) |
|
199 | self.assertEqual(nbnames, expected) | |
194 |
|
200 | |||
195 | def test_list_dirs(self): |
|
201 | def test_list_dirs(self): | |
|
202 | print(self.api.list().json()) | |||
196 | dirs = dirs_only(self.api.list().json()) |
|
203 | dirs = dirs_only(self.api.list().json()) | |
197 | dir_names = {normalize('NFC', d['name']) for d in dirs} |
|
204 | dir_names = {normalize('NFC', d['name']) for d in dirs} | |
|
205 | print(dir_names) | |||
|
206 | print(self.top_level_dirs) | |||
198 | self.assertEqual(dir_names, self.top_level_dirs) # Excluding hidden dirs |
|
207 | self.assertEqual(dir_names, self.top_level_dirs) # Excluding hidden dirs | |
199 |
|
208 | |||
200 | def test_list_nonexistant_dir(self): |
|
209 | def test_list_nonexistant_dir(self): | |
@@ -293,6 +302,18 class APITest(NotebookTestBase): | |||||
293 | resp = self.api.upload(path, body=json.dumps(nbmodel)) |
|
302 | resp = self.api.upload(path, body=json.dumps(nbmodel)) | |
294 | self._check_created(resp, path) |
|
303 | self._check_created(resp, path) | |
295 |
|
304 | |||
|
305 | def test_mkdir_untitled(self): | |||
|
306 | resp = self.api.mkdir_untitled(path=u'å b') | |||
|
307 | self._check_created(resp, u'å b/Untitled Folder0', type='directory') | |||
|
308 | ||||
|
309 | # Second time | |||
|
310 | resp = self.api.mkdir_untitled(path=u'å b') | |||
|
311 | self._check_created(resp, u'å b/Untitled Folder1', type='directory') | |||
|
312 | ||||
|
313 | # And two directories down | |||
|
314 | resp = self.api.mkdir_untitled(path='foo/bar') | |||
|
315 | self._check_created(resp, 'foo/bar/Untitled Folder0', type='directory') | |||
|
316 | ||||
296 | def test_mkdir(self): |
|
317 | def test_mkdir(self): | |
297 | path = u'å b/New ∂ir' |
|
318 | path = u'å b/New ∂ir' | |
298 | resp = self.api.mkdir(path) |
|
319 | resp = self.api.mkdir(path) |
@@ -90,9 +90,9 define([ | |||||
90 | this.element.find('#new_notebook').click(function () { |
|
90 | this.element.find('#new_notebook').click(function () { | |
91 | // Create a new notebook in the same path as the current |
|
91 | // Create a new notebook in the same path as the current | |
92 | // notebook's path. |
|
92 | // notebook's path. | |
93 |
var parent = utils.url_path_split(th |
|
93 | var parent = utils.url_path_split(that.notebook.notebook_path)[0]; | |
94 | that.contents.new(parent, { |
|
94 | that.contents.new(parent, { | |
95 |
e |
|
95 | type: "notebook", | |
96 | extra_settings: {async: false}, // So we can open a new window afterwards |
|
96 | extra_settings: {async: false}, // So we can open a new window afterwards | |
97 | success: function (data) { |
|
97 | success: function (data) { | |
98 | window.open( |
|
98 | window.open( |
@@ -98,9 +98,13 define([ | |||||
98 | * @param {String} path The path to create the new notebook at |
|
98 | * @param {String} path The path to create the new notebook at | |
99 | * @param {Object} options: |
|
99 | * @param {Object} options: | |
100 | * ext: file extension to use |
|
100 | * ext: file extension to use | |
|
101 | * type: model type to create ('notebook', 'file', or 'directory') | |||
101 | */ |
|
102 | */ | |
102 | Contents.prototype.new = function(path, options) { |
|
103 | Contents.prototype.new = function(path, options) { | |
103 |
var data = JSON.stringify({ |
|
104 | var data = JSON.stringify({ | |
|
105 | ext: options.ext, | |||
|
106 | type: options.type | |||
|
107 | }); | |||
104 |
|
108 | |||
105 | var settings = { |
|
109 | var settings = { | |
106 | processData : false, |
|
110 | processData : false, |
@@ -65,7 +65,7 require([ | |||||
65 |
|
65 | |||
66 | $('#new_notebook').click(function (e) { |
|
66 | $('#new_notebook').click(function (e) { | |
67 | contents.new(common_options.notebook_path, { |
|
67 | contents.new(common_options.notebook_path, { | |
68 |
e |
|
68 | type: "notebook", | |
69 | extra_settings: {async: false}, // So we can open a new window afterwards |
|
69 | extra_settings: {async: false}, // So we can open a new window afterwards | |
70 | success: function (data) { |
|
70 | success: function (data) { | |
71 | window.open( |
|
71 | window.open( |
@@ -37,9 +37,12 class TreeHandler(IPythonHandler): | |||||
37 | path = path.strip('/') |
|
37 | path = path.strip('/') | |
38 | cm = self.contents_manager |
|
38 | cm = self.contents_manager | |
39 | if cm.file_exists(path): |
|
39 | if cm.file_exists(path): | |
40 |
# i |
|
40 | # it's not a directory, we have redirecting to do | |
|
41 | model = cm.get(path, content=False) | |||
|
42 | # redirect to /api/notebooks if it's a notebook, otherwise /api/files | |||
|
43 | service = 'notebooks' if model['type'] == 'notebook' else 'files' | |||
41 | url = url_escape(url_path_join( |
|
44 | url = url_escape(url_path_join( | |
42 |
self.base_url, |
|
45 | self.base_url, service, path, | |
43 | )) |
|
46 | )) | |
44 | self.log.debug("Redirecting %s to %s", self.request.path, url) |
|
47 | self.log.debug("Redirecting %s to %s", self.request.path, url) | |
45 | self.redirect(url) |
|
48 | self.redirect(url) |
General Comments 0
You need to be logged in to leave comments.
Login now