##// END OF EJS Templates
adjust definition of 'path' in notebooks...
MinRK -
Show More
@@ -23,6 +23,7 b' from zmq.utils import jsonapi'
23
23
24
24
25 from ..base.handlers import IPythonHandler
25 from ..base.handlers import IPythonHandler
26 from ..services.notebooks.handlers import _notebook_path_regex, _path_regex
26 from ..utils import url_path_join
27 from ..utils import url_path_join
27 from urllib import quote
28 from urllib import quote
28
29
@@ -51,32 +52,31 b' class NotebookHandler(IPythonHandler):'
51 class NamedNotebookHandler(IPythonHandler):
52 class NamedNotebookHandler(IPythonHandler):
52
53
53 @web.authenticated
54 @web.authenticated
54 def get(self, notebook_path):
55 def get(self, path='', name=None):
55 """get renders the notebook template if a name is given, or
56 """get renders the notebook template if a name is given, or
56 redirects to the '/files/' handler if the name is not given."""
57 redirects to the '/files/' handler if the name is not given."""
57 nbm = self.notebook_manager
58 nbm = self.notebook_manager
58 name, path = nbm.named_notebook_path(notebook_path)
59 if name is None:
59 if name is not None:
60 url = url_path_join(self.base_project_url, 'files', path)
60 # a .ipynb filename was given
61 if not nbm.notebook_exists(name, path):
62 raise web.HTTPError(404, u'Notebook does not exist: %s' % name)
63 name = nbm.url_encode(name)
64 path = nbm.url_encode(path)
65 self.write(self.render_template('notebook.html',
66 project=self.project_dir,
67 notebook_path=path,
68 notebook_name=name,
69 kill_kernel=False,
70 mathjax_url=self.mathjax_url,
71 )
72 )
73 else:
74 url = "/files/" + notebook_path
75 self.redirect(url)
61 self.redirect(url)
62 return
76
63
64 # a .ipynb filename was given
65 if not nbm.notebook_exists(name, path):
66 raise web.HTTPError(404, u'Notebook does not exist: %s/%s' % (path, name))
67 name = nbm.url_encode(name)
68 path = nbm.url_encode(path)
69 self.write(self.render_template('notebook.html',
70 project=self.project_dir,
71 notebook_path=path,
72 notebook_name=name,
73 kill_kernel=False,
74 mathjax_url=self.mathjax_url,
75 )
76 )
77
77
78 @web.authenticated
78 @web.authenticated
79 def post(self, notebook_path):
79 def post(self, path='', name=None):
80 """post either creates a new notebook if no json data is
80 """post either creates a new notebook if no json data is
81 sent to the server, or copies the data and returns a
81 sent to the server, or copies the data and returns a
82 copied notebook in the location given by 'notebook_path."""
82 copied notebook in the location given by 'notebook_path."""
@@ -95,10 +95,9 b' class NamedNotebookHandler(IPythonHandler):'
95 #-----------------------------------------------------------------------------
95 #-----------------------------------------------------------------------------
96
96
97
97
98 _notebook_path_regex = r"(?P<notebook_path>.+)"
99
100 default_handlers = [
98 default_handlers = [
101 (r"/notebooks/%s" % _notebook_path_regex, NamedNotebookHandler),
99 (r"/notebooks/?%s" % _notebook_path_regex, NamedNotebookHandler),
102 (r"/notebooks/", NotebookHandler),
100 (r"/notebooks/?%s" % _path_regex, NamedNotebookHandler),
101 (r"/notebooks/?", NotebookHandler),
103 ]
102 ]
104
103
@@ -734,10 +734,9 b' class NotebookApp(BaseIPythonApplication):'
734 browser = None
734 browser = None
735
735
736 if self.file_to_run:
736 if self.file_to_run:
737 name, _ = os.path.splitext(os.path.basename(self.file_to_run))
737 url = url_path_join('notebooks', self.entry_path, self.file_to_run)
738 url = 'notebooks/' + self.entry_path + name + _
739 else:
738 else:
740 url = 'tree/' + self.entry_path
739 url = url_path_join('tree', self.entry_path)
741 if browser:
740 if browser:
742 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
741 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
743 self.port, self.base_project_url, url), new=2)
742 self.port, self.base_project_url, url), new=2)
@@ -73,14 +73,14 b' class FileNotebookManager(NotebookManager):'
73 except:
73 except:
74 raise TraitError("Couldn't create checkpoint dir %r" % new)
74 raise TraitError("Couldn't create checkpoint dir %r" % new)
75
75
76 def get_notebook_names(self, path='/'):
76 def get_notebook_names(self, path=''):
77 """List all notebook names in the notebook dir and path."""
77 """List all notebook names in the notebook dir and path."""
78 names = glob.glob(self.get_os_path('*'+self.filename_ext, path))
78 names = glob.glob(self.get_os_path('*'+self.filename_ext, path))
79 names = [os.path.basename(name)
79 names = [os.path.basename(name)
80 for name in names]
80 for name in names]
81 return names
81 return names
82
82
83 def increment_filename(self, basename, path='/'):
83 def increment_filename(self, basename, path=''):
84 """Return a non-used filename of the form basename<int>."""
84 """Return a non-used filename of the form basename<int>."""
85 i = 0
85 i = 0
86 while True:
86 while True:
@@ -97,7 +97,7 b' class FileNotebookManager(NotebookManager):'
97 if os.path.exists(path) is False:
97 if os.path.exists(path) is False:
98 raise web.HTTPError(404, "No file or directory found.")
98 raise web.HTTPError(404, "No file or directory found.")
99
99
100 def notebook_exists(self, name, path='/'):
100 def notebook_exists(self, name, path=''):
101 """Returns a True if the notebook exists. Else, returns False.
101 """Returns a True if the notebook exists. Else, returns False.
102
102
103 Parameters
103 Parameters
@@ -111,8 +111,8 b' class FileNotebookManager(NotebookManager):'
111 -------
111 -------
112 bool
112 bool
113 """
113 """
114 path = self.get_os_path(name, path='/')
114 nbpath = self.get_os_path(name, path=path)
115 return os.path.isfile(path)
115 return os.path.isfile(nbpath)
116
116
117 def list_notebooks(self, path):
117 def list_notebooks(self, path):
118 """Returns a list of dictionaries that are the standard model
118 """Returns a list of dictionaries that are the standard model
@@ -137,7 +137,7 b' class FileNotebookManager(NotebookManager):'
137 notebooks = sorted(notebooks, key=lambda item: item['name'])
137 notebooks = sorted(notebooks, key=lambda item: item['name'])
138 return notebooks
138 return notebooks
139
139
140 def get_notebook_model(self, name, path='/', content=True):
140 def get_notebook_model(self, name, path='', content=True):
141 """ Takes a path and name for a notebook and returns it's model
141 """ Takes a path and name for a notebook and returns it's model
142
142
143 Parameters
143 Parameters
@@ -173,13 +173,13 b' class FileNotebookManager(NotebookManager):'
173 model['content'] = nb
173 model['content'] = nb
174 return model
174 return model
175
175
176 def save_notebook_model(self, model, name, path='/'):
176 def save_notebook_model(self, model, name, path=''):
177 """Save the notebook model and return the model with no content."""
177 """Save the notebook model and return the model with no content."""
178
178
179 if 'content' not in model:
179 if 'content' not in model:
180 raise web.HTTPError(400, u'No notebook JSON data provided')
180 raise web.HTTPError(400, u'No notebook JSON data provided')
181
181
182 new_path = model.get('path', path)
182 new_path = model.get('path', path).strip('/')
183 new_name = model.get('name', name)
183 new_name = model.get('name', name)
184
184
185 if path != new_path or name != new_name:
185 if path != new_path or name != new_name:
@@ -20,10 +20,10 b' import json'
20
20
21 from tornado import web
21 from tornado import web
22
22
23 from ...utils import url_path_join
23 from IPython.html.utils import url_path_join
24 from IPython.utils.jsonutil import date_default
24 from IPython.utils.jsonutil import date_default
25
25
26 from ...base.handlers import IPythonHandler, json_errors
26 from IPython.html.base.handlers import IPythonHandler, json_errors
27
27
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29 # Notebook web service handlers
29 # Notebook web service handlers
@@ -34,31 +34,32 b' class NotebookHandler(IPythonHandler):'
34
34
35 SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE')
35 SUPPORTED_METHODS = (u'GET', u'PUT', u'PATCH', u'POST', u'DELETE')
36
36
37 def notebook_location(self, name, path):
37 def notebook_location(self, name, path=''):
38 """Return the full URL location of a notebook based.
38 """Return the full URL location of a notebook based.
39
39
40 Parameters
40 Parameters
41 ----------
41 ----------
42 name : unicode
42 name : unicode
43 The name of the notebook like "foo.ipynb".
43 The base name of the notebook, such as "foo.ipynb".
44 path : unicode
44 path : unicode
45 The URL path of the notebook.
45 The URL path of the notebook.
46 """
46 """
47 return url_path_join(self.base_project_url, u'/api/notebooks', path, name)
47 return url_path_join(self.base_project_url, 'api', 'notebooks', path, name)
48
48
49 @web.authenticated
49 @web.authenticated
50 @json_errors
50 @json_errors
51 def get(self, notebook_path):
51 def get(self, path='', name=None):
52 """get checks if a notebook is not named, an returns a list of notebooks
52 """
53 GET with path and no notebook lists notebooks in a directory
54 GET with path and notebook name
55
56 GET get checks if a notebook is not named, an returns a list of notebooks
53 in the notebook path given. If a name is given, return
57 in the notebook path given. If a name is given, return
54 the notebook representation"""
58 the notebook representation"""
55 nbm = self.notebook_manager
59 nbm = self.notebook_manager
56 # path will have leading and trailing slashes, such as '/foo/bar/'
57 name, path = nbm.named_notebook_path(notebook_path)
58
59 # Check to see if a notebook name was given
60 # Check to see if a notebook name was given
60 if name is None:
61 if name is None:
61 # List notebooks in 'notebook_path'
62 # List notebooks in 'path'
62 notebooks = nbm.list_notebooks(path)
63 notebooks = nbm.list_notebooks(path)
63 self.finish(json.dumps(notebooks, default=date_default))
64 self.finish(json.dumps(notebooks, default=date_default))
64 else:
65 else:
@@ -68,13 +69,11 b' class NotebookHandler(IPythonHandler):'
68 self.finish(json.dumps(model, default=date_default))
69 self.finish(json.dumps(model, default=date_default))
69
70
70 @web.authenticated
71 @web.authenticated
71 # @json_errors
72 @json_errors
72 def patch(self, notebook_path):
73 def patch(self, path='', name=None):
73 """patch is currently used strictly for notebook renaming.
74 """patch is currently used strictly for notebook renaming.
74 Changes the notebook name to the name given in data."""
75 Changes the notebook name to the name given in data."""
75 nbm = self.notebook_manager
76 nbm = self.notebook_manager
76 # path will have leading and trailing slashes, such as '/foo/bar/'
77 name, path = nbm.named_notebook_path(notebook_path)
78 if name is None:
77 if name is None:
79 raise web.HTTPError(400, u'Notebook name missing')
78 raise web.HTTPError(400, u'Notebook name missing')
80 model = self.get_json_body()
79 model = self.get_json_body()
@@ -90,11 +89,9 b' class NotebookHandler(IPythonHandler):'
90
89
91 @web.authenticated
90 @web.authenticated
92 @json_errors
91 @json_errors
93 def post(self, notebook_path):
92 def post(self, path='', name=None):
94 """Create a new notebook in the location given by 'notebook_path'."""
93 """Create a new notebook in the location given by 'notebook_path'."""
95 nbm = self.notebook_manager
94 nbm = self.notebook_manager
96 # path will have leading and trailing slashes, such as '/foo/bar/'
97 name, path = nbm.named_notebook_path(notebook_path)
98 model = self.get_json_body()
95 model = self.get_json_body()
99 if name is not None:
96 if name is not None:
100 raise web.HTTPError(400, 'No name can be provided when POSTing a new notebook.')
97 raise web.HTTPError(400, 'No name can be provided when POSTing a new notebook.')
@@ -108,11 +105,9 b' class NotebookHandler(IPythonHandler):'
108
105
109 @web.authenticated
106 @web.authenticated
110 @json_errors
107 @json_errors
111 def put(self, notebook_path):
108 def put(self, path='', name=None):
112 """saves the notebook in the location given by 'notebook_path'."""
109 """saves the notebook in the location given by 'notebook_path'."""
113 nbm = self.notebook_manager
110 nbm = self.notebook_manager
114 # path will have leading and trailing slashes, such as '/foo/bar/'
115 name, path = nbm.named_notebook_path(notebook_path)
116 model = self.get_json_body()
111 model = self.get_json_body()
117 if model is None:
112 if model is None:
118 raise web.HTTPError(400, u'JSON body missing')
113 raise web.HTTPError(400, u'JSON body missing')
@@ -121,11 +116,9 b' class NotebookHandler(IPythonHandler):'
121
116
122 @web.authenticated
117 @web.authenticated
123 @json_errors
118 @json_errors
124 def delete(self, notebook_path):
119 def delete(self, path='', name=None):
125 """delete the notebook in the given notebook path"""
120 """delete the notebook in the given notebook path"""
126 nbm = self.notebook_manager
121 nbm = self.notebook_manager
127 # path will have leading and trailing slashes, such as '/foo/bar/'
128 name, path = nbm.named_notebook_path(notebook_path)
129 nbm.delete_notebook_model(name, path)
122 nbm.delete_notebook_model(name, path)
130 self.set_status(204)
123 self.set_status(204)
131 self.finish()
124 self.finish()
@@ -137,26 +130,22 b' class NotebookCheckpointsHandler(IPythonHandler):'
137
130
138 @web.authenticated
131 @web.authenticated
139 @json_errors
132 @json_errors
140 def get(self, notebook_path):
133 def get(self, path='', name=None):
141 """get lists checkpoints for a notebook"""
134 """get lists checkpoints for a notebook"""
142 nbm = self.notebook_manager
135 nbm = self.notebook_manager
143 # path will have leading and trailing slashes, such as '/foo/bar/'
144 name, path = nbm.named_notebook_path(notebook_path)
145 checkpoints = nbm.list_checkpoints(name, path)
136 checkpoints = nbm.list_checkpoints(name, path)
146 data = json.dumps(checkpoints, default=date_default)
137 data = json.dumps(checkpoints, default=date_default)
147 self.finish(data)
138 self.finish(data)
148
139
149 @web.authenticated
140 @web.authenticated
150 @json_errors
141 @json_errors
151 def post(self, notebook_path):
142 def post(self, path='', name=None):
152 """post creates a new checkpoint"""
143 """post creates a new checkpoint"""
153 nbm = self.notebook_manager
144 nbm = self.notebook_manager
154 name, path = nbm.named_notebook_path(notebook_path)
155 # path will have leading and trailing slashes, such as '/foo/bar/'
156 checkpoint = nbm.create_checkpoint(name, path)
145 checkpoint = nbm.create_checkpoint(name, path)
157 data = json.dumps(checkpoint, default=date_default)
146 data = json.dumps(checkpoint, default=date_default)
158 location = url_path_join(self.base_project_url, u'/api/notebooks',
147 location = url_path_join(self.base_project_url, u'/api/notebooks',
159 path, name, '/checkpoints', checkpoint[u'checkpoint_id'])
148 path, name, 'checkpoints', checkpoint[u'checkpoint_id'])
160 self.set_header(u'Location', location)
149 self.set_header(u'Location', location)
161 self.finish(data)
150 self.finish(data)
162
151
@@ -167,22 +156,18 b' class ModifyNotebookCheckpointsHandler(IPythonHandler):'
167
156
168 @web.authenticated
157 @web.authenticated
169 @json_errors
158 @json_errors
170 def post(self, notebook_path, checkpoint_id):
159 def post(self, path, name, checkpoint_id):
171 """post restores a notebook from a checkpoint"""
160 """post restores a notebook from a checkpoint"""
172 nbm = self.notebook_manager
161 nbm = self.notebook_manager
173 # path will have leading and trailing slashes, such as '/foo/bar/'
174 name, path = nbm.named_notebook_path(notebook_path)
175 nbm.restore_checkpoint(checkpoint_id, name, path)
162 nbm.restore_checkpoint(checkpoint_id, name, path)
176 self.set_status(204)
163 self.set_status(204)
177 self.finish()
164 self.finish()
178
165
179 @web.authenticated
166 @web.authenticated
180 @json_errors
167 @json_errors
181 def delete(self, notebook_path, checkpoint_id):
168 def delete(self, path, name, checkpoint_id):
182 """delete clears a checkpoint for a given notebook"""
169 """delete clears a checkpoint for a given notebook"""
183 nbm = self.notebook_manager
170 nbm = self.notebook_manager
184 # path will have leading and trailing slashes, such as '/foo/bar/'
185 name, path = nbm.named_notebook_path(notebook_path)
186 nbm.delete_checkpoint(checkpoint_id, name, path)
171 nbm.delete_checkpoint(checkpoint_id, name, path)
187 self.set_status(204)
172 self.set_status(204)
188 self.finish()
173 self.finish()
@@ -192,14 +177,17 b' class ModifyNotebookCheckpointsHandler(IPythonHandler):'
192 #-----------------------------------------------------------------------------
177 #-----------------------------------------------------------------------------
193
178
194
179
195 _notebook_path_regex = r"(?P<notebook_path>.*)"
180 _path_regex = r"(?P<path>.*)"
196 _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
181 _checkpoint_id_regex = r"(?P<checkpoint_id>[\w-]+)"
182 _notebook_name_regex = r"(?P<name>[^/]+\.ipynb)"
183 _notebook_path_regex = "%s/%s" % (_path_regex, _notebook_name_regex)
197
184
198 default_handlers = [
185 default_handlers = [
199 (r"/api/notebooks/%s/checkpoints" % _notebook_path_regex, NotebookCheckpointsHandler),
186 (r"/api/notebooks/?%s/checkpoints" % _notebook_path_regex, NotebookCheckpointsHandler),
200 (r"/api/notebooks/%s/checkpoints/%s" % (_notebook_path_regex, _checkpoint_id_regex),
187 (r"/api/notebooks/?%s/checkpoints/%s" % (_notebook_path_regex, _checkpoint_id_regex),
201 ModifyNotebookCheckpointsHandler),
188 ModifyNotebookCheckpointsHandler),
202 (r"/api/notebooks%s" % _notebook_path_regex, NotebookHandler),
189 (r"/api/notebooks/?%s" % _notebook_path_regex, NotebookHandler),
190 (r"/api/notebooks/?%s/?" % _path_regex, NotebookHandler),
203 ]
191 ]
204
192
205
193
@@ -45,45 +45,33 b' class NotebookManager(LoggingConfigurable):'
45 """)
45 """)
46
46
47 filename_ext = Unicode(u'.ipynb')
47 filename_ext = Unicode(u'.ipynb')
48
48
49 def named_notebook_path(self, notebook_path):
49 def path_exists(self, path):
50 """Given notebook_path (*always* a URL path to notebook), returns a
50 """Does the API-style path (directory) actually exist?
51 (name, path) tuple, where name is a .ipynb file, and path is the
51
52 URL path that describes the file system path for the file.
52 Override this method for non-filesystem-based notebooks.
53 It *always* starts *and* ends with a '/' character.
53
54
55 Parameters
54 Parameters
56 ----------
55 ----------
57 notebook_path : string
56 path : string
58 A path that may be a .ipynb name or a directory
57 The
59
58
60 Returns
59 Returns
61 -------
60 -------
62 name : string or None
61 exists : bool
63 the filename of the notebook, or None if not a .ipynb extension
62 Whether the path does indeed exist.
64 path : string
65 the path to the directory which contains the notebook
66 """
63 """
67 names = notebook_path.split('/')
64 os_path = self.get_os_path(name, path)
68 names = [n for n in names if n != ''] # remove duplicate splits
65 return os.path.exists(os_path)
69
70 names = [''] + names
71
72 if names and names[-1].endswith(".ipynb"):
73 name = names[-1]
74 path = "/".join(names[:-1]) + '/'
75 else:
76 name = None
77 path = "/".join(names) + '/'
78 return name, path
79
66
80 def get_os_path(self, fname=None, path='/'):
67
68 def get_os_path(self, name=None, path=''):
81 """Given a notebook name and a URL path, return its file system
69 """Given a notebook name and a URL path, return its file system
82 path.
70 path.
83
71
84 Parameters
72 Parameters
85 ----------
73 ----------
86 fname : string
74 name : string
87 The name of a notebook file with the .ipynb extension
75 The name of a notebook file with the .ipynb extension
88 path : string
76 path : string
89 The relative URL path (with '/' as separator) to the named
77 The relative URL path (with '/' as separator) to the named
@@ -96,10 +84,10 b' class NotebookManager(LoggingConfigurable):'
96 server started), the relative path, and the filename with the
84 server started), the relative path, and the filename with the
97 current operating system's url.
85 current operating system's url.
98 """
86 """
99 parts = path.split('/')
87 parts = path.strip('/').split('/')
100 parts = [p for p in parts if p != ''] # remove duplicate splits
88 parts = [p for p in parts if p != ''] # remove duplicate splits
101 if fname is not None:
89 if name is not None:
102 parts += [fname]
90 parts.append(name)
103 path = os.path.join(self.notebook_dir, *parts)
91 path = os.path.join(self.notebook_dir, *parts)
104 return path
92 return path
105
93
@@ -132,7 +120,7 b' class NotebookManager(LoggingConfigurable):'
132
120
133 # Main notebook API
121 # Main notebook API
134
122
135 def increment_filename(self, basename, path='/'):
123 def increment_filename(self, basename, path=''):
136 """Increment a notebook filename without the .ipynb to make it unique.
124 """Increment a notebook filename without the .ipynb to make it unique.
137
125
138 Parameters
126 Parameters
@@ -157,15 +145,15 b' class NotebookManager(LoggingConfigurable):'
157 """
145 """
158 raise NotImplementedError('must be implemented in a subclass')
146 raise NotImplementedError('must be implemented in a subclass')
159
147
160 def get_notebook_model(self, name, path='/', content=True):
148 def get_notebook_model(self, name, path='', content=True):
161 """Get the notebook model with or without content."""
149 """Get the notebook model with or without content."""
162 raise NotImplementedError('must be implemented in a subclass')
150 raise NotImplementedError('must be implemented in a subclass')
163
151
164 def save_notebook_model(self, model, name, path='/'):
152 def save_notebook_model(self, model, name, path=''):
165 """Save the notebook model and return the model with no content."""
153 """Save the notebook model and return the model with no content."""
166 raise NotImplementedError('must be implemented in a subclass')
154 raise NotImplementedError('must be implemented in a subclass')
167
155
168 def update_notebook_model(self, model, name, path='/'):
156 def update_notebook_model(self, model, name, path=''):
169 """Update the notebook model and return the model with no content."""
157 """Update the notebook model and return the model with no content."""
170 raise NotImplementedError('must be implemented in a subclass')
158 raise NotImplementedError('must be implemented in a subclass')
171
159
@@ -173,7 +161,7 b' class NotebookManager(LoggingConfigurable):'
173 """Delete notebook by name and path."""
161 """Delete notebook by name and path."""
174 raise NotImplementedError('must be implemented in a subclass')
162 raise NotImplementedError('must be implemented in a subclass')
175
163
176 def create_notebook_model(self, model=None, path='/'):
164 def create_notebook_model(self, model=None, path=''):
177 """Create a new untitled notebook and return its model with no content."""
165 """Create a new untitled notebook and return its model with no content."""
178 name = self.increment_filename('Untitled', path)
166 name = self.increment_filename('Untitled', path)
179 if model is None:
167 if model is None:
@@ -67,48 +67,6 b' class TestNotebookManager(TestCase):'
67 except OSError:
67 except OSError:
68 print "Directory already exists."
68 print "Directory already exists."
69
69
70 def test_named_notebook_path(self):
71 """the `named_notebook_path` method takes a URL path to
72 a notebook and returns a url path split into nb and path"""
73 nm = NotebookManager()
74
75 # doesn't end with ipynb, should just be path
76 name, path = nm.named_notebook_path('hello')
77 self.assertEqual(name, None)
78 self.assertEqual(path, '/hello/')
79
80 # Root path returns just the root slash
81 name, path = nm.named_notebook_path('/')
82 self.assertEqual(name, None)
83 self.assertEqual(path, '/')
84
85 # get notebook, and return the path as '/'
86 name, path = nm.named_notebook_path('notebook.ipynb')
87 self.assertEqual(name, 'notebook.ipynb')
88 self.assertEqual(path, '/')
89
90 # Test a notebook name with leading slash returns
91 # the same as above
92 name, path = nm.named_notebook_path('/notebook.ipynb')
93 self.assertEqual(name, 'notebook.ipynb')
94 self.assertEqual(path, '/')
95
96 # Multiple path arguments splits the notebook name
97 # and returns path with leading and trailing '/'
98 name, path = nm.named_notebook_path('/this/is/a/path/notebook.ipynb')
99 self.assertEqual(name, 'notebook.ipynb')
100 self.assertEqual(path, '/this/is/a/path/')
101
102 # path without leading slash is returned with leading slash
103 name, path = nm.named_notebook_path('path/without/leading/slash/notebook.ipynb')
104 self.assertEqual(name, 'notebook.ipynb')
105 self.assertEqual(path, '/path/without/leading/slash/')
106
107 # path with spaces and no leading or trailing '/'
108 name, path = nm.named_notebook_path('foo / bar% path& to# @/ notebook name.ipynb')
109 self.assertEqual(name, ' notebook name.ipynb')
110 self.assertEqual(path, '/foo / bar% path& to# @/')
111
112 def test_url_encode(self):
70 def test_url_encode(self):
113 nm = NotebookManager()
71 nm = NotebookManager()
114
72
@@ -20,6 +20,7 b' import os'
20 from tornado import web
20 from tornado import web
21 from ..base.handlers import IPythonHandler
21 from ..base.handlers import IPythonHandler
22 from ..utils import url_path_join, path2url, url2path
22 from ..utils import url_path_join, path2url, url2path
23 from ..services.notebooks.handlers import _notebook_path_regex, _path_regex
23
24
24 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
25 # Handlers
26 # Handlers
@@ -30,23 +31,19 b' class TreeHandler(IPythonHandler):'
30 """Render the tree view, listing notebooks, clusters, etc."""
31 """Render the tree view, listing notebooks, clusters, etc."""
31
32
32 @web.authenticated
33 @web.authenticated
33 def get(self, notebook_path=""):
34 def get(self, path='', name=None):
34 nbm = self.notebook_manager
35 nbm = self.notebook_manager
35 name, path = nbm.named_notebook_path(notebook_path)
36 if name is not None:
36 if name is not None:
37 # is a notebook, redirect to notebook handler
37 # is a notebook, redirect to notebook handler
38 url = url_path_join(self.base_project_url, 'notebooks', path, name)
38 url = url_path_join(self.base_project_url, 'notebooks', path, name)
39 self.redirect(url)
39 self.redirect(url)
40 else:
40 else:
41 location = nbm.get_os_path(path=path)
41 if not nbm.path_exists(path=path):
42
43 if not os.path.exists(location):
44 # no such directory, 404
42 # no such directory, 404
45 raise web.HTTPError(404)
43 raise web.HTTPError(404)
46
47 self.write(self.render_template('tree.html',
44 self.write(self.render_template('tree.html',
48 project=self.project_dir,
45 project=self.project_dir,
49 tree_url_path=path2url(location),
46 tree_url_path=path,
50 notebook_path=path,
47 notebook_path=path,
51 ))
48 ))
52
49
@@ -55,8 +52,8 b' class TreeRedirectHandler(IPythonHandler):'
55 """Redirect a request to the corresponding tree URL"""
52 """Redirect a request to the corresponding tree URL"""
56
53
57 @web.authenticated
54 @web.authenticated
58 def get(self, notebook_path=''):
55 def get(self, path=''):
59 url = url_path_join(self.base_project_url, 'tree', notebook_path)
56 url = url_path_join(self.base_project_url, 'tree', path).rstrip('/')
60 self.log.debug("Redirecting %s to %s", self.request.uri, url)
57 self.log.debug("Redirecting %s to %s", self.request.uri, url)
61 self.redirect(url)
58 self.redirect(url)
62
59
@@ -66,11 +63,10 b' class TreeRedirectHandler(IPythonHandler):'
66 #-----------------------------------------------------------------------------
63 #-----------------------------------------------------------------------------
67
64
68
65
69 _notebook_path_regex = r"(?P<notebook_path>.+)"
70
71 default_handlers = [
66 default_handlers = [
72 (r"/tree/%s/" % _notebook_path_regex, TreeRedirectHandler),
67 (r"/tree/(.*)/", TreeRedirectHandler),
73 (r"/tree/%s" % _notebook_path_regex, TreeHandler),
68 (r"/tree/?%s" % _notebook_path_regex, TreeHandler),
69 (r"/tree/?%s" % _path_regex, TreeHandler),
74 (r"/tree/", TreeRedirectHandler),
70 (r"/tree/", TreeRedirectHandler),
75 (r"/tree", TreeHandler),
71 (r"/tree", TreeHandler),
76 (r"/", TreeRedirectHandler),
72 (r"/", TreeRedirectHandler),
General Comments 0
You need to be logged in to leave comments. Login now