| 1 | # This file is part of the OLD Sage notebook and is NOT actively developed, |
|---|
| 2 | # maintained, or supported. As of Sage v4.1.2, all notebook development has |
|---|
| 3 | # moved to the separate Sage Notebook project: |
|---|
| 4 | # |
|---|
| 5 | # http://nb.sagemath.org/ |
|---|
| 6 | # |
|---|
| 7 | # The new notebook is installed in Sage as an spkg (e.g., sagenb-0.3.spkg). |
|---|
| 8 | # |
|---|
| 9 | # Please visit the project's home page for more information, including directions on |
|---|
| 10 | # obtaining the latest source code. For notebook-related development and support, |
|---|
| 11 | # please consult the sage-notebook discussion group: |
|---|
| 12 | # |
|---|
| 13 | # http://groups.google.com/group/sage-notebook |
|---|
| 14 | r""" |
|---|
| 15 | The Sage Notebook Twisted Web Server |
|---|
| 16 | |
|---|
| 17 | TESTS: |
|---|
| 18 | |
|---|
| 19 | It is important that this file never be imported by default on |
|---|
| 20 | startup by Sage, since it is very expensive, since importing Twisted |
|---|
| 21 | is expensive. This doctest verifies that twist.py isn't imported on |
|---|
| 22 | startup. |
|---|
| 23 | |
|---|
| 24 | :: |
|---|
| 25 | |
|---|
| 26 | sage: os.system("sage -startuptime | grep twisted\.web2 1>/dev/null") != 0 # !=0 means not found |
|---|
| 27 | True |
|---|
| 28 | """ |
|---|
| 29 | |
|---|
| 30 | ############################################################################# |
|---|
| 31 | # Copyright (C) 2007 William Stein <wstein@gmail.com> |
|---|
| 32 | # Distributed under the terms of the GNU General Public License (GPL) |
|---|
| 33 | # The full text of the GPL is available at: |
|---|
| 34 | # http://www.gnu.org/licenses/ |
|---|
| 35 | ############################################################################# |
|---|
| 36 | |
|---|
| 37 | ############################################################ |
|---|
| 38 | # WARNING: Potential source of confusion!! |
|---|
| 39 | # |
|---|
| 40 | # The following three global variables get set on |
|---|
| 41 | # startup by the script that is generated by run_notebook.py |
|---|
| 42 | |
|---|
| 43 | notebook = None |
|---|
| 44 | OPEN_MODE = None |
|---|
| 45 | SID_COOKIE = None |
|---|
| 46 | |
|---|
| 47 | ############################################################ |
|---|
| 48 | |
|---|
| 49 | import os, shutil, time |
|---|
| 50 | import bz2 |
|---|
| 51 | from cgi import escape |
|---|
| 52 | |
|---|
| 53 | from twisted.web2 import http, resource |
|---|
| 54 | from twisted.web2 import static, http_headers |
|---|
| 55 | from twisted.web2.filter import gzip |
|---|
| 56 | import zipfile |
|---|
| 57 | |
|---|
| 58 | import css, js, keyboards |
|---|
| 59 | |
|---|
| 60 | |
|---|
| 61 | from sage.server.notebook.template import template |
|---|
| 62 | |
|---|
| 63 | HISTORY_MAX_OUTPUT = 92*5 |
|---|
| 64 | HISTORY_NCOLS = 90 |
|---|
| 65 | |
|---|
| 66 | from sage.misc.misc import SAGE_EXTCODE, SAGE_LOCAL, SAGE_DOC, walltime, tmp_filename, tmp_dir |
|---|
| 67 | |
|---|
| 68 | p = os.path.join |
|---|
| 69 | css_path = p(SAGE_EXTCODE, "notebook/css") |
|---|
| 70 | image_path = p(SAGE_EXTCODE, "notebook/images") |
|---|
| 71 | javascript_path = p(SAGE_EXTCODE, "notebook/javascript") |
|---|
| 72 | javascript_local_path = p(SAGE_LOCAL, "notebook/javascript") |
|---|
| 73 | java_path = p(SAGE_LOCAL, "java") |
|---|
| 74 | |
|---|
| 75 | # the list of users waiting to register |
|---|
| 76 | waiting = {} |
|---|
| 77 | |
|---|
| 78 | # the user database |
|---|
| 79 | from user_db import UserDatabase |
|---|
| 80 | users = UserDatabase() |
|---|
| 81 | |
|---|
| 82 | _cols = None |
|---|
| 83 | def word_wrap_cols(): |
|---|
| 84 | global _cols |
|---|
| 85 | if _cols is None: |
|---|
| 86 | _cols = notebook.conf()['word_wrap_cols'] |
|---|
| 87 | return _cols |
|---|
| 88 | |
|---|
| 89 | ############################ |
|---|
| 90 | # Encoding data to go between the server and client |
|---|
| 91 | ############################ |
|---|
| 92 | SEP = '___S_A_G_E___' |
|---|
| 93 | |
|---|
| 94 | def encode_list(v): |
|---|
| 95 | return SEP.join([str(x) for x in v]) |
|---|
| 96 | |
|---|
| 97 | |
|---|
| 98 | |
|---|
| 99 | ############################ |
|---|
| 100 | # Notebook autosave. |
|---|
| 101 | ############################ |
|---|
| 102 | # save if make a change to notebook and at least some seconds have elapsed since last save. |
|---|
| 103 | def init_updates(): |
|---|
| 104 | global save_interval, idle_interval, last_save_time, last_idle_time |
|---|
| 105 | |
|---|
| 106 | save_interval = notebook.conf()['save_interval'] |
|---|
| 107 | idle_interval = notebook.conf()['idle_check_interval'] |
|---|
| 108 | last_save_time = walltime() |
|---|
| 109 | last_idle_time = walltime() |
|---|
| 110 | |
|---|
| 111 | def notebook_save_check(): |
|---|
| 112 | global last_save_time |
|---|
| 113 | t = walltime() |
|---|
| 114 | if t > last_save_time + save_interval: |
|---|
| 115 | notebook.save() |
|---|
| 116 | last_save_time = t |
|---|
| 117 | |
|---|
| 118 | def notebook_idle_check(): |
|---|
| 119 | global last_idle_time |
|---|
| 120 | t = walltime() |
|---|
| 121 | if t > last_idle_time + idle_interval: |
|---|
| 122 | notebook.quit_idle_worksheet_processes() |
|---|
| 123 | last_idle_time = t |
|---|
| 124 | |
|---|
| 125 | def notebook_updates(): |
|---|
| 126 | notebook_save_check() |
|---|
| 127 | notebook_idle_check() |
|---|
| 128 | |
|---|
| 129 | ###################################################################################### |
|---|
| 130 | # RESOURCES |
|---|
| 131 | ###################################################################################### |
|---|
| 132 | def gzip_handler(request): |
|---|
| 133 | """ |
|---|
| 134 | Add gzip compression to the request if it makes sense. |
|---|
| 135 | """ |
|---|
| 136 | if request.host not in ('localhost', '127.0.0.1'): |
|---|
| 137 | request.addResponseFilter(gzip.gzipfilter, atEnd=True) |
|---|
| 138 | |
|---|
| 139 | ############################ |
|---|
| 140 | # An error message |
|---|
| 141 | ############################ |
|---|
| 142 | def message(msg, cont=None): |
|---|
| 143 | template_dict = {'msg': msg, 'cont': cont} |
|---|
| 144 | return template('error_message.html', **template_dict) |
|---|
| 145 | |
|---|
| 146 | def HTMLResponse(*args, **kwds): |
|---|
| 147 | """ |
|---|
| 148 | Returns an HTMLResponse object whose 'Content-Type' header has been set |
|---|
| 149 | to ``text/html; charset=utf-8`` |
|---|
| 150 | |
|---|
| 151 | EXAMPLES:: |
|---|
| 152 | |
|---|
| 153 | sage: from sage.server.notebook.twist import HTMLResponse |
|---|
| 154 | sage: response = HTMLResponse(stream='<html><head><title>Test</title></head><body>Test</body></html>') |
|---|
| 155 | sage: response.headers |
|---|
| 156 | <Headers: Raw: {'content-type': ['text/html; charset=utf-8']} Parsed: {'content-type': <RecalcNeeded>}> |
|---|
| 157 | |
|---|
| 158 | """ |
|---|
| 159 | response = http.Response(*args, **kwds) |
|---|
| 160 | response.headers.addRawHeader('Content-Type', 'text/html; charset=utf-8') |
|---|
| 161 | return response |
|---|
| 162 | |
|---|
| 163 | |
|---|
| 164 | ############################ |
|---|
| 165 | # Create a Sage worksheet from a latex2html'd file |
|---|
| 166 | ############################ |
|---|
| 167 | doc_worksheet_number = 0 |
|---|
| 168 | def doc_worksheet(): |
|---|
| 169 | global doc_worksheet_number |
|---|
| 170 | wnames = notebook.worksheet_names() |
|---|
| 171 | name = 'doc_browser_%s'%doc_worksheet_number |
|---|
| 172 | doc_worksheet_number = doc_worksheet_number % notebook.conf()['doc_pool_size'] |
|---|
| 173 | if name in wnames: |
|---|
| 174 | W = notebook.get_worksheet_with_name(name) |
|---|
| 175 | W.clear() |
|---|
| 176 | else: |
|---|
| 177 | W = notebook.create_new_worksheet(name, '_sage_', docbrowser=True) |
|---|
| 178 | W.set_is_doc_worksheet(True) |
|---|
| 179 | return W |
|---|
| 180 | |
|---|
| 181 | |
|---|
| 182 | class WorksheetFile(resource.Resource): |
|---|
| 183 | addSlash = False |
|---|
| 184 | |
|---|
| 185 | def __init__(self, path, username): |
|---|
| 186 | self.docpath = path |
|---|
| 187 | self.username = username |
|---|
| 188 | |
|---|
| 189 | def render(self, ctx=None): |
|---|
| 190 | # Create a live Sage worksheet out of self.path and render it. |
|---|
| 191 | if not os.path.exists(self.docpath): |
|---|
| 192 | return HTMLResponse(stream = message('Document does not exist.')) |
|---|
| 193 | |
|---|
| 194 | doc_page_html = open(self.docpath).read() |
|---|
| 195 | from docHTMLProcessor import SphinxHTMLProcessor |
|---|
| 196 | doc_page = SphinxHTMLProcessor().process_doc_html(doc_page_html) |
|---|
| 197 | |
|---|
| 198 | title = extract_title(doc_page_html).replace('—','--') |
|---|
| 199 | doc_page = title + '\nsystem:sage\n\n' + doc_page |
|---|
| 200 | |
|---|
| 201 | W = doc_worksheet() |
|---|
| 202 | W.edit_save(doc_page) |
|---|
| 203 | |
|---|
| 204 | #FIXME: For some reason, an extra cell gets added |
|---|
| 205 | #so we remove it here. |
|---|
| 206 | cells = W.cell_list() |
|---|
| 207 | cells.pop() |
|---|
| 208 | |
|---|
| 209 | s = notebook.html(worksheet_filename = W.filename(), |
|---|
| 210 | username = self.username) |
|---|
| 211 | |
|---|
| 212 | return HTMLResponse(stream=s) |
|---|
| 213 | |
|---|
| 214 | # This earlier code does not convert all reference manual pages into live |
|---|
| 215 | # worksheets, apparently: |
|---|
| 216 | # def childFactory(self, request, name): |
|---|
| 217 | # path = self.docpath + '/' + name |
|---|
| 218 | # if name.endswith('.html'): |
|---|
| 219 | # return WorksheetFile(path, self.username) |
|---|
| 220 | # else: |
|---|
| 221 | # return static.File(path) |
|---|
| 222 | |
|---|
| 223 | # Instead, we reimplement a lower-level method to detect, convert, |
|---|
| 224 | # and render live doc worksheets. We also return appropriate |
|---|
| 225 | # paths for "special" directories accessed via ../'s. See, e.g., |
|---|
| 226 | # http://twistedmatrix.com/trac/browser/trunk/doc/web2/howto/ |
|---|
| 227 | # about resources and object traversal. |
|---|
| 228 | def locateChild(self, request, segments): |
|---|
| 229 | path = os.path.join(self.docpath, *segments) |
|---|
| 230 | |
|---|
| 231 | if segments[-1].endswith('.html'): |
|---|
| 232 | return WorksheetFile(path, self.username), () |
|---|
| 233 | |
|---|
| 234 | for special in ['_static', '_sources', '_images']: |
|---|
| 235 | if special in segments: |
|---|
| 236 | ind = segments.index(special) |
|---|
| 237 | path = os.path.join(self.docpath, *segments[ind:]) |
|---|
| 238 | break |
|---|
| 239 | |
|---|
| 240 | return static.File(path), () |
|---|
| 241 | |
|---|
| 242 | |
|---|
| 243 | ############################ |
|---|
| 244 | # The documentation browsers |
|---|
| 245 | ############################ |
|---|
| 246 | |
|---|
| 247 | DOC = os.path.abspath(SAGE_DOC + '/output/html/en/') |
|---|
| 248 | DOC_PDF = os.path.abspath(SAGE_DOC + '/output/pdf') |
|---|
| 249 | DOC_REF_MEDIA = os.path.abspath(SAGE_DOC + '/en/reference/media') |
|---|
| 250 | |
|---|
| 251 | class DocPDF(resource.Resource): |
|---|
| 252 | addSlash = True |
|---|
| 253 | |
|---|
| 254 | def render(self, ctx): |
|---|
| 255 | return static.File('%s' % DOC_PDF) |
|---|
| 256 | |
|---|
| 257 | def childFactory(self, request, name): |
|---|
| 258 | gzip_handler(request) |
|---|
| 259 | return static.File('%s/%s' % (DOC_PDF, name)) |
|---|
| 260 | |
|---|
| 261 | class DocRefMedia(resource.Resource): |
|---|
| 262 | addSlash = True |
|---|
| 263 | |
|---|
| 264 | def render(self, ctx): |
|---|
| 265 | return static.File('%s' % DOC_REF_MEDIA) |
|---|
| 266 | |
|---|
| 267 | def childFactory(self, request, name): |
|---|
| 268 | gzip_handler(request) |
|---|
| 269 | return static.File('%s/%s' % (DOC_REF_MEDIA, name)) |
|---|
| 270 | |
|---|
| 271 | class DocReference(resource.Resource): |
|---|
| 272 | addSlash = True |
|---|
| 273 | child_media = DocRefMedia() |
|---|
| 274 | |
|---|
| 275 | def render(self, ctx): |
|---|
| 276 | return static.File('%s/reference/index.html' % DOC) |
|---|
| 277 | |
|---|
| 278 | def childFactory(self, request, name): |
|---|
| 279 | gzip_handler(request) |
|---|
| 280 | return static.File('%s/reference/%s' % (DOC, name)) |
|---|
| 281 | |
|---|
| 282 | class DocStatic(resource.Resource): |
|---|
| 283 | addSlash = True |
|---|
| 284 | child_reference = DocReference() |
|---|
| 285 | |
|---|
| 286 | def render(self, ctx): |
|---|
| 287 | return static.File('%s/index.html'%DOC) |
|---|
| 288 | |
|---|
| 289 | def childFactory(self, request, name): |
|---|
| 290 | gzip_handler(request) |
|---|
| 291 | return static.File('%s/%s'%(DOC,name)) |
|---|
| 292 | |
|---|
| 293 | class DocLive(resource.Resource): |
|---|
| 294 | addSlash = True |
|---|
| 295 | |
|---|
| 296 | def __init__(self, username): |
|---|
| 297 | self.username = username |
|---|
| 298 | |
|---|
| 299 | def render(self, ctx): |
|---|
| 300 | return HTMLResponse(stream=message('nothing to see.')) |
|---|
| 301 | |
|---|
| 302 | def childFactory(self, request, name): |
|---|
| 303 | gzip_handler(request) |
|---|
| 304 | return WorksheetFile('%s/%s'%(DOC,name), username = self.username) |
|---|
| 305 | |
|---|
| 306 | class Doc(resource.Resource): |
|---|
| 307 | addSlash = True |
|---|
| 308 | child_static = DocStatic() |
|---|
| 309 | |
|---|
| 310 | def __init__(self, username): |
|---|
| 311 | self.username = username |
|---|
| 312 | |
|---|
| 313 | def render(self, ctx): |
|---|
| 314 | s = notebook.html_doc(username = self.username) |
|---|
| 315 | return HTMLResponse(stream=s) |
|---|
| 316 | |
|---|
| 317 | def childFactory(self, request, name): |
|---|
| 318 | if name == "live": |
|---|
| 319 | gzip_handler(request) |
|---|
| 320 | return DocLive(username = self.username) |
|---|
| 321 | |
|---|
| 322 | |
|---|
| 323 | ############################ |
|---|
| 324 | # SageTex browser |
|---|
| 325 | ############################ |
|---|
| 326 | SAGETEX_PATH = "" |
|---|
| 327 | |
|---|
| 328 | |
|---|
| 329 | class SageTex(resource.Resource): |
|---|
| 330 | def __init__(self, username): |
|---|
| 331 | self.username = username |
|---|
| 332 | |
|---|
| 333 | def render(self, ctx): |
|---|
| 334 | s = notebook.html_doc(username = self.username) |
|---|
| 335 | return HTMLResponse(stream=s) |
|---|
| 336 | |
|---|
| 337 | def childFactory(self, request, name): |
|---|
| 338 | return WorksheetFile('%s/%s'%(SAGETEX_PATH,name), |
|---|
| 339 | username = self.username) |
|---|
| 340 | |
|---|
| 341 | |
|---|
| 342 | ############################ |
|---|
| 343 | # The source code browser |
|---|
| 344 | ############################ |
|---|
| 345 | |
|---|
| 346 | SRC = os.path.abspath(os.environ['SAGE_ROOT'] + '/devel/sage/sage/') |
|---|
| 347 | |
|---|
| 348 | class SourceBrowser(resource.Resource): |
|---|
| 349 | addSlash = True |
|---|
| 350 | |
|---|
| 351 | def __init__(self, username): |
|---|
| 352 | self.username = username |
|---|
| 353 | |
|---|
| 354 | def render(self, ctx): |
|---|
| 355 | return static.File(SRC) |
|---|
| 356 | |
|---|
| 357 | def childFactory(self, request, name): |
|---|
| 358 | return Source('%s/%s'%(SRC,name), self.username) |
|---|
| 359 | |
|---|
| 360 | class Source(resource.Resource): |
|---|
| 361 | addSlash = True |
|---|
| 362 | |
|---|
| 363 | def __init__(self, path, username): |
|---|
| 364 | self.path = path |
|---|
| 365 | self.username = username |
|---|
| 366 | |
|---|
| 367 | def render(self, ctx): |
|---|
| 368 | filename = self.path |
|---|
| 369 | if os.path.isfile(filename): |
|---|
| 370 | if not os.path.exists(filename): |
|---|
| 371 | src = "No such file '%s'"%filename |
|---|
| 372 | else: |
|---|
| 373 | src = open(filename).read() |
|---|
| 374 | src = escape(src) |
|---|
| 375 | return HTMLResponse(stream = template('source_code.html', src_filename=self.path, src=src, username=self.username)) |
|---|
| 376 | else: |
|---|
| 377 | return static.File(filename) |
|---|
| 378 | |
|---|
| 379 | def childFactory(self, request, name): |
|---|
| 380 | return Source(self.path + '/' + name, self.username) |
|---|
| 381 | |
|---|
| 382 | |
|---|
| 383 | ############################ |
|---|
| 384 | # A New Worksheet |
|---|
| 385 | ############################ |
|---|
| 386 | class NewWorksheet(resource.Resource): |
|---|
| 387 | def __init__(self, username): |
|---|
| 388 | self.username = username |
|---|
| 389 | |
|---|
| 390 | def render(self, ctx): |
|---|
| 391 | W = notebook.create_new_worksheet("Untitled", self.username) |
|---|
| 392 | return http.RedirectResponse('/home/'+W.filename()) |
|---|
| 393 | |
|---|
| 394 | |
|---|
| 395 | ############################ |
|---|
| 396 | # Uploading a saved worksheet file |
|---|
| 397 | ############################ |
|---|
| 398 | |
|---|
| 399 | def redirect(url): |
|---|
| 400 | return '<html><head><meta http-equiv="REFRESH" content="0; URL=%s"></head></html>'%url |
|---|
| 401 | |
|---|
| 402 | class Upload(resource.Resource): |
|---|
| 403 | def __init__(self, username): |
|---|
| 404 | self.username = username |
|---|
| 405 | |
|---|
| 406 | def render(self, ctx): |
|---|
| 407 | return HTMLResponse(stream = template('upload.html', username=self.username)) |
|---|
| 408 | |
|---|
| 409 | class UploadWorksheet(resource.PostableResource): |
|---|
| 410 | def __init__(self, username): |
|---|
| 411 | self.username = username |
|---|
| 412 | |
|---|
| 413 | def render(self, ctx): |
|---|
| 414 | url = ctx.args['urlField'][0].strip() |
|---|
| 415 | dir = '' # we will delete the directory below if it is used |
|---|
| 416 | if url != '': |
|---|
| 417 | # downloading a file from the internet |
|---|
| 418 | filename = tmp_filename()+".sws" |
|---|
| 419 | else: |
|---|
| 420 | # uploading a file from the user's computer |
|---|
| 421 | dir = tmp_dir() |
|---|
| 422 | filename = ctx.files['fileField'][0][0] |
|---|
| 423 | # Make tmp file in Sage temp directory |
|---|
| 424 | filename = '%s/%s'%(dir, filename) |
|---|
| 425 | f = file(filename,'wb') |
|---|
| 426 | # Then download to that file. |
|---|
| 427 | f.write(ctx.files['fileField'][0][2].read()) |
|---|
| 428 | # TODO: Server blocking issues (?!) |
|---|
| 429 | f.close() |
|---|
| 430 | |
|---|
| 431 | |
|---|
| 432 | #We make a callback so that we can download a file remotely |
|---|
| 433 | #while allowing the server to still serve requests. |
|---|
| 434 | def callback(result): |
|---|
| 435 | |
|---|
| 436 | if ctx.args.has_key('nameField'): |
|---|
| 437 | new_name = ctx.args['nameField'][0].strip() |
|---|
| 438 | else: |
|---|
| 439 | new_name = None |
|---|
| 440 | |
|---|
| 441 | try: |
|---|
| 442 | try: |
|---|
| 443 | |
|---|
| 444 | if filename.endswith('.zip'): |
|---|
| 445 | # Extract all the .sws files from a zip file. |
|---|
| 446 | zip_file = zipfile.ZipFile(filename) |
|---|
| 447 | sws_file = "%s/%s" % (dir, "tmp.sws") |
|---|
| 448 | for sws in zip_file.namelist(): |
|---|
| 449 | if sws.endswith('.sws'): |
|---|
| 450 | open(sws_file, 'w').write(zip_file.read(sws)) # 2.6 zip_file.extract(sws, sws_file) |
|---|
| 451 | W = notebook.import_worksheet(sws_file, self.username) |
|---|
| 452 | if new_name: |
|---|
| 453 | W.set_name("%s - %s" % (new_name, W.name())) |
|---|
| 454 | return http.RedirectResponse('/') |
|---|
| 455 | |
|---|
| 456 | else: |
|---|
| 457 | W = notebook.import_worksheet(filename, self.username) |
|---|
| 458 | |
|---|
| 459 | except IOError, msg: |
|---|
| 460 | print msg |
|---|
| 461 | raise ValueError, "Unfortunately, there was an error uploading the worksheet. It could be an old unsupported format or worse. If you desperately need its contents contact the Google group sage-support and post a link to your worksheet. Alternatively, an sws file is just a bzip2'd tarball; take a look inside!" |
|---|
| 462 | finally: |
|---|
| 463 | # Clean up the temporarily uploaded filename. |
|---|
| 464 | os.unlink(filename) |
|---|
| 465 | # if a temp directory was created, we delete it now. |
|---|
| 466 | if dir: |
|---|
| 467 | shutil.rmtree(dir) |
|---|
| 468 | |
|---|
| 469 | except ValueError, msg: |
|---|
| 470 | s = "Error uploading worksheet '%s'."%msg |
|---|
| 471 | return HTMLResponse(stream = message(s, '/')) |
|---|
| 472 | |
|---|
| 473 | # If the user requested in the form a specific title for |
|---|
| 474 | # the worksheet set it. |
|---|
| 475 | if new_name: |
|---|
| 476 | W.set_name(new_name) |
|---|
| 477 | |
|---|
| 478 | return http.RedirectResponse('/home/'+W.filename()) |
|---|
| 479 | |
|---|
| 480 | if url != '': |
|---|
| 481 | #We use the downloadPage function which returns a |
|---|
| 482 | #deferred which we are allowed to return to the server. |
|---|
| 483 | #The server waits until the download is finished and then runs |
|---|
| 484 | #the callback function specified. |
|---|
| 485 | from twisted.web.client import downloadPage |
|---|
| 486 | d = downloadPage(url, filename) |
|---|
| 487 | d.addCallback(callback) |
|---|
| 488 | return d |
|---|
| 489 | else: |
|---|
| 490 | #If we already have the file, then we |
|---|
| 491 | #can just return the result of callback which will |
|---|
| 492 | #give us the HTMLResponse. |
|---|
| 493 | return callback(None) |
|---|
| 494 | |
|---|
| 495 | |
|---|
| 496 | ############################ |
|---|
| 497 | # A resource attached to a given worksheet. |
|---|
| 498 | # |
|---|
| 499 | # This has the name of the worksheet and the |
|---|
| 500 | # worksheet object itself set as attributes. |
|---|
| 501 | # It's much better to do it once-for-all here |
|---|
| 502 | # instead of doing it in the derived classes |
|---|
| 503 | # over and over again. |
|---|
| 504 | ############################ |
|---|
| 505 | class WorksheetResource: |
|---|
| 506 | def __init__(self, name, username): |
|---|
| 507 | self.name = name |
|---|
| 508 | self.username = username |
|---|
| 509 | self.worksheet = notebook.get_worksheet_with_filename(name) |
|---|
| 510 | if not self.worksheet.is_published(): |
|---|
| 511 | self.worksheet.set_active(username) |
|---|
| 512 | owner = self.worksheet.owner() |
|---|
| 513 | if owner != '_sage_' and username != owner: |
|---|
| 514 | if not self.worksheet.is_published(): |
|---|
| 515 | if not username in self.worksheet.collaborators() and user_type(username) != 'admin': |
|---|
| 516 | raise RuntimeError, "illegal worksheet access" |
|---|
| 517 | |
|---|
| 518 | def id(self, ctx): |
|---|
| 519 | return int(ctx.args['id'][0]) |
|---|
| 520 | |
|---|
| 521 | |
|---|
| 522 | ############################################### |
|---|
| 523 | # Worksheet data -- a file that |
|---|
| 524 | # is associated with a cell in some worksheet. |
|---|
| 525 | # The file is stored on the filesystem. |
|---|
| 526 | # /home/worksheet_name/data/cell_number/filename |
|---|
| 527 | ############################################## |
|---|
| 528 | class Worksheet_savedatafile(WorksheetResource, resource.PostableResource): |
|---|
| 529 | def render(self, ctx): |
|---|
| 530 | if ctx.args.has_key('button_save'): |
|---|
| 531 | E = ctx.args['textfield'][0] |
|---|
| 532 | filename = ctx.args['filename'][0] |
|---|
| 533 | dest = '%s/%s'%(self.worksheet.data_directory(), filename) |
|---|
| 534 | open(dest,'w').write(E) |
|---|
| 535 | return http.RedirectResponse('/home/'+self.worksheet.filename()) |
|---|
| 536 | |
|---|
| 537 | class Worksheet_link_datafile(WorksheetResource, resource.Resource): |
|---|
| 538 | def render(self, ctx): |
|---|
| 539 | target_worksheet_filename = ctx.args['target'][0] |
|---|
| 540 | data_filename = ctx.args['filename'][0] |
|---|
| 541 | src = os.path.abspath(self.worksheet.data_directory() + '/' +data_filename) |
|---|
| 542 | target_ws = notebook.get_worksheet_with_filename(target_worksheet_filename) |
|---|
| 543 | target = os.path.abspath(target_ws.data_directory() + '/' + data_filename) |
|---|
| 544 | if target_ws.owner() != self.username and not target_ws.user_is_collaborator(self.username): |
|---|
| 545 | return HTMLResponse(stream=message("illegal link attempt!")) |
|---|
| 546 | os.system('ln "%s" "%s"'%(src, target)) |
|---|
| 547 | return http.RedirectResponse('/home/'+target_ws.filename() + '/datafile?name=%s'%data_filename) |
|---|
| 548 | |
|---|
| 549 | |
|---|
| 550 | class Worksheet_upload_data(WorksheetResource, resource.Resource): |
|---|
| 551 | def render(self, ctx): |
|---|
| 552 | return HTMLResponse(stream = notebook.html_upload_data_window(self.worksheet, self.username)) |
|---|
| 553 | |
|---|
| 554 | class Worksheet_do_upload_data(WorksheetResource, resource.PostableResource): |
|---|
| 555 | def render(self, ctx): |
|---|
| 556 | name = '' |
|---|
| 557 | if ctx.args.has_key('newField'): |
|---|
| 558 | newfield = ctx.args['newField'][0].strip() |
|---|
| 559 | else: |
|---|
| 560 | newfield = None |
|---|
| 561 | |
|---|
| 562 | if ctx.args.has_key('nameField'): |
|---|
| 563 | name = ctx.args['nameField'][0].strip() |
|---|
| 564 | |
|---|
| 565 | url = ctx.args['urlField'][0].strip() |
|---|
| 566 | |
|---|
| 567 | if not name: |
|---|
| 568 | name = ctx.files['fileField'][0][0] |
|---|
| 569 | |
|---|
| 570 | if not name: |
|---|
| 571 | name = newfield |
|---|
| 572 | |
|---|
| 573 | if url and not name: |
|---|
| 574 | name = os.path.split(url)[-1] |
|---|
| 575 | |
|---|
| 576 | dest = '%s/%s'%(self.worksheet.data_directory(), name) |
|---|
| 577 | response = http.RedirectResponse('/home/'+self.worksheet.filename() + '/datafile?name=%s'%name) |
|---|
| 578 | |
|---|
| 579 | if url != '': |
|---|
| 580 | #Here we use twisted's downloadPage function which |
|---|
| 581 | #returns a deferred object. We return the deferred to the server, |
|---|
| 582 | #and it will wait until the download has finished while |
|---|
| 583 | #still serving other requests. At the end of the deferred |
|---|
| 584 | #callback chain should be the response that we wanted to return. |
|---|
| 585 | from twisted.web.client import downloadPage |
|---|
| 586 | |
|---|
| 587 | #The callback just returns the response |
|---|
| 588 | def callback(result): |
|---|
| 589 | return response |
|---|
| 590 | |
|---|
| 591 | d = downloadPage(url, dest) |
|---|
| 592 | d.addCallback(callback) |
|---|
| 593 | return d |
|---|
| 594 | elif newfield: |
|---|
| 595 | open(dest,'w').close() |
|---|
| 596 | return response |
|---|
| 597 | else: |
|---|
| 598 | f = file(dest,'wb') |
|---|
| 599 | f.write(ctx.files['fileField'][0][2].read()) |
|---|
| 600 | f.close() |
|---|
| 601 | return response |
|---|
| 602 | |
|---|
| 603 | |
|---|
| 604 | ############################################## |
|---|
| 605 | # Download or delete a data file |
|---|
| 606 | ############################################## |
|---|
| 607 | |
|---|
| 608 | class Worksheet_datafile(WorksheetResource, resource.Resource): |
|---|
| 609 | def render(self, ctx): |
|---|
| 610 | dir = os.path.abspath(self.worksheet.data_directory()) |
|---|
| 611 | filename = ctx.args['name'][0] |
|---|
| 612 | if ctx.args.has_key('action'): |
|---|
| 613 | if ctx.args['action'][0] == 'delete': |
|---|
| 614 | path = '%s/%s'%(self.worksheet.data_directory(), filename) |
|---|
| 615 | os.unlink(path) |
|---|
| 616 | return HTMLResponse(stream = message("Successfully deleted '%s'"%filename, |
|---|
| 617 | '/home/' + self.worksheet.filename())) |
|---|
| 618 | s = notebook.html_download_or_delete_datafile(self.worksheet, self.username, filename) |
|---|
| 619 | return HTMLResponse(stream=s) |
|---|
| 620 | |
|---|
| 621 | ############################################## |
|---|
| 622 | # Returns an object in the datafile directory |
|---|
| 623 | ############################################## |
|---|
| 624 | class Worksheet_data(WorksheetResource, resource.Resource): |
|---|
| 625 | addSlash = True |
|---|
| 626 | |
|---|
| 627 | def render(self, ctx): |
|---|
| 628 | dir = os.path.abspath(self.worksheet.data_directory()) |
|---|
| 629 | if os.path.exists(dir): |
|---|
| 630 | return static.File(dir) |
|---|
| 631 | else: |
|---|
| 632 | return HTMLResponse(stream = message("No data files",'..')) |
|---|
| 633 | |
|---|
| 634 | def childFactory(self, request, name): |
|---|
| 635 | dir = os.path.abspath(self.worksheet.data_directory()) |
|---|
| 636 | return static.File('%s/%s'%(dir, name)) |
|---|
| 637 | |
|---|
| 638 | |
|---|
| 639 | class CellData(resource.Resource): |
|---|
| 640 | def __init__(self, worksheet, number): |
|---|
| 641 | self.worksheet = worksheet |
|---|
| 642 | self.number = number |
|---|
| 643 | |
|---|
| 644 | def render(self, ctx): |
|---|
| 645 | return HTMLResponse(stream = message("No data file (%s)"%self.number,'..')) |
|---|
| 646 | |
|---|
| 647 | def childFactory(self, request, name): |
|---|
| 648 | dir = self.worksheet.directory() |
|---|
| 649 | path = '%s/cells/%s/%s'%(dir, self.number, name) |
|---|
| 650 | request.setLastModified(os.stat(filename).st_mtime) |
|---|
| 651 | return static.File(path) |
|---|
| 652 | |
|---|
| 653 | |
|---|
| 654 | class Worksheet_cells(WorksheetResource, resource.Resource): |
|---|
| 655 | addSlash = True |
|---|
| 656 | |
|---|
| 657 | def render(self, ctx): |
|---|
| 658 | return static.File(self.worksheet.cells_directory()) |
|---|
| 659 | |
|---|
| 660 | def childFactory(self, request, segment): |
|---|
| 661 | return static.File(self.worksheet.cells_directory() + segment) |
|---|
| 662 | #return CellData(self.worksheet, segment) |
|---|
| 663 | |
|---|
| 664 | |
|---|
| 665 | |
|---|
| 666 | |
|---|
| 667 | ######################################################## |
|---|
| 668 | # Use this to wrap a worksheet operation in a confirmation |
|---|
| 669 | # request. See WorksheetDelete and WorksheetAdd for |
|---|
| 670 | # examples. |
|---|
| 671 | ######################################################## |
|---|
| 672 | ## class FastRedirect(resource.Resource): |
|---|
| 673 | ## def __init__(self, dest): |
|---|
| 674 | ## self.dest = dest |
|---|
| 675 | ## def render(self, ctx): |
|---|
| 676 | ## return http.RedirectResponse(self.dest) |
|---|
| 677 | ## class YesNo(resource.Resource): |
|---|
| 678 | ## addSlash = True |
|---|
| 679 | |
|---|
| 680 | ## def __init__(self, mesg, yes, no): |
|---|
| 681 | ## self.mesg = mesg |
|---|
| 682 | ## self.yes = yes |
|---|
| 683 | ## self.no = no |
|---|
| 684 | |
|---|
| 685 | ## def render(self, ctx): |
|---|
| 686 | ## from sage.server.notebook.template import yes_no_template |
|---|
| 687 | ## lt = yes_no_template(mesg=self.mesg) |
|---|
| 688 | ## return HTMLResponse(stream = lt) |
|---|
| 689 | |
|---|
| 690 | ## s = '%s<br>'%self.mesg |
|---|
| 691 | ## s += '<a href="yes">Yes</a> or <a href="no">No</a>' |
|---|
| 692 | ## return HTMLResponse(stream = message(s)) |
|---|
| 693 | |
|---|
| 694 | ## def childFactory(self, request, op): |
|---|
| 695 | ## if op == 'yes': |
|---|
| 696 | ## return FastRedirect(self.yes()) |
|---|
| 697 | ## elif op == 'no': |
|---|
| 698 | ## return FastRedirect(self.no()) |
|---|
| 699 | |
|---|
| 700 | |
|---|
| 701 | ######################################################## |
|---|
| 702 | # keep alive |
|---|
| 703 | ######################################################## |
|---|
| 704 | |
|---|
| 705 | class Worksheet_alive(WorksheetResource, resource.Resource): |
|---|
| 706 | def render(self, ctx): |
|---|
| 707 | #self.worksheet.ping(self.username) |
|---|
| 708 | self.worksheet.ping(username=None) # None so doesn't save a revision |
|---|
| 709 | return HTMLResponse(stream = '') |
|---|
| 710 | |
|---|
| 711 | ######################################################## |
|---|
| 712 | # Worksheet configuration. |
|---|
| 713 | ######################################################## |
|---|
| 714 | class Worksheet_conf(WorksheetResource, resource.Resource): |
|---|
| 715 | def render(self, ctx): |
|---|
| 716 | conf = self.worksheet.conf() |
|---|
| 717 | s = str(conf) |
|---|
| 718 | # TODO: This should be a form that allows for configuring all options |
|---|
| 719 | # of a given worksheet, saves the result, |
|---|
| 720 | return HTMLResponse(stream = s) |
|---|
| 721 | |
|---|
| 722 | class TrivialResource(resource.Resource): |
|---|
| 723 | def render(self, ctx): |
|---|
| 724 | return HTMLResponse(stream="success") |
|---|
| 725 | |
|---|
| 726 | class Worksheet_system(WorksheetResource, resource.Resource): |
|---|
| 727 | def childFactory(self, request, system): |
|---|
| 728 | self.worksheet.set_system(system) |
|---|
| 729 | return TrivialResource() |
|---|
| 730 | |
|---|
| 731 | class Worksheet_pretty_print(WorksheetResource, resource.Resource): |
|---|
| 732 | def childFactory(self, request, enable): |
|---|
| 733 | self.worksheet.set_pretty_print(enable) |
|---|
| 734 | return TrivialResource() |
|---|
| 735 | |
|---|
| 736 | |
|---|
| 737 | |
|---|
| 738 | ######################################################## |
|---|
| 739 | # Cell introspection |
|---|
| 740 | ######################################################## |
|---|
| 741 | class Worksheet_introspect(WorksheetResource, resource.PostableResource): |
|---|
| 742 | """ |
|---|
| 743 | Cell introspection. This is called when the user presses the tab |
|---|
| 744 | key in the browser in order to introspect. |
|---|
| 745 | """ |
|---|
| 746 | def render(self, ctx): |
|---|
| 747 | try: |
|---|
| 748 | id = int(ctx.args['id'][0]) |
|---|
| 749 | except (KeyError,TypeError): |
|---|
| 750 | return HTMLResponse(stream = 'Error in introspection -- invalid cell id.') |
|---|
| 751 | try: |
|---|
| 752 | before_cursor = ctx.args['before_cursor'][0] |
|---|
| 753 | except KeyError: |
|---|
| 754 | before_cursor = '' |
|---|
| 755 | try: |
|---|
| 756 | after_cursor = ctx.args['after_cursor'][0] |
|---|
| 757 | except KeyError: |
|---|
| 758 | after_cursor = '' |
|---|
| 759 | C = self.worksheet.get_cell_with_id(id) |
|---|
| 760 | C.evaluate(introspect=[before_cursor, after_cursor]) |
|---|
| 761 | return HTMLResponse(stream = encode_list([C.next_id(),'introspect',id])) |
|---|
| 762 | |
|---|
| 763 | ######################################################## |
|---|
| 764 | # Edit the entire worksheet |
|---|
| 765 | ######################################################## |
|---|
| 766 | class Worksheet_edit(WorksheetResource, resource.Resource): |
|---|
| 767 | """ |
|---|
| 768 | Return a window that allows the user to edit the text of the |
|---|
| 769 | worksheet with the given filename. |
|---|
| 770 | """ |
|---|
| 771 | def render(self, ctx): |
|---|
| 772 | s = notebook.html_edit_window(self.worksheet, self.username) |
|---|
| 773 | return HTMLResponse(stream = s) |
|---|
| 774 | |
|---|
| 775 | ######################################################## |
|---|
| 776 | # Plain text log view of worksheet |
|---|
| 777 | ######################################################## |
|---|
| 778 | class Worksheet_text(WorksheetResource, resource.Resource): |
|---|
| 779 | """ |
|---|
| 780 | Return a window that allows the user to edit the text of the |
|---|
| 781 | worksheet with the given filename. |
|---|
| 782 | """ |
|---|
| 783 | def render(self, ctx): |
|---|
| 784 | s = notebook.html_plain_text_window(self.worksheet, self.username) |
|---|
| 785 | return HTMLResponse(stream = s) |
|---|
| 786 | |
|---|
| 787 | ######################################################## |
|---|
| 788 | # Copy a worksheet |
|---|
| 789 | ######################################################## |
|---|
| 790 | class Worksheet_copy(WorksheetResource, resource.PostableResource): |
|---|
| 791 | def render(self, ctx): |
|---|
| 792 | W = notebook.copy_worksheet(self.worksheet, self.username) |
|---|
| 793 | if 'no_load' in ctx.args: |
|---|
| 794 | return http.StatusResponse(200, '') |
|---|
| 795 | else: |
|---|
| 796 | return http.RedirectResponse('/home/' + W.filename()) |
|---|
| 797 | |
|---|
| 798 | ######################################################## |
|---|
| 799 | # Get a copy of a published worksheet and start editing it |
|---|
| 800 | ######################################################## |
|---|
| 801 | class Worksheet_edit_published_page(WorksheetResource, resource.Resource): |
|---|
| 802 | def render(self, ctx): |
|---|
| 803 | if user_type(self.username) == 'guest': |
|---|
| 804 | return HTMLResponse(stream = message( |
|---|
| 805 | 'You must <a href="/">login first</a> in order to edit this worksheet.')) |
|---|
| 806 | ws = self.worksheet.worksheet_that_was_published() |
|---|
| 807 | if ws.owner() == self.username: |
|---|
| 808 | W = ws |
|---|
| 809 | else: |
|---|
| 810 | W = notebook.copy_worksheet(self.worksheet, self.username) |
|---|
| 811 | W.set_name(self.worksheet.name()) |
|---|
| 812 | return http.RedirectResponse('/home/' + W.filename()) |
|---|
| 813 | |
|---|
| 814 | ######################################################## |
|---|
| 815 | # Save a worksheet |
|---|
| 816 | ######################################################## |
|---|
| 817 | class Worksheet_save(WorksheetResource, resource.PostableResource): |
|---|
| 818 | """ |
|---|
| 819 | Save the contents of a worksheet after editing it in plain-text |
|---|
| 820 | edit mode. |
|---|
| 821 | """ |
|---|
| 822 | def render(self, ctx): |
|---|
| 823 | if ctx.args.has_key('button_save'): |
|---|
| 824 | E = ctx.args['textfield'][0] |
|---|
| 825 | self.worksheet.edit_save(E) |
|---|
| 826 | self.worksheet.record_edit(self.username) |
|---|
| 827 | return http.RedirectResponse('/home/'+self.worksheet.filename()) |
|---|
| 828 | |
|---|
| 829 | |
|---|
| 830 | class Worksheet_save_snapshot(WorksheetResource, resource.PostableResource): |
|---|
| 831 | """ |
|---|
| 832 | Save a snapshot of a worksheet. |
|---|
| 833 | """ |
|---|
| 834 | def render(self, ctx): |
|---|
| 835 | self.worksheet.save_snapshot(self.username) |
|---|
| 836 | return HTMLResponse(stream="saved") |
|---|
| 837 | |
|---|
| 838 | class Worksheet_save_and_quit(WorksheetResource, resource.PostableResource): |
|---|
| 839 | """ |
|---|
| 840 | Save a snapshot of a worksheet and quit. |
|---|
| 841 | """ |
|---|
| 842 | def render(self, ctx): |
|---|
| 843 | self.worksheet.save_snapshot(self.username) |
|---|
| 844 | self.worksheet.quit() |
|---|
| 845 | return HTMLResponse(stream="saved") |
|---|
| 846 | |
|---|
| 847 | class Worksheet_discard_and_quit(WorksheetResource, resource.PostableResource): |
|---|
| 848 | """ |
|---|
| 849 | Save a snapshot of a worksheet and quit. |
|---|
| 850 | """ |
|---|
| 851 | def render(self, ctx): |
|---|
| 852 | self.worksheet.revert_to_last_saved_state() |
|---|
| 853 | self.worksheet.quit() |
|---|
| 854 | return HTMLResponse(stream="saved") |
|---|
| 855 | |
|---|
| 856 | class Worksheet_revert_to_last_saved_state(WorksheetResource, resource.PostableResource): |
|---|
| 857 | def render(self, ctx): |
|---|
| 858 | self.worksheet.revert_to_last_saved_state() |
|---|
| 859 | return HTMLResponse(stream="reverted") |
|---|
| 860 | |
|---|
| 861 | class Worksheet_save_and_close(WorksheetResource, resource.PostableResource): |
|---|
| 862 | """ |
|---|
| 863 | Save a snapshot of a worksheet then quit it. |
|---|
| 864 | """ |
|---|
| 865 | def render(self, ctx): |
|---|
| 866 | self.worksheet.save_snapshot(self.username) |
|---|
| 867 | self.worksheet.quit() |
|---|
| 868 | return HTMLResponse(stream="saved") |
|---|
| 869 | |
|---|
| 870 | ######################################################## |
|---|
| 871 | # Collaborate with others |
|---|
| 872 | ######################################################## |
|---|
| 873 | class Worksheet_share(WorksheetResource, resource.Resource): |
|---|
| 874 | def render(self, ctx): |
|---|
| 875 | s = notebook.html_share(self.worksheet, self.username) |
|---|
| 876 | return HTMLResponse(stream = s) |
|---|
| 877 | |
|---|
| 878 | class Worksheet_invite_collab(WorksheetResource, resource.PostableResource): |
|---|
| 879 | def render(self, ctx): |
|---|
| 880 | if not ctx.args.has_key('collaborators'): |
|---|
| 881 | v = [] |
|---|
| 882 | else: |
|---|
| 883 | collab = ctx.args['collaborators'][0] |
|---|
| 884 | v = [x.strip() for x in collab.split(',')] |
|---|
| 885 | self.worksheet.set_collaborators(v) |
|---|
| 886 | return http.RedirectResponse('.') |
|---|
| 887 | |
|---|
| 888 | |
|---|
| 889 | ################################# |
|---|
| 890 | # Revisions |
|---|
| 891 | ################################# |
|---|
| 892 | |
|---|
| 893 | class PublishWorksheetRevision(resource.Resource): |
|---|
| 894 | def __init__(self, worksheet, rev): |
|---|
| 895 | self.worksheet = worksheet |
|---|
| 896 | self.rev = rev |
|---|
| 897 | |
|---|
| 898 | def render(self, ctx): |
|---|
| 899 | W = notebook.publish_worksheet(self.worksheet, self.username) |
|---|
| 900 | txt = open(self.worksheet.get_snapshot_text_filename(self.rev)).read() |
|---|
| 901 | W.delete_cells_directory() |
|---|
| 902 | W.edit_save(txt) |
|---|
| 903 | return http.RedirectResponse('/home/'+W.filename()) |
|---|
| 904 | |
|---|
| 905 | class RevertToWorksheetRevision(resource.Resource): |
|---|
| 906 | def __init__(self, worksheet, rev): |
|---|
| 907 | self.worksheet = worksheet |
|---|
| 908 | self.rev = rev |
|---|
| 909 | |
|---|
| 910 | def render(self, ctx): |
|---|
| 911 | self.worksheet.save_snapshot(self.username) |
|---|
| 912 | txt = open(self.worksheet.get_snapshot_text_filename(self.rev)).read() |
|---|
| 913 | self.worksheet.delete_cells_directory() |
|---|
| 914 | self.worksheet.edit_save(txt) |
|---|
| 915 | return http.RedirectResponse('/home/'+self.worksheet.filename()) |
|---|
| 916 | |
|---|
| 917 | def worksheet_revision_publish(worksheet, rev, username): |
|---|
| 918 | W = notebook.publish_worksheet(worksheet, username) |
|---|
| 919 | txt = bz2.decompress(open(worksheet.get_snapshot_text_filename(rev)).read()) |
|---|
| 920 | W.delete_cells_directory() |
|---|
| 921 | W.edit_save(txt) |
|---|
| 922 | return http.RedirectResponse('/home/'+W.filename()) |
|---|
| 923 | |
|---|
| 924 | def worksheet_revision_revert(worksheet, rev, username): |
|---|
| 925 | worksheet.save_snapshot(username) |
|---|
| 926 | txt = bz2.decompress(open(worksheet.get_snapshot_text_filename(rev)).read()) |
|---|
| 927 | worksheet.delete_cells_directory() |
|---|
| 928 | worksheet.edit_save(txt) |
|---|
| 929 | return http.RedirectResponse('/home/'+worksheet.filename()) |
|---|
| 930 | |
|---|
| 931 | |
|---|
| 932 | class Worksheet_revisions(WorksheetResource, resource.PostableResource): |
|---|
| 933 | """ |
|---|
| 934 | Show a list of revisions of this worksheet. |
|---|
| 935 | """ |
|---|
| 936 | def render(self, ctx): |
|---|
| 937 | if not ctx.args.has_key('action'): |
|---|
| 938 | if ctx.args.has_key('rev'): |
|---|
| 939 | rev = ctx.args['rev'][0] |
|---|
| 940 | s = notebook.html_specific_revision(self.username, self.worksheet, rev) |
|---|
| 941 | else: |
|---|
| 942 | s = notebook.html_worksheet_revision_list(self.username, self.worksheet) |
|---|
| 943 | else: |
|---|
| 944 | rev = ctx.args['rev'][0] |
|---|
| 945 | action = ctx.args['action'][0] |
|---|
| 946 | if action == 'revert': |
|---|
| 947 | return worksheet_revision_revert(self.worksheet, rev, self.username) |
|---|
| 948 | elif action == 'publish': |
|---|
| 949 | return worksheet_revision_publish(self.worksheet, rev, self.username) |
|---|
| 950 | else: |
|---|
| 951 | s = message('Error') |
|---|
| 952 | return HTMLResponse(stream = s) |
|---|
| 953 | |
|---|
| 954 | |
|---|
| 955 | ######################################################## |
|---|
| 956 | # Worksheet/User/Notebooks settings and configuration |
|---|
| 957 | ######################################################## |
|---|
| 958 | |
|---|
| 959 | class Worksheet_input_settings(WorksheetResource, resource.PostableResource): |
|---|
| 960 | def render(self, ctx): |
|---|
| 961 | if ctx.args.has_key('button_cancel'): |
|---|
| 962 | return http.RedirectResponse('/home/'+self.worksheet.filename()) |
|---|
| 963 | if user_type(self.username) == 'admin' or \ |
|---|
| 964 | self.worksheet.owner() == self.username \ |
|---|
| 965 | or self.worksheet.user_is_collaborator(self.username): |
|---|
| 966 | system = ctx.args['system'][0].strip().lower() |
|---|
| 967 | self.worksheet.set_system(system) |
|---|
| 968 | if system != 'sage': |
|---|
| 969 | post = ' (%s)'%system |
|---|
| 970 | n = self.worksheet.name() |
|---|
| 971 | i = n.rfind('(') |
|---|
| 972 | if i != -1: |
|---|
| 973 | j = n.rfind(')') |
|---|
| 974 | if j != -1: |
|---|
| 975 | n = n[:i] |
|---|
| 976 | n = n.strip() + post |
|---|
| 977 | self.worksheet.set_name(n) |
|---|
| 978 | return http.RedirectResponse('/home/'+ self.worksheet.filename()) |
|---|
| 979 | else: |
|---|
| 980 | s = 'You must be the owner of this worksheet to configure it.' |
|---|
| 981 | return HTMLResponse(stream = message(s)) |
|---|
| 982 | |
|---|
| 983 | class Worksheet_settings(WorksheetResource, resource.Resource): |
|---|
| 984 | def render(self, ctx): |
|---|
| 985 | if self.worksheet.owner() != self.username: |
|---|
| 986 | s = message('You must be the owner of this worksheet to configure it.') |
|---|
| 987 | else: |
|---|
| 988 | s = notebook.html_worksheet_settings(self.worksheet, self.username) |
|---|
| 989 | return HTMLResponse(stream = s) |
|---|
| 990 | |
|---|
| 991 | class ProcessUserSettings(resource.PostableResource): |
|---|
| 992 | def render(self, ctx): |
|---|
| 993 | pass |
|---|
| 994 | |
|---|
| 995 | #class UserSettings(resource.Resource): |
|---|
| 996 | # child_process = ProcessUserSettings() |
|---|
| 997 | # |
|---|
| 998 | # def __init__(self, username): |
|---|
| 999 | # self.username = username |
|---|
| 1000 | # |
|---|
| 1001 | # def render(self, ctx): |
|---|
| 1002 | # s = notebook.html_user_settings(self.username) |
|---|
| 1003 | # return HTMLResponse(stream = s) |
|---|
| 1004 | |
|---|
| 1005 | class ProcessNotebookSettings(resource.PostableResource): |
|---|
| 1006 | def render(self, ctx): |
|---|
| 1007 | pass |
|---|
| 1008 | |
|---|
| 1009 | class NotebookSettings(resource.Resource): |
|---|
| 1010 | child_process = ProcessNotebookSettings() |
|---|
| 1011 | |
|---|
| 1012 | def __init__(self, username): |
|---|
| 1013 | self.username = username |
|---|
| 1014 | |
|---|
| 1015 | def render(self, ctx): |
|---|
| 1016 | if user_type(self.username) != 'admin': |
|---|
| 1017 | s = message('You must an admin to configure the notebook.') |
|---|
| 1018 | else: |
|---|
| 1019 | s = notebook.html_notebook_settings() |
|---|
| 1020 | return HTMLResponse(stream = s) |
|---|
| 1021 | |
|---|
| 1022 | class SettingsPage(resource.PostableResource): |
|---|
| 1023 | def __init__(self, username): |
|---|
| 1024 | self.username = username |
|---|
| 1025 | |
|---|
| 1026 | def render(self, request): |
|---|
| 1027 | error = None |
|---|
| 1028 | redirect_to_home = None |
|---|
| 1029 | redirect_to_logout = None |
|---|
| 1030 | if 'autosave' in request.args: |
|---|
| 1031 | notebook.user(self.username)['autosave_interval'] = int(request.args['autosave'][0]) * 60 |
|---|
| 1032 | redirect_to_home = True |
|---|
| 1033 | |
|---|
| 1034 | if 'Newpass' in request.args or 'RetypePass' in request.args: |
|---|
| 1035 | if not 'Oldpass' in request.args: |
|---|
| 1036 | error = 'Old password not given' |
|---|
| 1037 | elif not notebook.user(self.username).password_is(request.args['Oldpass'][0]): |
|---|
| 1038 | error = 'Incorrect password given' |
|---|
| 1039 | elif not 'Newpass' in request.args: |
|---|
| 1040 | error = 'New password not given' |
|---|
| 1041 | elif not 'RetypePass' in request.args: |
|---|
| 1042 | error = 'Please type in new password again.' |
|---|
| 1043 | elif request.args['Newpass'][0] != request.args['RetypePass'][0]: |
|---|
| 1044 | error = 'The passwords you entered do not match.' |
|---|
| 1045 | |
|---|
| 1046 | if not error: #webbrowser may auto fill in "old password" even though the user may not want to change her password |
|---|
| 1047 | notebook.change_password(self.username, request.args['Newpass'][0]) |
|---|
| 1048 | redirect_to_logout = True |
|---|
| 1049 | if notebook.conf()['email']: |
|---|
| 1050 | if 'Newemail' in request.args: |
|---|
| 1051 | notebook.user(self.username).set_email(request.args['Newemail'][0]) |
|---|
| 1052 | redirect_to_home = True |
|---|
| 1053 | |
|---|
| 1054 | if error: |
|---|
| 1055 | return HTMLResponse(stream=message(error, '/settings')) |
|---|
| 1056 | |
|---|
| 1057 | if redirect_to_logout: |
|---|
| 1058 | return http.RedirectResponse('/logout') |
|---|
| 1059 | |
|---|
| 1060 | if redirect_to_home: |
|---|
| 1061 | return http.RedirectResponse('/home/%s' % self.username) |
|---|
| 1062 | |
|---|
| 1063 | template_dict = {} |
|---|
| 1064 | template_dict['username'] = self.username |
|---|
| 1065 | template_dict['autosave_intervals'] = ((i, ' selected') if notebook.user(self.username)['autosave_interval']/60 == i else (i, '') for i in range(1, 10, 2)) |
|---|
| 1066 | template_dict['email'] = notebook.conf()['email'] |
|---|
| 1067 | if template_dict['email']: |
|---|
| 1068 | template_dict['email_address'] = 'None' if not notebook.user(self.username)._User__email else notebook.user(self.username)._User__email |
|---|
| 1069 | template_dict['email_confirmed'] = 'Not confirmed' if not notebook.user(self.username).is_email_confirmed() else 'Confirmed' |
|---|
| 1070 | template_dict['admin'] = user_type(self.username) == 'admin' |
|---|
| 1071 | return HTMLResponse(stream=template('account_settings.html', **template_dict)) |
|---|
| 1072 | |
|---|
| 1073 | ######################################################## |
|---|
| 1074 | # Set output type of a cell |
|---|
| 1075 | ######################################################## |
|---|
| 1076 | class Worksheet_set_cell_output_type(WorksheetResource, resource.PostableResource): |
|---|
| 1077 | """ |
|---|
| 1078 | Set the output type of the cell. |
|---|
| 1079 | |
|---|
| 1080 | This enables the type of output cell, such as to allowing wrapping |
|---|
| 1081 | for output that is very long. |
|---|
| 1082 | """ |
|---|
| 1083 | def render(self, ctx): |
|---|
| 1084 | id = self.id(ctx) |
|---|
| 1085 | typ = ctx.args['type'][0] |
|---|
| 1086 | W = self.worksheet |
|---|
| 1087 | W.get_cell_with_id(id).set_cell_output_type(typ) |
|---|
| 1088 | return HTMLResponse(stream = '') |
|---|
| 1089 | |
|---|
| 1090 | ######################################################## |
|---|
| 1091 | # The new cell command: /home/worksheet/new_cell?id=number |
|---|
| 1092 | ######################################################## |
|---|
| 1093 | class Worksheet_new_cell_before(WorksheetResource, resource.PostableResource): |
|---|
| 1094 | """ |
|---|
| 1095 | Adds a new cell before a given cell. |
|---|
| 1096 | """ |
|---|
| 1097 | def render(self, ctx): |
|---|
| 1098 | id = self.id(ctx) |
|---|
| 1099 | if not ctx.args.has_key('input'): |
|---|
| 1100 | input = '' |
|---|
| 1101 | else: |
|---|
| 1102 | input = ctx.args['input'][0] |
|---|
| 1103 | |
|---|
| 1104 | cell = self.worksheet.new_cell_before(id, input=input) |
|---|
| 1105 | s = encode_list([cell.id(), cell.html(div_wrap=False), id]) |
|---|
| 1106 | return HTMLResponse(stream = s) |
|---|
| 1107 | |
|---|
| 1108 | class Worksheet_new_text_cell_before(WorksheetResource, resource.PostableResource): |
|---|
| 1109 | """ |
|---|
| 1110 | Adds a new cell before a given cell. |
|---|
| 1111 | """ |
|---|
| 1112 | def render(self, ctx): |
|---|
| 1113 | id = self.id(ctx) |
|---|
| 1114 | if not ctx.args.has_key('input'): |
|---|
| 1115 | input = '' |
|---|
| 1116 | else: |
|---|
| 1117 | input = ctx.args['input'][0] |
|---|
| 1118 | |
|---|
| 1119 | cell = self.worksheet.new_text_cell_before(id, input=input) |
|---|
| 1120 | s = encode_list([cell.id(), cell.html(editing=True), id]) |
|---|
| 1121 | return HTMLResponse(stream = s) |
|---|
| 1122 | |
|---|
| 1123 | |
|---|
| 1124 | class Worksheet_new_cell_after(WorksheetResource, resource.PostableResource): |
|---|
| 1125 | """ |
|---|
| 1126 | Adds a new cell after a given cell. |
|---|
| 1127 | """ |
|---|
| 1128 | def render(self, ctx): |
|---|
| 1129 | id = self.id(ctx) |
|---|
| 1130 | if not ctx.args.has_key('input'): |
|---|
| 1131 | input = '' |
|---|
| 1132 | else: |
|---|
| 1133 | input = ctx.args['input'][0] |
|---|
| 1134 | cell = self.worksheet.new_cell_after(id, input=input) |
|---|
| 1135 | s = encode_list([cell.id(), cell.html(div_wrap=False), id]) |
|---|
| 1136 | return HTMLResponse(stream = s) |
|---|
| 1137 | |
|---|
| 1138 | class Worksheet_new_text_cell_after(WorksheetResource, resource.PostableResource): |
|---|
| 1139 | """ |
|---|
| 1140 | Adds a new text cell after a given cell. |
|---|
| 1141 | """ |
|---|
| 1142 | def render(self, ctx): |
|---|
| 1143 | id = self.id(ctx) |
|---|
| 1144 | if not ctx.args.has_key('input'): |
|---|
| 1145 | input = '' |
|---|
| 1146 | else: |
|---|
| 1147 | input = ctx.args['input'][0] |
|---|
| 1148 | |
|---|
| 1149 | cell = self.worksheet.new_text_cell_after(id, input=input) |
|---|
| 1150 | s = encode_list([cell.id(), cell.html(editing=True), id]) |
|---|
| 1151 | return HTMLResponse(stream = s) |
|---|
| 1152 | |
|---|
| 1153 | |
|---|
| 1154 | ######################################################## |
|---|
| 1155 | # The delete cell command: /home/worksheet/delete_cell?id=number |
|---|
| 1156 | ######################################################## |
|---|
| 1157 | class Worksheet_delete_cell(WorksheetResource, resource.PostableResource): |
|---|
| 1158 | """ |
|---|
| 1159 | Deletes a notebook cell. |
|---|
| 1160 | |
|---|
| 1161 | If there is only one cell left in a given worksheet, the request to |
|---|
| 1162 | delete that cell is ignored because there must be a least one cell |
|---|
| 1163 | at all times in a worksheet. (This requirement exists so other |
|---|
| 1164 | functions that evaluate relative to existing cells will still work, |
|---|
| 1165 | and so one can add new cells.) |
|---|
| 1166 | """ |
|---|
| 1167 | def render(self, ctx): |
|---|
| 1168 | id = self.id(ctx) |
|---|
| 1169 | W = self.worksheet |
|---|
| 1170 | if len(W.compute_cell_id_list()) <= 1: |
|---|
| 1171 | s = 'ignore' |
|---|
| 1172 | else: |
|---|
| 1173 | prev_id = W.delete_cell_with_id(id) |
|---|
| 1174 | s = encode_list(['delete', id, prev_id, W.cell_id_list()]) |
|---|
| 1175 | return HTMLResponse(stream = s) |
|---|
| 1176 | |
|---|
| 1177 | |
|---|
| 1178 | ############################ |
|---|
| 1179 | # Get the latest update on output appearing |
|---|
| 1180 | # in a given output cell. |
|---|
| 1181 | ############################ |
|---|
| 1182 | class Worksheet_cell_update(WorksheetResource, resource.PostableResource): |
|---|
| 1183 | def render(self, ctx): |
|---|
| 1184 | id = self.id(ctx) |
|---|
| 1185 | |
|---|
| 1186 | worksheet = self.worksheet |
|---|
| 1187 | |
|---|
| 1188 | # update the computation one "step". |
|---|
| 1189 | worksheet.check_comp() |
|---|
| 1190 | |
|---|
| 1191 | # now get latest status on our cell |
|---|
| 1192 | status, cell = worksheet.check_cell(id) |
|---|
| 1193 | |
|---|
| 1194 | if status == 'd': |
|---|
| 1195 | new_input = cell.changed_input_text() |
|---|
| 1196 | out_html = cell.output_html() |
|---|
| 1197 | H = "Worksheet '%s' (%s)\n"%(worksheet.name(), time.strftime("%Y-%m-%d at %H:%M",time.localtime(time.time()))) |
|---|
| 1198 | H += cell.edit_text(ncols=HISTORY_NCOLS, prompts=False, |
|---|
| 1199 | max_out=HISTORY_MAX_OUTPUT) |
|---|
| 1200 | notebook.add_to_user_history(H, self.username) |
|---|
| 1201 | else: |
|---|
| 1202 | new_input = '' |
|---|
| 1203 | out_html = '' |
|---|
| 1204 | |
|---|
| 1205 | if cell.interrupted(): |
|---|
| 1206 | inter = 'true' |
|---|
| 1207 | else: |
|---|
| 1208 | inter = 'false' |
|---|
| 1209 | |
|---|
| 1210 | raw = cell.output_text(raw=True).split("\n") |
|---|
| 1211 | if "Unhandled SIGSEGV" in raw: |
|---|
| 1212 | inter = 'restart' |
|---|
| 1213 | print "Segmentation fault detected in output!" |
|---|
| 1214 | |
|---|
| 1215 | msg = '%s%s %s'%(status, cell.id(), |
|---|
| 1216 | encode_list([cell.output_text(html=True), |
|---|
| 1217 | cell.output_text(word_wrap_cols(), html=True), |
|---|
| 1218 | out_html, |
|---|
| 1219 | new_input, |
|---|
| 1220 | inter, |
|---|
| 1221 | cell.introspect_html()])) |
|---|
| 1222 | |
|---|
| 1223 | # There may be more computations left to do, so start one if there is one. |
|---|
| 1224 | worksheet.start_next_comp() |
|---|
| 1225 | |
|---|
| 1226 | return HTMLResponse(stream=msg) |
|---|
| 1227 | |
|---|
| 1228 | |
|---|
| 1229 | class Worksheet_eval(WorksheetResource, resource.PostableResource): |
|---|
| 1230 | """ |
|---|
| 1231 | Evaluate a worksheet cell. |
|---|
| 1232 | |
|---|
| 1233 | If the request is not authorized (the requester did not enter the |
|---|
| 1234 | correct password for the given worksheet), then the request to |
|---|
| 1235 | evaluate or introspect the cell is ignored. |
|---|
| 1236 | |
|---|
| 1237 | If the cell contains either 1 or 2 question marks at the end (not |
|---|
| 1238 | on a comment line), then this is interpreted as a request for |
|---|
| 1239 | either introspection to the documentation of the function, or the |
|---|
| 1240 | documentation of the function and the source code of the function |
|---|
| 1241 | respectively. |
|---|
| 1242 | """ |
|---|
| 1243 | def render(self, ctx): |
|---|
| 1244 | id = self.id(ctx) |
|---|
| 1245 | if not ctx.args.has_key('input'): |
|---|
| 1246 | input_text = '' |
|---|
| 1247 | else: |
|---|
| 1248 | input_text = ctx.args['input'][0] |
|---|
| 1249 | input_text = input_text.replace('\r\n', '\n') # DOS |
|---|
| 1250 | |
|---|
| 1251 | W = self.worksheet |
|---|
| 1252 | owner = W.owner() |
|---|
| 1253 | if owner != '_sage_': |
|---|
| 1254 | if W.owner() != self.username and not (self.username in W.collaborators()): |
|---|
| 1255 | return InvalidPage(msg = "can't evaluate worksheet cells", username = self.username) |
|---|
| 1256 | cell = W.get_cell_with_id(id) |
|---|
| 1257 | |
|---|
| 1258 | cell.set_input_text(input_text) |
|---|
| 1259 | |
|---|
| 1260 | if ctx.args.has_key('save_only') and ctx.args['save_only'][0] == '1': |
|---|
| 1261 | notebook_updates() |
|---|
| 1262 | return HTMLResponse(stream='') |
|---|
| 1263 | elif ctx.args.has_key('text_only') and ctx.args['text_only'][0] == '1': |
|---|
| 1264 | notebook_updates() |
|---|
| 1265 | return HTMLResponse(stream=encode_list([str(id), cell.html()])) |
|---|
| 1266 | else: |
|---|
| 1267 | newcell = int(ctx.args['newcell'][0]) # whether to insert a new cell or not |
|---|
| 1268 | |
|---|
| 1269 | cell.evaluate(username = self.username) |
|---|
| 1270 | |
|---|
| 1271 | if cell.is_last(): |
|---|
| 1272 | new_cell = W.append_new_cell() |
|---|
| 1273 | s = encode_list([new_cell.id(), 'append_new_cell', new_cell.html(div_wrap=False)]) |
|---|
| 1274 | elif newcell: |
|---|
| 1275 | new_cell = W.new_cell_after(id) |
|---|
| 1276 | s = encode_list([new_cell.id(), 'insert_cell', new_cell.html(div_wrap=False), str(id)]) |
|---|
| 1277 | else: |
|---|
| 1278 | s = encode_list([cell.next_id(), 'no_new_cell', str(id)]) |
|---|
| 1279 | |
|---|
| 1280 | notebook_updates() |
|---|
| 1281 | return HTMLResponse(stream=s) |
|---|
| 1282 | |
|---|
| 1283 | |
|---|
| 1284 | ######################################################## |
|---|
| 1285 | # Publication and rating of a worksheet |
|---|
| 1286 | ######################################################## |
|---|
| 1287 | |
|---|
| 1288 | class Worksheet_publish(WorksheetResource, resource.Resource): |
|---|
| 1289 | """ |
|---|
| 1290 | This is a child resource of the Worksheet resource. It provides a |
|---|
| 1291 | frontend to the management of worksheet publication. This management |
|---|
| 1292 | functionality includes initializational of publication, |
|---|
| 1293 | re-publication, automated publication when a worksheet saved, and |
|---|
| 1294 | ending of publication. |
|---|
| 1295 | """ |
|---|
| 1296 | addSlash = True |
|---|
| 1297 | |
|---|
| 1298 | def render(self, ctx): |
|---|
| 1299 | # Publishes worksheet and also sets worksheet to be published automatically when saved |
|---|
| 1300 | if 'yes' in ctx.args and 'auto' in ctx.args: |
|---|
| 1301 | notebook.publish_worksheet(self.worksheet, self.username) |
|---|
| 1302 | self.worksheet.set_auto_publish() |
|---|
| 1303 | return http.RedirectResponse("/home/%s/publish" % (self.worksheet.filename())) |
|---|
| 1304 | # Just publishes worksheet |
|---|
| 1305 | elif 'yes' in ctx.args: |
|---|
| 1306 | notebook.publish_worksheet(self.worksheet, self.username) |
|---|
| 1307 | return http.RedirectResponse("/home/%s/publish" % (self.worksheet.filename())) |
|---|
| 1308 | # Stops publication of worksheet |
|---|
| 1309 | elif 'stop' in ctx.args: |
|---|
| 1310 | notebook.delete_worksheet(self.worksheet.published_version().filename()) |
|---|
| 1311 | return http.RedirectResponse("/home/%s/publish" % (self.worksheet.filename())) |
|---|
| 1312 | # Re-publishes worksheet |
|---|
| 1313 | elif 're' in ctx.args: |
|---|
| 1314 | W = notebook.publish_worksheet(self.worksheet, self.username) |
|---|
| 1315 | return http.RedirectResponse("/home/%s/publish" % (self.worksheet.filename())) |
|---|
| 1316 | # Sets worksheet to be published automatically when saved |
|---|
| 1317 | elif 'auto' in ctx.args: |
|---|
| 1318 | self.worksheet.set_auto_publish() |
|---|
| 1319 | return http.RedirectResponse("/home/%s/publish" % (self.worksheet.filename())) |
|---|
| 1320 | # Returns boolean of "Is this worksheet set to be published automatically when saved?" |
|---|
| 1321 | elif 'is_auto' in ctx.args: |
|---|
| 1322 | return HTMLResponse(stream=str(self.worksheet.is_auto_publish())) |
|---|
| 1323 | # Returns the publication page |
|---|
| 1324 | else: |
|---|
| 1325 | # Page for when worksheet already published |
|---|
| 1326 | if self.worksheet.has_published_version(): |
|---|
| 1327 | addr = 'http%s://' % ('' if not notebook.secure else 's') |
|---|
| 1328 | addr += notebook.address |
|---|
| 1329 | addr += ':%s' % notebook.port |
|---|
| 1330 | addr += '/home/' + self.worksheet.published_version().filename() |
|---|
| 1331 | dtime = self.worksheet.published_version().date_edited() |
|---|
| 1332 | return HTMLResponse(stream=notebook.html_afterpublish_window(self.worksheet, self.username, addr, dtime)) |
|---|
| 1333 | # Page for when worksheet is not already published |
|---|
| 1334 | else: |
|---|
| 1335 | return HTMLResponse(stream=notebook.html_beforepublish_window(self.worksheet, self.username)) |
|---|
| 1336 | |
|---|
| 1337 | |
|---|
| 1338 | class Worksheet_rating_info(WorksheetResource, resource.Resource): |
|---|
| 1339 | def render(self, ctx): |
|---|
| 1340 | s = self.worksheet.html_ratings_info() |
|---|
| 1341 | return HTMLResponse(stream=message(''' |
|---|
| 1342 | <h2 align=center>Ratings for %s</h2> |
|---|
| 1343 | <h3 align=center><a href='/home/%s'>Go to the worksheet.</a> |
|---|
| 1344 | <br><br> |
|---|
| 1345 | <table width=70%%align=center border=1 cellpadding=10 cellspacing=0> |
|---|
| 1346 | <tr bgcolor="#7799bb"><td width=30em>User</td><td width=10em align=center>Rating</td><td width=10em align=center width=60em>Comment</td></tr> |
|---|
| 1347 | %s |
|---|
| 1348 | </table> |
|---|
| 1349 | <br><br> |
|---|
| 1350 | '''%(self.worksheet.name(), self.worksheet.filename(), s))) |
|---|
| 1351 | |
|---|
| 1352 | |
|---|
| 1353 | class Worksheet_rate(WorksheetResource, resource.Resource): |
|---|
| 1354 | def render(self, ctx): |
|---|
| 1355 | ret = '/home/' + self.worksheet.filename() |
|---|
| 1356 | #if self.worksheet.is_rater(self.username): |
|---|
| 1357 | # return HTMLResponse(stream=message("You have already rated the worksheet <i><b>%s</b></i>."%self.worksheet.name(), ret)) |
|---|
| 1358 | if user_type(self.username) == "guest": |
|---|
| 1359 | return HTMLResponse(stream = message( |
|---|
| 1360 | 'You must <a href="/">login first</a> in order to rate this worksheet.', ret)) |
|---|
| 1361 | |
|---|
| 1362 | rating = int(ctx.args['rating'][0]) |
|---|
| 1363 | if rating < 0 or rating >= 5: |
|---|
| 1364 | return HTMLResponse(stream = message( |
|---|
| 1365 | "Gees -- You can't fool the rating system that easily!", ret)) |
|---|
| 1366 | comment = ctx.args['comment'][0] |
|---|
| 1367 | self.worksheet.rate(rating, comment, self.username) |
|---|
| 1368 | return HTMLResponse(stream=message(""" |
|---|
| 1369 | Thank you for rating the worksheet <b><i>%s</i></b>! |
|---|
| 1370 | You can <a href="rating_info">see all ratings of this worksheet.</a> |
|---|
| 1371 | """%self.worksheet.name(), '/pub/')) |
|---|
| 1372 | |
|---|
| 1373 | |
|---|
| 1374 | ######################################################## |
|---|
| 1375 | # Downloading, moving around, renaming, etc. |
|---|
| 1376 | ######################################################## |
|---|
| 1377 | |
|---|
| 1378 | |
|---|
| 1379 | class Worksheet_download(WorksheetResource, resource.Resource): |
|---|
| 1380 | def childFactory(self, request, name): |
|---|
| 1381 | worksheet_name = self.name |
|---|
| 1382 | filename = tmp_filename() + '.sws' |
|---|
| 1383 | try: |
|---|
| 1384 | notebook.export_worksheet(worksheet_name, filename) |
|---|
| 1385 | except KeyError: |
|---|
| 1386 | return HTMLResponse(stream=message('No such worksheet.')) |
|---|
| 1387 | r = open(filename, 'rb').read() |
|---|
| 1388 | os.unlink(filename) |
|---|
| 1389 | return static.Data(r, 'application/sage') |
|---|
| 1390 | #return static.File(filename) |
|---|
| 1391 | |
|---|
| 1392 | class DownloadWorksheets(resource.Resource): |
|---|
| 1393 | |
|---|
| 1394 | def __init__(self, username): |
|---|
| 1395 | self.username = username |
|---|
| 1396 | |
|---|
| 1397 | def render(self, ctx): |
|---|
| 1398 | print type(ctx) |
|---|
| 1399 | worksheet_names = set() |
|---|
| 1400 | if ctx.args.has_key('filenames'): |
|---|
| 1401 | sep = ctx.args['sep'][0] |
|---|
| 1402 | worksheets = [notebook.get_worksheet_with_filename(x.strip()) for x in ctx.args['filenames'][0].split(sep) if len(x.strip()) > 0] |
|---|
| 1403 | else: |
|---|
| 1404 | worksheets = notebook.worksheet_list_for_user(self.username) |
|---|
| 1405 | zip_filename = tmp_filename() + ".zip" |
|---|
| 1406 | zip = zipfile.ZipFile(zip_filename, 'w', zipfile.ZIP_STORED) |
|---|
| 1407 | for worksheet in worksheets: |
|---|
| 1408 | sws_filename = tmp_filename() + '.sws' |
|---|
| 1409 | notebook.export_worksheet(worksheet.filename(), sws_filename) |
|---|
| 1410 | entry_name = worksheet.name() |
|---|
| 1411 | if entry_name in worksheet_names: |
|---|
| 1412 | i = 2 |
|---|
| 1413 | while ("%s_%s" % (entry_name, i)) in worksheet_names: |
|---|
| 1414 | i += 1 |
|---|
| 1415 | entry_name = "%s_%s" % (entry_name, i) |
|---|
| 1416 | zip.write(sws_filename, entry_name + ".sws") |
|---|
| 1417 | os.unlink(sws_filename) |
|---|
| 1418 | zip.close() |
|---|
| 1419 | r = open(zip_filename, 'rb').read() |
|---|
| 1420 | os.unlink(zip_filename) |
|---|
| 1421 | return static.Data(r, 'application/zip') |
|---|
| 1422 | |
|---|
| 1423 | class Worksheet_rename(WorksheetResource, resource.PostableResource): |
|---|
| 1424 | def render(self, ctx): |
|---|
| 1425 | self.worksheet.set_name(ctx.args['name'][0]) |
|---|
| 1426 | return HTMLResponse(stream='done') |
|---|
| 1427 | |
|---|
| 1428 | class Worksheet_restart_sage(WorksheetResource, resource.Resource): |
|---|
| 1429 | def render(self, ctx): |
|---|
| 1430 | # TODO -- this must not block long (!) |
|---|
| 1431 | self.worksheet.restart_sage() |
|---|
| 1432 | return HTMLResponse(stream='done') |
|---|
| 1433 | |
|---|
| 1434 | class Worksheet_quit_sage(WorksheetResource, resource.Resource): |
|---|
| 1435 | def render(self, ctx): |
|---|
| 1436 | # TODO -- this must not block long (!) |
|---|
| 1437 | self.worksheet.quit() |
|---|
| 1438 | return HTMLResponse(stream='done') |
|---|
| 1439 | |
|---|
| 1440 | class Worksheet_interrupt(WorksheetResource, resource.Resource): |
|---|
| 1441 | def render(self, ctx): |
|---|
| 1442 | # TODO -- this must not block long (!) |
|---|
| 1443 | s = self.worksheet.interrupt() |
|---|
| 1444 | return HTMLResponse(stream='ok' if s else 'failed') |
|---|
| 1445 | |
|---|
| 1446 | class Worksheet_plain(WorksheetResource, resource.Resource): |
|---|
| 1447 | def render(self, ctx): |
|---|
| 1448 | s = notebook.plain_text_worksheet_html(self.name) |
|---|
| 1449 | return HTMLResponse(stream=s) |
|---|
| 1450 | |
|---|
| 1451 | class Worksheet_hide_all(WorksheetResource, resource.Resource): |
|---|
| 1452 | def render(self, ctx): |
|---|
| 1453 | self.worksheet.hide_all() |
|---|
| 1454 | return HTMLResponse(stream='success') |
|---|
| 1455 | |
|---|
| 1456 | class Worksheet_show_all(WorksheetResource, resource.Resource): |
|---|
| 1457 | def render(self, ctx): |
|---|
| 1458 | self.worksheet.show_all() |
|---|
| 1459 | return HTMLResponse(stream='success') |
|---|
| 1460 | |
|---|
| 1461 | |
|---|
| 1462 | # Delete all the output of cells in a worksheet. |
|---|
| 1463 | class Worksheet_delete_all_output(WorksheetResource, resource.Resource): |
|---|
| 1464 | def render(self, ctx): |
|---|
| 1465 | try: |
|---|
| 1466 | self.worksheet.delete_all_output(self.username) |
|---|
| 1467 | except ValueError: |
|---|
| 1468 | return HTMLResponse(stream='fail') |
|---|
| 1469 | return HTMLResponse(stream='success') |
|---|
| 1470 | |
|---|
| 1471 | class Worksheet_print(WorksheetResource, resource.Resource): |
|---|
| 1472 | def render(self, ctx): |
|---|
| 1473 | s = notebook.worksheet_html(self.name, do_print=True) |
|---|
| 1474 | return HTMLResponse(stream=s) |
|---|
| 1475 | |
|---|
| 1476 | |
|---|
| 1477 | class NotImplementedWorksheetOp(resource.Resource): |
|---|
| 1478 | def __init__(self, op, ws): |
|---|
| 1479 | self.op = op |
|---|
| 1480 | self.ws = ws |
|---|
| 1481 | |
|---|
| 1482 | def render(self, ctx): |
|---|
| 1483 | return HTMLResponse(stream = message( |
|---|
| 1484 | 'The worksheet operation "%s" is not defined.'%self.op, |
|---|
| 1485 | '/home/'+self.ws.filename())) |
|---|
| 1486 | |
|---|
| 1487 | |
|---|
| 1488 | class Worksheet(WorksheetResource, resource.Resource): |
|---|
| 1489 | addSlash = True |
|---|
| 1490 | |
|---|
| 1491 | def render(self, ctx): |
|---|
| 1492 | self.worksheet.sage() |
|---|
| 1493 | s = notebook.html(worksheet_filename = self.name, username=self.username) |
|---|
| 1494 | return HTMLResponse(stream=s) |
|---|
| 1495 | |
|---|
| 1496 | def childFactory(self, request, op): |
|---|
| 1497 | notebook_updates() |
|---|
| 1498 | try: |
|---|
| 1499 | # Rather than a bunch of if-else statements, we wrap |
|---|
| 1500 | # any Worksheet_... class as a subresource of a worksheet |
|---|
| 1501 | # using the following statement: |
|---|
| 1502 | R = globals()['Worksheet_%s'%op] |
|---|
| 1503 | return R(self.name, username = self.username) |
|---|
| 1504 | except KeyError: |
|---|
| 1505 | file = self.worksheet.data_directory() + '/' + op |
|---|
| 1506 | if os.path.exists(file): |
|---|
| 1507 | return static.File(file) |
|---|
| 1508 | dir = self.worksheet.cells_directory() |
|---|
| 1509 | for F in os.listdir(dir): |
|---|
| 1510 | h = '%s/%s/%s'%(dir,F,op) |
|---|
| 1511 | if os.path.exists(h): |
|---|
| 1512 | return static.File(h) |
|---|
| 1513 | return NotImplementedWorksheetOp(op, self.worksheet) |
|---|
| 1514 | |
|---|
| 1515 | |
|---|
| 1516 | def render_worksheet_list(args, pub, username): |
|---|
| 1517 | """ |
|---|
| 1518 | Returns a rendered worksheet listing. |
|---|
| 1519 | |
|---|
| 1520 | INPUT: |
|---|
| 1521 | |
|---|
| 1522 | - ``args`` - ctx.args where ctx is the dict passed |
|---|
| 1523 | into a resource's render method |
|---|
| 1524 | |
|---|
| 1525 | - ``pub`` - boolean, True if this is a listing of |
|---|
| 1526 | public worksheets |
|---|
| 1527 | |
|---|
| 1528 | - ``username`` - the user whose worksheets we are |
|---|
| 1529 | listing |
|---|
| 1530 | |
|---|
| 1531 | OUTPUT: |
|---|
| 1532 | |
|---|
| 1533 | a string |
|---|
| 1534 | """ |
|---|
| 1535 | from sage.server.notebook.notebook import sort_worksheet_list |
|---|
| 1536 | typ = args['typ'][0] if 'typ' in args else 'active' |
|---|
| 1537 | search = args['search'][0] if 'search' in args else None |
|---|
| 1538 | sort = args['sort'][0] if 'sort' in args else 'last_edited' |
|---|
| 1539 | reverse = (args['reverse'][0] == 'True') if 'reverse' in args else False |
|---|
| 1540 | |
|---|
| 1541 | if not pub: |
|---|
| 1542 | worksheets = notebook.worksheet_list_for_user(username, typ=typ, sort=sort, |
|---|
| 1543 | search=search, reverse=reverse) |
|---|
| 1544 | |
|---|
| 1545 | else: |
|---|
| 1546 | worksheets = notebook.worksheet_list_for_public(username, sort=sort, |
|---|
| 1547 | search=search, reverse=reverse) |
|---|
| 1548 | |
|---|
| 1549 | worksheet_filenames = [x.filename() for x in worksheets] |
|---|
| 1550 | |
|---|
| 1551 | if pub and (not username or username == tuple([])): |
|---|
| 1552 | username = 'pub' |
|---|
| 1553 | |
|---|
| 1554 | accounts = notebook.get_accounts() |
|---|
| 1555 | |
|---|
| 1556 | return template('worksheet_listing.html', **locals()) |
|---|
| 1557 | |
|---|
| 1558 | |
|---|
| 1559 | class WorksheetsByUser(resource.Resource): |
|---|
| 1560 | addSlash = True |
|---|
| 1561 | |
|---|
| 1562 | def __init__(self, user, username): |
|---|
| 1563 | # user -- who we're requesting the worksheets of |
|---|
| 1564 | # username -- who is doing the requesting |
|---|
| 1565 | self.user = user |
|---|
| 1566 | self.username = username |
|---|
| 1567 | |
|---|
| 1568 | def render_list(self, ctx): |
|---|
| 1569 | s = render_worksheet_list(ctx.args, pub=False, username=self.user) |
|---|
| 1570 | return HTMLResponse(stream = s) |
|---|
| 1571 | |
|---|
| 1572 | def render(self, ctx): |
|---|
| 1573 | if self.user == self.username or user_type(self.username) == 'admin': |
|---|
| 1574 | return self.render_list(ctx) |
|---|
| 1575 | else: |
|---|
| 1576 | if not self.username == 'guest': |
|---|
| 1577 | s = message("User '%s' does not have permission to view the home page of '%s'."%(self.username, self.user)) |
|---|
| 1578 | else: |
|---|
| 1579 | return http.RedirectResponse('/') |
|---|
| 1580 | return HTMLResponse(stream = s) |
|---|
| 1581 | |
|---|
| 1582 | def childFactory(self, request, name): |
|---|
| 1583 | if name == "trash": |
|---|
| 1584 | return TrashCan(self.user) |
|---|
| 1585 | |
|---|
| 1586 | filename = self.user + '/' + name |
|---|
| 1587 | try: |
|---|
| 1588 | return Worksheet(filename, self.username) |
|---|
| 1589 | except KeyError: |
|---|
| 1590 | s = "The user '%s' has no worksheet '%s'."%(self.user, name) |
|---|
| 1591 | return InvalidPage(msg = s, username = self.user) |
|---|
| 1592 | except RuntimeError: |
|---|
| 1593 | s = "You are not logged in or do not have access to the worksheet '%s'."%name |
|---|
| 1594 | return InvalidPage(msg = s, username = self.user) |
|---|
| 1595 | |
|---|
| 1596 | |
|---|
| 1597 | |
|---|
| 1598 | ############################ |
|---|
| 1599 | # Trash can, archive and active |
|---|
| 1600 | ############################ |
|---|
| 1601 | class EmptyTrash(resource.Resource): |
|---|
| 1602 | def __init__(self, username): |
|---|
| 1603 | """ |
|---|
| 1604 | This twisted resource empties the trash of the current user when it |
|---|
| 1605 | is rendered. |
|---|
| 1606 | |
|---|
| 1607 | EXAMPLES: |
|---|
| 1608 | |
|---|
| 1609 | We create an instance of this resource. |
|---|
| 1610 | |
|---|
| 1611 | :: |
|---|
| 1612 | |
|---|
| 1613 | sage: import sage.server.notebook.twist |
|---|
| 1614 | sage: E = sage.server.notebook.twist.EmptyTrash('sage'); E |
|---|
| 1615 | <sage.server.notebook.twist.EmptyTrash object at ...> |
|---|
| 1616 | """ |
|---|
| 1617 | self.username = username |
|---|
| 1618 | |
|---|
| 1619 | def render(self, ctx): |
|---|
| 1620 | """ |
|---|
| 1621 | Rendering this resource (1) empties the trash, and (2) returns a |
|---|
| 1622 | message. |
|---|
| 1623 | |
|---|
| 1624 | EXAMPLES: |
|---|
| 1625 | |
|---|
| 1626 | We create a notebook with a worksheet, put it in the |
|---|
| 1627 | trash, then empty the trash by creating and rendering this |
|---|
| 1628 | worksheet. |
|---|
| 1629 | |
|---|
| 1630 | :: |
|---|
| 1631 | |
|---|
| 1632 | sage: n = sage.server.notebook.notebook.Notebook('notebook-test') |
|---|
| 1633 | sage: n.add_user('sage','sage','sage@sagemath.org',force=True) |
|---|
| 1634 | sage: W = n.new_worksheet_with_title_from_text('Sage', owner='sage') |
|---|
| 1635 | sage: W.move_to_trash('sage') |
|---|
| 1636 | sage: n.worksheet_names() |
|---|
| 1637 | ['sage/0'] |
|---|
| 1638 | sage: sage.server.notebook.twist.notebook = n |
|---|
| 1639 | sage: E = sage.server.notebook.twist.EmptyTrash('sage'); E |
|---|
| 1640 | <sage.server.notebook.twist.EmptyTrash object at ...> |
|---|
| 1641 | sage: E.render(None) |
|---|
| 1642 | <twisted.web2.http.Response code=200, streamlen=...> |
|---|
| 1643 | |
|---|
| 1644 | Finally we verify that the trashed worksheet is gone:: |
|---|
| 1645 | |
|---|
| 1646 | sage: n.worksheet_names() |
|---|
| 1647 | [] |
|---|
| 1648 | sage: n.delete() |
|---|
| 1649 | """ |
|---|
| 1650 | notebook.empty_trash(self.username) |
|---|
| 1651 | return HTMLResponse(stream = message("Trash emptied.")) |
|---|
| 1652 | |
|---|
| 1653 | class SendWorksheetToFolder(resource.PostableResource): |
|---|
| 1654 | def __init__(self, username): |
|---|
| 1655 | self.username = username |
|---|
| 1656 | |
|---|
| 1657 | def action(self, W): |
|---|
| 1658 | raise NotImplementedError |
|---|
| 1659 | |
|---|
| 1660 | def render(self, ctx): |
|---|
| 1661 | X = notebook.user(self.username) |
|---|
| 1662 | if user_type(self.username) == 'guest': |
|---|
| 1663 | return HTMLResponse(stream = message("You are not authorized to move '%s'"%W.name())) |
|---|
| 1664 | |
|---|
| 1665 | def send_worksheet_to_folder(filename): |
|---|
| 1666 | W = notebook.get_worksheet_with_filename(filename) |
|---|
| 1667 | self.action(W) |
|---|
| 1668 | |
|---|
| 1669 | if ctx.args.has_key('filename'): |
|---|
| 1670 | filenames = [ctx.args['filename'][0]] |
|---|
| 1671 | elif ctx.args.has_key('filenames'): |
|---|
| 1672 | sep = ctx.args['sep'][0] |
|---|
| 1673 | filenames = [x for x in ctx.args['filenames'][0].split(sep) if len(x.strip()) > 0] |
|---|
| 1674 | |
|---|
| 1675 | else: |
|---|
| 1676 | |
|---|
| 1677 | filenames = [] |
|---|
| 1678 | |
|---|
| 1679 | for F in filenames: |
|---|
| 1680 | send_worksheet_to_folder(F) |
|---|
| 1681 | |
|---|
| 1682 | return HTMLResponse(stream = '') |
|---|
| 1683 | |
|---|
| 1684 | class SendWorksheetToTrash(SendWorksheetToFolder): |
|---|
| 1685 | def action(self, W): |
|---|
| 1686 | W.move_to_trash(self.username) |
|---|
| 1687 | |
|---|
| 1688 | class SendWorksheetToArchive(SendWorksheetToFolder): |
|---|
| 1689 | def action(self, W): |
|---|
| 1690 | W.move_to_archive(self.username) |
|---|
| 1691 | |
|---|
| 1692 | class SendWorksheetToActive(SendWorksheetToFolder): |
|---|
| 1693 | def action(self, W): |
|---|
| 1694 | W.set_active(self.username) |
|---|
| 1695 | |
|---|
| 1696 | # Using SendWorksheet does feel somewhat hackish. It however is |
|---|
| 1697 | # exactly the right thing to actually do, and minimizes code |
|---|
| 1698 | # duplication. |
|---|
| 1699 | class SendWorksheetToStop(SendWorksheetToFolder): |
|---|
| 1700 | """ |
|---|
| 1701 | Quits each selected worksheet. |
|---|
| 1702 | """ |
|---|
| 1703 | def action(self, W): |
|---|
| 1704 | W.quit() |
|---|
| 1705 | |
|---|
| 1706 | ############################ |
|---|
| 1707 | # Publicly Available Worksheets |
|---|
| 1708 | ############################ |
|---|
| 1709 | class PublicWorksheets(resource.Resource): |
|---|
| 1710 | addSlash = True |
|---|
| 1711 | |
|---|
| 1712 | def __init__(self, username): |
|---|
| 1713 | self.username = username |
|---|
| 1714 | |
|---|
| 1715 | def render(self, ctx): |
|---|
| 1716 | s = render_worksheet_list(ctx.args, pub=True, username=self.username) |
|---|
| 1717 | return HTMLResponse(stream = s) |
|---|
| 1718 | |
|---|
| 1719 | def childFactory(self, request, name): |
|---|
| 1720 | return Worksheet('pub/' + name, username=self.username) |
|---|
| 1721 | |
|---|
| 1722 | class PublicWorksheetsHome(resource.Resource): |
|---|
| 1723 | addSlash = True |
|---|
| 1724 | |
|---|
| 1725 | def __init__(self, username): |
|---|
| 1726 | self.username = username |
|---|
| 1727 | |
|---|
| 1728 | def childFactor(self, request, name): |
|---|
| 1729 | if name == 'pub': |
|---|
| 1730 | return PublicWorksheets(self.username) |
|---|
| 1731 | |
|---|
| 1732 | ############################ |
|---|
| 1733 | # Resource that gives access to worksheets |
|---|
| 1734 | ############################ |
|---|
| 1735 | |
|---|
| 1736 | class Worksheets(resource.Resource): |
|---|
| 1737 | def __init__(self, username): |
|---|
| 1738 | self.username = username |
|---|
| 1739 | |
|---|
| 1740 | def render(self, ctx): |
|---|
| 1741 | return HTMLResponse(stream = message("Please request a specific worksheet")) |
|---|
| 1742 | |
|---|
| 1743 | def childFactory(self, request, name): |
|---|
| 1744 | return WorksheetsByUser(name, username=self.username) |
|---|
| 1745 | |
|---|
| 1746 | |
|---|
| 1747 | class WorksheetsByUserAdmin(WorksheetsByUser): |
|---|
| 1748 | def render(self, ctx): |
|---|
| 1749 | return self.render_list(ctx) |
|---|
| 1750 | |
|---|
| 1751 | class WorksheetsAdmin(Worksheets): |
|---|
| 1752 | def childFactory(self, request, name): |
|---|
| 1753 | return WorksheetsByUserAdmin(name, username=self.username) |
|---|
| 1754 | |
|---|
| 1755 | ############################ |
|---|
| 1756 | # Notebook configuration |
|---|
| 1757 | ############################ |
|---|
| 1758 | |
|---|
| 1759 | class NotebookConf(Worksheets): |
|---|
| 1760 | def render(self, ctx): |
|---|
| 1761 | s = '<html>' + notebook.conf().html_conf_form('submit') + '</html>' |
|---|
| 1762 | return HTMLResponse(stream = s) |
|---|
| 1763 | |
|---|
| 1764 | |
|---|
| 1765 | |
|---|
| 1766 | ############################ |
|---|
| 1767 | # Adding a new worksheet |
|---|
| 1768 | ############################ |
|---|
| 1769 | |
|---|
| 1770 | class AddWorksheet(resource.Resource): |
|---|
| 1771 | def render(self, ctx): |
|---|
| 1772 | name = ctx.args['name'][0] |
|---|
| 1773 | W = notebook.create_new_worksheet(name) |
|---|
| 1774 | v = notebook.worksheet_list_html(W.name()) |
|---|
| 1775 | return HTMLResponse(stream = encode_list([v, W.name()])) |
|---|
| 1776 | |
|---|
| 1777 | |
|---|
| 1778 | ############################ |
|---|
| 1779 | |
|---|
| 1780 | class Help(resource.Resource): |
|---|
| 1781 | addSlash = True |
|---|
| 1782 | def __init__(self, username): |
|---|
| 1783 | self.username = username |
|---|
| 1784 | |
|---|
| 1785 | def render(self, ctx): |
|---|
| 1786 | from tutorial import notebook_help |
|---|
| 1787 | return HTMLResponse(stream=template('docs.html', username=self.username, notebook_help=notebook_help)) |
|---|
| 1788 | |
|---|
| 1789 | |
|---|
| 1790 | ############################ |
|---|
| 1791 | |
|---|
| 1792 | ############################ |
|---|
| 1793 | |
|---|
| 1794 | class History(resource.Resource): |
|---|
| 1795 | def __init__(self, username): |
|---|
| 1796 | self.username = username |
|---|
| 1797 | |
|---|
| 1798 | def render(self, ctx): |
|---|
| 1799 | t = template('history.html', username=self.username, |
|---|
| 1800 | text = notebook.user_history_text(self.username), |
|---|
| 1801 | actions=False) |
|---|
| 1802 | return HTMLResponse(stream=t) |
|---|
| 1803 | |
|---|
| 1804 | class LiveHistory(resource.Resource): |
|---|
| 1805 | def __init__(self, username): |
|---|
| 1806 | self.username = username |
|---|
| 1807 | |
|---|
| 1808 | def render(self, ctx): |
|---|
| 1809 | W = notebook.create_new_worksheet_from_history('Log', self.username, 100) |
|---|
| 1810 | return http.RedirectResponse('/home/'+W.filename()) |
|---|
| 1811 | |
|---|
| 1812 | |
|---|
| 1813 | ############################ |
|---|
| 1814 | |
|---|
| 1815 | class Main_css(resource.Resource): |
|---|
| 1816 | def render(self, ctx): |
|---|
| 1817 | s = css.css() |
|---|
| 1818 | gzip_handler(ctx) |
|---|
| 1819 | response = http.Response(stream=s) |
|---|
| 1820 | response.headers.addRawHeader('Content-Type', 'text/css; charset=utf-8') |
|---|
| 1821 | return response |
|---|
| 1822 | |
|---|
| 1823 | class Reset_css(resource.Resource): |
|---|
| 1824 | def render(self, ctx): |
|---|
| 1825 | s = template('css/reset.css') |
|---|
| 1826 | gzip_handler(ctx) |
|---|
| 1827 | return http.Response(stream=s) |
|---|
| 1828 | |
|---|
| 1829 | class CSS(resource.Resource): |
|---|
| 1830 | addSlash = True |
|---|
| 1831 | |
|---|
| 1832 | def render(self, ctx): |
|---|
| 1833 | return static.File(css_path) |
|---|
| 1834 | |
|---|
| 1835 | def childFactory(self, request, name): |
|---|
| 1836 | gzip_handler(request) |
|---|
| 1837 | return static.File(css_path + "/" + name) |
|---|
| 1838 | |
|---|
| 1839 | setattr(CSS, 'child_main.css', Main_css()) |
|---|
| 1840 | setattr(CSS, 'child_reset.css', Reset_css()) |
|---|
| 1841 | |
|---|
| 1842 | ############################ |
|---|
| 1843 | |
|---|
| 1844 | |
|---|
| 1845 | ############################ |
|---|
| 1846 | # Javascript resources |
|---|
| 1847 | ############################ |
|---|
| 1848 | |
|---|
| 1849 | class Main_js(resource.Resource): |
|---|
| 1850 | def render(self, ctx): |
|---|
| 1851 | gzip_handler(ctx) |
|---|
| 1852 | s = js.javascript() |
|---|
| 1853 | return http.Response(stream=s) |
|---|
| 1854 | |
|---|
| 1855 | |
|---|
| 1856 | class Keyboard_js_specific(resource.Resource): |
|---|
| 1857 | def __init__(self, browser_os): |
|---|
| 1858 | self.s = keyboards.get_keyboard(browser_os) |
|---|
| 1859 | |
|---|
| 1860 | def render(self, ctx): |
|---|
| 1861 | gzip_handler(ctx) |
|---|
| 1862 | return http.Response(stream = self.s) |
|---|
| 1863 | |
|---|
| 1864 | |
|---|
| 1865 | class Keyboard_js(resource.Resource): |
|---|
| 1866 | def childFactory(self, request, browser_os): |
|---|
| 1867 | gzip_handler(request) |
|---|
| 1868 | return Keyboard_js_specific(browser_os) |
|---|
| 1869 | |
|---|
| 1870 | class Javascript(resource.Resource): |
|---|
| 1871 | addSlash = True |
|---|
| 1872 | child_keyboard = Keyboard_js() |
|---|
| 1873 | |
|---|
| 1874 | def render(self, ctx): |
|---|
| 1875 | return static.File(javascript_path) |
|---|
| 1876 | |
|---|
| 1877 | def childFactory(self, request, name): |
|---|
| 1878 | gzip_handler(request) |
|---|
| 1879 | return static.File(javascript_path + "/" + name) |
|---|
| 1880 | |
|---|
| 1881 | setattr(Javascript, 'child_main.js', Main_js()) |
|---|
| 1882 | |
|---|
| 1883 | |
|---|
| 1884 | class JavascriptLocal(resource.Resource): |
|---|
| 1885 | addSlash = True |
|---|
| 1886 | |
|---|
| 1887 | def render(self, ctx): |
|---|
| 1888 | return static.File(javascript_local_path) |
|---|
| 1889 | |
|---|
| 1890 | def childFactory(self, request, name): |
|---|
| 1891 | gzip_handler(request) |
|---|
| 1892 | return static.File(javascript_local_path + "/" + name) |
|---|
| 1893 | |
|---|
| 1894 | |
|---|
| 1895 | |
|---|
| 1896 | ############################ |
|---|
| 1897 | # Java resources |
|---|
| 1898 | ############################ |
|---|
| 1899 | |
|---|
| 1900 | class Java(resource.Resource): |
|---|
| 1901 | addSlash = True |
|---|
| 1902 | |
|---|
| 1903 | def render(self, ctx): |
|---|
| 1904 | return static.File(java_path) |
|---|
| 1905 | |
|---|
| 1906 | def childFactory(self, request, name): |
|---|
| 1907 | gzip_handler(request) |
|---|
| 1908 | return static.File(java_path + "/" + name) |
|---|
| 1909 | |
|---|
| 1910 | ############################ |
|---|
| 1911 | # Logout |
|---|
| 1912 | ############################ |
|---|
| 1913 | class Logout(resource.Resource): |
|---|
| 1914 | def render(self, ctx): |
|---|
| 1915 | # TODO -- actually log out. |
|---|
| 1916 | s = message("<br>Thank you for using Sage.<br><br><a href='/'>Please login and use Sage again soon.</a><br>") |
|---|
| 1917 | return HTMLResponse(stream=s) |
|---|
| 1918 | |
|---|
| 1919 | ############################ |
|---|
| 1920 | # Image resource |
|---|
| 1921 | ############################ |
|---|
| 1922 | |
|---|
| 1923 | class Images(resource.Resource): |
|---|
| 1924 | addSlash = True |
|---|
| 1925 | |
|---|
| 1926 | def render(self, ctx): |
|---|
| 1927 | return static.File(image_path) |
|---|
| 1928 | |
|---|
| 1929 | def childFactory(self, request, name): |
|---|
| 1930 | return static.File(image_path + "/" + name) |
|---|
| 1931 | |
|---|
| 1932 | ##################################### |
|---|
| 1933 | # Confirmation of registration |
|---|
| 1934 | #################################### |
|---|
| 1935 | class RegConfirmation(resource.Resource): |
|---|
| 1936 | def render(self, request): |
|---|
| 1937 | if not notebook.conf()['email']: |
|---|
| 1938 | return HTMLResponse(stream=message('The confirmation system is not active.')) |
|---|
| 1939 | key = request.args['key'][0] |
|---|
| 1940 | invalid_confirm_key = """\ |
|---|
| 1941 | <h1>Invalid confirmation key</h1> |
|---|
| 1942 | <p>You are reporting a confirmation key that has not been assigned by this |
|---|
| 1943 | server. Please <a href="/register">register</a> with the server.</p> |
|---|
| 1944 | """ |
|---|
| 1945 | key = int(key) |
|---|
| 1946 | global waiting |
|---|
| 1947 | try: |
|---|
| 1948 | username = waiting[key] |
|---|
| 1949 | user = notebook.user(username) |
|---|
| 1950 | user.set_email_confirmation(True) |
|---|
| 1951 | except KeyError: |
|---|
| 1952 | return HTMLResponse(stream=message(invalid_confirm_key, '/register')) |
|---|
| 1953 | success = """<h1>Email address confirmed for user %s</h1>""" % username |
|---|
| 1954 | del waiting[key] |
|---|
| 1955 | return HTMLResponse(stream=message(success)) |
|---|
| 1956 | |
|---|
| 1957 | ############################ |
|---|
| 1958 | # Registration page |
|---|
| 1959 | ############################ |
|---|
| 1960 | import re |
|---|
| 1961 | re_valid_username = re.compile('[a-z|A-Z|0-9|_|.]*') |
|---|
| 1962 | def is_valid_username(username): |
|---|
| 1963 | r""" |
|---|
| 1964 | Returns True if and only if ``username`` is valid, |
|---|
| 1965 | i.e., starts with a letter, is between 4 and 32 characters long, |
|---|
| 1966 | and contains only letters, numbers, underscores, and and one dot |
|---|
| 1967 | (.). |
|---|
| 1968 | |
|---|
| 1969 | EXAMPLES:: |
|---|
| 1970 | |
|---|
| 1971 | sage: from sage.server.notebook.twist import is_valid_username |
|---|
| 1972 | |
|---|
| 1973 | ``username`` must start with a letter |
|---|
| 1974 | |
|---|
| 1975 | :: |
|---|
| 1976 | |
|---|
| 1977 | sage: is_valid_username('mark10') |
|---|
| 1978 | True |
|---|
| 1979 | sage: is_valid_username('10mark') |
|---|
| 1980 | False |
|---|
| 1981 | |
|---|
| 1982 | ``username`` must be between 4 and 32 characters long |
|---|
| 1983 | |
|---|
| 1984 | :: |
|---|
| 1985 | |
|---|
| 1986 | sage: is_valid_username('bob') |
|---|
| 1987 | False |
|---|
| 1988 | sage: is_valid_username('I_love_computer_science_and_maths') #33 characters long |
|---|
| 1989 | False |
|---|
| 1990 | |
|---|
| 1991 | ``username`` must not have more than one dot (.) |
|---|
| 1992 | |
|---|
| 1993 | :: |
|---|
| 1994 | |
|---|
| 1995 | sage: is_valid_username('david.andrews') |
|---|
| 1996 | True |
|---|
| 1997 | sage: is_valid_username('david.m.andrews') |
|---|
| 1998 | False |
|---|
| 1999 | sage: is_valid_username('math125.TA.5') |
|---|
| 2000 | False |
|---|
| 2001 | |
|---|
| 2002 | ``username`` must not have any spaces |
|---|
| 2003 | |
|---|
| 2004 | :: |
|---|
| 2005 | |
|---|
| 2006 | sage: is_valid_username('David Andrews') |
|---|
| 2007 | False |
|---|
| 2008 | sage: is_valid_username('David M. Andrews') |
|---|
| 2009 | False |
|---|
| 2010 | |
|---|
| 2011 | :: |
|---|
| 2012 | |
|---|
| 2013 | sage: is_valid_username('sarah_andrews') |
|---|
| 2014 | True |
|---|
| 2015 | |
|---|
| 2016 | :: |
|---|
| 2017 | |
|---|
| 2018 | sage: is_valid_username('TA-1') |
|---|
| 2019 | False |
|---|
| 2020 | sage: is_valid_username('math125-TA') |
|---|
| 2021 | False |
|---|
| 2022 | |
|---|
| 2023 | :: |
|---|
| 2024 | |
|---|
| 2025 | sage: is_valid_username('dandrews@sagemath.org') |
|---|
| 2026 | False |
|---|
| 2027 | """ |
|---|
| 2028 | import string |
|---|
| 2029 | |
|---|
| 2030 | if not (len(username) > 3 and len(username) < 33): |
|---|
| 2031 | return False |
|---|
| 2032 | if not username[0] in string.letters: |
|---|
| 2033 | return False |
|---|
| 2034 | if '.' in username: |
|---|
| 2035 | if username.count('.') > 1: |
|---|
| 2036 | return False |
|---|
| 2037 | |
|---|
| 2038 | m = re_valid_username.match(username) |
|---|
| 2039 | return m.start() == 0 and m.end() == len(username) |
|---|
| 2040 | |
|---|
| 2041 | def is_valid_password(password, username): |
|---|
| 2042 | r""" |
|---|
| 2043 | Return True if and only if ``password`` is valid, i.e., |
|---|
| 2044 | is between 6 and 32 characters long, doesn't contain space(s), and |
|---|
| 2045 | doesn't contain ``username``. |
|---|
| 2046 | |
|---|
| 2047 | EXAMPLES:: |
|---|
| 2048 | |
|---|
| 2049 | sage: from sage.server.notebook.twist import is_valid_password |
|---|
| 2050 | sage: is_valid_password('uip@un7!', None) |
|---|
| 2051 | True |
|---|
| 2052 | sage: is_valid_password('markusup89', None) |
|---|
| 2053 | True |
|---|
| 2054 | sage: is_valid_password('8u7', None) |
|---|
| 2055 | False |
|---|
| 2056 | sage: is_valid_password('fUmDagaz8LmtonAowjSe0Pvu9C5Gvr6eKcC6wsAT', None) |
|---|
| 2057 | False |
|---|
| 2058 | sage: is_valid_password('rrcF !u78!', None) |
|---|
| 2059 | False |
|---|
| 2060 | sage: is_valid_password('markusup89', 'markus') |
|---|
| 2061 | False |
|---|
| 2062 | """ |
|---|
| 2063 | import string |
|---|
| 2064 | if len(password) < 6 or len(password) > 32 or ' ' in password: |
|---|
| 2065 | return False |
|---|
| 2066 | if username: |
|---|
| 2067 | if string.lower(username) in string.lower(password): |
|---|
| 2068 | return False |
|---|
| 2069 | return True |
|---|
| 2070 | |
|---|
| 2071 | def do_passwords_match(pass1, pass2): |
|---|
| 2072 | """ |
|---|
| 2073 | EXAMPLES:: |
|---|
| 2074 | |
|---|
| 2075 | sage: from sage.server.notebook.twist import do_passwords_match |
|---|
| 2076 | sage: do_passwords_match('momcat', 'mothercat') |
|---|
| 2077 | False |
|---|
| 2078 | sage: do_passwords_match('mothercat', 'mothercat') |
|---|
| 2079 | True |
|---|
| 2080 | """ |
|---|
| 2081 | return pass1 == pass2 |
|---|
| 2082 | |
|---|
| 2083 | def is_valid_email(email): |
|---|
| 2084 | """ |
|---|
| 2085 | from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/65215 |
|---|
| 2086 | |
|---|
| 2087 | EXAMPLES:: |
|---|
| 2088 | |
|---|
| 2089 | sage: from sage.server.notebook.twist import is_valid_email |
|---|
| 2090 | sage: is_valid_email('joe@washinton.gov') |
|---|
| 2091 | True |
|---|
| 2092 | sage: is_valid_email('joe.washington.gov') |
|---|
| 2093 | False |
|---|
| 2094 | """ |
|---|
| 2095 | if len(email) > 7: |
|---|
| 2096 | if re.match("^[a-zA-Z0-9._%-]+@[a-zA-Z0-9._%-]+\.[a-zA-Z]{2,6}$", email) != None: |
|---|
| 2097 | return True |
|---|
| 2098 | return False |
|---|
| 2099 | |
|---|
| 2100 | class RegistrationPage(resource.PostableResource): |
|---|
| 2101 | def __init__(self, userdb): |
|---|
| 2102 | self.userdb = userdb |
|---|
| 2103 | |
|---|
| 2104 | def render(self, request): |
|---|
| 2105 | input_boxes = ['username', 'password', 'retype_password'] |
|---|
| 2106 | if notebook.conf()['email']: |
|---|
| 2107 | input_boxes.append('email') |
|---|
| 2108 | is_valid_dict = {'username': is_valid_username, 'password': is_valid_password, |
|---|
| 2109 | 'retype_password': do_passwords_match, 'email': is_valid_email} |
|---|
| 2110 | |
|---|
| 2111 | missing = [False] * len(input_boxes) |
|---|
| 2112 | filled_in = {} |
|---|
| 2113 | template_dict = {} |
|---|
| 2114 | |
|---|
| 2115 | def errors_found(): |
|---|
| 2116 | for key, value in filled_in.iteritems(): |
|---|
| 2117 | template_dict[key] = value |
|---|
| 2118 | minus = 1 if 'email' in template_dict else 0 |
|---|
| 2119 | size = len(template_dict) - minus |
|---|
| 2120 | count = 0 |
|---|
| 2121 | boxes = [] |
|---|
| 2122 | if size == 1: |
|---|
| 2123 | return ('',) |
|---|
| 2124 | for i in template_dict: |
|---|
| 2125 | if '_' in i: |
|---|
| 2126 | count += 1 |
|---|
| 2127 | plural = True if count > 1 else False |
|---|
| 2128 | template_dict['error'] = 'Es ' if plural else 'E ' |
|---|
| 2129 | |
|---|
| 2130 | if notebook.conf()['email']: |
|---|
| 2131 | template_dict['email'] = True |
|---|
| 2132 | |
|---|
| 2133 | if set(input_boxes) <= set(request.args): |
|---|
| 2134 | for i, box in enumerate(input_boxes): |
|---|
| 2135 | filled_in[box] = request.args[box][0] |
|---|
| 2136 | if box == 'retype_password': |
|---|
| 2137 | if not is_valid_dict[box](filled_in[box], filled_in['password']): |
|---|
| 2138 | template_dict['passwords_dont_match'] = True |
|---|
| 2139 | elif box == 'password': |
|---|
| 2140 | if 'username' in filled_in: |
|---|
| 2141 | u = filled_in['username'] |
|---|
| 2142 | else: |
|---|
| 2143 | u = None |
|---|
| 2144 | if not is_valid_dict[box](filled_in[box], u): |
|---|
| 2145 | template_dict['password_invalid'] = True |
|---|
| 2146 | else: |
|---|
| 2147 | if not is_valid_dict[box](filled_in[box]): |
|---|
| 2148 | template_dict[box + '_invalid'] = True |
|---|
| 2149 | else: |
|---|
| 2150 | for i, box in enumerate(input_boxes): |
|---|
| 2151 | if not box in request.args: |
|---|
| 2152 | missing[i] = True |
|---|
| 2153 | |
|---|
| 2154 | if set(missing) == set([True]): |
|---|
| 2155 | return HTMLResponse(stream=template('registration.html', **template_dict)) |
|---|
| 2156 | elif set(missing) == set([False]): |
|---|
| 2157 | for i, box in enumerate(input_boxes): |
|---|
| 2158 | filled_in[box] = request.args[box][0] |
|---|
| 2159 | if box == 'retype_password': |
|---|
| 2160 | if not is_valid_dict[box](filled_in[box], filled_in['password']): |
|---|
| 2161 | template_dict['passwords_dont_match'] = True |
|---|
| 2162 | elif box == 'password': |
|---|
| 2163 | if 'username' in filled_in: |
|---|
| 2164 | u = filled_in['username'] |
|---|
| 2165 | else: |
|---|
| 2166 | u = None |
|---|
| 2167 | if not is_valid_dict[box](filled_in[box], u): |
|---|
| 2168 | template_dict['password_invalid'] = True |
|---|
| 2169 | else: |
|---|
| 2170 | if not is_valid_dict[box](filled_in[box]): |
|---|
| 2171 | template_dict[box + '_invalid'] = True |
|---|
| 2172 | else: |
|---|
| 2173 | for i, value in enumerate(missing): |
|---|
| 2174 | if value: |
|---|
| 2175 | template_dict[input_boxes[i] + '_missing'] = True |
|---|
| 2176 | elif not value: |
|---|
| 2177 | filled_in[input_boxes[i]] = request.args[input_boxes[i]][0] |
|---|
| 2178 | |
|---|
| 2179 | if template_dict and set(template_dict) != set(['email']): |
|---|
| 2180 | errors_found() |
|---|
| 2181 | return HTMLResponse(stream=template('registration.html', **template_dict)) |
|---|
| 2182 | else: |
|---|
| 2183 | try: |
|---|
| 2184 | e = filled_in['email'] if notebook.conf()['email'] else '' |
|---|
| 2185 | self.userdb.add_user(filled_in['username'], request.args['password'][0], |
|---|
| 2186 | e) |
|---|
| 2187 | except ValueError: |
|---|
| 2188 | template_dict['username_taken'] = True |
|---|
| 2189 | errors_found() |
|---|
| 2190 | return HTMLResponse(stream=template('registration.html', **template_dict)) |
|---|
| 2191 | |
|---|
| 2192 | if notebook.conf()['email']: |
|---|
| 2193 | destaddr = filled_in['email'] |
|---|
| 2194 | from sage.server.notebook.smtpsend import send_mail |
|---|
| 2195 | from sage.server.notebook.register import make_key, build_msg |
|---|
| 2196 | # TODO: make this come from the server settings |
|---|
| 2197 | key = make_key() |
|---|
| 2198 | listenaddr = notebook.address |
|---|
| 2199 | port = notebook.port |
|---|
| 2200 | fromaddr = 'no-reply@%s' % listenaddr |
|---|
| 2201 | body = build_msg(key, filled_in['username'], listenaddr, port, |
|---|
| 2202 | notebook.secure) |
|---|
| 2203 | |
|---|
| 2204 | # Send a confirmation message to the user. |
|---|
| 2205 | try: |
|---|
| 2206 | send_mail(fromaddr, destaddr, "Sage Notebook Registration",body) |
|---|
| 2207 | waiting[key] = filled_in['username'] |
|---|
| 2208 | except ValueError: |
|---|
| 2209 | pass |
|---|
| 2210 | |
|---|
| 2211 | template_dict = {'accounts': notebook.get_accounts(), |
|---|
| 2212 | 'default_user': notebook.default_user(), |
|---|
| 2213 | 'welcome': filled_in['username'], |
|---|
| 2214 | 'recovery': notebook.conf()['email']} |
|---|
| 2215 | return HTMLResponse(stream=template('login.html', **template_dict)) |
|---|
| 2216 | |
|---|
| 2217 | class ForgotPassPage(resource.Resource): |
|---|
| 2218 | |
|---|
| 2219 | def render(self, request): |
|---|
| 2220 | if not notebook.conf()['email']: |
|---|
| 2221 | return HTMLResponse(stream=message('The account recovery system is not active.')) |
|---|
| 2222 | |
|---|
| 2223 | if request.args.has_key('username'): |
|---|
| 2224 | def error(msg): |
|---|
| 2225 | return HTMLResponse(stream=message(msg, '/forgotpass')) |
|---|
| 2226 | |
|---|
| 2227 | try: |
|---|
| 2228 | import string |
|---|
| 2229 | user = notebook.user(request.args[string.strip('username')][0]) |
|---|
| 2230 | except KeyError: |
|---|
| 2231 | return error('Username is invalid.') |
|---|
| 2232 | |
|---|
| 2233 | if not user.is_email_confirmed(): |
|---|
| 2234 | return error("The e-mail address hasn't been confirmed.") |
|---|
| 2235 | |
|---|
| 2236 | from random import choice |
|---|
| 2237 | import string |
|---|
| 2238 | chara = string.letters + string.digits |
|---|
| 2239 | old_pass = user.password() |
|---|
| 2240 | password = ''.join([choice(chara) for i in range(8)]) |
|---|
| 2241 | user.set_password(password) |
|---|
| 2242 | |
|---|
| 2243 | from sage.server.notebook.smtpsend import send_mail |
|---|
| 2244 | from sage.server.notebook.register import build_password_msg |
|---|
| 2245 | # TODO: make this come from the server settings |
|---|
| 2246 | |
|---|
| 2247 | listenaddr = notebook.address |
|---|
| 2248 | port = notebook.port |
|---|
| 2249 | fromaddr = 'no-reply@%s' % listenaddr |
|---|
| 2250 | body = build_password_msg(password, request.args[string.strip('username')][0], listenaddr, port, notebook.secure) |
|---|
| 2251 | destaddr = user.get_email() |
|---|
| 2252 | try: |
|---|
| 2253 | send_mail(fromaddr, destaddr, "Sage Notebook Account Recovery",body) |
|---|
| 2254 | except ValueError: |
|---|
| 2255 | # the email address is invalid |
|---|
| 2256 | user.set_password(oldpass) |
|---|
| 2257 | return error("The new password couldn't be sent."%destaddr) |
|---|
| 2258 | |
|---|
| 2259 | return HTMLResponse(stream=message("A new password has been sent to your e-mail address.", '/')) |
|---|
| 2260 | else: |
|---|
| 2261 | s = template('account_recovery.html') |
|---|
| 2262 | return HTMLResponse(stream=s) |
|---|
| 2263 | |
|---|
| 2264 | class ListOfUsers(resource.Resource): |
|---|
| 2265 | addSlash = True |
|---|
| 2266 | |
|---|
| 2267 | def __init__(self, username): |
|---|
| 2268 | self.username = username |
|---|
| 2269 | |
|---|
| 2270 | def render(self, ctx): |
|---|
| 2271 | template_dict = {} |
|---|
| 2272 | |
|---|
| 2273 | if 'reset' in ctx.args: |
|---|
| 2274 | user = ctx.args['reset'][0] |
|---|
| 2275 | from random import choice |
|---|
| 2276 | import string |
|---|
| 2277 | chara = string.letters + string.digits |
|---|
| 2278 | password = ''.join([choice(chara) for i in range(8)]) |
|---|
| 2279 | try: |
|---|
| 2280 | U = notebook.user(user) |
|---|
| 2281 | U.set_password(password) |
|---|
| 2282 | except KeyError: |
|---|
| 2283 | pass |
|---|
| 2284 | template_dict['reset'] = [user, password] |
|---|
| 2285 | |
|---|
| 2286 | if 'suspension' in ctx.args: |
|---|
| 2287 | user = ctx.args['suspension'][0] |
|---|
| 2288 | try: |
|---|
| 2289 | U = notebook.user(user) |
|---|
| 2290 | U.set_suspension() |
|---|
| 2291 | except KeyError: |
|---|
| 2292 | pass |
|---|
| 2293 | |
|---|
| 2294 | template_dict['number_of_users'] = len(notebook.valid_login_names()) if len(notebook.valid_login_names()) > 1 else None |
|---|
| 2295 | users = sorted(notebook.valid_login_names()) |
|---|
| 2296 | del users[users.index('admin')] |
|---|
| 2297 | template_dict['users'] = [notebook.user(i) for i in users] |
|---|
| 2298 | return HTMLResponse(stream = template('user_management.html', **template_dict)) |
|---|
| 2299 | |
|---|
| 2300 | class AdminAddUser(resource.PostableResource): |
|---|
| 2301 | def __init__(self, userdb): |
|---|
| 2302 | self.userdb = userdb |
|---|
| 2303 | |
|---|
| 2304 | def render(self, request): |
|---|
| 2305 | |
|---|
| 2306 | if 'username' in request.args: |
|---|
| 2307 | username = request.args['username'][0] |
|---|
| 2308 | if not is_valid_username(username): |
|---|
| 2309 | return HTMLResponse(stream=template('admin_add_user.html', error='username_invalid', username=username)) |
|---|
| 2310 | |
|---|
| 2311 | from random import choice |
|---|
| 2312 | import string |
|---|
| 2313 | chara = string.letters + string.digits |
|---|
| 2314 | password = ''.join([choice(chara) for i in range(8)]) |
|---|
| 2315 | if username in notebook.usernames(): |
|---|
| 2316 | return HTMLResponse(stream=template('admin_add_user.html', error='username_taken', username=username)) |
|---|
| 2317 | notebook.add_user(username, password, '', force=True) |
|---|
| 2318 | return HTMLResponse(stream=message('The temporary password for the new user <em>%s</em> is <em>%s</em>' % (username, password), '/adduser')) |
|---|
| 2319 | else: |
|---|
| 2320 | return HTMLResponse(stream=template('admin_add_user.html')) |
|---|
| 2321 | |
|---|
| 2322 | class InvalidPage(resource.Resource): |
|---|
| 2323 | addSlash = True |
|---|
| 2324 | |
|---|
| 2325 | def __init__(self, msg, username): |
|---|
| 2326 | self.msg = msg |
|---|
| 2327 | self.username = username |
|---|
| 2328 | |
|---|
| 2329 | def render(self, ctx): |
|---|
| 2330 | if self.msg: |
|---|
| 2331 | s = self.msg |
|---|
| 2332 | else: |
|---|
| 2333 | s = "This is an invalid page." |
|---|
| 2334 | if self.username == 'guest': |
|---|
| 2335 | s += ' You might have to login to view this page.' |
|---|
| 2336 | return HTMLResponse(stream = message(s, '/')) |
|---|
| 2337 | |
|---|
| 2338 | def childFactory(self, request, name): |
|---|
| 2339 | return InvalidPage(msg = self.msg, username = self.username) |
|---|
| 2340 | |
|---|
| 2341 | |
|---|
| 2342 | class RedirectLogin(resource.PostableResource): |
|---|
| 2343 | def render(self, ctx): |
|---|
| 2344 | return http.RedirectResponse('/') |
|---|
| 2345 | def childFactory(self, request, name): |
|---|
| 2346 | return RedirectLogin() |
|---|
| 2347 | |
|---|
| 2348 | import sage.server.simple.twist |
|---|
| 2349 | |
|---|
| 2350 | class Toplevel(resource.PostableResource): |
|---|
| 2351 | child_login = RedirectLogin() |
|---|
| 2352 | child_simple = sage.server.simple.twist.SimpleServer() |
|---|
| 2353 | |
|---|
| 2354 | def __init__(self, cookie, username): |
|---|
| 2355 | self.cookie = cookie |
|---|
| 2356 | self.username = username if username else 'guest' |
|---|
| 2357 | |
|---|
| 2358 | def render(self, ctx): |
|---|
| 2359 | template_dict = {'accounts': notebook.get_accounts(), |
|---|
| 2360 | 'default_user': notebook.default_user(), |
|---|
| 2361 | 'recovery': notebook.conf()['email']} |
|---|
| 2362 | return HTMLResponse(stream=template('login.html', **template_dict)) |
|---|
| 2363 | |
|---|
| 2364 | def userchildFactory(self, request, name): |
|---|
| 2365 | return InvalidPage(msg = "unauthorized request", username = self.username) |
|---|
| 2366 | |
|---|
| 2367 | def childFactory(self, request, name): |
|---|
| 2368 | return self.userchildFactory(request, name) |
|---|
| 2369 | |
|---|
| 2370 | setattr(Toplevel, 'child_favicon.ico', static.File(image_path + '/favicon.ico')) |
|---|
| 2371 | |
|---|
| 2372 | class LoginResourceClass(resource.Resource): |
|---|
| 2373 | def render(self, ctx): |
|---|
| 2374 | template_dict = {'accounts': notebook.get_accounts(), |
|---|
| 2375 | 'default_user': notebook.default_user(), |
|---|
| 2376 | 'recovery': notebook.conf()['email']} |
|---|
| 2377 | return HTMLResponse(stream=template('login.html', **template_dict)) |
|---|
| 2378 | |
|---|
| 2379 | def childFactory(self, request, name): |
|---|
| 2380 | return LoginResource |
|---|
| 2381 | |
|---|
| 2382 | LoginResource = LoginResourceClass() |
|---|
| 2383 | |
|---|
| 2384 | class AnonymousToplevel(Toplevel): |
|---|
| 2385 | from sage.server.notebook.avatars import PasswordChecker |
|---|
| 2386 | addSlash = True |
|---|
| 2387 | child_register = RegistrationPage(PasswordChecker()) |
|---|
| 2388 | child_confirm = RegConfirmation() |
|---|
| 2389 | child_forgotpass = ForgotPassPage() |
|---|
| 2390 | |
|---|
| 2391 | child_images = Images() |
|---|
| 2392 | child_css = CSS() |
|---|
| 2393 | child_javascript = Javascript() |
|---|
| 2394 | child_javascript_local = JavascriptLocal() |
|---|
| 2395 | child_java = Java() |
|---|
| 2396 | child_logout = RedirectLogin() |
|---|
| 2397 | |
|---|
| 2398 | def userchildFactory(self, request, name): |
|---|
| 2399 | # This is called from Toplevel above |
|---|
| 2400 | try: |
|---|
| 2401 | return AnonymousToplevel.__dict__['userchild_%s'%name](username = self.username) |
|---|
| 2402 | except KeyError: |
|---|
| 2403 | pass |
|---|
| 2404 | |
|---|
| 2405 | userchild_home = Worksheets |
|---|
| 2406 | userchild_pub = PublicWorksheets |
|---|
| 2407 | userchild_src = SourceBrowser |
|---|
| 2408 | |
|---|
| 2409 | #child_login = LoginResource |
|---|
| 2410 | |
|---|
| 2411 | def render(self, ctx): |
|---|
| 2412 | template_dict = {'accounts': notebook.get_accounts(), |
|---|
| 2413 | 'default_user': notebook.default_user(), |
|---|
| 2414 | 'recovery': notebook.conf()['email']} |
|---|
| 2415 | response = HTMLResponse(stream=template('login.html', **template_dict)) |
|---|
| 2416 | response.headers.setHeader("set-cookie", [http_headers.Cookie('cookie_test', 'cookie_test')]) |
|---|
| 2417 | return response |
|---|
| 2418 | |
|---|
| 2419 | class FailedToplevel(Toplevel): |
|---|
| 2420 | def __init__(self, info, problem, username=None): |
|---|
| 2421 | self.info = info |
|---|
| 2422 | self.problem= problem |
|---|
| 2423 | self.username = username |
|---|
| 2424 | |
|---|
| 2425 | def render(self, ctx): |
|---|
| 2426 | # Since public access is allowed, which lists usernames in the published |
|---|
| 2427 | # worksheets and ratings, this gives no new information away. |
|---|
| 2428 | # If published pages were disabled, then this should be disabled too. |
|---|
| 2429 | if self.problem == 'username': |
|---|
| 2430 | template_dict = {'accounts': notebook.get_accounts(), |
|---|
| 2431 | 'default_user': notebook.default_user(), |
|---|
| 2432 | 'username_error': True, |
|---|
| 2433 | 'recovery': notebook.conf()['email']} |
|---|
| 2434 | return HTMLResponse(stream=template('login.html', **template_dict)) |
|---|
| 2435 | elif self.problem == 'password': |
|---|
| 2436 | template_dict = {'accounts': notebook.get_accounts(), |
|---|
| 2437 | 'default_user': self.username, |
|---|
| 2438 | 'password_error': True, |
|---|
| 2439 | 'recovery': notebook.conf()['email']} |
|---|
| 2440 | return HTMLResponse(stream=template('login.html', **template_dict)) |
|---|
| 2441 | elif self.problem == 'suspended': |
|---|
| 2442 | return HTMLResponse(stream = message("Your account is currently suspended.")) |
|---|
| 2443 | else: |
|---|
| 2444 | return HTMLResponse(stream = message("Please enable cookies and try again.")) |
|---|
| 2445 | |
|---|
| 2446 | |
|---|
| 2447 | class UserToplevel(Toplevel): |
|---|
| 2448 | addSlash = True |
|---|
| 2449 | |
|---|
| 2450 | child_pdf = DocPDF() |
|---|
| 2451 | child_images = Images() |
|---|
| 2452 | child_css = CSS() |
|---|
| 2453 | child_javascript = Javascript() |
|---|
| 2454 | child_javascript_local = JavascriptLocal() |
|---|
| 2455 | child_java = Java() |
|---|
| 2456 | |
|---|
| 2457 | child_logout = Logout() |
|---|
| 2458 | |
|---|
| 2459 | child_confirm = RegConfirmation() |
|---|
| 2460 | |
|---|
| 2461 | |
|---|
| 2462 | |
|---|
| 2463 | #child_login = RedirectLogin() |
|---|
| 2464 | |
|---|
| 2465 | # userchild_* is like Twisted's child_, etc. except StandardError: |
|---|
| 2466 | # (1) it also sets the username in the __init__ method, and |
|---|
| 2467 | # (2) it calls the constructor for the object, i.e., it is |
|---|
| 2468 | # a class rather than an object. |
|---|
| 2469 | # NOTE: If you overload childFactory in any derived class, you |
|---|
| 2470 | # better call userchildFactory it in the base class (Toplevel)! |
|---|
| 2471 | def userchildFactory(self, request, name): |
|---|
| 2472 | try: |
|---|
| 2473 | return UserToplevel.__dict__['userchild_%s'%name](username = self.username) |
|---|
| 2474 | except KeyError: |
|---|
| 2475 | pass |
|---|
| 2476 | |
|---|
| 2477 | userchild_doc = Doc |
|---|
| 2478 | userchild_sagetex = SageTex |
|---|
| 2479 | userchild_help = Help |
|---|
| 2480 | userchild_history = History |
|---|
| 2481 | userchild_home = Worksheets |
|---|
| 2482 | userchild_live_history = LiveHistory |
|---|
| 2483 | userchild_new_worksheet = NewWorksheet |
|---|
| 2484 | userchild_notebook_settings = NotebookSettings |
|---|
| 2485 | userchild_settings = SettingsPage |
|---|
| 2486 | userchild_pub = PublicWorksheets |
|---|
| 2487 | userchild_upload = Upload |
|---|
| 2488 | userchild_download_worksheets = DownloadWorksheets |
|---|
| 2489 | |
|---|
| 2490 | userchild_send_to_trash = SendWorksheetToTrash |
|---|
| 2491 | userchild_send_to_archive = SendWorksheetToArchive |
|---|
| 2492 | userchild_send_to_active = SendWorksheetToActive |
|---|
| 2493 | userchild_send_to_stop = SendWorksheetToStop |
|---|
| 2494 | |
|---|
| 2495 | userchild_src = SourceBrowser |
|---|
| 2496 | userchild_upload_worksheet = UploadWorksheet |
|---|
| 2497 | userchild_emptytrash = EmptyTrash |
|---|
| 2498 | |
|---|
| 2499 | def render(self, request): |
|---|
| 2500 | # This resource always does a redirect to the user's home directory |
|---|
| 2501 | # so that after login (which is a POST operation), the postdata will not remain |
|---|
| 2502 | # in the browser on return. This method is sometimes |
|---|
| 2503 | # referred to as the post-redirect-get method. |
|---|
| 2504 | response = http.RedirectResponse("/home/%s" % self.username) |
|---|
| 2505 | # This allows a Notebook user to select a "remember me" checkbox and not have to |
|---|
| 2506 | # sign back in when she restarts her web browser |
|---|
| 2507 | # This works by setting an expiration date because without one the browser forgets the cookie. |
|---|
| 2508 | if 'remember' in request.args: |
|---|
| 2509 | response.headers.setHeader("set-cookie", [http_headers.Cookie('nb_session', self.cookie, expires=(time.time() + 60 * 60 * 24 * 14)), http_headers.Cookie('cookie_test', self.cookie, expires=1)]) |
|---|
| 2510 | else: |
|---|
| 2511 | response.headers.setHeader("set-cookie", [http_headers.Cookie('nb_session', self.cookie), http_headers.Cookie('cookie_test', self.cookie, expires=1)]) |
|---|
| 2512 | return response |
|---|
| 2513 | |
|---|
| 2514 | |
|---|
| 2515 | class AdminToplevel(UserToplevel): |
|---|
| 2516 | addSlash = True |
|---|
| 2517 | |
|---|
| 2518 | userchild_home = WorksheetsAdmin |
|---|
| 2519 | child_users = ListOfUsers |
|---|
| 2520 | child_adduser = AdminAddUser |
|---|
| 2521 | |
|---|
| 2522 | def user_type(username): |
|---|
| 2523 | # one of admin, guest, user |
|---|
| 2524 | try: |
|---|
| 2525 | U = notebook.user(username) |
|---|
| 2526 | except KeyError: |
|---|
| 2527 | return 'guest' |
|---|
| 2528 | return U.account_type() |
|---|
| 2529 | |
|---|
| 2530 | def extract_title(html_page): |
|---|
| 2531 | h = html_page.lower() |
|---|
| 2532 | i = h.find('<title>') |
|---|
| 2533 | if i == -1: |
|---|
| 2534 | return "Untitled" |
|---|
| 2535 | j = h.find('</title>') |
|---|
| 2536 | return html_page[i + len('<title>') : j] |
|---|