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