Merge lp:~gmb/launchpad/refactor-bugnotification-recipient-rationales-bug-594208 into lp:launchpad

Proposed by Graham Binns
Status: Merged
Approved by: Graham Binns
Approved revision: no longer in the source branch.
Merged at revision: 11040
Proposed branch: lp:~gmb/launchpad/refactor-bugnotification-recipient-rationales-bug-594208
Merge into: lp:launchpad
Diff against target: 418 lines (+173/-150)
8 files modified
lib/canonical/launchpad/mailnotification.py (+9/-144)
lib/lp/bugs/configure.zcml (+1/-1)
lib/lp/bugs/doc/bug-change.txt (+1/-1)
lib/lp/bugs/doc/bugnotificationrecipients.txt (+1/-1)
lib/lp/bugs/mail/bugnotificationrecipients.py (+158/-0)
lib/lp/bugs/model/bug.py (+1/-1)
lib/lp/bugs/scripts/tests/test_bugnotification.py (+1/-1)
lib/lp/registry/doc/structural-subscriptions.txt (+1/-1)
To merge this branch: bzr merge lp:~gmb/launchpad/refactor-bugnotification-recipient-rationales-bug-594208
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) code Approve
Review via email: mp+27687@code.launchpad.net

Commit message

BugNotificationRecipients has been moved to lp.bugs.mail.bugnotificationrecipients.

Description of the change

This branch moves BugNotificationRecipients into its own module in the lp.bugs.mail package.

This turned out to be a much easier branch than I anticipated. I thought that some refactoring would be needed to make BugNotificationRecipients play nice with lp.services.mail, but it turned out that it already extends lp.services.mail.notificationrecipientset.NotificationRecipientSet.

I've moved the class out of mailnotification.py and updated all the import sites I could find. I've added an XXX to mailnotification.py for the import of NotificationRecipientSet, since there's a lot of things that import it indirectly but fixing those import sites is out-of-scope for this branch.

To post a comment you must log in.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

Fine with me. As discussed on IRC, there's plenty more work to be done on the code that's moving (dealing with supervisors of duplicate bugs, factoring out the repetition in text composition, replacing reason = reason + x with reason += x) but as you point out that's best left to later branches after first moving the code out of conflicts' way.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/canonical/launchpad/mailnotification.py'
--- lib/canonical/launchpad/mailnotification.py 2010-06-10 10:54:51 +0000
+++ lib/canonical/launchpad/mailnotification.py 2010-06-16 08:38:25 +0000
@@ -32,9 +32,8 @@
32 get_contact_email_addresses, get_email_template, shortlist)32 get_contact_email_addresses, get_email_template, shortlist)
33from canonical.launchpad.interfaces import (33from canonical.launchpad.interfaces import (
34 IEmailAddressSet, IHeldMessageDetails, ILaunchpadCelebrities,34 IEmailAddressSet, IHeldMessageDetails, ILaunchpadCelebrities,
35 INotificationRecipientSet, IPerson, IPersonSet, ISpecification,35 IPerson, IPersonSet, ISpecification, IStructuralSubscriptionTarget,
36 IStructuralSubscriptionTarget, ITeamMembershipSet, IUpstreamBugTask,36 ITeamMembershipSet, IUpstreamBugTask, TeamMembershipStatus)
37 TeamMembershipStatus)
38from lp.bugs.interfaces.bugchange import IBugChange37from lp.bugs.interfaces.bugchange import IBugChange
39from canonical.launchpad.interfaces.launchpad import ILaunchpadRoot38from canonical.launchpad.interfaces.launchpad import ILaunchpadRoot
40from canonical.launchpad.interfaces.message import (39from canonical.launchpad.interfaces.message import (
@@ -44,154 +43,20 @@
44from canonical.launchpad.mail import (43from canonical.launchpad.mail import (
45 sendmail, simple_sendmail, simple_sendmail_from_person, format_address)44 sendmail, simple_sendmail, simple_sendmail_from_person, format_address)
46from lp.services.mail.mailwrapper import MailWrapper45from lp.services.mail.mailwrapper import MailWrapper
46from canonical.launchpad.webapp.publisher import canonical_url
47from canonical.launchpad.webapp.url import urlappend
48
49# XXX 2010-06-16 gmb bug=594985
50# This shouldn't be here, but if we take it out lots of things cry,
51# which is sad.
47from lp.services.mail.notificationrecipientset import (52from lp.services.mail.notificationrecipientset import (
48 NotificationRecipientSet)53 NotificationRecipientSet)
49from canonical.launchpad.webapp.publisher import canonical_url
50from canonical.launchpad.webapp.url import urlappend
5154
55from lp.bugs.mail.bugnotificationrecipients import BugNotificationRecipients
5256
53CC = "CC"57CC = "CC"
5458
5559
56class BugNotificationRecipients(NotificationRecipientSet):
57 """A set of emails and rationales notified for a bug change.
58
59 Each email address registered in a BugNotificationRecipients is
60 associated to a string and a header that explain why the address is
61 being emailed. For instance, if the email address is that of a
62 distribution bug supervisor for a bug, the string and header will make
63 that fact clear.
64
65 The string is meant to be rendered in the email footer. The header
66 is meant to be used in an X-Launchpad-Message-Rationale header.
67
68 The first rationale registered for an email address is the one
69 which will be used, regardless of other rationales being added
70 for it later. This gives us a predictable policy of preserving
71 the first reason added to the registry; the callsite should
72 ensure that the manipulation of the BugNotificationRecipients
73 instance is done in preferential order.
74
75 Instances of this class are meant to be returned by
76 IBug.getBugNotificationRecipients().
77 """
78 implements(INotificationRecipientSet)
79
80 def __init__(self, duplicateof=None):
81 """Constructs a new BugNotificationRecipients instance.
82
83 If this bug is a duplicate, duplicateof should be used to
84 specify which bug ID it is a duplicate of.
85
86 Note that there are two duplicate situations that are
87 important:
88 - One is when this bug is a duplicate of another bug:
89 the subscribers to the main bug get notified of our
90 changes.
91 - Another is when the bug we are changing has
92 duplicates; in that case, direct subscribers of
93 duplicate bugs get notified of our changes.
94 These two situations are catered respectively by the
95 duplicateof parameter above and the addDupeSubscriber method.
96 Don't confuse them!
97 """
98 NotificationRecipientSet.__init__(self)
99 self.duplicateof = duplicateof
100
101 def _addReason(self, person, reason, header):
102 """Adds a reason (text and header) for a person.
103
104 It takes care of modifying the message when the person is notified
105 via a duplicate.
106 """
107 if self.duplicateof is not None:
108 reason = reason + " (via bug %s)" % self.duplicateof.id
109 header = header + " via Bug %s" % self.duplicateof.id
110 reason = "You received this bug notification because you %s." % reason
111 self.add(person, reason, header)
112
113 def addDupeSubscriber(self, person):
114 """Registers a subscriber of a duplicate of this bug."""
115 reason = "Subscriber of Duplicate"
116 if person.isTeam():
117 text = ("are a member of %s, which is a subscriber "
118 "of a duplicate bug" % person.displayname)
119 reason += " @%s" % person.name
120 else:
121 text = "are a direct subscriber of a duplicate bug"
122 self._addReason(person, text, reason)
123
124 def addDirectSubscriber(self, person):
125 """Registers a direct subscriber of this bug."""
126 reason = "Subscriber"
127 if person.isTeam():
128 text = ("are a member of %s, which is a direct subscriber"
129 % person.displayname)
130 reason += " @%s" % person.name
131 else:
132 text = "are a direct subscriber of the bug"
133 self._addReason(person, text, reason)
134
135 def addAssignee(self, person):
136 """Registers an assignee of a bugtask of this bug."""
137 reason = "Assignee"
138 if person.isTeam():
139 text = ("are a member of %s, which is a bug assignee"
140 % person.displayname)
141 reason += " @%s" % person.name
142 else:
143 text = "are a bug assignee"
144 self._addReason(person, text, reason)
145
146 def addDistroBugSupervisor(self, person, distro):
147 """Registers a distribution bug supervisor for this bug."""
148 reason = "Bug Supervisor (%s)" % distro.displayname
149 # All displaynames in these reasons should be changed to bugtargetname
150 # (as part of bug 113262) once bugtargetname is finalized for packages
151 # (bug 113258). Changing it before then would be excessively
152 # disruptive.
153 if person.isTeam():
154 text = ("are a member of %s, which is the bug supervisor for %s" %
155 (person.displayname, distro.displayname))
156 reason += " @%s" % person.name
157 else:
158 text = "are the bug supervisor for %s" % distro.displayname
159 self._addReason(person, text, reason)
160
161 def addStructuralSubscriber(self, person, target):
162 """Registers a structural subscriber to this bug's target."""
163 reason = "Subscriber (%s)" % target.displayname
164 if person.isTeam():
165 text = ("are a member of %s, which is subscribed to %s" %
166 (person.displayname, target.displayname))
167 reason += " @%s" % person.name
168 else:
169 text = "are subscribed to %s" % target.displayname
170 self._addReason(person, text, reason)
171
172 def addUpstreamBugSupervisor(self, person, upstream):
173 """Registers an upstream bug supervisor for this bug."""
174 reason = "Bug Supervisor (%s)" % upstream.displayname
175 if person.isTeam():
176 text = ("are a member of %s, which is the bug supervisor for %s" %
177 (person.displayname, upstream.displayname))
178 reason += " @%s" % person.name
179 else:
180 text = "are the bug supervisor for %s" % upstream.displayname
181 self._addReason(person, text, reason)
182
183 def addRegistrant(self, person, upstream):
184 """Registers an upstream product registrant for this bug."""
185 reason = "Registrant (%s)" % upstream.displayname
186 if person.isTeam():
187 text = ("are a member of %s, which is the registrant for %s" %
188 (person.displayname, upstream.displayname))
189 reason += " @%s" % person.name
190 else:
191 text = "are the registrant for %s" % upstream.displayname
192 self._addReason(person, text, reason)
193
194
195def format_rfc2822_date(date):60def format_rfc2822_date(date):
196 """Formats a date according to RFC2822's desires."""61 """Formats a date according to RFC2822's desires."""
197 return formatdate(rfc822.mktime_tz(date.utctimetuple() + (0, )))62 return formatdate(rfc822.mktime_tz(date.utctimetuple() + (0, )))
19863
=== modified file 'lib/lp/bugs/configure.zcml'
--- lib/lp/bugs/configure.zcml 2010-06-04 09:31:21 +0000
+++ lib/lp/bugs/configure.zcml 2010-06-16 08:38:25 +0000
@@ -952,7 +952,7 @@
952 interface="lp.bugs.interfaces.bugnotification.IBugNotificationRecipient"/>952 interface="lp.bugs.interfaces.bugnotification.IBugNotificationRecipient"/>
953 </class>953 </class>
954 <class954 <class
955 class="canonical.launchpad.mailnotification.BugNotificationRecipients">955 class="lp.bugs.mail.bugnotificationrecipients.BugNotificationRecipients">
956 <allow956 <allow
957 interface="canonical.launchpad.interfaces.INotificationRecipientSet"/>957 interface="canonical.launchpad.interfaces.INotificationRecipientSet"/>
958 </class>958 </class>
959959
=== modified file 'lib/lp/bugs/doc/bug-change.txt'
--- lib/lp/bugs/doc/bug-change.txt 2009-06-12 16:36:02 +0000
+++ lib/lp/bugs/doc/bug-change.txt 2010-06-16 08:38:25 +0000
@@ -54,7 +54,7 @@
5454
55We'll create a test class that actually implements the methods we need.55We'll create a test class that actually implements the methods we need.
5656
57 >>> from canonical.launchpad.mailnotification import (57 >>> from lp.bugs.mail.bugnotificationrecipients import (
58 ... BugNotificationRecipients)58 ... BugNotificationRecipients)
5959
60 >>> example_message = factory.makeMessage(content="Hello, world")60 >>> example_message = factory.makeMessage(content="Hello, world")
6161
=== modified file 'lib/lp/bugs/doc/bugnotificationrecipients.txt'
--- lib/lp/bugs/doc/bugnotificationrecipients.txt 2009-08-13 19:03:36 +0000
+++ lib/lp/bugs/doc/bugnotificationrecipients.txt 2010-06-16 08:38:25 +0000
@@ -63,7 +63,7 @@
63Let's up some data for our test:63Let's up some data for our test:
6464
65 >>> from canonical.launchpad.interfaces import IPersonSet65 >>> from canonical.launchpad.interfaces import IPersonSet
66 >>> from canonical.launchpad.mailnotification import (66 >>> from lp.bugs.mail.bugnotificationrecipients import (
67 ... BugNotificationRecipients)67 ... BugNotificationRecipients)
68 >>> debian = Distribution.selectOneBy(name="debian")68 >>> debian = Distribution.selectOneBy(name="debian")
69 >>> pmount = debian.getSourcePackage("pmount")69 >>> pmount = debian.getSourcePackage("pmount")
7070
=== added file 'lib/lp/bugs/mail/bugnotificationrecipients.py'
--- lib/lp/bugs/mail/bugnotificationrecipients.py 1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/mail/bugnotificationrecipients.py 2010-06-16 08:38:25 +0000
@@ -0,0 +1,158 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Code for handling bug notification recipients in bug mail."""
5
6__metaclass__ = type
7__all__ = [
8 'BugNotificationRecipients',
9 ]
10
11from zope.interface import implements
12
13from canonical.launchpad.interfaces import INotificationRecipientSet
14
15from lp.services.mail.notificationrecipientset import (
16 NotificationRecipientSet)
17
18
19class BugNotificationRecipients(NotificationRecipientSet):
20 """A set of emails and rationales notified for a bug change.
21
22 Each email address registered in a BugNotificationRecipients is
23 associated to a string and a header that explain why the address is
24 being emailed. For instance, if the email address is that of a
25 distribution bug supervisor for a bug, the string and header will make
26 that fact clear.
27
28 The string is meant to be rendered in the email footer. The header
29 is meant to be used in an X-Launchpad-Message-Rationale header.
30
31 The first rationale registered for an email address is the one
32 which will be used, regardless of other rationales being added
33 for it later. This gives us a predictable policy of preserving
34 the first reason added to the registry; the callsite should
35 ensure that the manipulation of the BugNotificationRecipients
36 instance is done in preferential order.
37
38 Instances of this class are meant to be returned by
39 IBug.getBugNotificationRecipients().
40 """
41 implements(INotificationRecipientSet)
42
43 def __init__(self, duplicateof=None):
44 """Constructs a new BugNotificationRecipients instance.
45
46 If this bug is a duplicate, duplicateof should be used to
47 specify which bug ID it is a duplicate of.
48
49 Note that there are two duplicate situations that are
50 important:
51 - One is when this bug is a duplicate of another bug:
52 the subscribers to the main bug get notified of our
53 changes.
54 - Another is when the bug we are changing has
55 duplicates; in that case, direct subscribers of
56 duplicate bugs get notified of our changes.
57 These two situations are catered respectively by the
58 duplicateof parameter above and the addDupeSubscriber method.
59 Don't confuse them!
60 """
61 NotificationRecipientSet.__init__(self)
62 self.duplicateof = duplicateof
63
64 def _addReason(self, person, reason, header):
65 """Adds a reason (text and header) for a person.
66
67 It takes care of modifying the message when the person is notified
68 via a duplicate.
69 """
70 if self.duplicateof is not None:
71 reason = reason + " (via bug %s)" % self.duplicateof.id
72 header = header + " via Bug %s" % self.duplicateof.id
73 reason = "You received this bug notification because you %s." % reason
74 self.add(person, reason, header)
75
76 def addDupeSubscriber(self, person):
77 """Registers a subscriber of a duplicate of this bug."""
78 reason = "Subscriber of Duplicate"
79 if person.isTeam():
80 text = ("are a member of %s, which is a subscriber "
81 "of a duplicate bug" % person.displayname)
82 reason += " @%s" % person.name
83 else:
84 text = "are a direct subscriber of a duplicate bug"
85 self._addReason(person, text, reason)
86
87 def addDirectSubscriber(self, person):
88 """Registers a direct subscriber of this bug."""
89 reason = "Subscriber"
90 if person.isTeam():
91 text = ("are a member of %s, which is a direct subscriber"
92 % person.displayname)
93 reason += " @%s" % person.name
94 else:
95 text = "are a direct subscriber of the bug"
96 self._addReason(person, text, reason)
97
98 def addAssignee(self, person):
99 """Registers an assignee of a bugtask of this bug."""
100 reason = "Assignee"
101 if person.isTeam():
102 text = ("are a member of %s, which is a bug assignee"
103 % person.displayname)
104 reason += " @%s" % person.name
105 else:
106 text = "are a bug assignee"
107 self._addReason(person, text, reason)
108
109 def addDistroBugSupervisor(self, person, distro):
110 """Registers a distribution bug supervisor for this bug."""
111 reason = "Bug Supervisor (%s)" % distro.displayname
112 # All displaynames in these reasons should be changed to bugtargetname
113 # (as part of bug 113262) once bugtargetname is finalized for packages
114 # (bug 113258). Changing it before then would be excessively
115 # disruptive.
116 if person.isTeam():
117 text = ("are a member of %s, which is the bug supervisor for %s" %
118 (person.displayname, distro.displayname))
119 reason += " @%s" % person.name
120 else:
121 text = "are the bug supervisor for %s" % distro.displayname
122 self._addReason(person, text, reason)
123
124 def addStructuralSubscriber(self, person, target):
125 """Registers a structural subscriber to this bug's target."""
126 reason = "Subscriber (%s)" % target.displayname
127 if person.isTeam():
128 text = ("are a member of %s, which is subscribed to %s" %
129 (person.displayname, target.displayname))
130 reason += " @%s" % person.name
131 else:
132 text = "are subscribed to %s" % target.displayname
133 self._addReason(person, text, reason)
134
135 def addUpstreamBugSupervisor(self, person, upstream):
136 """Registers an upstream bug supervisor for this bug."""
137 reason = "Bug Supervisor (%s)" % upstream.displayname
138 if person.isTeam():
139 text = ("are a member of %s, which is the bug supervisor for %s" %
140 (person.displayname, upstream.displayname))
141 reason += " @%s" % person.name
142 else:
143 text = "are the bug supervisor for %s" % upstream.displayname
144 self._addReason(person, text, reason)
145
146 def addRegistrant(self, person, upstream):
147 """Registers an upstream product registrant for this bug."""
148 reason = "Registrant (%s)" % upstream.displayname
149 if person.isTeam():
150 text = ("are a member of %s, which is the registrant for %s" %
151 (person.displayname, upstream.displayname))
152 reason += " @%s" % person.name
153 else:
154 text = "are the registrant for %s" % upstream.displayname
155 self._addReason(person, text, reason)
156
157
158
0159
=== modified file 'lib/lp/bugs/model/bug.py'
--- lib/lp/bugs/model/bug.py 2010-06-10 18:55:22 +0000
+++ lib/lp/bugs/model/bug.py 2010-06-16 08:38:25 +0000
@@ -58,7 +58,7 @@
58 IMessage, IndexedMessage)58 IMessage, IndexedMessage)
59from lp.registry.interfaces.structuralsubscription import (59from lp.registry.interfaces.structuralsubscription import (
60 BugNotificationLevel, IStructuralSubscriptionTarget)60 BugNotificationLevel, IStructuralSubscriptionTarget)
61from canonical.launchpad.mailnotification import BugNotificationRecipients61from lp.bugs.mail.bugnotificationrecipients import BugNotificationRecipients
62from canonical.launchpad.validators import LaunchpadValidationError62from canonical.launchpad.validators import LaunchpadValidationError
63from canonical.launchpad.webapp.interfaces import (63from canonical.launchpad.webapp.interfaces import (
64 IStoreSelector, DEFAULT_FLAVOR, MAIN_STORE, NotFoundError)64 IStoreSelector, DEFAULT_FLAVOR, MAIN_STORE, NotFoundError)
6565
=== modified file 'lib/lp/bugs/scripts/tests/test_bugnotification.py'
--- lib/lp/bugs/scripts/tests/test_bugnotification.py 2010-01-12 21:12:12 +0000
+++ lib/lp/bugs/scripts/tests/test_bugnotification.py 2010-06-16 08:38:25 +0000
@@ -21,7 +21,7 @@
21from lp.bugs.interfaces.bug import IBug, IBugSet21from lp.bugs.interfaces.bug import IBug, IBugSet
22from lp.registry.interfaces.person import IPersonSet22from lp.registry.interfaces.person import IPersonSet
23from lp.registry.interfaces.product import IProductSet23from lp.registry.interfaces.product import IProductSet
24from canonical.launchpad.mailnotification import BugNotificationRecipients24from lp.bugs.mail.bugnotificationrecipients import BugNotificationRecipients
25from lp.bugs.scripts.bugnotification import (25from lp.bugs.scripts.bugnotification import (
26 get_email_notifications)26 get_email_notifications)
27from canonical.testing import LaunchpadZopelessLayer27from canonical.testing import LaunchpadZopelessLayer
2828
=== modified file 'lib/lp/registry/doc/structural-subscriptions.txt'
--- lib/lp/registry/doc/structural-subscriptions.txt 2010-02-20 14:16:37 +0000
+++ lib/lp/registry/doc/structural-subscriptions.txt 2010-06-16 08:38:25 +0000
@@ -86,7 +86,7 @@
86 >>> from canonical.launchpad.ftests import syncUpdate86 >>> from canonical.launchpad.ftests import syncUpdate
87 >>> from canonical.launchpad.interfaces import (87 >>> from canonical.launchpad.interfaces import (
88 ... BlueprintNotificationLevel, BugNotificationLevel)88 ... BlueprintNotificationLevel, BugNotificationLevel)
89 >>> from canonical.launchpad.mailnotification import (89 >>> from lp.bugs.mail.bugnotificationrecipients import (
90 ... BugNotificationRecipients)90 ... BugNotificationRecipients)
9191
92We define some utility functions for printing out bug subscriptions and92We define some utility functions for printing out bug subscriptions and