Merge lp:~cjwatson/launchpad/answers-mail-team into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 17697
Proposed branch: lp:~cjwatson/launchpad/answers-mail-team
Merge into: lp:launchpad
Diff against target: 245 lines (+94/-44)
4 files modified
lib/lp/answers/doc/question.txt (+11/-10)
lib/lp/answers/mail/question.py (+46/-0)
lib/lp/answers/model/question.py (+25/-26)
lib/lp/answers/model/questionjob.py (+12/-8)
To merge this branch: bzr merge lp:~cjwatson/launchpad/answers-mail-team
Reviewer Review Type Date Requested Status
William Grant (community) code Approve
Review via email: mp+269091@code.launchpad.net

Commit message

Make question notification rationales more consistent, including team annotations for subscribers.

Description of the change

Make question notification rationales more consistent, including team annotations for subscribers.

This isn't a full conversion to BaseMailer, but I made use of RecipientReason from there because it handles all the rationale logic for teams.

As a bonus, this fixes an inconsistency that apparently nobody had noticed before, in which mail to answer contacts included the target name in parentheses if the answer contact was a team, but the target display name if the answer contact was a person.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) wrote :

I'd be tempted to use phrases such as "assigned to" and "subscribed to", but perhaps that'd break a lot of tests.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/answers/doc/question.txt'
2--- lib/lp/answers/doc/question.txt 2014-04-24 08:21:42 +0000
3+++ lib/lp/answers/doc/question.txt 2015-08-26 12:29:35 +0000
4@@ -212,7 +212,8 @@
5 True
6 >>> def print_reason(subscribers):
7 ... for person in subscribers:
8- ... text, header = subscribers.getReason(person)
9+ ... reason, header = subscribers.getReason(person)
10+ ... text = removeSecurityProxy(reason).getReason()
11 ... print header, person.displayname, text
12 >>> print_reason(subscribers)
13 Asker Sample Person
14@@ -239,7 +240,7 @@
15 >>> verifyObject(INotificationRecipientSet, indirect_subscribers)
16 True
17 >>> print_reason(indirect_subscribers)
18- Answer Contact (Mozilla Firefox) No Privileges Person
19+ Answer Contact (firefox) No Privileges Person
20 You received this question notification because you are an answer
21 contact for Mozilla Firefox.
22
23@@ -273,11 +274,11 @@
24 No Privileges Person
25 Ubuntu Team
26
27- >>> text, header = indirect_subscribers.getReason(ubuntu_team)
28- >>> print header, text
29+ >>> reason, header = indirect_subscribers.getReason(ubuntu_team)
30+ >>> print header, removeSecurityProxy(reason).getReason()
31 Answer Contact (ubuntu) @ubuntu-team
32- You received this question notification because you are a member of
33- Ubuntu Team, which is an answer contact for Ubuntu.
34+ You received this question notification because your team Ubuntu Team is
35+ an answer contact for Ubuntu.
36
37 The question's assignee is also part of the indirect subscription list:
38
39@@ -291,12 +292,12 @@
40 No Privileges Person
41 Ubuntu Team
42
43- >>> text, header = indirect_subscribers.getReason(
44+ >>> reason, header = indirect_subscribers.getReason(
45 ... package_question.assignee)
46- >>> print header, text
47+ >>> print header, removeSecurityProxy(reason).getReason()
48 Assignee
49- You received this question notification because you are the assignee for
50- this question.
51+ You received this question notification because you are assigned to this
52+ question.
53
54 The getIndirectSubscribers() method iterates like the indirect_recipients
55 method, but it returns a sorted list instead of a NotificationRecipientSet.
56
57=== added file 'lib/lp/answers/mail/question.py'
58--- lib/lp/answers/mail/question.py 1970-01-01 00:00:00 +0000
59+++ lib/lp/answers/mail/question.py 2015-08-26 12:29:35 +0000
60@@ -0,0 +1,46 @@
61+# Copyright 2015 Canonical Ltd. This software is licensed under the
62+# GNU Affero General Public License version 3 (see the file LICENSE).
63+
64+__metaclass__ = type
65+__all__ = [
66+ 'QuestionRecipientReason',
67+ ]
68+
69+from lp.services.mail.basemailer import RecipientReason
70+
71+
72+class QuestionRecipientReason(RecipientReason):
73+
74+ @classmethod
75+ def forSubscriber(cls, subscriber, recipient):
76+ header = cls.makeRationale("Subscriber", subscriber)
77+ reason = (
78+ "You received this question notification because "
79+ "%(lc_entity_is)s subscribed to the question.")
80+ return cls(subscriber, recipient, header, reason)
81+
82+ @classmethod
83+ def forAsker(cls, asker, recipient):
84+ header = cls.makeRationale("Asker", asker)
85+ reason = (
86+ "You received this question notification because you asked the "
87+ "question.")
88+ return cls(asker, recipient, header, reason)
89+
90+ @classmethod
91+ def forAssignee(cls, assignee, recipient):
92+ header = cls.makeRationale("Assignee", assignee)
93+ reason = (
94+ "You received this question notification because "
95+ "%(lc_entity_is)s assigned to this question.")
96+ return cls(assignee, recipient, header, reason)
97+
98+ @classmethod
99+ def forAnswerContact(cls, answer_contact, recipient, target_name,
100+ target_displayname):
101+ header = cls.makeRationale(
102+ "Answer Contact (%s)" % target_name, answer_contact)
103+ reason = (
104+ "You received this question notification because "
105+ "%%(lc_entity_is)s an answer contact for %s." % target_displayname)
106+ return cls(answer_contact, recipient, header, reason)
107
108=== modified file 'lib/lp/answers/model/question.py'
109--- lib/lp/answers/model/question.py 2015-07-08 16:05:11 +0000
110+++ lib/lp/answers/model/question.py 2015-08-26 12:29:35 +0000
111@@ -1,4 +1,4 @@
112-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
113+# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
114 # GNU Affero General Public License version 3 (see the file LICENSE).
115
116 """Question models."""
117@@ -66,6 +66,7 @@
118 from lp.answers.interfaces.question import IQuestion
119 from lp.answers.interfaces.questioncollection import IQuestionSet
120 from lp.answers.interfaces.questiontarget import IQuestionTarget
121+from lp.answers.mail.question import QuestionRecipientReason
122 from lp.answers.model.answercontact import AnswerContact
123 from lp.answers.model.questionmessage import QuestionMessage
124 from lp.answers.model.questionreopening import create_questionreopening
125@@ -586,26 +587,30 @@
126 @cachedproperty
127 def direct_recipients(self):
128 """See `IQuestion`."""
129+ # Circular import.
130+ from lp.registry.model.person import get_recipients
131 subscribers = NotificationRecipientSet()
132- reason = ("You received this question notification because you are "
133- "a direct subscriber of the question.")
134- subscribers.add(self.subscribers, reason, 'Subscriber')
135- if self.owner in subscribers:
136- subscribers.remove(self.owner)
137- reason = (
138- "You received this question notification because you "
139- "asked the question.")
140- subscribers.add(self.owner, reason, 'Asker')
141+ for subscriber in self.subscribers:
142+ if subscriber == self.owner:
143+ reason_factory = QuestionRecipientReason.forAsker
144+ else:
145+ reason_factory = QuestionRecipientReason.forSubscriber
146+ for recipient in get_recipients(subscriber):
147+ reason = reason_factory(subscriber, recipient)
148+ subscribers.add(recipient, reason, reason.mail_header)
149 return subscribers
150
151 @cachedproperty
152 def indirect_recipients(self):
153 """See `IQuestion`."""
154+ # Circular import.
155+ from lp.registry.model.person import get_recipients
156 subscribers = self.target.getAnswerContactRecipients(self.language)
157 if self.assignee:
158- reason = ('You received this question notification because you '
159- 'are the assignee for this question.')
160- subscribers.add(self.assignee, reason, 'Assignee')
161+ for recipient in get_recipients(self.assignee):
162+ reason = QuestionRecipientReason.forAssignee(
163+ self.assignee, recipient)
164+ subscribers.add(recipient, reason, reason.mail_header)
165 return subscribers
166
167 def _newMessage(self, owner, content, action, new_status, subject=None,
168@@ -1423,24 +1428,18 @@
169
170 def getAnswerContactRecipients(self, language):
171 """See `IQuestionTarget`."""
172+ # Circular import.
173+ from lp.registry.model.person import get_recipients
174 if language is None:
175 contacts = self.answer_contacts
176 else:
177 contacts = self.getAnswerContactsForLanguage(language)
178 recipients = NotificationRecipientSet()
179- for person in contacts:
180- reason_start = (
181- "You received this question notification because you are ")
182- if person.is_team:
183- reason = reason_start + (
184- 'a member of %s, which is an answer contact for %s.' % (
185- person.displayname, self.displayname))
186- header = 'Answer Contact (%s) @%s' % (self.name, person.name)
187- else:
188- reason = reason_start + (
189- 'an answer contact for %s.' % self.displayname)
190- header = 'Answer Contact (%s)' % self.displayname
191- recipients.add(person, reason, header)
192+ for contact in contacts:
193+ for recipient in get_recipients(contact):
194+ reason = QuestionRecipientReason.forAnswerContact(
195+ contact, recipient, self.name, self.displayname)
196+ recipients.add(recipient, reason, reason.mail_header)
197 return recipients
198
199 def removeAnswerContact(self, person, subscribed_by):
200
201=== modified file 'lib/lp/answers/model/questionjob.py'
202--- lib/lp/answers/model/questionjob.py 2015-07-09 20:06:17 +0000
203+++ lib/lp/answers/model/questionjob.py 2015-08-26 12:29:35 +0000
204@@ -1,4 +1,4 @@
205-# Copyright 2011-2012 Canonical Ltd. This software is licensed under the
206+# Copyright 2011-2015 Canonical Ltd. This software is licensed under the
207 # GNU Affero General Public License version 3 (see the file LICENSE).
208
209 """Job classes related to QuestionJob."""
210@@ -193,14 +193,18 @@
211 recipients = NotificationRecipientSet()
212 owner = self.question.owner
213 original_recipients = self.question.direct_recipients
214- if owner in original_recipients:
215- rationale, header = original_recipients.getReason(owner)
216- recipients.add(owner, rationale, header)
217+ for recipient in original_recipients:
218+ reason, header = original_recipients.getReason(recipient)
219+ if reason.subscriber == owner:
220+ recipients.add(recipient, reason, header)
221 return recipients
222 elif question_recipient_set == QuestionRecipientSet.SUBSCRIBER:
223 recipients = self.question.getRecipients()
224- if self.question.owner in recipients:
225- recipients.remove(self.question.owner)
226+ owner = self.question.owner
227+ asker_recipients = [
228+ recipient for recipient in recipients
229+ if recipients.getReason(recipient)[0].subscriber == owner]
230+ recipients.remove(asker_recipients)
231 return recipients
232 elif question_recipient_set == QuestionRecipientSet.ASKER_SUBSCRIBER:
233 return self.question.getRecipients()
234@@ -230,9 +234,9 @@
235 headers = self.headers
236 recipients = self.recipients
237 for email in recipients.getEmails():
238- rationale, header = recipients.getReason(email)
239+ reason, header = recipients.getReason(email)
240 headers['X-Launchpad-Message-Rationale'] = header
241- formatted_body = self.buildBody(rationale)
242+ formatted_body = self.buildBody(reason.getReason())
243 simple_sendmail(
244 self.from_address, email, self.subject, formatted_body,
245 headers)