##// END OF EJS Templates
add read-only view for notebooks...
MinRK -
Show More
@@ -46,6 +46,22 b' def not_if_readonly(f, self, *args, **kwargs):'
46 46 else:
47 47 return f(self, *args, **kwargs)
48 48
49 @decorator
50 def authenticate_unless_readonly(f, self, *args, **kwargs):
51 """authenticate this page *unless* readonly view is active.
52
53 In read-only mode, the notebook list and print view should
54 be accessible without authentication.
55 """
56
57 @web.authenticated
58 def auth_f(self, *args, **kwargs):
59 return f(self, *args, **kwargs)
60 if self.application.ipython_app.read_only:
61 return f(self, *args, **kwargs)
62 else:
63 return auth_f(self, *args, **kwargs)
64
49 65 #-----------------------------------------------------------------------------
50 66 # Top-level handlers
51 67 #-----------------------------------------------------------------------------
@@ -68,7 +84,7 b' class AuthenticatedHandler(web.RequestHandler):'
68 84
69 85 class ProjectDashboardHandler(AuthenticatedHandler):
70 86
71 @web.authenticated
87 @authenticate_unless_readonly
72 88 def get(self):
73 89 nbm = self.application.notebook_manager
74 90 project = nbm.notebook_dir
@@ -81,7 +97,7 b' class ProjectDashboardHandler(AuthenticatedHandler):'
81 97 class LoginHandler(AuthenticatedHandler):
82 98
83 99 def get(self):
84 self.render('login.html', next='/')
100 self.render('login.html', next=self.get_argument('next', default='/'))
85 101
86 102 def post(self):
87 103 pwd = self.get_argument('password', default=u'')
@@ -93,7 +109,6 b' class LoginHandler(AuthenticatedHandler):'
93 109
94 110 class NewHandler(AuthenticatedHandler):
95 111
96 @not_if_readonly
97 112 @web.authenticated
98 113 def get(self):
99 114 nbm = self.application.notebook_manager
@@ -109,7 +124,7 b' class NewHandler(AuthenticatedHandler):'
109 124
110 125 class NamedNotebookHandler(AuthenticatedHandler):
111 126
112 @web.authenticated
127 @authenticate_unless_readonly
113 128 def get(self, notebook_id):
114 129 nbm = self.application.notebook_manager
115 130 project = nbm.notebook_dir
@@ -130,13 +145,11 b' class NamedNotebookHandler(AuthenticatedHandler):'
130 145
131 146 class MainKernelHandler(AuthenticatedHandler):
132 147
133 @not_if_readonly
134 148 @web.authenticated
135 149 def get(self):
136 150 km = self.application.kernel_manager
137 151 self.finish(jsonapi.dumps(km.kernel_ids))
138 152
139 @not_if_readonly
140 153 @web.authenticated
141 154 def post(self):
142 155 km = self.application.kernel_manager
@@ -152,7 +165,6 b' class KernelHandler(AuthenticatedHandler):'
152 165
153 166 SUPPORTED_METHODS = ('DELETE')
154 167
155 @not_if_readonly
156 168 @web.authenticated
157 169 def delete(self, kernel_id):
158 170 km = self.application.kernel_manager
@@ -163,7 +175,6 b' class KernelHandler(AuthenticatedHandler):'
163 175
164 176 class KernelActionHandler(AuthenticatedHandler):
165 177
166 @not_if_readonly
167 178 @web.authenticated
168 179 def post(self, kernel_id, action):
169 180 km = self.application.kernel_manager
@@ -242,7 +253,6 b' class AuthenticatedZMQStreamHandler(ZMQStreamHandler):'
242 253 except:
243 254 logging.warn("couldn't parse cookie string: %s",msg, exc_info=True)
244 255
245 @not_if_readonly
246 256 def on_first_message(self, msg):
247 257 self._inject_cookie_message(msg)
248 258 if self.get_current_user() is None:
@@ -380,13 +390,19 b' class ShellHandler(AuthenticatedZMQStreamHandler):'
380 390
381 391 class NotebookRootHandler(AuthenticatedHandler):
382 392
383 @web.authenticated
393 @authenticate_unless_readonly
384 394 def get(self):
395
396 # communicate read-only via Allow header
397 if self.application.ipython_app.read_only and not self.get_current_user():
398 self.set_header('Allow', 'GET')
399 else:
400 self.set_header('Allow', ', '.join(self.SUPPORTED_METHODS))
401
385 402 nbm = self.application.notebook_manager
386 403 files = nbm.list_notebooks()
387 404 self.finish(jsonapi.dumps(files))
388 405
389 @not_if_readonly
390 406 @web.authenticated
391 407 def post(self):
392 408 nbm = self.application.notebook_manager
@@ -405,11 +421,18 b' class NotebookHandler(AuthenticatedHandler):'
405 421
406 422 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
407 423
408 @web.authenticated
424 @authenticate_unless_readonly
409 425 def get(self, notebook_id):
410 426 nbm = self.application.notebook_manager
411 427 format = self.get_argument('format', default='json')
412 428 last_mod, name, data = nbm.get_notebook(notebook_id, format)
429
430 # communicate read-only via Allow header
431 if self.application.ipython_app.read_only and not self.get_current_user():
432 self.set_header('Allow', 'GET')
433 else:
434 self.set_header('Allow', ', '.join(self.SUPPORTED_METHODS))
435
413 436 if format == u'json':
414 437 self.set_header('Content-Type', 'application/json')
415 438 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
@@ -419,7 +442,6 b' class NotebookHandler(AuthenticatedHandler):'
419 442 self.set_header('Last-Modified', last_mod)
420 443 self.finish(data)
421 444
422 @not_if_readonly
423 445 @web.authenticated
424 446 def put(self, notebook_id):
425 447 nbm = self.application.notebook_manager
@@ -429,7 +451,6 b' class NotebookHandler(AuthenticatedHandler):'
429 451 self.set_status(204)
430 452 self.finish()
431 453
432 @not_if_readonly
433 454 @web.authenticated
434 455 def delete(self, notebook_id):
435 456 nbm = self.application.notebook_manager
@@ -118,13 +118,22 b" flags['no-browser']=("
118 118 )
119 119 flags['read-only'] = (
120 120 {'NotebookApp' : {'read_only' : True}},
121 "Launch the Notebook server in read-only mode, not allowing execution or editing"
121 """Allow read-only access to notebooks.
122
123 When using a password to protect the notebook server, this flag
124 allows unauthenticated clients to view the notebook list, and
125 individual notebooks, but not edit them, start kernels, or run
126 code.
127
128 This flag only makes sense in conjunction with setting a password,
129 via the ``NotebookApp.password`` configurable.
130 """
122 131 )
123 132
124 133 # the flags that are specific to the frontend
125 134 # these must be scrubbed before being passed to the kernel,
126 135 # or it will raise an error on unrecognized flags
127 notebook_flags = ['no-browser']
136 notebook_flags = ['no-browser', 'read-only']
128 137
129 138 aliases = dict(ipkernel_aliases)
130 139
@@ -51,3 +51,12 b' div#main_app {'
51 51 padding: 0.2em 0.8em;
52 52 font-size: 77%;
53 53 }
54
55 span#login_widget {
56 float: right;
57 }
58
59 /* generic class for hidden objects */
60 .hidden {
61 display: none;
62 } No newline at end of file
@@ -15,6 +15,10 b' var IPython = (function (IPython) {'
15 15
16 16 var Cell = function (notebook) {
17 17 this.notebook = notebook;
18 this.read_only = false;
19 if (notebook){
20 this.read_only = notebook.read_only;
21 }
18 22 this.selected = false;
19 23 this.element = null;
20 24 this.create_element();
@@ -37,6 +37,7 b' var IPython = (function (IPython) {'
37 37 indentUnit : 4,
38 38 mode: 'python',
39 39 theme: 'ipython',
40 readOnly: this.read_only,
40 41 onKeyEvent: $.proxy(this.handle_codemirror_keyevent,this)
41 42 });
42 43 input.append(input_area);
@@ -14,6 +14,7 b' var IPython = (function (IPython) {'
14 14 var utils = IPython.utils;
15 15
16 16 var Notebook = function (selector) {
17 this.read_only = false;
17 18 this.element = $(selector);
18 19 this.element.scroll();
19 20 this.element.data("notebook", this);
@@ -42,6 +43,7 b' var IPython = (function (IPython) {'
42 43 var that = this;
43 44 var end_space = $('<div class="end_space"></div>').height(150);
44 45 end_space.dblclick(function (e) {
46 if (that.read_only) return;
45 47 var ncells = that.ncells();
46 48 that.insert_code_cell_below(ncells-1);
47 49 });
@@ -54,6 +56,7 b' var IPython = (function (IPython) {'
54 56 var that = this;
55 57 $(document).keydown(function (event) {
56 58 // console.log(event);
59 if (that.read_only) return;
57 60 if (event.which === 38) {
58 61 var cell = that.selected_cell();
59 62 if (cell.at_top()) {
@@ -189,7 +192,7 b' var IPython = (function (IPython) {'
189 192 if (kill_kernel) {
190 193 that.kernel.kill();
191 194 }
192 if (that.dirty) {
195 if (that.dirty && ! that.read_only) {
193 196 return "You have unsaved changes that will be lost if you leave this page.";
194 197 };
195 198 });
@@ -975,14 +978,26 b' var IPython = (function (IPython) {'
975 978
976 979
977 980 Notebook.prototype.notebook_loaded = function (data, status, xhr) {
981 var allowed = xhr.getResponseHeader('Allow');
982 if (allowed && allowed.indexOf('PUT') == -1){
983 this.read_only = true;
984 // unhide login button if it's relevant
985 $('span#login_widget').removeClass('hidden');
986 }else{
987 this.read_only = false;
988 }
978 989 this.fromJSON(data);
979 990 if (this.ncells() === 0) {
980 991 this.insert_code_cell_below();
981 992 };
982 993 IPython.save_widget.status_save();
983 994 IPython.save_widget.set_notebook_name(data.metadata.name);
984 this.start_kernel();
985 995 this.dirty = false;
996 if (this.read_only) {
997 this.handle_read_only();
998 }else{
999 this.start_kernel();
1000 }
986 1001 // fromJSON always selects the last cell inserted. We need to wait
987 1002 // until that is done before scrolling to the top.
988 1003 setTimeout(function () {
@@ -992,6 +1007,15 b' var IPython = (function (IPython) {'
992 1007 };
993 1008
994 1009
1010 Notebook.prototype.handle_read_only = function(){
1011 IPython.left_panel.collapse();
1012 IPython.save_widget.element.find('button#save_notebook').addClass('hidden');
1013 $('button#new_notebook').addClass('hidden');
1014 $('div#cell_section').addClass('hidden');
1015 $('div#kernel_section').addClass('hidden');
1016 }
1017
1018
995 1019 IPython.Notebook = Notebook;
996 1020
997 1021
@@ -73,6 +73,15 b' var IPython = (function (IPython) {'
73 73
74 74
75 75 NotebookList.prototype.list_loaded = function (data, status, xhr) {
76 var allowed = xhr.getResponseHeader('Allow');
77 if (allowed && allowed.indexOf('PUT') == -1){
78 this.read_only = true;
79 $('#new_notebook').addClass('hidden');
80 // unhide login button if it's relevant
81 $('span#login_widget').removeClass('hidden');
82 }else{
83 this.read_only = false;
84 }
76 85 var len = data.length;
77 86 // Todo: remove old children
78 87 for (var i=0; i<len; i++) {
@@ -80,7 +89,10 b' var IPython = (function (IPython) {'
80 89 var nbname = data[i].name;
81 90 var item = this.new_notebook_item(i);
82 91 this.add_link(notebook_id, nbname, item);
92 if (!this.read_only){
93 // hide delete buttons when readonly
83 94 this.add_delete_button(item);
95 }
84 96 };
85 97 };
86 98
@@ -33,6 +33,7 b' $(document).ready(function () {'
33 33 IPython.left_panel = new IPython.LeftPanel('div#left_panel', 'div#left_panel_splitter');
34 34 IPython.save_widget = new IPython.SaveWidget('span#save_widget');
35 35 IPython.quick_help = new IPython.QuickHelp('span#quick_help_area');
36 IPython.login_widget = new IPython.LoginWidget('span#login_widget');
36 37 IPython.print_widget = new IPython.PrintWidget('span#print_widget');
37 38 IPython.notebook = new IPython.Notebook('div#notebook');
38 39 IPython.kernel_status_widget = new IPython.KernelStatusWidget('#kernel_status');
@@ -28,6 +28,7 b' $(document).ready(function () {'
28 28 $('div#right_panel').addClass('box-flex');
29 29
30 30 IPython.notebook_list = new IPython.NotebookList('div#notebook_list');
31 IPython.login_widget = new IPython.LoginWidget('span#login_widget');
31 32 IPython.notebook_list.load_list();
32 33
33 34 // These have display: none in the css file and are made visible here to prevent FLOUC.
@@ -33,7 +33,8 b' var IPython = (function (IPython) {'
33 33 indentUnit : 4,
34 34 mode: this.code_mirror_mode,
35 35 theme: 'default',
36 value: this.placeholder
36 value: this.placeholder,
37 readOnly: this.read_only,
37 38 });
38 39 // The tabindex=-1 makes this div focusable.
39 40 var render_area = $('<div/>').addClass('text_cell_render').
@@ -65,6 +66,7 b' var IPython = (function (IPython) {'
65 66
66 67
67 68 TextCell.prototype.edit = function () {
69 if ( this.read_only ) return;
68 70 if (this.rendered === true) {
69 71 var text_cell = this.element;
70 72 var output = text_cell.find("div.text_cell_render");
@@ -58,6 +58,9 b''
58 58 <span id="quick_help_area">
59 59 <button id="quick_help">Quick<u>H</u>elp</button>
60 60 </span>
61 <span id="login_widget" class="hidden">
62 <button id="login">Login</button>
63 </span>
61 64 <span id="kernel_status">Idle</span>
62 65 </div>
63 66
@@ -278,6 +281,7 b''
278 281 <script src="static/js/layout.js" type="text/javascript" charset="utf-8"></script>
279 282 <script src="static/js/savewidget.js" type="text/javascript" charset="utf-8"></script>
280 283 <script src="static/js/quickhelp.js" type="text/javascript" charset="utf-8"></script>
284 <script src="static/js/loginwidget.js" type="text/javascript" charset="utf-8"></script>
281 285 <script src="static/js/pager.js" type="text/javascript" charset="utf-8"></script>
282 286 <script src="static/js/panelsection.js" type="text/javascript" charset="utf-8"></script>
283 287 <script src="static/js/printwidget.js" type="text/javascript" charset="utf-8"></script>
@@ -19,6 +19,9 b''
19 19
20 20 <div id="header">
21 21 <span id="ipython_notebook"><h1>IPython Notebook</h1></span>
22 <span id="login_widget" class="hidden">
23 <button id="login">Login</button>
24 </span>
22 25 </div>
23 26
24 27 <div id="header_border"></div>
@@ -54,6 +57,7 b''
54 57 <script src="static/jquery/js/jquery-ui-1.8.14.custom.min.js" type="text/javascript" charset="utf-8"></script>
55 58 <script src="static/js/namespace.js" type="text/javascript" charset="utf-8"></script>
56 59 <script src="static/js/notebooklist.js" type="text/javascript" charset="utf-8"></script>
60 <script src="static/js/loginwidget.js" type="text/javascript" charset="utf-8"></script>
57 61 <script src="static/js/projectdashboardmain.js" type="text/javascript" charset="utf-8"></script>
58 62
59 63 </body>
General Comments 0
You need to be logged in to leave comments. Login now