Merge lp:~allenap/launchpad/refactor-mailnotification into lp:launchpad

Proposed by Gavin Panella
Status: Merged
Approved by: Aaron Bentley
Approved revision: no longer in the source branch.
Merged at revision: 11417
Proposed branch: lp:~allenap/launchpad/refactor-mailnotification
Merge into: lp:launchpad
Prerequisite: lp:~allenap/launchpad/refactor-get-email-notifications
Diff against target: 1160 lines (+465/-471)
11 files modified
lib/canonical/launchpad/emailtemplates/notify-unhandled-email.txt (+0/-7)
lib/canonical/launchpad/mailnotification.py (+4/-434)
lib/canonical/launchpad/subscribers/karma.py (+3/-1)
lib/lp/bugs/configure.zcml (+6/-9)
lib/lp/bugs/doc/bugnotification-email.txt (+3/-6)
lib/lp/bugs/doc/bugsubscription.txt (+2/-4)
lib/lp/bugs/mail/newbug.py (+95/-0)
lib/lp/bugs/scripts/bugnotification.py (+2/-4)
lib/lp/bugs/subscribers/bug.py (+268/-5)
lib/lp/bugs/subscribers/bugcreation.py (+3/-1)
lib/lp/bugs/subscribers/bugtask.py (+79/-0)
To merge this branch: bzr merge lp:~allenap/launchpad/refactor-mailnotification
Reviewer Review Type Date Requested Status
Aaron Bentley (community) Approve
Review via email: mp+32923@code.launchpad.net

Commit message

Move most of the remaining bugs related code out of c.l.mailnotification and into the lp.bugs hierarchy, and remove a few unused functions.

Description of the change

Moves almost all of the remaining bug related code out of c/l/mailnotification.py. Also removes a few unused functions.

To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) wrote :

This is almost entirely just moving code; I have avoided making changes to the logic.

Revision history for this message
Aaron Bentley (abentley) wrote :

Please fix the copyright date on newbug.py. Otherwise, this looks fine.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== removed file 'lib/canonical/launchpad/emailtemplates/notify-unhandled-email.txt'
--- lib/canonical/launchpad/emailtemplates/notify-unhandled-email.txt 2005-10-31 18:29:12 +0000
+++ lib/canonical/launchpad/emailtemplates/notify-unhandled-email.txt 1970-01-01 00:00:00 +0000
@@ -1,7 +0,0 @@
1The following email was unhandled:
2
3%(url)s
4
5Error message:
6
7%(error_msg)s
80
=== modified file 'lib/canonical/launchpad/mailnotification.py'
--- lib/canonical/launchpad/mailnotification.py 2010-08-20 20:31:18 +0000
+++ lib/canonical/launchpad/mailnotification.py 2010-08-23 20:18:06 +0000
@@ -8,7 +8,6 @@
88
9__metaclass__ = type9__metaclass__ = type
1010
11import datetime
12from difflib import unified_diff11from difflib import unified_diff
13from email.Header import Header12from email.Header import Header
14from email.MIMEMessage import MIMEMessage13from email.MIMEMessage import MIMEMessage
@@ -18,7 +17,6 @@
18 formataddr,17 formataddr,
19 make_msgid,18 make_msgid,
20 )19 )
21import operator
22import re20import re
2321
24from zope.component import (22from zope.component import (
@@ -37,9 +35,7 @@
37 IPerson,35 IPerson,
38 IPersonSet,36 IPersonSet,
39 ISpecification,37 ISpecification,
40 IStructuralSubscriptionTarget,
41 ITeamMembershipSet,38 ITeamMembershipSet,
42 IUpstreamBugTask,
43 TeamMembershipStatus,39 TeamMembershipStatus,
44 )40 )
45from canonical.launchpad.interfaces.launchpad import ILaunchpadRoot41from canonical.launchpad.interfaces.launchpad import ILaunchpadRoot
@@ -55,102 +51,20 @@
55 )51 )
56from canonical.launchpad.webapp.publisher import canonical_url52from canonical.launchpad.webapp.publisher import canonical_url
57from canonical.launchpad.webapp.url import urlappend53from canonical.launchpad.webapp.url import urlappend
58from lp.bugs.adapters.bugchange import (54from lp.bugs.mail.bugnotificationbuilder import get_bugmail_error_address
59 BugDuplicateChange,
60 BugTaskAssigneeChange,
61 get_bug_changes,
62 )
63from lp.bugs.adapters.bugdelta import BugDelta
64from lp.bugs.interfaces.bugchange import IBugChange
65from lp.bugs.mail.bugnotificationbuilder import (
66 BugNotificationBuilder,
67 get_bugmail_error_address,
68 )
69from lp.bugs.mail.bugnotificationrecipients import BugNotificationRecipients
70from lp.registry.enum import BugNotificationLevel
71from lp.services.mail.mailwrapper import MailWrapper55from lp.services.mail.mailwrapper import MailWrapper
72# XXX 2010-06-16 gmb bug=59498556# XXX 2010-06-16 gmb bug=594985
73# This shouldn't be here, but if we take it out lots of things cry,57# This shouldn't be here, but if we take it out lots of things cry,
74# which is sad.58# which is sad.
75from lp.services.mail.notificationrecipientset import NotificationRecipientSet59from lp.services.mail.notificationrecipientset import NotificationRecipientSet
7660
61# Silence lint warnings.
62NotificationRecipientSet
63
7764
78CC = "CC"65CC = "CC"
7966
8067
81def _send_bug_details_to_new_bug_subscribers(
82 bug, previous_subscribers, current_subscribers, subscribed_by=None,
83 event_creator=None):
84 """Send an email containing full bug details to new bug subscribers.
85
86 This function is designed to handle situations where bugtasks get
87 reassigned to new products or sourcepackages, and the new bug subscribers
88 need to be notified of the bug.
89 """
90 prev_subs_set = set(previous_subscribers)
91 cur_subs_set = set(current_subscribers)
92 new_subs = cur_subs_set.difference(prev_subs_set)
93
94 to_addrs = set()
95 for new_sub in new_subs:
96 to_addrs.update(get_contact_email_addresses(new_sub))
97
98 if not to_addrs:
99 return
100
101 from_addr = format_address(
102 'Launchpad Bug Tracker',
103 "%s@%s" % (bug.id, config.launchpad.bugs_domain))
104 # Now's a good a time as any for this email; don't use the original
105 # reported date for the bug as it will just confuse mailer and
106 # recipient.
107 email_date = datetime.datetime.now()
108
109 # The new subscriber email is effectively the initial message regarding
110 # a new bug. The bug's initial message is used in the References
111 # header to establish the message's context in the email client.
112 references = [bug.initial_message.rfc822msgid]
113 recipients = bug.getBugNotificationRecipients()
114
115 bug_notification_builder = BugNotificationBuilder(bug, event_creator)
116 for to_addr in sorted(to_addrs):
117 reason, rationale = recipients.getReason(to_addr)
118 subject, contents = generate_bug_add_email(
119 bug, new_recipients=True, subscribed_by=subscribed_by,
120 reason=reason, event_creator=event_creator)
121 msg = bug_notification_builder.build(
122 from_addr, to_addr, contents, subject, email_date,
123 rationale=rationale, references=references)
124 sendmail(msg)
125
126
127@block_implicit_flushes
128def update_security_contact_subscriptions(modified_bugtask, event):
129 """Subscribe the new security contact when a bugtask's product changes.
130
131 Only subscribes the new security contact if the bug was marked a
132 security issue originally.
133
134 No change is made for private bugs.
135 """
136 if event.object.bug.private:
137 return
138
139 if not IUpstreamBugTask.providedBy(event.object):
140 return
141
142 bugtask_before_modification = event.object_before_modification
143 bugtask_after_modification = event.object
144
145 if (bugtask_before_modification.product !=
146 bugtask_after_modification.product):
147 new_product = bugtask_after_modification.product
148 if (bugtask_before_modification.bug.security_related and
149 new_product.security_contact):
150 bugtask_after_modification.bug.subscribe(
151 new_product.security_contact, IPerson(event.user))
152
153
154def send_process_error_notification(to_address, subject, error_msg,68def send_process_error_notification(to_address, subject, error_msg,
155 original_msg, failing_command=None):69 original_msg, failing_command=None):
156 """Send a mail about an error occurring while using the email interface.70 """Send a mail about an error occurring while using the email interface.
@@ -193,102 +107,6 @@
193 sendmail(msg)107 sendmail(msg)
194108
195109
196def notify_errors_list(message, file_alias_url):
197 """Sends an error to the Launchpad errors list."""
198 template = get_email_template('notify-unhandled-email.txt')
199 # We add the error message in as a header too
200 # (X-Launchpad-Unhandled-Email) so we can create filters in the
201 # Launchpad-Error-Reports Mailman mailing list.
202 simple_sendmail(
203 get_bugmail_error_address(), [config.launchpad.errors_address],
204 'Unhandled Email: %s' % file_alias_url,
205 template % {'url': file_alias_url, 'error_msg': message},
206 headers={'X-Launchpad-Unhandled-Email': message})
207
208
209def generate_bug_add_email(bug, new_recipients=False, reason=None,
210 subscribed_by=None, event_creator=None):
211 """Generate a new bug notification from the given IBug.
212
213 If new_recipients is supplied we generate a notification explaining
214 that the new recipients have been subscribed to the bug. Otherwise
215 it's just a notification of a new bug report.
216 """
217 subject = u"[Bug %d] [NEW] %s" % (bug.id, bug.title)
218 contents = ''
219
220 if bug.private:
221 # This is a confidential bug.
222 visibility = u"Private"
223 else:
224 # This is a public bug.
225 visibility = u"Public"
226
227 if bug.security_related:
228 visibility += ' security'
229 contents += '*** This bug is a security vulnerability ***\n\n'
230
231 bug_info = []
232 # Add information about the affected upstreams and packages.
233 for bugtask in bug.bugtasks:
234 bug_info.append(u"** Affects: %s" % bugtask.bugtargetname)
235 bug_info.append(u" Importance: %s" % bugtask.importance.title)
236
237 if bugtask.assignee:
238 # There's a person assigned to fix this task, so show that
239 # information too.
240 bug_info.append(
241 u" Assignee: %s" % bugtask.assignee.unique_displayname)
242 bug_info.append(u" Status: %s\n" % bugtask.status.title)
243
244 if bug.tags:
245 bug_info.append('\n** Tags: %s' % ' '.join(bug.tags))
246
247 mailwrapper = MailWrapper(width=72)
248 content_substitutions = {
249 'visibility': visibility,
250 'bug_url': canonical_url(bug),
251 'bug_info': "\n".join(bug_info),
252 'bug_title': bug.title,
253 'description': mailwrapper.format(bug.description),
254 'notification_rationale': reason,
255 }
256
257 if new_recipients:
258 if "assignee" in reason:
259 contents += (
260 "You have been assigned a bug task for a %(visibility)s bug")
261 if event_creator is not None:
262 contents += " by %(assigner)s"
263 content_substitutions['assigner'] = (
264 event_creator.unique_displayname)
265 else:
266 contents += "You have been subscribed to a %(visibility)s bug"
267 if subscribed_by is not None:
268 contents += " by %(subscribed_by)s"
269 content_substitutions['subscribed_by'] = (
270 subscribed_by.unique_displayname)
271 contents += (":\n\n"
272 "%(description)s\n\n%(bug_info)s")
273 # The visibility appears mid-phrase so.. hack hack.
274 content_substitutions['visibility'] = visibility.lower()
275 # XXX: kiko, 2007-03-21:
276 # We should really have a centralized way of adding this
277 # footer, but right now we lack a INotificationRecipientSet
278 # for this particular situation.
279 contents += (
280 "\n-- \n%(bug_title)s\n%(bug_url)s\n%(notification_rationale)s")
281 else:
282 contents += ("%(visibility)s bug reported:\n\n"
283 "%(description)s\n\n%(bug_info)s")
284
285 contents = contents % content_substitutions
286
287 contents = contents.rstrip()
288
289 return (subject, contents)
290
291
292def get_unified_diff(old_text, new_text, text_width):110def get_unified_diff(old_text, new_text, text_width):
293 r"""Return a unified diff of the two texts.111 r"""Return a unified diff of the two texts.
294112
@@ -329,254 +147,6 @@
329 return text_diff147 return text_diff
330148
331149
332def _get_task_change_row(label, oldval_display, newval_display):
333 """Return a row formatted for display in task change info."""
334 return u"%(label)13s: %(oldval)s => %(newval)s\n" % {
335 'label': label.capitalize(),
336 'oldval': oldval_display,
337 'newval': newval_display}
338
339
340def _get_task_change_values(task_change, displayattrname):
341 """Return the old value and the new value for a task field change."""
342 oldval = task_change.get('old')
343 newval = task_change.get('new')
344
345 oldval_display = None
346 newval_display = None
347
348 if oldval:
349 oldval_display = getattr(oldval, displayattrname)
350 if newval:
351 newval_display = getattr(newval, displayattrname)
352
353 return (oldval_display, newval_display)
354
355
356def get_bug_delta(old_bug, new_bug, user):
357 """Compute the delta from old_bug to new_bug.
358
359 old_bug and new_bug are IBug's. user is an IPerson. Returns an
360 IBugDelta if there are changes, or None if there were no changes.
361 """
362 changes = {}
363
364 for field_name in ("title", "description", "name", "private",
365 "security_related", "duplicateof", "tags"):
366 # fields for which we show old => new when their values change
367 old_val = getattr(old_bug, field_name)
368 new_val = getattr(new_bug, field_name)
369 if old_val != new_val:
370 changes[field_name] = {}
371 changes[field_name]["old"] = old_val
372 changes[field_name]["new"] = new_val
373
374 if changes:
375 changes["bug"] = new_bug
376 changes["bug_before_modification"] = old_bug
377 changes["bugurl"] = canonical_url(new_bug)
378 changes["user"] = user
379
380 return BugDelta(**changes)
381 else:
382 return None
383
384
385@block_implicit_flushes
386def notify_bug_added(bug, event):
387 """Send an email notification that a bug was added.
388
389 Event must be an IObjectCreatedEvent.
390 """
391
392 bug.addCommentNotification(bug.initial_message)
393
394
395@block_implicit_flushes
396def notify_bug_modified(modified_bug, event):
397 """Notify the Cc'd list that this bug has been modified.
398
399 modified_bug bug must be an IBug. event must be an
400 IObjectModifiedEvent.
401 """
402 bug_delta = get_bug_delta(
403 old_bug=event.object_before_modification,
404 new_bug=event.object, user=IPerson(event.user))
405
406 if bug_delta is not None:
407 add_bug_change_notifications(bug_delta)
408
409
410def get_bugtask_indirect_subscribers(bugtask, recipients=None, level=None):
411 """Return the indirect subscribers for a bug task.
412
413 Return the list of people who should get notifications about
414 changes to the task because of having an indirect subscription
415 relationship with it (by subscribing to its target, being an
416 assignee or owner, etc...)
417
418 If `recipients` is present, add the subscribers to the set of
419 bug notification recipients.
420 """
421 if bugtask.bug.private:
422 return set()
423
424 also_notified_subscribers = set()
425
426 # Assignees are indirect subscribers.
427 if bugtask.assignee:
428 also_notified_subscribers.add(bugtask.assignee)
429 if recipients is not None:
430 recipients.addAssignee(bugtask.assignee)
431
432 if IStructuralSubscriptionTarget.providedBy(bugtask.target):
433 also_notified_subscribers.update(
434 bugtask.target.getBugNotificationsRecipients(
435 recipients, level=level))
436
437 if bugtask.milestone is not None:
438 also_notified_subscribers.update(
439 bugtask.milestone.getBugNotificationsRecipients(
440 recipients, level=level))
441
442 # If the target's bug supervisor isn't set,
443 # we add the owner as a subscriber.
444 pillar = bugtask.pillar
445 if pillar.bug_supervisor is None:
446 also_notified_subscribers.add(pillar.owner)
447 if recipients is not None:
448 recipients.addRegistrant(pillar.owner, pillar)
449
450 return sorted(
451 also_notified_subscribers,
452 key=operator.attrgetter('displayname'))
453
454
455def add_bug_change_notifications(bug_delta, old_bugtask=None,
456 new_subscribers=None):
457 """Generate bug notifications and add them to the bug."""
458 changes = get_bug_changes(bug_delta)
459 recipients = bug_delta.bug.getBugNotificationRecipients(
460 old_bug=bug_delta.bug_before_modification,
461 level=BugNotificationLevel.METADATA)
462 if old_bugtask is not None:
463 old_bugtask_recipients = BugNotificationRecipients()
464 get_bugtask_indirect_subscribers(
465 old_bugtask, recipients=old_bugtask_recipients,
466 level=BugNotificationLevel.METADATA)
467 recipients.update(old_bugtask_recipients)
468 for change in changes:
469 # XXX 2009-03-17 gmb [bug=344125]
470 # This if..else should be removed once the new BugChange API
471 # is complete and ubiquitous.
472 if IBugChange.providedBy(change):
473 if isinstance(change, BugDuplicateChange):
474 no_dupe_master_recipients = (
475 bug_delta.bug.getBugNotificationRecipients(
476 old_bug=bug_delta.bug_before_modification,
477 level=BugNotificationLevel.METADATA,
478 include_master_dupe_subscribers=False))
479 bug_delta.bug.addChange(
480 change, recipients=no_dupe_master_recipients)
481 elif (isinstance(change, BugTaskAssigneeChange) and
482 new_subscribers is not None):
483 for person in new_subscribers:
484 reason, rationale = recipients.getReason(person)
485 if 'Assignee' in rationale:
486 recipients.remove(person)
487 bug_delta.bug.addChange(change, recipients=recipients)
488 else:
489 bug_delta.bug.addChange(change, recipients=recipients)
490 else:
491 bug_delta.bug.addChangeNotification(
492 change, person=bug_delta.user, recipients=recipients)
493
494
495@block_implicit_flushes
496def notify_bugtask_edited(modified_bugtask, event):
497 """Notify CC'd subscribers of this bug that something has changed
498 on this task.
499
500 modified_bugtask must be an IBugTask. event must be an
501 IObjectModifiedEvent.
502 """
503 bugtask_delta = event.object.getDelta(event.object_before_modification)
504 bug_delta = BugDelta(
505 bug=event.object.bug,
506 bugurl=canonical_url(event.object.bug),
507 bugtask_deltas=bugtask_delta,
508 user=IPerson(event.user))
509
510 event_creator = IPerson(event.user)
511 previous_subscribers = event.object_before_modification.bug_subscribers
512 current_subscribers = event.object.bug_subscribers
513 prev_subs_set = set(previous_subscribers)
514 cur_subs_set = set(current_subscribers)
515 new_subs = cur_subs_set.difference(prev_subs_set)
516
517 add_bug_change_notifications(
518 bug_delta, old_bugtask=event.object_before_modification,
519 new_subscribers=new_subs)
520
521 _send_bug_details_to_new_bug_subscribers(
522 event.object.bug, previous_subscribers, current_subscribers,
523 event_creator=event_creator)
524 update_security_contact_subscriptions(modified_bugtask, event)
525
526
527@block_implicit_flushes
528def notify_bug_comment_added(bugmessage, event):
529 """Notify CC'd list that a message was added to this bug.
530
531 bugmessage must be an IBugMessage. event must be an
532 IObjectCreatedEvent. If bugmessage.bug is a duplicate the
533 comment will also be sent to the dup target's subscribers.
534 """
535 bug = bugmessage.bug
536 bug.addCommentNotification(bugmessage.message)
537
538
539@block_implicit_flushes
540def notify_bug_attachment_added(bugattachment, event):
541 """Notify CC'd list that a new attachment has been added.
542
543 bugattachment must be an IBugAttachment. event must be an
544 IObjectCreatedEvent.
545 """
546 bug = bugattachment.bug
547 bug_delta = BugDelta(
548 bug=bug,
549 bugurl=canonical_url(bug),
550 user=IPerson(event.user),
551 attachment={'new': bugattachment, 'old': None})
552
553 add_bug_change_notifications(bug_delta)
554
555
556@block_implicit_flushes
557def notify_bug_attachment_removed(bugattachment, event):
558 """Notify that an attachment has been removed."""
559 bug = bugattachment.bug
560 bug_delta = BugDelta(
561 bug=bug,
562 bugurl=canonical_url(bug),
563 user=IPerson(event.user),
564 attachment={'old': bugattachment, 'new': None})
565
566 add_bug_change_notifications(bug_delta)
567
568
569@block_implicit_flushes
570def notify_bug_subscription_added(bug_subscription, event):
571 """Notify that a new bug subscription was added."""
572 # When a user is subscribed to a bug by someone other
573 # than themselves, we send them a notification email.
574 if bug_subscription.person != bug_subscription.subscribed_by:
575 _send_bug_details_to_new_bug_subscribers(
576 bug_subscription.bug, [], [bug_subscription.person],
577 subscribed_by=bug_subscription.subscribed_by)
578
579
580@block_implicit_flushes150@block_implicit_flushes
581def notify_invitation_to_join_team(event):151def notify_invitation_to_join_team(event):
582 """Notify team admins that the team has been invited to join another team.152 """Notify team admins that the team has been invited to join another team.
583153
=== modified file 'lib/canonical/launchpad/subscribers/karma.py'
--- lib/canonical/launchpad/subscribers/karma.py 2010-08-20 20:31:18 +0000
+++ lib/canonical/launchpad/subscribers/karma.py 2010-08-23 20:18:06 +0000
@@ -6,7 +6,7 @@
66
7from canonical.database.sqlbase import block_implicit_flushes7from canonical.database.sqlbase import block_implicit_flushes
8from canonical.launchpad.interfaces import BugTaskStatus8from canonical.launchpad.interfaces import BugTaskStatus
9from canonical.launchpad.mailnotification import get_bug_delta9from lp.bugs.subscribers.bug import get_bug_delta
10from lp.code.enums import BranchMergeProposalStatus10from lp.code.enums import BranchMergeProposalStatus
11from lp.registry.interfaces.person import IPerson11from lp.registry.interfaces.person import IPerson
1212
@@ -18,6 +18,7 @@
18 assert len(bug.bugtasks) >= 118 assert len(bug.bugtasks) >= 1
19 _assignKarmaUsingBugContext(IPerson(event.user), bug, 'bugcreated')19 _assignKarmaUsingBugContext(IPerson(event.user), bug, 'bugcreated')
2020
21
21def _assign_karma_using_bugtask_context(person, bugtask, actionname):22def _assign_karma_using_bugtask_context(person, bugtask, actionname):
22 """Extract the right context from the bugtask and assign karma."""23 """Extract the right context from the bugtask and assign karma."""
23 distribution = bugtask.distribution24 distribution = bugtask.distribution
@@ -157,6 +158,7 @@
157 """Assign karma to the user who registered the branch."""158 """Assign karma to the user who registered the branch."""
158 branch.target.assignKarma(branch.registrant, 'branchcreated')159 branch.target.assignKarma(branch.registrant, 'branchcreated')
159160
161
160@block_implicit_flushes162@block_implicit_flushes
161def bug_branch_created(bug_branch, event):163def bug_branch_created(bug_branch, event):
162 """Assign karma to the user who linked the bug to the branch."""164 """Assign karma to the user who linked the bug to the branch."""
163165
=== modified file 'lib/lp/bugs/configure.zcml'
--- lib/lp/bugs/configure.zcml 2010-08-19 03:06:27 +0000
+++ lib/lp/bugs/configure.zcml 2010-08-23 20:18:06 +0000
@@ -55,25 +55,22 @@
55 handler="lp.bugs.subscribers.bugcreation.at_least_one_task"/>55 handler="lp.bugs.subscribers.bugcreation.at_least_one_task"/>
56 <subscriber56 <subscriber
57 for="canonical.launchpad.interfaces.IBug lazr.lifecycle.interfaces.IObjectCreatedEvent"57 for="canonical.launchpad.interfaces.IBug lazr.lifecycle.interfaces.IObjectCreatedEvent"
58 handler="canonical.launchpad.mailnotification.notify_bug_added"/>58 handler="lp.bugs.subscribers.bug.notify_bug_added"/>
59 <subscriber59 <subscriber
60 for="canonical.launchpad.interfaces.IBug lazr.lifecycle.interfaces.IObjectCreatedEvent"60 for="canonical.launchpad.interfaces.IBug lazr.lifecycle.interfaces.IObjectCreatedEvent"
61 handler="canonical.launchpad.subscribers.karma.bug_created"/>61 handler="canonical.launchpad.subscribers.karma.bug_created"/>
62 <subscriber62 <subscriber
63 for="canonical.launchpad.interfaces.IBug lazr.lifecycle.interfaces.IObjectModifiedEvent"63 for="canonical.launchpad.interfaces.IBug lazr.lifecycle.interfaces.IObjectModifiedEvent"
64 handler="canonical.launchpad.mailnotification.notify_bug_modified"/>
65 <subscriber
66 for="canonical.launchpad.interfaces.IBug lazr.lifecycle.interfaces.IObjectModifiedEvent"
67 handler="canonical.launchpad.subscribers.karma.bug_modified"/>64 handler="canonical.launchpad.subscribers.karma.bug_modified"/>
68 <subscriber65 <subscriber
69 for="canonical.launchpad.interfaces.IBug lazr.lifecycle.interfaces.IObjectModifiedEvent"66 for="canonical.launchpad.interfaces.IBug lazr.lifecycle.interfaces.IObjectModifiedEvent"
70 handler="lp.bugs.subscribers.buglastupdated.update_bug_date_last_updated"/>67 handler="lp.bugs.subscribers.buglastupdated.update_bug_date_last_updated"/>
71 <subscriber68 <subscriber
72 for="canonical.launchpad.interfaces.IBugAttachment lazr.lifecycle.interfaces.IObjectCreatedEvent"69 for="canonical.launchpad.interfaces.IBugAttachment lazr.lifecycle.interfaces.IObjectCreatedEvent"
73 handler="canonical.launchpad.mailnotification.notify_bug_attachment_added"/>70 handler="lp.bugs.subscribers.bug.notify_bug_attachment_added"/>
74 <subscriber71 <subscriber
75 for="canonical.launchpad.interfaces.IBugAttachment lazr.lifecycle.interfaces.IObjectDeletedEvent"72 for="canonical.launchpad.interfaces.IBugAttachment lazr.lifecycle.interfaces.IObjectDeletedEvent"
76 handler="canonical.launchpad.mailnotification.notify_bug_attachment_removed"/>73 handler="lp.bugs.subscribers.bug.notify_bug_attachment_removed"/>
77 <subscriber74 <subscriber
78 for="canonical.launchpad.interfaces.IBugAttachment lazr.lifecycle.interfaces.IObjectCreatedEvent"75 for="canonical.launchpad.interfaces.IBugAttachment lazr.lifecycle.interfaces.IObjectCreatedEvent"
79 handler="lp.bugs.subscribers.buglastupdated.update_bug_date_last_updated"/>76 handler="lp.bugs.subscribers.buglastupdated.update_bug_date_last_updated"/>
@@ -94,7 +91,7 @@
94 handler="canonical.launchpad.subscribers.karma.cve_added"/>91 handler="canonical.launchpad.subscribers.karma.cve_added"/>
95 <subscriber92 <subscriber
96 for="canonical.launchpad.interfaces.IBugMessage lazr.lifecycle.interfaces.IObjectCreatedEvent"93 for="canonical.launchpad.interfaces.IBugMessage lazr.lifecycle.interfaces.IObjectCreatedEvent"
97 handler="canonical.launchpad.mailnotification.notify_bug_comment_added"/>94 handler="lp.bugs.subscribers.bug.notify_bug_comment_added"/>
98 <subscriber95 <subscriber
99 for="canonical.launchpad.interfaces.IBugMessage lazr.lifecycle.interfaces.IObjectCreatedEvent"96 for="canonical.launchpad.interfaces.IBugMessage lazr.lifecycle.interfaces.IObjectCreatedEvent"
100 handler="canonical.launchpad.subscribers.karma.bug_comment_added"/>97 handler="canonical.launchpad.subscribers.karma.bug_comment_added"/>
@@ -115,7 +112,7 @@
115 handler="lp.bugs.subscribers.buglastupdated.update_bug_date_last_updated"/>112 handler="lp.bugs.subscribers.buglastupdated.update_bug_date_last_updated"/>
116 <subscriber113 <subscriber
117 for="canonical.launchpad.interfaces.IBugSubscription lazr.lifecycle.interfaces.IObjectCreatedEvent"114 for="canonical.launchpad.interfaces.IBugSubscription lazr.lifecycle.interfaces.IObjectCreatedEvent"
118 handler="canonical.launchpad.mailnotification.notify_bug_subscription_added"/>115 handler="lp.bugs.subscribers.bug.notify_bug_subscription_added"/>
119 <subscriber116 <subscriber
120 for="canonical.launchpad.interfaces.IBug lazr.lifecycle.interfaces.IObjectModifiedEvent"117 for="canonical.launchpad.interfaces.IBug lazr.lifecycle.interfaces.IObjectModifiedEvent"
121 handler="lp.bugs.subscribers.bug.notify_bug_modified"/>118 handler="lp.bugs.subscribers.bug.notify_bug_modified"/>
@@ -932,7 +929,7 @@
932 handler="lp.bugs.subscribers.buglastupdated.update_bug_date_last_updated"/>929 handler="lp.bugs.subscribers.buglastupdated.update_bug_date_last_updated"/>
933 <subscriber930 <subscriber
934 for="canonical.launchpad.interfaces.IBugTask lazr.lifecycle.interfaces.IObjectModifiedEvent"931 for="canonical.launchpad.interfaces.IBugTask lazr.lifecycle.interfaces.IObjectModifiedEvent"
935 handler="canonical.launchpad.mailnotification.notify_bugtask_edited"/>932 handler="lp.bugs.subscribers.bugtask.notify_bugtask_edited"/>
936 <subscriber933 <subscriber
937 for="canonical.launchpad.interfaces.IBugTask lazr.lifecycle.interfaces.IObjectModifiedEvent"934 for="canonical.launchpad.interfaces.IBugTask lazr.lifecycle.interfaces.IObjectModifiedEvent"
938 handler="canonical.launchpad.subscribers.karma.bugtask_modified"/>935 handler="canonical.launchpad.subscribers.karma.bugtask_modified"/>
939936
=== modified file 'lib/lp/bugs/doc/bugnotification-email.txt'
--- lib/lp/bugs/doc/bugnotification-email.txt 2010-08-04 09:42:07 +0000
+++ lib/lp/bugs/doc/bugnotification-email.txt 2010-08-23 20:18:06 +0000
@@ -19,10 +19,8 @@
19object it gets passed, the formatting logic has been cut into two19object it gets passed, the formatting logic has been cut into two
20pieces: get_bug_changes and generate_bug_add_email.20pieces: get_bug_changes and generate_bug_add_email.
2121
22 >>> from lp.bugs.adapters.bugchange import (22 >>> from lp.bugs.adapters.bugchange import get_bug_changes
23 ... get_bug_changes)23 >>> from lp.bugs.mail.newbug import generate_bug_add_email
24 >>> from canonical.launchpad.mailnotification import (
25 ... generate_bug_add_email)
2624
27Let's demonstrate what the bugmails will look like, by going through25Let's demonstrate what the bugmails will look like, by going through
28the various events that can happen that would cause a notification to26the various events that can happen that would cause a notification to
@@ -479,8 +477,7 @@
479mailnotification.py contains a class, BugNotificationBuilder, which is477mailnotification.py contains a class, BugNotificationBuilder, which is
480used to construct bug notification emails.478used to construct bug notification emails.
481479
482 >>> from canonical.launchpad.mailnotification import (480 >>> from lp.bugs.mail.bugnotificationbuilder import BugNotificationBuilder
483 ... BugNotificationBuilder)
484481
485When instantiatiated it derives a list of common unchanging headers482When instantiatiated it derives a list of common unchanging headers
486from the bug so that they are not calculated for every recipient.483from the bug so that they are not calculated for every recipient.
487484
=== modified file 'lib/lp/bugs/doc/bugsubscription.txt'
--- lib/lp/bugs/doc/bugsubscription.txt 2010-08-16 13:58:33 +0000
+++ lib/lp/bugs/doc/bugsubscription.txt 2010-08-23 20:18:06 +0000
@@ -101,10 +101,8 @@
101It is also possible to get the list of indirect subscribers for an101It is also possible to get the list of indirect subscribers for an
102individual bug task.102individual bug task.
103103
104 >>> from canonical.launchpad.mailnotification import (104 >>> from lp.bugs.subscribers.bug import get_bugtask_indirect_subscribers
105 ... get_bugtask_indirect_subscribers)105 >>> get_bugtask_indirect_subscribers(linux_source_bug.bugtasks[0])
106 >>> get_bugtask_indirect_subscribers(
107 ... linux_source_bug.bugtasks[0])
108 [<Person at ...>]106 [<Person at ...>]
109107
110The list of all bug subscribers can also be accessed via108The list of all bug subscribers can also be accessed via
111109
=== added file 'lib/lp/bugs/mail/newbug.py'
--- lib/lp/bugs/mail/newbug.py 1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/mail/newbug.py 2010-08-23 20:18:06 +0000
@@ -0,0 +1,95 @@
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"""Mail for new bugs."""
5
6__metaclass__ = type
7__all__ = [
8 'generate_bug_add_email',
9 ]
10
11from canonical.launchpad.webapp.publisher import canonical_url
12from lp.services.mail.mailwrapper import MailWrapper
13
14
15def generate_bug_add_email(bug, new_recipients=False, reason=None,
16 subscribed_by=None, event_creator=None):
17 """Generate a new bug notification from the given IBug.
18
19 If new_recipients is supplied we generate a notification explaining
20 that the new recipients have been subscribed to the bug. Otherwise
21 it's just a notification of a new bug report.
22 """
23 subject = u"[Bug %d] [NEW] %s" % (bug.id, bug.title)
24 contents = ''
25
26 if bug.private:
27 # This is a confidential bug.
28 visibility = u"Private"
29 else:
30 # This is a public bug.
31 visibility = u"Public"
32
33 if bug.security_related:
34 visibility += ' security'
35 contents += '*** This bug is a security vulnerability ***\n\n'
36
37 bug_info = []
38 # Add information about the affected upstreams and packages.
39 for bugtask in bug.bugtasks:
40 bug_info.append(u"** Affects: %s" % bugtask.bugtargetname)
41 bug_info.append(u" Importance: %s" % bugtask.importance.title)
42
43 if bugtask.assignee:
44 # There's a person assigned to fix this task, so show that
45 # information too.
46 bug_info.append(
47 u" Assignee: %s" % bugtask.assignee.unique_displayname)
48 bug_info.append(u" Status: %s\n" % bugtask.status.title)
49
50 if bug.tags:
51 bug_info.append('\n** Tags: %s' % ' '.join(bug.tags))
52
53 mailwrapper = MailWrapper(width=72)
54 content_substitutions = {
55 'visibility': visibility,
56 'bug_url': canonical_url(bug),
57 'bug_info': "\n".join(bug_info),
58 'bug_title': bug.title,
59 'description': mailwrapper.format(bug.description),
60 'notification_rationale': reason,
61 }
62
63 if new_recipients:
64 if "assignee" in reason:
65 contents += (
66 "You have been assigned a bug task for a %(visibility)s bug")
67 if event_creator is not None:
68 contents += " by %(assigner)s"
69 content_substitutions['assigner'] = (
70 event_creator.unique_displayname)
71 else:
72 contents += "You have been subscribed to a %(visibility)s bug"
73 if subscribed_by is not None:
74 contents += " by %(subscribed_by)s"
75 content_substitutions['subscribed_by'] = (
76 subscribed_by.unique_displayname)
77 contents += (":\n\n"
78 "%(description)s\n\n%(bug_info)s")
79 # The visibility appears mid-phrase so.. hack hack.
80 content_substitutions['visibility'] = visibility.lower()
81 # XXX: kiko, 2007-03-21:
82 # We should really have a centralized way of adding this
83 # footer, but right now we lack a INotificationRecipientSet
84 # for this particular situation.
85 contents += (
86 "\n-- \n%(bug_title)s\n%(bug_url)s\n%(notification_rationale)s")
87 else:
88 contents += ("%(visibility)s bug reported:\n\n"
89 "%(description)s\n\n%(bug_info)s")
90
91 contents = contents % content_substitutions
92
93 contents = contents.rstrip()
94
95 return (subject, contents)
096
=== modified file 'lib/lp/bugs/scripts/bugnotification.py'
--- lib/lp/bugs/scripts/bugnotification.py 2010-08-20 20:31:18 +0000
+++ lib/lp/bugs/scripts/bugnotification.py 2010-08-23 20:18:06 +0000
@@ -27,10 +27,6 @@
27 get_email_template,27 get_email_template,
28 )28 )
29from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities29from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
30from canonical.launchpad.mailnotification import (
31 generate_bug_add_email,
32 MailWrapper,
33 )
34from canonical.launchpad.scripts.logger import log30from canonical.launchpad.scripts.logger import log
35from canonical.launchpad.webapp import canonical_url31from canonical.launchpad.webapp import canonical_url
36from lp.bugs.interfaces.bugmessage import IBugMessageSet32from lp.bugs.interfaces.bugmessage import IBugMessageSet
@@ -38,7 +34,9 @@
38 BugNotificationBuilder,34 BugNotificationBuilder,
39 get_bugmail_from_address,35 get_bugmail_from_address,
40 )36 )
37from lp.bugs.mail.newbug import generate_bug_add_email
41from lp.registry.interfaces.person import IPersonSet38from lp.registry.interfaces.person import IPersonSet
39from lp.services.mail.mailwrapper import MailWrapper
4240
4341
44def construct_email_notifications(bug_notifications):42def construct_email_notifications(bug_notifications):
4543
=== modified file 'lib/lp/bugs/subscribers/bug.py'
--- lib/lp/bugs/subscribers/bug.py 2010-01-08 03:12:30 +0000
+++ lib/lp/bugs/subscribers/bug.py 2010-08-23 20:18:06 +0000
@@ -2,19 +2,63 @@
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4__metaclass__ = type4__metaclass__ = type
5__all__ = ['notify_bug_modified']5__all__ = [
66 'add_bug_change_notifications',
77 'get_bug_delta',
8 'get_bugtask_indirect_subscribers',
9 'notify_bug_added',
10 'notify_bug_attachment_added',
11 'notify_bug_attachment_removed',
12 'notify_bug_comment_added',
13 'notify_bug_modified',
14 'notify_bug_subscription_added',
15 'send_bug_details_to_new_bug_subscribers',
16 ]
17
18
19import datetime
20from operator import attrgetter
21
22from canonical.config import config
8from canonical.database.sqlbase import block_implicit_flushes23from canonical.database.sqlbase import block_implicit_flushes
24from canonical.launchpad.helpers import get_contact_email_addresses
25from canonical.launchpad.mail import (
26 format_address,
27 sendmail,
28 )
29from canonical.launchpad.webapp.publisher import canonical_url
30from lp.bugs.adapters.bugchange import (
31 BugDuplicateChange,
32 BugTaskAssigneeChange,
33 get_bug_changes,
34 )
35from lp.bugs.adapters.bugdelta import BugDelta
36from lp.bugs.interfaces.bugchange import IBugChange
37from lp.bugs.mail.bugnotificationbuilder import BugNotificationBuilder
38from lp.bugs.mail.bugnotificationrecipients import BugNotificationRecipients
39from lp.bugs.mail.newbug import generate_bug_add_email
40from lp.registry.enum import BugNotificationLevel
9from lp.registry.interfaces.person import IPerson41from lp.registry.interfaces.person import IPerson
42from lp.registry.interfaces.structuralsubscription import (
43 IStructuralSubscriptionTarget,
44 )
45
46
47@block_implicit_flushes
48def notify_bug_added(bug, event):
49 """Send an email notification that a bug was added.
50
51 Event must be an IObjectCreatedEvent.
52 """
53 bug.addCommentNotification(bug.initial_message)
1054
1155
12@block_implicit_flushes56@block_implicit_flushes
13def notify_bug_modified(bug, event):57def notify_bug_modified(bug, event):
14 """Handle bug change events.58 """Handle bug change events.
1559
16 Subscribe the security contacts for a bug when it60 Subscribe the security contacts for a bug when it becomes
17 becomes security-related.61 security-related, and add notifications for the changes.
18 """62 """
19 if (event.object.security_related and63 if (event.object.security_related and
20 not event.object_before_modification.security_related):64 not event.object_before_modification.security_related):
@@ -23,3 +67,222 @@
23 for pillar in bug.affected_pillars:67 for pillar in bug.affected_pillars:
24 if pillar.security_contact is not None:68 if pillar.security_contact is not None:
25 bug.subscribe(pillar.security_contact, IPerson(event.user))69 bug.subscribe(pillar.security_contact, IPerson(event.user))
70
71 bug_delta = get_bug_delta(
72 old_bug=event.object_before_modification,
73 new_bug=event.object, user=IPerson(event.user))
74
75 if bug_delta is not None:
76 add_bug_change_notifications(bug_delta)
77
78
79@block_implicit_flushes
80def notify_bug_comment_added(bugmessage, event):
81 """Notify CC'd list that a message was added to this bug.
82
83 bugmessage must be an IBugMessage. event must be an
84 IObjectCreatedEvent. If bugmessage.bug is a duplicate the
85 comment will also be sent to the dup target's subscribers.
86 """
87 bug = bugmessage.bug
88 bug.addCommentNotification(bugmessage.message)
89
90
91@block_implicit_flushes
92def notify_bug_attachment_added(bugattachment, event):
93 """Notify CC'd list that a new attachment has been added.
94
95 bugattachment must be an IBugAttachment. event must be an
96 IObjectCreatedEvent.
97 """
98 bug = bugattachment.bug
99 bug_delta = BugDelta(
100 bug=bug,
101 bugurl=canonical_url(bug),
102 user=IPerson(event.user),
103 attachment={'new': bugattachment, 'old': None})
104
105 add_bug_change_notifications(bug_delta)
106
107
108@block_implicit_flushes
109def notify_bug_attachment_removed(bugattachment, event):
110 """Notify that an attachment has been removed."""
111 bug = bugattachment.bug
112 bug_delta = BugDelta(
113 bug=bug,
114 bugurl=canonical_url(bug),
115 user=IPerson(event.user),
116 attachment={'old': bugattachment, 'new': None})
117
118 add_bug_change_notifications(bug_delta)
119
120
121@block_implicit_flushes
122def notify_bug_subscription_added(bug_subscription, event):
123 """Notify that a new bug subscription was added."""
124 # When a user is subscribed to a bug by someone other
125 # than themselves, we send them a notification email.
126 if bug_subscription.person != bug_subscription.subscribed_by:
127 send_bug_details_to_new_bug_subscribers(
128 bug_subscription.bug, [], [bug_subscription.person],
129 subscribed_by=bug_subscription.subscribed_by)
130
131
132def get_bug_delta(old_bug, new_bug, user):
133 """Compute the delta from old_bug to new_bug.
134
135 old_bug and new_bug are IBug's. user is an IPerson. Returns an
136 IBugDelta if there are changes, or None if there were no changes.
137 """
138 changes = {}
139
140 for field_name in ("title", "description", "name", "private",
141 "security_related", "duplicateof", "tags"):
142 # fields for which we show old => new when their values change
143 old_val = getattr(old_bug, field_name)
144 new_val = getattr(new_bug, field_name)
145 if old_val != new_val:
146 changes[field_name] = {}
147 changes[field_name]["old"] = old_val
148 changes[field_name]["new"] = new_val
149
150 if changes:
151 changes["bug"] = new_bug
152 changes["bug_before_modification"] = old_bug
153 changes["bugurl"] = canonical_url(new_bug)
154 changes["user"] = user
155 return BugDelta(**changes)
156 else:
157 return None
158
159
160def get_bugtask_indirect_subscribers(bugtask, recipients=None, level=None):
161 """Return the indirect subscribers for a bug task.
162
163 Return the list of people who should get notifications about
164 changes to the task because of having an indirect subscription
165 relationship with it (by subscribing to its target, being an
166 assignee or owner, etc...)
167
168 If `recipients` is present, add the subscribers to the set of
169 bug notification recipients.
170 """
171 if bugtask.bug.private:
172 return set()
173
174 also_notified_subscribers = set()
175
176 # Assignees are indirect subscribers.
177 if bugtask.assignee:
178 also_notified_subscribers.add(bugtask.assignee)
179 if recipients is not None:
180 recipients.addAssignee(bugtask.assignee)
181
182 if IStructuralSubscriptionTarget.providedBy(bugtask.target):
183 also_notified_subscribers.update(
184 bugtask.target.getBugNotificationsRecipients(
185 recipients, level=level))
186
187 if bugtask.milestone is not None:
188 also_notified_subscribers.update(
189 bugtask.milestone.getBugNotificationsRecipients(
190 recipients, level=level))
191
192 # If the target's bug supervisor isn't set,
193 # we add the owner as a subscriber.
194 pillar = bugtask.pillar
195 if pillar.bug_supervisor is None:
196 also_notified_subscribers.add(pillar.owner)
197 if recipients is not None:
198 recipients.addRegistrant(pillar.owner, pillar)
199
200 return sorted(
201 also_notified_subscribers,
202 key=attrgetter('displayname'))
203
204
205def add_bug_change_notifications(bug_delta, old_bugtask=None,
206 new_subscribers=None):
207 """Generate bug notifications and add them to the bug."""
208 changes = get_bug_changes(bug_delta)
209 recipients = bug_delta.bug.getBugNotificationRecipients(
210 old_bug=bug_delta.bug_before_modification,
211 level=BugNotificationLevel.METADATA)
212 if old_bugtask is not None:
213 old_bugtask_recipients = BugNotificationRecipients()
214 get_bugtask_indirect_subscribers(
215 old_bugtask, recipients=old_bugtask_recipients,
216 level=BugNotificationLevel.METADATA)
217 recipients.update(old_bugtask_recipients)
218 for change in changes:
219 # XXX 2009-03-17 gmb [bug=344125]
220 # This if..else should be removed once the new BugChange API
221 # is complete and ubiquitous.
222 if IBugChange.providedBy(change):
223 if isinstance(change, BugDuplicateChange):
224 no_dupe_master_recipients = (
225 bug_delta.bug.getBugNotificationRecipients(
226 old_bug=bug_delta.bug_before_modification,
227 level=BugNotificationLevel.METADATA,
228 include_master_dupe_subscribers=False))
229 bug_delta.bug.addChange(
230 change, recipients=no_dupe_master_recipients)
231 elif (isinstance(change, BugTaskAssigneeChange) and
232 new_subscribers is not None):
233 for person in new_subscribers:
234 reason, rationale = recipients.getReason(person)
235 if 'Assignee' in rationale:
236 recipients.remove(person)
237 bug_delta.bug.addChange(change, recipients=recipients)
238 else:
239 bug_delta.bug.addChange(change, recipients=recipients)
240 else:
241 bug_delta.bug.addChangeNotification(
242 change, person=bug_delta.user, recipients=recipients)
243
244
245def send_bug_details_to_new_bug_subscribers(
246 bug, previous_subscribers, current_subscribers, subscribed_by=None,
247 event_creator=None):
248 """Send an email containing full bug details to new bug subscribers.
249
250 This function is designed to handle situations where bugtasks get
251 reassigned to new products or sourcepackages, and the new bug subscribers
252 need to be notified of the bug.
253 """
254 prev_subs_set = set(previous_subscribers)
255 cur_subs_set = set(current_subscribers)
256 new_subs = cur_subs_set.difference(prev_subs_set)
257
258 to_addrs = set()
259 for new_sub in new_subs:
260 to_addrs.update(get_contact_email_addresses(new_sub))
261
262 if not to_addrs:
263 return
264
265 from_addr = format_address(
266 'Launchpad Bug Tracker',
267 "%s@%s" % (bug.id, config.launchpad.bugs_domain))
268 # Now's a good a time as any for this email; don't use the original
269 # reported date for the bug as it will just confuse mailer and
270 # recipient.
271 email_date = datetime.datetime.now()
272
273 # The new subscriber email is effectively the initial message regarding
274 # a new bug. The bug's initial message is used in the References
275 # header to establish the message's context in the email client.
276 references = [bug.initial_message.rfc822msgid]
277 recipients = bug.getBugNotificationRecipients()
278
279 bug_notification_builder = BugNotificationBuilder(bug, event_creator)
280 for to_addr in sorted(to_addrs):
281 reason, rationale = recipients.getReason(to_addr)
282 subject, contents = generate_bug_add_email(
283 bug, new_recipients=True, subscribed_by=subscribed_by,
284 reason=reason, event_creator=event_creator)
285 msg = bug_notification_builder.build(
286 from_addr, to_addr, contents, subject, email_date,
287 rationale=rationale, references=references)
288 sendmail(msg)
26289
=== modified file 'lib/lp/bugs/subscribers/bugcreation.py'
--- lib/lp/bugs/subscribers/bugcreation.py 2010-08-20 20:31:18 +0000
+++ lib/lp/bugs/subscribers/bugcreation.py 2010-08-23 20:18:06 +0000
@@ -2,8 +2,10 @@
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4__metaclass__ = type4__metaclass__ = type
5__all__ = [
6 'at_least_one_task',
7 ]
58
6from canonical.database.sqlbase import block_implicit_flushes
7from lp.bugs.interfaces.bug import CreatedBugWithNoBugTasksError9from lp.bugs.interfaces.bug import CreatedBugWithNoBugTasksError
810
911
1012
=== added file 'lib/lp/bugs/subscribers/bugtask.py'
--- lib/lp/bugs/subscribers/bugtask.py 1970-01-01 00:00:00 +0000
+++ lib/lp/bugs/subscribers/bugtask.py 2010-08-23 20:18:06 +0000
@@ -0,0 +1,79 @@
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__metaclass__ = type
5__all__ = [
6 'notify_bugtask_edited',
7 'update_security_contact_subscriptions',
8 ]
9
10
11from canonical.database.sqlbase import block_implicit_flushes
12from canonical.launchpad.webapp.publisher import canonical_url
13from lp.bugs.adapters.bugdelta import BugDelta
14from lp.bugs.interfaces.bugtask import IUpstreamBugTask
15from lp.bugs.subscribers.bug import (
16 add_bug_change_notifications,
17 send_bug_details_to_new_bug_subscribers,
18 )
19from lp.registry.interfaces.person import IPerson
20
21
22@block_implicit_flushes
23def update_security_contact_subscriptions(event):
24 """Subscribe the new security contact when a bugtask's product changes.
25
26 Only subscribes the new security contact if the bug was marked a
27 security issue originally.
28
29 No change is made for private bugs.
30 """
31 if event.object.bug.private:
32 return
33
34 if not IUpstreamBugTask.providedBy(event.object):
35 return
36
37 bugtask_before_modification = event.object_before_modification
38 bugtask_after_modification = event.object
39
40 if (bugtask_before_modification.product !=
41 bugtask_after_modification.product):
42 new_product = bugtask_after_modification.product
43 if (bugtask_before_modification.bug.security_related and
44 new_product.security_contact):
45 bugtask_after_modification.bug.subscribe(
46 new_product.security_contact, IPerson(event.user))
47
48
49@block_implicit_flushes
50def notify_bugtask_edited(modified_bugtask, event):
51 """Notify CC'd subscribers of this bug that something has changed
52 on this task.
53
54 modified_bugtask must be an IBugTask. event must be an
55 IObjectModifiedEvent.
56 """
57 bugtask_delta = event.object.getDelta(event.object_before_modification)
58 bug_delta = BugDelta(
59 bug=event.object.bug,
60 bugurl=canonical_url(event.object.bug),
61 bugtask_deltas=bugtask_delta,
62 user=IPerson(event.user))
63
64 event_creator = IPerson(event.user)
65 previous_subscribers = event.object_before_modification.bug_subscribers
66 current_subscribers = event.object.bug_subscribers
67 prev_subs_set = set(previous_subscribers)
68 cur_subs_set = set(current_subscribers)
69 new_subs = cur_subs_set.difference(prev_subs_set)
70
71 add_bug_change_notifications(
72 bug_delta, old_bugtask=event.object_before_modification,
73 new_subscribers=new_subs)
74
75 send_bug_details_to_new_bug_subscribers(
76 event.object.bug, previous_subscribers, current_subscribers,
77 event_creator=event_creator)
78
79 update_security_contact_subscriptions(event)