##// END OF EJS Templates
use ?download=1 to trigger download in /files/...
Min RK -
Show More
@@ -1,48 +1,49 b''
1 1 """Serve files directly from the ContentsManager."""
2 2
3 3 # Copyright (c) IPython Development Team.
4 4 # Distributed under the terms of the Modified BSD License.
5 5
6 6 import os
7 7 import mimetypes
8 8 import json
9 9 import base64
10 10
11 11 from tornado import web
12 12
13 13 from IPython.html.base.handlers import IPythonHandler
14 14
15 15 class FilesHandler(IPythonHandler):
16 16 """serve files via ContentsManager"""
17 17
18 18 @web.authenticated
19 19 def get(self, path):
20 20 cm = self.settings['contents_manager']
21 21 if cm.is_hidden(path):
22 22 self.log.info("Refusing to serve hidden file, via 404 Error")
23 23 raise web.HTTPError(404)
24 24
25 25 path, name = os.path.split(path)
26 26 model = cm.get_model(name, path)
27
27
28 if self.get_argument("download", False):
29 self.set_header('Content-Disposition','attachment; filename="%s"' % name)
30
28 31 if model['type'] == 'notebook':
29 32 self.set_header('Content-Type', 'application/json')
30 33 else:
31 34 cur_mime = mimetypes.guess_type(name)[0]
32 35 if cur_mime is not None:
33 36 self.set_header('Content-Type', cur_mime)
34 37
35 self.set_header('Content-Disposition','attachment; filename="%s"' % name)
36
37 38 if model['format'] == 'base64':
38 39 b64_bytes = model['content'].encode('ascii')
39 40 self.write(base64.decodestring(b64_bytes))
40 41 elif model['format'] == 'json':
41 42 self.write(json.dumps(model['content']))
42 43 else:
43 44 self.write(model['content'])
44 45 self.flush()
45 46
46 47 default_handlers = [
47 48 (r"/files/(.*)", FilesHandler),
48 49 ] No newline at end of file
@@ -1,353 +1,353 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define([
5 5 'base/js/namespace',
6 6 'jquery',
7 7 'base/js/utils',
8 8 'notebook/js/tour',
9 9 'bootstrap',
10 10 'moment',
11 11 ], function(IPython, $, utils, tour, bootstrap, moment) {
12 12 "use strict";
13 13
14 14 var MenuBar = function (selector, options) {
15 15 // Constructor
16 16 //
17 17 // A MenuBar Class to generate the menubar of IPython notebook
18 18 //
19 19 // Parameters:
20 20 // selector: string
21 21 // options: dictionary
22 22 // Dictionary of keyword arguments.
23 23 // notebook: Notebook instance
24 24 // layout_manager: LayoutManager instance
25 25 // events: $(Events) instance
26 26 // save_widget: SaveWidget instance
27 27 // quick_help: QuickHelp instance
28 28 // base_url : string
29 29 // notebook_path : string
30 30 // notebook_name : string
31 31 options = options || {};
32 32 this.base_url = options.base_url || utils.get_body_data("baseUrl");
33 33 this.selector = selector;
34 34 this.notebook = options.notebook;
35 35 this.layout_manager = options.layout_manager;
36 36 this.events = options.events;
37 37 this.save_widget = options.save_widget;
38 38 this.quick_help = options.quick_help;
39 39
40 40 try {
41 41 this.tour = new tour.Tour(this.notebook, this.events);
42 42 } catch (e) {
43 43 this.tour = undefined;
44 44 console.log("Failed to instantiate Notebook Tour", e);
45 45 }
46 46
47 47 if (this.selector !== undefined) {
48 48 this.element = $(selector);
49 49 this.style();
50 50 this.bind_events();
51 51 }
52 52 };
53 53
54 54 // TODO: This has definitively nothing to do with style ...
55 55 MenuBar.prototype.style = function () {
56 56 var that = this;
57 57 this.element.find("li").click(function (event, ui) {
58 58 // The selected cell loses focus when the menu is entered, so we
59 59 // re-select it upon selection.
60 60 var i = that.notebook.get_selected_index();
61 61 that.notebook.select(i);
62 62 }
63 63 );
64 64 };
65 65
66 66 MenuBar.prototype._nbconvert = function (format, download) {
67 67 download = download || false;
68 68 var notebook_path = this.notebook.notebook_path;
69 69 var notebook_name = this.notebook.notebook_name;
70 70 if (this.notebook.dirty) {
71 71 this.notebook.save_notebook({async : false});
72 72 }
73 73 var url = utils.url_join_encode(
74 74 this.base_url,
75 75 'nbconvert',
76 76 format,
77 77 notebook_path,
78 78 notebook_name
79 79 ) + "?download=" + download.toString();
80 80
81 81 window.open(url);
82 82 };
83 83
84 84 MenuBar.prototype.bind_events = function () {
85 85 // File
86 86 var that = this;
87 87 this.element.find('#new_notebook').click(function () {
88 88 that.notebook.new_notebook();
89 89 });
90 90 this.element.find('#open_notebook').click(function () {
91 91 window.open(utils.url_join_encode(
92 92 that.notebook.base_url,
93 93 'tree',
94 94 that.notebook.notebook_path
95 95 ));
96 96 });
97 97 this.element.find('#copy_notebook').click(function () {
98 98 that.notebook.copy_notebook();
99 99 return false;
100 100 });
101 101 this.element.find('#download_ipynb').click(function () {
102 102 var base_url = that.notebook.base_url;
103 103 var notebook_path = that.notebook.notebook_path;
104 104 var notebook_name = that.notebook.notebook_name;
105 105 if (that.notebook.dirty) {
106 106 that.notebook.save_notebook({async : false});
107 107 }
108 108
109 109 var url = utils.url_join_encode(
110 110 base_url,
111 111 'files',
112 112 notebook_path,
113 113 notebook_name
114 114 );
115 window.open(url);
115 window.open(url + '?download=1');
116 116 });
117 117
118 118 this.element.find('#print_preview').click(function () {
119 119 that._nbconvert('html', false);
120 120 });
121 121
122 122 this.element.find('#download_py').click(function () {
123 123 that._nbconvert('python', true);
124 124 });
125 125
126 126 this.element.find('#download_html').click(function () {
127 127 that._nbconvert('html', true);
128 128 });
129 129
130 130 this.element.find('#download_rst').click(function () {
131 131 that._nbconvert('rst', true);
132 132 });
133 133
134 134 this.element.find('#download_pdf').click(function () {
135 135 that._nbconvert('pdf', true);
136 136 });
137 137
138 138 this.element.find('#rename_notebook').click(function () {
139 139 that.save_widget.rename_notebook({notebook: that.notebook});
140 140 });
141 141 this.element.find('#save_checkpoint').click(function () {
142 142 that.notebook.save_checkpoint();
143 143 });
144 144 this.element.find('#restore_checkpoint').click(function () {
145 145 });
146 146 this.element.find('#trust_notebook').click(function () {
147 147 that.notebook.trust_notebook();
148 148 });
149 149 this.events.on('trust_changed.Notebook', function (event, trusted) {
150 150 if (trusted) {
151 151 that.element.find('#trust_notebook')
152 152 .addClass("disabled")
153 153 .find("a").text("Trusted Notebook");
154 154 } else {
155 155 that.element.find('#trust_notebook')
156 156 .removeClass("disabled")
157 157 .find("a").text("Trust Notebook");
158 158 }
159 159 });
160 160 this.element.find('#kill_and_exit').click(function () {
161 161 var close_window = function () {
162 162 // allow closing of new tabs in Chromium, impossible in FF
163 163 window.open('', '_self', '');
164 164 window.close();
165 165 };
166 166 // finish with close on success or failure
167 167 that.notebook.session.delete(close_window, close_window);
168 168 });
169 169 // Edit
170 170 this.element.find('#cut_cell').click(function () {
171 171 that.notebook.cut_cell();
172 172 });
173 173 this.element.find('#copy_cell').click(function () {
174 174 that.notebook.copy_cell();
175 175 });
176 176 this.element.find('#delete_cell').click(function () {
177 177 that.notebook.delete_cell();
178 178 });
179 179 this.element.find('#undelete_cell').click(function () {
180 180 that.notebook.undelete_cell();
181 181 });
182 182 this.element.find('#split_cell').click(function () {
183 183 that.notebook.split_cell();
184 184 });
185 185 this.element.find('#merge_cell_above').click(function () {
186 186 that.notebook.merge_cell_above();
187 187 });
188 188 this.element.find('#merge_cell_below').click(function () {
189 189 that.notebook.merge_cell_below();
190 190 });
191 191 this.element.find('#move_cell_up').click(function () {
192 192 that.notebook.move_cell_up();
193 193 });
194 194 this.element.find('#move_cell_down').click(function () {
195 195 that.notebook.move_cell_down();
196 196 });
197 197 this.element.find('#edit_nb_metadata').click(function () {
198 198 that.notebook.edit_metadata({
199 199 notebook: that.notebook,
200 200 keyboard_manager: that.notebook.keyboard_manager});
201 201 });
202 202
203 203 // View
204 204 this.element.find('#toggle_header').click(function () {
205 205 $('div#header').toggle();
206 206 that.layout_manager.do_resize();
207 207 });
208 208 this.element.find('#toggle_toolbar').click(function () {
209 209 $('div#maintoolbar').toggle();
210 210 that.layout_manager.do_resize();
211 211 });
212 212 // Insert
213 213 this.element.find('#insert_cell_above').click(function () {
214 214 that.notebook.insert_cell_above('code');
215 215 that.notebook.select_prev();
216 216 });
217 217 this.element.find('#insert_cell_below').click(function () {
218 218 that.notebook.insert_cell_below('code');
219 219 that.notebook.select_next();
220 220 });
221 221 // Cell
222 222 this.element.find('#run_cell').click(function () {
223 223 that.notebook.execute_cell();
224 224 });
225 225 this.element.find('#run_cell_select_below').click(function () {
226 226 that.notebook.execute_cell_and_select_below();
227 227 });
228 228 this.element.find('#run_cell_insert_below').click(function () {
229 229 that.notebook.execute_cell_and_insert_below();
230 230 });
231 231 this.element.find('#run_all_cells').click(function () {
232 232 that.notebook.execute_all_cells();
233 233 });
234 234 this.element.find('#run_all_cells_above').click(function () {
235 235 that.notebook.execute_cells_above();
236 236 });
237 237 this.element.find('#run_all_cells_below').click(function () {
238 238 that.notebook.execute_cells_below();
239 239 });
240 240 this.element.find('#to_code').click(function () {
241 241 that.notebook.to_code();
242 242 });
243 243 this.element.find('#to_markdown').click(function () {
244 244 that.notebook.to_markdown();
245 245 });
246 246 this.element.find('#to_raw').click(function () {
247 247 that.notebook.to_raw();
248 248 });
249 249 this.element.find('#to_heading1').click(function () {
250 250 that.notebook.to_heading(undefined, 1);
251 251 });
252 252 this.element.find('#to_heading2').click(function () {
253 253 that.notebook.to_heading(undefined, 2);
254 254 });
255 255 this.element.find('#to_heading3').click(function () {
256 256 that.notebook.to_heading(undefined, 3);
257 257 });
258 258 this.element.find('#to_heading4').click(function () {
259 259 that.notebook.to_heading(undefined, 4);
260 260 });
261 261 this.element.find('#to_heading5').click(function () {
262 262 that.notebook.to_heading(undefined, 5);
263 263 });
264 264 this.element.find('#to_heading6').click(function () {
265 265 that.notebook.to_heading(undefined, 6);
266 266 });
267 267
268 268 this.element.find('#toggle_current_output').click(function () {
269 269 that.notebook.toggle_output();
270 270 });
271 271 this.element.find('#toggle_current_output_scroll').click(function () {
272 272 that.notebook.toggle_output_scroll();
273 273 });
274 274 this.element.find('#clear_current_output').click(function () {
275 275 that.notebook.clear_output();
276 276 });
277 277
278 278 this.element.find('#toggle_all_output').click(function () {
279 279 that.notebook.toggle_all_output();
280 280 });
281 281 this.element.find('#toggle_all_output_scroll').click(function () {
282 282 that.notebook.toggle_all_output_scroll();
283 283 });
284 284 this.element.find('#clear_all_output').click(function () {
285 285 that.notebook.clear_all_output();
286 286 });
287 287
288 288 // Kernel
289 289 this.element.find('#int_kernel').click(function () {
290 290 that.notebook.kernel.interrupt();
291 291 });
292 292 this.element.find('#restart_kernel').click(function () {
293 293 that.notebook.restart_kernel();
294 294 });
295 295 // Help
296 296 if (this.tour) {
297 297 this.element.find('#notebook_tour').click(function () {
298 298 that.tour.start();
299 299 });
300 300 } else {
301 301 this.element.find('#notebook_tour').addClass("disabled");
302 302 }
303 303 this.element.find('#keyboard_shortcuts').click(function () {
304 304 that.quick_help.show_keyboard_shortcuts();
305 305 });
306 306
307 307 this.update_restore_checkpoint(null);
308 308
309 309 this.events.on('checkpoints_listed.Notebook', function (event, data) {
310 310 that.update_restore_checkpoint(that.notebook.checkpoints);
311 311 });
312 312
313 313 this.events.on('checkpoint_created.Notebook', function (event, data) {
314 314 that.update_restore_checkpoint(that.notebook.checkpoints);
315 315 });
316 316 };
317 317
318 318 MenuBar.prototype.update_restore_checkpoint = function(checkpoints) {
319 319 var ul = this.element.find("#restore_checkpoint").find("ul");
320 320 ul.empty();
321 321 if (!checkpoints || checkpoints.length === 0) {
322 322 ul.append(
323 323 $("<li/>")
324 324 .addClass("disabled")
325 325 .append(
326 326 $("<a/>")
327 327 .text("No checkpoints")
328 328 )
329 329 );
330 330 return;
331 331 }
332 332
333 333 var that = this;
334 334 checkpoints.map(function (checkpoint) {
335 335 var d = new Date(checkpoint.last_modified);
336 336 ul.append(
337 337 $("<li/>").append(
338 338 $("<a/>")
339 339 .attr("href", "#")
340 340 .text(moment(d).format("LLLL"))
341 341 .click(function () {
342 342 that.notebook.restore_checkpoint_dialog(checkpoint);
343 343 })
344 344 )
345 345 );
346 346 });
347 347 };
348 348
349 349 // Backwards compatability.
350 350 IPython.MenuBar = MenuBar;
351 351
352 352 return {'MenuBar': MenuBar};
353 353 });
@@ -1,135 +1,151 b''
1 1 # coding: utf-8
2 2 """Test the /files/ handler."""
3 3
4 4 import io
5 5 import os
6 6 from unicodedata import normalize
7 7
8 8 pjoin = os.path.join
9 9
10 10 import requests
11 11 import json
12 12
13 13 from IPython.nbformat.current import (new_notebook, write, new_worksheet,
14 14 new_heading_cell, new_code_cell,
15 15 new_output)
16 16
17 17 from IPython.html.utils import url_path_join
18 18 from .launchnotebook import NotebookTestBase
19 19 from IPython.utils import py3compat
20 20
21 21
22 22 class FilesTest(NotebookTestBase):
23 23 def test_hidden_files(self):
24 24 not_hidden = [
25 25 u'Γ₯ b',
26 26 u'Γ₯ b/Γ§. d',
27 27 ]
28 28 hidden = [
29 29 u'.Γ₯ b',
30 30 u'Γ₯ b/.Γ§ d',
31 31 ]
32 32 dirs = not_hidden + hidden
33 33
34 34 nbdir = self.notebook_dir.name
35 35 for d in dirs:
36 36 path = pjoin(nbdir, d.replace('/', os.sep))
37 37 if not os.path.exists(path):
38 38 os.mkdir(path)
39 39 with open(pjoin(path, 'foo'), 'w') as f:
40 40 f.write('foo')
41 41 with open(pjoin(path, '.foo'), 'w') as f:
42 42 f.write('.foo')
43 43 url = self.base_url()
44 44
45 45 for d in not_hidden:
46 46 path = pjoin(nbdir, d.replace('/', os.sep))
47 47 r = requests.get(url_path_join(url, 'files', d, 'foo'))
48 48 r.raise_for_status()
49 49 self.assertEqual(r.text, 'foo')
50 50 r = requests.get(url_path_join(url, 'files', d, '.foo'))
51 51 self.assertEqual(r.status_code, 404)
52 52
53 53 for d in hidden:
54 54 path = pjoin(nbdir, d.replace('/', os.sep))
55 55 for foo in ('foo', '.foo'):
56 56 r = requests.get(url_path_join(url, 'files', d, foo))
57 57 self.assertEqual(r.status_code, 404)
58 58
59 59 def test_contents_manager(self):
60 60 "make sure ContentsManager returns right files (ipynb, bin, txt)."
61 61
62 62 nbdir = self.notebook_dir.name
63 63 base = self.base_url()
64 64
65 65 nb = new_notebook(name='testnb')
66 66
67 67 ws = new_worksheet()
68 68 nb.worksheets = [ws]
69 69 ws.cells.append(new_heading_cell(u'Created by test Β³'))
70 70 cc1 = new_code_cell(input=u'print(2*6)')
71 71 cc1.outputs.append(new_output(output_text=u'12', output_type='stream'))
72 72 ws.cells.append(cc1)
73 73
74 74 with io.open(pjoin(nbdir, 'testnb.ipynb'), 'w',
75 75 encoding='utf-8') as f:
76 76 write(nb, f, format='ipynb')
77 77
78 78 with io.open(pjoin(nbdir, 'test.bin'), 'wb') as f:
79 79 f.write(b'\xff' + os.urandom(5))
80 80 f.close()
81 81
82 82 with io.open(pjoin(nbdir, 'test.txt'), 'w') as f:
83 83 f.write(u'foobar')
84 84 f.close()
85 85
86 86 r = requests.get(url_path_join(base, 'files', 'testnb.ipynb'))
87 87 self.assertEqual(r.status_code, 200)
88 88 self.assertIn('print(2*6)', r.text)
89 89 json.loads(r.text)
90 90
91 91 r = requests.get(url_path_join(base, 'files', 'test.bin'))
92 92 self.assertEqual(r.status_code, 200)
93 93 self.assertEqual(r.headers['content-type'], 'application/octet-stream')
94 94 self.assertEqual(r.content[:1], b'\xff')
95 95 self.assertEqual(len(r.content), 6)
96 96
97 97 r = requests.get(url_path_join(base, 'files', 'test.txt'))
98 98 self.assertEqual(r.status_code, 200)
99 99 self.assertEqual(r.headers['content-type'], 'text/plain')
100 100 self.assertEqual(r.text, 'foobar')
101
102 def test_download(self):
103 nbdir = self.notebook_dir.name
104 base = self.base_url()
105
106 text = 'hello'
107 with open(pjoin(nbdir, 'test.txt'), 'w') as f:
108 f.write(text)
109
110 r = requests.get(url_path_join(base, 'files', 'test.txt'))
111 disposition = r.headers.get('Content-Disposition', '')
112 self.assertNotIn('attachment', disposition)
101 113
114 r = requests.get(url_path_join(base, 'files', 'test.txt') + '?download=1')
115 disposition = r.headers.get('Content-Disposition', '')
116 self.assertIn('attachment', disposition)
117 self.assertIn('filename="test.txt"', disposition)
102 118
103 119 def test_old_files_redirect(self):
104 120 """pre-2.0 'files/' prefixed links are properly redirected"""
105 121 nbdir = self.notebook_dir.name
106 122 base = self.base_url()
107 123
108 124 os.mkdir(pjoin(nbdir, 'files'))
109 125 os.makedirs(pjoin(nbdir, 'sub', 'files'))
110 126
111 127 for prefix in ('', 'sub'):
112 128 with open(pjoin(nbdir, prefix, 'files', 'f1.txt'), 'w') as f:
113 129 f.write(prefix + '/files/f1')
114 130 with open(pjoin(nbdir, prefix, 'files', 'f2.txt'), 'w') as f:
115 131 f.write(prefix + '/files/f2')
116 132 with open(pjoin(nbdir, prefix, 'f2.txt'), 'w') as f:
117 133 f.write(prefix + '/f2')
118 134 with open(pjoin(nbdir, prefix, 'f3.txt'), 'w') as f:
119 135 f.write(prefix + '/f3')
120 136
121 137 url = url_path_join(base, 'notebooks', prefix, 'files', 'f1.txt')
122 138 r = requests.get(url)
123 139 self.assertEqual(r.status_code, 200)
124 140 self.assertEqual(r.text, prefix + '/files/f1')
125 141
126 142 url = url_path_join(base, 'notebooks', prefix, 'files', 'f2.txt')
127 143 r = requests.get(url)
128 144 self.assertEqual(r.status_code, 200)
129 145 self.assertEqual(r.text, prefix + '/files/f2')
130 146
131 147 url = url_path_join(base, 'notebooks', prefix, 'files', 'f3.txt')
132 148 r = requests.get(url)
133 149 self.assertEqual(r.status_code, 200)
134 150 self.assertEqual(r.text, prefix + '/f3')
135 151
General Comments 0
You need to be logged in to leave comments. Login now