##// END OF EJS Templates
Renaming user cookie to username to better match usage.
MinRK -
Show More
@@ -1,448 +1,448 b''
1 """Tornado handlers for the notebook.
1 """Tornado handlers for the notebook.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
9 # Copyright (C) 2008-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 logging
19 import logging
20 import Cookie
20 import Cookie
21 import uuid
21 import uuid
22
22
23 from tornado import web
23 from tornado import web
24 from tornado import websocket
24 from tornado import websocket
25
25
26 from zmq.eventloop import ioloop
26 from zmq.eventloop import ioloop
27 from zmq.utils import jsonapi
27 from zmq.utils import jsonapi
28
28
29 from IPython.zmq.session import Session
29 from IPython.zmq.session import Session
30
30
31 try:
31 try:
32 from docutils.core import publish_string
32 from docutils.core import publish_string
33 except ImportError:
33 except ImportError:
34 publish_string = None
34 publish_string = None
35
35
36
36
37
37
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39 # Top-level handlers
39 # Top-level handlers
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41
41
42 class AuthenticatedHandler(web.RequestHandler):
42 class AuthenticatedHandler(web.RequestHandler):
43 """A RequestHandler with an authenticated user."""
43 """A RequestHandler with an authenticated user."""
44
44
45 def get_current_user(self):
45 def get_current_user(self):
46 user_id = self.get_secure_cookie("user")
46 user_id = self.get_secure_cookie("username")
47 # For now the user_id should not return empty, but it could eventually
47 # For now the user_id should not return empty, but it could eventually
48 if user_id == '':
48 if user_id == '':
49 user_id = 'anonymous'
49 user_id = 'anonymous'
50 if user_id is None:
50 if user_id is None:
51 # prevent extra Invalid cookie sig warnings:
51 # prevent extra Invalid cookie sig warnings:
52 self.clear_cookie('user')
52 self.clear_cookie('username')
53 if not self.application.password:
53 if not self.application.password:
54 user_id = 'anonymous'
54 user_id = 'anonymous'
55 return user_id
55 return user_id
56
56
57
57
58 class ProjectDashboardHandler(AuthenticatedHandler):
58 class ProjectDashboardHandler(AuthenticatedHandler):
59
59
60 @web.authenticated
60 @web.authenticated
61 def get(self):
61 def get(self):
62 nbm = self.application.notebook_manager
62 nbm = self.application.notebook_manager
63 project = nbm.notebook_dir
63 project = nbm.notebook_dir
64 self.render(
64 self.render(
65 'projectdashboard.html', project=project,
65 'projectdashboard.html', project=project,
66 base_project_url=u'/', base_kernel_url=u'/'
66 base_project_url=u'/', base_kernel_url=u'/'
67 )
67 )
68
68
69
69
70 class LoginHandler(AuthenticatedHandler):
70 class LoginHandler(AuthenticatedHandler):
71
71
72 def get(self):
72 def get(self):
73 self.render('login.html', next='/')
73 self.render('login.html', next='/')
74
74
75 def post(self):
75 def post(self):
76 pwd = self.get_argument('password', default=u'')
76 pwd = self.get_argument('password', default=u'')
77 if self.application.password and pwd == self.application.password:
77 if self.application.password and pwd == self.application.password:
78 self.set_secure_cookie('user', str(uuid.uuid4()))
78 self.set_secure_cookie('username', str(uuid.uuid4()))
79 url = self.get_argument('next', default='/')
79 url = self.get_argument('next', default='/')
80 self.redirect(url)
80 self.redirect(url)
81
81
82
82
83 class NewHandler(AuthenticatedHandler):
83 class NewHandler(AuthenticatedHandler):
84
84
85 @web.authenticated
85 @web.authenticated
86 def get(self):
86 def get(self):
87 nbm = self.application.notebook_manager
87 nbm = self.application.notebook_manager
88 project = nbm.notebook_dir
88 project = nbm.notebook_dir
89 notebook_id = nbm.new_notebook()
89 notebook_id = nbm.new_notebook()
90 self.render(
90 self.render(
91 'notebook.html', project=project,
91 'notebook.html', project=project,
92 notebook_id=notebook_id,
92 notebook_id=notebook_id,
93 base_project_url=u'/', base_kernel_url=u'/'
93 base_project_url=u'/', base_kernel_url=u'/'
94 )
94 )
95
95
96
96
97 class NamedNotebookHandler(AuthenticatedHandler):
97 class NamedNotebookHandler(AuthenticatedHandler):
98
98
99 @web.authenticated
99 @web.authenticated
100 def get(self, notebook_id):
100 def get(self, notebook_id):
101 nbm = self.application.notebook_manager
101 nbm = self.application.notebook_manager
102 project = nbm.notebook_dir
102 project = nbm.notebook_dir
103 if not nbm.notebook_exists(notebook_id):
103 if not nbm.notebook_exists(notebook_id):
104 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
104 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
105 self.render(
105 self.render(
106 'notebook.html', project=project,
106 'notebook.html', project=project,
107 notebook_id=notebook_id,
107 notebook_id=notebook_id,
108 base_project_url=u'/', base_kernel_url=u'/'
108 base_project_url=u'/', base_kernel_url=u'/'
109 )
109 )
110
110
111
111
112 #-----------------------------------------------------------------------------
112 #-----------------------------------------------------------------------------
113 # Kernel handlers
113 # Kernel handlers
114 #-----------------------------------------------------------------------------
114 #-----------------------------------------------------------------------------
115
115
116
116
117 class MainKernelHandler(AuthenticatedHandler):
117 class MainKernelHandler(AuthenticatedHandler):
118
118
119 @web.authenticated
119 @web.authenticated
120 def get(self):
120 def get(self):
121 km = self.application.kernel_manager
121 km = self.application.kernel_manager
122 self.finish(jsonapi.dumps(km.kernel_ids))
122 self.finish(jsonapi.dumps(km.kernel_ids))
123
123
124 @web.authenticated
124 @web.authenticated
125 def post(self):
125 def post(self):
126 km = self.application.kernel_manager
126 km = self.application.kernel_manager
127 notebook_id = self.get_argument('notebook', default=None)
127 notebook_id = self.get_argument('notebook', default=None)
128 kernel_id = km.start_kernel(notebook_id)
128 kernel_id = km.start_kernel(notebook_id)
129 ws_url = self.application.ipython_app.get_ws_url()
129 ws_url = self.application.ipython_app.get_ws_url()
130 data = {'ws_url':ws_url,'kernel_id':kernel_id}
130 data = {'ws_url':ws_url,'kernel_id':kernel_id}
131 self.set_header('Location', '/'+kernel_id)
131 self.set_header('Location', '/'+kernel_id)
132 self.finish(jsonapi.dumps(data))
132 self.finish(jsonapi.dumps(data))
133
133
134
134
135 class KernelHandler(AuthenticatedHandler):
135 class KernelHandler(AuthenticatedHandler):
136
136
137 SUPPORTED_METHODS = ('DELETE')
137 SUPPORTED_METHODS = ('DELETE')
138
138
139 @web.authenticated
139 @web.authenticated
140 def delete(self, kernel_id):
140 def delete(self, kernel_id):
141 km = self.application.kernel_manager
141 km = self.application.kernel_manager
142 km.kill_kernel(kernel_id)
142 km.kill_kernel(kernel_id)
143 self.set_status(204)
143 self.set_status(204)
144 self.finish()
144 self.finish()
145
145
146
146
147 class KernelActionHandler(AuthenticatedHandler):
147 class KernelActionHandler(AuthenticatedHandler):
148
148
149 @web.authenticated
149 @web.authenticated
150 def post(self, kernel_id, action):
150 def post(self, kernel_id, action):
151 km = self.application.kernel_manager
151 km = self.application.kernel_manager
152 if action == 'interrupt':
152 if action == 'interrupt':
153 km.interrupt_kernel(kernel_id)
153 km.interrupt_kernel(kernel_id)
154 self.set_status(204)
154 self.set_status(204)
155 if action == 'restart':
155 if action == 'restart':
156 new_kernel_id = km.restart_kernel(kernel_id)
156 new_kernel_id = km.restart_kernel(kernel_id)
157 ws_url = self.application.ipython_app.get_ws_url()
157 ws_url = self.application.ipython_app.get_ws_url()
158 data = {'ws_url':ws_url,'kernel_id':new_kernel_id}
158 data = {'ws_url':ws_url,'kernel_id':new_kernel_id}
159 self.set_header('Location', '/'+new_kernel_id)
159 self.set_header('Location', '/'+new_kernel_id)
160 self.write(jsonapi.dumps(data))
160 self.write(jsonapi.dumps(data))
161 self.finish()
161 self.finish()
162
162
163
163
164 class ZMQStreamHandler(websocket.WebSocketHandler):
164 class ZMQStreamHandler(websocket.WebSocketHandler):
165
165
166 def _reserialize_reply(self, msg_list):
166 def _reserialize_reply(self, msg_list):
167 """Reserialize a reply message using JSON.
167 """Reserialize a reply message using JSON.
168
168
169 This takes the msg list from the ZMQ socket, unserializes it using
169 This takes the msg list from the ZMQ socket, unserializes it using
170 self.session and then serializes the result using JSON. This method
170 self.session and then serializes the result using JSON. This method
171 should be used by self._on_zmq_reply to build messages that can
171 should be used by self._on_zmq_reply to build messages that can
172 be sent back to the browser.
172 be sent back to the browser.
173 """
173 """
174 idents, msg_list = self.session.feed_identities(msg_list)
174 idents, msg_list = self.session.feed_identities(msg_list)
175 msg = self.session.unserialize(msg_list)
175 msg = self.session.unserialize(msg_list)
176 try:
176 try:
177 msg['header'].pop('date')
177 msg['header'].pop('date')
178 except KeyError:
178 except KeyError:
179 pass
179 pass
180 try:
180 try:
181 msg['parent_header'].pop('date')
181 msg['parent_header'].pop('date')
182 except KeyError:
182 except KeyError:
183 pass
183 pass
184 msg.pop('buffers')
184 msg.pop('buffers')
185 return jsonapi.dumps(msg)
185 return jsonapi.dumps(msg)
186
186
187 def _on_zmq_reply(self, msg_list):
187 def _on_zmq_reply(self, msg_list):
188 try:
188 try:
189 msg = self._reserialize_reply(msg_list)
189 msg = self._reserialize_reply(msg_list)
190 except:
190 except:
191 self.application.log.critical("Malformed message: %r" % msg_list)
191 self.application.log.critical("Malformed message: %r" % msg_list)
192 else:
192 else:
193 self.write_message(msg)
193 self.write_message(msg)
194
194
195
195
196 class AuthenticatedZMQStreamHandler(ZMQStreamHandler):
196 class AuthenticatedZMQStreamHandler(ZMQStreamHandler):
197
197
198 def open(self, kernel_id):
198 def open(self, kernel_id):
199 self.kernel_id = kernel_id.decode('ascii')
199 self.kernel_id = kernel_id.decode('ascii')
200 try:
200 try:
201 cfg = self.application.ipython_app.config
201 cfg = self.application.ipython_app.config
202 except AttributeError:
202 except AttributeError:
203 # protect from the case where this is run from something other than
203 # protect from the case where this is run from something other than
204 # the notebook app:
204 # the notebook app:
205 cfg = None
205 cfg = None
206 self.session = Session(config=cfg)
206 self.session = Session(config=cfg)
207 self.save_on_message = self.on_message
207 self.save_on_message = self.on_message
208 self.on_message = self.on_first_message
208 self.on_message = self.on_first_message
209
209
210 def get_current_user(self):
210 def get_current_user(self):
211 user_id = self.get_secure_cookie("user")
211 user_id = self.get_secure_cookie("username")
212 if user_id == '' or (user_id is None and not self.application.password):
212 if user_id == '' or (user_id is None and not self.application.password):
213 user_id = 'anonymous'
213 user_id = 'anonymous'
214 return user_id
214 return user_id
215
215
216 def _inject_cookie_message(self, msg):
216 def _inject_cookie_message(self, msg):
217 """Inject the first message, which is the document cookie,
217 """Inject the first message, which is the document cookie,
218 for authentication."""
218 for authentication."""
219 if isinstance(msg, unicode):
219 if isinstance(msg, unicode):
220 # Cookie can't constructor doesn't accept unicode strings for some reason
220 # Cookie can't constructor doesn't accept unicode strings for some reason
221 msg = msg.encode('utf8', 'replace')
221 msg = msg.encode('utf8', 'replace')
222 try:
222 try:
223 self._cookies = Cookie.SimpleCookie(msg)
223 self._cookies = Cookie.SimpleCookie(msg)
224 except:
224 except:
225 logging.warn("couldn't parse cookie string: %s",msg, exc_info=True)
225 logging.warn("couldn't parse cookie string: %s",msg, exc_info=True)
226
226
227 def on_first_message(self, msg):
227 def on_first_message(self, msg):
228 self._inject_cookie_message(msg)
228 self._inject_cookie_message(msg)
229 if self.get_current_user() is None:
229 if self.get_current_user() is None:
230 logging.warn("Couldn't authenticate WebSocket connection")
230 logging.warn("Couldn't authenticate WebSocket connection")
231 raise web.HTTPError(403)
231 raise web.HTTPError(403)
232 self.on_message = self.save_on_message
232 self.on_message = self.save_on_message
233
233
234
234
235 class IOPubHandler(AuthenticatedZMQStreamHandler):
235 class IOPubHandler(AuthenticatedZMQStreamHandler):
236
236
237 def initialize(self, *args, **kwargs):
237 def initialize(self, *args, **kwargs):
238 self._kernel_alive = True
238 self._kernel_alive = True
239 self._beating = False
239 self._beating = False
240 self.iopub_stream = None
240 self.iopub_stream = None
241 self.hb_stream = None
241 self.hb_stream = None
242
242
243 def on_first_message(self, msg):
243 def on_first_message(self, msg):
244 try:
244 try:
245 super(IOPubHandler, self).on_first_message(msg)
245 super(IOPubHandler, self).on_first_message(msg)
246 except web.HTTPError:
246 except web.HTTPError:
247 self.close()
247 self.close()
248 return
248 return
249 km = self.application.kernel_manager
249 km = self.application.kernel_manager
250 self.time_to_dead = km.time_to_dead
250 self.time_to_dead = km.time_to_dead
251 kernel_id = self.kernel_id
251 kernel_id = self.kernel_id
252 try:
252 try:
253 self.iopub_stream = km.create_iopub_stream(kernel_id)
253 self.iopub_stream = km.create_iopub_stream(kernel_id)
254 self.hb_stream = km.create_hb_stream(kernel_id)
254 self.hb_stream = km.create_hb_stream(kernel_id)
255 except web.HTTPError:
255 except web.HTTPError:
256 # WebSockets don't response to traditional error codes so we
256 # WebSockets don't response to traditional error codes so we
257 # close the connection.
257 # close the connection.
258 if not self.stream.closed():
258 if not self.stream.closed():
259 self.stream.close()
259 self.stream.close()
260 self.close()
260 self.close()
261 else:
261 else:
262 self.iopub_stream.on_recv(self._on_zmq_reply)
262 self.iopub_stream.on_recv(self._on_zmq_reply)
263 self.start_hb(self.kernel_died)
263 self.start_hb(self.kernel_died)
264
264
265 def on_message(self, msg):
265 def on_message(self, msg):
266 pass
266 pass
267
267
268 def on_close(self):
268 def on_close(self):
269 # This method can be called twice, once by self.kernel_died and once
269 # This method can be called twice, once by self.kernel_died and once
270 # from the WebSocket close event. If the WebSocket connection is
270 # from the WebSocket close event. If the WebSocket connection is
271 # closed before the ZMQ streams are setup, they could be None.
271 # closed before the ZMQ streams are setup, they could be None.
272 self.stop_hb()
272 self.stop_hb()
273 if self.iopub_stream is not None and not self.iopub_stream.closed():
273 if self.iopub_stream is not None and not self.iopub_stream.closed():
274 self.iopub_stream.on_recv(None)
274 self.iopub_stream.on_recv(None)
275 self.iopub_stream.close()
275 self.iopub_stream.close()
276 if self.hb_stream is not None and not self.hb_stream.closed():
276 if self.hb_stream is not None and not self.hb_stream.closed():
277 self.hb_stream.close()
277 self.hb_stream.close()
278
278
279 def start_hb(self, callback):
279 def start_hb(self, callback):
280 """Start the heartbeating and call the callback if the kernel dies."""
280 """Start the heartbeating and call the callback if the kernel dies."""
281 if not self._beating:
281 if not self._beating:
282 self._kernel_alive = True
282 self._kernel_alive = True
283
283
284 def ping_or_dead():
284 def ping_or_dead():
285 if self._kernel_alive:
285 if self._kernel_alive:
286 self._kernel_alive = False
286 self._kernel_alive = False
287 self.hb_stream.send(b'ping')
287 self.hb_stream.send(b'ping')
288 else:
288 else:
289 try:
289 try:
290 callback()
290 callback()
291 except:
291 except:
292 pass
292 pass
293 finally:
293 finally:
294 self._hb_periodic_callback.stop()
294 self._hb_periodic_callback.stop()
295
295
296 def beat_received(msg):
296 def beat_received(msg):
297 self._kernel_alive = True
297 self._kernel_alive = True
298
298
299 self.hb_stream.on_recv(beat_received)
299 self.hb_stream.on_recv(beat_received)
300 self._hb_periodic_callback = ioloop.PeriodicCallback(ping_or_dead, self.time_to_dead*1000)
300 self._hb_periodic_callback = ioloop.PeriodicCallback(ping_or_dead, self.time_to_dead*1000)
301 self._hb_periodic_callback.start()
301 self._hb_periodic_callback.start()
302 self._beating= True
302 self._beating= True
303
303
304 def stop_hb(self):
304 def stop_hb(self):
305 """Stop the heartbeating and cancel all related callbacks."""
305 """Stop the heartbeating and cancel all related callbacks."""
306 if self._beating:
306 if self._beating:
307 self._hb_periodic_callback.stop()
307 self._hb_periodic_callback.stop()
308 if not self.hb_stream.closed():
308 if not self.hb_stream.closed():
309 self.hb_stream.on_recv(None)
309 self.hb_stream.on_recv(None)
310
310
311 def kernel_died(self):
311 def kernel_died(self):
312 self.application.kernel_manager.delete_mapping_for_kernel(self.kernel_id)
312 self.application.kernel_manager.delete_mapping_for_kernel(self.kernel_id)
313 self.write_message(
313 self.write_message(
314 {'header': {'msg_type': 'status'},
314 {'header': {'msg_type': 'status'},
315 'parent_header': {},
315 'parent_header': {},
316 'content': {'execution_state':'dead'}
316 'content': {'execution_state':'dead'}
317 }
317 }
318 )
318 )
319 self.on_close()
319 self.on_close()
320
320
321
321
322 class ShellHandler(AuthenticatedZMQStreamHandler):
322 class ShellHandler(AuthenticatedZMQStreamHandler):
323
323
324 def initialize(self, *args, **kwargs):
324 def initialize(self, *args, **kwargs):
325 self.shell_stream = None
325 self.shell_stream = None
326
326
327 def on_first_message(self, msg):
327 def on_first_message(self, msg):
328 try:
328 try:
329 super(ShellHandler, self).on_first_message(msg)
329 super(ShellHandler, self).on_first_message(msg)
330 except web.HTTPError:
330 except web.HTTPError:
331 self.close()
331 self.close()
332 return
332 return
333 km = self.application.kernel_manager
333 km = self.application.kernel_manager
334 self.max_msg_size = km.max_msg_size
334 self.max_msg_size = km.max_msg_size
335 kernel_id = self.kernel_id
335 kernel_id = self.kernel_id
336 try:
336 try:
337 self.shell_stream = km.create_shell_stream(kernel_id)
337 self.shell_stream = km.create_shell_stream(kernel_id)
338 except web.HTTPError:
338 except web.HTTPError:
339 # WebSockets don't response to traditional error codes so we
339 # WebSockets don't response to traditional error codes so we
340 # close the connection.
340 # close the connection.
341 if not self.stream.closed():
341 if not self.stream.closed():
342 self.stream.close()
342 self.stream.close()
343 self.close()
343 self.close()
344 else:
344 else:
345 self.shell_stream.on_recv(self._on_zmq_reply)
345 self.shell_stream.on_recv(self._on_zmq_reply)
346
346
347 def on_message(self, msg):
347 def on_message(self, msg):
348 if len(msg) < self.max_msg_size:
348 if len(msg) < self.max_msg_size:
349 msg = jsonapi.loads(msg)
349 msg = jsonapi.loads(msg)
350 self.session.send(self.shell_stream, msg)
350 self.session.send(self.shell_stream, msg)
351
351
352 def on_close(self):
352 def on_close(self):
353 # Make sure the stream exists and is not already closed.
353 # Make sure the stream exists and is not already closed.
354 if self.shell_stream is not None and not self.shell_stream.closed():
354 if self.shell_stream is not None and not self.shell_stream.closed():
355 self.shell_stream.close()
355 self.shell_stream.close()
356
356
357
357
358 #-----------------------------------------------------------------------------
358 #-----------------------------------------------------------------------------
359 # Notebook web service handlers
359 # Notebook web service handlers
360 #-----------------------------------------------------------------------------
360 #-----------------------------------------------------------------------------
361
361
362 class NotebookRootHandler(AuthenticatedHandler):
362 class NotebookRootHandler(AuthenticatedHandler):
363
363
364 @web.authenticated
364 @web.authenticated
365 def get(self):
365 def get(self):
366 nbm = self.application.notebook_manager
366 nbm = self.application.notebook_manager
367 files = nbm.list_notebooks()
367 files = nbm.list_notebooks()
368 self.finish(jsonapi.dumps(files))
368 self.finish(jsonapi.dumps(files))
369
369
370 @web.authenticated
370 @web.authenticated
371 def post(self):
371 def post(self):
372 nbm = self.application.notebook_manager
372 nbm = self.application.notebook_manager
373 body = self.request.body.strip()
373 body = self.request.body.strip()
374 format = self.get_argument('format', default='json')
374 format = self.get_argument('format', default='json')
375 name = self.get_argument('name', default=None)
375 name = self.get_argument('name', default=None)
376 if body:
376 if body:
377 notebook_id = nbm.save_new_notebook(body, name=name, format=format)
377 notebook_id = nbm.save_new_notebook(body, name=name, format=format)
378 else:
378 else:
379 notebook_id = nbm.new_notebook()
379 notebook_id = nbm.new_notebook()
380 self.set_header('Location', '/'+notebook_id)
380 self.set_header('Location', '/'+notebook_id)
381 self.finish(jsonapi.dumps(notebook_id))
381 self.finish(jsonapi.dumps(notebook_id))
382
382
383
383
384 class NotebookHandler(AuthenticatedHandler):
384 class NotebookHandler(AuthenticatedHandler):
385
385
386 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
386 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
387
387
388 @web.authenticated
388 @web.authenticated
389 def get(self, notebook_id):
389 def get(self, notebook_id):
390 nbm = self.application.notebook_manager
390 nbm = self.application.notebook_manager
391 format = self.get_argument('format', default='json')
391 format = self.get_argument('format', default='json')
392 last_mod, name, data = nbm.get_notebook(notebook_id, format)
392 last_mod, name, data = nbm.get_notebook(notebook_id, format)
393 if format == u'json':
393 if format == u'json':
394 self.set_header('Content-Type', 'application/json')
394 self.set_header('Content-Type', 'application/json')
395 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
395 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
396 elif format == u'py':
396 elif format == u'py':
397 self.set_header('Content-Type', 'application/x-python')
397 self.set_header('Content-Type', 'application/x-python')
398 self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
398 self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
399 self.set_header('Last-Modified', last_mod)
399 self.set_header('Last-Modified', last_mod)
400 self.finish(data)
400 self.finish(data)
401
401
402 @web.authenticated
402 @web.authenticated
403 def put(self, notebook_id):
403 def put(self, notebook_id):
404 nbm = self.application.notebook_manager
404 nbm = self.application.notebook_manager
405 format = self.get_argument('format', default='json')
405 format = self.get_argument('format', default='json')
406 name = self.get_argument('name', default=None)
406 name = self.get_argument('name', default=None)
407 nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
407 nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
408 self.set_status(204)
408 self.set_status(204)
409 self.finish()
409 self.finish()
410
410
411 @web.authenticated
411 @web.authenticated
412 def delete(self, notebook_id):
412 def delete(self, notebook_id):
413 nbm = self.application.notebook_manager
413 nbm = self.application.notebook_manager
414 nbm.delete_notebook(notebook_id)
414 nbm.delete_notebook(notebook_id)
415 self.set_status(204)
415 self.set_status(204)
416 self.finish()
416 self.finish()
417
417
418 #-----------------------------------------------------------------------------
418 #-----------------------------------------------------------------------------
419 # RST web service handlers
419 # RST web service handlers
420 #-----------------------------------------------------------------------------
420 #-----------------------------------------------------------------------------
421
421
422
422
423 class RSTHandler(AuthenticatedHandler):
423 class RSTHandler(AuthenticatedHandler):
424
424
425 @web.authenticated
425 @web.authenticated
426 def post(self):
426 def post(self):
427 if publish_string is None:
427 if publish_string is None:
428 raise web.HTTPError(503, u'docutils not available')
428 raise web.HTTPError(503, u'docutils not available')
429 body = self.request.body.strip()
429 body = self.request.body.strip()
430 source = body
430 source = body
431 # template_path=os.path.join(os.path.dirname(__file__), u'templates', u'rst_template.html')
431 # template_path=os.path.join(os.path.dirname(__file__), u'templates', u'rst_template.html')
432 defaults = {'file_insertion_enabled': 0,
432 defaults = {'file_insertion_enabled': 0,
433 'raw_enabled': 0,
433 'raw_enabled': 0,
434 '_disable_config': 1,
434 '_disable_config': 1,
435 'stylesheet_path': 0
435 'stylesheet_path': 0
436 # 'template': template_path
436 # 'template': template_path
437 }
437 }
438 try:
438 try:
439 html = publish_string(source, writer_name='html',
439 html = publish_string(source, writer_name='html',
440 settings_overrides=defaults
440 settings_overrides=defaults
441 )
441 )
442 except:
442 except:
443 raise web.HTTPError(400, u'Invalid RST')
443 raise web.HTTPError(400, u'Invalid RST')
444 print html
444 print html
445 self.set_header('Content-Type', 'text/html')
445 self.set_header('Content-Type', 'text/html')
446 self.finish(html)
446 self.finish(html)
447
447
448
448
General Comments 0
You need to be logged in to leave comments. Login now