Code

. #514854 ] History: "User" is always ticket creator
[roundup.git] / roundup / roundupdb.py
index 3478bd54dcb9403a235ec9ac41ecf7b257fb0f98..c77a3411bf27b7f093dd9f3742b8da2d98353ce2 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.37 2002-01-08 04:12:05 richard Exp $
+# $Id: roundupdb.py,v 1.44 2002-02-15 07:08:44 richard Exp $
 
 __doc__ = """
 Extending hyperdb with types specific to issue-tracking.
@@ -42,6 +42,23 @@ def splitDesignator(designator, dre=re.compile(r'([^\d]+)(\d+)')):
     return m.group(1), m.group(2)
 
 
+def extractUserFromList(users):
+    '''Given a list of users, try to extract the first non-anonymous user
+       and return that user, otherwise return None
+    '''
+    if len(users) > 1:
+        # make sure we don't match the anonymous or admin user
+        for user in users:
+            if user == '1': continue
+            if self.user.get(user, 'username') == 'anonymous': continue
+            # first valid match will do
+            return user
+        # well, I guess we have no choice
+        return user[0]
+    elif users:
+        return users[0]
+    return None
+
 class Database:
     def getuid(self):
         """Return the id of the "user" node associated with the user
@@ -54,26 +71,25 @@ class Database:
             user is created if they don't exist in the db already
         '''
         (realname, address) = address
-        users = self.user.stringFind(address=address)
-        for dummy in range(2):
-            if len(users) > 1:
-                # make sure we don't match the anonymous or admin user
-                for user in users:
-                    if user == '1': continue
-                    if self.user.get(user, 'username') == 'anonymous': continue
-                    # first valid match will do
-                    return user
-                # well, I guess we have no choice
-                return user[0]
-            elif users:
-                return users[0]
-            # try to match the username to the address (for local
-            # submissions where the address is empty)
-            users = self.user.stringFind(username=address)
+
+        # try a straight match of the address
+        user = extractUserFromList(self.user.stringFind(address=address))
+        if user is not None: return user
+
+        # try the user alternate addresses if possible
+        props = self.user.getprops()
+        if props.has_key('alternate_addresses'):
+            users = self.user.filter({'alternate_addresses': address},
+                [], [])
+            user = extractUserFromList(users)
+            if user is not None: return user
+
+        # try to match the username to the address (for local
+        # submissions where the address is empty)
+        user = extractUserFromList(self.user.stringFind(username=address))
 
         # couldn't match address or username, so create a new user
         if create:
-            print 'CREATING USER', address
             return self.user.create(username=address, address=address,
                 realname=realname)
         else:
@@ -237,10 +253,6 @@ class DetectorError(RuntimeError):
 
 # XXX deviation from spec - was called ItemClass
 class IssueClass(Class):
-    # configuration
-    MESSAGES_TO_AUTHOR = 'no'
-    INSTANCE_NAME = 'Roundup issue tracker'
-    EMAIL_SIGNATURE_POSITION = 'bottom'
 
     # Overridden methods:
 
@@ -303,7 +315,7 @@ class IssueClass(Class):
 
         # possibly send the message to the author, as long as they aren't
         # anonymous
-        if (self.MESSAGES_TO_AUTHOR == 'yes' and
+        if (self.db.config.MESSAGES_TO_AUTHOR == 'yes' and
                 users.get(authid, 'username') != 'anonymous'):
             sendto.append(authid)
         r[authid] = 1
@@ -332,7 +344,7 @@ class IssueClass(Class):
             # this is an old message that didn't get a messageid, so
             # create one
             messageid = "<%s.%s.%s%s@%s>"%(time.time(), random.random(),
-                self.classname, nodeid, self.MAIL_DOMAIN)
+                self.classname, nodeid, self.db.config.MAIL_DOMAIN)
             messages.set(msgid, messageid=messageid)
 
         # update the message's recipients list
@@ -356,7 +368,7 @@ class IssueClass(Class):
         m = ['']
 
         # put in roundup's signature
-        if self.EMAIL_SIGNATURE_POSITION == 'top':
+        if self.db.config.EMAIL_SIGNATURE_POSITION == 'top':
             m.append(self.email_signature(nodeid, msgid))
 
         # add author information
@@ -374,33 +386,37 @@ class IssueClass(Class):
             m.append(change_note)
 
         # put in roundup's signature
-        if self.EMAIL_SIGNATURE_POSITION == 'bottom':
+        if self.db.config.EMAIL_SIGNATURE_POSITION == 'bottom':
             m.append(self.email_signature(nodeid, msgid))
 
         # get the files for this message
-        files = messages.get(msgid, 'files')
+        message_files = messages.get(msgid, 'files')
 
         # create the message
         message = cStringIO.StringIO()
         writer = MimeWriter.MimeWriter(message)
         writer.addheader('Subject', '[%s%s] %s'%(cn, nodeid, title))
         writer.addheader('To', ', '.join(sendto))
-        writer.addheader('From', '%s <%s>'%(authname, self.ISSUE_TRACKER_EMAIL))
-        writer.addheader('Reply-To', '%s <%s>'%(self.INSTANCE_NAME,
-            self.ISSUE_TRACKER_EMAIL))
+        writer.addheader('From', '%s <%s>'%(authname,
+            self.db.config.ISSUE_TRACKER_EMAIL))
+        writer.addheader('Reply-To', '%s <%s>'%(self.db.config.INSTANCE_NAME,
+            self.db.config.ISSUE_TRACKER_EMAIL))
         writer.addheader('MIME-Version', '1.0')
         if messageid:
             writer.addheader('Message-Id', messageid)
         if inreplyto:
             writer.addheader('In-Reply-To', inreplyto)
 
+        # add a uniquely Roundup header to help filtering
+        writer.addheader('X-Roundup-Name', self.db.config.INSTANCE_NAME)
+
         # attach files
-        if files:
+        if message_files:
             part = writer.startmultipartbody('mixed')
             part = writer.nextpart()
             body = part.startbody('text/plain')
             body.write('\n'.join(m))
-            for fileid in files:
+            for fileid in message_files:
                 name = files.get(fileid, 'name')
                 mime_type = files.get(fileid, 'type')
                 content = files.get(fileid, 'content')
@@ -431,13 +447,14 @@ class IssueClass(Class):
         # now try to send the message
         if SENDMAILDEBUG:
             open(SENDMAILDEBUG, 'w').write('FROM: %s\nTO: %s\n%s\n'%(
-                self.ADMIN_EMAIL, ', '.join(sendto), message.getvalue()))
+                self.db.config.ADMIN_EMAIL,', '.join(sendto),message.getvalue()))
         else:
             try:
                 # send the message as admin so bounces are sent there
                 # instead of to roundup
-                smtp = smtplib.SMTP(self.MAILHOST)
-                smtp.sendmail(self.ADMIN_EMAIL, sendto, message.getvalue())
+                smtp = smtplib.SMTP(self.db.config.MAILHOST)
+                smtp.sendmail(self.db.config.ADMIN_EMAIL, sendto,
+                    message.getvalue())
             except socket.error, value:
                 raise MessageSendError, \
                     "Couldn't send confirmation email: mailhost %s"%value
@@ -448,11 +465,49 @@ class IssueClass(Class):
     def email_signature(self, nodeid, msgid):
         ''' Add a signature to the e-mail with some useful information
         '''
-        web = self.ISSUE_TRACKER_WEB + 'issue'+ nodeid
-        email = '"%s" <%s>'%(self.INSTANCE_NAME, self.ISSUE_TRACKER_EMAIL)
+        web = self.db.config.ISSUE_TRACKER_WEB + 'issue'+ nodeid
+        email = '"%s" <%s>'%(self.db.config.INSTANCE_NAME,
+            self.db.config.ISSUE_TRACKER_EMAIL)
         line = '_' * max(len(web), len(email))
         return '%s\n%s\n%s\n%s'%(line, email, web, line)
 
+    def generateCreateNote(self, nodeid):
+        """Generate a create note that lists initial property values
+        """
+        cn = self.classname
+        cl = self.db.classes[cn]
+        props = cl.getprops(protected=0)
+
+        # list the values
+        m = []
+        l = props.items()
+        l.sort()
+        for propname, prop in l:
+            value = cl.get(nodeid, propname, None)
+            # skip boring entries
+            if not value:
+                continue
+            if isinstance(prop, hyperdb.Link):
+                link = self.db.classes[prop.classname]
+                if value:
+                    key = link.labelprop(default_to_id=1)
+                    if key:
+                        value = link.get(value, key)
+                else:
+                    value = ''
+            elif isinstance(prop, hyperdb.Multilink):
+                if value is None: value = []
+                l = []
+                link = self.db.classes[prop.classname]
+                key = link.labelprop(default_to_id=1)
+                if key:
+                    value = [link.get(entry, key) for entry in value]
+                value = ', '.join(value)
+            m.append('%s: %s'%(propname, value))
+        m.insert(0, '----------')
+        m.insert(0, '')
+        return '\n'.join(m)
+
     def generateChangeNote(self, nodeid, oldvalues):
         """Generate a change note that lists property changes
         """
@@ -478,7 +533,9 @@ class IssueClass(Class):
 
         # list the changes
         m = []
-        for propname, oldvalue in changed.items():
+        l = changed.items()
+        l.sort()
+        for propname, oldvalue in l:
             prop = cl.properties[propname]
             value = cl.get(nodeid, propname, None)
             if isinstance(prop, hyperdb.Link):
@@ -530,6 +587,33 @@ class IssueClass(Class):
 
 #
 # $Log: not supported by cvs2svn $
+# Revision 1.43  2002/02/14 22:33:15  richard
+#  . Added a uniquely Roundup header to email, "X-Roundup-Name"
+#
+# Revision 1.42  2002/01/21 09:55:14  rochecompaan
+# Properties in change note are now sorted
+#
+# Revision 1.41  2002/01/15 00:12:40  richard
+# #503340 ] creating issue with [asignedto=p.ohly]
+#
+# Revision 1.40  2002/01/14 22:21:38  richard
+# #503353 ] setting properties in initial email
+#
+# Revision 1.39  2002/01/14 02:20:15  richard
+#  . changed all config accesses so they access either the instance or the
+#    config attriubute on the db. This means that all config is obtained from
+#    instance_config instead of the mish-mash of classes. This will make
+#    switching to a ConfigParser setup easier too, I hope.
+#
+# At a minimum, this makes migration a _little_ easier (a lot easier in the
+# 0.5.0 switch, I hope!)
+#
+# Revision 1.38  2002/01/10 05:57:45  richard
+# namespace clobberation
+#
+# Revision 1.37  2002/01/08 04:12:05  richard
+# Changed message-id format to "<%s.%s.%s%s@%s>" so it complies with RFC822
+#
 # Revision 1.36  2002/01/02 02:31:38  richard
 # Sorry for the huge checkin message - I was only intending to implement #496356
 # but I found a number of places where things had been broken by transactions: