Software Freedom Law Center

Changeset 55

Show
Ignore:
Timestamp:
03/19/08 17:24:39 (6 months ago)
Author:
bkuhn
Message:

Load ./upstream-trac into branches/trac.upstream-r6719.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • branches/trac.upstream-r6719/ChangeLog

    r46 r55  
    1 Trac 0.11b1 'Genshi' (December 18, 2007
    2 http://svn.edgewall.org/repos/trac/tags/trac-0.11b1 
    3  
    4  Trac 0.11b1 contains a great number of new features, improvements and 
     1Trac 0.11b2 'Genshi' (March 12, 2008
     2http://svn.edgewall.org/repos/trac/tags/trac-0.11b2 
     3 
     4 Trac 0.11b2 contains a great number of new features, improvements and 
    55 bug fixes. The following list contains only a few highlights: 
    66 
     
    1717 A more complete list of new features can be found in the RELEASE file. 
    1818 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 
     21Trac 0.11b1 'Genshi' (December 18, 2007) 
     22http://svn.edgewall.org/repos/trac/tags/trac-0.11b1 
     23 
     24 See 0.11b2. 
    2025 
    2126Trac 0.10.4 (April 20, 2007) 
  • branches/trac.upstream-r6719/RELEASE

    r46 r55  
    1 Release Notes for Trac 0.11 beta1 Genshi Release 
     1Release Notes for Trac 0.11 beta2 Genshi Release 
    22================================================ 
    3 December 18, 2007 
     3March 12, 2008 
    44 
    55Highlights 
  • branches/trac.upstream-r6719/setup.py

    r52 r55  
    3030    url = 'http://trac.edgewall.org/', 
    3131    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    ], 
    3242 
    3343    packages = find_packages(exclude=['*.tests']), 
  • branches/trac.upstream-r6719/THANKS

    r46 r55  
    99 * Toni Brkic                     toni.brkic@switchcore.com 
    1010 * Eli Carter                     eli.carter@commprove.com 
     11 * Michele Cella 
    1112 * Felix Colins                   felix@keyghost.com 
    1213 * Wesley Crucius                 wcrucius@sandc.com 
     
    1617 * Markus Fuchs                    
    1718 * Eric Gillespie                 epg@netbsd.org 
     19 * Daniel Kahn Gillmor 
    1820 * Matthew Good                   trac@matt-good.net 
    1921 * Shun-ichi Goto                 gotoh@taiyo.co.jp 
  • branches/trac.upstream-r6719/trac/db_default.py

    r46 r55  
    281281  LEFT JOIN enum p ON p.name = t.priority AND p.type = 'priority' 
    282282  ORDER BY (milestone IS NULL), milestone DESC, (status = 'closed'),  
    283         (CASE status WHEN 'closed' THEN modified ELSE (-1)*p.value END) DESC 
    284 """), 
     283        (CASE status WHEN 'closed' THEN changetime ELSE (-1) * %s END) DESC 
     284""" % db.cast('p.value', 'int')), 
    285285#---------------------------------------------------------------------------- 
    286286('My Tickets', 
  • branches/trac.upstream-r6719/trac/htdocs/css/trac.css

    r46 r55  
    438438table.listing tbody tr.odd { background-color: #f7f7f7 } 
    439439table.listing tbody tr:hover { background: #eed !important } 
     440table.listing tbody tr.focus { background: #ddf !important } 
    440441 
    441442/* Styles for the page history table 
  • branches/trac.upstream-r6719/trac/htdocs/js/expand_dir.js

    r46 r55  
    11// Enable expanding/folding folders in TracBrowser 
    22 
    3 var FOLDERID_COUNTER = 0; 
    4 var SUBFOLDER_INDENT = 20; 
    5  
    63(function($){ 
     4  var FOLDERID_COUNTER = 0; 
     5  var SUBFOLDER_INDENT = 20; 
    76   
    87  // 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 $ namespace 
    2                      // to use the $ shorthand, use (function($){ ... })(jQuery); 
    3                      // and for the onload handler: jQuery(function($){ ... }); 
    4  
    51(function($){ 
    62   
  • branches/trac.upstream-r6719/trac/mimeview/api.py

    r46 r55  
    585585        if not content: 
    586586            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) 
    587591 
    588592        # Ensure we have a MIME type for this content 
  • branches/trac.upstream-r6719/trac/notification.py

    r46 r55  
    2525from trac.util.text import CRLF 
    2626from trac.util.translation import _ 
    27 from trac.web.chrome import Chrome 
    2827 
    2928MAXHEADERLEN = 76 
    30  
     29EMAIL_LOOKALIKE_PATTERN = (r"[a-zA-Z0-9.'=+_-]+" '@' 
     30                            '(?:[a-zA-Z0-9_-]+\.)+[a-zA-Z]{2,4}') 
    3131 
    3232class NotificationSystem(Component): 
     
    118118        self.db = env.get_db_cnx() 
    119119 
     120        from trac.web.chrome import Chrome 
    120121        self.template = Chrome(self.env).load_template(self.template_name, 
    121122                                                       method='text') 
     
    167168 
    168169    def __init__(self, env): 
     170        global EMAIL_LOOKALIKE_PATTERN 
    169171        Notify.__init__(self, env) 
    170172 
    171         addrfmt = r'[\w\d_\.\-\+=]+\@(?:(?:[\w\d\-])+\.)+(?:[\w\d]{2,4})' 
     173        addrfmt = EMAIL_LOOKALIKE_PATTERN 
    172174        admit_domains = self.env.config.get('notification', 'admit_domains') 
    173175        if admit_domains: 
  • branches/trac.upstream-r6719/trac/prefs/templates/prefs_datetime.html

    r46 r55  
    1212 
    1313    <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"> 
    1715      <label>Time zone: 
    1816      <select name="tz"> 
    1917        <option>Default time zone</option> 
    2018        <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 
    2221                            selected_tz == timezone(tzname) or 
    23                             selected_tzname == tzname or None}">$tzname</option> 
     22                            session_tzname == tzname or None}">$tzname</option> 
    2423      </select></label> 
    2524      <p class="hint">Configuring your time zone will result in all 
     
    3029        Example: The current time is <strong>${format_time(now, 'iso8601', tzinfo=utc)}</strong> (UTC). 
    3130        <br /> 
    32         In ${session_tzname and 'your' or ''} time zone ${selected_tz.tzname(None)}, this would be displayed as 
    33         <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>. 
    3433      </p> 
    3534 
  • branches/trac.upstream-r6719/trac/prefs/web_ui.py

    r46 r55  
    2323from trac.core import * 
    2424from trac.prefs.api import IPreferencePanelProvider 
    25 from trac.util.datefmt import all_timezones, get_timezone 
     25from trac.util.datefmt import all_timezones, get_timezone, localtz 
    2626from trac.util.translation import _ 
    2727from trac.web import HTTPNotFound, IRequestHandler 
     
    9696        return 'prefs_%s.html' % (panel or 'general'), { 
    9797            '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 
    99100        } 
    100101 
  • branches/trac.upstream-r6719/trac/templates/error.html

    r46 r55  
    3232       $("#traceback pre").hide(); 
    3333       $("#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($) { 
    3541       var descr = $("#description").text(); 
    3642       descr = descr.replace(/==== System Information ====\s+/m, 
     
    4147       ); 
    4248       $("#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>"); 
    4649      }); 
    4750    /*]]>*/</script> 
     
    5457           value="${'dev' in trac.version and 'devel' or trac.version}" /> 
    5558    <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 
    5761==== How to Reproduce ==== 
    5862 
  • branches/trac.upstream-r6719/trac/templates/layout.html

    r52 r55  
    3535    </ul> 
    3636  </div> 
    37  
     37   
    3838  <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     
    9041      <script type="text/javascript" py:if="chrome.late_links"> 
    9142        <py:for each="link in chrome.late_links.get('stylesheet')"> 
     
    10657        </ul> 
    10758      </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> 
    12262  <xi:include href="site.html"><xi:fallback /></xi:include> 
    12363 
  • branches/trac.upstream-r6719/trac/ticket/web_ui.py

    r52 r55  
    1717import csv 
    1818from datetime import datetime 
     19from itertools import chain 
    1920import os 
    2021import pkg_resources 
     
    10581059                    value = ticket.values.get(name) 
    10591060                    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): 
    10611066                        # Current ticket value must be visible, 
    10621067                        # even if it's not among the possible values 
  • branches/trac.upstream-r6719/trac/timeline/web_ui.py

    r46 r55  
    5353        """Default number of days displayed in the Timeline, in days. 
    5454        (''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'')""") 
    5559 
    5660    abbreviated_messages = BoolOption('timeline', 'abbreviated_messages', 
     
    113117                daysback = self.default_daysback 
    114118        daysback = max(0, daysback) 
     119        if self.max_daysback >= 0: 
     120            daysback = min(self.max_daysback, daysback) 
    115121 
    116122        data = {'fromdate': fromdate, 'daysback': daysback, 
  • branches/trac.upstream-r6719/trac/util/datefmt.py

    r52 r55  
    343343        return tz 
    344344 
    345     _pytz_zones = [tzname for tzname in pytz.all_timezones 
     345    _pytz_zones = [tzname for tzname in pytz.common_timezones 
    346346                   if not tzname.startswith('Etc/') and 
    347347                      not tzname.startswith('GMT')] 
  • branches/trac.upstream-r6719/trac/util/__init__.py

    r46 r55  
    129129 
    130130# -- sys utils 
     131 
     132def arity(f): 
     133    return f.func_code.co_argcount 
    131134 
    132135def get_last_traceback(): 
  • branches/trac.upstream-r6719/trac/versioncontrol/svn_fs.py

    r46 r55  
    348348            href = self._externals_map.get(base_url) 
    349349            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://')): 
    351352                href = url 
    352353            if href: 
  • branches/trac.upstream-r6719/trac/versioncontrol/web_ui/browser.py

    r52 r55  
    457457 
    458458        add_script(req, 'common/js/expand_dir.js') 
     459        add_script(req, 'common/js/keyboard_nav.js') 
    459460 
    460461        return {'order': order, 'desc': desc and 1 or None, 
  • branches/trac.upstream-r6719/trac/web/chrome.py

    r52 r55  
    2020import pprint 
    2121import re 
     22try:  
     23    from cStringIO import StringIO as cStringIO  
     24except ImportError:  
     25    cStringIO = StringIO  
    2226 
    2327from genshi import Markup 
     
    3539from trac.resource import * 
    3640from trac.util import compat, get_reporter_id, presentation, get_pkginfo, \ 
    37                       get_module_path, translation 
     41                      get_module_path, translation, arity 
    3842from trac.util.compat import partial, set 
    3943from trac.util.html import plaintext 
     
    432436        add_stylesheet(fakereq, 'common/css/trac.css') 
    433437        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') 
    434442        add_script(fakereq, 'common/js/trac.js') 
    435443        add_script(fakereq, 'common/js/search.js') 
     
    497505 
    498506        chrome['nav'] = nav 
     507         
     508        # Default theme file 
     509        chrome['theme'] = 'theme.html' 
    499510 
    500511        return chrome 
     
    683694 
    684695        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() 
    686703 
    687704        doctype = {'text/html': DocType.XHTML_STRICT}.get(content_type) 
     
    702719 
    703720        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() 
    705728        except: 
    706729            # restore what may be needed by the error template 
  • branches/trac.upstream-r6719/trac/web/main.py

    r46 r55  
    3939from trac.perm import PermissionCache, PermissionError, PermissionSystem 
    4040from trac.resource import ResourceNotFound 
    41 from trac.util import get_lines_from_file, get_last_traceback, hex_entropy 
     41from trac.util import get_lines_from_file, get_last_traceback, hex_entropy, \ 
     42                      arity 
    4243from trac.util.compat import partial, reversed 
    4344from trac.util.datefmt import format_datetime, http_date, localtz, timezone 
     
    287288 
    288289    def _post_process_request(self, req, *args): 
    289         nbargs = len(args) 
    290290        resp = args 
    291291        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) 
    298296        return resp 
    299297 
  • branches/trac.upstream-r6719/trac/web/session.py

    r46 r55  
    11# -*- coding: utf-8 -*- 
    22# 
    3 # Copyright (C) 2004-2006 Edgewall Software 
     3# Copyright (C) 2004-2008 Edgewall Software 
    44# Copyright (C) 2004 Daniel Lundin <daniel@edgewall.com> 
    55# Copyright (C) 2004-2006 Christopher Lenz <cmlenz@gmx.de> 
    66# Copyright (C) 2006 Jonas Borgström <jonas@edgewall.com> 
     7# Copyright (C) 2008 Matt Good <matt@matt-good.net> 
    78# All rights reserved. 
    89# 
     
    2930 
    3031 
    31 class Session(dict): 
    32     """Basic session handling and per-session storage.""" 
    33  
    34     def __init__(self, env, req): 
     32class DetachedSession(dict): 
     33    def __init__(self, env, sid): 
    3534        dict.__init__(self) 
    3635        self.env = env 
    37         self.req = req 
    3836        self.sid = None 
    3937        self.last_visit = 0 
    4038        self._new = True 
    4139        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 
     125class 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 
    42131        if req.authname == 'anonymous': 
    43132            if not req.incookie.has_key(COOKIE_KEY): 
     
    60149 
    61150    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() 
    66151        refresh_cookie = False 
    67152 
    68153        if self.sid and sid != self.sid: 
    69154            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) 
    80157        if self.last_visit and time.time() - self.last_visit > UPDATE_INTERVAL: 
    81158            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] = value 
    88         self._old.update(self) 
    89159 
    90160        # Refresh the session cookie if this is the first visit since over a day 
     
    158228        self.sid = sid 
    159229        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 to 
    164             # persist it 
    165             return 
    166  
    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 = False 
    173             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 sessions 
    187                 cursor.execute("DELETE FROM session " 
    188                                "WHERE sid=%s AND authenticated=0", (self.sid,)) 
    189                 return 
    190  
    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 purged 
    194         if now - self.last_visit > UPDATE_INTERVAL: 
    195             self.last_visit = now 
    196             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 was 
    201             # changed as to minimize the purging. 
    202             mintime = now - PURGE_AGE 
    203             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  
    77from trac.test import EnvironmentStub, Mock 
    88from trac.web.href import Href 
    9 from trac.web.session import Session, PURGE_AGE, UPDATE_INTERVAL 
     9from trac.web.session import DetachedSession, Session, PURGE_AGE, UPDATE_INTERVAL 
    1010 
    1111 
     
    287287        self.assertAlmostEqual(now, int(cursor.fetchone()[0]), -1) 
    288288 
     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 
    289325 
    290326def suite(): 
  • branches/trac.upstream-r6719/trac/wiki/parser.py

    r46 r55  
    2222 
    2323from trac.core import * 
     24from trac.notification import EMAIL_LOOKALIKE_PATTERN 
    2425 
    2526class WikiParser(Component): 
     
    7475    _post_rules = [ 
    7576        # e-mails 
    76         r"(?P<email>\w[\w.]+@\w[\w.]+\w)"
     77        r"(?P<email>%s)" % EMAIL_LOOKALIKE_PATTERN
    7778        # > ... 
    7879        r"(?P<citation>^(?P<cdepth>>(?: *>