[Python-checkins] r52699 - in tracker/vendor/roundup/current: CHANGES.txt MANIFEST.in doc/announcement.txt doc/customizing.txt doc/index.txt doc/installation.txt doc/upgrading.txt frontends/README.txt frontends/roundup.cgi roundup/__init__.py roundup/backends/__init__.py roundup/backends/back_postgresql.py roundup/backends/back_sqlite.py roundup/cgi/apache.py roundup/cgi/client.py roundup/cgi/templating.py roundup/cgi/wsgi_handler.py roundup/hyperdb.py roundup/roundupdb.py roundup/scripts/roundup_server.py setup.py templates/classic/html/_generic.index.html templates/classic/html/_generic.item.html templates/classic/html/file.item.html templates/classic/html/issue.index.html templates/classic/html/issue.item.html templates/classic/html/msg.item.html templates/classic/html/page.html templates/classic/html/user.index.html templates/classic/html/user.item.html templates/minimal/html/_generic.index.html templates/minimal/html/_generic.item.html templates/minimal/html/page.html templates/minimal/html/user.index.html templates/minimal/html/user.register.html test/db_test_base.py

erik.forsberg python-checkins at python.org
Thu Nov 9 20:15:13 CET 2006


Author: erik.forsberg
Date: Thu Nov  9 20:15:10 2006
New Revision: 52699

Added:
   tracker/vendor/roundup/current/frontends/roundup.cgi   (contents, props changed)
   tracker/vendor/roundup/current/roundup/cgi/wsgi_handler.py
Modified:
   tracker/vendor/roundup/current/CHANGES.txt
   tracker/vendor/roundup/current/MANIFEST.in
   tracker/vendor/roundup/current/doc/announcement.txt
   tracker/vendor/roundup/current/doc/customizing.txt
   tracker/vendor/roundup/current/doc/index.txt
   tracker/vendor/roundup/current/doc/installation.txt
   tracker/vendor/roundup/current/doc/upgrading.txt
   tracker/vendor/roundup/current/frontends/README.txt
   tracker/vendor/roundup/current/roundup/__init__.py
   tracker/vendor/roundup/current/roundup/backends/__init__.py
   tracker/vendor/roundup/current/roundup/backends/back_postgresql.py
   tracker/vendor/roundup/current/roundup/backends/back_sqlite.py
   tracker/vendor/roundup/current/roundup/cgi/apache.py
   tracker/vendor/roundup/current/roundup/cgi/client.py
   tracker/vendor/roundup/current/roundup/cgi/templating.py
   tracker/vendor/roundup/current/roundup/hyperdb.py
   tracker/vendor/roundup/current/roundup/roundupdb.py
   tracker/vendor/roundup/current/roundup/scripts/roundup_server.py
   tracker/vendor/roundup/current/setup.py
   tracker/vendor/roundup/current/templates/classic/html/_generic.index.html
   tracker/vendor/roundup/current/templates/classic/html/_generic.item.html
   tracker/vendor/roundup/current/templates/classic/html/file.item.html
   tracker/vendor/roundup/current/templates/classic/html/issue.index.html
   tracker/vendor/roundup/current/templates/classic/html/issue.item.html
   tracker/vendor/roundup/current/templates/classic/html/msg.item.html
   tracker/vendor/roundup/current/templates/classic/html/page.html
   tracker/vendor/roundup/current/templates/classic/html/user.index.html
   tracker/vendor/roundup/current/templates/classic/html/user.item.html
   tracker/vendor/roundup/current/templates/minimal/html/_generic.index.html
   tracker/vendor/roundup/current/templates/minimal/html/_generic.item.html
   tracker/vendor/roundup/current/templates/minimal/html/page.html
   tracker/vendor/roundup/current/templates/minimal/html/user.index.html
   tracker/vendor/roundup/current/templates/minimal/html/user.register.html
   tracker/vendor/roundup/current/test/db_test_base.py
Log:

Importing roundup 1.3.0.


Modified: tracker/vendor/roundup/current/CHANGES.txt
==============================================================================
--- tracker/vendor/roundup/current/CHANGES.txt	(original)
+++ tracker/vendor/roundup/current/CHANGES.txt	Thu Nov  9 20:15:10 2006
@@ -1,7 +1,25 @@
 This file contains the changes to the Roundup system over time. The entries
 are given with the most recent entry first.
 
-2006-??-?? 1.2.1
+2006-??-?? 1.3.0
+Feature:
+- WSGI support via roundup.cgi.wsgi_handler
+
+Fixed:
+- sqlite module detection was broken for python 2.5 compiled without sqlite
+  support
+- fixed support for pysqlite2 (version 2.1.0 is the minimum version
+  supported)
+- roundup-server called setuid when run by non-root user
+- fix sort/group direction checkbox in issue.index.html (sf bug 1593025)
+- fix error detection for non-EN locales of postgres (sf bug 1592249)
+- fix email change note rendering of multiline properties (sf patch 1575223)
+- fix sidebar search links (sf patch 1574467)
+- nicer "permission required" messages (sf patch 1558183)
+- fix unstable ordering of detectors (sf bug 1585378)
+
+
+2006-10-07 1.2.1
 Fixed:
 - E-mail subject line prefix delimiter configuration was being ignored.
 - Password confirm field in user editing.

Modified: tracker/vendor/roundup/current/MANIFEST.in
==============================================================================
--- tracker/vendor/roundup/current/MANIFEST.in	(original)
+++ tracker/vendor/roundup/current/MANIFEST.in	Thu Nov  9 20:15:10 2006
@@ -2,7 +2,6 @@
 recursive-include frontends *.*
 recursive-include scripts *.* *-*
 recursive-include tools *.*
-recursive-include cgi-bin *.cgi
 recursive-include test *.py *.txt
 recursive-include doc *.html *.png *.txt *.css *.1 *.example
 recursive-include detectors *.py

Modified: tracker/vendor/roundup/current/doc/announcement.txt
==============================================================================
--- tracker/vendor/roundup/current/doc/announcement.txt	(original)
+++ tracker/vendor/roundup/current/doc/announcement.txt	Thu Nov  9 20:15:10 2006
@@ -1,35 +1,20 @@
-I'm proud to release version 1.2.1 of Roundup.
+I'm proud to release version 1.3.0 of Roundup.
 
-Bugs fixed in 1.2.1:
+New Features in 1.3.0:
+- WSGI support via roundup.cgi.wsgi_handler
 
-- E-mail subject line prefix delimiter configuration was being ignored.
-- Password confirm field in user editing.
-
-New Features in 1.2.x:
-
-- supports Python 2.5, including the sqlite3 module
-- full timezone support (sf patch 1465296)
-- handle connection loss when responding to web requests
-- match incoming mail In-Reply-To against existing messages when no issue
-  id is specified in the Subject
-- added StringHTMLProperty wrapped() method to wrap long lines in issue
-  display
-- include the popcal in Date field editing and search fields by default
-- @required in forms may now specify properties of linked items (sf patch
-  1507093)
-- update for latest version of pysqlite (sf bug 1487098; patch 1534227)
-- update for latest version of psycopg2 (sf patch 1429391)
-- new "exporttables" command in roundup-admin (sf bug 1533791)
-- roundup-admin "export" may specify classes to exclude (sf bug 1533791)
-- sorting and grouping by multiple properties is now supported by the
-  backends *and* the classic template.
-- sorting, grouping, and searching by transitive properties (e.g.,
-  messages.author.supervisor) is now supported in all backends
-- added filter_sql to SQL backends which takes an arbitrary SQL statement
-  and returns a list of item ids
-
-There was also a lot of bugfixes - see the bundled CHANGES.txt file for the
-list.
+Fixed in 1.3.0:
+- sqlite module detection was broken for python 2.5 compiled without sqlite
+  support
+- fixed support for pysqlite2 (version 2.1.0 is the minimum version
+  supported)
+- roundup-server called setuid when run by non-root user
+- fix sort/group direction checkbox in issue.index.html (sf bug 1593025)
+- fix error detection for non-EN locales of postgres (sf bug 1592249)
+- fix email change note rendering of multiline properties (sf patch 1575223)
+- fix sidebar search links (sf patch 1574467)
+- nicer "permission required" messages (sf patch 1558183)
+- fix unstable ordering of detectors (sf bug 1585378)
 
 If you're upgrading from an older version of Roundup you *must* follow
 the "Software Upgrade" guidelines given in the maintenance documentation.

Modified: tracker/vendor/roundup/current/doc/customizing.txt
==============================================================================
--- tracker/vendor/roundup/current/doc/customizing.txt	(original)
+++ tracker/vendor/roundup/current/doc/customizing.txt	Thu Nov  9 20:15:10 2006
@@ -2,7 +2,7 @@
 Customising Roundup
 ===================
 
-:Version: $Revision: 1.209 $
+:Version: $Revision: 1.210 $
 
 .. This document borrows from the ZopeBook section on ZPT. The original is at:
    http://www.zope.org/Documentation/Books/ZopeBook/current/ZPT.stx
@@ -1040,13 +1040,14 @@
  - they're *anonymous*.
 
 **automatic registration of users in the e-mail gateway**
- By giving the "anonymous" user the ("Create", "user" Permission, any
+ By giving the "anonymous" user the ("Create", "user") Permission, any
  unidentified user will automatically be registered with the tracker
  (with no password, so they won't be able to log in through
- the web until an admin sets their password). This is the default
- behaviour in the tracker templates that ship with Roundup. The new user
- is given the Roles list defined in the "new_email_user_roles" config
- variable.
+ the web until an admin sets their password). By default new Roundup
+ trackers don't allow this as it opens them up to spam. It may be enabled
+ by uncommenting the appropriate addPermissionToRole in your tracker's
+ ``schema.py`` file. The new user is given the Roles list defined in the
+"new_email_user_roles" config variable. 
 
 **only developers may be assigned issues**
  Create a new Permission called "Fixer" for the "issue" class. Create a

Modified: tracker/vendor/roundup/current/doc/index.txt
==============================================================================
--- tracker/vendor/roundup/current/doc/index.txt	(original)
+++ tracker/vendor/roundup/current/doc/index.txt	Thu Nov  9 20:15:10 2006
@@ -81,6 +81,7 @@
 Wil Cooley,
 Joe Cooper,
 Kelley Dagley,
+Toby Dickenson,
 Paul F. Dubois,
 Eric Earnst,
 Andrew Eland,
@@ -136,6 +137,7 @@
 Bernhard Reiter,
 Roy Rapoport,
 John P. Rouillard,
+Luke Ross,
 Ollie Rutherfurd,
 Toby Sargeant,
 Giuseppe Scelsi,
@@ -153,6 +155,7 @@
 Emil Sit,
 Alexander Smishlajev,
 Nathaniel Smith,
+Leonardo Soto,
 Maciej Starzyk,
 Mitchell Surface,
 Jon C. Thomason

Modified: tracker/vendor/roundup/current/doc/installation.txt
==============================================================================
--- tracker/vendor/roundup/current/doc/installation.txt	(original)
+++ tracker/vendor/roundup/current/doc/installation.txt	Thu Nov  9 20:15:10 2006
@@ -53,9 +53,9 @@
 You may optionally install and use:
 
 Timezone Definitions
-  Full timezone support requires pytz_ module which brings the
-  `Olson tz database`_ into Python.  If pytz_ is not installed,
-  timezones may be specified as numeric hour offsets only.
+  Full timezone support requires pytz_ module (version 2005i or later)
+  which brings the `Olson tz database`_ into Python.  If pytz_ is not
+  installed, timezones may be specified as numeric hour offsets only.
 
 An RDBMS
   Sqlite, MySQL and Postgresql are all supported by Roundup and will be
@@ -76,7 +76,7 @@
   successfully.
 
 .. _Xapian: http://www.xapian.org/
-.. _pytz: http://pytz.sourceforge.net/
+.. _pytz: http://www.python.org/pypi/pytz
 .. _Olson tz database: http://www.twinsun.com/tz/tz-link.htm
 
 
@@ -271,18 +271,23 @@
 Name       Speed       Users   Support
 ========== =========== ===== ==============================
 anydbm     Slowest     Few   Always available
-sqlite     Fastest(*)  Few   Needs install (PySQLite_)
+sqlite     Fastest(*)  Few   May need install (PySQLite_)
 metakit    Fastest(*)  Few   Needs install (metakit_)
 postgresql Fast        Many  Needs install/admin (psycopg_)
 mysql      Fast        Many  Needs install/admin (MySQLdb_)
 ========== =========== ===== ==============================
 
-**sqlite** and **metakit**
+**sqlite**
   These use the embedded database engines PySQLite_ and metakit_ to provide
   very fast backends. They are not suitable for trackers which will have
   many simultaneous users, but require much less installation and
   maintenance effort than more scalable postgresql and mysql backends.
-  If you are choosing from these two, please select sqlite.
+
+  SQLite is supported via PySQLite versions 1.1.7, 2.1.0 and sqlite3 (the last
+  being bundled with Python 2.5+)
+**metakit**
+  Similar performance to sqlite. If you are choosing between these two,
+  please select sqlite.
 **postgresql**
   Backend for popular RDBMS PostgreSQL. You must read doc/postgresql.txt for
   additional installation steps and requirements. You must also configure
@@ -313,6 +318,7 @@
 2. `stand-alone web server`_
 3. `Zope product - ZRoundup`_
 4. `Apache HTTP Server with mod_python`_
+5. `WSGI handler`_
 
 You may need to give the web server user permission to access the tracker home
 - see the `UNIX environment steps`_ for information. You may also need to
@@ -342,7 +348,7 @@
 
    http://support.microsoft.com/default.aspx?scid=kb%3Ben-us%3B276494
 
-Copy the ``cgi-bin/roundup.cgi`` file to your web server's ``cgi-bin``
+Copy the ``frontends/roundup.cgi`` file to your web server's ``cgi-bin``
 directory. You will need to configure it to tell it where your tracker home
 is. You can do this either:
 
@@ -502,6 +508,25 @@
       PythonOption TrackerHome /var/db/roundup/devel
     </Directory>
 
+WSGI Handler
+~~~~~~~~~~~~
+
+The WSGI handler is quite simple. The following sample code shows how
+to use it::
+
+    from wsgiref.simple_server import make_server
+
+    # obtain the WSGI request dispatcher
+    from roundup.cgi.wsgi_handler import RequestDispatcher
+    tracker_home = 'demo'
+    app = RequestDispatcher(tracker_home)
+
+    httpd = make_server('', 8917, app)
+    httpd.serve_forever()
+
+To test the above you should create a demo tracker with ``python demo.py``.
+Edit the ``config.ini`` to change the web URL to "http://localhost:8917/".
+
 
 Configure an Email Interface
 ----------------------------

Modified: tracker/vendor/roundup/current/doc/upgrading.txt
==============================================================================
--- tracker/vendor/roundup/current/doc/upgrading.txt	(original)
+++ tracker/vendor/roundup/current/doc/upgrading.txt	Thu Nov  9 20:15:10 2006
@@ -13,10 +13,21 @@
 
 .. contents::
 
-Migrating from 1.1.2 to 1.X.X
+Migrating from 1.2.x to 1.3.0
 =============================
 
-1.X.X Sorting and grouping by multiple properties
+1.3.0 Web interface changes
+---------------------------
+
+Some of the HTML files in the "classic" and "minimal" tracker templates
+were changed to fix some bugs and clean them up. You may wish to compare
+them to the HTML files in your tracker and apply any changes.
+
+
+Migrating from 1.1.2 to 1.2.0
+=============================
+
+1.2.0 Sorting and grouping by multiple properties
 -------------------------------------------------
 
 Starting with this version, sorting and grouping by multiple properties
@@ -248,9 +259,12 @@
 You may remove the ``__init__.py`` module from the "detectors" directory as
 it is no longer used.
 
-There's a new way to write extension code for Roundup - the old
-``interfaces.py`` file will be ignored. See the `customisation
+There's a new way to write extension code for Roundup. If you have code in
+an ``interfaces.py`` file you should move it. See the `customisation
 documentation`_ for information about how extensions are now written.
+Note that some older trackers may use ``interfaces.py`` to customise the
+mail gateway behaviour. You will need to keep your ``interfaces.py`` file
+if this is the case.
 
 
 0.8.0 Permissions Changes

Modified: tracker/vendor/roundup/current/frontends/README.txt
==============================================================================
--- tracker/vendor/roundup/current/frontends/README.txt	(original)
+++ tracker/vendor/roundup/current/frontends/README.txt	Thu Nov  9 20:15:10 2006
@@ -1,8 +1,10 @@
 This directory contains alternate front-ends for Roundup.
 
-Zope - ZRoundup
----------------
+roundup.cgi
+   This is a cgi-bin script.
 
-This installs as a regular Zope product. See Roundup's doc/installation.txt
-for more info.
+ZRoundup
+   This is a simple Zope frontend that installs as a regular Zope product.
 
+See Roundup's doc/installation.txt for more info on installing these
+frontends.

Added: tracker/vendor/roundup/current/frontends/roundup.cgi
==============================================================================
--- (empty file)
+++ tracker/vendor/roundup/current/frontends/roundup.cgi	Thu Nov  9 20:15:10 2006
@@ -0,0 +1,229 @@
+#!/usr/bin/env python
+#
+# Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/)
+# This module is free software, and you may redistribute it and/or modify
+# under the same terms as Python, so long as this copyright message and
+# disclaimer are retained in their original form.
+#
+# IN NO EVENT SHALL BIZAR SOFTWARE PTY LTD BE LIABLE TO ANY PARTY FOR
+# DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING
+# OUT OF THE USE OF THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+#
+# BIZAR SOFTWARE PTY LTD SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
+# BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE.  THE CODE PROVIDED HEREUNDER IS ON AN "AS IS"
+# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
+# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
+# 
+# $Id: roundup.cgi,v 1.1 2006/11/09 00:36:21 richard Exp $
+
+# python version check
+from roundup import version_check
+from roundup.i18n import _
+import sys, time
+
+#
+##  Configuration
+#
+
+# Configuration can also be provided through the OS environment (or via
+# the Apache "SetEnv" configuration directive). If the variables
+# documented below are set, they _override_ any configuation defaults
+# given in this file. 
+
+# TRACKER_HOMES is a list of trackers, in the form
+# "NAME=DIR<sep>NAME2=DIR2<sep>...", where <sep> is the directory path
+# separator (";" on Windows, ":" on Unix). 
+
+# Make sure the NAME part doesn't include any url-unsafe characters like 
+# spaces, as these confuse the cookie handling in browsers like IE.
+
+# ROUNDUP_LOG is the name of the logfile; if it's empty or does not exist,
+# logging is turned off (unless you changed the default below). 
+
+# DEBUG_TO_CLIENT specifies whether debugging goes to the HTTP server (via
+# stderr) or to the web client (via cgitb).
+DEBUG_TO_CLIENT = False
+
+# This indicates where the Roundup tracker lives
+TRACKER_HOMES = {
+#    'example': '/path/to/example',
+}
+
+# Where to log debugging information to. Use an instance of DevNull if you
+# don't want to log anywhere.
+class DevNull:
+    def write(self, info):
+        pass
+    def close(self):
+        pass
+    def flush(self):
+        pass
+#LOG = open('/var/log/roundup.cgi.log', 'a')
+LOG = DevNull()
+
+#
+##  end configuration
+#
+
+
+#
+# Set up the error handler
+# 
+try:
+    import traceback, StringIO, cgi
+    from roundup.cgi import cgitb
+except:
+    print "Content-Type: text/plain\n"
+    print _("Failed to import cgitb!\n\n")
+    s = StringIO.StringIO()
+    traceback.print_exc(None, s)
+    print s.getvalue()
+
+
+#
+# Check environment for config items
+#
+def checkconfig():
+    import os, string
+    global TRACKER_HOMES, LOG
+
+    # see if there's an environment var. ROUNDUP_INSTANCE_HOMES is the
+    # old name for it.
+    if os.environ.has_key('ROUNDUP_INSTANCE_HOMES'):
+        homes = os.environ.get('ROUNDUP_INSTANCE_HOMES')
+    else:
+        homes = os.environ.get('TRACKER_HOMES', '')
+    if homes:
+        TRACKER_HOMES = {}
+        for home in string.split(homes, os.pathsep):
+            try:
+                name, dir = string.split(home, '=', 1)
+            except ValueError:
+                # ignore invalid definitions
+                continue
+            if name and dir:
+                TRACKER_HOMES[name] = dir
+                
+    logname = os.environ.get('ROUNDUP_LOG', '')
+    if logname:
+        LOG = open(logname, 'a')
+
+    # ROUNDUP_DEBUG is checked directly in "roundup.cgi.client"
+
+
+#
+# Provide interface to CGI HTTP response handling
+#
+class RequestWrapper:
+    '''Used to make the CGI server look like a BaseHTTPRequestHandler
+    '''
+    def __init__(self, wfile):
+        self.wfile = wfile
+    def write(self, data):
+        self.wfile.write(data)
+    def send_response(self, code):
+        self.write('Status: %s\r\n'%code)
+    def send_header(self, keyword, value):
+        self.write("%s: %s\r\n" % (keyword, value))
+    def end_headers(self):
+        self.write("\r\n")
+
+#
+# Main CGI handler
+#
+def main(out, err):
+    import os, string
+    import roundup.instance
+    path = string.split(os.environ.get('PATH_INFO', '/'), '/')
+    request = RequestWrapper(out)
+    request.path = os.environ.get('PATH_INFO', '/')
+    tracker = path[1]
+    os.environ['TRACKER_NAME'] = tracker
+    os.environ['PATH_INFO'] = string.join(path[2:], '/')
+    if TRACKER_HOMES.has_key(tracker):
+        # redirect if we need a trailing '/'
+        if len(path) == 2:
+            request.send_response(301)
+            # redirect
+            if os.environ.get('HTTPS', '') == 'on':
+                protocol = 'https'
+            else:
+                protocol = 'http'
+            absolute_url = '%s://%s%s/'%(protocol, os.environ['HTTP_HOST'],
+                os.environ.get('REQUEST_URI', ''))
+            request.send_header('Location', absolute_url)
+            request.end_headers()
+            out.write('Moved Permanently')
+        else:
+            tracker_home = TRACKER_HOMES[tracker]
+            tracker = roundup.instance.open(tracker_home)
+            import roundup.cgi.client
+            if hasattr(tracker, 'Client'):
+                client = tracker.Client(tracker, request, os.environ)
+            else:
+                client = roundup.cgi.client.Client(tracker, request, os.environ)
+            try:
+                client.main()
+            except roundup.cgi.client.Unauthorised:
+                request.send_response(403)
+                request.send_header('Content-Type', 'text/html')
+                request.end_headers()
+                out.write('Unauthorised')
+            except roundup.cgi.client.NotFound:
+                request.send_response(404)
+                request.send_header('Content-Type', 'text/html')
+                request.end_headers()
+                out.write('Not found: %s'%client.path)
+
+    else:
+        import urllib
+        request.send_response(200)
+        request.send_header('Content-Type', 'text/html')
+        request.end_headers()
+        w = request.write
+        w(_('<html><head><title>Roundup trackers index</title></head>\n'))
+        w(_('<body><h1>Roundup trackers index</h1><ol>\n'))
+        homes = TRACKER_HOMES.keys()
+        homes.sort()
+        for tracker in homes:
+            w(_('<li><a href="%(tracker_url)s/index">%(tracker_name)s</a>\n')%{
+                'tracker_url': os.environ['SCRIPT_NAME']+'/'+
+                               urllib.quote(tracker),
+                'tracker_name': cgi.escape(tracker)})
+        w(_('</ol></body></html>'))
+
+#
+# Now do the actual CGI handling
+#
+out, err = sys.stdout, sys.stderr
+try:
+    # force input/output to binary (important for file up/downloads)
+    if sys.platform == "win32":
+        import os, msvcrt
+        msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
+        msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
+    checkconfig()
+    sys.stdout = sys.stderr = LOG
+    main(out, err)
+except SystemExit:
+    pass
+except:
+    sys.stdout, sys.stderr = out, err
+    out.write('Content-Type: text/html\n\n')
+    if DEBUG_TO_CLIENT:
+        cgitb.handler()
+    else:
+        out.write(cgitb.breaker())
+        ts = time.ctime()
+        out.write('''<p>%s: An error occurred. Please check
+            the server log for more infomation.</p>'''%ts)
+        print >> sys.stderr, 'EXCEPTION AT', ts
+        traceback.print_exc(0, sys.stderr)
+
+sys.stdout.flush()
+sys.stdout, sys.stderr = out, err
+LOG.close()
+
+# vim: set filetype=python ts=4 sw=4 et si

Modified: tracker/vendor/roundup/current/roundup/__init__.py
==============================================================================
--- tracker/vendor/roundup/current/roundup/__init__.py	(original)
+++ tracker/vendor/roundup/current/roundup/__init__.py	Thu Nov  9 20:15:10 2006
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: __init__.py,v 1.41 2006/10/05 23:08:20 richard Exp $
+# $Id: __init__.py,v 1.43 2006/11/09 05:38:54 richard Exp $
 
 '''Roundup - issue tracking for knowledge workers.
 
@@ -68,6 +68,6 @@
 '''
 __docformat__ = 'restructuredtext'
 
-__version__ = '1.2.1'
+__version__ = '1.3.0'
 
 # vim: set filetype=python ts=4 sw=4 et si

Modified: tracker/vendor/roundup/current/roundup/backends/__init__.py
==============================================================================
--- tracker/vendor/roundup/current/roundup/backends/__init__.py	(original)
+++ tracker/vendor/roundup/current/roundup/backends/__init__.py	Thu Nov  9 20:15:10 2006
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 #
-# $Id: __init__.py,v 1.38 2006/10/04 02:58:10 richard Exp $
+# $Id: __init__.py,v 1.39 2006/10/09 23:49:32 richard Exp $
 
 '''Container for the hyperdb storage backend implementations.
 '''
@@ -31,7 +31,7 @@
     'mysql': ('MySQLdb',),
     'postgresql': ('psycopg',),
     'tsearch2': ('psycopg',),
-    'sqlite': ('pysqlite', 'pysqlite2', 'sqlite3'),
+    'sqlite': ('pysqlite', 'pysqlite2', 'sqlite3', '_sqlite3'),
 }
 
 def get_backend(name):

Modified: tracker/vendor/roundup/current/roundup/backends/back_postgresql.py
==============================================================================
--- tracker/vendor/roundup/current/roundup/backends/back_postgresql.py	(original)
+++ tracker/vendor/roundup/current/roundup/backends/back_postgresql.py	Thu Nov  9 20:15:10 2006
@@ -1,4 +1,4 @@
-#$Id: back_postgresql.py,v 1.36 2006/10/03 23:28:51 richard Exp $
+#$Id: back_postgresql.py,v 1.37 2006/11/09 00:55:33 richard Exp $
 #
 # Copyright (c) 2003 Martynas Sklyzmantas, Andrey Lebedev <andrey at micro.lt>
 #
@@ -131,7 +131,7 @@
         try:
             self.load_dbschema()
         except psycopg.ProgrammingError, message:
-            if str(message).find('"schema" does not exist') == -1:
+            if str(message).find('schema') == -1:
                 raise
             self.rollback()
             self.init_dbschema()

Modified: tracker/vendor/roundup/current/roundup/backends/back_sqlite.py
==============================================================================
--- tracker/vendor/roundup/current/roundup/backends/back_sqlite.py	(original)
+++ tracker/vendor/roundup/current/roundup/backends/back_sqlite.py	Thu Nov  9 20:15:10 2006
@@ -1,4 +1,4 @@
-# $Id: back_sqlite.py,v 1.47 2006/10/04 01:12:00 richard Exp $
+# $Id: back_sqlite.py,v 1.48 2006/10/10 03:55:31 richard Exp $
 '''Implements a backend for SQLite.
 
 See https://pysqlite.sourceforge.net/ for pysqlite info
@@ -13,15 +13,17 @@
 
 from roundup import hyperdb, date, password
 from roundup.backends import rdbms_common
-is_sqlite3 = False
+sqlite_version = None
 try:
     import sqlite
+    sqlite_version = 1
 except ImportError:
     try:
         from pysqlite2 import dbapi2 as sqlite
+        sqlite_version = 2
     except ImportError:
         import sqlite3 as sqlite
-        is_sqlite3 = True
+        sqlite_version = 3
 
 def db_exists(config):
     return os.path.exists(os.path.join(config.DATABASE, 'db'))
@@ -31,7 +33,7 @@
 
 class Database(rdbms_common.Database):
     # char to use for positional arguments
-    if is_sqlite3:
+    if sqlite_version in (2,3):
         arg = '?'
     else:
         arg = '%s'
@@ -98,12 +100,12 @@
         logging.getLogger('hyperdb').info('open database %r'%db)
         # set a 30 second timeout (extraordinarily generous) for handling
         # locked database
-        if is_sqlite3:
-            conn = sqlite.connect(db, 30)
-            conn.row_factory = sqlite.Row
-        else:
+        if sqlite_version == 1:
             conn = sqlite.connect(db=db)
             conn.db.sqlite_busy_handler(self.sqlite_busy_handler)
+        else:
+            conn = sqlite.connect(db, timeout=30)
+            conn.row_factory = sqlite.Row
         cursor = conn.cursor()
         return (conn, cursor)
 
@@ -264,7 +266,7 @@
                     # generate the new value for the Interval int column
                     if name.endswith('_int__'):
                         name = name[2:-6]
-                        if is_sqlite3:
+                        if sqlite_version in (2,3):
                             try:
                                 v = hyperdb.Interval(entry[name]).as_seconds()
                             except IndexError:
@@ -273,12 +275,12 @@
                             v = hyperdb.Interval(entry[name]).as_seconds()
                         else:
                             v = None
-                    elif is_sqlite3:
+                    elif sqlite_version in (2,3):
                         try:
                             v = entry[name]
                         except IndexError:
                             v = None
-                    elif (not is_sqlite3 and entry.has_key(name)):
+                    elif (sqlite_version == 1 and entry.has_key(name)):
                         v = entry[name]
                     else:
                         v = None
@@ -368,7 +370,7 @@
         vals = (spec.classname, 1)
         self.sql(sql, vals)
 
-    if is_sqlite3:
+    if sqlite_version in (2,3):
         def load_journal(self, classname, cols, nodeid):
             '''We need to turn the sqlite3.Row into a tuple so it can be
             unpacked'''

Modified: tracker/vendor/roundup/current/roundup/cgi/apache.py
==============================================================================
--- tracker/vendor/roundup/current/roundup/cgi/apache.py	(original)
+++ tracker/vendor/roundup/current/roundup/cgi/apache.py	Thu Nov  9 20:15:10 2006
@@ -20,8 +20,8 @@
 #                   instead of mod_python FieldStorage
 # 29-apr-2004 [als] created
 
-__version__ = "$Revision: 1.4 $"[11:-2]
-__date__ = "$Date: 2004/11/22 07:33:34 $"[7:-2]
+__version__ = "$Revision: 1.6 $"[11:-2]
+__date__ = "$Date: 2006/11/09 00:36:21 $"[7:-2]
 
 import cgi
 import os
@@ -52,6 +52,12 @@
         # .wfile.write()
         self.wfile = self._req
 
+    def start_response(self, headers, response):
+        self.send_response(response)
+        for key, value in headers:
+            self.send_header(key, value)
+        self.end_headers()
+
     def send_response(self, response_code):
         """Set HTTP response code"""
         self._req.status = response_code
@@ -80,7 +86,7 @@
     if _timing.lower() in ("no", "false"):
         _timing = ""
     _debug = _options.get("TrackerDebug", "no")
-    _debug = _debug.lower not in ("no", "false")
+    _debug = _debug.lower() not in ("no", "false")
     if not (_home and os.path.isdir(_home)):
         apache.log_error(
             "PythonOption TrackerHome missing or invalid for %(uri)s"

Modified: tracker/vendor/roundup/current/roundup/cgi/client.py
==============================================================================
--- tracker/vendor/roundup/current/roundup/cgi/client.py	(original)
+++ tracker/vendor/roundup/current/roundup/cgi/client.py	Thu Nov  9 20:15:10 2006
@@ -1,4 +1,4 @@
-# $Id: client.py,v 1.227 2006/08/29 04:20:50 richard Exp $
+# $Id: client.py,v 1.228 2006/11/09 00:36:21 richard Exp $
 
 """WWW request handler (also used in the stand-alone server).
 """
@@ -278,7 +278,7 @@
             # the headers, otherwise the headers have been set before the
             # exception was raised
             if url:
-                self.additional_headers['Location'] = url
+                self.additional_headers['Location'] = str(url)
                 self.response_code = 302
             self.write_html('Redirecting to <a href="%s">%s</a>'%(url, url))
         except SendFile, designator:
@@ -286,15 +286,15 @@
                 self.serve_file(designator)
             except NotModified:
                 # send the 304 response
-                self.request.send_response(304)
-                self.request.end_headers()
+                self.response_code = 304
+                self.header()
         except SendStaticFile, file:
             try:
                 self.serve_static_file(str(file))
             except NotModified:
                 # send the 304 response
-                self.request.send_response(304)
-                self.request.end_headers()
+                self.response_code = 304
+                self.header()
         except Unauthorised, message:
             # users may always see the front page
             self.classname = self.nodeid = None
@@ -693,11 +693,18 @@
     def _serve_file(self, lmt, mime_type, content):
         ''' guts of serve_file() and serve_static_file()
         '''
+        # spit out headers
+        self.additional_headers['Content-Type'] = mime_type
+        self.additional_headers['Content-Length'] = str(len(content))
+        lmt = rfc822.formatdate(lmt)
+        self.additional_headers['Last-Modified'] = lmt
+
         ims = None
         # see if there's an if-modified-since...
-        if hasattr(self.request, 'headers'):
-            ims = self.request.headers.getheader('if-modified-since')
-        elif self.env.has_key('HTTP_IF_MODIFIED_SINCE'):
+        # XXX see which interfaces set this
+        #if hasattr(self.request, 'headers'):
+            #ims = self.request.headers.getheader('if-modified-since')
+        if self.env.has_key('HTTP_IF_MODIFIED_SINCE'):
             # cgi will put the header in the env var
             ims = self.env['HTTP_IF_MODIFIED_SINCE']
         if ims:
@@ -706,11 +713,6 @@
             if lmtt <= ims:
                 raise NotModified
 
-        # spit out headers
-        self.additional_headers['Content-Type'] = mime_type
-        self.additional_headers['Content-Length'] = len(content)
-        lmt = rfc822.formatdate(lmt)
-        self.additional_headers['Last-Modified'] = lmt
         self.write(content)
 
     def renderContext(self):
@@ -870,15 +872,17 @@
 
         if headers.get('Content-Type', 'text/html') == 'text/html':
             headers['Content-Type'] = 'text/html; charset=utf-8'
-        self.request.send_response(response)
-        for entry in headers.items():
-            self.request.send_header(*entry)
+
+        headers = headers.items()
+
         for ((path, name), (value, expire)) in self.add_cookies.items():
             cookie = "%s=%s; Path=%s;"%(name, value, path)
             if expire is not None:
                 cookie += " expires=%s;"%Cookie._getdate(expire)
-            self.request.send_header('Set-Cookie', cookie)
-        self.request.end_headers()
+            headers.append(('Set-Cookie', cookie))
+
+        self.request.start_response(headers, response)
+
         self.headers_done = 1
         if self.debug:
             self.headers_sent = headers

Modified: tracker/vendor/roundup/current/roundup/cgi/templating.py
==============================================================================
--- tracker/vendor/roundup/current/roundup/cgi/templating.py	(original)
+++ tracker/vendor/roundup/current/roundup/cgi/templating.py	Thu Nov  9 20:15:10 2006
@@ -2119,7 +2119,7 @@
     - "search_text" text to perform a full-text search on for an index
     '''
     def __repr__(self):
-        return '<HTMLRequest %r>'%self.form
+        return '<HTMLRequest %r>'%self.__dict__
 
     def __init__(self, client):
         # _client is needed by HTMLInputMixin

Added: tracker/vendor/roundup/current/roundup/cgi/wsgi_handler.py
==============================================================================
--- (empty file)
+++ tracker/vendor/roundup/current/roundup/cgi/wsgi_handler.py	Thu Nov  9 20:15:10 2006
@@ -0,0 +1,75 @@
+# WSGI interface for Roundup Issue Tracker
+#
+# This module is free software, you may redistribute it
+# and/or modify under the same terms as Python.
+#
+
+import os
+import cgi
+import weakref
+
+import roundup.instance
+from roundup.cgi import TranslationService
+from BaseHTTPServer import BaseHTTPRequestHandler
+
+
+class Writer(object):
+    '''Perform a start_response if need be when we start writing.'''
+    def __init__(self, request):
+        self.request = request #weakref.ref(request)
+    def write(self, data):
+        f = self.request.get_wfile()
+        self.write = f
+        return f(data)
+
+class RequestDispatcher(object):
+    def __init__(self, home, debug=False, timing=False, lang=None):
+        assert os.path.isdir(home), '%r is not a directory'%(home,)
+        self.home = home
+        self.debug = debug
+        self.timing = timing
+        if lang:
+            self.translator = TranslationService.get_translation(lang,
+                tracker_home=home)
+        else:
+            self.translator = None
+
+    def __call__(self, environ, start_response):
+        """Initialize with `apache.Request` object"""
+        self.environ = environ
+        self.__start_response = start_response
+
+        self.wfile = Writer(self)
+        self.__wfile = None
+
+        tracker = roundup.instance.open(self.home, not self.debug)
+
+        # need to strip the leading '/'
+        environ["PATH_INFO"] = environ["PATH_INFO"][1:]
+        if self.timing:
+            environ["CGI_SHOW_TIMING"] = self.timing
+
+        form = cgi.FieldStorage(fp=environ['wsgi.input'], environ=environ)
+
+        client = tracker.Client(tracker, self, environ, form,
+            self.translator)
+        try:
+            client.main()
+        except roundup.cgi.client.NotFound:
+            self.start_response([('Content-Type', 'text/html')], 404)
+            self.wfile.write('Not found: %s'%client.path)
+
+        # all body data has been written using wfile
+        return []
+
+    def start_response(self, headers, response_code):
+        """Set HTTP response code"""
+        description = BaseHTTPRequestHandler.responses[response_code]
+        self.__wfile = self.__start_response('%d %s'%(response_code,
+            description), headers)
+
+    def get_wfile(self):
+        if self.__wfile is None:
+            raise ValueError, 'start_response() not called'
+        return self.__wfile
+

Modified: tracker/vendor/roundup/current/roundup/hyperdb.py
==============================================================================
--- tracker/vendor/roundup/current/roundup/hyperdb.py	(original)
+++ tracker/vendor/roundup/current/roundup/hyperdb.py	Thu Nov  9 20:15:10 2006
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 #
-# $Id: hyperdb.py,v 1.127 2006/08/30 09:35:31 schlatterbeck Exp $
+# $Id: hyperdb.py,v 1.128 2006/11/09 03:08:22 richard Exp $
 
 """Hyperdatabase implementation, especially field types.
 """
@@ -1196,20 +1196,20 @@
     #
     def audit(self, event, detector, priority = 100):
         """Register an auditor detector"""
-        self.auditors[event].append((priority, detector))
+        self.auditors[event].append((priority, detector.__name__, detector))
 
     def fireAuditors(self, event, nodeid, newvalues):
         """Fire all registered auditors"""
-        for prio, audit in self.auditors[event]:
+        for prio, name, audit in self.auditors[event]:
             audit(self.db, self, nodeid, newvalues)
 
     def react(self, event, detector, priority = 100):
         """Register a reactor detector"""
-        self.reactors[event].append((priority, detector))
+        self.reactors[event].append((priority, detector.__name__, detector))
 
     def fireReactors(self, event, nodeid, oldvalues):
         """Fire all registered reactors"""
-        for prio, react in self.reactors[event]:
+        for prio, name, react in self.reactors[event]:
             react(self.db, self, nodeid, oldvalues)
 
     #

Modified: tracker/vendor/roundup/current/roundup/roundupdb.py
==============================================================================
--- tracker/vendor/roundup/current/roundup/roundupdb.py	(original)
+++ tracker/vendor/roundup/current/roundup/roundupdb.py	Thu Nov  9 20:15:10 2006
@@ -16,7 +16,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 #
-# $Id: roundupdb.py,v 1.125 2006/09/09 05:50:17 richard Exp $
+# $Id: roundupdb.py,v 1.126 2006/11/09 01:13:56 richard Exp $
 
 """Extending hyperdb with types specific to issue-tracking.
 """
@@ -496,6 +496,10 @@
                     value = [link.get(entry, key) for entry in value]
                 value.sort()
                 value = ', '.join(value)
+            else:
+                value = str(value)
+                if '\n' in value:
+                    value = '\n'+self.indentChangeNoteValue(value)
             m.append('%s: %s'%(propname, value))
         m.insert(0, '----------')
         m.insert(0, '')
@@ -587,10 +591,19 @@
                     change += ' -%s'%(', '.join(l))
             else:
                 change = '%s -> %s'%(oldvalue, value)
+                if '\n' in change:
+                    value = self.indentChangeNoteValue(str(value))
+                    oldvalue = self.indentChangeNoteValue(str(oldvalue))
+                    change = '\nNow:\n%s\nWas:\n%s'%(value, oldvalue)
             m.append('%s: %s'%(propname, change))
         if m:
             m.insert(0, '----------')
             m.insert(0, '')
         return '\n'.join(m)
 
+    def indentChangeNoteValue(self, text):
+        lines = text.rstrip('\n').split('\n')
+        lines = [ '  '+line for line in lines ]
+        return '\n'.join(lines)
+
 # vim: set filetype=python sts=4 sw=4 et si :

Modified: tracker/vendor/roundup/current/roundup/scripts/roundup_server.py
==============================================================================
--- tracker/vendor/roundup/current/roundup/scripts/roundup_server.py	(original)
+++ tracker/vendor/roundup/current/roundup/scripts/roundup_server.py	Thu Nov  9 20:15:10 2006
@@ -17,7 +17,7 @@
 
 """Command-line script that runs a server over roundup.cgi.client.
 
-$Id: roundup_server.py,v 1.83 2006/04/27 04:59:37 richard Exp $
+$Id: roundup_server.py,v 1.85 2006/11/09 00:36:21 richard Exp $
 """
 __docformat__ = 'restructuredtext'
 
@@ -282,6 +282,12 @@
             # stderr is no longer viable
             pass
 
+    def start_response(self, headers, response):
+        self.send_response(response)
+        for key, value in headers:
+            self.send_header(key, value)
+        self.end_headers()
+
 def error():
     exc_type, exc_value = sys.exc_info()[:2]
     return _('Error: %s: %s' % (exc_type, exc_value))
@@ -324,6 +330,7 @@
 
     if os.getuid():
         print _('WARNING: ignoring "-u" argument, not root')
+        return
 
     try:
         import pwd

Modified: tracker/vendor/roundup/current/setup.py
==============================================================================
--- tracker/vendor/roundup/current/setup.py	(original)
+++ tracker/vendor/roundup/current/setup.py	Thu Nov  9 20:15:10 2006
@@ -16,7 +16,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 #
-# $Id: setup.py,v 1.91 2006/10/07 03:03:28 richard Exp $
+# $Id: setup.py,v 1.92 2006/11/09 05:38:54 richard Exp $
 
 from distutils.core import setup, Extension
 from distutils.util import get_platform
@@ -348,39 +348,67 @@
 command-line, web and e-mail interfaces. It is based on the winning design
 from Ka-Ping Yee in the Software Carpentry "Track" design competition.
 
+New Features in 1.3.0:
+- WSGI support via roundup.cgi.wsgi_handler
+
+Fixed in 1.3.0:
+- sqlite module detection was broken for python 2.5 compiled without sqlite
+  support
+- fixed support for pysqlite2 (version 2.1.0 is the minimum version
+  supported)
+- roundup-server called setuid when run by non-root user
+- fix sort/group direction checkbox in issue.index.html (sf bug 1593025)
+- fix error detection for non-EN locales of postgres (sf bug 1592249)
+- fix email change note rendering of multiline properties (sf patch 1575223)
+- fix sidebar search links (sf patch 1574467)
+- nicer "permission required" messages (sf patch 1558183)
+- fix unstable ordering of detectors (sf bug 1585378)
+
 If you're upgrading from an older version of Roundup you *must* follow
 the "Software Upgrade" guidelines given in the maintenance documentation.
 
-Bugs fixed in 1.2.1:
+Roundup requires python 2.3 or later for correct operation.
+
+To give Roundup a try, just download (see below), unpack and run::
+
+    roundup-demo
+
+Release info and download page:
+     http://cheeseshop.python.org/pypi/roundup
+Source and documentation is available at the website:
+     http://roundup.sourceforge.net/
+Mailing lists - the place to ask questions:
+     http://sourceforge.net/mail/?group_id=31577
+
+
+About Roundup
+=============
+
+Roundup is a simple-to-use and -install issue-tracking system with
+command-line, web and e-mail interfaces. It is based on the winning design
+from Ka-Ping Yee in the Software Carpentry "Track" design competition.
 
-- E-mail subject line prefix delimiter configuration was being ignored.
-- Password confirm field in user editing.
+Note: Ping is not responsible for this project. The contact for this
+project is richard at users.sourceforge.net.
 
-New Features in 1.2.x:
+Roundup manages a number of issues (with flexible properties such as
+"description", "priority", and so on) and provides the ability to:
 
-- supports Python 2.5, including the sqlite3 module
-- full timezone support (sf patch 1465296)
-- handle connection loss when responding to web requests
-- match incoming mail In-Reply-To against existing messages when no issue
-  id is specified in the Subject
-- added StringHTMLProperty wrapped() method to wrap long lines in issue
-  display
-- include the popcal in Date field editing and search fields by default
-- @required in forms may now specify properties of linked items (sf patch
-  1507093)
-- update for latest version of pysqlite (sf bug 1487098; patch 1534227)
-- update for latest version of psycopg2 (sf patch 1429391)
-- new "exporttables" command in roundup-admin (sf bug 1533791)
-- roundup-admin "export" may specify classes to exclude (sf bug 1533791)
-- sorting and grouping by multiple properties is now supported by the
-  backends *and* the classic template.
-- sorting, grouping, and searching by transitive properties (e.g.,
-  messages.author.supervisor) is now supported in all backends
-- added filter_sql to SQL backends which takes an arbitrary SQL statement
-  and returns a list of item ids
+(a) submit new issues,
+(b) find and edit existing issues, and
+(c) discuss issues with other participants.
+
+The system will facilitate communication among the participants by managing
+discussions and notifying interested parties when issues are edited. One of
+the major design goals for Roundup that it be simple to get going. Roundup
+is therefore usable "out of the box" with any python 2.3+ installation. It
+doesn't even need to be "installed" to be operational, though a
+disutils-based install script is provided.
+
+It comes with two issue tracker templates (a classic bug/feature tracker and
+a minimal skeleton) and five database back-ends (anydbm, sqlite, metakit,
+mysql and postgresql).
 
-There was also a lot of bugfixes - see the bundled CHANGES.txt file for the
-list.
 ''',
         'author': "Richard Jones",
         'author_email': "richard at users.sourceforge.net",

Modified: tracker/vendor/roundup/current/templates/classic/html/_generic.index.html
==============================================================================
--- tracker/vendor/roundup/current/templates/classic/html/_generic.index.html	(original)
+++ tracker/vendor/roundup/current/templates/classic/html/_generic.index.html	Thu Nov  9 20:15:10 2006
@@ -11,10 +11,16 @@
 
 <td class="content" metal:fill-slot="content">
 
-<span tal:condition="python:not (context.is_view_ok() or context.is_edit_ok())"
+<span tal:condition="python:not (context.is_view_ok() or context.is_edit_ok()
+ or request.user.hasRole('Anonymous'))"
  tal:omit-tag="python:1" i18n:translate=""
 >You are not allowed to view this page.</span>
 
+<span tal:condition="python:not (context.is_view_ok() or context.is_edit_ok())
+ and request.user.hasRole('Anonymous')"
+ tal:omit-tag="python:1" i18n:translate=""
+>Please login with your username and password.</span>
+
 <tal:block tal:condition="context/is_edit_ok">
 <tal:block i18n:translate="">
 <p class="form-help">

Modified: tracker/vendor/roundup/current/templates/classic/html/_generic.item.html
==============================================================================
--- tracker/vendor/roundup/current/templates/classic/html/_generic.item.html	(original)
+++ tracker/vendor/roundup/current/templates/classic/html/_generic.item.html	Thu Nov  9 20:15:10 2006
@@ -9,8 +9,13 @@
 
 <td class="content" metal:fill-slot="content">
 
-<p tal:condition="not:context/is_view_ok" i18n:translate="">You are not
-    allowed to view this page.</p>
+<p tal:condition="python:not (context.is_view_ok()
+ or request.user.hasRole('Anonymous'))" i18n:translate="">
+ You are not allowed to view this page.</p>
+
+<p tal:condition="python:not context.is_view_ok()
+ and request.user.hasRole('Anonymous')" i18n:translate="">
+ Please login with your username and password.</p>
 
 <div tal:condition="context/is_view_ok">
 

Modified: tracker/vendor/roundup/current/templates/classic/html/file.item.html
==============================================================================
--- tracker/vendor/roundup/current/templates/classic/html/file.item.html	(original)
+++ tracker/vendor/roundup/current/templates/classic/html/file.item.html	Thu Nov  9 20:15:10 2006
@@ -6,8 +6,13 @@
 
 <td class="content" metal:fill-slot="content">
 
-<p tal:condition="not:context/is_view_ok" i18n:translate="">You are not
-    allowed to view this page.</p>
+<p tal:condition="python:not (context.is_view_ok()
+ or request.user.hasRole('Anonymous'))" i18n:translate="">
+ You are not allowed to view this page.</p>
+
+<p tal:condition="python:not context.is_view_ok()
+ and request.user.hasRole('Anonymous')" i18n:translate="">
+ Please login with your username and password.</p>
 
 <form method="POST" onSubmit="return submit_once()"
       enctype="multipart/form-data" tal:condition="context/is_view_ok"

Modified: tracker/vendor/roundup/current/templates/classic/html/issue.index.html
==============================================================================
--- tracker/vendor/roundup/current/templates/classic/html/issue.index.html	(original)
+++ tracker/vendor/roundup/current/templates/classic/html/issue.index.html	Thu Nov  9 20:15:10 2006
@@ -1,4 +1,4 @@
-<!-- $Id: issue.index.html,v 1.25 2006/09/18 00:03:02 tobias-herp Exp $ -->
+<!-- $Id: issue.index.html,v 1.27 2006/11/09 01:26:28 richard Exp $ -->
 <tal:block metal:use-macro="templates/page/macros/icing">
 <title metal:fill-slot="head_title" >
   <span tal:omit-tag="true" i18n:translate="" >List of issues</span>
@@ -13,8 +13,13 @@
 </span>
 <td class="content" metal:fill-slot="content">
 
-<p tal:condition="not:context/is_view_ok" i18n:translate="">You are not
-  allowed to view this page.</p>
+<p tal:condition="python:not (context.is_view_ok()
+ or request.user.hasRole('Anonymous'))" i18n:translate="">
+ You are not allowed to view this page.</p>
+
+<p tal:condition="python:not context.is_view_ok()
+ and request.user.hasRole('Anonymous')" i18n:translate="">
+ Please login with your username and password.</p>
 
 <tal:block tal:define="batch request/batch" tal:condition="context/is_view_ok">
  <table class="list">
@@ -119,8 +124,8 @@
     </select>
    </td>
    <th i18n:translate="">Descending:</th>
-   <td><input type="checkbox" name="@sortdir"
-              tal:attributes="checked python:key and key[0] == '-'">
+   <td><input type="checkbox" tal:attributes="name python:'@sortdir%d'%n;
+              checked python:key and key[0] == '-'">
    </td>
   </tr>
   </tal:block>
@@ -140,8 +145,8 @@
     </select>
    </td>
    <th i18n:translate="">Descending:</th>
-   <td><input type="checkbox" name="@groupdir"
-              tal:attributes="checked python:key and key[0] == '-'">
+   <td><input type="checkbox" tal:attributes="name python:'@groupdir%d'%n;
+              checked python:key and key[0] == '-'">
    </td>
   </tr>
   </tal:block>

Modified: tracker/vendor/roundup/current/templates/classic/html/issue.item.html
==============================================================================
--- tracker/vendor/roundup/current/templates/classic/html/issue.item.html	(original)
+++ tracker/vendor/roundup/current/templates/classic/html/issue.item.html	Thu Nov  9 20:15:10 2006
@@ -25,8 +25,13 @@
 
 <td class="content" metal:fill-slot="content">
 
-<p tal:condition="not:context/is_view_ok" i18n:translate="">You are not
-    allowed to view this page.</p>
+<p tal:condition="python:not (context.is_view_ok()
+ or request.user.hasRole('Anonymous'))" i18n:translate="">
+ You are not allowed to view this page.</p>
+
+<p tal:condition="python:not context.is_view_ok()
+ and request.user.hasRole('Anonymous')" i18n:translate="">
+ Please login with your username and password.</p>
 
 <div tal:condition="context/is_view_ok">
 

Modified: tracker/vendor/roundup/current/templates/classic/html/msg.item.html
==============================================================================
--- tracker/vendor/roundup/current/templates/classic/html/msg.item.html	(original)
+++ tracker/vendor/roundup/current/templates/classic/html/msg.item.html	Thu Nov  9 20:15:10 2006
@@ -23,8 +23,13 @@
 </tal:block>
 <td class="content" metal:fill-slot="content">
 
-<p tal:condition="not:context/is_view_ok" i18n:translate="">You are not
-    allowed to view this page.</p>
+<p tal:condition="python:not (context.is_view_ok()
+ or request.user.hasRole('Anonymous'))" i18n:translate="">
+ You are not allowed to view this page.</p>
+
+<p tal:condition="python:not context.is_view_ok()
+ and request.user.hasRole('Anonymous')" i18n:translate="">
+ Please login with your username and password.</p>
 
 <div tal:condition="context/is_view_ok">
 <table class="form">

Modified: tracker/vendor/roundup/current/templates/classic/html/page.html
==============================================================================
--- tracker/vendor/roundup/current/templates/classic/html/page.html	(original)
+++ tracker/vendor/roundup/current/templates/classic/html/page.html	Thu Nov  9 20:15:10 2006
@@ -39,7 +39,7 @@
        <input type="hidden" name="@sort" value="activity"/>
        <input type="hidden" name="@group" value="priority"/>
        <input id="search-text" name="@search_text" size="10"/>
-       <input type="submit" id="submit" name="submit" value="Search" i18n:attributes="value"/>
+       <input type="submit" id="submit" name="submit" value="Search" i18n:attributes="value" tal:attributes="value request/search_text" />
      </form>
   </div>
  </td>
@@ -70,6 +70,7 @@
       '@group': 'priority',
       '@filter': 'status,assignedto',
       '@columns': columns,
+      '@search_text': '',
       'status': status_notresolved,
       'assignedto': '-1',
       '@dispname': i18n.gettext('Show Unassigned'),
@@ -81,6 +82,7 @@
       '@group': 'priority',
       '@filter': 'status',
       '@columns': columns_showall,
+      '@search_text': '',
       'status': status_notresolved,
       '@dispname': i18n.gettext('Show All'),
      })"
@@ -141,7 +143,18 @@
   <p class="userblock" tal:condition="python:request.user.username != 'anonymous'">
    <b i18n:translate="">Hello, <span i18n:name="user"
     tal:replace="request/user/username">username</span></b><br>
-   <a href="#" tal:attributes="href string:issue?@sort=-activity&@group=priority&@filter=status,assignedto&@columns=id,activity,title,creator,status&status=-1,1,2,3,4,5,6,7&assignedto=${request/user/id}" i18n:translate="">Your Issues</a><br>
+    <a href="#"
+       tal:attributes="href python:request.indexargs_url('issue', {
+      '@sort': '-activity',
+      '@group': 'priority',
+      '@filter': 'status,assignedto',
+      '@columns': 'id,activity,title,creator,status',
+      '@search_text': '',
+      'status': status_notresolved,
+      'assignedto': request.user.id,
+      '@dispname': i18n.gettext('Your Issues'),
+     })"
+    i18n:translate="">Your Issues</a><br>
    <a href="#" tal:attributes="href string:user${request/user/id}"
     i18n:translate="">Your Details</a><br>
    <a href="#" tal:attributes="href python:request.indexargs_url('',

Modified: tracker/vendor/roundup/current/templates/classic/html/user.index.html
==============================================================================
--- tracker/vendor/roundup/current/templates/classic/html/user.index.html	(original)
+++ tracker/vendor/roundup/current/templates/classic/html/user.index.html	Thu Nov  9 20:15:10 2006
@@ -6,9 +6,14 @@
  i18n:translate="">User listing</span>
 <td class="content" metal:fill-slot="content">
 
-<span tal:condition="not:context/is_view_ok"
+<span tal:condition="python:not (context.is_view_ok()
+ or request.user.hasRole('Anonymous'))"
  i18n:translate="">You are not allowed to view this page.</span>
 
+<span tal:condition="python:not context.is_view_ok()
+ and request.user.hasRole('Anonymous')"
+ i18n:translate="">Please login with your username and password.</span>
+
 <table width="100%" tal:condition="context/is_view_ok" class="list">
 <tr>
  <th i18n:translate="">Username</th>

Modified: tracker/vendor/roundup/current/templates/classic/html/user.item.html
==============================================================================
--- tracker/vendor/roundup/current/templates/classic/html/user.item.html	(original)
+++ tracker/vendor/roundup/current/templates/classic/html/user.item.html	Thu Nov  9 20:15:10 2006
@@ -32,8 +32,13 @@
 
 <td class="content" metal:fill-slot="content">
 
-<p tal:condition="not:context/is_view_ok" i18n:translate="">You are not
-    allowed to view this page.</p>
+<p tal:condition="python:not (context.is_view_ok()
+ or request.user.hasRole('Anonymous'))" i18n:translate="">
+ You are not allowed to view this page.</p>
+
+<p tal:condition="python:not context.is_view_ok()
+ and request.user.hasRole('Anonymous')" i18n:translate="">
+ Please login with your username and password.</p>
 
 <div tal:condition="context/is_view_ok">
 

Modified: tracker/vendor/roundup/current/templates/minimal/html/_generic.index.html
==============================================================================
--- tracker/vendor/roundup/current/templates/minimal/html/_generic.index.html	(original)
+++ tracker/vendor/roundup/current/templates/minimal/html/_generic.index.html	Thu Nov  9 20:15:10 2006
@@ -11,10 +11,16 @@
 
 <td class="content" metal:fill-slot="content">
 
-<span tal:condition="python:not (context.is_view_ok() or context.is_edit_ok())"
+<span tal:condition="python:not (context.is_view_ok() or context.is_edit_ok()
+ or request.user.hasRole('Anonymous'))"
  tal:omit-tag="python:1" i18n:translate=""
 >You are not allowed to view this page.</span>
 
+<span tal:condition="python:not (context.is_view_ok() or context.is_edit_ok())
+ and request.user.hasRole('Anonymous')"
+ tal:omit-tag="python:1" i18n:translate=""
+>Please login with your username and password.</span>
+
 <tal:block tal:condition="context/is_edit_ok">
 <tal:block i18n:translate="">
 <p class="form-help">

Modified: tracker/vendor/roundup/current/templates/minimal/html/_generic.item.html
==============================================================================
--- tracker/vendor/roundup/current/templates/minimal/html/_generic.item.html	(original)
+++ tracker/vendor/roundup/current/templates/minimal/html/_generic.item.html	Thu Nov  9 20:15:10 2006
@@ -9,10 +9,16 @@
 
 <td class="content" metal:fill-slot="content">
 
-<span tal:condition="python:not (context.is_view_ok() or context.is_edit_ok())"
+<span tal:condition="python:not (context.is_view_ok() or context.is_edit_ok()
+ or request.user.hasRole('Anonymous'))"
  tal:omit-tag="python:1" i18n:translate=""
 >You are not allowed to view this page.</span>
 
+<span tal:condition="python:not (context.is_view_ok() or context.is_edit_ok())
+ and request.user.hasRole('Anonymous')"
+ tal:omit-tag="python:1" i18n:translate=""
+>Please login with your username and password.</span>
+
 <form method="POST" onSubmit="return submit_once()"
       enctype="multipart/form-data" tal:condition="context/is_edit_ok"
       tal:attributes="action context/designator">

Modified: tracker/vendor/roundup/current/templates/minimal/html/page.html
==============================================================================
--- tracker/vendor/roundup/current/templates/minimal/html/page.html	(original)
+++ tracker/vendor/roundup/current/templates/minimal/html/page.html	Thu Nov  9 20:15:10 2006
@@ -39,7 +39,7 @@
        <input type="hidden" name="@sort" value="activity"/>
        <input type="hidden" name="@group" value="priority"/>
        <input id="search-text" name="@search_text" size="10"/>
-       <input type="submit" id="submit" name="submit" value="Search" i18n:attributes="value"/>
+       <input type="submit" id="submit" name="submit" value="Search" i18n:attributes="value" tal:attributes="value request/search_text" />
      </form>
   </div>
  </td>
@@ -70,6 +70,7 @@
       '@group': 'priority',
       '@filter': 'status,assignedto',
       '@columns': columns,
+      '@search_text': '',
       'status': status_notresolved,
       'assignedto': '-1',
       '@dispname': i18n.gettext('Show Unassigned'),
@@ -81,6 +82,7 @@
       '@group': 'priority',
       '@filter': 'status',
       '@columns': columns_showall,
+      '@search_text': '',
       'status': status_notresolved,
       '@dispname': i18n.gettext('Show All'),
      })"
@@ -141,7 +143,6 @@
   <p class="userblock" tal:condition="python:request.user.username != 'anonymous'">
    <b i18n:translate="">Hello, <span i18n:name="user"
     tal:replace="request/user/username">username</span></b><br>
-   <a href="#" tal:attributes="href string:issue?@sort=-activity&@group=priority&@filter=status,assignedto&@columns=id,activity,title,creator,status&status=-1,1,2,3,4,5,6,7&assignedto=${request/user/id}" i18n:translate="">Your Issues</a><br>
    <a href="#" tal:attributes="href string:user${request/user/id}"
     i18n:translate="">Your Details</a><br>
    <a href="#" tal:attributes="href python:request.indexargs_url('',

Modified: tracker/vendor/roundup/current/templates/minimal/html/user.index.html
==============================================================================
--- tracker/vendor/roundup/current/templates/minimal/html/user.index.html	(original)
+++ tracker/vendor/roundup/current/templates/minimal/html/user.index.html	Thu Nov  9 20:15:10 2006
@@ -6,9 +6,14 @@
  i18n:translate="">User listing</span>
 <td class="content" metal:fill-slot="content">
 
-<span tal:condition="not:context/is_view_ok"
+<span tal:condition="python:not (context.is_view_ok()
+ or request.user.hasRole('Anonymous'))"
  i18n:translate="">You are not allowed to view this page.</span>
 
+<span tal:condition="python:not context.is_view_ok()
+ and request.user.hasRole('Anonymous')"
+ i18n:translate="">Please login with your username and password.</span>
+
 <table width="100%" tal:condition="context/is_view_ok" class="list">
 <tr>
  <th i18n:translate="">Username</th>

Modified: tracker/vendor/roundup/current/templates/minimal/html/user.register.html
==============================================================================
--- tracker/vendor/roundup/current/templates/minimal/html/user.register.html	(original)
+++ tracker/vendor/roundup/current/templates/minimal/html/user.register.html	Thu Nov  9 20:15:10 2006
@@ -11,9 +11,12 @@
 <tal:block tal:define=" editok python:request.user.username=='anonymous' and
            request.user.hasPermission('Web Registration')">
 
-<span tal:condition="python:not editok"
+<span tal:condition="python:not (editok or request.user.hasRole('Anonymous'))"
  i18n:translate="">You are not allowed to view this page.</span>
 
+<span tal:condition="python:not editok and request.user.hasRole('Anonymous')"
+ i18n:translate="">Please login with your username and password.</span>
+
 <tal:block tal:condition="editok">
 <form method="POST" onSubmit="return submit_once()" enctype="multipart/form-data">
 <input type="hidden" name=":template" value="register">

Modified: tracker/vendor/roundup/current/test/db_test_base.py
==============================================================================
--- tracker/vendor/roundup/current/test/db_test_base.py	(original)
+++ tracker/vendor/roundup/current/test/db_test_base.py	Thu Nov  9 20:15:10 2006
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 #
-# $Id: db_test_base.py,v 1.78 2006/08/30 08:50:44 schlatterbeck Exp $
+# $Id: db_test_base.py,v 1.80 2006/11/09 05:44:51 richard Exp $
 
 import unittest, os, shutil, errno, imp, sys, time, pprint, sets
 
@@ -626,6 +626,41 @@
         # invalid boolean value
         ar(TypeError, self.db.user.set, nid, assignable='true')
 
+    def testAuditors(self):
+        class test:
+            called = False
+            def call(self, *args): self.called = True
+        create = test()
+
+        self.db.user.audit('create', create.call)
+        self.db.user.create(username="mary")
+        self.assertEqual(create.called, True)
+
+        set = test()
+        self.db.user.audit('set', set.call)
+        self.db.user.set('1', username="joe")
+        self.assertEqual(set.called, True)
+
+        retire = test()
+        self.db.user.audit('retire', retire.call)
+        self.db.user.retire('1')
+        self.assertEqual(retire.called, True)
+
+    def testAuditorTwo(self):
+        class test:
+            n = 0
+            def a(self, *args): self.call_a = self.n; self.n += 1
+            def b(self, *args): self.call_b = self.n; self.n += 1
+            def c(self, *args): self.call_c = self.n; self.n += 1
+        test = test()
+        self.db.user.audit('create', test.b, 1)
+        self.db.user.audit('create', test.a, 1)
+        self.db.user.audit('create', test.c, 2)
+        self.db.user.create(username="mary")
+        self.assertEqual(test.call_a, 0)
+        self.assertEqual(test.call_b, 1)
+        self.assertEqual(test.call_c, 2)
+
     def testJournals(self):
         muid = self.db.user.create(username="mary")
         self.db.user.create(username="pete")
@@ -1010,18 +1045,19 @@
         ae(filt(None, {'nosy': '2', 'status': '1'}, ('+','id'), (None,None)),
             ['3'])
 
-    def testFilteringRange(self):
+    def testFilteringRangeBasic(self):
         ae, filt = self.filteringSetup()
-        # Date ranges
         ae(filt(None, {'deadline': 'from 2003-02-10 to 2003-02-23'}), ['1','3'])
         ae(filt(None, {'deadline': '2003-02-10; 2003-02-23'}), ['1','3'])
         ae(filt(None, {'deadline': '; 2003-02-16'}), ['2'])
-        # Lets assume people won't invent a time machine, otherwise this test
-        # may fail :)
+
+    def testFilteringRangeTwoSyntaxes(self):
+        ae, filt = self.filteringSetup()
         ae(filt(None, {'deadline': 'from 2003-02-16'}), ['1', '3', '4'])
         ae(filt(None, {'deadline': '2003-02-16;'}), ['1', '3', '4'])
-        ae(filt(None, {'deadline': '2003-02-16;'}), ['1', '3', '4'])
-        # year and month granularity
+
+    def testFilteringRangeYearMonthDay(self):
+        ae, filt = self.filteringSetup()
         ae(filt(None, {'deadline': '2002'}), [])
         ae(filt(None, {'deadline': '2003'}), ['1', '2', '3'])
         ae(filt(None, {'deadline': '2004'}), ['4'])
@@ -1029,13 +1065,16 @@
         ae(filt(None, {'deadline': '2003-03'}), [])
         ae(filt(None, {'deadline': '2003-02-16'}), ['1'])
         ae(filt(None, {'deadline': '2003-02-17'}), [])
-        # Interval ranges
+
+    def testFilteringRangeInterval(self):
+        ae, filt = self.filteringSetup()
         ae(filt(None, {'foo': 'from 0:50 to 2:00'}), ['1'])
         ae(filt(None, {'foo': 'from 0:50 to 1d 2:00'}), ['1', '2'])
         ae(filt(None, {'foo': 'from 5:50'}), ['2'])
         ae(filt(None, {'foo': 'to 0:05'}), [])
 
-        # further
+    def testFilteringRangeGeekInterval(self):
+        ae, filt = self.filteringSetup()
         for issue in (
                 { 'deadline': date.Date('. -2d')},
                 { 'deadline': date.Date('. -1d')},
@@ -1406,7 +1445,6 @@
             ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'])
 
 # XXX add sorting tests for other types
-# XXX test auditors and reactors
 
     def testImportExport(self):
         # use the filtering setup to create a bunch of items


More information about the Python-checkins mailing list