diff --git a/roundup/roundupdb.py b/roundup/roundupdb.py
index 599d1886c587b10f5516a340e2dbcc41ea308847..f6228d9744f63d7bb559b9fc44a8e676d7f93ae7 100644 (file)
--- a/roundup/roundupdb.py
+++ b/roundup/roundupdb.py
# BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
# SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
#
-# $Id: roundupdb.py,v 1.12 2001-10-04 02:16:15 richard Exp $
+# $Id: roundupdb.py,v 1.18 2001-11-15 10:36:17 richard Exp $
import re, os, smtplib, socket
+import mimetools, MimeWriter, cStringIO
+import binascii, mimetypes
import hyperdb, date
+class DesignatorError(ValueError):
+ pass
def splitDesignator(designator, dre=re.compile(r'([^\d]+)(\d+)')):
''' Take a foo123 and return ('foo', 123)
'''
m = dre.match(designator)
+ if m is None:
+ raise DesignatorError, '"%s" not a node designator'%designator
return m.group(1), m.group(2)
+
class Database:
def getuid(self):
"""Return the id of the "user" node associated with the user
'''
(realname, address) = address
users = self.user.stringFind(address=address)
- if users: return users[0]
+ 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)
+
+ # couldn't match address or username, so create a new user
return self.user.create(username=address, address=address,
realname=realname)
class Class(hyperdb.Class):
# Overridden methods:
def __init__(self, db, classname, **properties):
+ if (properties.has_key('creation') or properties.has_key('activity')
+ or properties.has_key('creator')):
+ raise ValueError, '"creation", "activity" and "creator" are reserved'
hyperdb.Class.__init__(self, db, classname, **properties)
self.auditors = {'create': [], 'set': [], 'retire': []}
self.reactors = {'create': [], 'set': [], 'retire': []}
d['content'] = hyperdb.String()
return d
+class MessageSendError(RuntimeError):
+ pass
+
+class DetectorError(RuntimeError):
+ pass
+
# XXX deviation from spec - was called ItemClass
class IssueClass(Class):
+ # configuration
+ MESSAGES_TO_AUTHOR = 'no'
+
# Overridden methods:
def __init__(self, db, classname, **properties):
properties['nosy'] = hyperdb.Multilink("user")
if not properties.has_key('superseder'):
properties['superseder'] = hyperdb.Multilink(classname)
- if (properties.has_key('creation') or properties.has_key('activity')
- or properties.has_key('creator')):
- raise ValueError, '"creation", "activity" and "creator" are reserved'
Class.__init__(self, db, classname, **properties)
# New methods:
r = {}
for recipid in recipients:
r[recipid] = 1
+ rlen = len(recipients)
+
+ # figure the author's id, and indicate they've received the message
authid = self.db.msg.get(msgid, 'author')
+
+ # ... but duplicate the message to the author as long as it's not
+ # the anonymous user
+ if (self.MESSAGES_TO_AUTHOR == 'yes' and
+ self.db.user.get(authid, 'username') != 'anonymous'):
+ if not r.has_key(authid):
+ recipients.append(authid)
r[authid] = 1
# now figure the nosy people who weren't recipients
- sendto = []
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
+ # do...)
+ if self.db.user.get(nosyid, 'username') == 'anonymous': continue
if not r.has_key(nosyid):
- sendto.append(nosyid)
recipients.append(nosyid)
- if sendto:
- # update the message's recipients list
- self.db.msg.set(msgid, recipients=recipients)
-
- # send an email to the people who missed out
- sendto = [self.db.user.get(i, 'address') for i in recipients]
- cn = self.classname
- title = self.get(nodeid, 'title') or '%s message copy'%cn
- m = ['Subject: [%s%s] %s'%(cn, nodeid, title)]
- m.append('To: %s'%', '.join(sendto))
- m.append('Reply-To: %s'%self.ISSUE_TRACKER_EMAIL)
- m.append('')
- m.append(self.db.msg.get(msgid, 'content'))
- m.append(self.email_footer(nodeid, msgid))
- # TODO attachments
- try:
- smtp = smtplib.SMTP(self.MAILHOST)
- smtp.sendmail(self.ISSUE_TRACKER_EMAIL, sendto, '\n'.join(m))
- except socket.error, value:
- return "Couldn't send confirmation email: mailhost %s"%value
- except smtplib.SMTPException, value:
- return "Couldn't send confirmation email: %s"%value
+ # no new recipients
+ if rlen == len(recipients):
+ return
+
+ # update the message's recipients list
+ self.db.msg.set(msgid, recipients=recipients)
+
+ # send an email to the people who missed out
+ sendto = [self.db.user.get(i, 'address') for i in recipients]
+ cn = self.classname
+ title = self.get(nodeid, 'title') or '%s message copy'%cn
+ # figure author information
+ authname = self.db.user.get(authid, 'realname')
+ if not authname:
+ authname = self.db.user.get(authid, 'username')
+ authaddr = self.db.user.get(authid, 'address')
+ if authaddr:
+ authaddr = '<%s> '%authaddr
+ else:
+ authaddr = ''
+ # make the message body
+ m = []
+ # add author information
+ m.append("%s %sadded 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))
+
+ # get the files for this message
+ files = self.db.msg.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('Form', self.ISSUE_TRACKER_EMAIL)
+ writer.addheader('Reply-To:', self.ISSUE_TRACKER_EMAIL)
+ if files:
+ part = writer.startmultipartbody('mixed')
+ part = writer.nextpart()
+ body = part.startbody('text/plain')
+ body.write('\n'.join(m))
+ for fileid in files:
+ name = self.db.file.get(fileid, 'name')
+ type = self.db.file.get(fileid, 'type')
+ content = self.db.file.get(fileid, 'content')
+ part = writer.nextpart()
+ if 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]
+ 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))
+ writer.lastpart()
+ else:
+ body = writer.startbody('text/plain')
+ body.write('\n'.join(m))
+
+ # now try to send the message
+ try:
+ smtp = smtplib.SMTP(self.MAILHOST)
+ smtp.sendmail(self.ISSUE_TRACKER_EMAIL, sendto, message.getvalue())
+ except socket.error, value:
+ raise MessageSendError, \
+ "Couldn't send confirmation email: mailhost %s"%value
+ except smtplib.SMTPException, value:
+ 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
'''
- web = self.ISSUE_TRACKER_WEB
+ web = self.ISSUE_TRACKER_WEB + 'issue'+ nodeid
return '''%s
Roundup issue tracker
%s
#
# $Log: not supported by cvs2svn $
+# Revision 1.17 2001/11/12 22:01:06 richard
+# Fixed issues with nosy reaction and author copies.
+#
+# Revision 1.16 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.15 2001/10/23 01:00:18 richard
+# Re-enabled login and registration access after lopping them off via
+# disabling access for anonymous users.
+# Major re-org of the htmltemplate code, cleaning it up significantly. Fixed
+# a couple of bugs while I was there. Probably introduced a couple, but
+# things seem to work OK at the moment.
+#
+# Revision 1.14 2001/10/21 07:26:35 richard
+# feature #473127: Filenames. I modified the file.index and htmltemplate
+# source so that the filename is used in the link and the creation
+# information is displayed.
+#
+# Revision 1.13 2001/10/21 00:45:15 richard
+# Added author identification to e-mail messages from roundup.
+#
+# Revision 1.12 2001/10/04 02:16:15 richard
+# Forgot to pass the protected flag down *sigh*.
+#
# Revision 1.11 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