changeset 264:a671e5917b33

Many features and fixes. . roundup-admin create now prompts for property info if none is supplied on the command-line. . hyperdb Class getprops() method may now return only the mutable properties. . Login now uses cookies, which makes it a whole lot more flexible. We can now support anonymous user access (read-only, unless there's an "anonymous" user, in which case write access is permitted). Login handling has been moved into cgi_client.Client.main() . The "extended" schema is now the default in roundup init. . The schemas have had their page headings modified to cope with the new login handling. Existing installations should copy the interfaces.py file from the roundup lib directory to their instance home. . Incorrectly had a Bizar Software copyright on the cgitb.py module from Ping - has been removed. . Fixed a whole bunch of places in the CGI interface where we should have been returning Not Found instead of throwing an exception. . Fixed a deviation from the spec: trying to modify the 'id' property of an item now throws an exception.
author Richard Jones <richard@users.sourceforge.net>
date Fri, 05 Oct 2001 02:23:24 +0000
parents e13d55912cd4
children 0a28169a61b0
files CHANGES.txt cgi-bin/roundup.cgi roundup-admin roundup-server roundup/cgi_client.py roundup/hyperdb.py roundup/mailgw.py roundup/templates/extended/htmlbase.py roundup/templates/extended/interfaces.py
diffstat 9 files changed, 333 insertions(+), 130 deletions(-) [+]
line wrap: on
line diff
--- a/CHANGES.txt	Thu Oct 04 02:16:15 2001 +0000
+++ b/CHANGES.txt	Fri Oct 05 02:23:24 2001 +0000
@@ -2,11 +2,34 @@
 are given with the most recent entry first.
 
 2001-??-?? - 0.2.9
+Feature:
+ . roundup-admin create now prompts for property info if none is supplied
+   on the command-line.
+ . hyperdb Class getprops() method may now return only the mutable
+   properties.
+ . CGI interfaces now generate a top-level index of their known instances.
+
+Changed:
+ . Login now uses cookies, which makes it a whole lot more flexible. We can
+   now support anonymous user access (read-only, unless there's an
+   "anonymous" user, in which case write access is permitted). Login
+   handling has been moved into cgi_client.Client.main()
+ . The "extended" schema is now the default in roundup init.
+ . The schemas have had their page headings modified to cope with the new
+   login handling. Existing installations should copy the interfaces.py
+   file from the roundup lib directory to their instance home.
+
 Fixed:
+ . Incorrectly had a Bizar Software copyright on the cgitb.py module from
+   Ping - has been removed.
  . Pretty time interval wasn't handling > 1 month properly.
  . Generation of links to Link/Multilink in indexes. (thanks Hubert Hoegl)
  . AssignedTo wasn't in the "classic" schema's item page.
-
+ . Fixed a whole bunch of places in the CGI interface where we should have
+   been returning Not Found instead of throwing an exception.
+ . Fixed a deviation from the spec: trying to modify the 'id' property of
+   an item now throws an exception.
+ . The plain() template function now html-escapes the content.
 
 2001-08-30 - 0.2.8
 Fixed:
--- a/cgi-bin/roundup.cgi	Thu Oct 04 02:16:15 2001 +0000
+++ b/cgi-bin/roundup.cgi	Fri Oct 05 02:23:24 2001 +0000
@@ -16,7 +16,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: roundup.cgi,v 1.12 2001-10-01 05:55:41 richard Exp $
+# $Id: roundup.cgi,v 1.13 2001-10-05 02:23:24 richard Exp $
 
 # python version check
 import sys
@@ -59,55 +59,38 @@
     traceback.print_exc(None, s)
     print cgi.escape(s.getvalue()), "</pre>"
 
-def main(instance, out):
-    from roundup import cgi_client
-    db = instance.open('admin')
-    auth = os.environ.get("HTTP_CGI_AUTHORIZATION", None)
-    message = 'Unauthorised'
-    if auth:
-        import binascii
-        l = binascii.a2b_base64(auth.split(' ')[1]).split(':')
-        user = l[0]
-        password = None
-        if len(l) > 1:
-            password = l[1]
+def main(out, err):
+    import os, string
+    import roundup.instance
+    path = string.split(os.environ['PATH_INFO'], '/')
+    instance = path[1]
+    os.environ['INSTANCE_NAME'] = instance
+    os.environ['PATH_INFO'] = string.join(path[2:], '/')
+    if ROUNDUP_INSTANCE_HOMES.has_key(instance):
+        instance_home = ROUNDUP_INSTANCE_HOMES[instance]
+        instance = roundup.instance.open(instance_home)
+        from roundup import cgi_client
+        client = instance.Client(instance, out, os.environ)
         try:
-            uid = db.user.lookup(user)
-        except KeyError:
-            auth = None
-            message = 'Username not recognised'
-        else:
-            if password != db.user.get(uid, 'password'):
-                message = 'Incorrect password'
-                auth = None
-    if not auth:
-        out.write('Content-Type: text/html\n')
-        out.write('Status: 401\n')
-        out.write('WWW-Authenticate: basic realm="Roundup"\n\n')
-        keys = os.environ.keys()
-        keys.sort()
-        out.write(message)
-        return
-    client = instance.Client(out, db, os.environ, user)
-    try:
-        client.main()
-    except cgi_client.Unauthorised:
-        out.write('Content-Type: text/html\n')
-        out.write('Status: 403\n\n')
-        out.write('Unauthorised')
-
-def index(out):
-    ''' Print up an index of the available instances
-    '''
-    import urllib
-    w = out.write
-    w("Content-Type: text/html\n\n")
-    w('<html><head><title>Roundup instances index</title><head>\n')
-    w('<body><h1>Roundup instances index</h1><ol>\n')
-    for instance in ROUNDUP_INSTANCE_HOMES.keys():
-        w('<li><a href="%s/index">%s</a>\n'%(urllib.quote(instance),
-            instance))
-    w('</ol></body></html>')
+            client.main()
+        except cgi_client.Unauthorised:
+            out.write('Content-Type: text/html\n')
+            out.write('Status: 403\n\n')
+            out.write('Unauthorised')
+        except cgi_client.NotFound:
+            out.write('Content-Type: text/html\n')
+            out.write('Status: 404\n\n')
+            out.write('Not found: %s'%client.path)
+    else:
+        import urllib
+        w = out.write
+        w("Content-Type: text/html\n\n")
+        w('<html><head><title>Roundup instances index</title><head>\n')
+        w('<body><h1>Roundup instances index</h1><ol>\n')
+        for instance in ROUNDUP_INSTANCE_HOMES.keys():
+            w('<li><a href="%s/index">%s</a>\n'%(urllib.quote(instance),
+                instance))
+        w('</ol></body></html>')
 
 #
 # Now do the actual CGI handling
@@ -115,17 +98,7 @@
 out, err = sys.stdout, sys.stderr
 try:
     sys.stdout = sys.stderr = LOG
-    import os, string
-    import roundup.instance
-    path = string.split(os.environ['PATH_INFO'], '/')
-    instance = path[1]
-    os.environ['PATH_INFO'] = string.join(path[2:], '/')
-    if ROUNDUP_INSTANCE_HOMES.has_key(instance):
-        instance_home = ROUNDUP_INSTANCE_HOMES[instance]
-        instance = roundup.instance.open(instance_home)
-        main(instance, out)
-    else:
-        index(out)
+    main(out, err)
 except:
     sys.stdout, sys.stderr = out, err
     out.write('Content-Type: text/html\n\n')
@@ -135,6 +108,9 @@
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.12  2001/10/01 05:55:41  richard
+# Fixes to the top-level index
+#
 # Revision 1.11  2001/09/29 13:27:00  richard
 # CGI interfaces now spit up a top-level index of all the instances they can
 # serve.
--- a/roundup-admin	Thu Oct 04 02:16:15 2001 +0000
+++ b/roundup-admin	Fri Oct 05 02:23:24 2001 +0000
@@ -16,7 +16,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: roundup-admin,v 1.20 2001-10-04 02:12:42 richard Exp $
+# $Id: roundup-admin,v 1.21 2001-10-05 02:23:24 richard Exp $
 
 import sys
 if int(sys.version[0]) < 2:
@@ -119,9 +119,9 @@
     if template not in templates:
         print 'Templates:', ', '.join(templates)
     while template not in templates:
-        template = raw_input('Select template [classic]: ').strip()
+        template = raw_input('Select template [extended]: ').strip()
         if not template:
-            template = 'classic'
+            template = 'extended'
 
     import roundup.backends
     backends = roundup.backends.__all__
@@ -449,6 +449,14 @@
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.20  2001/10/04 02:12:42  richard
+# Added nicer command-line item adding: passing no arguments will enter an
+# interactive more which asks for each property in turn. While I was at it, I
+# fixed an implementation problem WRT the spec - I wasn't raising a
+# ValueError if the key property was missing from a create(). Also added a
+# protected=boolean argument to getprops() so we can list only the mutable
+# properties (defaults to yes, which lists the immutables).
+#
 # Revision 1.19  2001/10/01 06:40:43  richard
 # made do_get have the args in the correct order
 #
--- a/roundup-server	Thu Oct 04 02:16:15 2001 +0000
+++ b/roundup-server	Fri Oct 05 02:23:24 2001 +0000
@@ -20,7 +20,7 @@
 
 Based on CGIHTTPServer in the Python library.
 
-$Id: roundup-server,v 1.12 2001-09-29 13:27:00 richard Exp $
+$Id: roundup-server,v 1.13 2001-10-05 02:23:24 richard Exp $
 
 """
 import sys
@@ -75,10 +75,12 @@
         sys.stdin = self.rfile
         try:
             self.inner_run_cgi()
+        except cgi_client.NotFound:
+            self.send_error(404, self.path)
         except cgi_client.Unauthorised:
             self.wfile.write('Content-Type: text/html\n')
-            self.wfile.write('Status: 403\n')
-            self.wfile.write('Unauthorised')
+            self.wfile.write('Status: 403\n\n')
+            self.wfile.write('You are not authorised to access this URL.')
         except:
             try:
                 reload(cgitb)
@@ -121,12 +123,12 @@
         if rest == '/':
             return self.index()
         l_path = string.split(rest, '/')
-        instance = urllib.unquote(l_path[1])
-        if self.ROUNDUP_INSTANCE_HOMES.has_key(instance):
-            instance_home = self.ROUNDUP_INSTANCE_HOMES[instance]
+        instance_name = urllib.unquote(l_path[1])
+        if self.ROUNDUP_INSTANCE_HOMES.has_key(instance_name):
+            instance_home = self.ROUNDUP_INSTANCE_HOMES[instance_name]
             instance = roundup.instance.open(instance_home)
         else:
-            return self.index()
+            raise cgi_client.NotFound
 
         # figure out what the rest of the path is
         if len(l_path) > 2:
@@ -136,6 +138,7 @@
 
         # Set up the CGI environment
         env = {}
+        env['INSTANCE_NAME'] = instance_name
         env['REQUEST_METHOD'] = self.command
         env['PATH_INFO'] = urllib.unquote(rest)
         if query:
@@ -176,41 +179,12 @@
         #finally:
         #    del sys.path[0]
 
-        # initialise the roundupdb, check for auth
-        db = instance.open('admin')
-        message = 'Unauthorised'
-        auth = self.headers.getheader('authorization')
-        if auth:
-            l = binascii.a2b_base64(auth.split(' ')[1]).split(':')
-            user = l[0]
-            password = None
-            if len(l) > 1:
-                password = l[1]
-            try:
-                uid = db.user.lookup(user)
-            except KeyError:
-                auth = None
-                message = 'Username not recognised'
-            else:
-                if password != db.user.get(uid, 'password'):
-                    message = 'Incorrect password'
-                    auth = None
-        db.close()
-        del db
-        if not auth:
-            self.send_response(401)
-            self.send_header('Content-Type', 'text/html')
-            self.send_header('WWW-Authenticate', 'basic realm="Roundup"')
-            self.end_headers()
-            self.wfile.write(message)
-            return
-
         self.send_response(200, "Script output follows")
 
         # do the roundup thang
-        db = instance.open(user)
-        client = instance.Client(self.wfile, db, env, user)
+        client = instance.Client(instance, self.wfile, env)
         client.main()
+
     do_POST = run_cgi
 
 nobody = None
@@ -282,6 +256,10 @@
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.12  2001/09/29 13:27:00  richard
+# CGI interfaces now spit up a top-level index of all the instances they can
+# serve.
+#
 # Revision 1.11  2001/08/07 00:24:42  richard
 # stupid typo
 #
--- a/roundup/cgi_client.py	Thu Oct 04 02:16:15 2001 +0000
+++ b/roundup/cgi_client.py	Fri Oct 05 02:23:24 2001 +0000
@@ -15,21 +15,37 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: cgi_client.py,v 1.26 2001-09-12 08:31:42 richard Exp $
+# $Id: cgi_client.py,v 1.27 2001-10-05 02:23:24 richard Exp $
 
 import os, cgi, pprint, StringIO, urlparse, re, traceback, mimetypes
+import base64, Cookie, time
 
 import roundupdb, htmltemplate, date, hyperdb
 
 class Unauthorised(ValueError):
     pass
 
+class NotFound(ValueError):
+    pass
+
 class Client:
-    def __init__(self, out, db, env, user):
+    '''
+
+    A note about login
+    ------------------
+
+    If the user has no login cookie, then they are anonymous. There
+    are two levels of anonymous use. If there is no 'anonymous' user, there
+    is no login at all and the database is opened in read-only mode. If the
+    'anonymous' user exists, the user is logged in using that user (though
+    there is no cookie). This allows them to modify the database, and all
+    modifications are attributed to the 'anonymous' user.
+    '''
+
+    def __init__(self, instance, out, env):
+        self.instance = instance
         self.out = out
-        self.db = db
         self.env = env
-        self.user = user
         self.path = env['PATH_INFO']
         self.split_path = self.path.split('/')
 
@@ -60,7 +76,11 @@
         else:
             message = ''
         style = open(os.path.join(self.TEMPLATES, 'style.css')).read()
-        userid = self.db.user.lookup(self.user)
+        if self.user is not None:
+            userid = self.db.user.lookup(self.user)
+            user_info = '(login: <a href="user%s">%s</a>)'%(userid, self.user)
+        else:
+            user_info = ''
         self.write('''<html><head>
 <title>%s</title>
 <style type="text/css">%s</style>
@@ -68,10 +88,9 @@
 <body bgcolor=#ffffff>
 %s
 <table width=100%% border=0 cellspacing=0 cellpadding=2>
-<tr class="location-bar"><td><big><strong>%s</strong></big>
-(login: <a href="user%s">%s</a>)</td></tr>
+<tr class="location-bar"><td><big><strong>%s</strong></big> %s</td></tr>
 </table>
-'''%(title, style, message, title, userid, self.user))
+'''%(title, style, message, title, user_info))
 
     def pagefoot(self):
         if self.debug:
@@ -122,6 +141,7 @@
         filterspec = {}
         for key in self.form.keys():
             if key[0] == ':': continue
+            if not props.has_key(key): continue
             prop = props[key]
             value = self.form[key]
             if (isinstance(prop, hyperdb.Link) or
@@ -433,7 +453,143 @@
         else:
             raise Unauthorised
 
-    def main(self, dre=re.compile(r'([^\d]+)(\d+)'), nre=re.compile(r'new(\w+)')):
+    def login(self, message=None):
+        self.pagehead('Login to roundup', message)
+        self.write('''
+<table>
+<tr><td colspan=2 class="strong-header">Existing User Login</td></tr>
+<form action="login_action" method=POST>
+<tr><td align=right>Login name: </td>
+    <td><input name="__login_name"></td></tr>
+<tr><td align=right>Password: </td>
+    <td><input type="password" name="__login_password"></td></tr>
+<tr><td></td>
+    <td><input type="submit" value="Log In"></td></tr>
+</form>
+
+<p>
+<tr><td colspan=2 class="strong-header">New User Registration</td></tr>
+<tr><td colspan=2><em>marked items</em> are optional...</td></tr>
+<form action="newuser_action" method=POST>
+<tr><td align=right><em>Name: </em></td>
+    <td><input name="__newuser_realname"></td></tr>
+<tr><td align=right><em>Organisation: </em></td>
+    <td><input name="__newuser_organisation"></td></tr>
+<tr><td align=right>E-Mail Address: </td>
+    <td><input name="__newuser_address"></td></tr>
+<tr><td align=right><em>Phone: </em></td>
+    <td><input name="__newuser_phone"></td></tr>
+<tr><td align=right>Preferred Login name: </td>
+    <td><input name="__newuser_username"></td></tr>
+<tr><td align=right>Password: </td>
+    <td><input type="password" name="__newuser_password"></td></tr>
+<tr><td align=right>Password Again: </td>
+    <td><input type="password" name="__newuser_confirm"></td></tr>
+<tr><td></td>
+    <td><input type="submit" value="Register"></td></tr>
+</form>
+</table>
+''')
+
+    def login_action(self, message=None):
+        self.user = self.form['__login_name'].value
+        password = self.form['__login_password'].value
+        # make sure the user exists
+        try:
+            uid = self.db.user.lookup(self.user)
+        except KeyError:
+            name = self.user
+            self.make_user_anonymous()
+            return self.login(message='No such user "%s"'%name)
+
+        # and that the password is correct
+        if password != self.db.user.get(uid, 'password'):
+            return self.login(message='Incorrect password')
+
+        # construct the cookie
+        uid = self.db.user.lookup(self.user)
+        user = base64.encodestring('%s:%s'%(self.user, password))[:-1]
+        path = '/'.join((self.env['SCRIPT_NAME'], self.env['INSTANCE_NAME'],
+            ''))
+        cookie = Cookie.SmartCookie()
+        cookie['roundup_user'] = user
+        cookie['roundup_user']['path'] = path
+        self.header({'Set-Cookie': str(cookie)})
+        return self.index()
+
+    def make_user_anonymous(self):
+        # make us anonymous if we can
+        try:
+            self.db.user.lookup('anonymous')
+            self.user = 'anonymous'
+        except KeyError:
+            self.user = None
+
+    def logout(self, message=None):
+        self.make_user_anonymous()
+        # construct the logout cookie
+        path = '/'.join((self.env['SCRIPT_NAME'], self.env['INSTANCE_NAME'],
+            ''))
+        cookie = Cookie.SmartCookie()
+        cookie['roundup_user'] = 'deleted'
+        cookie['roundup_user']['path'] = path
+        cookie['roundup_user']['expires'] = 0
+        cookie['roundup_user']['max-age'] = 0
+        self.header({'Set-Cookie': str(cookie)})
+        return self.index()
+
+    def newuser_action(self, message=None):
+        ''' create a new user based on the contents of the form and then
+        set the cookie
+        '''
+        # TODO: pre-check the required fields and username key property
+        cl = self.db.classes['user']
+        props, dummy = parsePropsFromForm(cl, self.form)
+        uid = cl.create(**props)
+        self.user = self.db.user.get(uid, 'username')
+        password = self.db.user.get(uid, 'password')
+        # construct the cookie
+        uid = self.db.user.lookup(self.user)
+        user = base64.encodestring('%s:%s'%(self.user, password))[:-1]
+        path = '/'.join((self.env['SCRIPT_NAME'], self.env['INSTANCE_NAME'],
+            ''))
+        cookie = Cookie.SmartCookie()
+        cookie['roundup_user'] = user
+        cookie['roundup_user']['path'] = path
+        self.header({'Set-Cookie': str(cookie)})
+        return self.index()
+
+    def main(self, dre=re.compile(r'([^\d]+)(\d+)'),
+            nre=re.compile(r'new(\w+)')):
+
+        # determine the uid to use
+        self.db = self.instance.open('admin')
+        cookie = Cookie.Cookie(self.env.get('HTTP_COOKIE', ''))
+        user = 'anonymous'
+        if (cookie.has_key('roundup_user') and
+                cookie['roundup_user'].value != 'deleted'):
+            cookie = cookie['roundup_user'].value
+            user, password = base64.decodestring(cookie).split(':')
+            # make sure the user exists
+            try:
+                uid = self.db.user.lookup(user)
+                # now validate the password
+                if password != self.db.user.get(uid, 'password'):
+                    user = 'anonymous'
+            except KeyError:
+                user = 'anonymous'
+
+        # make sure the anonymous user is valid if we're using it
+        if user == 'anonymous':
+            self.make_user_anonymous()
+        else:
+            self.user = user
+        self.db.close()
+
+        # re-open the database for real, using the user
+        self.db = self.instance.open(self.user)
+
+        # now figure which function to call
         path = self.split_path
         if not path or path[0] in ('', 'index'):
             self.index()
@@ -441,18 +597,48 @@
             if path[0] == 'list_classes':
                 self.classes()
                 return
+            if path[0] == 'login':
+                self.login()
+                return
+            if path[0] == 'login_action':
+                self.login_action()
+                return
+            if path[0] == 'newuser_action':
+                self.newuser_action()
+                return
+            if path[0] == 'logout':
+                self.logout()
+                return
             m = dre.match(path[0])
             if m:
                 self.classname = m.group(1)
                 self.nodeid = m.group(2)
-                getattr(self, 'show%s'%self.classname)()
+                try:
+                    cl = self.db.classes[self.classname]
+                except KeyError:
+                    raise NotFound
+                try:
+                    cl.get(self.nodeid, 'id')
+                except IndexError:
+                    raise NotFound
+                try:
+                    getattr(self, 'show%s'%self.classname)()
+                except AttributeError:
+                    raise NotFound
                 return
             m = nre.match(path[0])
             if m:
                 self.classname = m.group(1)
-                getattr(self, 'new%s'%self.classname)()
+                try:
+                    getattr(self, 'new%s'%self.classname)()
+                except AttributeError:
+                    raise NotFound
                 return
             self.classname = path[0]
+            try:
+                self.db.getclass(self.classname)
+            except KeyError:
+                raise NotFound
             self.list()
         else:
             raise 'ValueError', 'Path not understood'
@@ -515,6 +701,9 @@
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.26  2001/09/12 08:31:42  richard
+# handle cases where mime type is not guessable
+#
 # Revision 1.25  2001/08/29 05:30:49  richard
 # change messages weren't being saved when there was no-one on the nosy list.
 #
--- a/roundup/hyperdb.py	Thu Oct 04 02:16:15 2001 +0000
+++ b/roundup/hyperdb.py	Fri Oct 05 02:23:24 2001 +0000
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: hyperdb.py,v 1.20 2001-10-04 02:12:42 richard Exp $
+# $Id: hyperdb.py,v 1.21 2001-10-05 02:23:24 richard Exp $
 
 # standard python modules
 import cPickle, re, string
@@ -219,9 +219,9 @@
         IndexError is raised.  'propname' must be the name of a property
         of this class or a KeyError is raised.
         """
+        d = self.db.getnode(self.classname, nodeid)
         if propname == 'id':
             return nodeid
-        d = self.db.getnode(self.classname, nodeid)
         if not d.has_key(propname) and default is not _marker:
             return default
         return d[propname]
@@ -800,6 +800,14 @@
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.20  2001/10/04 02:12:42  richard
+# Added nicer command-line item adding: passing no arguments will enter an
+# interactive more which asks for each property in turn. While I was at it, I
+# fixed an implementation problem WRT the spec - I wasn't raising a
+# ValueError if the key property was missing from a create(). Also added a
+# protected=boolean argument to getprops() so we can list only the mutable
+# properties (defaults to yes, which lists the immutables).
+#
 # Revision 1.19  2001/08/29 04:47:18  richard
 # Fixed CGI client change messages so they actually include the properties
 # changed (again).
--- a/roundup/mailgw.py	Thu Oct 04 02:16:15 2001 +0000
+++ b/roundup/mailgw.py	Fri Oct 05 02:23:24 2001 +0000
@@ -72,7 +72,7 @@
 an exception, the original message is bounced back to the sender with the
 explanatory message given in the exception. 
 
-$Id: mailgw.py,v 1.15 2001-08-30 06:01:17 richard Exp $
+$Id: mailgw.py,v 1.16 2001-10-05 02:23:24 richard Exp $
 '''
 
 
@@ -230,7 +230,9 @@
                 elif isinstance(type, hyperdb.Multilink):
                     props[key] = value.split(',')
 
+        #
         # handle the users
+        #
         author = self.db.uidFromAddress(message.getaddrlist('from')[0])
         recipients = []
         for recipient in message.getaddrlist('to') + message.getaddrlist('cc'):
@@ -398,6 +400,9 @@
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.15  2001/08/30 06:01:17  richard
+# Fixed missing import in mailgw :(
+#
 # Revision 1.14  2001/08/13 23:02:54  richard
 # Make the mail parser a little more robust.
 #
--- a/roundup/templates/extended/htmlbase.py	Thu Oct 04 02:16:15 2001 +0000
+++ b/roundup/templates/extended/htmlbase.py	Fri Oct 05 02:23:24 2001 +0000
@@ -182,7 +182,7 @@
 
 """
 
-msgDOTindex = """<!-- dollarId: msg.index,v 1.1 2001/07/23 04:21:20 richard Exp dollar-->
+msgDOTindex = """<!-- dollarId: msg.index,v 1.3 2001/09/27 06:45:58 richard Exp dollar-->
 <tr class="row-hilite">
     <property name="date">
         <td><display call="link('date')"></td>
--- a/roundup/templates/extended/interfaces.py	Thu Oct 04 02:16:15 2001 +0000
+++ b/roundup/templates/extended/interfaces.py	Fri Oct 05 02:23:24 2001 +0000
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: interfaces.py,v 1.9 2001-08-07 00:24:43 richard Exp $
+# $Id: interfaces.py,v 1.10 2001-10-05 02:23:24 richard Exp $
 
 import instance_config, urlparse, os
 from roundup import cgi_client, mailgw 
@@ -47,11 +47,29 @@
         else:
             message = ''
         style = open(os.path.join(self.TEMPLATES, 'style.css')).read()
-        userid = self.db.user.lookup(self.user)
+        user_name = self.user or ''
         if self.user == 'admin':
-            extras = ' | <a href="list_classes">Class List</a>'
+            admin_links = ' | <a href="list_classes">Class List</a>'
+        else:
+            admin_links = ''
+        if self.user not in (None, 'anonymous'):
+            userid = self.db.user.lookup(self.user)
+            user_info = '''
+<a href="issue?assignedto=%s&status=unread,deferred,chatting,need-eg,in-progress,testing,done-cbb&:sort=activity&:columns=id,activity,status,title,assignedto&:group=priority">My Issues</a> |
+<a href="support?assignedto=%s&status=unread,deferred,chatting,need-eg,in-progress,testing,done-cbb&:sort=activity&:columns=id,activity,status,title,assignedto&:group=customername">My Support</a> |
+<a href="user%s">My Details</a> | <a href="logout">Logout</a>
+'''%(userid, userid, userid)
         else:
-            extras = ''
+            user_info = '<a href="login">Login</a>'
+        if self.user is not None:
+            add_links = '''
+| Add
+<a href="newissue">Issue</a>,
+<a href="newsupport">Support</a>,
+<a href="newuser">User</a>
+'''
+        else:
+            add_links = ''
         self.write('''<html><head>
 <title>%s</title>
 <style type="text/css">%s</style>
@@ -68,17 +86,12 @@
 | Unassigned
 <a href="issue?assignedto=admin&status=unread,deferred,chatting,need-eg,in-progress,testing,done-cbb&:sort=activity&:columns=id,activity,status,title,assignedto&:group=priority">Issues</a>,
 <a href="support?assignedto=admin&status=unread,deferred,chatting,need-eg,in-progress,testing,done-cbb&:sort=activity&:columns=id,activity,status,title,assignedto&:group=customername">Support</a>
-| Add
-<a href="newissue">Issue</a>,
-<a href="newsupport">Support</a>,
-<a href="newuser">User</a>
+%s
 %s</td>
-<td align=right>
-<a href="issue?assignedto=%s&status=unread,deferred,chatting,need-eg,in-progress,testing,done-cbb&:sort=activity&:columns=id,activity,status,title,assignedto&:group=priority">My Issues</a> |
-<a href="support?assignedto=%s&status=unread,deferred,chatting,need-eg,in-progress,testing,done-cbb&:sort=activity&:columns=id,activity,status,title,assignedto&:group=customername">My Support</a> |
-<a href="user%s">My Details</a></td>
+<td align=right>%s</td>
 </table>
-'''%(title, style, message, title, self.user, extras, userid, userid, userid))
+'''%(title, style, message, title, user_name, add_links, admin_links,
+    user_info))
  
 class MailGW(mailgw.MailGW): 
     ''' derives basic mail gateway implementation from the standard module, 
@@ -90,6 +103,9 @@
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.9  2001/08/07 00:24:43  richard
+# stupid typo
+#
 # Revision 1.8  2001/08/07 00:15:51  richard
 # Added the copyright/license notice to (nearly) all files at request of
 # Bizar Software.

Roundup Issue Tracker: http://roundup-tracker.org/