Ticket #3950: trac_3950.patch
| File trac_3950.patch, 27.2 kB (added by mhansen, 1 month ago) |
|---|
-
a/sage/server/notebook/notebook.py
old new 94 94 # This must happen after twist.notebook is set. 95 95 self.save() 96 96 97 def all_worksheets(self): 98 return self.__worksheets 97 99 98 100 def _migrate_worksheets(self): 99 101 v = [] … … 1296 1298 1297 1299 X = self.get_worksheets_with_viewer(user) 1298 1300 if typ == "trash": 1299 worksheet_heading = " Trash"1301 worksheet_heading = "Deleted Worksheets" 1300 1302 W = [x for x in X if x.is_trashed(user)] 1301 1303 elif typ == "active": 1302 1304 worksheet_heading = "Active Worksheets" … … 1311 1313 1312 1314 top = self.html_worksheet_list_top(user, typ=typ, search=search) 1313 1315 list = self.html_worksheet_list(W, user, worksheet_heading, sort=sort, reverse=reverse, typ=typ) 1314 worksheet_filenames = [x.filename() for x in W]1315 1316 1316 1317 s = """ 1317 1318 <html> … … 1336 1337 else: 1337 1338 entries.append(('/home/%s'%user, 'Home', 'Back to your personal worksheet list')) 1338 1339 entries.append(('/pub', 'Published', 'Browse the published worksheets')) 1339 #entries.append(('/settings', 'Settings', 'Change user settings')) # TODO -- settings1340 1340 entries.append(('help()', 'Help', 'Documentation')) 1341 1341 1342 1342 ## TODO -- settings -
a/sage/server/notebook/template.py
old new 12 12 HTML templating for the notebook 13 13 14 14 AUTHORS: 15 -- Bobby Moretti 16 -- Timothy Clemans 15 -- Bobby Moretti (2007-07-18): initial version 16 -- Timothy Clemans and Mike Hansen (2008-10-27): major update 17 17 """ 18 18 19 from jinja import Environment, FileSystemLoader 20 from jinja.exceptions import TemplateNotFound 21 from os.path import join, exists, getmtime 22 from sage.misc.misc import SAGE_ROOT 19 import jinja 20 import sage.misc.misc as misc 23 21 24 env = Environment(loader=FileSystemLoader(SAGE_ROOT+"/devel/sage/sage/server/notebook/templates"))22 env = jinja.Environment(loader=jinja.FileSystemLoader(misc.SAGE_ROOT + '/devel/sage/sage/server/notebook/templates')) 25 23 26 class PageTemplate: 27 def __init__(self, filename): 28 self.__template = env.get_template(filename) 24 def contained_in(container): 25 def wrapped(env, context, value): 26 return value in container 27 return wrapped 29 28 30 def __call__(self, **kwds): 31 return str(self.__template.render(kwds)) 29 env.tests['contained_in'] = contained_in 32 30 33 # Define variables for each template 34 G = globals() 35 templates = ['login', 'yes_no', 'registration', 'account_settings', 'account_recovery', 'user_management', 'error_message'] 36 for name in templates: 37 G[name] = PageTemplate('%s.html'%name) 31 default_context = {'sitename': 'Sage Notebook'} 32 33 def template(filename, **context): 34 """ 35 Returns a compiled template. 36 """ 37 try: 38 tmpl = env.get_template(filename) 39 except jinja.exceptions.TemplateNotFound: 40 return template('template_error.html') 41 context.update(default_context) 42 return str(tmpl.render(**context)) -
a/sage/server/notebook/templates/base.html
old new 1 1 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"> 2 2 <html lang="en"> 3 3 <head> 4 <title>{% block title %}{% endblock %} | Sage Notebook</title> 5 <link type="text/css" rel="stylesheet" href="/css/{% block css %}master{% endblock %}.css" media="screen" /> 4 <title>{% block title %}{% endblock %} | {{ sitename }}</title> 5 <link type="text/css" rel="stylesheet" href="/css/{% block css %}master{% endblock %}.css" media="screen" /> 6 {% block javascript %}{% endblock %} 6 7 </head> 7 8 <body> 8 9 {% block body %}{% endblock %} -
/dev/null
old new 1 {% include 'top_bar.html' %} 2 {% set actions = True %} 3 {% if not pub %} 4 <a class="boldusercontrol" href="/new_worksheet">New Worksheet</a> 5 <a class="boldusercontrol" href="/upload">Upload</a> 6 {% endif %} 7 <span class="flush-right"> 8 <input id="search_worksheets" size=20 onkeypress="return search_worksheets_enter_pressed(event, '{{ typ }}');" value="{{ search }}"></input> 9 <button class="add_new_worksheet_menu" onClick="search_worksheets('{{ typ }}');">Search Worksheets</button> 10 11 </span> 12 <br> 13 <hr class="usercontrol"> 14 {% if not pub %} 15 {% if typ == 'archive' %} 16 <button onClick="make_active_button();" title="Unarchive selected worksheets so it appears in the default worksheet list">Unarchive</button> 17 {% else %} 18 <button onClick="archive_button();" title="Archive selected worksheets so they do not appear in the default worksheet list">Archive</button> 19 {% endif %} 20 {% if typ != 'trash' %} 21 <button onClick="delete_button();" title="Move the selected worksheets to the trash">Delete</button> 22 {% else %} 23 <button onClick="make_active_button();" title="Move the selected worksheets out of the trash">Undelete</button> 24 {% endif %} 25 26 <button onClick="stop_worksheets_button();" title="Stop selected worksheets">Stop</button> 27 28 <span> 29 30 Current Folder: 31 <a class="{{ 'bold' if typ == 'active' else '' }}usercontrol" href=".">Active</a> 32 <a class="{{ 'bold' if typ == 'archive' else '' }}usercontrol" href=".?typ=archive">Archived</a> 33 <a class="{{ 'bold' if typ == 'trash' else '' }}usercontrol" href=".?typ=trash">Trash</a> 34 {% if typ == 'trash' %} 35 <a class="boldusercontrol" onClick="empty_trash();return false;" href="">(Empty Trash)</a> 36 {% endif %} 37 </span> 38 {% endif %} -
/dev/null
old new 1 <table width="100%%"><tr><td> 2 <div class="banner"> 3 <table width="100%%"><tr><td> 4 <a class="banner" href="http://www.sagemath.org"><img align="top" src="/images/sagelogo.png" alt="Sage"> Notebook</a></td><td><span class="ping" id="ping">Searching for Sage server...</span></td> 5 </tr><tr><td style="font-size:xx-small; text-indent:13px; color:black">Version {{ version }}</td><td></td></tr></table> 6 </div> 7 </td><td align=right> 8 <span class="username">{{ username }}</span> 9 {% if username == 'guest' %} 10 <span class="vbar"></span><a title="Please log in to the Sage notebook" class="usercontrol" href="/">Log in</a> 11 {% else %} 12 <span class="vbar"></span><a title="Back to your personal worksheet list" class="usercontrol" href="/home/{{ username }}">Home</a> 13 {% if not pub %} 14 <span class="vbar"></span><a title="Browse the published worksheets" class="usercontrol" href="/pub">Published</a> 15 <span class="vbar"></span><a title="View a log of recent computations" class="usercontrol" onClick="history_window()">Log</a> 16 {% else %} 17 <span class="vbar"></span><span class="usercontrol">Published</span> 18 {% endif %} 19 <span class="vbar"></span><a title="Documentation" class="usercontrol" onClick="help()">Help</a> 20 <span class="vbar"></span><a title="Change account settings including password" class="usercontrol" href="/settings">Settings</a> 21 <span class="vbar"></span><a title="Log out of the Sage notebook" class="usercontrol" href="/logout">Sign out</a> 22 {% endif %} 23 </td></tr> 24 </table> 25 <hr class="usercontrol"> -
/dev/null
old new 1 {% extends "base.html" %} 2 {% block title %}{{ worksheet_heading }}{% endblock %} 3 {% block css %}main{% endblock %} 4 {% block javascript %} 5 <script type="text/javascript" src="/javascript/main.js"></script> 6 {% if not pub %} 7 <script type="text/javascript"> 8 var worksheet_filenames = {{ worksheet_filenames }}; 9 </script> 10 {% endif %} 11 {% endblock %} 12 {% block body %} 13 {% include "list_top.html" %} 14 <br><br> 15 <table width="100%" border=0 cellspacing=0 cellpadding=0> 16 <tr class="greybox"><td colspan=4><div class="thinspace"></div></td></tr> 17 <tr class="greybox"> 18 19 {% if not pub %} 20 <td> <input id="controlbox" onClick="set_worksheet_list_checks();" class="entry" type=checkbox></td> 21 {% else %} 22 <td> <a class="listcontrol" href=".?sort=rating">Rating</a></td> 23 {% endif %} 24 25 <td><a class="listcontrol" href=".?typ={{ typ }}&sort=name{{ '' if sort != 'name' or reverse else '&reverse=True' }}">{{ worksheet_heading }}</a> </td> 26 27 <td><a class="listcontrol" href=".?typ={{ typ }}&sort=owner{{ '' if sort != 'owner' or reverse else '&reverse=True' }}">Owner {{ '' if pub else ' / Collaborators' }}</a> </td> 28 <td><a class="listcontrol" href=".?typ={{ typ }}&{{ '' if sort != 'last_edited' or reverse else 'reverse=True' }}">Last Edited</a> </td> 29 </tr> 30 <tr class="greybox"><td colspan=4><div class="thinspace"></div></td></tr> 31 {% if not any_worksheets %} 32 {% if pub %} 33 <tr><td colspan="5" style="padding:20px;text-align:center">There are no published worksheets.</td></tr> 34 {% elif typ == 'active' %} 35 <tr><td colspan="5" style="padding:20px;text-align:center">Welcome to Sage! You can 36 <a href="/new_worksheet">create a new worksheet</a>, view <a href="/pub/">published worksheets</a>, or read the <a href="/help" target="_new">documentation</a>.</td></tr> 37 {% endif %} 38 {% else %} 39 40 {% for worksheet in worksheets %} 41 {% set name = worksheet.filename() %} 42 <tr><td class="entry"> 43 {% if pub %} 44 <a class="worksheet_edit" href="/home/{{ name }}/rating_info">{% if worksheet.rating() < 0 %}---- {% else %} {{ worksheet.rating() }} {% endif %}</a> 45 {% else %} 46 <input type=checkbox unchecked id="{{ name }}"> 47 48 <select onchange="go_option(this);" class="worksheet_edit"> 49 <option value="" title="File options" selected>File</option> 50 <option value="list_rename_worksheet('{{ name }}','{{ worksheet.name() }}');" title="Change the name of this worksheet.">Rename...</option> 51 <option value="list_edit_worksheet('{{ name }}');" title="Open this worksheet and edit it">Edit</option> 52 <option value="list_copy_worksheet('{{ name }}');" title="Copy this worksheet">Copy Worksheet</option> 53 <option value="list_share_worksheet('{{ name }}');" title="Share this worksheet with others">Collaborate</option> 54 <option value="list_publish_worksheet('{{ name }}');" title="Publish this worksheet on the internet">Publish</option> 55 <option value="list_revisions_of_worksheet('{{ name }}');" title="See all revisions of this worksheet">Revisions</option> 56 </select> 57 58 {% endif %} 59 </td> 60 {% if worksheet.is_active(username) %} 61 <td class="worksheet_link"> 62 {% else %} 63 <td class="archived_worksheet_link"> 64 {% endif %} 65 <a title="{{ worksheet_name }}" id="name/{{ name }}" class="worksheetname" href="/home/{{ name }}">{{ worksheet.truncated_name(35) }}</a> {% if not pub and worksheet.is_published() %}(Published){% endif %} 66 </td> 67 <td class="owner_collab"> 68 69 {{ worksheet.owner() }} 70 {% if not pub and typ != 'trash' %} 71 {{ worksheet.collab() }} 72 {% if worksheet.collab() %} 73 <a class="share" href="/home/{{ worksheet.filename() }}/share">Add or Delete</a> 74 {% else %} 75 <a class="share" href="/home/{{ worksheet.filename() }}/share">Share now</a> 76 {% endif %} 77 {% if worksheet.has_published_version() %} 78 <a href="/home/{{ worksheet.published_version().filename() }}">(published) 79 {% endif %} 80 {% endif %} 81 </td> 82 <td><span class="lastedit">{{ worksheet.ws_time_since_last_edited() }} ago by {{ worksheet.last_to_edit() }}</span></td> 83 </tr> 84 <tr class="thingreybox"><td colspan=4><div class="ultrathinspace"></div></td></tr> 85 {% endfor %} 86 {% endif %} 87 </table> 88 {% endblock %} -
a/sage/server/notebook/twist.py
old new 31 31 ############################################################ 32 32 33 33 import os, shutil, time 34 34 from sage.version import version 35 35 import bz2 36 36 37 37 from twisted.web2 import server, http, resource, channel … … 41 41 42 42 import notebook as _notebook 43 43 44 import sage.server.notebook.template astemplate44 from sage.server.notebook.template import template 45 45 46 46 HISTORY_MAX_OUTPUT = 92*5 47 47 HISTORY_NCOLS = 90 … … 118 118 # An error message 119 119 ############################ 120 120 def message(msg, cont=None): 121 return template.error_message(msg=msg, cont=cont) 121 template_dict = {'msg': msg, 'cont': cont} 122 return template('error_message.html', **template_dict) 122 123 123 124 ############################ 124 125 # Create a Sage worksheet from a latex2html'd file … … 930 931 if template_dict['email']: 931 932 template_dict['email_address'] = 'None' if not notebook.user(self.username)._User__email else notebook.user(self.username)._User__email 932 933 template_dict['email_confirmed'] = 'Not confirmed' if not notebook.user(self.username).is_email_confirmed() else 'Confirmed' 933 return http.Response(stream=template .account_settings(**template_dict))934 return http.Response(stream=template('account_settings.html', **template_dict)) 934 935 935 936 ######################################################## 936 937 # Set output type of a cell … … 1307 1308 return static.File(h) 1308 1309 return NotImplementedWorksheetOp(op, self.worksheet) 1309 1310 1311 def worksheets_by_group(user, group): 1312 X = notebook.get_worksheets_with_viewer(user) 1313 if group == "trash": 1314 return ('Trash', [x for x in X if x.is_trashed(user)]) 1315 elif group == "active": 1316 return ('Active Worksheets', [x for x in X if x.is_active(user)]) 1317 else: # typ must be archived or "all" 1318 return ('Archived and Active', [x for x in X if not x.is_trashed(user)]) 1319 1320 def sort_worksheet_list(v, sort, reverse): 1321 """ 1322 INPUT: 1323 sort -- 'last_edited', 'owner', or 'name' 1324 reverse -- if True, reverse the order of the sort. 1325 """ 1326 f = None 1327 if sort == 'last_edited': 1328 def c(a, b): 1329 return -cmp(a.last_edited(), b.last_edited()) 1330 f = c 1331 elif sort == 'name': 1332 def c(a,b): 1333 return cmp((a.name().lower(), -a.last_edited()), (b.name().lower(), -b.last_edited())) 1334 f = c 1335 elif sort == 'owner': 1336 def c(a,b): 1337 return cmp((a.owner().lower(), -a.last_edited()), (b.owner().lower(), -b.last_edited())) 1338 f = c 1339 elif sort == "rating": 1340 def c(a,b): 1341 return -cmp((a.rating(), -a.last_edited()), (b.rating(), -b.last_edited())) 1342 f = c 1343 else: 1344 raise ValueError, "invalid sort key '%s'"%sort 1345 v.sort(cmp = f, reverse=reverse) 1346 1310 1347 def render_worksheet_list(args, pub, username): 1311 if args.has_key('typ'): 1312 typ = args['typ'][0] 1348 template_dict = {'pub': pub, 1349 'typ': args['typ'][0] if 'typ' in args else 'active', 1350 'search': args['search'][0] if 'search' in args else None, 1351 'sort': args['sort'][0] if 'sort' in args else 'last_edited', 1352 'reverse': (args['reverse'][0] == 'True') if 'reverse' in args else False} 1353 typ = template_dict['typ'] 1354 if not pub: 1355 template_dict['group'], template_dict['worksheets'] = worksheets_by_group(username, template_dict['typ']) 1313 1356 else: 1314 typ = 'active' 1315 if args.has_key('search'): 1316 search = args['search'][0] 1317 else: 1318 search = None 1319 if not args.has_key('sort'): 1320 sort = 'last_edited' 1321 else: 1322 sort = args['sort'][0] 1323 if args.has_key('reverse'): 1324 reverse = (args['reverse'][0] == 'True') 1325 else: 1326 reverse = False 1357 template_dict['group'] = '' 1358 template_dict['worksheets'] = [x for x in notebook.all_worksheets().itervalues() if x.is_published() and not x.is_trashed(username)] 1359 sort_worksheet_list(template_dict['worksheets'], template_dict['sort'], template_dict['reverse']) 1360 template_dict['worksheet_filenames'] = [x.filename() for x in template_dict['worksheets']] 1361 template_dict['username'] = username 1362 template_dict['version'] = version 1363 if pub and (not username or username == tuple([])): 1364 template_dict['username'] = 'pub' 1365 1366 template_dict['any_worksheets'] = bool(template_dict['worksheets']) 1367 #k += '<td class="owner_collab">%s</td>'%html_owner_collab_view(w, username, typ) 1368 #k += '<td class="last_edited">%s</td>'%w.html_time_since_last_edited() 1327 1369 1328 1370 if pub: 1329 if username is None or username == tuple([]): 1330 user = 'pub' 1331 else: 1332 user = username 1333 s = notebook.html_worksheet_list_public( 1334 user, sort=sort, reverse=reverse, search=search) 1335 else: 1336 s = notebook.html_worksheet_list_for_user( 1337 username, typ=typ, sort=sort, reverse=reverse, search=search) 1338 return s 1371 template_dict['worksheet_heading'] = "Published Worksheets" 1372 elif template_dict['typ'] == "trash": 1373 template_dict['worksheet_heading'] = "Deleted Worksheets" 1374 #W = [x for x in X if x.is_trashed(user)] 1375 elif template_dict['typ'] == "active": 1376 template_dict['worksheet_heading'] = "Active Worksheets" 1377 #W = [x for x in X if x.is_active(user)] 1378 else: # typ must be archived or "all" 1379 template_dict['worksheet_heading'] = "Archived and Active Worksheets" 1380 #W = [x for x in X if not x.is_trashed(user)] 1381 1382 return template('worksheet_listing.html', **template_dict) 1339 1383 1340 1384 1341 1385 class WorksheetsByUser(resource.Resource): … … 1712 1756 user.set_email_confirmation(True) 1713 1757 except KeyError: 1714 1758 return http.Response(stream=message(invalid_confirm_key, '/register')) 1715 success = """<h1> Hello, %s. Thank you for registering!</h1>""" % username1759 success = """<h1>Email address confirmed for user %s</h1>""" % username 1716 1760 del waiting[key] 1717 1761 return http.Response(stream=message(success)) 1718 1762 … … 1888 1932 missing[i] = True 1889 1933 1890 1934 if set(missing) == set([True]): 1891 return http.Response(stream=template .registration(**template_dict))1935 return http.Response(stream=template('registration.html', **template_dict)) 1892 1936 elif set(missing) == set([False]): 1893 1937 for i, box in enumerate(input_boxes): 1894 1938 filled_in[box] = request.args[box][0] … … 1914 1958 1915 1959 if template_dict and set(template_dict) != set(['email']): 1916 1960 errors_found() 1917 return http.Response(stream=template .registration(**template_dict))1961 return http.Response(stream=template('registration.html', **template_dict)) 1918 1962 else: 1919 1963 try: 1920 1964 e = filled_in['email'] if notebook.conf()['email'] else '' … … 1923 1967 except ValueError: 1924 1968 template_dict['username_taken'] = True 1925 1969 errors_found() 1926 return http.Response(stream=template .registration(**template_dict))1970 return http.Response(stream=template('registration.html', **template_dict)) 1927 1971 1928 1972 if notebook.conf()['email']: 1929 1973 destaddr = filled_in['email'] … … 1944 1988 except ValueError: 1945 1989 pass 1946 1990 1947 return http.Response(stream=template.login(accounts=notebook.get_accounts(), 1948 default_user=notebook.default_user(), welcome=filled_in['username'], 1949 recovery=notebook.conf()['email'])) 1991 template_dict = {'accounts': notebook.get_accounts(), 1992 'default_user': notebook.default_user(), 1993 'welcome': filled_in['username'], 1994 'recovery': notebook.conf()['email']} 1995 return http.Response(stream=template('login.html', **template_dict)) 1950 1996 1951 1997 class ForgotPassPage(resource.Resource): 1952 1998 … … 1992 2038 1993 2039 return http.Response(stream=message("A new password has been sent to your e-mail address.", '/')) 1994 2040 else: 1995 s = template .account_recovery()2041 s = template('account_recovery.html') 1996 2042 return http.Response(stream=s) 1997 2043 1998 2044 class ListOfUsers(resource.Resource): … … 2003 2049 if user_type(self.username) != 'admin': 2004 2050 s = message('You must an admin to manage other users.') 2005 2051 else: 2006 s = template .user_management(users=notebook.valid_login_names())2052 s = template('user_management.html', {'users':notebook.valid_login_names()}) 2007 2053 return http.Response(stream = s) 2008 2054 2009 2055 class InvalidPage(resource.Resource): … … 2043 2089 self.username = username if username else 'guest' 2044 2090 2045 2091 def render(self, ctx): 2046 return http.Response(stream = template.login(accounts = notebook.get_accounts(), default_user = notebook.default_user(), recovery=notebook.conf()['email'])) 2047 2092 template_dict = {'accounts': notebook.get_accounts(), 2093 'default_user': notebook.default_user(), 2094 'recovery': notebook.conf()['email']} 2095 return http.Response(stream=template('login.html', **template_dict)) 2096 2048 2097 def userchildFactory(self, request, name): 2049 2098 return InvalidPage(msg = "unauthorized request", username = self.username) 2050 2099 … … 2055 2104 2056 2105 class LoginResourceClass(resource.Resource): 2057 2106 def render(self, ctx): 2058 return http.Response(stream = template.login(accounts=notebook.get_accounts(), default_user=notebook.default_user(), recovery=notebook.conf()['email'])) 2107 template_dict = {'accounts': notebook.get_accounts(), 2108 'default_user': notebook.default_user(), 2109 'recovery': notebook.conf()['email']} 2110 return http.Response(stream=template('login.html', **template_dict)) 2059 2111 2060 2112 def childFactory(self, request, name): 2061 2113 return LoginResource … … 2090 2142 #child_login = LoginResource 2091 2143 2092 2144 def render(self, ctx): 2093 response = http.Response(stream = template.login(accounts=notebook.get_accounts(), default_user=notebook.default_user(), recovery=notebook.conf()['email'])) 2145 template_dict = {'accounts': notebook.get_accounts(), 2146 'default_user': notebook.default_user(), 2147 'recovery': notebook.conf()['email']} 2148 response = http.Response(stream=template('login.html', **template_dict)) 2094 2149 response.headers.setHeader("set-cookie", [http_headers.Cookie('cookie_test', 'cookie_test')]) 2095 2150 return response 2096 2151 … … 2105 2160 # worksheets and ratings, this gives no new information way. 2106 2161 # If published pages were disabled, then this should be disabled too. 2107 2162 if self.problem == 'username': 2108 return http.Response(stream = template.login(accounts=notebook.get_accounts(), default_user='', username_error=True, recovery=notebook.conf()['email'])) 2163 template_dict = {'accounts': notebook.get_accounts(), 2164 'default_user': notebook.default_user(), 2165 'username_error': True, 2166 'recovery': notebook.conf()['email']} 2167 return http.Response(stream=template('login.html', **template_dict)) 2109 2168 elif self.problem == 'password': 2110 return http.Response(stream = template.login(accounts=notebook.get_accounts(), default_user=self.username, password_error=True, recovery=notebook.conf()['email'])) 2169 template_dict = {'accounts': notebook.get_accounts(), 2170 'default_user': self.username, 2171 'password_error': True, 2172 'recovery': notebook.conf()['email']} 2173 return http.Response(stream=template('login.html', **template_dict)) 2111 2174 else: 2112 2175 return http.Response(stream = message("Please enable cookies and try again.")) 2113 2176 -
a/sage/server/notebook/worksheet.py
old new 387 387 return self.__collaborators 388 388 except AttributeError: 389 389 self.__collaborators = [] 390 return self.__collaborators 390 return self.__collaborators 391 392 def collab(self): 393 collab = [x for x in self.collaborators() if x != self.owner()] 394 collaborators = ', '.join([x for x in collab]) 395 if len(collaborators) > 21: 396 collaborators = collaborators[:21] + '...' 397 return collaborators 391 398 392 399 def set_collaborators(self, v): 393 400 """ … … 2165 2172 return True, user 2166 2173 False 2167 2174 2175 def ws_time_since_last_edited(self): 2176 t = self.time_since_last_edited() 2177 tm = convert_seconds_to_meaningful_time_span(t) 2178 return tm 2168 2179 2169 2180 def html_time_since_last_edited(self): 2170 t = self.time_since_last_edited() 2171 tm = convert_seconds_to_meaningful_time_span(t) 2172 who = ' by %s'%self.last_to_edit() 2173 return '<span class="lastedit">%s ago%s</span>'%(tm, who) 2181 tm, who = time_since_last_edited 2182 return '<span class="lastedit">%s ago%s</span>'%(tm, self.last_to_edit) 2174 2183 2175 2184 def html_time_last_edited(self): 2176 2185 tm = convert_time_to_string(self.last_edited())