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