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
1=== modified file 'lib/canonical/launchpad/mailnotification.py'
2--- lib/canonical/launchpad/mailnotification.py 2010-06-10 10:54:51 +0000
3+++ lib/canonical/launchpad/mailnotification.py 2010-06-16 08:38:25 +0000
4@@ -32,9 +32,8 @@
5 get_contact_email_addresses, get_email_template, shortlist)
6 from canonical.launchpad.interfaces import (
7 IEmailAddressSet, IHeldMessageDetails, ILaunchpadCelebrities,
8- INotificationRecipientSet, IPerson, IPersonSet, ISpecification,
9- IStructuralSubscriptionTarget, ITeamMembershipSet, IUpstreamBugTask,
10- TeamMembershipStatus)
11+ IPerson, IPersonSet, ISpecification, IStructuralSubscriptionTarget,
12+ ITeamMembershipSet, IUpstreamBugTask, TeamMembershipStatus)
13 from lp.bugs.interfaces.bugchange import IBugChange
14 from canonical.launchpad.interfaces.launchpad import ILaunchpadRoot
15 from canonical.launchpad.interfaces.message import (
16@@ -44,154 +43,20 @@
17 from canonical.launchpad.mail import (
18 sendmail, simple_sendmail, simple_sendmail_from_person, format_address)
19 from lp.services.mail.mailwrapper import MailWrapper
20+from canonical.launchpad.webapp.publisher import canonical_url
21+from canonical.launchpad.webapp.url import urlappend
22+
23+# XXX 2010-06-16 gmb bug=594985
24+# This shouldn't be here, but if we take it out lots of things cry,
25+# which is sad.
26 from lp.services.mail.notificationrecipientset import (
27 NotificationRecipientSet)
28-from canonical.launchpad.webapp.publisher import canonical_url
29-from canonical.launchpad.webapp.url import urlappend
30
31+from lp.bugs.mail.bugnotificationrecipients import BugNotificationRecipients
32
33 CC = "CC"
34
35
36-class BugNotificationRecipients(NotificationRecipientSet):
37- """A set of emails and rationales notified for a bug change.
38-
39- Each email address registered in a BugNotificationRecipients is
40- associated to a string and a header that explain why the address is
41- being emailed. For instance, if the email address is that of a
42- distribution bug supervisor for a bug, the string and header will make
43- that fact clear.
44-
45- The string is meant to be rendered in the email footer. The header
46- is meant to be used in an X-Launchpad-Message-Rationale header.
47-
48- The first rationale registered for an email address is the one
49- which will be used, regardless of other rationales being added
50- for it later. This gives us a predictable policy of preserving
51- the first reason added to the registry; the callsite should
52- ensure that the manipulation of the BugNotificationRecipients
53- instance is done in preferential order.
54-
55- Instances of this class are meant to be returned by
56- IBug.getBugNotificationRecipients().
57- """
58- implements(INotificationRecipientSet)
59-
60- def __init__(self, duplicateof=None):
61- """Constructs a new BugNotificationRecipients instance.
62-
63- If this bug is a duplicate, duplicateof should be used to
64- specify which bug ID it is a duplicate of.
65-
66- Note that there are two duplicate situations that are
67- important:
68- - One is when this bug is a duplicate of another bug:
69- the subscribers to the main bug get notified of our
70- changes.
71- - Another is when the bug we are changing has
72- duplicates; in that case, direct subscribers of
73- duplicate bugs get notified of our changes.
74- These two situations are catered respectively by the
75- duplicateof parameter above and the addDupeSubscriber method.
76- Don't confuse them!
77- """
78- NotificationRecipientSet.__init__(self)
79- self.duplicateof = duplicateof
80-
81- def _addReason(self, person, reason, header):
82- """Adds a reason (text and header) for a person.
83-
84- It takes care of modifying the message when the person is notified
85- via a duplicate.
86- """
87- if self.duplicateof is not None:
88- reason = reason + " (via bug %s)" % self.duplicateof.id
89- header = header + " via Bug %s" % self.duplicateof.id
90- reason = "You received this bug notification because you %s." % reason
91- self.add(person, reason, header)
92-
93- def addDupeSubscriber(self, person):
94- """Registers a subscriber of a duplicate of this bug."""
95- reason = "Subscriber of Duplicate"
96- if person.isTeam():
97- text = ("are a member of %s, which is a subscriber "
98- "of a duplicate bug" % person.displayname)
99- reason += " @%s" % person.name
100- else:
101- text = "are a direct subscriber of a duplicate bug"
102- self._addReason(person, text, reason)
103-
104- def addDirectSubscriber(self, person):
105- """Registers a direct subscriber of this bug."""
106- reason = "Subscriber"
107- if person.isTeam():
108- text = ("are a member of %s, which is a direct subscriber"
109- % person.displayname)
110- reason += " @%s" % person.name
111- else:
112- text = "are a direct subscriber of the bug"
113- self._addReason(person, text, reason)
114-
115- def addAssignee(self, person):
116- """Registers an assignee of a bugtask of this bug."""
117- reason = "Assignee"
118- if person.isTeam():
119- text = ("are a member of %s, which is a bug assignee"
120- % person.displayname)
121- reason += " @%s" % person.name
122- else:
123- text = "are a bug assignee"
124- self._addReason(person, text, reason)
125-
126- def addDistroBugSupervisor(self, person, distro):
127- """Registers a distribution bug supervisor for this bug."""
128- reason = "Bug Supervisor (%s)" % distro.displayname
129- # All displaynames in these reasons should be changed to bugtargetname
130- # (as part of bug 113262) once bugtargetname is finalized for packages
131- # (bug 113258). Changing it before then would be excessively
132- # disruptive.
133- if person.isTeam():
134- text = ("are a member of %s, which is the bug supervisor for %s" %
135- (person.displayname, distro.displayname))
136- reason += " @%s" % person.name
137- else:
138- text = "are the bug supervisor for %s" % distro.displayname
139- self._addReason(person, text, reason)
140-
141- def addStructuralSubscriber(self, person, target):
142- """Registers a structural subscriber to this bug's target."""
143- reason = "Subscriber (%s)" % target.displayname
144- if person.isTeam():
145- text = ("are a member of %s, which is subscribed to %s" %
146- (person.displayname, target.displayname))
147- reason += " @%s" % person.name
148- else:
149- text = "are subscribed to %s" % target.displayname
150- self._addReason(person, text, reason)
151-
152- def addUpstreamBugSupervisor(self, person, upstream):
153- """Registers an upstream bug supervisor for this bug."""
154- reason = "Bug Supervisor (%s)" % upstream.displayname
155- if person.isTeam():
156- text = ("are a member of %s, which is the bug supervisor for %s" %
157- (person.displayname, upstream.displayname))
158- reason += " @%s" % person.name
159- else:
160- text = "are the bug supervisor for %s" % upstream.displayname
161- self._addReason(person, text, reason)
162-
163- def addRegistrant(self, person, upstream):
164- """Registers an upstream product registrant for this bug."""
165- reason = "Registrant (%s)" % upstream.displayname
166- if person.isTeam():
167- text = ("are a member of %s, which is the registrant for %s" %
168- (person.displayname, upstream.displayname))
169- reason += " @%s" % person.name
170- else:
171- text = "are the registrant for %s" % upstream.displayname
172- self._addReason(person, text, reason)
173-
174-
175 def format_rfc2822_date(date):
176 """Formats a date according to RFC2822's desires."""
177 return formatdate(rfc822.mktime_tz(date.utctimetuple() + (0, )))
178
179=== modified file 'lib/lp/bugs/configure.zcml'
180--- lib/lp/bugs/configure.zcml 2010-06-04 09:31:21 +0000
181+++ lib/lp/bugs/configure.zcml 2010-06-16 08:38:25 +0000
182@@ -952,7 +952,7 @@
183 interface="lp.bugs.interfaces.bugnotification.IBugNotificationRecipient"/>
184 </class>
185 <class
186- class="canonical.launchpad.mailnotification.BugNotificationRecipients">
187+ class="lp.bugs.mail.bugnotificationrecipients.BugNotificationRecipients">
188 <allow
189 interface="canonical.launchpad.interfaces.INotificationRecipientSet"/>
190 </class>
191
192=== modified file 'lib/lp/bugs/doc/bug-change.txt'
193--- lib/lp/bugs/doc/bug-change.txt 2009-06-12 16:36:02 +0000
194+++ lib/lp/bugs/doc/bug-change.txt 2010-06-16 08:38:25 +0000
195@@ -54,7 +54,7 @@
196
197 We'll create a test class that actually implements the methods we need.
198
199- >>> from canonical.launchpad.mailnotification import (
200+ >>> from lp.bugs.mail.bugnotificationrecipients import (
201 ... BugNotificationRecipients)
202
203 >>> example_message = factory.makeMessage(content="Hello, world")
204
205=== modified file 'lib/lp/bugs/doc/bugnotificationrecipients.txt'
206--- lib/lp/bugs/doc/bugnotificationrecipients.txt 2009-08-13 19:03:36 +0000
207+++ lib/lp/bugs/doc/bugnotificationrecipients.txt 2010-06-16 08:38:25 +0000
208@@ -63,7 +63,7 @@
209 Let's up some data for our test:
210
211 >>> from canonical.launchpad.interfaces import IPersonSet
212- >>> from canonical.launchpad.mailnotification import (
213+ >>> from lp.bugs.mail.bugnotificationrecipients import (
214 ... BugNotificationRecipients)
215 >>> debian = Distribution.selectOneBy(name="debian")
216 >>> pmount = debian.getSourcePackage("pmount")
217
218=== added file 'lib/lp/bugs/mail/bugnotificationrecipients.py'
219--- lib/lp/bugs/mail/bugnotificationrecipients.py 1970-01-01 00:00:00 +0000
220+++ lib/lp/bugs/mail/bugnotificationrecipients.py 2010-06-16 08:38:25 +0000
221@@ -0,0 +1,158 @@
222+# Copyright 2010 Canonical Ltd. This software is licensed under the
223+# GNU Affero General Public License version 3 (see the file LICENSE).
224+
225+"""Code for handling bug notification recipients in bug mail."""
226+
227+__metaclass__ = type
228+__all__ = [
229+ 'BugNotificationRecipients',
230+ ]
231+
232+from zope.interface import implements
233+
234+from canonical.launchpad.interfaces import INotificationRecipientSet
235+
236+from lp.services.mail.notificationrecipientset import (
237+ NotificationRecipientSet)
238+
239+
240+class BugNotificationRecipients(NotificationRecipientSet):
241+ """A set of emails and rationales notified for a bug change.
242+
243+ Each email address registered in a BugNotificationRecipients is
244+ associated to a string and a header that explain why the address is
245+ being emailed. For instance, if the email address is that of a
246+ distribution bug supervisor for a bug, the string and header will make
247+ that fact clear.
248+
249+ The string is meant to be rendered in the email footer. The header
250+ is meant to be used in an X-Launchpad-Message-Rationale header.
251+
252+ The first rationale registered for an email address is the one
253+ which will be used, regardless of other rationales being added
254+ for it later. This gives us a predictable policy of preserving
255+ the first reason added to the registry; the callsite should
256+ ensure that the manipulation of the BugNotificationRecipients
257+ instance is done in preferential order.
258+
259+ Instances of this class are meant to be returned by
260+ IBug.getBugNotificationRecipients().
261+ """
262+ implements(INotificationRecipientSet)
263+
264+ def __init__(self, duplicateof=None):
265+ """Constructs a new BugNotificationRecipients instance.
266+
267+ If this bug is a duplicate, duplicateof should be used to
268+ specify which bug ID it is a duplicate of.
269+
270+ Note that there are two duplicate situations that are
271+ important:
272+ - One is when this bug is a duplicate of another bug:
273+ the subscribers to the main bug get notified of our
274+ changes.
275+ - Another is when the bug we are changing has
276+ duplicates; in that case, direct subscribers of
277+ duplicate bugs get notified of our changes.
278+ These two situations are catered respectively by the
279+ duplicateof parameter above and the addDupeSubscriber method.
280+ Don't confuse them!
281+ """
282+ NotificationRecipientSet.__init__(self)
283+ self.duplicateof = duplicateof
284+
285+ def _addReason(self, person, reason, header):
286+ """Adds a reason (text and header) for a person.
287+
288+ It takes care of modifying the message when the person is notified
289+ via a duplicate.
290+ """
291+ if self.duplicateof is not None:
292+ reason = reason + " (via bug %s)" % self.duplicateof.id
293+ header = header + " via Bug %s" % self.duplicateof.id
294+ reason = "You received this bug notification because you %s." % reason
295+ self.add(person, reason, header)
296+
297+ def addDupeSubscriber(self, person):
298+ """Registers a subscriber of a duplicate of this bug."""
299+ reason = "Subscriber of Duplicate"
300+ if person.isTeam():
301+ text = ("are a member of %s, which is a subscriber "
302+ "of a duplicate bug" % person.displayname)
303+ reason += " @%s" % person.name
304+ else:
305+ text = "are a direct subscriber of a duplicate bug"
306+ self._addReason(person, text, reason)
307+
308+ def addDirectSubscriber(self, person):
309+ """Registers a direct subscriber of this bug."""
310+ reason = "Subscriber"
311+ if person.isTeam():
312+ text = ("are a member of %s, which is a direct subscriber"
313+ % person.displayname)
314+ reason += " @%s" % person.name
315+ else:
316+ text = "are a direct subscriber of the bug"
317+ self._addReason(person, text, reason)
318+
319+ def addAssignee(self, person):
320+ """Registers an assignee of a bugtask of this bug."""
321+ reason = "Assignee"
322+ if person.isTeam():
323+ text = ("are a member of %s, which is a bug assignee"
324+ % person.displayname)
325+ reason += " @%s" % person.name
326+ else:
327+ text = "are a bug assignee"
328+ self._addReason(person, text, reason)
329+
330+ def addDistroBugSupervisor(self, person, distro):
331+ """Registers a distribution bug supervisor for this bug."""
332+ reason = "Bug Supervisor (%s)" % distro.displayname
333+ # All displaynames in these reasons should be changed to bugtargetname
334+ # (as part of bug 113262) once bugtargetname is finalized for packages
335+ # (bug 113258). Changing it before then would be excessively
336+ # disruptive.
337+ if person.isTeam():
338+ text = ("are a member of %s, which is the bug supervisor for %s" %
339+ (person.displayname, distro.displayname))
340+ reason += " @%s" % person.name
341+ else:
342+ text = "are the bug supervisor for %s" % distro.displayname
343+ self._addReason(person, text, reason)
344+
345+ def addStructuralSubscriber(self, person, target):
346+ """Registers a structural subscriber to this bug's target."""
347+ reason = "Subscriber (%s)" % target.displayname
348+ if person.isTeam():
349+ text = ("are a member of %s, which is subscribed to %s" %
350+ (person.displayname, target.displayname))
351+ reason += " @%s" % person.name
352+ else:
353+ text = "are subscribed to %s" % target.displayname
354+ self._addReason(person, text, reason)
355+
356+ def addUpstreamBugSupervisor(self, person, upstream):
357+ """Registers an upstream bug supervisor for this bug."""
358+ reason = "Bug Supervisor (%s)" % upstream.displayname
359+ if person.isTeam():
360+ text = ("are a member of %s, which is the bug supervisor for %s" %
361+ (person.displayname, upstream.displayname))
362+ reason += " @%s" % person.name
363+ else:
364+ text = "are the bug supervisor for %s" % upstream.displayname
365+ self._addReason(person, text, reason)
366+
367+ def addRegistrant(self, person, upstream):
368+ """Registers an upstream product registrant for this bug."""
369+ reason = "Registrant (%s)" % upstream.displayname
370+ if person.isTeam():
371+ text = ("are a member of %s, which is the registrant for %s" %
372+ (person.displayname, upstream.displayname))
373+ reason += " @%s" % person.name
374+ else:
375+ text = "are the registrant for %s" % upstream.displayname
376+ self._addReason(person, text, reason)
377+
378+
379+
380
381=== modified file 'lib/lp/bugs/model/bug.py'
382--- lib/lp/bugs/model/bug.py 2010-06-10 18:55:22 +0000
383+++ lib/lp/bugs/model/bug.py 2010-06-16 08:38:25 +0000
384@@ -58,7 +58,7 @@
385 IMessage, IndexedMessage)
386 from lp.registry.interfaces.structuralsubscription import (
387 BugNotificationLevel, IStructuralSubscriptionTarget)
388-from canonical.launchpad.mailnotification import BugNotificationRecipients
389+from lp.bugs.mail.bugnotificationrecipients import BugNotificationRecipients
390 from canonical.launchpad.validators import LaunchpadValidationError
391 from canonical.launchpad.webapp.interfaces import (
392 IStoreSelector, DEFAULT_FLAVOR, MAIN_STORE, NotFoundError)
393
394=== modified file 'lib/lp/bugs/scripts/tests/test_bugnotification.py'
395--- lib/lp/bugs/scripts/tests/test_bugnotification.py 2010-01-12 21:12:12 +0000
396+++ lib/lp/bugs/scripts/tests/test_bugnotification.py 2010-06-16 08:38:25 +0000
397@@ -21,7 +21,7 @@
398 from lp.bugs.interfaces.bug import IBug, IBugSet
399 from lp.registry.interfaces.person import IPersonSet
400 from lp.registry.interfaces.product import IProductSet
401-from canonical.launchpad.mailnotification import BugNotificationRecipients
402+from lp.bugs.mail.bugnotificationrecipients import BugNotificationRecipients
403 from lp.bugs.scripts.bugnotification import (
404 get_email_notifications)
405 from canonical.testing import LaunchpadZopelessLayer
406
407=== modified file 'lib/lp/registry/doc/structural-subscriptions.txt'
408--- lib/lp/registry/doc/structural-subscriptions.txt 2010-02-20 14:16:37 +0000
409+++ lib/lp/registry/doc/structural-subscriptions.txt 2010-06-16 08:38:25 +0000
410@@ -86,7 +86,7 @@
411 >>> from canonical.launchpad.ftests import syncUpdate
412 >>> from canonical.launchpad.interfaces import (
413 ... BlueprintNotificationLevel, BugNotificationLevel)
414- >>> from canonical.launchpad.mailnotification import (
415+ >>> from lp.bugs.mail.bugnotificationrecipients import (
416 ... BugNotificationRecipients)
417
418 We define some utility functions for printing out bug subscriptions and