Code

Feature:
authorrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Mon, 26 Nov 2001 22:55:56 +0000 (22:55 +0000)
committerrichard <richard@57a73879-2fb5-44c3-a270-3262357dd7e2>
Mon, 26 Nov 2001 22:55:56 +0000 (22:55 +0000)
 . Added INSTANCE_NAME to configuration - used in web and email to identify
   the instance.
 . Added EMAIL_SIGNATURE_POSITION to indicate where to place the roundup
   signature info in e-mails.
 . Some more flexibility in the mail gateway and more error handling.
 . Login now takes you to the page you back to the were denied access to.

Fixed:
 . Lots of bugs, thanks Roch�nd others on the devel mailing list!

git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@423 57a73879-2fb5-44c3-a270-3262357dd7e2

14 files changed:
CHANGES.txt
cgi-bin/roundup.cgi
roundup-admin
roundup-server
roundup/cgi_client.py
roundup/htmltemplate.py
roundup/mailgw.py
roundup/roundupdb.py
roundup/templates/classic/dbinit.py
roundup/templates/classic/detectors/nosyreaction.py
roundup/templates/classic/instance_config.py
roundup/templates/extended/dbinit.py
roundup/templates/extended/detectors/nosyreaction.py
roundup/templates/extended/instance_config.py

index 8686a36858ede10816a8e4a41848a8e8075d9b07..33c8566683fada9fafc570324214b79f83b16dcf 100644 (file)
@@ -1,7 +1,20 @@
 This file contains the changes to the Roundup system over time. The entries
 are given with the most recent entry first.
 
-2001-10-?? - 0.3.0 
+2001-11-?? - 0.3.1
+Feature:
+ . Added INSTANCE_NAME to configuration - used in web and email to identify
+   the instance.
+ . Added EMAIL_SIGNATURE_POSITION to indicate where to place the roundup
+   signature info in e-mails.
+ . Some more flexibility in the mail gateway and more error handling.
+ . Login now takes you to the page you back to the were denied access to.
+
+Fixed:
+ . Lots of bugs, thanks Roché and others on the devel mailing list!
+
+
+2001-11-23 - 0.3.0 
 Feature:
  . #467129 ] Lossage when username=e-mail-address
  . #473123 ] Change message generation for author
index e755c08bab4854d88e1495a0a8977a88a5feebde..d327ee3b8d4298e1f8009ccb370536a38a818e32 100755 (executable)
@@ -16,7 +16,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: roundup.cgi,v 1.19 2001-11-22 00:25:10 richard Exp $
+# $Id: roundup.cgi,v 1.20 2001-11-26 22:55:56 richard Exp $
 
 # python version check
 import sys
@@ -133,22 +133,32 @@ def main(out, err):
     os.environ['PATH_INFO'] = string.join(path[2:], '/')
     request = RequestWrapper(out)
     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, request, os.environ)
-        try:
-            client.main()
-        except cgi_client.Unauthorised:
-            request.send_response(403)
-            request.send_header('Content-Type', 'text/html')
+        # redirect if we need a trailing '/'
+        if len(path) == 2:
+            request.send_response(301)
+            absolute_url = 'http://%s%s/'%(os.environ['HTTP_HOST'],
+                os.environ['REQUEST_URI'])
+            request.send_header('Location', absolute_url)
             request.end_headers()
-            out.write('Unauthorised')
-        except cgi_client.NotFound:
-            request.send_response(404)
-            request.send_header('Content-Type', 'text/html')
-            request.end_headers()
-            out.write('Not found: %s'%client.path)
+            out.write('Moved Permanently')
+        else:
+            instance_home = ROUNDUP_INSTANCE_HOMES[instance]
+            instance = roundup.instance.open(instance_home)
+            from roundup import cgi_client
+            client = instance.Client(instance, request, os.environ)
+            try:
+                client.main()
+            except cgi_client.Unauthorised:
+                request.send_response(403)
+                request.send_header('Content-Type', 'text/html')
+                request.end_headers()
+                out.write('Unauthorised')
+            except 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)
@@ -190,6 +200,9 @@ LOG.close()
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.19  2001/11/22 00:25:10  richard
+# quick fix for file uploads on windows in roundup.cgi
+#
 # Revision 1.18  2001/11/06 22:10:11  jhermann
 # Added env config; fixed request wrapper & index list; sort list by key
 #
index bca9aaf9b214cb87f2d6c7be72eaed20fa9f3229..cde8cee934a84d330485a17bfb30c0d5780e291a 100755 (executable)
@@ -16,7 +16,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: roundup-admin,v 1.46 2001-11-21 03:40:54 richard Exp $
+# $Id: roundup-admin,v 1.47 2001-11-26 22:55:56 richard Exp $
 
 import sys
 if int(sys.version[0]) < 2:
@@ -306,24 +306,24 @@ Command help:
 
             properties = cl.getprops()
             for key, value in props.items():
-                type =  properties[key]
-                if isinstance(type, hyperdb.String):
+                proptype =  properties[key]
+                if isinstance(proptype, hyperdb.String):
                     continue
-                elif isinstance(type, hyperdb.Password):
+                elif isinstance(proptype, hyperdb.Password):
                     props[key] = password.Password(value)
-                elif isinstance(type, hyperdb.Date):
+                elif isinstance(proptype, hyperdb.Date):
                     try:
                         props[key] = date.Date(value)
                     except ValueError, message:
                         raise UsageError, '"%s": %s'%(value, message)
-                elif isinstance(type, hyperdb.Interval):
+                elif isinstance(proptype, hyperdb.Interval):
                     try:
                         props[key] = date.Interval(value)
                     except ValueError, message:
                         raise UsageError, '"%s": %s'%(value, message)
-                elif isinstance(type, hyperdb.Link):
+                elif isinstance(proptype, hyperdb.Link):
                     props[key] = value
-                elif isinstance(type, hyperdb.Multilink):
+                elif isinstance(proptype, hyperdb.Multilink):
                     props[key] = value.split(',')
 
             # try the set
@@ -369,7 +369,7 @@ Command help:
 
             # make sure it's a link
             if (not isinstance(property, hyperdb.Link) and not
-                    isinstance(type, hyperdb.Multilink)):
+                    isinstance(proptype, hyperdb.Multilink)):
                 raise UsageError, 'You may only "find" link properties'
 
             # get the linked-to class and look up the key property
@@ -469,23 +469,23 @@ Command help:
         for key in props.keys():
             # get the property
             try:
-                type = properties[key]
+                proptype = properties[key]
             except KeyError:
                 raise UsageError, '%s has no property "%s"'%(classname, key)
 
-            if isinstance(type, hyperdb.Date):
+            if isinstance(proptype, hyperdb.Date):
                 try:
                     props[key] = date.Date(value)
                 except ValueError, message:
                     raise UsageError, '"%s": %s'%(value, message)
-            elif isinstance(type, hyperdb.Interval):
+            elif isinstance(proptype, hyperdb.Interval):
                 try:
                     props[key] = date.Interval(value)
                 except ValueError, message:
                     raise UsageError, '"%s": %s'%(value, message)
-            elif isinstance(type, hyperdb.Password):
+            elif isinstance(proptype, hyperdb.Password):
                 props[key] = password.Password(value)
-            elif isinstance(type, hyperdb.Multilink):
+            elif isinstance(proptype, hyperdb.Multilink):
                 props[key] = value.split(',')
 
         # check for the key property
@@ -666,14 +666,14 @@ Command help:
             properties = cl.properties.items()
             for nodeid in cl.list():
                 l = []
-                for prop, type in properties:
+                for prop, proptype in properties:
                     value = cl.get(nodeid, prop)
                     # convert data where needed
-                    if isinstance(type, hyperdb.Date):
+                    if isinstance(proptype, hyperdb.Date):
                         value = value.get_tuple()
-                    elif isinstance(type, hyperdb.Interval):
+                    elif isinstance(proptype, hyperdb.Interval):
                         value = value.get_tuple()
-                    elif isinstance(type, hyperdb.Password):
+                    elif isinstance(proptype, hyperdb.Password):
                         value = str(value)
                     l.append(repr(value))
 
@@ -712,6 +712,7 @@ Command help:
         from roundup import hyperdb
 
         # ensure that the properties and the CSV file headings match
+        classname = args[0]
         try:
             cl = self.db.getclass(classname)
         except KeyError:
@@ -745,13 +746,13 @@ Command help:
                 value = eval(l[i])
                 # Figure the property for this column
                 key = file_props[i]
-                type = cl.properties[key]
+                proptype = cl.properties[key]
                 # Convert for property type
-                if isinstance(type, hyperdb.Date):
+                if isinstance(proptype, hyperdb.Date):
                     value = date.Date(value)
-                elif isinstance(type, hyperdb.Interval):
+                elif isinstance(proptype, hyperdb.Interval):
                     value = date.Interval(value)
-                elif isinstance(type, hyperdb.Password):
+                elif isinstance(proptype, hyperdb.Password):
                     pwd = password.Password()
                     pwd.unpack(value)
                     value = pwd
@@ -892,6 +893,9 @@ if __name__ == '__main__':
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.46  2001/11/21 03:40:54  richard
+# more new property handling
+#
 # Revision 1.45  2001/11/12 22:51:59  jhermann
 # Fixed option & associated error handling
 #
index a71ce4783814a448c682604d0234a7bf4f559993..4ff953a266105fcfeae3bcc021cddac67db33fef 100755 (executable)
@@ -20,7 +20,7 @@
 
 Based on CGIHTTPServer in the Python library.
 
-$Id: roundup-server,v 1.19 2001-11-12 22:51:04 jhermann Exp $
+$Id: roundup-server,v 1.20 2001-11-26 22:55:56 richard Exp $
 
 """
 import sys
@@ -247,8 +247,8 @@ def main():
     except SystemExit:
         raise
     except:
-        type, value = sys.exc_info()[:2]
-        usage('%s: %s'%(type, value))
+        exc_type, exc_value = sys.exc_info()[:2]
+        usage('%s: %s'%(exc_type, exc_value))
 
     # we don't want the cgi module interpreting the command-line args ;)
     sys.argv = sys.argv[:1]
@@ -262,6 +262,9 @@ if __name__ == '__main__':
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.19  2001/11/12 22:51:04  jhermann
+# Fixed option & associated error handling
+#
 # Revision 1.18  2001/11/01 22:04:37  richard
 # Started work on supporting a pop3-fetching server
 # Fixed bugs:
index a4c88e0080b19ba8f9f9112d68028493b759e830..8e80355b082726303b322ff8dfffcac1edfacd0f 100644 (file)
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: cgi_client.py,v 1.62 2001-11-24 00:45:42 jhermann Exp $
+# $Id: cgi_client.py,v 1.63 2001-11-26 22:55:56 richard Exp $
 
 __doc__ = """
 WWW request handler (also used in the stand-alone server).
 """
 
 import os, cgi, pprint, StringIO, urlparse, re, traceback, mimetypes
-import binascii, Cookie, time, __builtin__
+import binascii, Cookie, time
 
 import roundupdb, htmltemplate, date, hyperdb, password
 from roundup.i18n import _
 
-# avoid clash with database field "type"
-typeof = __builtin__.type
-
 class Unauthorised(ValueError):
     pass
 
@@ -55,6 +52,9 @@ class Client:
       ANONYMOUS_ACCESS - one of 'deny', 'allow'
       ANONYMOUS_REGISTER - one of 'deny', 'allow'
 
+    from the roundup class:
+      INSTANCE_NAME - defaults to 'Roundup issue tracker'
+
     '''
     FILTER_POSITION = 'bottom'       # one of 'top', 'bottom', 'top and bottom'
     ANONYMOUS_ACCESS = 'deny'        # one of 'deny', 'allow'
@@ -155,7 +155,7 @@ class Client:
                 self.write('<dt><b>Form entries</b></dt>')
                 for k in self.form.keys():
                     v = self.form.getvalue(k, "<empty>")
-                    if typeof(v) is typeof([]):
+                    if type(v) is type([]):
                         # Multiple username fields specified
                         v = "|".join(v)
                     self.write('<dd><em>%s</em>=%s</dd>'%(k, cgi.escape(v)))
@@ -186,7 +186,7 @@ class Client:
         '''
         if self.form.has_key(arg):
             arg =  self.form[arg]
-            if typeof(arg) == typeof([]):
+            if type(arg) == type([]):
                 return [arg.value for arg in arg]
             return arg.value.split(',')
         return []
@@ -208,7 +208,7 @@ class Client:
             value = self.form[key]
             if (isinstance(prop, hyperdb.Link) or
                     isinstance(prop, hyperdb.Multilink)):
-                if typeof(value) == typeof([]):
+                if type(value) == type([]):
                     value = [arg.value for arg in value]
                 else:
                     value = value.value.split(',')
@@ -282,7 +282,9 @@ class Client:
 
         '''
         cn = self.classname
-        self.pagehead(_('Index of %(classname)s')%{'classname': cn})
+        cl = self.db.classes[cn]
+        self.pagehead(_('%(instancename)s: Index of %(classname)s')%{
+            'classname': cn, 'instancename': cl.INSTANCE_NAME})
         if sort is None: sort = self.index_arg(':sort')
         if group is None: group = self.index_arg(':group')
         if filter is None: filter = self.index_arg(':filter')
@@ -305,14 +307,38 @@ class Client:
         # possibly perform an edit
         keys = self.form.keys()
         num_re = re.compile('^\d+$')
-        if keys:
+        # don't try to set properties if the user has just logged in
+        if keys and not self.form.has_key('__login_name')::
             try:
                 props, changed = parsePropsFromForm(self.db, cl, self.form,
                     self.nodeid)
-                cl.set(self.nodeid, **props)
-                self._post_editnode(self.nodeid, changed)
-                # and some nice feedback for the user
-                message = '%s edited ok'%', '.join(changed)
+
+                # set status to chatting if 'unread' or 'resolved'
+                if 'status' not in changed:
+                    try:
+                        # determine the id of 'unread','resolved' and 'chatting'
+                        unread_id = self.db.status.lookup('unread')
+                        resolved_id = self.db.status.lookup('resolved')
+                        chatting_id = self.db.status.lookup('chatting')
+                    except KeyError:
+                        pass
+                    else:
+                        if (not props.has_key('status') or
+                                props['status'] == unread_id or
+                                props['status'] == resolved_id):
+                            props['status'] = chatting_id
+                            changed.append('status')
+                note = None
+                if self.form.has_key('__note'):
+                    note = self.form['__note']
+                    note = note.value
+                if changed or note:
+                    cl.set(self.nodeid, **props)
+                    self._post_editnode(self.nodeid, changed)
+                    # and some nice feedback for the user
+                    message = '%s edited ok'%', '.join(changed)
+                else:
+                    message = 'nothing changed'
             except:
                 s = StringIO.StringIO()
                 traceback.print_exc(None, s)
@@ -358,10 +384,14 @@ class Client:
             try:
                 props, changed = parsePropsFromForm(self.db, user, self.form,
                     self.nodeid)
+                set_cookie = 0
                 if self.nodeid == self.getuid() and 'password' in changed:
-                    set_cookie = self.form['password'].value.strip()
-                else:
-                    set_cookie = 0
+                    password = self.form['password'].value.strip()
+                    if password:
+                        set_cookie = password
+                    else:
+                        del props['password']
+                        del changed[changed.index('password')]
                 user.set(self.nodeid, **props)
                 self._post_editnode(self.nodeid, changed)
                 # and some feedback for the user
@@ -392,10 +422,10 @@ class Client:
         '''
         nodeid = self.nodeid
         cl = self.db.file
-        mimetype = cl.get(nodeid, 'type')
-        if mimetype == 'message/rfc822':
-            mimetype = 'text/plain'
-        self.header(headers={'Content-Type': mimetype})
+        mime_type = cl.get(nodeid, 'type')
+        if mime_type == 'message/rfc822':
+            mime_type = 'text/plain'
+        self.header(headers={'Content-Type': mime_type})
         self.write(cl.get(nodeid, 'content'))
 
     def _createnode(self):
@@ -415,7 +445,7 @@ class Client:
         for key in keys:
             if key == ':multilink':
                 value = self.form[key].value
-                if typeof(value) != typeof([]): value = [value]
+                if type(value) != type([]): value = [value]
                 for value in value:
                     designator, property = value.split(':')
                     link, nodeid = roundupdb.splitDesignator(designator)
@@ -425,7 +455,7 @@ class Client:
                     link.set(nodeid, **{property: value})
             elif key == ':link':
                 value = self.form[key].value
-                if typeof(value) != typeof([]): value = [value]
+                if type(value) != type([]): value = [value]
                 for value in value:
                     designator, property = value.split(':')
                     link, nodeid = roundupdb.splitDesignator(designator)
@@ -437,12 +467,12 @@ class Client:
         if self.form.has_key('__file'):
             file = self.form['__file']
             if file.filename:
-                type = mimetypes.guess_type(file.filename)[0]
-                if not type:
-                    type = "application/octet-stream"
+                mime_type = mimetypes.guess_type(file.filename)[0]
+                if not mime_type:
+                    mime_type = "application/octet-stream"
                 # create the new file entry
-                files.append(self.db.file.create(type=type, name=file.filename,
-                    content=file.file.read()))
+                files.append(self.db.file.create(type=mime_type,
+                    name=file.filename, content=file.file.read()))
 
         # generate an edit message
         # don't bother if there's no messages or nosy list 
@@ -457,15 +487,15 @@ class Client:
                 props['messages'].classname == 'msg'):
 
             # handle the note
+            edit_msg = 'This %s has been edited through the web.\n'%cn
             if note:
                 if '\n' in note:
                     summary = re.split(r'\n\r?', note)[0]
                 else:
                     summary = note
-                m = ['%s\n'%note]
+                m = [edit_msg + '%s\n'%note]
             else:
-                summary = 'This %s has been edited through the web.\n'%cn
-                m = [summary]
+                m = [edit_msg]
 
             first = 1
             for name, prop in props.items():
@@ -498,7 +528,7 @@ class Client:
             content = '\n'.join(m)
             message_id = self.db.msg.create(author=self.getuid(),
                 recipients=[], date=date.Date('.'), summary=summary,
-                content=content)
+                content=content, files=files)
             messages = cl.get(nid, 'messages')
             messages.append(message_id)
             props = {'messages': messages, 'files': files}
@@ -567,11 +597,11 @@ class Client:
         if [i for i in keys if i[0] != ':']:
             try:
                 file = self.form['content']
-                type = mimetypes.guess_type(file.filename)[0]
-                if not type:
-                    type = "application/octet-stream"
+                mime_type = mimetypes.guess_type(file.filename)[0]
+                if not mime_type:
+                    mime_type = "application/octet-stream"
                 self._post_editnode(cl.create(content=file.file.read(),
-                    type=type, name=file.filename))
+                    type=mime_type, name=file.filename))
                 # and some nice feedback for the user
                 message = '%s created ok'%cn
             except:
@@ -606,12 +636,13 @@ class Client:
         else:
             raise Unauthorised
 
-    def login(self, message=None, newuser_form=None):
+    def login(self, message=None, newuser_form=None, action='index'):
         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>
+<input type="hidden" name="__destination_url" value="%s">
 <tr><td align=right>Login name: </td>
     <td><input name="__login_name"></td></tr>
 <tr><td align=right>Password: </td>
@@ -619,7 +650,7 @@ class Client:
 <tr><td></td>
     <td><input type="submit" value="Log In"></td></tr>
 </form>
-''')
+'''%action)
         if self.user is None and self.ANONYMOUS_REGISTER == 'deny':
             self.write('</table>')
             self.pagefoot()
@@ -678,7 +709,6 @@ class Client:
             return self.login(message=_('Incorrect password'))
 
         self.set_cookie(self.user, password)
-        return self.index()
 
     def set_cookie(self, user, password):
         # construct the cookie
@@ -731,9 +761,7 @@ class Client:
         self.set_cookie(self.user, self.form['password'].value)
         return self.index()
 
-    def main(self, dre=re.compile(r'([^\d]+)(\d+)'),
-            nre=re.compile(r'new(\w+)')):
-
+    def main(self):
         # determine the uid to use
         self.db = self.instance.open('admin')
         cookie = Cookie.Cookie(self.env.get('HTTP_COOKIE', ''))
@@ -770,6 +798,8 @@ class Client:
 
         # now figure which function to call
         path = self.split_path
+
+        # default action to index if the path has no information in it
         if not path or path[0] in ('', 'index'):
             action = 'index'
         else:
@@ -782,20 +812,43 @@ class Client:
 
         # everyone is allowed to try to log in
         if action == 'login_action':
-            return self.login_action()
+            # do the login
+            self.login_action()
+            # figure the resulting page
+            action = self.form['__destination_url'].value
+            if not action:
+                action = 'index'
+            return self.do_action(action)
 
         # allow anonymous people to register
         if action == 'newuser_action':
             # if we don't have a login and anonymous people aren't allowed to
             # register, then spit up the login form
             if self.ANONYMOUS_REGISTER == 'deny' and self.user is None:
-                return self.login()
-            return self.newuser_action()
-
-        # make sure totally anonymous access is OK
+                if action == 'login':
+                    return self.login()         # go to the index after login
+                else:
+                    return self.login(action=action)
+            # add the user
+            self.newuser_action()
+            # figure the resulting page
+            action = self.form['__destination_url'].value
+            if not action:
+                action = 'index'
+            return self.do_action(action)
+
+        # no login or registration, make sure totally anonymous access is OK
         if self.ANONYMOUS_ACCESS == 'deny' and self.user is None:
-            return self.login()
+            if action == 'login':
+                return self.login()             # go to the index after login
+            else:
+                return self.login(action=action)
+
+        # just a regular action
+        return self.do_action(action)
 
+    def do_action(self, action, dre=re.compile(r'([^\d]+)(\d+)'),
+            nre=re.compile(r'new(\w+)')):
         # here be the "normal" functionality
         if action == 'index':
             return self.index()
@@ -835,7 +888,7 @@ class Client:
             self.db.getclass(self.classname)
         except KeyError:
             raise NotFound
-        self.list()
+        return self.list()
 
     def __del__(self):
         self.db.close()
@@ -949,7 +1002,7 @@ def parsePropsFromForm(db, cl, form, nodeid=0):
                             key, value, link)
         elif isinstance(proptype, hyperdb.Multilink):
             value = form[key]
-            if typeof(value) != typeof([]):
+            if type(value) != type([]):
                 value = [i.strip() for i in value.value.split(',')]
             else:
                 value = [i.value.strip() for i in value]
@@ -985,6 +1038,18 @@ def parsePropsFromForm(db, cl, form, nodeid=0):
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.62  2001/11/24 00:45:42  jhermann
+# typeof() instead of type(): avoid clash with database field(?) "type"
+#
+# Fixes this traceback:
+#
+# Traceback (most recent call last):
+#   File "roundup\cgi_client.py", line 535, in newnode
+#     self._post_editnode(nid)
+#   File "roundup\cgi_client.py", line 415, in _post_editnode
+#     if type(value) != type([]): value = [value]
+# UnboundLocalError: local variable 'type' referenced before assignment
+#
 # Revision 1.61  2001/11/22 15:46:42  jhermann
 # Added module docstrings to all modules.
 #
index a10f9e765630e5c56d1e2d069722e8789a95e05c..042e7e53ea63da840887cb9f7ba906c71390e1d6 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: htmltemplate.py,v 1.46 2001-11-24 00:53:12 jhermann Exp $
+# $Id: htmltemplate.py,v 1.47 2001-11-26 22:55:56 richard Exp $
 
 __doc__ = """
 Template engine.
@@ -634,6 +634,10 @@ class IndexTemplate(TemplateFunctions):
 
         w = self.client.write
 
+        # wrap the template in a single table to ensure the whole widget
+        # is displayed at once
+        w('<table><tr><td>')
+
         if template and filter:
             # display the filter section
             w('<table width=100% border=0 cellspacing=0 cellpadding=2>')
@@ -733,6 +737,9 @@ class IndexTemplate(TemplateFunctions):
         w('</tr>\n')
         w('</table>\n')
 
+        # and the outer table
+        w('</td></tr></table>')
+
 
     def sortby(self, sort_name, filterspec, columns, filter, group, sort):
         l = []
@@ -824,7 +831,8 @@ class ItemTemplate(TemplateFunctions):
             #  designators...
 
         w = self.client.write
-        w('<form action="%s%s">'%(self.classname, nodeid))
+        w('<form action="%s%s" method="POST" enctype="multipart/form-data">'%(
+            self.classname, nodeid))
         s = open(os.path.join(self.templates, self.classname+'.item')).read()
         replace = ItemTemplateReplace(self.globals, locals(), self.cl, nodeid)
         w(replace.go(s))
@@ -865,6 +873,9 @@ class NewItemTemplate(TemplateFunctions):
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.46  2001/11/24 00:53:12  jhermann
+# "except:" is bad, bad , bad!
+#
 # Revision 1.45  2001/11/22 15:46:42  jhermann
 # Added module docstrings to all modules.
 #
index 6274173e836511b409bb31f111892c3badbfad26..8a15ad0890ffbc49df3d8c5cbb6b3848c69fa214 100644 (file)
@@ -73,12 +73,12 @@ are calling the create() method to create a new node). If an auditor raises
 an exception, the original message is bounced back to the sender with the
 explanatory message given in the exception. 
 
-$Id: mailgw.py,v 1.35 2001-11-22 15:46:42 jhermann Exp $
+$Id: mailgw.py,v 1.36 2001-11-26 22:55:56 richard Exp $
 '''
 
 
 import string, re, os, mimetools, cStringIO, smtplib, socket, binascii, quopri
-import traceback
+import traceback, MimeWriter
 import hyperdb, date, password
 
 class MailGWError(ValueError):
@@ -111,8 +111,8 @@ class Message(mimetools.Message):
         return Message(s)
 
 subject_re = re.compile(r'(?P<refwd>\s*\W?\s*(fwd|re)\s*\W?\s*)*'
-    r'\s*(\[(?P<classname>[^\d]+)(?P<nodeid>\d+)?\])'
-    r'\s*(?P<title>[^\[]+)(\[(?P<args>.+?)\])?', re.I)
+    r'\s*(\[(?P<classname>[^\d\s]+)(?P<nodeid>\d+)?\])'
+    r'\s*(?P<title>[^[]+)?(\[(?P<args>.+?)\])?', re.I)
 
 class MailGW:
     def __init__(self, instance, db):
@@ -133,38 +133,31 @@ class MailGW:
         errors in a sane manner. It should be replaced if you wish to
         handle errors in a different manner.
         '''
-        m = []
         # in some rare cases, a particularly stuffed-up e-mail will make
         # its way into here... try to handle it gracefully
         sendto = message.getaddrlist('from')
         if sendto:
             try:
-                self.handle_message(message)
-                return
+                return self.handle_message(message)
             except MailUsageError, value:
                 # bounce the message back to the sender with the usage message
                 fulldoc = '\n'.join(string.split(__doc__, '\n')[2:])
                 sendto = [sendto[0][1]]
-                m = ['Subject: Failed issue tracker submission', '']
+                m = ['']
                 m.append(str(value))
                 m.append('\n\nMail Gateway Help\n=================')
                 m.append(fulldoc)
+                m = self.bounce_message(message, sendto, m)
             except:
                 # bounce the message back to the sender with the error message
                 sendto = [sendto[0][1]]
-                m = ['Subject: failed issue tracker submission', '']
-                # TODO as attachments?
+                m = ['']
                 m.append('----  traceback of failure  ----')
                 s = cStringIO.StringIO()
                 import traceback
                 traceback.print_exc(None, s)
                 m.append(s.getvalue())
-                m.append('---- failed message follows ----')
-                try:
-                    message.fp.seek(0)
-                except:
-                    pass
-                m.append(message.fp.read())
+                m = self.bounce_message(message, sendto, m)
         else:
             # very bad-looking message - we don't even know who sent it
             sendto = [self.ADMIN_EMAIL]
@@ -172,25 +165,63 @@ class MailGW:
             m.append('')
             m.append('The mail gateway retrieved a message which has no From:')
             m.append('line, indicating that it is corrupt. Please check your')
-            m.append('mail gateway source.')
+            m.append('mail gateway source. Failed message is attached.')
             m.append('')
-            m.append('---- failed message follows ----')
-            try:
-                message.fp.seek(0)
-            except:
-                pass
-            m.append(message.fp.read())
+            m = self.bounce_message(message, sendto, m,
+                subject='Badly formed message from mail gateway')
 
         # now send the message
         try:
             smtp = smtplib.SMTP(self.MAILHOST)
-            smtp.sendmail(self.ADMIN_EMAIL, sendto, '\n'.join(m))
+            smtp.sendmail(self.ADMIN_EMAIL, sendto, m.getvalue())
         except socket.error, value:
             raise MailGWError, "Couldn't send confirmation email: "\
                 "mailhost %s"%value
         except smtplib.SMTPException, value:
             raise MailGWError, "Couldn't send confirmation email: %s"%value
 
+    def bounce_message(self, message, sendto, error,
+            subject='Failed issue tracker submission'):
+        ''' create a message that explains the reason for the failed
+            issue submission to the author and attach the original
+            message.
+        '''
+        msg = cStringIO.StringIO()
+        writer = MimeWriter.MimeWriter(msg)
+        writer.addheader('Subject', subject)
+        writer.addheader('From', '%s <%s>'% (self.instance.INSTANCE_NAME,
+                                            self.ISSUE_TRACKER_EMAIL))
+        writer.addheader('To', ','.join(sendto))
+        writer.addheader('MIME-Version', '1.0')
+        part = writer.startmultipartbody('mixed')
+        part = writer.nextpart()
+        body = part.startbody('text/plain')
+        body.write('\n'.join(error))
+
+        # reconstruct the original message
+        m = cStringIO.StringIO()
+        w = MimeWriter.MimeWriter(m)
+        for header in message.headers:
+            header_name = header.split(':')[0]
+            if message.getheader(header_name):
+                w.addheader(header_name,message.getheader(header_name))
+        body = w.startbody('text/plain')
+        try:
+            message.fp.seek(0)
+        except:
+            pass
+        body.write(message.fp.read())
+
+        # attach the original message to the returned message
+        part = writer.nextpart()
+        part.addheader('Content-Disposition','attachment')
+        part.addheader('Content-Transfer-Encoding', '7bit')
+        body = part.startbody('message/rfc822')
+        body.write(m.getvalue())
+
+        writer.lastpart()
+        return msg
+
     def handle_message(self, message):
         ''' message - a Message instance
 
@@ -213,10 +244,9 @@ line. The subject must contain a class name or designator to indicate the
 
 Subject was: "%s"
 '''%subject
+
+        # get the classname
         classname = m.group('classname')
-        nodeid = m.group('nodeid')
-        title = m.group('title').strip()
-        subject_args = m.group('args')
         try:
             cl = self.db.getclass(classname)
         except KeyError:
@@ -228,6 +258,29 @@ Valid class names are: %s
 Subject was: "%s"
 '''%(classname, ', '.join(self.db.getclasses()), subject)
 
+        # get the optional nodeid
+        nodeid = m.group('nodeid')
+
+        # title is optional too
+        title = m.group('title')
+        if title:
+            title = title.strip()
+        else:
+            title = ''
+
+        # but we do need either a title or a nodeid...
+        if not nodeid and not title:
+            raise MailUsageError, '''
+I cannot match your message to a node in the database - you need to either
+supply a full node identifier (with number, eg "[issue123]" or keep the
+previous subject title intact so I can match that.
+
+Subject was: "%s"
+'''%(classname, subject)
+
+        # extract the args
+        subject_args = m.group('args')
+
         # If there's no nodeid, check to see if this is a followup and
         # maybe someone's responded to the initial mail that created an
         # entry. Try to find the matching nodes with the same title, and
@@ -255,21 +308,22 @@ Subject argument list not of form [arg=value,value,...;arg=value,value...]
 
 Subject was: "%s"
 '''%(message, subject)
+                key = key.strip()
                 try:
-                    type =  properties[key]
+                    proptype =  properties[key]
                 except KeyError:
                     raise MailUsageError, '''
 Subject argument list refers to an invalid property: "%s"
 
 Subject was: "%s"
 '''%(key, subject)
-                if isinstance(type, hyperdb.String):
-                    props[key] = value 
-                if isinstance(type, hyperdb.Password):
-                    props[key] = password.Password(value)
-                elif isinstance(type, hyperdb.Date):
+                if isinstance(proptype, hyperdb.String):
+                    props[key] = value.strip()
+                if isinstance(proptype, hyperdb.Password):
+                    props[key] = password.Password(value.strip())
+                elif isinstance(proptype, hyperdb.Date):
                     try:
-                        props[key] = date.Date(value)
+                        props[key] = date.Date(value.strip())
                     except ValueError, message:
                         raise UsageError, '''
 Subject argument list contains an invalid date for %s.
@@ -277,9 +331,9 @@ Subject argument list contains an invalid date for %s.
 Error was: %s
 Subject was: "%s"
 '''%(key, message, subject)
-                elif isinstance(type, hyperdb.Interval):
+                elif isinstance(proptype, hyperdb.Interval):
                     try:
-                        props[key] = date.Interval(value)
+                        props[key] = date.Interval(value) # no strip needed
                     except ValueError, message:
                         raise UsageError, '''
 Subject argument list contains an invalid date interval for %s.
@@ -287,10 +341,10 @@ Subject argument list contains an invalid date interval for %s.
 Error was: %s
 Subject was: "%s"
 '''%(key, message, subject)
-                elif isinstance(type, hyperdb.Link):
-                    props[key] = value
-                elif isinstance(type, hyperdb.Multilink):
-                    props[key] = value.split(',')
+                elif isinstance(proptype, hyperdb.Link):
+                    props[key] = value.strip()
+                elif isinstance(proptype, hyperdb.Multilink):
+                    props[key] = [x.strip() for x in value.split(',')]
 
         #
         # handle the users
@@ -392,8 +446,8 @@ not find a text/plain part to use.
 
         # handle the files
         files = []
-        for (name, type, data) in attachments:
-            files.append(self.db.file.create(type=type, name=name,
+        for (name, mime_type, data) in attachments:
+            files.append(self.db.file.create(type=mime_type, name=name,
                 content=data))
 
         # now handle the db stuff
@@ -433,6 +487,24 @@ Subject was: "%s"
                             props['status'] == resolved_id):
                         props['status'] = chatting_id
 
+            # add nosy in arguments to issue's nosy list, don't replace
+            if props.has_key('nosy'):
+                n = {}
+                for nid in cl.get(nodeid, 'nosy'):
+                    n[nid] = 1
+                for value in props['nosy']:
+                    if self.db.hasnode('user', value):
+                        nid = value
+                    else:
+                        try:
+                            nid = self.db.user.lookup(value)
+                        except:
+                            continue
+                    if n.has_key(nid): continue
+                    n[nid] = 1
+                props['nosy'] = n.keys()
+
+            # now apply the changes
             try:
                 cl.set(nodeid, **props)
             except (TypeError, IndexError, ValueError), message:
@@ -448,10 +520,6 @@ There was a problem with the message you sent:
             message_id = self.db.msg.create(author=author,
                 recipients=recipients, date=date.Date('.'), summary=summary,
                 content=content, files=files)
-            # fill out the properties with defaults where required
-            if properties.has_key('assignedto') and \
-                    not props.has_key('assignedto'):
-                props['assignedto'] = '1'             # "admin"
 
             # pre-set the issue to unread
             if properties.has_key('status') and not props.has_key('status'):
@@ -522,6 +590,9 @@ def parseContent(content, blank_line=re.compile(r'[\r\n]+\s*[\r\n]+'),
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.35  2001/11/22 15:46:42  jhermann
+# Added module docstrings to all modules.
+#
 # Revision 1.34  2001/11/15 10:24:27  richard
 # handle the case where there is no file attached
 #
index 7650cc8e78768f6f3c2a6b63991bef55a5223429..dde58dfec043c673d48d276ed1095d4f87903b17 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: roundupdb.py,v 1.20 2001-11-25 10:11:14 jhermann Exp $
+# $Id: roundupdb.py,v 1.21 2001-11-26 22:55:56 richard Exp $
 
 __doc__ = """
 Extending hyperdb with types specific to issue-tracking.
@@ -23,7 +23,7 @@ Extending hyperdb with types specific to issue-tracking.
 
 import re, os, smtplib, socket
 import mimetools, MimeWriter, cStringIO
-import binascii, mimetypes
+import base64, mimetypes
 
 import hyperdb, date
 
@@ -231,6 +231,8 @@ class DetectorError(RuntimeError):
 class IssueClass(Class):
     # configuration
     MESSAGES_TO_AUTHOR = 'no'
+    INSTANCE_NAME = 'Roundup issue tracker'
+    EMAIL_SIGNATURE_POSITION = 'bottom'
 
     # Overridden methods:
 
@@ -284,6 +286,9 @@ class IssueClass(Class):
         # figure the author's id, and indicate they've received the message
         authid = self.db.msg.get(msgid, 'author')
 
+        # get the current nosy list, we'll need it
+        nosy = self.get(nodeid, 'nosy')
+
         # ... but duplicate the message to the author as long as it's not
         # the anonymous user
         if (self.MESSAGES_TO_AUTHOR == 'yes' and
@@ -293,7 +298,6 @@ class IssueClass(Class):
         r[authid] = 1
 
         # now figure the nosy people who weren't recipients
-        nosy = self.get(nodeid, 'nosy')
         for nosyid in nosy:
             # Don't send nosy mail to the anonymous user (that user
             # shouldn't appear in the nosy list, but just in case they
@@ -323,14 +327,25 @@ class IssueClass(Class):
         else:
             authaddr = ''
         # make the message body
-        m = []
+        m = ['']
+
+        # put in roundup's signature
+        if self.EMAIL_SIGNATURE_POSITION == 'top':
+            m.append(self.email_signature(nodeid, msgid))
+
         # add author information
-        m.append("%s %sadded the comment:"%(authname, authaddr))
+        if len(self.db.issue.get(nodeid, 'messages')) == 1:
+            m.append("New submission from %s <%s>:"%(authname, authaddr))
+        else:
+            m.append("%s <%s> added the comment:"%(authname, authaddr))
         m.append('')
+
         # add the content
         m.append(self.db.msg.get(msgid, 'content'))
+
         # "list information" footer
-        m.append(self.email_footer(nodeid, msgid))
+        if self.EMAIL_SIGNATURE_POSITION == 'bottom':
+            m.append(self.email_signature(nodeid, msgid))
 
         # get the files for this message
         files = self.db.msg.get(msgid, 'files')
@@ -340,8 +355,13 @@ class IssueClass(Class):
         writer = MimeWriter.MimeWriter(message)
         writer.addheader('Subject', '[%s%s] %s'%(cn, nodeid, title))
         writer.addheader('To', ', '.join(sendto))
-        writer.addheader('From', self.ISSUE_TRACKER_EMAIL)
-        writer.addheader('Reply-To:', self.ISSUE_TRACKER_EMAIL)
+        writer.addheader('From', '%s <%s>'%(self.INSTANCE_NAME,
+            self.ISSUE_TRACKER_EMAIL))
+        writer.addheader('Reply-To:', '%s <%s>'%(self.INSTANCE_NAME,
+            self.ISSUE_TRACKER_EMAIL))
+        writer.addheader('MIME-Version', '1.0')
+
+        # attach files
         if files:
             part = writer.startmultipartbody('mixed')
             part = writer.nextpart()
@@ -349,22 +369,27 @@ class IssueClass(Class):
             body.write('\n'.join(m))
             for fileid in files:
                 name = self.db.file.get(fileid, 'name')
-                type = self.db.file.get(fileid, 'type')
+                mime_type = self.db.file.get(fileid, 'type')
                 content = self.db.file.get(fileid, 'content')
                 part = writer.nextpart()
-                if type == 'text/plain':
+                if mime_type == 'text/plain':
                     part.addheader('Content-Disposition',
                         'attachment;\n filename="%s"'%name)
                     part.addheader('Content-Transfer-Encoding', '7bit')
                     body = part.startbody('text/plain')
                     body.write(content)
                 else:
-                    type = mimetypes.guess_type(name)[0]
+                    # some other type, so encode it
+                    if not mime_type:
+                        # this should have been done when the file was saved
+                        mime_type = mimetypes.guess_type(name)[0]
+                    if mime_type is None:
+                        mime_type = 'application/octet-stream'
                     part.addheader('Content-Disposition',
                         'attachment;\n filename="%s"'%name)
                     part.addheader('Content-Transfer-Encoding', 'base64')
-                    body = part.startbody(type)
-                body.write(binascii.b2a_base64(content))
+                    body = part.startbody(mime_type)
+                    body.write(base64.encodestring(content))
             writer.lastpart()
         else:
             body = writer.startbody('text/plain')
@@ -381,18 +406,22 @@ class IssueClass(Class):
             raise MessageSendError, \
                 "Couldn't send confirmation email: %s"%value
 
-    def email_footer(self, nodeid, msgid):
-        ''' Add a footer to the e-mail with some useful information
+    def email_signature(self, nodeid, msgid):
+        ''' Add a signature to the e-mail with some useful information
         '''
         web = self.ISSUE_TRACKER_WEB + 'issue'+ nodeid
         return '''%s
-Roundup issue tracker
 %s
 %s
-'''%('_'*len(web), self.ISSUE_TRACKER_EMAIL, web)
+%s
+'''%('_'*len(web), self.INSTANCE_NAME, self.ISSUE_TRACKER_EMAIL, web,
+    '_'*len(web))
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.20  2001/11/25 10:11:14  jhermann
+# Typo fix
+#
 # Revision 1.19  2001/11/22 15:46:42  jhermann
 # Added module docstrings to all modules.
 #
index a48553c9050a7bb66f6a8f5442e475cf0b8f3050..bb389d7ae08e250ac1e599cfa6b7aff90a844e15 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: dbinit.py,v 1.9 2001-10-30 00:54:45 richard Exp $
+# $Id: dbinit.py,v 1.10 2001-11-26 22:55:56 richard Exp $
 
 import os
 
@@ -35,11 +35,13 @@ class Database(roundupdb.Database, select_db.Database):
 class IssueClass(roundupdb.IssueClass):
     ''' issues need the email information
     '''
+    INSTANCE_NAME = instance_config.INSTANCE_NAME
     ISSUE_TRACKER_WEB = instance_config.ISSUE_TRACKER_WEB
     ISSUE_TRACKER_EMAIL = instance_config.ISSUE_TRACKER_EMAIL
     ADMIN_EMAIL = instance_config.ADMIN_EMAIL
     MAILHOST = instance_config.MAILHOST
     MESSAGES_TO_AUTHOR = instance_config.MESSAGES_TO_AUTHOR
+    EMAIL_SIGNATURE_POSITION = instance_config.EMAIL_SIGNATURE_POSITION
 
  
 def open(name=None):
@@ -126,6 +128,12 @@ def init(adminpw):
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.9  2001/10/30 00:54:45  richard
+# Features:
+#  . #467129 ] Lossage when username=e-mail-address
+#  . #473123 ] Change message generation for author
+#  . MailGW now moves 'resolved' to 'chatting' on receiving e-mail for an issue.
+#
 # Revision 1.8  2001/10/09 07:25:59  richard
 # Added the Password property type. See "pydoc roundup.password" for
 # implementation details. Have updated some of the documentation too.
index 876219784d4545e17d4bcc6c10178ed5d763223e..6f84a39498a8ef257b0587f88401d41d6ae6c39e 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-#$Id: nosyreaction.py,v 1.5 2001-11-12 22:01:07 richard Exp $
+#$Id: nosyreaction.py,v 1.6 2001-11-26 22:55:56 richard Exp $
 
 from roundup import roundupdb
 
@@ -76,7 +76,9 @@ def nosyreaction(db, cl, nodeid, oldvalues):
         if n.has_key(authid): continue
         if db.user.get(authid, 'username') == 'anonymous': continue
         change = 1
-        nosy.append(authid)
+        # append the author only after issue creation
+        if oldvalues is None:
+            nosy.append(authid)
     if change:
         cl.set(nodeid, nosy=nosy)
 
@@ -87,6 +89,9 @@ def init(db):
 
 #
 #$Log: not supported by cvs2svn $
+#Revision 1.5  2001/11/12 22:01:07  richard
+#Fixed issues with nosy reaction and author copies.
+#
 #Revision 1.4  2001/10/30 00:54:45  richard
 #Features:
 # . #467129 ] Lossage when username=e-mail-address
index de3759f3ea23b5de0266e1243c17feb350b47c91..ac2cffea7acc07b7be0730f35a030c9d6e4009cb 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: instance_config.py,v 1.9 2001-10-30 00:54:45 richard Exp $
+# $Id: instance_config.py,v 1.10 2001-11-26 22:55:56 richard Exp $
 
 MAIL_DOMAIN=MAILHOST=HTTP_HOST=None
 HTTP_PORT=0
@@ -50,6 +50,9 @@ DATABASE = os.path.join(INSTANCE_HOME, 'db')
 # This is the directory that the HTML templates reside in
 TEMPLATES = os.path.join(INSTANCE_HOME, 'html')
 
+# A descriptive name for your roundup instance
+INSTANCE_NAME = 'Roundup issue tracker'
+
 # The email address that mail to roundup should go to
 ISSUE_TRACKER_EMAIL = 'issue_tracker@%s'%MAIL_DOMAIN
 
@@ -74,8 +77,17 @@ ANONYMOUS_REGISTER = 'deny'     # either 'deny' or 'allow'
 # Send nosy messages to the author of the message
 MESSAGES_TO_AUTHOR = 'no'       # either 'yes' or 'no'
 
+# Where to place the email signature
+EMAIL_SIGNATURE_POSITION = 'bottom'
+
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.9  2001/10/30 00:54:45  richard
+# Features:
+#  . #467129 ] Lossage when username=e-mail-address
+#  . #473123 ] Change message generation for author
+#  . MailGW now moves 'resolved' to 'chatting' on receiving e-mail for an issue.
+#
 # Revision 1.8  2001/10/23 01:00:18  richard
 # Re-enabled login and registration access after lopping them off via
 # disabling access for anonymous users.
index 45754cb3984167d1204a2657fc6b7fa1c5ae0c1a..2a92ea7ff15f89b611d332b82aeb87588148251c 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: dbinit.py,v 1.14 2001-11-21 02:34:18 richard Exp $
+# $Id: dbinit.py,v 1.15 2001-11-26 22:55:56 richard Exp $
 
 import os
 
@@ -35,11 +35,13 @@ class Database(roundupdb.Database, select_db.Database):
 class IssueClass(roundupdb.IssueClass):
     ''' issues need the email information
     '''
+    INSTANCE_NAME = instance_config.INSTANCE_NAME
     ISSUE_TRACKER_WEB = instance_config.ISSUE_TRACKER_WEB
     ISSUE_TRACKER_EMAIL = instance_config.ISSUE_TRACKER_EMAIL
     ADMIN_EMAIL = instance_config.ADMIN_EMAIL
     MAILHOST = instance_config.MAILHOST
     MESSAGES_TO_AUTHOR = instance_config.MESSAGES_TO_AUTHOR
+    EMAIL_SIGNATURE_POSITION = instance_config.EMAIL_SIGNATURE_POSITION
 
  
 def open(name=None):
@@ -176,6 +178,9 @@ def init(adminpw):
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.14  2001/11/21 02:34:18  richard
+# Added a target version field to the extended issue schema
+#
 # Revision 1.13  2001/10/30 00:54:45  richard
 # Features:
 #  . #467129 ] Lossage when username=e-mail-address
index ea8f69576c7187347c3b8f302034e5f834ee2f2f..dc7ff83449121099bdc34e541be67a4f349ec533 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-#$Id: nosyreaction.py,v 1.5 2001-11-12 22:01:07 richard Exp $
+#$Id: nosyreaction.py,v 1.6 2001-11-26 22:55:56 richard Exp $
 
 from roundup import roundupdb
 
@@ -76,7 +76,9 @@ def nosyreaction(db, cl, nodeid, oldvalues):
         if n.has_key(authid): continue
         if db.user.get(authid, 'username') == 'anonymous': continue
         change = 1
-        nosy.append(authid)
+        # append the author only after issue creation
+        if oldvalues is None:
+            nosy.append(authid)
     if change:
         cl.set(nodeid, nosy=nosy)
 
@@ -87,6 +89,9 @@ def init(db):
 
 #
 #$Log: not supported by cvs2svn $
+#Revision 1.5  2001/11/12 22:01:07  richard
+#Fixed issues with nosy reaction and author copies.
+#
 #Revision 1.4  2001/10/30 00:54:45  richard
 #Features:
 # . #467129 ] Lossage when username=e-mail-address
index 78bff5b726be84e8c2f56f7d1bd45bd0f2904be7..97cf108ee4a9f794172bb879e8f857e2392e649e 100644 (file)
@@ -15,7 +15,7 @@
 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 # 
-# $Id: instance_config.py,v 1.9 2001-10-30 00:54:45 richard Exp $
+# $Id: instance_config.py,v 1.10 2001-11-26 22:55:56 richard Exp $
 
 MAIL_DOMAIN=MAILHOST=HTTP_HOST=None
 HTTP_PORT=0
@@ -50,6 +50,9 @@ DATABASE = os.path.join(INSTANCE_HOME, 'db')
 # This is the directory that the HTML templates reside in
 TEMPLATES = os.path.join(INSTANCE_HOME, 'html')
 
+# A descriptive name for your roundup instance
+INSTANCE_NAME = 'Roundup issue tracker'
+
 # The email address that mail to roundup should go to
 ISSUE_TRACKER_EMAIL = 'issue_tracker@%s'%MAIL_DOMAIN
 
@@ -74,8 +77,17 @@ ANONYMOUS_REGISTER = 'deny'
 # Send nosy messages to the author of the message
 MESSAGES_TO_AUTHOR = 'no'       # either 'yes' or 'no'
 
+# Where to place the email signature
+EMAIL_SIGNATURE_POSITION = 'bottom'
+
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.9  2001/10/30 00:54:45  richard
+# Features:
+#  . #467129 ] Lossage when username=e-mail-address
+#  . #473123 ] Change message generation for author
+#  . MailGW now moves 'resolved' to 'chatting' on receiving e-mail for an issue.
+#
 # Revision 1.8  2001/10/23 01:00:18  richard
 # Re-enabled login and registration access after lopping them off via
 # disabling access for anonymous users.