Changeset 55 for branches/trac.upstream-r6719
- Timestamp:
- 03/19/08 17:24:39 (10 months ago)
- Files:
-
- branches/trac.upstream-r6719/ChangeLog (modified) (2 diffs)
- branches/trac.upstream-r6719/RELEASE (modified) (1 diff)
- branches/trac.upstream-r6719/setup.py (modified) (1 diff)
- branches/trac.upstream-r6719/THANKS (modified) (2 diffs)
- branches/trac.upstream-r6719/trac/db_default.py (modified) (1 diff)
- branches/trac.upstream-r6719/trac/htdocs/css/trac.css (modified) (1 diff)
- branches/trac.upstream-r6719/trac/htdocs/js/expand_dir.js (modified) (1 diff)
- branches/trac.upstream-r6719/trac/htdocs/js/keyboard_nav.js (added)
- branches/trac.upstream-r6719/trac/htdocs/js/noconflict.js (added)
- branches/trac.upstream-r6719/trac/htdocs/js/trac.js (modified) (1 diff)
- branches/trac.upstream-r6719/trac/mimeview/api.py (modified) (1 diff)
- branches/trac.upstream-r6719/trac/notification.py (modified) (3 diffs)
- branches/trac.upstream-r6719/trac/prefs/templates/prefs_datetime.html (modified) (2 diffs)
- branches/trac.upstream-r6719/trac/prefs/web_ui.py (modified) (2 diffs)
- branches/trac.upstream-r6719/trac/templates/error.html (modified) (3 diffs)
- branches/trac.upstream-r6719/trac/templates/layout.html (modified) (2 diffs)
- branches/trac.upstream-r6719/trac/templates/theme.html (added)
- branches/trac.upstream-r6719/trac/ticket/web_ui.py (modified) (2 diffs)
- branches/trac.upstream-r6719/trac/timeline/web_ui.py (modified) (2 diffs)
- branches/trac.upstream-r6719/trac/util/datefmt.py (modified) (1 diff)
- branches/trac.upstream-r6719/trac/util/__init__.py (modified) (1 diff)
- branches/trac.upstream-r6719/trac/versioncontrol/svn_fs.py (modified) (1 diff)
- branches/trac.upstream-r6719/trac/versioncontrol/web_ui/browser.py (modified) (1 diff)
- branches/trac.upstream-r6719/trac/web/chrome.py (modified) (6 diffs)
- branches/trac.upstream-r6719/trac/web/main.py (modified) (2 diffs)
- branches/trac.upstream-r6719/trac/web/session.py (modified) (4 diffs)
- branches/trac.upstream-r6719/trac/web/tests/session.py (modified) (2 diffs)
- branches/trac.upstream-r6719/trac/wiki/parser.py (modified) (2 diffs)
- branches/trac.upstream-r6719/trac/wiki/tests/wiki-tests.txt (modified) (1 diff)
- branches/trac.upstream-r6719/trac/wiki/web_ui.py (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
branches/trac.upstream-r6719/ChangeLog
r46 r55 1 Trac 0.11b 1 'Genshi' (December 18, 2007)2 http://svn.edgewall.org/repos/trac/tags/trac-0.11b 13 4 Trac 0.11b 1contains a great number of new features, improvements and1 Trac 0.11b2 'Genshi' (March 12, 2008) 2 http://svn.edgewall.org/repos/trac/tags/trac-0.11b2 3 4 Trac 0.11b2 contains a great number of new features, improvements and 5 5 bug fixes. The following list contains only a few highlights: 6 6 … … 17 17 A more complete list of new features can be found in the RELEASE file. 18 18 The complete list of closed tickets can be found here: 19 http://trac.edgewall.org/query?status=closed&milestone=0.10 19 http://trac.edgewall.org/query?status=closed&milestone=0.11 20 21 Trac 0.11b1 'Genshi' (December 18, 2007) 22 http://svn.edgewall.org/repos/trac/tags/trac-0.11b1 23 24 See 0.11b2. 20 25 21 26 Trac 0.10.4 (April 20, 2007) branches/trac.upstream-r6719/RELEASE
r46 r55 1 Release Notes for Trac 0.11 beta 1Genshi Release1 Release Notes for Trac 0.11 beta2 Genshi Release 2 2 ================================================ 3 December 18, 2007 3 March 12, 2008 4 4 5 5 Highlights branches/trac.upstream-r6719/setup.py
r52 r55 30 30 url = 'http://trac.edgewall.org/', 31 31 download_url = 'http://trac.edgewall.org/wiki/TracDownload', 32 classifiers = [ 33 'Environment :: Web Environment', 34 'Framework :: Trac', 35 'Intended Audience :: Developers', 36 'License :: OSI Approved :: BSD License', 37 'Operating System :: OS Independent', 38 'Programming Language :: Python', 39 'Topic :: Software Development :: Bug Tracking', 40 'Topic :: Software Development :: Version Control', 41 ], 32 42 33 43 packages = find_packages(exclude=['*.tests']), branches/trac.upstream-r6719/THANKS
r46 r55 9 9 * Toni Brkic toni.brkic@switchcore.com 10 10 * Eli Carter eli.carter@commprove.com 11 * Michele Cella 11 12 * Felix Colins felix@keyghost.com 12 13 * Wesley Crucius wcrucius@sandc.com … … 16 17 * Markus Fuchs 17 18 * Eric Gillespie epg@netbsd.org 19 * Daniel Kahn Gillmor 18 20 * Matthew Good trac@matt-good.net 19 21 * Shun-ichi Goto gotoh@taiyo.co.jp branches/trac.upstream-r6719/trac/db_default.py
r46 r55 281 281 LEFT JOIN enum p ON p.name = t.priority AND p.type = 'priority' 282 282 ORDER BY (milestone IS NULL), milestone DESC, (status = 'closed'), 283 (CASE status WHEN 'closed' THEN modified ELSE (-1)*p.valueEND) DESC284 """ ),283 (CASE status WHEN 'closed' THEN changetime ELSE (-1) * %s END) DESC 284 """ % db.cast('p.value', 'int')), 285 285 #---------------------------------------------------------------------------- 286 286 ('My Tickets', branches/trac.upstream-r6719/trac/htdocs/css/trac.css
r46 r55 438 438 table.listing tbody tr.odd { background-color: #f7f7f7 } 439 439 table.listing tbody tr:hover { background: #eed !important } 440 table.listing tbody tr.focus { background: #ddf !important } 440 441 441 442 /* Styles for the page history table branches/trac.upstream-r6719/trac/htdocs/js/expand_dir.js
r46 r55 1 1 // Enable expanding/folding folders in TracBrowser 2 2 3 var FOLDERID_COUNTER = 0;4 var SUBFOLDER_INDENT = 20;5 6 3 (function($){ 4 var FOLDERID_COUNTER = 0; 5 var SUBFOLDER_INDENT = 20; 7 6 8 7 // enableExpandDir adds the capability to folder rows to be expanded and folded branches/trac.upstream-r6719/trac/htdocs/js/trac.js
r46 r55 1 jQuery.noConflict(); // jQuery is now removed from the $ namespace2 // to use the $ shorthand, use (function($){ ... })(jQuery);3 // and for the onload handler: jQuery(function($){ ... });4 5 1 (function($){ 6 2 branches/trac.upstream-r6719/trac/mimeview/api.py
r46 r55 585 585 if not content: 586 586 return '' 587 if not isinstance(context, Context): 588 # backwards compatibility: the first argument used to be the 589 # request prior to 0.11 590 context = Context.from_request(context) 587 591 588 592 # Ensure we have a MIME type for this content branches/trac.upstream-r6719/trac/notification.py
r46 r55 25 25 from trac.util.text import CRLF 26 26 from trac.util.translation import _ 27 from trac.web.chrome import Chrome28 27 29 28 MAXHEADERLEN = 76 30 29 EMAIL_LOOKALIKE_PATTERN = (r"[a-zA-Z0-9.'=+_-]+" '@' 30 '(?:[a-zA-Z0-9_-]+\.)+[a-zA-Z]{2,4}') 31 31 32 32 class NotificationSystem(Component): … … 118 118 self.db = env.get_db_cnx() 119 119 120 from trac.web.chrome import Chrome 120 121 self.template = Chrome(self.env).load_template(self.template_name, 121 122 method='text') … … 167 168 168 169 def __init__(self, env): 170 global EMAIL_LOOKALIKE_PATTERN 169 171 Notify.__init__(self, env) 170 172 171 addrfmt = r'[\w\d_\.\-\+=]+\@(?:(?:[\w\d\-])+\.)+(?:[\w\d]{2,4})'173 addrfmt = EMAIL_LOOKALIKE_PATTERN 172 174 admit_domains = self.env.config.get('notification', 'admit_domains') 173 175 if admit_domains: branches/trac.upstream-r6719/trac/prefs/templates/prefs_datetime.html
r46 r55 12 12 13 13 <div class="field" py:with="session_tzname = settings.session.get('tz'); 14 default_tzname = timezones[len(timezones)/2]; 15 selected_tz = timezone(session_tzname or default_tzname) or timezone(default_tzname) or utc; 16 selected_tzname = '%s' % selected_tz"> 14 selected_tz = timezone(session_tzname) or utc"> 17 15 <label>Time zone: 18 16 <select name="tz"> 19 17 <option>Default time zone</option> 20 18 <option py:for="tzname in timezones" 21 selected="${selected_tzname.startswith('Etc/') and 19 selected="${session_tzname != None and 20 session_tzname.startswith('Etc/') and 22 21 selected_tz == timezone(tzname) or 23 se lected_tzname == tzname or None}">$tzname</option>22 session_tzname == tzname or None}">$tzname</option> 24 23 </select></label> 25 24 <p class="hint">Configuring your time zone will result in all … … 30 29 Example: The current time is <strong>${format_time(now, 'iso8601', tzinfo=utc)}</strong> (UTC). 31 30 <br /> 32 In ${session_tzname and 'your' or ' '} time zone ${selected_tz.tzname(None)}, this would be displayed as33 <strong>${format_time(now, 'iso8601', tzinfo= selected_tz)}</strong>.31 In ${session_tzname and 'your' or 'the Default'} time zone ${session_tzname and ' ' + selected_tz.tzname(None) or ''}, this would be displayed as 32 <strong>${format_time(now, 'iso8601', tzinfo=(session_tzname and selected_tz or localtz))}</strong>. 34 33 </p> 35 34 branches/trac.upstream-r6719/trac/prefs/web_ui.py
r46 r55 23 23 from trac.core import * 24 24 from trac.prefs.api import IPreferencePanelProvider 25 from trac.util.datefmt import all_timezones, get_timezone 25 from trac.util.datefmt import all_timezones, get_timezone, localtz 26 26 from trac.util.translation import _ 27 27 from trac.web import HTTPNotFound, IRequestHandler … … 96 96 return 'prefs_%s.html' % (panel or 'general'), { 97 97 'settings': {'session': req.session, 'session_id': req.session.sid}, 98 'timezones': all_timezones, 'timezone': get_timezone 98 'timezones': all_timezones, 'timezone': get_timezone, 99 'localtz': localtz 99 100 } 100 101 branches/trac.upstream-r6719/trac/templates/error.html
r46 r55 32 32 $("#traceback pre").hide(); 33 33 $("#tbtoggle").parent().show(); 34 34 35 $("#systeminfo").append("<tr><th>jQuery:</th><td>"+$().jquery+"</td></tr>"); 36 $("#systeminfo").before("<p>User Agent: <tt>"+navigator.userAgent+"</tt></p>"); 37 }); 38 /*]]>*/</script> 39 <script type="text/javascript">/*<![CDATA[*/ 40 jQuery(document).ready(function($) { 35 41 var descr = $("#description").text(); 36 42 descr = descr.replace(/==== System Information ====\s+/m, … … 41 47 ); 42 48 $("#description").text(descr); 43 44 $("#systeminfo").append("<tr><th>jQuery:</th><td>"+$().jquery+"</td></tr>");45 $("#systeminfo").before("<p>User Agent: <tt>"+navigator.userAgent+"</tt></p>");46 49 }); 47 50 /*]]>*/</script> … … 54 57 value="${'dev' in trac.version and 'devel' or trac.version}" /> 55 58 <input type="hidden" name="summary" value="$message" /> 56 <textarea name="description" rows="3" cols="10"> 59 <textarea id="description" name="description" rows="3" cols="10"> 60 57 61 ==== How to Reproduce ==== 58 62 branches/trac.upstream-r6719/trac/templates/layout.html
r52 r55 35 35 </ul> 36 36 </div> 37 37 38 38 <py:match path="body" once="true"><body> 39 <div id="banner"> 40 <div id="header" py:choose=""> 41 <a py:when="chrome.logo.src" id="logo" href="${chrome.logo.link or href.wiki('TracIni')+'#header_logo-section'}"><img 42 src="${chrome.logo.src}" alt="${chrome.logo.alt}" 43 height="${chrome.logo.height or None}" width="${chrome.logo.width or None}" /></a> 44 <h1 py:otherwise=""><a href="${chrome.logo.link}">${project.name}</a></h1> 45 </div> 46 <form id="search" action="${href.search()}" method="get"> 47 <div py:if="'SEARCH_VIEW' in perm"> 48 <label for="proj-search">Search:</label> 49 <input type="text" id="proj-search" name="q" size="18" accesskey="f" value="" /> 50 <input type="submit" value="Search" /> 51 </div> 52 </form> 53 ${navigation('metanav')} 54 </div> 55 ${navigation('mainnav')} 56 57 <div id="main"> 58 <div id="ctxtnav" class="nav"> 59 <h2>Context Navigation</h2> 60 <ul> 61 <li py:for="i, elm in enumerate(chrome.ctxtnav)" class="${i == 0 and 'first ' or None}${i+1 == len(chrome.ctxtnav) and 'last' or None}">$elm</li> 62 </ul> 63 <hr /> 64 </div> 65 <div id="warning" py:if="chrome.warnings" class="system-message"> 66 <py:choose test="len(chrome.warnings)"> 67 <py:when test="1"> 68 <strong>Warning:</strong> ${chrome.warnings[0]} 69 </py:when> 70 <py:otherwise> 71 <strong>Warnings:</strong> 72 <ul><li py:for="w in chrome.warnings">$w</li></ul> 73 </py:otherwise> 74 </py:choose> 75 </div> 76 <div id="notice" py:if="chrome.notices" class="system-message"> 77 <py:choose test="len(chrome.notices)"> 78 <py:when test="1"> 79 <strong>Notice:</strong> ${chrome.notices[0]} 80 </py:when> 81 <py:otherwise> 82 <strong>Notices:</strong> 83 <ul><li py:for="w in chrome.notices">$w</li></ul> 84 </py:otherwise> 85 </py:choose> 86 </div> 87 88 ${select('*|text()')} 89 39 ${select('*|text()')} 40 90 41 <script type="text/javascript" py:if="chrome.late_links"> 91 42 <py:for each="link in chrome.late_links.get('stylesheet')"> … … 106 57 </ul> 107 58 </div> 108 </div> 109 110 <div id="footer" xml:lang="en"><hr/> 111 <a id="tracpowered" href="http://trac.edgewall.org/"><img 112 src="${chrome.htdocs_location}trac_logo_mini.png" height="30" 113 width="107" alt="Trac Powered"/></a> 114 <p class="left"> 115 Powered by <a href="${href.about()}"><strong>Trac ${trac.version}</strong></a><br /> 116 By <a href="http://www.edgewall.org/">Edgewall Software</a>. 117 </p> 118 <p class="right">${chrome.footer}</p> 119 </div> 120 </body></py:match> 121 59 </body></py:match> 60 61 <xi:include href="$chrome.theme"><xi:fallback /></xi:include> 122 62 <xi:include href="site.html"><xi:fallback /></xi:include> 123 63 branches/trac.upstream-r6719/trac/ticket/web_ui.py
r52 r55 17 17 import csv 18 18 from datetime import datetime 19 from itertools import chain 19 20 import os 20 21 import pkg_resources … … 1058 1059 value = ticket.values.get(name) 1059 1060 options = field['options'] 1060 if value and not value in options: 1061 optgroups = list(chain(*[x['options'] for x in 1062 field.get('optgroups', [])])) 1063 if value and \ 1064 (not value in options and \ 1065 not value in optgroups): 1061 1066 # Current ticket value must be visible, 1062 1067 # even if it's not among the possible values branches/trac.upstream-r6719/trac/timeline/web_ui.py
r46 r55 53 53 """Default number of days displayed in the Timeline, in days. 54 54 (''since 0.9.'')""") 55 56 max_daysback = IntOption('timeline', 'max_daysback', 90, 57 """Maximum number of days (-1 for unlimited) displayable in the 58 Timeline. (''since 0.11'')""") 55 59 56 60 abbreviated_messages = BoolOption('timeline', 'abbreviated_messages', … … 113 117 daysback = self.default_daysback 114 118 daysback = max(0, daysback) 119 if self.max_daysback >= 0: 120 daysback = min(self.max_daysback, daysback) 115 121 116 122 data = {'fromdate': fromdate, 'daysback': daysback, branches/trac.upstream-r6719/trac/util/datefmt.py
r52 r55 343 343 return tz 344 344 345 _pytz_zones = [tzname for tzname in pytz. all_timezones345 _pytz_zones = [tzname for tzname in pytz.common_timezones 346 346 if not tzname.startswith('Etc/') and 347 347 not tzname.startswith('GMT')] branches/trac.upstream-r6719/trac/util/__init__.py
r46 r55 129 129 130 130 # -- sys utils 131 132 def arity(f): 133 return f.func_code.co_argcount 131 134 132 135 def get_last_traceback(): branches/trac.upstream-r6719/trac/versioncontrol/svn_fs.py
r46 r55 348 348 href = self._externals_map.get(base_url) 349 349 revstr = rev and ' at revision '+rev or '' 350 if not href and url.startswith('http://'): 350 if not href and (url.startswith('http://') or 351 url.startswith('https://')): 351 352 href = url 352 353 if href: branches/trac.upstream-r6719/trac/versioncontrol/web_ui/browser.py
r52 r55 457 457 458 458 add_script(req, 'common/js/expand_dir.js') 459 add_script(req, 'common/js/keyboard_nav.js') 459 460 460 461 return {'order': order, 'desc': desc and 1 or None, branches/trac.upstream-r6719/trac/web/chrome.py
r52 r55 20 20 import pprint 21 21 import re 22 try: 23 from cStringIO import StringIO as cStringIO 24 except ImportError: 25 cStringIO = StringIO 22 26 23 27 from genshi import Markup … … 35 39 from trac.resource import * 36 40 from trac.util import compat, get_reporter_id, presentation, get_pkginfo, \ 37 get_module_path, translation 41 get_module_path, translation, arity 38 42 from trac.util.compat import partial, set 39 43 from trac.util.html import plaintext … … 432 436 add_stylesheet(fakereq, 'common/css/trac.css') 433 437 add_script(fakereq, 'common/js/jquery.js') 438 # Only activate noConflict mode if requested to by the handler 439 if handler is not None and \ 440 getattr(handler.__class__, 'jquery_noconflict', False): 441 add_script(fakereq, 'common/js/noconflict.js') 434 442 add_script(fakereq, 'common/js/trac.js') 435 443 add_script(fakereq, 'common/js/search.js') … … 497 505 498 506 chrome['nav'] = nav 507 508 # Default theme file 509 chrome['theme'] = 'theme.html' 499 510 500 511 return chrome … … 683 694 684 695 if method == 'text': 685 return stream.render('text') 696 if arity(stream.render) == 3: 697 # TODO: remove this when we depend on Genshi >= 0.5 698 return stream.render('text') 699 else: 700 buffer = cStringIO() 701 stream.render('text', out=buffer) 702 return buffer.getvalue() 686 703 687 704 doctype = {'text/html': DocType.XHTML_STRICT}.get(content_type) … … 702 719 703 720 try: 704 return stream.render(method, doctype=doctype) 721 if arity(stream.render) == 3: 722 # TODO: remove this when we depend on Genshi >= 0.5 723 return stream.render(method, doctype=doctype) 724 else: 725 buffer = cStringIO() 726 stream.render(method, doctype=doctype, out=buffer) 727 return buffer.getvalue() 705 728 except: 706 729 # restore what may be needed by the error template branches/trac.upstream-r6719/trac/web/main.py
r46 r55 39 39 from trac.perm import PermissionCache, PermissionError, PermissionSystem 40 40 from trac.resource import ResourceNotFound 41 from trac.util import get_lines_from_file, get_last_traceback, hex_entropy 41 from trac.util import get_lines_from_file, get_last_traceback, hex_entropy, \ 42 arity 42 43 from trac.util.compat import partial, reversed 43 44 from trac.util.datefmt import format_datetime, http_date, localtz, timezone … … 287 288 288 289 def _post_process_request(self, req, *args): 289 nbargs = len(args)290 290 resp = args 291 291 for f in reversed(self.filters): 292 arity = f.post_process_request.func_code.co_argcount - 2 293 if nbargs: 294 if arity == nbargs: 295 resp = f.post_process_request(req, *resp) 296 else: 297 resp = f.post_process_request(req, *(None,)*arity) 292 # as the arity of `post_process_request` has changed since 293 # Trac 0.10, we only call filters which have the same arity 294 if arity(f.post_process_request) - 2 == len(args): 295 resp = f.post_process_request(req, *resp) 298 296 return resp 299 297 branches/trac.upstream-r6719/trac/web/session.py
r46 r55 1 1 # -*- coding: utf-8 -*- 2 2 # 3 # Copyright (C) 2004-200 6Edgewall Software3 # Copyright (C) 2004-2008 Edgewall Software 4 4 # Copyright (C) 2004 Daniel Lundin <daniel@edgewall.com> 5 5 # Copyright (C) 2004-2006 Christopher Lenz <cmlenz@gmx.de> 6 6 # Copyright (C) 2006 Jonas Borgström <jonas@edgewall.com> 7 # Copyright (C) 2008 Matt Good <matt@matt-good.net> 7 8 # All rights reserved. 8 9 # … … 29 30 30 31 31 class Session(dict): 32 """Basic session handling and per-session storage.""" 33 34 def __init__(self, env, req): 32 class DetachedSession(dict): 33 def __init__(self, env, sid): 35 34 dict.__init__(self) 36 35 self.env = env 37 self.req = req38 36 self.sid = None 39 37 self.last_visit = 0 40 38 self._new = True 41 39 self._old = {} 40 if sid: 41 self.get_session(sid, authenticated=True) 42 else: 43 self.authenticated = False 44 45 def get_session(self, sid, authenticated=False): 46 self.env.log.debug('Retrieving session for ID %r', sid) 47 48 db = self.env.get_db_cnx() 49 cursor = db.cursor() 50 51 self.sid = sid 52 self.authenticated = authenticated 53 54 cursor.execute("SELECT last_visit FROM session " 55 "WHERE sid=%s AND authenticated=%s", 56 (sid, int(authenticated))) 57 row = cursor.fetchone() 58 if not row: 59 return 60 self._new = False 61 self.last_visit = int(row[0]) 62 63 cursor.execute("SELECT name,value FROM session_attribute " 64 "WHERE sid=%s and authenticated=%s", 65 (sid, int(authenticated))) 66 for name, value in cursor: 67 self[name] = value 68 self._old.update(self) 69 70 def save(self): 71 if not self._old and not self.items(): 72 # The session doesn't have associated data, so there's no need to 73 # persist it 74 return 75 76 db = self.env.get_db_cnx() 77 cursor = db.cursor() 78 authenticated = int(self.authenticated) 79 80 if self._new: 81 self._new = False 82 cursor.execute("INSERT INTO session (sid,last_visit,authenticated)" 83 " VALUES(%s,%s,%s)", 84 (self.sid, self.last_visit, authenticated)) 85 if self._old != self: 86 attrs = [(self.sid, authenticated, k, v) for k, v in self.items()] 87 cursor.execute("DELETE FROM session_attribute WHERE sid=%s", 88 (self.sid,)) 89 self._old = dict(self.items()) 90 if attrs: 91 cursor.executemany("INSERT INTO session_attribute " 92 "(sid,authenticated,name,value) " 93 "VALUES(%s,%s,%s,%s)", attrs) 94 elif not authenticated: 95 # No need to keep around empty unauthenticated sessions 96 cursor.execute("DELETE FROM session " 97 "WHERE sid=%s AND authenticated=0", (self.sid,)) 98 db.commit() 99 return 100 101 now = int(time.time()) 102 # Update the session last visit time if it is over an hour old, 103 # so that session doesn't get purged 104 if now - self.last_visit > UPDATE_INTERVAL: 105 self.last_visit = now 106 self.env.log.info("Refreshing session %s" % self.sid) 107 cursor.execute('UPDATE session SET last_visit=%s ' 108 'WHERE sid=%s AND authenticated=%s', 109 (self.last_visit, self.sid, authenticated)) 110 # Purge expired sessions. We do this only when the session was 111 # changed as to minimize the purging. 112 mintime = now - PURGE_AGE 113 self.env.log.debug('Purging old, expired, sessions.') 114 cursor.execute("DELETE FROM session_attribute " 115 "WHERE authenticated=0 AND sid " 116 "IN (SELECT sid FROM session WHERE " 117 "authenticated=0 AND last_visit < %s)", 118 (mintime,)) 119 cursor.execute("DELETE FROM session WHERE " 120 "authenticated=0 AND last_visit < %s", 121 (mintime,)) 122 db.commit() 123 124 125 class Session(DetachedSession): 126 """Basic session handling and per-session storage.""" 127 128 def __init__(self, env, req): 129 super(Session, self).__init__(env, None) 130 self.req = req 42 131 if req.authname == 'anonymous': 43 132 if not req.incookie.has_key(COOKIE_KEY): … … 60 149 61 150 def get_session(self, sid, authenticated=False): 62 self.env.log.debug('Retrieving session for ID %r', sid)63 64 db = self.env.get_db_cnx()65 cursor = db.cursor()66 151 refresh_cookie = False 67 152 68 153 if self.sid and sid != self.sid: 69 154 refresh_cookie = True 70 self.sid = sid 71 72 cursor.execute("SELECT last_visit FROM session " 73 "WHERE sid=%s AND authenticated=%s", 74 (sid, int(authenticated))) 75 row = cursor.fetchone() 76 if not row: 77 return 78 self._new = False 79 self.last_visit = int(row[0]) 155 156 super(Session, self).get_session(sid, authenticated) 80 157 if self.last_visit and time.time() - self.last_visit > UPDATE_INTERVAL: 81 158 refresh_cookie = True 82 83 cursor.execute("SELECT name,value FROM session_attribute "84 "WHERE sid=%s and authenticated=%s",85 (sid, int(authenticated)))86 for name, value in cursor:87 self[name] = value88 self._old.update(self)89 159 90 160 # Refresh the session cookie if this is the first visit since over a day … … 158 228 self.sid = sid 159 229 self.bake_cookie(0) # expire the cookie 160 161 def save(self):162 if not self._old and not self.items():163 # The session doesn't have associated data, so there's no need to164 # persist it165 return166 167 db = self.env.get_db_cnx()168 cursor = db.cursor()169 authenticated = int(self.req.authname != 'anonymous')170 171 if self._new:172 self._new = False173 cursor.execute("INSERT INTO session (sid,last_visit,authenticated)"174 " VALUES(%s,%s,%s)",175 (self.sid, self.last_visit, authenticated))176 if self._old != self:177 attrs = [(self.sid, authenticated, k, v) for k, v in self.items()]178 cursor.execute("DELETE FROM session_attribute WHERE sid=%s",179 (self.sid,))180 self._old = dict(self.items())181 if attrs:182 cursor.executemany("INSERT INTO session_attribute "183 "(sid,authenticated,name,value) "184 "VALUES(%s,%s,%s,%s)", attrs)185 elif not authenticated:186 # No need to keep around empty unauthenticated sessions187 cursor.execute("DELETE FROM session "188 "WHERE sid=%s AND authenticated=0", (self.sid,))189 return190 191 now = int(time.time())192 # Update the session last visit time if it is over an hour old,193 # so that session doesn't get purged194 if now - self.last_visit > UPDATE_INTERVAL:195 self.last_visit = now196 self.env.log.info("Refreshing session %s" % self.sid)197 cursor.execute('UPDATE session SET last_visit=%s '198 'WHERE sid=%s AND authenticated=%s',199 (self.last_visit, self.sid, authenticated))200 # Purge expired sessions. We do this only when the session was201 # changed as to minimize the purging.202 mintime = now - PURGE_AGE203 self.env.log.debug('Purging old, expired, sessions.')204 cursor.execute("DELETE FROM session_attribute "205 "WHERE authenticated=0 AND sid "206 "IN (SELECT sid FROM session WHERE "207 "authenticated=0 AND last_visit < %s)",208 (mintime,))209 cursor.execute("DELETE FROM session WHERE "210 "authenticated=0 AND last_visit < %s",211 (mintime,))212 db.commit()branches/trac.upstream-r6719/trac/web/tests/session.py
r46 r55 7 7 from trac.test import EnvironmentStub, Mock 8 8 from trac.web.href import Href 9 from trac.web.session import Session, PURGE_AGE, UPDATE_INTERVAL9 from trac.web.session import DetachedSession, Session, PURGE_AGE, UPDATE_INTERVAL 10 10 11 11 … … 287 287 self.assertAlmostEqual(now, int(cursor.fetchone()[0]), -1) 288 288 289 def test_modify_detached_session(self): 290 """ 291 Verify that a modifying a variable in a session not associated with a 292 request updates the database accordingly. 293 """ 294 cursor = self.db.cursor() 295 cursor.execute("INSERT INTO session VALUES ('john', 1, 0)") 296 cursor.execute("INSERT INTO session_attribute VALUES " 297 "('john', 1, 'foo', 'bar')") 298 299 session = DetachedSession(self.env, 'john') 300 self.assertEqual('bar', session['foo']) 301 session['foo'] = 'baz' 302 session.save() 303 cursor.execute("SELECT value FROM session_attribute " 304 "WHERE sid='john' AND name='foo'") 305 self.assertEqual('baz', cursor.fetchone()[0]) 306 307 def test_delete_detached_session_var(self): 308 """ 309 Verify that removing a variable in a session not associated with a 310 request deletes the variable from the database. 311 """ 312 cursor = self.db.cursor() 313 cursor.execute("INSERT INTO session VALUES ('john', 1, 0)") 314 cursor.execute("INSERT INTO session_attribute VALUES " 315 "('john', 1, 'foo', 'bar')") 316 317 session = DetachedSession(self.env, 'john') 318 self.assertEqual('bar', session['foo']) 319 del session['foo'] 320 session.save() 321 cursor.execute("SELECT COUNT(*) FROM session_attribute " 322 "WHERE sid='john' AND name='foo'") 323 self.assertEqual(0, cursor.fetchone()[0]) 324 289 325 290 326 def suite(): branches/trac.upstream-r6719/trac/wiki/parser.py
r46 r55 22 22 23 23 from trac.core import * 24 from trac.notification import EMAIL_LOOKALIKE_PATTERN 24 25 25 26 class WikiParser(Component): … … 74 75 _post_rules = [ 75 76 # e-mails 76 r"(?P<email> \w[\w.]+@\w[\w.]+\w)",77 r"(?P<email>%s)" % EMAIL_LOOKALIKE_PATTERN,