Merge lp:~allenap/launchpad/refactor-mailnotification into lp:launchpad
- refactor-mailnotification
- Merge into devel
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 |
Related bugs: |
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.mailnotific
Description of the change
Moves almost all of the remaining bug related code out of c/l/mailnotific
To post a comment you must log in.
Revision history for this message
Gavin Panella (allenap) wrote : | # |
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
1 | === removed file 'lib/canonical/launchpad/emailtemplates/notify-unhandled-email.txt' |
2 | --- lib/canonical/launchpad/emailtemplates/notify-unhandled-email.txt 2005-10-31 18:29:12 +0000 |
3 | +++ lib/canonical/launchpad/emailtemplates/notify-unhandled-email.txt 1970-01-01 00:00:00 +0000 |
4 | @@ -1,7 +0,0 @@ |
5 | -The following email was unhandled: |
6 | - |
7 | -%(url)s |
8 | - |
9 | -Error message: |
10 | - |
11 | -%(error_msg)s |
12 | |
13 | === modified file 'lib/canonical/launchpad/mailnotification.py' |
14 | --- lib/canonical/launchpad/mailnotification.py 2010-08-20 20:31:18 +0000 |
15 | +++ lib/canonical/launchpad/mailnotification.py 2010-08-23 20:18:06 +0000 |
16 | @@ -8,7 +8,6 @@ |
17 | |
18 | __metaclass__ = type |
19 | |
20 | -import datetime |
21 | from difflib import unified_diff |
22 | from email.Header import Header |
23 | from email.MIMEMessage import MIMEMessage |
24 | @@ -18,7 +17,6 @@ |
25 | formataddr, |
26 | make_msgid, |
27 | ) |
28 | -import operator |
29 | import re |
30 | |
31 | from zope.component import ( |
32 | @@ -37,9 +35,7 @@ |
33 | IPerson, |
34 | IPersonSet, |
35 | ISpecification, |
36 | - IStructuralSubscriptionTarget, |
37 | ITeamMembershipSet, |
38 | - IUpstreamBugTask, |
39 | TeamMembershipStatus, |
40 | ) |
41 | from canonical.launchpad.interfaces.launchpad import ILaunchpadRoot |
42 | @@ -55,102 +51,20 @@ |
43 | ) |
44 | from canonical.launchpad.webapp.publisher import canonical_url |
45 | from canonical.launchpad.webapp.url import urlappend |
46 | -from lp.bugs.adapters.bugchange import ( |
47 | - BugDuplicateChange, |
48 | - BugTaskAssigneeChange, |
49 | - get_bug_changes, |
50 | - ) |
51 | -from lp.bugs.adapters.bugdelta import BugDelta |
52 | -from lp.bugs.interfaces.bugchange import IBugChange |
53 | -from lp.bugs.mail.bugnotificationbuilder import ( |
54 | - BugNotificationBuilder, |
55 | - get_bugmail_error_address, |
56 | - ) |
57 | -from lp.bugs.mail.bugnotificationrecipients import BugNotificationRecipients |
58 | -from lp.registry.enum import BugNotificationLevel |
59 | +from lp.bugs.mail.bugnotificationbuilder import get_bugmail_error_address |
60 | from lp.services.mail.mailwrapper import MailWrapper |
61 | # XXX 2010-06-16 gmb bug=594985 |
62 | # This shouldn't be here, but if we take it out lots of things cry, |
63 | # which is sad. |
64 | from lp.services.mail.notificationrecipientset import NotificationRecipientSet |
65 | |
66 | +# Silence lint warnings. |
67 | +NotificationRecipientSet |
68 | + |
69 | |
70 | CC = "CC" |
71 | |
72 | |
73 | -def _send_bug_details_to_new_bug_subscribers( |
74 | - bug, previous_subscribers, current_subscribers, subscribed_by=None, |
75 | - event_creator=None): |
76 | - """Send an email containing full bug details to new bug subscribers. |
77 | - |
78 | - This function is designed to handle situations where bugtasks get |
79 | - reassigned to new products or sourcepackages, and the new bug subscribers |
80 | - need to be notified of the bug. |
81 | - """ |
82 | - prev_subs_set = set(previous_subscribers) |
83 | - cur_subs_set = set(current_subscribers) |
84 | - new_subs = cur_subs_set.difference(prev_subs_set) |
85 | - |
86 | - to_addrs = set() |
87 | - for new_sub in new_subs: |
88 | - to_addrs.update(get_contact_email_addresses(new_sub)) |
89 | - |
90 | - if not to_addrs: |
91 | - return |
92 | - |
93 | - from_addr = format_address( |
94 | - 'Launchpad Bug Tracker', |
95 | - "%s@%s" % (bug.id, config.launchpad.bugs_domain)) |
96 | - # Now's a good a time as any for this email; don't use the original |
97 | - # reported date for the bug as it will just confuse mailer and |
98 | - # recipient. |
99 | - email_date = datetime.datetime.now() |
100 | - |
101 | - # The new subscriber email is effectively the initial message regarding |
102 | - # a new bug. The bug's initial message is used in the References |
103 | - # header to establish the message's context in the email client. |
104 | - references = [bug.initial_message.rfc822msgid] |
105 | - recipients = bug.getBugNotificationRecipients() |
106 | - |
107 | - bug_notification_builder = BugNotificationBuilder(bug, event_creator) |
108 | - for to_addr in sorted(to_addrs): |
109 | - reason, rationale = recipients.getReason(to_addr) |
110 | - subject, contents = generate_bug_add_email( |
111 | - bug, new_recipients=True, subscribed_by=subscribed_by, |
112 | - reason=reason, event_creator=event_creator) |
113 | - msg = bug_notification_builder.build( |
114 | - from_addr, to_addr, contents, subject, email_date, |
115 | - rationale=rationale, references=references) |
116 | - sendmail(msg) |
117 | - |
118 | - |
119 | -@block_implicit_flushes |
120 | -def update_security_contact_subscriptions(modified_bugtask, event): |
121 | - """Subscribe the new security contact when a bugtask's product changes. |
122 | - |
123 | - Only subscribes the new security contact if the bug was marked a |
124 | - security issue originally. |
125 | - |
126 | - No change is made for private bugs. |
127 | - """ |
128 | - if event.object.bug.private: |
129 | - return |
130 | - |
131 | - if not IUpstreamBugTask.providedBy(event.object): |
132 | - return |
133 | - |
134 | - bugtask_before_modification = event.object_before_modification |
135 | - bugtask_after_modification = event.object |
136 | - |
137 | - if (bugtask_before_modification.product != |
138 | - bugtask_after_modification.product): |
139 | - new_product = bugtask_after_modification.product |
140 | - if (bugtask_before_modification.bug.security_related and |
141 | - new_product.security_contact): |
142 | - bugtask_after_modification.bug.subscribe( |
143 | - new_product.security_contact, IPerson(event.user)) |
144 | - |
145 | - |
146 | def send_process_error_notification(to_address, subject, error_msg, |
147 | original_msg, failing_command=None): |
148 | """Send a mail about an error occurring while using the email interface. |
149 | @@ -193,102 +107,6 @@ |
150 | sendmail(msg) |
151 | |
152 | |
153 | -def notify_errors_list(message, file_alias_url): |
154 | - """Sends an error to the Launchpad errors list.""" |
155 | - template = get_email_template('notify-unhandled-email.txt') |
156 | - # We add the error message in as a header too |
157 | - # (X-Launchpad-Unhandled-Email) so we can create filters in the |
158 | - # Launchpad-Error-Reports Mailman mailing list. |
159 | - simple_sendmail( |
160 | - get_bugmail_error_address(), [config.launchpad.errors_address], |
161 | - 'Unhandled Email: %s' % file_alias_url, |
162 | - template % {'url': file_alias_url, 'error_msg': message}, |
163 | - headers={'X-Launchpad-Unhandled-Email': message}) |
164 | - |
165 | - |
166 | -def generate_bug_add_email(bug, new_recipients=False, reason=None, |
167 | - subscribed_by=None, event_creator=None): |
168 | - """Generate a new bug notification from the given IBug. |
169 | - |
170 | - If new_recipients is supplied we generate a notification explaining |
171 | - that the new recipients have been subscribed to the bug. Otherwise |
172 | - it's just a notification of a new bug report. |
173 | - """ |
174 | - subject = u"[Bug %d] [NEW] %s" % (bug.id, bug.title) |
175 | - contents = '' |
176 | - |
177 | - if bug.private: |
178 | - # This is a confidential bug. |
179 | - visibility = u"Private" |
180 | - else: |
181 | - # This is a public bug. |
182 | - visibility = u"Public" |
183 | - |
184 | - if bug.security_related: |
185 | - visibility += ' security' |
186 | - contents += '*** This bug is a security vulnerability ***\n\n' |
187 | - |
188 | - bug_info = [] |
189 | - # Add information about the affected upstreams and packages. |
190 | - for bugtask in bug.bugtasks: |
191 | - bug_info.append(u"** Affects: %s" % bugtask.bugtargetname) |
192 | - bug_info.append(u" Importance: %s" % bugtask.importance.title) |
193 | - |
194 | - if bugtask.assignee: |
195 | - # There's a person assigned to fix this task, so show that |
196 | - # information too. |
197 | - bug_info.append( |
198 | - u" Assignee: %s" % bugtask.assignee.unique_displayname) |
199 | - bug_info.append(u" Status: %s\n" % bugtask.status.title) |
200 | - |
201 | - if bug.tags: |
202 | - bug_info.append('\n** Tags: %s' % ' '.join(bug.tags)) |
203 | - |
204 | - mailwrapper = MailWrapper(width=72) |
205 | - content_substitutions = { |
206 | - 'visibility': visibility, |
207 | - 'bug_url': canonical_url(bug), |
208 | - 'bug_info': "\n".join(bug_info), |
209 | - 'bug_title': bug.title, |
210 | - 'description': mailwrapper.format(bug.description), |
211 | - 'notification_rationale': reason, |
212 | - } |
213 | - |
214 | - if new_recipients: |
215 | - if "assignee" in reason: |
216 | - contents += ( |
217 | - "You have been assigned a bug task for a %(visibility)s bug") |
218 | - if event_creator is not None: |
219 | - contents += " by %(assigner)s" |
220 | - content_substitutions['assigner'] = ( |
221 | - event_creator.unique_displayname) |
222 | - else: |
223 | - contents += "You have been subscribed to a %(visibility)s bug" |
224 | - if subscribed_by is not None: |
225 | - contents += " by %(subscribed_by)s" |
226 | - content_substitutions['subscribed_by'] = ( |
227 | - subscribed_by.unique_displayname) |
228 | - contents += (":\n\n" |
229 | - "%(description)s\n\n%(bug_info)s") |
230 | - # The visibility appears mid-phrase so.. hack hack. |
231 | - content_substitutions['visibility'] = visibility.lower() |
232 | - # XXX: kiko, 2007-03-21: |
233 | - # We should really have a centralized way of adding this |
234 | - # footer, but right now we lack a INotificationRecipientSet |
235 | - # for this particular situation. |
236 | - contents += ( |
237 | - "\n-- \n%(bug_title)s\n%(bug_url)s\n%(notification_rationale)s") |
238 | - else: |
239 | - contents += ("%(visibility)s bug reported:\n\n" |
240 | - "%(description)s\n\n%(bug_info)s") |
241 | - |
242 | - contents = contents % content_substitutions |
243 | - |
244 | - contents = contents.rstrip() |
245 | - |
246 | - return (subject, contents) |
247 | - |
248 | - |
249 | def get_unified_diff(old_text, new_text, text_width): |
250 | r"""Return a unified diff of the two texts. |
251 | |
252 | @@ -329,254 +147,6 @@ |
253 | return text_diff |
254 | |
255 | |
256 | -def _get_task_change_row(label, oldval_display, newval_display): |
257 | - """Return a row formatted for display in task change info.""" |
258 | - return u"%(label)13s: %(oldval)s => %(newval)s\n" % { |
259 | - 'label': label.capitalize(), |
260 | - 'oldval': oldval_display, |
261 | - 'newval': newval_display} |
262 | - |
263 | - |
264 | -def _get_task_change_values(task_change, displayattrname): |
265 | - """Return the old value and the new value for a task field change.""" |
266 | - oldval = task_change.get('old') |
267 | - newval = task_change.get('new') |
268 | - |
269 | - oldval_display = None |
270 | - newval_display = None |
271 | - |
272 | - if oldval: |
273 | - oldval_display = getattr(oldval, displayattrname) |
274 | - if newval: |
275 | - newval_display = getattr(newval, displayattrname) |
276 | - |
277 | - return (oldval_display, newval_display) |
278 | - |
279 | - |
280 | -def get_bug_delta(old_bug, new_bug, user): |
281 | - """Compute the delta from old_bug to new_bug. |
282 | - |
283 | - old_bug and new_bug are IBug's. user is an IPerson. Returns an |
284 | - IBugDelta if there are changes, or None if there were no changes. |
285 | - """ |
286 | - changes = {} |
287 | - |
288 | - for field_name in ("title", "description", "name", "private", |
289 | - "security_related", "duplicateof", "tags"): |
290 | - # fields for which we show old => new when their values change |
291 | - old_val = getattr(old_bug, field_name) |
292 | - new_val = getattr(new_bug, field_name) |
293 | - if old_val != new_val: |
294 | - changes[field_name] = {} |
295 | - changes[field_name]["old"] = old_val |
296 | - changes[field_name]["new"] = new_val |
297 | - |
298 | - if changes: |
299 | - changes["bug"] = new_bug |
300 | - changes["bug_before_modification"] = old_bug |
301 | - changes["bugurl"] = canonical_url(new_bug) |
302 | - changes["user"] = user |
303 | - |
304 | - return BugDelta(**changes) |
305 | - else: |
306 | - return None |
307 | - |
308 | - |
309 | -@block_implicit_flushes |
310 | -def notify_bug_added(bug, event): |
311 | - """Send an email notification that a bug was added. |
312 | - |
313 | - Event must be an IObjectCreatedEvent. |
314 | - """ |
315 | - |
316 | - bug.addCommentNotification(bug.initial_message) |
317 | - |
318 | - |
319 | -@block_implicit_flushes |
320 | -def notify_bug_modified(modified_bug, event): |
321 | - """Notify the Cc'd list that this bug has been modified. |
322 | - |
323 | - modified_bug bug must be an IBug. event must be an |
324 | - IObjectModifiedEvent. |
325 | - """ |
326 | - bug_delta = get_bug_delta( |
327 | - old_bug=event.object_before_modification, |
328 | - new_bug=event.object, user=IPerson(event.user)) |
329 | - |
330 | - if bug_delta is not None: |
331 | - add_bug_change_notifications(bug_delta) |
332 | - |
333 | - |
334 | -def get_bugtask_indirect_subscribers(bugtask, recipients=None, level=None): |
335 | - """Return the indirect subscribers for a bug task. |
336 | - |
337 | - Return the list of people who should get notifications about |
338 | - changes to the task because of having an indirect subscription |
339 | - relationship with it (by subscribing to its target, being an |
340 | - assignee or owner, etc...) |
341 | - |
342 | - If `recipients` is present, add the subscribers to the set of |
343 | - bug notification recipients. |
344 | - """ |
345 | - if bugtask.bug.private: |
346 | - return set() |
347 | - |
348 | - also_notified_subscribers = set() |
349 | - |
350 | - # Assignees are indirect subscribers. |
351 | - if bugtask.assignee: |
352 | - also_notified_subscribers.add(bugtask.assignee) |
353 | - if recipients is not None: |
354 | - recipients.addAssignee(bugtask.assignee) |
355 | - |
356 | - if IStructuralSubscriptionTarget.providedBy(bugtask.target): |
357 | - also_notified_subscribers.update( |
358 | - bugtask.target.getBugNotificationsRecipients( |
359 | - recipients, level=level)) |
360 | - |
361 | - if bugtask.milestone is not None: |
362 | - also_notified_subscribers.update( |
363 | - bugtask.milestone.getBugNotificationsRecipients( |
364 | - recipients, level=level)) |
365 | - |
366 | - # If the target's bug supervisor isn't set, |
367 | - # we add the owner as a subscriber. |
368 | - pillar = bugtask.pillar |
369 | - if pillar.bug_supervisor is None: |
370 | - also_notified_subscribers.add(pillar.owner) |
371 | - if recipients is not None: |
372 | - recipients.addRegistrant(pillar.owner, pillar) |
373 | - |
374 | - return sorted( |
375 | - also_notified_subscribers, |
376 | - key=operator.attrgetter('displayname')) |
377 | - |
378 | - |
379 | -def add_bug_change_notifications(bug_delta, old_bugtask=None, |
380 | - new_subscribers=None): |
381 | - """Generate bug notifications and add them to the bug.""" |
382 | - changes = get_bug_changes(bug_delta) |
383 | - recipients = bug_delta.bug.getBugNotificationRecipients( |
384 | - old_bug=bug_delta.bug_before_modification, |
385 | - level=BugNotificationLevel.METADATA) |
386 | - if old_bugtask is not None: |
387 | - old_bugtask_recipients = BugNotificationRecipients() |
388 | - get_bugtask_indirect_subscribers( |
389 | - old_bugtask, recipients=old_bugtask_recipients, |
390 | - level=BugNotificationLevel.METADATA) |
391 | - recipients.update(old_bugtask_recipients) |
392 | - for change in changes: |
393 | - # XXX 2009-03-17 gmb [bug=344125] |
394 | - # This if..else should be removed once the new BugChange API |
395 | - # is complete and ubiquitous. |
396 | - if IBugChange.providedBy(change): |
397 | - if isinstance(change, BugDuplicateChange): |
398 | - no_dupe_master_recipients = ( |
399 | - bug_delta.bug.getBugNotificationRecipients( |
400 | - old_bug=bug_delta.bug_before_modification, |
401 | - level=BugNotificationLevel.METADATA, |
402 | - include_master_dupe_subscribers=False)) |
403 | - bug_delta.bug.addChange( |
404 | - change, recipients=no_dupe_master_recipients) |
405 | - elif (isinstance(change, BugTaskAssigneeChange) and |
406 | - new_subscribers is not None): |
407 | - for person in new_subscribers: |
408 | - reason, rationale = recipients.getReason(person) |
409 | - if 'Assignee' in rationale: |
410 | - recipients.remove(person) |
411 | - bug_delta.bug.addChange(change, recipients=recipients) |
412 | - else: |
413 | - bug_delta.bug.addChange(change, recipients=recipients) |
414 | - else: |
415 | - bug_delta.bug.addChangeNotification( |
416 | - change, person=bug_delta.user, recipients=recipients) |
417 | - |
418 | - |
419 | -@block_implicit_flushes |
420 | -def notify_bugtask_edited(modified_bugtask, event): |
421 | - """Notify CC'd subscribers of this bug that something has changed |
422 | - on this task. |
423 | - |
424 | - modified_bugtask must be an IBugTask. event must be an |
425 | - IObjectModifiedEvent. |
426 | - """ |
427 | - bugtask_delta = event.object.getDelta(event.object_before_modification) |
428 | - bug_delta = BugDelta( |
429 | - bug=event.object.bug, |
430 | - bugurl=canonical_url(event.object.bug), |
431 | - bugtask_deltas=bugtask_delta, |
432 | - user=IPerson(event.user)) |
433 | - |
434 | - event_creator = IPerson(event.user) |
435 | - previous_subscribers = event.object_before_modification.bug_subscribers |
436 | - current_subscribers = event.object.bug_subscribers |
437 | - prev_subs_set = set(previous_subscribers) |
438 | - cur_subs_set = set(current_subscribers) |
439 | - new_subs = cur_subs_set.difference(prev_subs_set) |
440 | - |
441 | - add_bug_change_notifications( |
442 | - bug_delta, old_bugtask=event.object_before_modification, |
443 | - new_subscribers=new_subs) |
444 | - |
445 | - _send_bug_details_to_new_bug_subscribers( |
446 | - event.object.bug, previous_subscribers, current_subscribers, |
447 | - event_creator=event_creator) |
448 | - update_security_contact_subscriptions(modified_bugtask, event) |
449 | - |
450 | - |
451 | -@block_implicit_flushes |
452 | -def notify_bug_comment_added(bugmessage, event): |
453 | - """Notify CC'd list that a message was added to this bug. |
454 | - |
455 | - bugmessage must be an IBugMessage. event must be an |
456 | - IObjectCreatedEvent. If bugmessage.bug is a duplicate the |
457 | - comment will also be sent to the dup target's subscribers. |
458 | - """ |
459 | - bug = bugmessage.bug |
460 | - bug.addCommentNotification(bugmessage.message) |
461 | - |
462 | - |
463 | -@block_implicit_flushes |
464 | -def notify_bug_attachment_added(bugattachment, event): |
465 | - """Notify CC'd list that a new attachment has been added. |
466 | - |
467 | - bugattachment must be an IBugAttachment. event must be an |
468 | - IObjectCreatedEvent. |
469 | - """ |
470 | - bug = bugattachment.bug |
471 | - bug_delta = BugDelta( |
472 | - bug=bug, |
473 | - bugurl=canonical_url(bug), |
474 | - user=IPerson(event.user), |
475 | - attachment={'new': bugattachment, 'old': None}) |
476 | - |
477 | - add_bug_change_notifications(bug_delta) |
478 | - |
479 | - |
480 | -@block_implicit_flushes |
481 | -def notify_bug_attachment_removed(bugattachment, event): |
482 | - """Notify that an attachment has been removed.""" |
483 | - bug = bugattachment.bug |
484 | - bug_delta = BugDelta( |
485 | - bug=bug, |
486 | - bugurl=canonical_url(bug), |
487 | - user=IPerson(event.user), |
488 | - attachment={'old': bugattachment, 'new': None}) |
489 | - |
490 | - add_bug_change_notifications(bug_delta) |
491 | - |
492 | - |
493 | -@block_implicit_flushes |
494 | -def notify_bug_subscription_added(bug_subscription, event): |
495 | - """Notify that a new bug subscription was added.""" |
496 | - # When a user is subscribed to a bug by someone other |
497 | - # than themselves, we send them a notification email. |
498 | - if bug_subscription.person != bug_subscription.subscribed_by: |
499 | - _send_bug_details_to_new_bug_subscribers( |
500 | - bug_subscription.bug, [], [bug_subscription.person], |
501 | - subscribed_by=bug_subscription.subscribed_by) |
502 | - |
503 | - |
504 | @block_implicit_flushes |
505 | def notify_invitation_to_join_team(event): |
506 | """Notify team admins that the team has been invited to join another team. |
507 | |
508 | === modified file 'lib/canonical/launchpad/subscribers/karma.py' |
509 | --- lib/canonical/launchpad/subscribers/karma.py 2010-08-20 20:31:18 +0000 |
510 | +++ lib/canonical/launchpad/subscribers/karma.py 2010-08-23 20:18:06 +0000 |
511 | @@ -6,7 +6,7 @@ |
512 | |
513 | from canonical.database.sqlbase import block_implicit_flushes |
514 | from canonical.launchpad.interfaces import BugTaskStatus |
515 | -from canonical.launchpad.mailnotification import get_bug_delta |
516 | +from lp.bugs.subscribers.bug import get_bug_delta |
517 | from lp.code.enums import BranchMergeProposalStatus |
518 | from lp.registry.interfaces.person import IPerson |
519 | |
520 | @@ -18,6 +18,7 @@ |
521 | assert len(bug.bugtasks) >= 1 |
522 | _assignKarmaUsingBugContext(IPerson(event.user), bug, 'bugcreated') |
523 | |
524 | + |
525 | def _assign_karma_using_bugtask_context(person, bugtask, actionname): |
526 | """Extract the right context from the bugtask and assign karma.""" |
527 | distribution = bugtask.distribution |
528 | @@ -157,6 +158,7 @@ |
529 | """Assign karma to the user who registered the branch.""" |
530 | branch.target.assignKarma(branch.registrant, 'branchcreated') |
531 | |
532 | + |
533 | @block_implicit_flushes |
534 | def bug_branch_created(bug_branch, event): |
535 | """Assign karma to the user who linked the bug to the branch.""" |
536 | |
537 | === modified file 'lib/lp/bugs/configure.zcml' |
538 | --- lib/lp/bugs/configure.zcml 2010-08-19 03:06:27 +0000 |
539 | +++ lib/lp/bugs/configure.zcml 2010-08-23 20:18:06 +0000 |
540 | @@ -55,25 +55,22 @@ |
541 | handler="lp.bugs.subscribers.bugcreation.at_least_one_task"/> |
542 | <subscriber |
543 | for="canonical.launchpad.interfaces.IBug lazr.lifecycle.interfaces.IObjectCreatedEvent" |
544 | - handler="canonical.launchpad.mailnotification.notify_bug_added"/> |
545 | + handler="lp.bugs.subscribers.bug.notify_bug_added"/> |
546 | <subscriber |
547 | for="canonical.launchpad.interfaces.IBug lazr.lifecycle.interfaces.IObjectCreatedEvent" |
548 | handler="canonical.launchpad.subscribers.karma.bug_created"/> |
549 | <subscriber |
550 | for="canonical.launchpad.interfaces.IBug lazr.lifecycle.interfaces.IObjectModifiedEvent" |
551 | - handler="canonical.launchpad.mailnotification.notify_bug_modified"/> |
552 | - <subscriber |
553 | - for="canonical.launchpad.interfaces.IBug lazr.lifecycle.interfaces.IObjectModifiedEvent" |
554 | handler="canonical.launchpad.subscribers.karma.bug_modified"/> |
555 | <subscriber |
556 | for="canonical.launchpad.interfaces.IBug lazr.lifecycle.interfaces.IObjectModifiedEvent" |
557 | handler="lp.bugs.subscribers.buglastupdated.update_bug_date_last_updated"/> |
558 | <subscriber |
559 | for="canonical.launchpad.interfaces.IBugAttachment lazr.lifecycle.interfaces.IObjectCreatedEvent" |
560 | - handler="canonical.launchpad.mailnotification.notify_bug_attachment_added"/> |
561 | + handler="lp.bugs.subscribers.bug.notify_bug_attachment_added"/> |
562 | <subscriber |
563 | for="canonical.launchpad.interfaces.IBugAttachment lazr.lifecycle.interfaces.IObjectDeletedEvent" |
564 | - handler="canonical.launchpad.mailnotification.notify_bug_attachment_removed"/> |
565 | + handler="lp.bugs.subscribers.bug.notify_bug_attachment_removed"/> |
566 | <subscriber |
567 | for="canonical.launchpad.interfaces.IBugAttachment lazr.lifecycle.interfaces.IObjectCreatedEvent" |
568 | handler="lp.bugs.subscribers.buglastupdated.update_bug_date_last_updated"/> |
569 | @@ -94,7 +91,7 @@ |
570 | handler="canonical.launchpad.subscribers.karma.cve_added"/> |
571 | <subscriber |
572 | for="canonical.launchpad.interfaces.IBugMessage lazr.lifecycle.interfaces.IObjectCreatedEvent" |
573 | - handler="canonical.launchpad.mailnotification.notify_bug_comment_added"/> |
574 | + handler="lp.bugs.subscribers.bug.notify_bug_comment_added"/> |
575 | <subscriber |
576 | for="canonical.launchpad.interfaces.IBugMessage lazr.lifecycle.interfaces.IObjectCreatedEvent" |
577 | handler="canonical.launchpad.subscribers.karma.bug_comment_added"/> |
578 | @@ -115,7 +112,7 @@ |
579 | handler="lp.bugs.subscribers.buglastupdated.update_bug_date_last_updated"/> |
580 | <subscriber |
581 | for="canonical.launchpad.interfaces.IBugSubscription lazr.lifecycle.interfaces.IObjectCreatedEvent" |
582 | - handler="canonical.launchpad.mailnotification.notify_bug_subscription_added"/> |
583 | + handler="lp.bugs.subscribers.bug.notify_bug_subscription_added"/> |
584 | <subscriber |
585 | for="canonical.launchpad.interfaces.IBug lazr.lifecycle.interfaces.IObjectModifiedEvent" |
586 | handler="lp.bugs.subscribers.bug.notify_bug_modified"/> |
587 | @@ -932,7 +929,7 @@ |
588 | handler="lp.bugs.subscribers.buglastupdated.update_bug_date_last_updated"/> |
589 | <subscriber |
590 | for="canonical.launchpad.interfaces.IBugTask lazr.lifecycle.interfaces.IObjectModifiedEvent" |
591 | - handler="canonical.launchpad.mailnotification.notify_bugtask_edited"/> |
592 | + handler="lp.bugs.subscribers.bugtask.notify_bugtask_edited"/> |
593 | <subscriber |
594 | for="canonical.launchpad.interfaces.IBugTask lazr.lifecycle.interfaces.IObjectModifiedEvent" |
595 | handler="canonical.launchpad.subscribers.karma.bugtask_modified"/> |
596 | |
597 | === modified file 'lib/lp/bugs/doc/bugnotification-email.txt' |
598 | --- lib/lp/bugs/doc/bugnotification-email.txt 2010-08-04 09:42:07 +0000 |
599 | +++ lib/lp/bugs/doc/bugnotification-email.txt 2010-08-23 20:18:06 +0000 |
600 | @@ -19,10 +19,8 @@ |
601 | object it gets passed, the formatting logic has been cut into two |
602 | pieces: get_bug_changes and generate_bug_add_email. |
603 | |
604 | - >>> from lp.bugs.adapters.bugchange import ( |
605 | - ... get_bug_changes) |
606 | - >>> from canonical.launchpad.mailnotification import ( |
607 | - ... generate_bug_add_email) |
608 | + >>> from lp.bugs.adapters.bugchange import get_bug_changes |
609 | + >>> from lp.bugs.mail.newbug import generate_bug_add_email |
610 | |
611 | Let's demonstrate what the bugmails will look like, by going through |
612 | the various events that can happen that would cause a notification to |
613 | @@ -479,8 +477,7 @@ |
614 | mailnotification.py contains a class, BugNotificationBuilder, which is |
615 | used to construct bug notification emails. |
616 | |
617 | - >>> from canonical.launchpad.mailnotification import ( |
618 | - ... BugNotificationBuilder) |
619 | + >>> from lp.bugs.mail.bugnotificationbuilder import BugNotificationBuilder |
620 | |
621 | When instantiatiated it derives a list of common unchanging headers |
622 | from the bug so that they are not calculated for every recipient. |
623 | |
624 | === modified file 'lib/lp/bugs/doc/bugsubscription.txt' |
625 | --- lib/lp/bugs/doc/bugsubscription.txt 2010-08-16 13:58:33 +0000 |
626 | +++ lib/lp/bugs/doc/bugsubscription.txt 2010-08-23 20:18:06 +0000 |
627 | @@ -101,10 +101,8 @@ |
628 | It is also possible to get the list of indirect subscribers for an |
629 | individual bug task. |
630 | |
631 | - >>> from canonical.launchpad.mailnotification import ( |
632 | - ... get_bugtask_indirect_subscribers) |
633 | - >>> get_bugtask_indirect_subscribers( |
634 | - ... linux_source_bug.bugtasks[0]) |
635 | + >>> from lp.bugs.subscribers.bug import get_bugtask_indirect_subscribers |
636 | + >>> get_bugtask_indirect_subscribers(linux_source_bug.bugtasks[0]) |
637 | [<Person at ...>] |
638 | |
639 | The list of all bug subscribers can also be accessed via |
640 | |
641 | === added file 'lib/lp/bugs/mail/newbug.py' |
642 | --- lib/lp/bugs/mail/newbug.py 1970-01-01 00:00:00 +0000 |
643 | +++ lib/lp/bugs/mail/newbug.py 2010-08-23 20:18:06 +0000 |
644 | @@ -0,0 +1,95 @@ |
645 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
646 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
647 | + |
648 | +"""Mail for new bugs.""" |
649 | + |
650 | +__metaclass__ = type |
651 | +__all__ = [ |
652 | + 'generate_bug_add_email', |
653 | + ] |
654 | + |
655 | +from canonical.launchpad.webapp.publisher import canonical_url |
656 | +from lp.services.mail.mailwrapper import MailWrapper |
657 | + |
658 | + |
659 | +def generate_bug_add_email(bug, new_recipients=False, reason=None, |
660 | + subscribed_by=None, event_creator=None): |
661 | + """Generate a new bug notification from the given IBug. |
662 | + |
663 | + If new_recipients is supplied we generate a notification explaining |
664 | + that the new recipients have been subscribed to the bug. Otherwise |
665 | + it's just a notification of a new bug report. |
666 | + """ |
667 | + subject = u"[Bug %d] [NEW] %s" % (bug.id, bug.title) |
668 | + contents = '' |
669 | + |
670 | + if bug.private: |
671 | + # This is a confidential bug. |
672 | + visibility = u"Private" |
673 | + else: |
674 | + # This is a public bug. |
675 | + visibility = u"Public" |
676 | + |
677 | + if bug.security_related: |
678 | + visibility += ' security' |
679 | + contents += '*** This bug is a security vulnerability ***\n\n' |
680 | + |
681 | + bug_info = [] |
682 | + # Add information about the affected upstreams and packages. |
683 | + for bugtask in bug.bugtasks: |
684 | + bug_info.append(u"** Affects: %s" % bugtask.bugtargetname) |
685 | + bug_info.append(u" Importance: %s" % bugtask.importance.title) |
686 | + |
687 | + if bugtask.assignee: |
688 | + # There's a person assigned to fix this task, so show that |
689 | + # information too. |
690 | + bug_info.append( |
691 | + u" Assignee: %s" % bugtask.assignee.unique_displayname) |
692 | + bug_info.append(u" Status: %s\n" % bugtask.status.title) |
693 | + |
694 | + if bug.tags: |
695 | + bug_info.append('\n** Tags: %s' % ' '.join(bug.tags)) |
696 | + |
697 | + mailwrapper = MailWrapper(width=72) |
698 | + content_substitutions = { |
699 | + 'visibility': visibility, |
700 | + 'bug_url': canonical_url(bug), |
701 | + 'bug_info': "\n".join(bug_info), |
702 | + 'bug_title': bug.title, |
703 | + 'description': mailwrapper.format(bug.description), |
704 | + 'notification_rationale': reason, |
705 | + } |
706 | + |
707 | + if new_recipients: |
708 | + if "assignee" in reason: |
709 | + contents += ( |
710 | + "You have been assigned a bug task for a %(visibility)s bug") |
711 | + if event_creator is not None: |
712 | + contents += " by %(assigner)s" |
713 | + content_substitutions['assigner'] = ( |
714 | + event_creator.unique_displayname) |
715 | + else: |
716 | + contents += "You have been subscribed to a %(visibility)s bug" |
717 | + if subscribed_by is not None: |
718 | + contents += " by %(subscribed_by)s" |
719 | + content_substitutions['subscribed_by'] = ( |
720 | + subscribed_by.unique_displayname) |
721 | + contents += (":\n\n" |
722 | + "%(description)s\n\n%(bug_info)s") |
723 | + # The visibility appears mid-phrase so.. hack hack. |
724 | + content_substitutions['visibility'] = visibility.lower() |
725 | + # XXX: kiko, 2007-03-21: |
726 | + # We should really have a centralized way of adding this |
727 | + # footer, but right now we lack a INotificationRecipientSet |
728 | + # for this particular situation. |
729 | + contents += ( |
730 | + "\n-- \n%(bug_title)s\n%(bug_url)s\n%(notification_rationale)s") |
731 | + else: |
732 | + contents += ("%(visibility)s bug reported:\n\n" |
733 | + "%(description)s\n\n%(bug_info)s") |
734 | + |
735 | + contents = contents % content_substitutions |
736 | + |
737 | + contents = contents.rstrip() |
738 | + |
739 | + return (subject, contents) |
740 | |
741 | === modified file 'lib/lp/bugs/scripts/bugnotification.py' |
742 | --- lib/lp/bugs/scripts/bugnotification.py 2010-08-20 20:31:18 +0000 |
743 | +++ lib/lp/bugs/scripts/bugnotification.py 2010-08-23 20:18:06 +0000 |
744 | @@ -27,10 +27,6 @@ |
745 | get_email_template, |
746 | ) |
747 | from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities |
748 | -from canonical.launchpad.mailnotification import ( |
749 | - generate_bug_add_email, |
750 | - MailWrapper, |
751 | - ) |
752 | from canonical.launchpad.scripts.logger import log |
753 | from canonical.launchpad.webapp import canonical_url |
754 | from lp.bugs.interfaces.bugmessage import IBugMessageSet |
755 | @@ -38,7 +34,9 @@ |
756 | BugNotificationBuilder, |
757 | get_bugmail_from_address, |
758 | ) |
759 | +from lp.bugs.mail.newbug import generate_bug_add_email |
760 | from lp.registry.interfaces.person import IPersonSet |
761 | +from lp.services.mail.mailwrapper import MailWrapper |
762 | |
763 | |
764 | def construct_email_notifications(bug_notifications): |
765 | |
766 | === modified file 'lib/lp/bugs/subscribers/bug.py' |
767 | --- lib/lp/bugs/subscribers/bug.py 2010-01-08 03:12:30 +0000 |
768 | +++ lib/lp/bugs/subscribers/bug.py 2010-08-23 20:18:06 +0000 |
769 | @@ -2,19 +2,63 @@ |
770 | # GNU Affero General Public License version 3 (see the file LICENSE). |
771 | |
772 | __metaclass__ = type |
773 | -__all__ = ['notify_bug_modified'] |
774 | - |
775 | - |
776 | +__all__ = [ |
777 | + 'add_bug_change_notifications', |
778 | + 'get_bug_delta', |
779 | + 'get_bugtask_indirect_subscribers', |
780 | + 'notify_bug_added', |
781 | + 'notify_bug_attachment_added', |
782 | + 'notify_bug_attachment_removed', |
783 | + 'notify_bug_comment_added', |
784 | + 'notify_bug_modified', |
785 | + 'notify_bug_subscription_added', |
786 | + 'send_bug_details_to_new_bug_subscribers', |
787 | + ] |
788 | + |
789 | + |
790 | +import datetime |
791 | +from operator import attrgetter |
792 | + |
793 | +from canonical.config import config |
794 | from canonical.database.sqlbase import block_implicit_flushes |
795 | +from canonical.launchpad.helpers import get_contact_email_addresses |
796 | +from canonical.launchpad.mail import ( |
797 | + format_address, |
798 | + sendmail, |
799 | + ) |
800 | +from canonical.launchpad.webapp.publisher import canonical_url |
801 | +from lp.bugs.adapters.bugchange import ( |
802 | + BugDuplicateChange, |
803 | + BugTaskAssigneeChange, |
804 | + get_bug_changes, |
805 | + ) |
806 | +from lp.bugs.adapters.bugdelta import BugDelta |
807 | +from lp.bugs.interfaces.bugchange import IBugChange |
808 | +from lp.bugs.mail.bugnotificationbuilder import BugNotificationBuilder |
809 | +from lp.bugs.mail.bugnotificationrecipients import BugNotificationRecipients |
810 | +from lp.bugs.mail.newbug import generate_bug_add_email |
811 | +from lp.registry.enum import BugNotificationLevel |
812 | from lp.registry.interfaces.person import IPerson |
813 | +from lp.registry.interfaces.structuralsubscription import ( |
814 | + IStructuralSubscriptionTarget, |
815 | + ) |
816 | + |
817 | + |
818 | +@block_implicit_flushes |
819 | +def notify_bug_added(bug, event): |
820 | + """Send an email notification that a bug was added. |
821 | + |
822 | + Event must be an IObjectCreatedEvent. |
823 | + """ |
824 | + bug.addCommentNotification(bug.initial_message) |
825 | |
826 | |
827 | @block_implicit_flushes |
828 | def notify_bug_modified(bug, event): |
829 | """Handle bug change events. |
830 | |
831 | - Subscribe the security contacts for a bug when it |
832 | - becomes security-related. |
833 | + Subscribe the security contacts for a bug when it becomes |
834 | + security-related, and add notifications for the changes. |
835 | """ |
836 | if (event.object.security_related and |
837 | not event.object_before_modification.security_related): |
838 | @@ -23,3 +67,222 @@ |
839 | for pillar in bug.affected_pillars: |
840 | if pillar.security_contact is not None: |
841 | bug.subscribe(pillar.security_contact, IPerson(event.user)) |
842 | + |
843 | + bug_delta = get_bug_delta( |
844 | + old_bug=event.object_before_modification, |
845 | + new_bug=event.object, user=IPerson(event.user)) |
846 | + |
847 | + if bug_delta is not None: |
848 | + add_bug_change_notifications(bug_delta) |
849 | + |
850 | + |
851 | +@block_implicit_flushes |
852 | +def notify_bug_comment_added(bugmessage, event): |
853 | + """Notify CC'd list that a message was added to this bug. |
854 | + |
855 | + bugmessage must be an IBugMessage. event must be an |
856 | + IObjectCreatedEvent. If bugmessage.bug is a duplicate the |
857 | + comment will also be sent to the dup target's subscribers. |
858 | + """ |
859 | + bug = bugmessage.bug |
860 | + bug.addCommentNotification(bugmessage.message) |
861 | + |
862 | + |
863 | +@block_implicit_flushes |
864 | +def notify_bug_attachment_added(bugattachment, event): |
865 | + """Notify CC'd list that a new attachment has been added. |
866 | + |
867 | + bugattachment must be an IBugAttachment. event must be an |
868 | + IObjectCreatedEvent. |
869 | + """ |
870 | + bug = bugattachment.bug |
871 | + bug_delta = BugDelta( |
872 | + bug=bug, |
873 | + bugurl=canonical_url(bug), |
874 | + user=IPerson(event.user), |
875 | + attachment={'new': bugattachment, 'old': None}) |
876 | + |
877 | + add_bug_change_notifications(bug_delta) |
878 | + |
879 | + |
880 | +@block_implicit_flushes |
881 | +def notify_bug_attachment_removed(bugattachment, event): |
882 | + """Notify that an attachment has been removed.""" |
883 | + bug = bugattachment.bug |
884 | + bug_delta = BugDelta( |
885 | + bug=bug, |
886 | + bugurl=canonical_url(bug), |
887 | + user=IPerson(event.user), |
888 | + attachment={'old': bugattachment, 'new': None}) |
889 | + |
890 | + add_bug_change_notifications(bug_delta) |
891 | + |
892 | + |
893 | +@block_implicit_flushes |
894 | +def notify_bug_subscription_added(bug_subscription, event): |
895 | + """Notify that a new bug subscription was added.""" |
896 | + # When a user is subscribed to a bug by someone other |
897 | + # than themselves, we send them a notification email. |
898 | + if bug_subscription.person != bug_subscription.subscribed_by: |
899 | + send_bug_details_to_new_bug_subscribers( |
900 | + bug_subscription.bug, [], [bug_subscription.person], |
901 | + subscribed_by=bug_subscription.subscribed_by) |
902 | + |
903 | + |
904 | +def get_bug_delta(old_bug, new_bug, user): |
905 | + """Compute the delta from old_bug to new_bug. |
906 | + |
907 | + old_bug and new_bug are IBug's. user is an IPerson. Returns an |
908 | + IBugDelta if there are changes, or None if there were no changes. |
909 | + """ |
910 | + changes = {} |
911 | + |
912 | + for field_name in ("title", "description", "name", "private", |
913 | + "security_related", "duplicateof", "tags"): |
914 | + # fields for which we show old => new when their values change |
915 | + old_val = getattr(old_bug, field_name) |
916 | + new_val = getattr(new_bug, field_name) |
917 | + if old_val != new_val: |
918 | + changes[field_name] = {} |
919 | + changes[field_name]["old"] = old_val |
920 | + changes[field_name]["new"] = new_val |
921 | + |
922 | + if changes: |
923 | + changes["bug"] = new_bug |
924 | + changes["bug_before_modification"] = old_bug |
925 | + changes["bugurl"] = canonical_url(new_bug) |
926 | + changes["user"] = user |
927 | + return BugDelta(**changes) |
928 | + else: |
929 | + return None |
930 | + |
931 | + |
932 | +def get_bugtask_indirect_subscribers(bugtask, recipients=None, level=None): |
933 | + """Return the indirect subscribers for a bug task. |
934 | + |
935 | + Return the list of people who should get notifications about |
936 | + changes to the task because of having an indirect subscription |
937 | + relationship with it (by subscribing to its target, being an |
938 | + assignee or owner, etc...) |
939 | + |
940 | + If `recipients` is present, add the subscribers to the set of |
941 | + bug notification recipients. |
942 | + """ |
943 | + if bugtask.bug.private: |
944 | + return set() |
945 | + |
946 | + also_notified_subscribers = set() |
947 | + |
948 | + # Assignees are indirect subscribers. |
949 | + if bugtask.assignee: |
950 | + also_notified_subscribers.add(bugtask.assignee) |
951 | + if recipients is not None: |
952 | + recipients.addAssignee(bugtask.assignee) |
953 | + |
954 | + if IStructuralSubscriptionTarget.providedBy(bugtask.target): |
955 | + also_notified_subscribers.update( |
956 | + bugtask.target.getBugNotificationsRecipients( |
957 | + recipients, level=level)) |
958 | + |
959 | + if bugtask.milestone is not None: |
960 | + also_notified_subscribers.update( |
961 | + bugtask.milestone.getBugNotificationsRecipients( |
962 | + recipients, level=level)) |
963 | + |
964 | + # If the target's bug supervisor isn't set, |
965 | + # we add the owner as a subscriber. |
966 | + pillar = bugtask.pillar |
967 | + if pillar.bug_supervisor is None: |
968 | + also_notified_subscribers.add(pillar.owner) |
969 | + if recipients is not None: |
970 | + recipients.addRegistrant(pillar.owner, pillar) |
971 | + |
972 | + return sorted( |
973 | + also_notified_subscribers, |
974 | + key=attrgetter('displayname')) |
975 | + |
976 | + |
977 | +def add_bug_change_notifications(bug_delta, old_bugtask=None, |
978 | + new_subscribers=None): |
979 | + """Generate bug notifications and add them to the bug.""" |
980 | + changes = get_bug_changes(bug_delta) |
981 | + recipients = bug_delta.bug.getBugNotificationRecipients( |
982 | + old_bug=bug_delta.bug_before_modification, |
983 | + level=BugNotificationLevel.METADATA) |
984 | + if old_bugtask is not None: |
985 | + old_bugtask_recipients = BugNotificationRecipients() |
986 | + get_bugtask_indirect_subscribers( |
987 | + old_bugtask, recipients=old_bugtask_recipients, |
988 | + level=BugNotificationLevel.METADATA) |
989 | + recipients.update(old_bugtask_recipients) |
990 | + for change in changes: |
991 | + # XXX 2009-03-17 gmb [bug=344125] |
992 | + # This if..else should be removed once the new BugChange API |
993 | + # is complete and ubiquitous. |
994 | + if IBugChange.providedBy(change): |
995 | + if isinstance(change, BugDuplicateChange): |
996 | + no_dupe_master_recipients = ( |
997 | + bug_delta.bug.getBugNotificationRecipients( |
998 | + old_bug=bug_delta.bug_before_modification, |
999 | + level=BugNotificationLevel.METADATA, |
1000 | + include_master_dupe_subscribers=False)) |
1001 | + bug_delta.bug.addChange( |
1002 | + change, recipients=no_dupe_master_recipients) |
1003 | + elif (isinstance(change, BugTaskAssigneeChange) and |
1004 | + new_subscribers is not None): |
1005 | + for person in new_subscribers: |
1006 | + reason, rationale = recipients.getReason(person) |
1007 | + if 'Assignee' in rationale: |
1008 | + recipients.remove(person) |
1009 | + bug_delta.bug.addChange(change, recipients=recipients) |
1010 | + else: |
1011 | + bug_delta.bug.addChange(change, recipients=recipients) |
1012 | + else: |
1013 | + bug_delta.bug.addChangeNotification( |
1014 | + change, person=bug_delta.user, recipients=recipients) |
1015 | + |
1016 | + |
1017 | +def send_bug_details_to_new_bug_subscribers( |
1018 | + bug, previous_subscribers, current_subscribers, subscribed_by=None, |
1019 | + event_creator=None): |
1020 | + """Send an email containing full bug details to new bug subscribers. |
1021 | + |
1022 | + This function is designed to handle situations where bugtasks get |
1023 | + reassigned to new products or sourcepackages, and the new bug subscribers |
1024 | + need to be notified of the bug. |
1025 | + """ |
1026 | + prev_subs_set = set(previous_subscribers) |
1027 | + cur_subs_set = set(current_subscribers) |
1028 | + new_subs = cur_subs_set.difference(prev_subs_set) |
1029 | + |
1030 | + to_addrs = set() |
1031 | + for new_sub in new_subs: |
1032 | + to_addrs.update(get_contact_email_addresses(new_sub)) |
1033 | + |
1034 | + if not to_addrs: |
1035 | + return |
1036 | + |
1037 | + from_addr = format_address( |
1038 | + 'Launchpad Bug Tracker', |
1039 | + "%s@%s" % (bug.id, config.launchpad.bugs_domain)) |
1040 | + # Now's a good a time as any for this email; don't use the original |
1041 | + # reported date for the bug as it will just confuse mailer and |
1042 | + # recipient. |
1043 | + email_date = datetime.datetime.now() |
1044 | + |
1045 | + # The new subscriber email is effectively the initial message regarding |
1046 | + # a new bug. The bug's initial message is used in the References |
1047 | + # header to establish the message's context in the email client. |
1048 | + references = [bug.initial_message.rfc822msgid] |
1049 | + recipients = bug.getBugNotificationRecipients() |
1050 | + |
1051 | + bug_notification_builder = BugNotificationBuilder(bug, event_creator) |
1052 | + for to_addr in sorted(to_addrs): |
1053 | + reason, rationale = recipients.getReason(to_addr) |
1054 | + subject, contents = generate_bug_add_email( |
1055 | + bug, new_recipients=True, subscribed_by=subscribed_by, |
1056 | + reason=reason, event_creator=event_creator) |
1057 | + msg = bug_notification_builder.build( |
1058 | + from_addr, to_addr, contents, subject, email_date, |
1059 | + rationale=rationale, references=references) |
1060 | + sendmail(msg) |
1061 | |
1062 | === modified file 'lib/lp/bugs/subscribers/bugcreation.py' |
1063 | --- lib/lp/bugs/subscribers/bugcreation.py 2010-08-20 20:31:18 +0000 |
1064 | +++ lib/lp/bugs/subscribers/bugcreation.py 2010-08-23 20:18:06 +0000 |
1065 | @@ -2,8 +2,10 @@ |
1066 | # GNU Affero General Public License version 3 (see the file LICENSE). |
1067 | |
1068 | __metaclass__ = type |
1069 | +__all__ = [ |
1070 | + 'at_least_one_task', |
1071 | + ] |
1072 | |
1073 | -from canonical.database.sqlbase import block_implicit_flushes |
1074 | from lp.bugs.interfaces.bug import CreatedBugWithNoBugTasksError |
1075 | |
1076 | |
1077 | |
1078 | === added file 'lib/lp/bugs/subscribers/bugtask.py' |
1079 | --- lib/lp/bugs/subscribers/bugtask.py 1970-01-01 00:00:00 +0000 |
1080 | +++ lib/lp/bugs/subscribers/bugtask.py 2010-08-23 20:18:06 +0000 |
1081 | @@ -0,0 +1,79 @@ |
1082 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
1083 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
1084 | + |
1085 | +__metaclass__ = type |
1086 | +__all__ = [ |
1087 | + 'notify_bugtask_edited', |
1088 | + 'update_security_contact_subscriptions', |
1089 | + ] |
1090 | + |
1091 | + |
1092 | +from canonical.database.sqlbase import block_implicit_flushes |
1093 | +from canonical.launchpad.webapp.publisher import canonical_url |
1094 | +from lp.bugs.adapters.bugdelta import BugDelta |
1095 | +from lp.bugs.interfaces.bugtask import IUpstreamBugTask |
1096 | +from lp.bugs.subscribers.bug import ( |
1097 | + add_bug_change_notifications, |
1098 | + send_bug_details_to_new_bug_subscribers, |
1099 | + ) |
1100 | +from lp.registry.interfaces.person import IPerson |
1101 | + |
1102 | + |
1103 | +@block_implicit_flushes |
1104 | +def update_security_contact_subscriptions(event): |
1105 | + """Subscribe the new security contact when a bugtask's product changes. |
1106 | + |
1107 | + Only subscribes the new security contact if the bug was marked a |
1108 | + security issue originally. |
1109 | + |
1110 | + No change is made for private bugs. |
1111 | + """ |
1112 | + if event.object.bug.private: |
1113 | + return |
1114 | + |
1115 | + if not IUpstreamBugTask.providedBy(event.object): |
1116 | + return |
1117 | + |
1118 | + bugtask_before_modification = event.object_before_modification |
1119 | + bugtask_after_modification = event.object |
1120 | + |
1121 | + if (bugtask_before_modification.product != |
1122 | + bugtask_after_modification.product): |
1123 | + new_product = bugtask_after_modification.product |
1124 | + if (bugtask_before_modification.bug.security_related and |
1125 | + new_product.security_contact): |
1126 | + bugtask_after_modification.bug.subscribe( |
1127 | + new_product.security_contact, IPerson(event.user)) |
1128 | + |
1129 | + |
1130 | +@block_implicit_flushes |
1131 | +def notify_bugtask_edited(modified_bugtask, event): |
1132 | + """Notify CC'd subscribers of this bug that something has changed |
1133 | + on this task. |
1134 | + |
1135 | + modified_bugtask must be an IBugTask. event must be an |
1136 | + IObjectModifiedEvent. |
1137 | + """ |
1138 | + bugtask_delta = event.object.getDelta(event.object_before_modification) |
1139 | + bug_delta = BugDelta( |
1140 | + bug=event.object.bug, |
1141 | + bugurl=canonical_url(event.object.bug), |
1142 | + bugtask_deltas=bugtask_delta, |
1143 | + user=IPerson(event.user)) |
1144 | + |
1145 | + event_creator = IPerson(event.user) |
1146 | + previous_subscribers = event.object_before_modification.bug_subscribers |
1147 | + current_subscribers = event.object.bug_subscribers |
1148 | + prev_subs_set = set(previous_subscribers) |
1149 | + cur_subs_set = set(current_subscribers) |
1150 | + new_subs = cur_subs_set.difference(prev_subs_set) |
1151 | + |
1152 | + add_bug_change_notifications( |
1153 | + bug_delta, old_bugtask=event.object_before_modification, |
1154 | + new_subscribers=new_subs) |
1155 | + |
1156 | + send_bug_details_to_new_bug_subscribers( |
1157 | + event.object.bug, previous_subscribers, current_subscribers, |
1158 | + event_creator=event_creator) |
1159 | + |
1160 | + update_security_contact_subscriptions(event) |
This is almost entirely just moving code; I have avoided making changes to the logic.