##// END OF EJS Templates
added folder creation ability using '/-new'
Zachary Sailer -
Show More
@@ -1,242 +1,248 b''
1 """A base class notebook manager.
1 """A base class notebook manager.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
9 # Copyright (C) 2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 import os
19 import os
20 import uuid
20 import uuid
21
21
22 from tornado import web
22 from tornado import web
23
23
24 from IPython.config.configurable import LoggingConfigurable
24 from IPython.config.configurable import LoggingConfigurable
25 from IPython.nbformat import current
25 from IPython.nbformat import current
26 from IPython.utils.traitlets import List, Dict, Unicode, TraitError
26 from IPython.utils.traitlets import List, Dict, Unicode, TraitError
27
27
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29 # Classes
29 # Classes
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31
31
32 class NotebookManager(LoggingConfigurable):
32 class NotebookManager(LoggingConfigurable):
33
33
34 # Todo:
34 # Todo:
35 # The notebook_dir attribute is used to mean a couple of different things:
35 # The notebook_dir attribute is used to mean a couple of different things:
36 # 1. Where the notebooks are stored if FileNotebookManager is used.
36 # 1. Where the notebooks are stored if FileNotebookManager is used.
37 # 2. The cwd of the kernel for a project.
37 # 2. The cwd of the kernel for a project.
38 # Right now we use this attribute in a number of different places and
38 # Right now we use this attribute in a number of different places and
39 # we are going to have to disentangle all of this.
39 # we are going to have to disentangle all of this.
40 notebook_dir = Unicode(os.getcwdu(), config=True, help="""
40 notebook_dir = Unicode(os.getcwdu(), config=True, help="""
41 The directory to use for notebooks.
41 The directory to use for notebooks.
42 """)
42 """)
43
43
44 def named_notebook_path(self, notebook_path):
44 def named_notebook_path(self, notebook_path):
45
45
46 l = len(notebook_path)
46 l = len(notebook_path)
47 names = notebook_path.split('/')
47 names = notebook_path.split('/')
48 if len(names) > 1:
48 if len(names) > 1:
49 name = names[len(names)-1]
49 name = names[len(names)-1]
50 if name[(len(name)-6):(len(name))] == ".ipynb":
50 if name[(len(name)-6):(len(name))] == ".ipynb":
51 name = name
51 name = name
52 path = notebook_path[0:l-len(name)-1]+'/'
52 path = notebook_path[0:l-len(name)-1]+'/'
53 else:
53 else:
54 name = None
54 name = None
55 path = notebook_path+'/'
55 path = notebook_path+'/'
56 else:
56 else:
57 name = names[0]
57 name = names[0]
58 if name[(len(name)-6):(len(name))] == ".ipynb":
58 if name[(len(name)-6):(len(name))] == ".ipynb":
59 name = name
59 name = name
60 path = None
60 path = None
61 else:
61 else:
62 name = None
62 name = None
63 path = notebook_path+'/'
63 path = notebook_path+'/'
64 return name, path
64 return name, path
65
65
66 def _notebook_dir_changed(self, new):
66 def _notebook_dir_changed(self, new):
67 """do a bit of validation of the notebook dir"""
67 """do a bit of validation of the notebook dir"""
68 if not os.path.isabs(new):
68 if not os.path.isabs(new):
69 # If we receive a non-absolute path, make it absolute.
69 # If we receive a non-absolute path, make it absolute.
70 abs_new = os.path.abspath(new)
70 abs_new = os.path.abspath(new)
71 #self.notebook_dir = os.path.dirname(abs_new)
71 #self.notebook_dir = os.path.dirname(abs_new)
72 return
72 return
73 if os.path.exists(new) and not os.path.isdir(new):
73 if os.path.exists(new) and not os.path.isdir(new):
74 raise TraitError("notebook dir %r is not a directory" % new)
74 raise TraitError("notebook dir %r is not a directory" % new)
75 if not os.path.exists(new):
75 if not os.path.exists(new):
76 self.log.info("Creating notebook dir %s", new)
76 self.log.info("Creating notebook dir %s", new)
77 try:
77 try:
78 os.mkdir(new)
78 os.mkdir(new)
79 except:
79 except:
80 raise TraitError("Couldn't create notebook dir %r" % new)
80 raise TraitError("Couldn't create notebook dir %r" % new)
81
81
82 allowed_formats = List([u'json',u'py'])
82 allowed_formats = List([u'json',u'py'])
83
83
84 def add_new_folder(self, path=None):
85 new_path = os.path.join(self.notebook_dir, path)
86 if not os.path.exists(new_path):
87 os.makedirs(new_path)
88 else:
89 raise web.HTTPError(409, u'Directory already exists or creation permission not allowed.')
84
90
85 def load_notebook_names(self, path):
91 def load_notebook_names(self, path):
86 """Load the notebook names into memory.
92 """Load the notebook names into memory.
87
93
88 This should be called once immediately after the notebook manager
94 This should be called once immediately after the notebook manager
89 is created to load the existing notebooks into the mapping in
95 is created to load the existing notebooks into the mapping in
90 memory.
96 memory.
91 """
97 """
92 self.list_notebooks(path)
98 self.list_notebooks(path)
93
99
94 def list_notebooks(self):
100 def list_notebooks(self):
95 """List all notebooks.
101 """List all notebooks.
96
102
97 This returns a list of dicts, each of the form::
103 This returns a list of dicts, each of the form::
98
104
99 dict(notebook_id=notebook,name=name)
105 dict(notebook_id=notebook,name=name)
100
106
101 This list of dicts should be sorted by name::
107 This list of dicts should be sorted by name::
102
108
103 data = sorted(data, key=lambda item: item['name'])
109 data = sorted(data, key=lambda item: item['name'])
104 """
110 """
105 raise NotImplementedError('must be implemented in a subclass')
111 raise NotImplementedError('must be implemented in a subclass')
106
112
107
113
108 def notebook_exists(self, notebook_name):
114 def notebook_exists(self, notebook_name):
109 """Does a notebook exist?"""
115 """Does a notebook exist?"""
110 return notebook_name in self.mapping
116 return notebook_name in self.mapping
111
117
112 def notebook_model(self, notebook_name, notebook_path=None):
118 def notebook_model(self, notebook_name, notebook_path=None):
113 """ Creates the standard notebook model """
119 """ Creates the standard notebook model """
114 last_modified, content = self.read_notebook_object(notebook_name, notebook_path)
120 last_modified, content = self.read_notebook_object(notebook_name, notebook_path)
115 model = {"notebook_name": notebook_name,
121 model = {"notebook_name": notebook_name,
116 "notebook_path": notebook_path,
122 "notebook_path": notebook_path,
117 "content": content,
123 "content": content,
118 "last_modified": last_modified.ctime()}
124 "last_modified": last_modified.ctime()}
119 return model
125 return model
120
126
121 def get_notebook(self, notebook_name, notebook_path=None, format=u'json'):
127 def get_notebook(self, notebook_name, notebook_path=None, format=u'json'):
122 """Get the representation of a notebook in format by notebook_name."""
128 """Get the representation of a notebook in format by notebook_name."""
123 format = unicode(format)
129 format = unicode(format)
124 if format not in self.allowed_formats:
130 if format not in self.allowed_formats:
125 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
131 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
126 kwargs = {}
132 kwargs = {}
127 last_mod, nb = self.read_notebook_object(notebook_name, notebook_path)
133 last_mod, nb = self.read_notebook_object(notebook_name, notebook_path)
128 if format == 'json':
134 if format == 'json':
129 # don't split lines for sending over the wire, because it
135 # don't split lines for sending over the wire, because it
130 # should match the Python in-memory format.
136 # should match the Python in-memory format.
131 kwargs['split_lines'] = False
137 kwargs['split_lines'] = False
132 representation = current.writes(nb, format, **kwargs)
138 representation = current.writes(nb, format, **kwargs)
133 name = nb.metadata.get('name', 'notebook')
139 name = nb.metadata.get('name', 'notebook')
134 return last_mod, representation, name
140 return last_mod, representation, name
135
141
136 def read_notebook_object(self, notebook_name, notebook_path):
142 def read_notebook_object(self, notebook_name, notebook_path):
137 """Get the object representation of a notebook by notebook_id."""
143 """Get the object representation of a notebook by notebook_id."""
138 raise NotImplementedError('must be implemented in a subclass')
144 raise NotImplementedError('must be implemented in a subclass')
139
145
140 def save_new_notebook(self, data, notebook_path = None, name=None, format=u'json'):
146 def save_new_notebook(self, data, notebook_path = None, name=None, format=u'json'):
141 """Save a new notebook and return its notebook_id.
147 """Save a new notebook and return its notebook_id.
142
148
143 If a name is passed in, it overrides any values in the notebook data
149 If a name is passed in, it overrides any values in the notebook data
144 and the value in the data is updated to use that value.
150 and the value in the data is updated to use that value.
145 """
151 """
146 if format not in self.allowed_formats:
152 if format not in self.allowed_formats:
147 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
153 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
148
154
149 try:
155 try:
150 nb = current.reads(data.decode('utf-8'), format)
156 nb = current.reads(data.decode('utf-8'), format)
151 except:
157 except:
152 raise web.HTTPError(400, u'Invalid JSON data')
158 raise web.HTTPError(400, u'Invalid JSON data')
153
159
154 if name is None:
160 if name is None:
155 try:
161 try:
156 name = nb.metadata.name
162 name = nb.metadata.name
157 except AttributeError:
163 except AttributeError:
158 raise web.HTTPError(400, u'Missing notebook name')
164 raise web.HTTPError(400, u'Missing notebook name')
159 nb.metadata.name = name
165 nb.metadata.name = name
160
166
161 notebook_name = self.write_notebook_object(nb, notebook_path=notebook_path)
167 notebook_name = self.write_notebook_object(nb, notebook_path=notebook_path)
162 return notebook_name
168 return notebook_name
163
169
164 def save_notebook(self, data, notebook_path=None, name=None, new_name=None, format=u'json'):
170 def save_notebook(self, data, notebook_path=None, name=None, new_name=None, format=u'json'):
165 """Save an existing notebook by notebook_name."""
171 """Save an existing notebook by notebook_name."""
166 if format not in self.allowed_formats:
172 if format not in self.allowed_formats:
167 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
173 raise web.HTTPError(415, u'Invalid notebook format: %s' % format)
168
174
169 try:
175 try:
170 nb = current.reads(data.decode('utf-8'), format)
176 nb = current.reads(data.decode('utf-8'), format)
171 except:
177 except:
172 raise web.HTTPError(400, u'Invalid JSON data')
178 raise web.HTTPError(400, u'Invalid JSON data')
173
179
174 if name is not None:
180 if name is not None:
175 nb.metadata.name = name
181 nb.metadata.name = name
176 self.write_notebook_object(nb, name, notebook_path, new_name)
182 self.write_notebook_object(nb, name, notebook_path, new_name)
177
183
178 def write_notebook_object(self, nb, notebook_name=None, notebook_path=None, new_name=None):
184 def write_notebook_object(self, nb, notebook_name=None, notebook_path=None, new_name=None):
179 """Write a notebook object and return its notebook_name.
185 """Write a notebook object and return its notebook_name.
180
186
181 If notebook_name is None, this method should create a new notebook_name.
187 If notebook_name is None, this method should create a new notebook_name.
182 If notebook_name is not None, this method should check to make sure it
188 If notebook_name is not None, this method should check to make sure it
183 exists and is valid.
189 exists and is valid.
184 """
190 """
185 raise NotImplementedError('must be implemented in a subclass')
191 raise NotImplementedError('must be implemented in a subclass')
186
192
187 def delete_notebook(self, notebook_name, notebook_path):
193 def delete_notebook(self, notebook_name, notebook_path):
188 """Delete notebook by notebook_id."""
194 """Delete notebook by notebook_id."""
189 raise NotImplementedError('must be implemented in a subclass')
195 raise NotImplementedError('must be implemented in a subclass')
190
196
191 def increment_filename(self, name):
197 def increment_filename(self, name):
192 """Increment a filename to make it unique.
198 """Increment a filename to make it unique.
193
199
194 This exists for notebook stores that must have unique names. When a notebook
200 This exists for notebook stores that must have unique names. When a notebook
195 is created or copied this method constructs a unique filename, typically
201 is created or copied this method constructs a unique filename, typically
196 by appending an integer to the name.
202 by appending an integer to the name.
197 """
203 """
198 return name
204 return name
199
205
200 def new_notebook(self, notebook_path=None):
206 def new_notebook(self, notebook_path=None):
201 """Create a new notebook and return its notebook_id."""
207 """Create a new notebook and return its notebook_id."""
202 name = self.increment_filename('Untitled', notebook_path)
208 name = self.increment_filename('Untitled', notebook_path)
203 metadata = current.new_metadata(name=name)
209 metadata = current.new_metadata(name=name)
204 nb = current.new_notebook(metadata=metadata)
210 nb = current.new_notebook(metadata=metadata)
205 notebook_name = self.write_notebook_object(nb, notebook_path=notebook_path)
211 notebook_name = self.write_notebook_object(nb, notebook_path=notebook_path)
206 return notebook_name
212 return notebook_name
207
213
208 def copy_notebook(self, name, path):
214 def copy_notebook(self, name, path):
209 """Copy an existing notebook and return its notebook_id."""
215 """Copy an existing notebook and return its notebook_id."""
210 last_mod, nb = self.read_notebook_object(name, path)
216 last_mod, nb = self.read_notebook_object(name, path)
211 name = nb.metadata.name + '-Copy'
217 name = nb.metadata.name + '-Copy'
212 name = self.increment_filename(name, path)
218 name = self.increment_filename(name, path)
213 nb.metadata.name = name
219 nb.metadata.name = name
214 notebook_name = self.write_notebook_object(nb, notebook_path = path)
220 notebook_name = self.write_notebook_object(nb, notebook_path = path)
215 return notebook_name
221 return notebook_name
216
222
217 # Checkpoint-related
223 # Checkpoint-related
218
224
219 def create_checkpoint(self, notebook_name, notebook_path=None):
225 def create_checkpoint(self, notebook_name, notebook_path=None):
220 """Create a checkpoint of the current state of a notebook
226 """Create a checkpoint of the current state of a notebook
221
227
222 Returns a checkpoint_id for the new checkpoint.
228 Returns a checkpoint_id for the new checkpoint.
223 """
229 """
224 raise NotImplementedError("must be implemented in a subclass")
230 raise NotImplementedError("must be implemented in a subclass")
225
231
226 def list_checkpoints(self, notebook_name, notebook_path=None):
232 def list_checkpoints(self, notebook_name, notebook_path=None):
227 """Return a list of checkpoints for a given notebook"""
233 """Return a list of checkpoints for a given notebook"""
228 return []
234 return []
229
235
230 def restore_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
236 def restore_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
231 """Restore a notebook from one of its checkpoints"""
237 """Restore a notebook from one of its checkpoints"""
232 raise NotImplementedError("must be implemented in a subclass")
238 raise NotImplementedError("must be implemented in a subclass")
233
239
234 def delete_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
240 def delete_checkpoint(self, notebook_name, checkpoint_id, notebook_path=None):
235 """delete a checkpoint for a notebook"""
241 """delete a checkpoint for a notebook"""
236 raise NotImplementedError("must be implemented in a subclass")
242 raise NotImplementedError("must be implemented in a subclass")
237
243
238 def log_info(self):
244 def log_info(self):
239 self.log.info(self.info_string())
245 self.log.info(self.info_string())
240
246
241 def info_string(self):
247 def info_string(self):
242 return "Serving notebooks"
248 return "Serving notebooks"
@@ -1,93 +1,105 b''
1 """Tornado handlers for the tree view.
1 """Tornado handlers for the tree view.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2011 The IPython Development Team
9 # Copyright (C) 2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 from tornado import web
19 from tornado import web
20 from ..base.handlers import IPythonHandler
20 from ..base.handlers import IPythonHandler
21 from urllib import quote, unquote
21 from urllib import quote, unquote
22
22
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 # Handlers
24 # Handlers
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26
26
27
27
28 class ProjectDashboardHandler(IPythonHandler):
28 class ProjectDashboardHandler(IPythonHandler):
29
29
30 @web.authenticated
30 @web.authenticated
31 def get(self):
31 def get(self):
32 self.write(self.render_template('tree.html',
32 self.write(self.render_template('tree.html',
33 project=self.project,
33 project=self.project,
34 project_component=self.project.split('/'),
34 project_component=self.project.split('/'),
35 notebook_path= "''"
35 notebook_path= "''"
36 ))
36 ))
37
37
38
38
39 class ProjectPathDashboardHandler(IPythonHandler):
39 class ProjectPathDashboardHandler(IPythonHandler):
40
40
41 @authenticate_unless_readonly
41 @authenticate_unless_readonly
42 def get(self, notebook_path):
42 def get(self, notebook_path):
43 nbm = self.notebook_manager
43 nbm = self.notebook_manager
44 name, path = nbm.named_notebook_path(notebook_path)
44 name, path = nbm.named_notebook_path(notebook_path)
45 if name != None:
45 if name != None:
46 if path == None:
46 if path == None:
47 self.redirect(self.base_project_url + 'notebooks/' + quote(name))
47 self.redirect(self.base_project_url + 'notebooks/' + quote(name))
48 else:
48 else:
49 self.redirect(self.base_project_url + 'notebooks/' + path + quote(name))
49 self.redirect(self.base_project_url + 'notebooks/' + path + quote(name))
50 else:
50 else:
51 project = self.project + '/' + notebook_path
51 project = self.project + '/' + notebook_path
52 self.write(self.render_template('tree.html',
52 self.write(self.render_template('tree.html',
53 project=project,
53 project=project,
54 project_component=project.split('/'),
54 project_component=project.split('/'),
55 notebook_path=path,
55 notebook_path=path,
56 notebook_name=name))
56 notebook_name=name))
57
57
58
58
59 class TreeRedirectHandler(IPythonHandler):
59 class TreeRedirectHandler(IPythonHandler):
60
60
61 @authenticate_unless_readonly
61 @authenticate_unless_readonly
62 def get(self):
62 def get(self):
63 url = self.base_project_url + 'tree'
63 url = self.base_project_url + 'tree'
64 self.redirect(url)
64 self.redirect(url)
65
65
66 class TreePathRedirectHandler(IPythonHandler):
66 class TreePathRedirectHandler(IPythonHandler):
67
67
68 @authenticate_unless_readonly
68 @authenticate_unless_readonly
69 def get(self, notebook_path):
69 def get(self, notebook_path):
70 url = self.base_project_url + 'tree/'+ notebook_path
70 url = self.base_project_url + 'tree/'+ notebook_path
71 self.redirect(url)
71 self.redirect(url)
72
72
73 class ProjectRedirectHandler(IPythonHandler):
73 class ProjectRedirectHandler(IPythonHandler):
74
74
75 @authenticate_unless_readonly
75 @authenticate_unless_readonly
76 def get(self):
76 def get(self):
77 url = self.base_project_url + 'tree'
77 url = self.base_project_url + 'tree'
78 self.redirect(url)
78 self.redirect(url)
79
79
80 class NewFolderHandler(IPythonHandler):
81
82 @authenticate_unless_readonly
83 def get(self, notebook_path):
84 nbm = self.notebook_manager
85 name, path = nbm.named_notebook_path(notebook_path)
86 nbm.add_new_folder(path)
87 url = self.base_project_url + 'tree/' + notebook_path
88 self.redirect(url)
89
90
80 #-----------------------------------------------------------------------------
91 #-----------------------------------------------------------------------------
81 # URL to handler mappings
92 # URL to handler mappings
82 #-----------------------------------------------------------------------------
93 #-----------------------------------------------------------------------------
83
94
84
95
85 _notebook_path_regex = r"(?P<notebook_path>.+)"
96 _notebook_path_regex = r"(?P<notebook_path>.+)"
86
97
87 default_handlers = [
98 default_handlers = [
99 (r"/tree/%s/-new" %_notebook_path_regex, NewFolderHandler),
88 (r"/tree/%s/" % _notebook_path_regex, TreePathRedirectHandler),
100 (r"/tree/%s/" % _notebook_path_regex, TreePathRedirectHandler),
89 (r"/tree/%s" % _notebook_path_regex, ProjectPathDashboardHandler),
101 (r"/tree/%s" % _notebook_path_regex, ProjectPathDashboardHandler),
90 (r"/tree", ProjectDashboardHandler),
102 (r"/tree", ProjectDashboardHandler),
91 (r"/tree/", TreeRedirectHandler),
103 (r"/tree/", TreeRedirectHandler),
92 (r"/", ProjectRedirectHandler)
104 (r"/", ProjectRedirectHandler)
93 ]
105 ]
General Comments 0
You need to be logged in to leave comments. Login now