From 5804b3d347231a0f49494b270253b27c835554fa Mon Sep 17 00:00:00 2001 From: richard Date: Thu, 2 May 2002 07:56:34 +0000 Subject: [PATCH] . added option to automatically add the authors and recipients of messages to the nosy lists with the options ADD_AUTHOR_TO_NOSY (default 'new') and ADD_RECIPIENTS_TO_NOSY (default 'new'). These settings emulate the current behaviour. Setting them to 'yes' will add the author/recipients to the nosy on messages that create issues and followup messages. . added missing documentation for a few of the config option values git-svn-id: http://svn.roundup-tracker.org/svnroot/roundup/trunk@713 57a73879-2fb5-44c3-a270-3262357dd7e2 --- CHANGES.txt | 7 +- roundup/mailgw.py | 109 +++++--- roundup/templates/classic/instance_config.py | 35 ++- roundup/templates/extended/instance_config.py | 35 ++- test/test_mailgw.py | 262 +++++++++++++++++- 5 files changed, 381 insertions(+), 67 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index b0b8a4a..807c97c 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -22,6 +22,11 @@ Feature: . stripping of the email message body can now be controlled through the config variables EMAIL_KEEP_QUOTED_TEXT and EMAIL_LEAVE_BODY_UNCHANGED. . all database files created are now group readable and writable. + . added option to automatically add the authors and recipients of messages + to the nosy lists with the options ADD_AUTHOR_TO_NOSY (default 'new') and + ADD_RECIPIENTS_TO_NOSY (default 'new'). These settings emulate the current + behaviour. Setting them to 'yes' will add the author/recipients to the nosy + on messages that create issues and followup messages. Fixed: . stop sending blank (whitespace-only) notes @@ -31,7 +36,7 @@ Fixed: to the nodes that are actually linked to in the "field" template function. This adds about 20+ seconds in the display of an issue if your database has a 1000 or more issues in it. - + . added missing documentation for a few of the config option values 2002-03-25 - 0.4.1 Feature: diff --git a/roundup/mailgw.py b/roundup/mailgw.py index c496772..3246dca 100644 --- a/roundup/mailgw.py +++ b/roundup/mailgw.py @@ -73,7 +73,7 @@ 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.67 2002-04-23 15:46:49 rochecompaan Exp $ +$Id: mailgw.py,v 1.68 2002-05-02 07:56:34 richard Exp $ ''' @@ -131,7 +131,7 @@ class MailGW: def main(self, fp): ''' fp - the file from which to read the Message. ''' - self.handle_Message(Message(fp)) + return self.handle_Message(Message(fp)) def handle_Message(self, message): '''Handle an RFC822 Message @@ -493,7 +493,14 @@ Unknown address: %s r = recipient[1].strip().lower() if r == tracker_email or not r: continue - recipients.append(self.db.uidFromAddress(recipient)) + + # look up the recipient - create if necessary (and we're + # allowed to) + recipient = self.db.uidFromAddress(recipient, create) + + # if all's well, add the recipient to the list + if recipient: + recipients.append(recipient) # # handle message-id and in-reply-to @@ -638,25 +645,13 @@ not find a text/plain part to use. current_status == resolved_id): props['status'] = chatting_id - # add nosy in arguments to issue's nosy list - if not props.has_key('nosy'): props['nosy'] = [] - n = {} + # update the nosy list + current = {} for nid in cl.get(nodeid, 'nosy'): - n[nid] = 1 - for value in props['nosy']: - if self.db.hasnode('user', value): - nid = value - else: - continue - if n.has_key(nid): continue - n[nid] = 1 - props['nosy'] = n.keys() - # add assignedto to the nosy list - if props.has_key('assignedto'): - assignedto = props['assignedto'] - if assignedto not in props['nosy']: - props['nosy'].append(assignedto) + current[nid] = 1 + self.updateNosy(props, author, recipients, current) + # create the message message_id = self.db.msg.create(author=author, recipients=recipients, date=date.Date('.'), summary=summary, content=content, files=files, messageid=messageid, @@ -711,30 +706,7 @@ There was a problem with the message you sent: props['messages'] = [message_id] # set up (clean) the nosy list - nosy = props.get('nosy', []) - n = {} - for value in nosy: - nid = value - if n.has_key(nid): continue - n[nid] = 1 - props['nosy'] = n.keys() - # add on the recipients of the message - for recipient in recipients: - if not n.has_key(recipient): - props['nosy'].append(recipient) - n[recipient] = 1 - - # add the author to the nosy list - if not n.has_key(author): - props['nosy'].append(author) - n[author] = 1 - - # add assignedto to the nosy list - if properties.has_key('assignedto') and props.has_key('assignedto'): - assignedto = props['assignedto'] - if not n.has_key(assignedto): - props['nosy'].append(assignedto) - n[assignedto] = 1 + self.updateNosy(props, author, recipients) # and attempt to create the new node try: @@ -748,6 +720,50 @@ There was a problem with the message you sent: # commit the new node(s) to the DB self.db.commit() + return nodeid + + def updateNosy(self, props, author, recipients, current=None): + '''Determine what the nosy list should be given: + + props: properties specified on the subject line of the message + author: the sender of the message + recipients: the recipients (to, cc) of the message + current: if the issue already exists, this is the current nosy + list, as a dictionary. + ''' + if current is None: + current = {} + ok = ('new', 'yes') + else: + ok = ('yes',) + + # add nosy in arguments to issue's nosy list + nosy = props.get('nosy', []) + for value in nosy: + if not self.db.hasnode('user', value): + continue + if not current.has_key(value): + current[value] = 1 + + # add the author to the nosy list + if getattr(self.instance, 'ADD_AUTHOR_TO_NOSY', 'new') in ok: + if not current.has_key(author): + current[author] = 1 + + # add on the recipients of the message + if getattr(self.instance, 'ADD_RECIPIENTS_TO_NOSY', 'new') in ok: + for recipient in recipients: + if not current.has_key(recipient): + current[recipient] = 1 + + # add assignedto to the nosy list + if props.has_key('assignedto'): + assignedto = props['assignedto'] + if not current.has_key(assignedto): + current[assignedto] = 1 + + props['nosy'] = current.keys() + def parseContent(content, keep_citations, keep_body, blank_line=re.compile(r'[\r\n]+\s*[\r\n]+'), eol=re.compile(r'[\r\n]+'), @@ -812,6 +828,11 @@ def parseContent(content, keep_citations, keep_body, # # $Log: not supported by cvs2svn $ +# Revision 1.67 2002/04/23 15:46:49 rochecompaan +# . stripping of the email message body can now be controlled through +# the config variables EMAIL_KEEP_QUOTED_TEST and +# EMAIL_LEAVE_BODY_UNCHANGED. +# # Revision 1.66 2002/03/14 23:59:24 richard # . #517734 ] web header customisation is obscure # diff --git a/roundup/templates/classic/instance_config.py b/roundup/templates/classic/instance_config.py index cc57d2a..34e5b33 100644 --- a/roundup/templates/classic/instance_config.py +++ b/roundup/templates/classic/instance_config.py @@ -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.14 2002-04-23 15:46:49 rochecompaan Exp $ +# $Id: instance_config.py,v 1.15 2002-05-02 07:56:34 richard Exp $ MAIL_DOMAIN=MAILHOST=HTTP_HOST=None HTTP_PORT=0 @@ -66,28 +66,40 @@ ADMIN_EMAIL = 'roundup-admin@%s'%MAIL_DOMAIN LOG = os.path.join(INSTANCE_HOME, 'roundup.log') # Where to place the web filtering HTML on the index page -FILTER_POSITION = 'bottom' # one of 'top', 'bottom', 'top and bottom' +FILTER_POSITION = 'bottom' # one of 'top', 'bottom', 'top and bottom' # Deny or allow anonymous access to the web interface -ANONYMOUS_ACCESS = 'deny' # either 'deny' or 'allow' +ANONYMOUS_ACCESS = 'deny' # either 'deny' or 'allow' # Deny or allow anonymous users to register through the web interface -ANONYMOUS_REGISTER = 'deny' # either 'deny' or 'allow' +ANONYMOUS_REGISTER = 'deny' # either 'deny' or 'allow' # Deny or allow anonymous users to register through the mail interface -ANONYMOUS_REGISTER_MAIL = 'deny' # either 'deny' or 'allow' +ANONYMOUS_REGISTER_MAIL = 'deny' # either 'deny' or 'allow' # Send nosy messages to the author of the message -MESSAGES_TO_AUTHOR = 'no' # either 'yes' or 'no' +MESSAGES_TO_AUTHOR = 'no' # either 'yes' or 'no' + +# Does the author of a message get placed on the nosy list automatically? +# If 'new' is used, then the author will only be added when a message +# creates a new issue. If 'yes', then the author will be added on followups +# too. If 'no', they're never added to the nosy. +ADD_AUTHOR_TO_NOSY = 'new' # one of 'yes', 'no', 'new' + +# Do the recipients (To:, Cc:) of a message get placed on the nosy list? +# If 'new' is used, then the recipients will only be added when a message +# creates a new issue. If 'yes', then the recipients will be added on followups +# too. If 'no', they're never added to the nosy. +ADD_RECIPIENTS_TO_NOSY = 'new' # either 'yes', 'no', 'new' # Where to place the email signature -EMAIL_SIGNATURE_POSITION = 'bottom' +EMAIL_SIGNATURE_POSITION = 'bottom' # one of 'top', 'bottom', 'none' # Keep email citations -EMAIL_KEEP_QUOTED_TEXT = 'no' +EMAIL_KEEP_QUOTED_TEXT = 'no' # either 'yes' or 'no' # Preserve the email body as is -EMAIL_LEAVE_BODY_UNCHANGED = 'no' +EMAIL_LEAVE_BODY_UNCHANGED = 'no' # either 'yes' or 'no' # Default class to use in the mailgw if one isn't supplied in email # subjects. To disable, comment out the variable below or leave it blank. @@ -150,6 +162,11 @@ USER_INDEX = { # # $Log: not supported by cvs2svn $ +# Revision 1.14 2002/04/23 15:46:49 rochecompaan +# . stripping of the email message body can now be controlled through +# the config variables EMAIL_KEEP_QUOTED_TEST and +# EMAIL_LEAVE_BODY_UNCHANGED. +# # Revision 1.13 2002/03/14 23:59:24 richard # . #517734 ] web header customisation is obscure # diff --git a/roundup/templates/extended/instance_config.py b/roundup/templates/extended/instance_config.py index e60c930..264f4da 100644 --- a/roundup/templates/extended/instance_config.py +++ b/roundup/templates/extended/instance_config.py @@ -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.14 2002-04-23 15:46:49 rochecompaan Exp $ +# $Id: instance_config.py,v 1.15 2002-05-02 07:56:34 richard Exp $ MAIL_DOMAIN=MAILHOST=HTTP_HOST=None HTTP_PORT=0 @@ -66,28 +66,40 @@ ADMIN_EMAIL = 'roundup-admin@%s'%MAIL_DOMAIN LOG = os.path.join(INSTANCE_HOME, 'roundup.log') # Where to place the web filtering HTML on the index page -FILTER_POSITION = 'bottom' # one of 'top', 'bottom', 'top and bottom' +FILTER_POSITION = 'bottom' # one of 'top', 'bottom', 'top and bottom' # Deny or allow anonymous access to the web interface -ANONYMOUS_ACCESS = 'deny' # either 'deny' or 'allow' +ANONYMOUS_ACCESS = 'deny' # either 'deny' or 'allow' # Deny or allow anonymous users to register through the web interface -ANONYMOUS_REGISTER = 'deny' # either 'deny' or 'allow' +ANONYMOUS_REGISTER = 'deny' # either 'deny' or 'allow' # Deny or allow anonymous users to register through the mail interface -ANONYMOUS_REGISTER_MAIL = 'deny' # either 'deny' or 'allow' +ANONYMOUS_REGISTER_MAIL = 'deny' # either 'deny' or 'allow' # Send nosy messages to the author of the message -MESSAGES_TO_AUTHOR = 'no' # either 'yes' or 'no' +MESSAGES_TO_AUTHOR = 'no' # either 'yes' or 'no' + +# Does the author of a message get placed on the nosy list automatically? +# If 'new' is used, then the author will only be added when a message +# creates a new issue. If 'yes', then the author will be added on followups +# too. If 'no', they're never added to the nosy. +ADD_AUTHOR_TO_NOSY = 'new' # one of 'yes', 'no', 'new' + +# Do the recipients (To:, Cc:) of a message get placed on the nosy list? +# If 'new' is used, then the recipients will only be added when a message +# creates a new issue. If 'yes', then the recipients will be added on followups +# too. If 'no', they're never added to the nosy. +ADD_RECIPIENTS_TO_NOSY = 'new' # either 'yes', 'no', 'new' # Where to place the email signature -EMAIL_SIGNATURE_POSITION = 'bottom' +EMAIL_SIGNATURE_POSITION = 'bottom' # one of 'top', 'bottom', 'none' # Keep email citations -EMAIL_KEEP_QUOTED_TEXT = 'no' +EMAIL_KEEP_QUOTED_TEXT = 'no' # either 'yes' or 'no' # Preserve the email body as is -EMAIL_LEAVE_BODY_UNCHANGED = 'no' +EMAIL_LEAVE_BODY_UNCHANGED = 'no' # either 'yes' or 'no' # Default class to use in the mailgw if one isn't supplied in email # subjects. To disable, comment out the variable below or leave it blank. @@ -187,6 +199,11 @@ MY_SUPPORT_INDEX = { # # $Log: not supported by cvs2svn $ +# Revision 1.14 2002/04/23 15:46:49 rochecompaan +# . stripping of the email message body can now be controlled through +# the config variables EMAIL_KEEP_QUOTED_TEST and +# EMAIL_LEAVE_BODY_UNCHANGED. +# # Revision 1.13 2002/03/14 23:59:24 richard # . #517734 ] web header customisation is obscure # diff --git a/test/test_mailgw.py b/test/test_mailgw.py index 2e696db..1c50d99 100644 --- a/test/test_mailgw.py +++ b/test/test_mailgw.py @@ -8,7 +8,7 @@ # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # -# $Id: test_mailgw.py,v 1.16 2002-03-19 21:58:11 grubert Exp $ +# $Id: test_mailgw.py,v 1.17 2002-05-02 07:56:34 richard Exp $ import unittest, cStringIO, tempfile, os, shutil, errno, imp, sys, difflib @@ -93,10 +93,30 @@ Subject: [issue] Testing... This is a test submission of a new issue. ''') handler = self.instance.MailGW(self.instance, self.db) - handler.main(message) + nodeid = handler.main(message) if os.path.exists(os.environ['SENDMAILDEBUG']): error = open(os.environ['SENDMAILDEBUG']).read() self.assertEqual('no error', error) + self.assertEqual(self.db.issue.get(nodeid, 'nosy'), ['2', '3']) + + def testNewIssueNosy(self): + self.instance.ADD_AUTHOR_TO_NOSY = 'yes' + message = cStringIO.StringIO('''Content-Type: text/plain; + charset="iso-8859-1" +From: Chef +Subject: [issue] Testing... + +This is a test submission of a new issue. +''') + handler = self.instance.MailGW(self.instance, self.db) + nodeid = handler.main(message) + if os.path.exists(os.environ['SENDMAILDEBUG']): + error = open(os.environ['SENDMAILDEBUG']).read() + self.assertEqual('no error', error) + self.assertEqual(self.db.issue.get(nodeid, 'nosy'), ['2', '3']) def testAlternateAddress(self): message = cStringIO.StringIO('''Content-Type: text/plain; @@ -319,6 +339,237 @@ ___________________________________________________ "Roundup issue tracker" http://some.useful.url/issue1 ___________________________________________________ +''') + + def testFollowupNosyAuthor(self): + self.testNewIssue() + self.instance.ADD_AUTHOR_TO_NOSY = 'yes' + message = cStringIO.StringIO('''Content-Type: text/plain; + charset="iso-8859-1" +From: john@test +To: issue_tracker@fill.me.in. +Message-Id: +In-Reply-To: +Subject: [issue1] Testing... + +This is a followup +''') + handler = self.instance.MailGW(self.instance, self.db) + handler.main(message) + + self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(), +'''FROM: roundup-admin@fill.me.in. +TO: chef@bork.bork.bork, richard@test +Content-Type: text/plain +Subject: [issue1] Testing... +To: chef@bork.bork.bork, richard@test +From: john +Reply-To: Roundup issue tracker +MIME-Version: 1.0 +Message-Id: +In-Reply-To: +X-Roundup-Name: Roundup issue tracker +Content-Transfer-Encoding: quoted-printable + + +john added the comment: + +This is a followup + + +---------- +nosy: +john +status: unread -> chatting +___________________________________________________ +"Roundup issue tracker" +http://some.useful.url/issue1 +___________________________________________________ + +''') + + def testFollowupNosyRecipients(self): + self.testNewIssue() + self.instance.ADD_RECIPIENTS_TO_NOSY = 'yes' + message = cStringIO.StringIO('''Content-Type: text/plain; + charset="iso-8859-1" +From: richard@test +To: issue_tracker@fill.me.in. +Cc: john@test +Message-Id: +In-Reply-To: +Subject: [issue1] Testing... + +This is a followup +''') + handler = self.instance.MailGW(self.instance, self.db) + handler.main(message) + + self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(), +'''FROM: roundup-admin@fill.me.in. +TO: chef@bork.bork.bork +Content-Type: text/plain +Subject: [issue1] Testing... +To: chef@bork.bork.bork +From: richard +Reply-To: Roundup issue tracker +MIME-Version: 1.0 +Message-Id: +In-Reply-To: +X-Roundup-Name: Roundup issue tracker +Content-Transfer-Encoding: quoted-printable + + +richard added the comment: + +This is a followup + + +---------- +nosy: +john +status: unread -> chatting +___________________________________________________ +"Roundup issue tracker" +http://some.useful.url/issue1 +___________________________________________________ + +''') + + def testFollowupNosyAuthorAndCopy(self): + self.testNewIssue() + self.instance.ADD_AUTHOR_TO_NOSY = 'yes' + self.db.config.MESSAGES_TO_AUTHOR = 'yes' + message = cStringIO.StringIO('''Content-Type: text/plain; + charset="iso-8859-1" +From: john@test +To: issue_tracker@fill.me.in. +Message-Id: +In-Reply-To: +Subject: [issue1] Testing... + +This is a followup +''') + handler = self.instance.MailGW(self.instance, self.db) + handler.main(message) + + self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(), +'''FROM: roundup-admin@fill.me.in. +TO: john@test, chef@bork.bork.bork, richard@test +Content-Type: text/plain +Subject: [issue1] Testing... +To: john@test, chef@bork.bork.bork, richard@test +From: john +Reply-To: Roundup issue tracker +MIME-Version: 1.0 +Message-Id: +In-Reply-To: +X-Roundup-Name: Roundup issue tracker +Content-Transfer-Encoding: quoted-printable + + +john added the comment: + +This is a followup + + +---------- +nosy: +john +status: unread -> chatting +___________________________________________________ +"Roundup issue tracker" +http://some.useful.url/issue1 +___________________________________________________ + +''') + + def testFollowupNoNosyAuthor(self): + self.testNewIssue() + self.instance.ADD_AUTHOR_TO_NOSY = 'no' + message = cStringIO.StringIO('''Content-Type: text/plain; + charset="iso-8859-1" +From: john@test +To: issue_tracker@fill.me.in. +Message-Id: +In-Reply-To: +Subject: [issue1] Testing... + +This is a followup +''') + handler = self.instance.MailGW(self.instance, self.db) + handler.main(message) + + self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(), +'''FROM: roundup-admin@fill.me.in. +TO: chef@bork.bork.bork, richard@test +Content-Type: text/plain +Subject: [issue1] Testing... +To: chef@bork.bork.bork, richard@test +From: john +Reply-To: Roundup issue tracker +MIME-Version: 1.0 +Message-Id: +In-Reply-To: +X-Roundup-Name: Roundup issue tracker +Content-Transfer-Encoding: quoted-printable + + +john added the comment: + +This is a followup + + +---------- +status: unread -> chatting +___________________________________________________ +"Roundup issue tracker" +http://some.useful.url/issue1 +___________________________________________________ + +''') + + def testFollowupNoNosyRecipients(self): + self.testNewIssue() + self.instance.ADD_RECIPIENTS_TO_NOSY = 'no' + message = cStringIO.StringIO('''Content-Type: text/plain; + charset="iso-8859-1" +From: richard@test +To: issue_tracker@fill.me.in. +Cc: john@test +Message-Id: +In-Reply-To: +Subject: [issue1] Testing... + +This is a followup +''') + handler = self.instance.MailGW(self.instance, self.db) + handler.main(message) + + self.compareStrings(open(os.environ['SENDMAILDEBUG']).read(), +'''FROM: roundup-admin@fill.me.in. +TO: chef@bork.bork.bork +Content-Type: text/plain +Subject: [issue1] Testing... +To: chef@bork.bork.bork +From: richard +Reply-To: Roundup issue tracker +MIME-Version: 1.0 +Message-Id: +In-Reply-To: +X-Roundup-Name: Roundup issue tracker +Content-Transfer-Encoding: quoted-printable + + +richard added the comment: + +This is a followup + + +---------- +status: unread -> chatting +___________________________________________________ +"Roundup issue tracker" +http://some.useful.url/issue1 +___________________________________________________ + ''') def testEnc01(self): @@ -423,14 +674,17 @@ class ExtMailgwTestCase(MailgwTestCase): schema = 'extended' def suite(): - l = [unittest.makeSuite(MailgwTestCase, 'test'), -# unittest.makeSuite(ExtMailgwTestCase, 'test') + l = [unittest.makeSuite(MailgwTestCase), + unittest.makeSuite(ExtMailgwTestCase, 'test') ] return unittest.TestSuite(l) # # $Log: not supported by cvs2svn $ +# Revision 1.16 2002/03/19 21:58:11 grubert +# . for python2.1 test_mailgw compareString allows an extra trailing empty line (for quopri. +# # Revision 1.15 2002/03/19 06:37:00 richard # Made the email checking spit out a diff - much easier to spot the problem! # -- 2.30.2