Merge pull request #899 from minrk/nbwork

Internal notebook refactoring and cleanup, plus fixing a bug that prevented Tornado from authenticating users with password.
This commit is contained in:
Fernando Perez 2011-10-18 18:58:25 -07:00
commit 38af53d027
14 changed files with 145 additions and 86 deletions

View File

@ -18,6 +18,7 @@ Authors:
import logging
import Cookie
import uuid
from tornado import web
from tornado import websocket
@ -40,51 +41,74 @@ except ImportError:
class AuthenticatedHandler(web.RequestHandler):
"""A RequestHandler with an authenticated user."""
def get_current_user(self):
user_id = self.get_secure_cookie("user")
user_id = self.get_secure_cookie("username")
# For now the user_id should not return empty, but it could eventually
if user_id == '':
user_id = 'anonymous'
if user_id is None:
# prevent extra Invalid cookie sig warnings:
self.clear_cookie('user')
self.clear_cookie('username')
if not self.application.password:
user_id = 'anonymous'
return user_id
class NBBrowserHandler(AuthenticatedHandler):
class ProjectDashboardHandler(AuthenticatedHandler):
@web.authenticated
def get(self):
nbm = self.application.notebook_manager
project = nbm.notebook_dir
self.render('nbbrowser.html', project=project)
self.render(
'projectdashboard.html', project=project,
base_project_url=u'/', base_kernel_url=u'/'
)
class LoginHandler(AuthenticatedHandler):
def get(self):
user_id = self.get_secure_cookie("user") or ''
self.render('login.html', user_id=user_id)
self.render('login.html', next='/')
def post(self):
pwd = self.get_argument("password", default=u'')
pwd = self.get_argument('password', default=u'')
if self.application.password and pwd == self.application.password:
self.set_secure_cookie("user", self.get_argument("name", default=u''))
url = self.get_argument("next", default="/")
self.set_secure_cookie('username', str(uuid.uuid4()))
url = self.get_argument('next', default='/')
self.redirect(url)
class NewHandler(AuthenticatedHandler):
@web.authenticated
def get(self):
notebook_id = self.application.notebook_manager.new_notebook()
self.render('notebook.html', notebook_id=notebook_id)
nbm = self.application.notebook_manager
project = nbm.notebook_dir
notebook_id = nbm.new_notebook()
self.render(
'notebook.html', project=project,
notebook_id=notebook_id,
base_project_url=u'/', base_kernel_url=u'/',
kill_kernel=False
)
class NamedNotebookHandler(AuthenticatedHandler):
@web.authenticated
def get(self, notebook_id):
nbm = self.application.notebook_manager
project = nbm.notebook_dir
if not nbm.notebook_exists(notebook_id):
raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
self.render('notebook.html', notebook_id=notebook_id)
self.render(
'notebook.html', project=project,
notebook_id=notebook_id,
base_project_url=u'/', base_kernel_url=u'/',
kill_kernel=False
)
#-----------------------------------------------------------------------------
@ -166,11 +190,13 @@ class ZMQStreamHandler(websocket.WebSocketHandler):
try:
msg = self._reserialize_reply(msg_list)
except:
self.application.kernel_manager.log.critical("Malformed message: %r" % msg_list)
self.application.log.critical("Malformed message: %r" % msg_list)
else:
self.write_message(msg)
class AuthenticatedZMQStreamHandler(ZMQStreamHandler):
def open(self, kernel_id):
self.kernel_id = kernel_id.decode('ascii')
try:
@ -184,7 +210,7 @@ class AuthenticatedZMQStreamHandler(ZMQStreamHandler):
self.on_message = self.on_first_message
def get_current_user(self):
user_id = self.get_secure_cookie("user")
user_id = self.get_secure_cookie("username")
if user_id == '' or (user_id is None and not self.application.password):
user_id = 'anonymous'
return user_id
@ -196,7 +222,7 @@ class AuthenticatedZMQStreamHandler(ZMQStreamHandler):
# Cookie can't constructor doesn't accept unicode strings for some reason
msg = msg.encode('utf8', 'replace')
try:
self._cookies = Cookie.SimpleCookie(msg)
self.request._cookies = Cookie.SimpleCookie(msg)
except:
logging.warn("couldn't parse cookie string: %s",msg, exc_info=True)

View File

@ -37,7 +37,7 @@ from tornado import web
from .kernelmanager import MappingKernelManager
from .handlers import (LoginHandler,
NBBrowserHandler, NewHandler, NamedNotebookHandler,
ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
ShellHandler, NotebookRootHandler, NotebookHandler, RSTHandler
)
@ -80,7 +80,7 @@ class NotebookWebApplication(web.Application):
def __init__(self, ipython_app, kernel_manager, notebook_manager, log):
handlers = [
(r"/", NBBrowserHandler),
(r"/", ProjectDashboardHandler),
(r"/login", LoginHandler),
(r"/new", NewHandler),
(r"/%s" % _notebook_id_regex, NamedNotebookHandler),
@ -125,11 +125,11 @@ notebook_flags = ['no-browser']
aliases = dict(ipkernel_aliases)
aliases.update({
'ip': 'IPythonNotebookApp.ip',
'port': 'IPythonNotebookApp.port',
'keyfile': 'IPythonNotebookApp.keyfile',
'certfile': 'IPythonNotebookApp.certfile',
'ws-hostname': 'IPythonNotebookApp.ws_hostname',
'ip': 'NotebookApp.ip',
'port': 'NotebookApp.port',
'keyfile': 'NotebookApp.keyfile',
'certfile': 'NotebookApp.certfile',
'ws-hostname': 'NotebookApp.ws_hostname',
'notebook-dir': 'NotebookManager.notebook_dir',
})
@ -141,10 +141,10 @@ notebook_aliases = [u'port', u'ip', u'keyfile', u'certfile', u'ws-hostname',
u'notebook-dir']
#-----------------------------------------------------------------------------
# IPythonNotebookApp
# NotebookApp
#-----------------------------------------------------------------------------
class IPythonNotebookApp(BaseIPythonApplication):
class NotebookApp(BaseIPythonApplication):
name = 'ipython-notebook'
default_config_file_name='ipython_notebook_config.py'
@ -213,7 +213,7 @@ class IPythonNotebookApp(BaseIPythonApplication):
return prefix + self.ws_hostname + u':' + unicode(self.port)
def parse_command_line(self, argv=None):
super(IPythonNotebookApp, self).parse_command_line(argv)
super(NotebookApp, self).parse_command_line(argv)
if argv is None:
argv = sys.argv[1:]
@ -254,14 +254,14 @@ class IPythonNotebookApp(BaseIPythonApplication):
self.notebook_manager.list_notebooks()
def init_logging(self):
super(IPythonNotebookApp, self).init_logging()
super(NotebookApp, self).init_logging()
# This prevents double log messages because tornado use a root logger that
# self.log is a child of. The logging module dipatches log messages to a log
# and all of its ancenstors until propagate is set to False.
self.log.propagate = False
def initialize(self, argv=None):
super(IPythonNotebookApp, self).initialize(argv)
super(NotebookApp, self).initialize(argv)
self.init_configurables()
self.web_app = NotebookWebApplication(
self, self.kernel_manager, self.notebook_manager, self.log
@ -309,7 +309,7 @@ class IPythonNotebookApp(BaseIPythonApplication):
#-----------------------------------------------------------------------------
def launch_new_instance():
app = IPythonNotebookApp()
app = NotebookApp()
app.initialize()
app.start()

View File

@ -37,9 +37,8 @@ input#notebook_name {
}
span#kernel_status {
position: absolute;
padding: 8px 5px 5px 5px;
right: 10px;
float: right;
padding: 0px 5px;
font-weight: bold;
}
@ -65,10 +64,14 @@ div#left_panel {
position: absolute;
}
h3.section_header {
div.section_header {
padding: 5px;
}
div.section_header h3 {
display: inline;
}
div.section_content {
padding: 5px;
}

View File

@ -15,12 +15,10 @@ var IPython = (function (IPython) {
var Kernel = function () {
this.kernel_id = null;
this.base_url = "/kernels";
this.kernel_url = null;
this.shell_channel = null;
this.iopub_channel = null;
this.base_url = $('body').data('baseKernelUrl') + "kernels";
this.running = false;
this.username = "username";
this.session_id = utils.uuid();
@ -52,7 +50,8 @@ var IPython = (function (IPython) {
var that = this;
if (!this.running) {
var qs = $.param({notebook:notebook_id});
$.post(this.base_url + '?' + qs,
var url = this.base_url + '?' + qs
$.post(url,
function (kernel_id) {
that._handle_start_kernel(kernel_id, callback);
},
@ -97,7 +96,6 @@ var IPython = (function (IPython) {
this.iopub_channel = new this.WebSocket(ws_url + "/iopub");
send_cookie = function(){
this.send(document.cookie);
console.log(this);
}
this.shell_channel.onopen = send_cookie;
this.iopub_channel.onopen = send_cookie;

View File

@ -0,0 +1,30 @@
//----------------------------------------------------------------------------
// Copyright (C) 2008-2011 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
//============================================================================
// On document ready
//============================================================================
$(document).ready(function () {
$('div#header').addClass('border-box-sizing');
$('div#header_border').addClass('border-box-sizing ui-widget ui-widget-content');
$('div#main_app').addClass('border-box-sizing ui-widget');
$('div#app_hbox').addClass('hbox');
$('div#left_panel').addClass('box-flex');
$('div#right_panel').addClass('box-flex');
$('input#signin').button();
// These have display: none in the css file and are made visible here to prevent FLOUC.
$('div#header').css('display','block');
$('div#main_app').css('display','block');
});

View File

@ -931,7 +931,8 @@ var IPython = (function (IPython) {
error : $.proxy(this.notebook_save_failed,this)
};
IPython.save_widget.status_saving();
$.ajax("/notebooks/" + notebook_id, settings);
var url = $('body').data('baseProjectUrl') + 'notebooks/' + notebook_id
$.ajax(url, settings);
};
};
@ -939,7 +940,7 @@ var IPython = (function (IPython) {
Notebook.prototype.notebook_saved = function (data, status, xhr) {
this.dirty = false;
IPython.save_widget.notebook_saved();
setTimeout($.proxy(IPython.save_widget.status_save,IPython.save_widget),500);
IPython.save_widget.status_save();
}
@ -947,7 +948,7 @@ var IPython = (function (IPython) {
// Notify the user and reset the save button
// TODO: Handle different types of errors (timeout etc.)
alert('An unexpected error occured while saving the notebook.');
setTimeout($.proxy(IPython.save_widget.reset_status,IPython.save_widget),500);
IPython.save_widget.reset_status();
}
@ -968,7 +969,8 @@ var IPython = (function (IPython) {
}
};
IPython.save_widget.status_loading();
$.ajax("/notebooks/" + notebook_id, settings);
var url = $('body').data('baseProjectUrl') + 'notebooks/' + notebook_id
$.ajax(url, settings);
}
@ -989,8 +991,10 @@ var IPython = (function (IPython) {
}, 50);
};
IPython.Notebook = Notebook;
return IPython;
}(IPython));

View File

@ -67,7 +67,8 @@ var IPython = (function (IPython) {
dataType : "json",
success : $.proxy(this.list_loaded, this)
};
$.ajax("/notebooks", settings);
var url = $('body').data('baseProjectUrl') + 'notebooks'
$.ajax(url, settings);
};
@ -105,7 +106,7 @@ var IPython = (function (IPython) {
var new_item_name = $('<span/>').addClass('item_name');
new_item_name.append(
$('<a/>').
attr('href','/'+notebook_id).
attr('href', $('body').data('baseProjectURL')+notebook_id).
attr('target','_blank').
text(nbname)
);
@ -170,7 +171,8 @@ var IPython = (function (IPython) {
parent_item.remove();
}
};
$.ajax("/notebooks/" + notebook_id, settings);
var url = $('body').data('baseProjectUrl') + 'notebooks/' + notebook_id
$.ajax(url, settings);
$(this).dialog('close');
},
"Cancel": function () {
@ -217,7 +219,8 @@ var IPython = (function (IPython) {
};
var qs = $.param({name:nbname, format:nbformat});
$.ajax('/notebooks?' + qs, settings);
var url = $('body').data('baseProjectUrl') + 'notebooks?' + qs
$.ajax(url, settings);
});
var cancel_button = $('<button>Cancel</button>').button().
click(function (e) {

View File

@ -21,7 +21,7 @@ $(document).ready(function () {
$('div#content_toolbar').addClass('ui-widget ui-helper-clearfix');
$('#new_notebook').button().click(function (e) {
window.open('/new');
window.open($('body').data('baseProjectUrl')+'new');
});
$('div#left_panel').addClass('box-flex');

View File

@ -82,12 +82,12 @@ var IPython = (function (IPython) {
SaveWidget.prototype.set_document_title = function () {
nbname = this.get_notebook_name();
document.title = 'IPy: ' + nbname;
document.title = nbname;
};
SaveWidget.prototype.get_notebook_id = function () {
return this.element.find('span#notebook_id').text()
return $('body').data('notebookId');
};

View File

@ -7,24 +7,13 @@
<title>IPython Notebook</title>
<link rel="stylesheet" href="static/jquery/css/themes/aristo/jquery-wijmo.css" type="text/css" />
<!-- <link rel="stylesheet" href="static/jquery/css/themes/rocket/jquery-wijmo.css" type="text/css" /> -->
<!-- <link rel="stylesheet" href="static/jquery/css/themes/smoothness/jquery-ui-1.8.14.custom.css" type="text/css" />-->
<link rel="stylesheet" href="static/css/boilerplate.css" type="text/css" />
<link rel="stylesheet" href="static/css/layout.css" type="text/css" />
<link rel="stylesheet" href="static/css/base.css" type="text/css" />
<script type="text/javascript" charset="utf-8">
function add_next_to_action(){
// add 'next' argument to action url, to preserve redirect
var query = location.search.substring(1);
var form = document.forms[0];
var action = form.getAttribute("action");
form.setAttribute("action", action + '?' + query);
}
</script>
</head>
<body onload="add_next_to_action()">
<body>
<div id="header">
<span id="ipython_notebook"><h1>IPython Notebook</h1></span>
@ -40,10 +29,9 @@ function add_next_to_action(){
</div>
<div id="content_panel">
<form action="/login" method="post">
Name: <input type="text" name="name" value="{{user_id}}">
<form action="/login?next={{url_escape(next)}}" method="post">
Password: <input type="password" name="password">
<input type="submit" value="Sign in">
<input type="submit" value="Sign in" id="signin">
</form>
</div>
<div id="right_panel">
@ -56,8 +44,7 @@ function add_next_to_action(){
<script src="static/jquery/js/jquery-1.6.2.min.js" type="text/javascript" charset="utf-8"></script>
<script src="static/jquery/js/jquery-ui-1.8.14.custom.min.js" type="text/javascript" charset="utf-8"></script>
<script src="static/js/namespace.js" type="text/javascript" charset="utf-8"></script>
<script src="static/js/notebooklist.js" type="text/javascript" charset="utf-8"></script>
<script src="static/js/nbbrowser_main.js" type="text/javascript" charset="utf-8"></script>
<script src="static/js/loginmain.js" type="text/javascript" charset="utf-8"></script>
</body>

View File

@ -6,10 +6,6 @@
<title>IPython Notebook</title>
<link rel="stylesheet" href="static/jquery/css/themes/aristo/jquery-wijmo.css" type="text/css" />
<!-- <link rel="stylesheet" href="static/jquery/css/themes/rocket/jquery-wijmo.css" type="text/css" /> -->
<!-- <link rel="stylesheet" href="static/jquery/css/themes/smoothness/jquery-ui-1.8.14.custom.css" type="text/css" />-->
<!-- <script type="text/javascript" src="http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS_HTML" charset="utf-8"></script> -->
<script type='text/javascript' src='static/mathjax/MathJax.js?config=TeX-AMS_HTML' charset='utf-8'></script>
<script type="text/javascript">
@ -30,6 +26,7 @@
}
</script>
<link rel="stylesheet" href="static/jquery/css/themes/aristo/jquery-wijmo.css" type="text/css" />
<link rel="stylesheet" href="static/codemirror/lib/codemirror.css">
<link rel="stylesheet" href="static/codemirror/mode/markdown/markdown.css">
<link rel="stylesheet" href="static/codemirror/mode/rst/rst.css">
@ -47,19 +44,20 @@
</head>
<body onload='CheckMathJax();'>
<body onload='CheckMathJax();'
data-project={{project}} data-notebook-id={{notebook_id}}
data-base-project-url={{base_project_url}} data-base-kernel-url={{base_kernel_url}}
>
<div id="header">
<span id="ipython_notebook"><h1>IPython Notebook</h1></span>
<span id="save_widget">
<input type="text" id="notebook_name" size="20"></textarea>
<span id="notebook_id" style="display:none">{{notebook_id}}</span>
<button id="save_notebook"><u>S</u>ave</button>
</span>
<span id="quick_help_area">
<button id="quick_help">Quick<u>H</u>elp</button>
</span>
<span id="kernel_status">Idle</span>
</div>
<div id="MathJaxFetchingWarning"
@ -90,7 +88,9 @@
<div id="left_panel">
<div id="notebook_section">
<h3 class="section_header">Notebook</h3>
<div class="section_header">
<h3>Notebook</h3>
</div>
<div class="section_content">
<div class="section_row">
<span id="new_open" class="section_row_buttons">
@ -121,7 +121,9 @@
</div>
<div id="cell_section">
<h3 class="section_header">Cell</h3>
<div class="section_header">
<h3>Cell</h3>
</div>
<div class="section_content">
<div class="section_row">
<span class="section_row_buttons">
@ -175,7 +177,10 @@
</div>
<div id="kernel_section">
<h3 class="section_header">Kernel</h3>
<div class="section_header">
<h3>Kernel</h3>
<span id="kernel_status">Idle</span>
</div>
<div class="section_content">
<div class="section_row">
<span id="int_restart" class="section_row_buttons">
@ -186,7 +191,11 @@
</div>
<div class="section_row">
<span id="kernel_persist">
<input type="checkbox" id="kill_kernel"></input>
{% if kill_kernel %}
<input type="checkbox" id="kill_kernel" checked="true"></input>
{% else %}
<input type="checkbox" id="kill_kernel"></input>
{% end %}
</span>
<span class="checkbox_label" id="kill_kernel_label">Kill kernel upon exit:</span>
</div>
@ -194,7 +203,9 @@
</div>
<div id="help_section">
<h3 class="section_header">Help</h3>
<div class="section_header">
<h3>Cell</h3>
</div>
<div class="section_content">
<div class="section_row">
<span id="help_buttons0" class="section_row_buttons">
@ -272,8 +283,7 @@
<script src="static/js/printwidget.js" type="text/javascript" charset="utf-8"></script>
<script src="static/js/leftpanel.js" type="text/javascript" charset="utf-8"></script>
<script src="static/js/notebook.js" type="text/javascript" charset="utf-8"></script>
<script src="static/js/notebook_main.js" type="text/javascript" charset="utf-8"></script>
<script src="static/js/notebookmain.js" type="text/javascript" charset="utf-8"></script>
</body>

View File

@ -7,17 +7,15 @@
<title>IPython Notebook</title>
<link rel="stylesheet" href="static/jquery/css/themes/aristo/jquery-wijmo.css" type="text/css" />
<!-- <link rel="stylesheet" href="static/jquery/css/themes/rocket/jquery-wijmo.css" type="text/css" /> -->
<!-- <link rel="stylesheet" href="static/jquery/css/themes/smoothness/jquery-ui-1.8.14.custom.css" type="text/css" />-->
<link rel="stylesheet" href="static/css/boilerplate.css" type="text/css" />
<link rel="stylesheet" href="static/css/layout.css" type="text/css" />
<link rel="stylesheet" href="static/css/base.css" type="text/css" />
<link rel="stylesheet" href="static/css/nbbrowser.css" type="text/css" />
<link rel="stylesheet" href="static/css/projectdashboard.css" type="text/css" />
</head>
<body>
<body data-project={{project}} data-base-project-url={{base_project_url}}
data-base-kernel-url={{base_kernel_url}}>
<div id="header">
<span id="ipython_notebook"><h1>IPython Notebook</h1></span>
@ -56,7 +54,7 @@
<script src="static/jquery/js/jquery-ui-1.8.14.custom.min.js" type="text/javascript" charset="utf-8"></script>
<script src="static/js/namespace.js" type="text/javascript" charset="utf-8"></script>
<script src="static/js/notebooklist.js" type="text/javascript" charset="utf-8"></script>
<script src="static/js/nbbrowser_main.js" type="text/javascript" charset="utf-8"></script>
<script src="static/js/projectdashboardmain.js" type="text/javascript" charset="utf-8"></script>
</body>