Merge lp:~sinzui/launchpad/question-email-3 into lp:launchpad
- question-email-3
- Merge into devel
Status: | Merged | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Merged at revision: | 12976 | ||||||||||||||||||||
Proposed branch: | lp:~sinzui/launchpad/question-email-3 | ||||||||||||||||||||
Merge into: | lp:launchpad | ||||||||||||||||||||
Diff against target: |
2719 lines (+622/-934) 14 files modified
database/schema/security.cfg (+220/-212) lib/lp/answers/browser/tests/views.txt (+0/-56) lib/lp/answers/doc/notifications.txt (+84/-386) lib/lp/answers/model/questionjob.py (+2/-1) lib/lp/answers/notification.py (+22/-79) lib/lp/answers/stories/question-confirm-url.txt (+0/-105) lib/lp/answers/tests/test_question_notifications.py (+143/-30) lib/lp/answers/tests/test_questionjob.py (+25/-2) lib/lp/bugs/tests/test_bugnotification.py (+11/-9) lib/lp/coop/answersbugs/tests/notifications-linked-bug.txt (+17/-16) lib/lp/coop/answersbugs/tests/notifications-linked-private-bug.txt (+6/-3) lib/lp/services/mail/doc/notification-recipient-set.txt (+72/-35) lib/lp/services/mail/notificationrecipientset.py (+1/-0) lib/lp/services/mail/tests/test_doc.py (+19/-0) |
||||||||||||||||||||
To merge this branch: | bzr merge lp:~sinzui/launchpad/question-email-3 | ||||||||||||||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
j.c.sackett (community) | Approve | ||
Benji York (community) | Abstain | ||
Review via email: mp+59574@code.launchpad.net |
Commit message
Description of the change
Send question email asynchronously.
Launchpad bug:
https:/
https:/
https:/
Pre-
Change QuestionNotific
This addresses four kinds of timeouts that happen when a question needs
email sent to many users.
-------
RULES
* Replace QuestionNotific
* Remove unneeded methods and tests that were used to send email.
* Update the notification doctests to to use questionemailjobs instead
of sent email.
QA
* Create a question on qastaging.
* Ask an admin to run cronscripts/
qastaging
* Verify the email arrives in the staging inbox.
LINT
database/
lib/
lib/
lib/
lib/
lib/
TEST
./bin/test -vv -t answers.
IMPLEMENTATION
Gave all processes that could update a bug or question access to questionjob.
database/
Added the enqueue() method and the recipient_set attribute.
Removes send(), getRecipients(), buildBody(), getFromAddress() methods.
lib/
lib/
/o\ More than 1000 lines of the diff is dedicated to updating the existing
doctests to check questionemailjob instead of sent emails. 900 lines are
just notifications.txt. For all three tests, I added a utility that behaves
like pop_notification, but questionemailjobs are not emails. I deleted all
tests for email addresses and reason footers, because those are provided by
recipientsets, which are already tested in question.txt and elsewhere. The
same is true the the two chunks about team email, I deleted them
since Question, not QuestionNotific
cases where the counts of notifications changed. This is because notification
were implicitly created when the test question was created; the tests were
were somewhat wrong. Since the sort rules changed, email addresses versus
recipientset types, The order of question asker and subscriber changed.
lib/
lib/
lib/
j.c.sackett (jcsackett) wrote : | # |
Curtis--
This mostly looks solid.
I have only one minor quibble. Names like
{{{
class TestQuestionNot
}}}
strike me as somewhat confusing, since they follow our naming convention for testcases. I think something like FakeQuestionNot
This (and the other branches in this set of work) are really great. Thanks for them.
Preview Diff
1 | === modified file 'database/schema/security.cfg' | |||
2 | --- database/schema/security.cfg 2011-05-03 12:52:05 +0000 | |||
3 | +++ database/schema/security.cfg 2011-05-04 17:17:01 +0000 | |||
4 | @@ -583,6 +583,7 @@ | |||
5 | 583 | public.productseries = SELECT | 583 | public.productseries = SELECT |
6 | 584 | public.project = SELECT, UPDATE | 584 | public.project = SELECT, UPDATE |
7 | 585 | public.question = SELECT | 585 | public.question = SELECT |
8 | 586 | public.questionjob = SELECT, INSERT | ||
9 | 586 | public.questionbug = SELECT | 587 | public.questionbug = SELECT |
10 | 587 | public.questionsubscription = SELECT | 588 | public.questionsubscription = SELECT |
11 | 588 | public.section = SELECT | 589 | public.section = SELECT |
12 | @@ -599,56 +600,56 @@ | |||
13 | 599 | 600 | ||
14 | 600 | [branchscanner] | 601 | [branchscanner] |
15 | 601 | groups=write, script | 602 | groups=write, script |
60 | 602 | public.account = SELECT, INSERT | 603 | public.account = SELECT, INSERT |
61 | 603 | public.accountpassword = SELECT, INSERT | 604 | public.accountpassword = SELECT, INSERT |
62 | 604 | public.branch = SELECT, UPDATE | 605 | public.branch = SELECT, UPDATE |
63 | 605 | public.branchjob = SELECT, INSERT, UPDATE, DELETE | 606 | public.branchjob = SELECT, INSERT, UPDATE, DELETE |
64 | 606 | public.branchmergeproposal = SELECT, UPDATE | 607 | public.branchmergeproposal = SELECT, UPDATE |
65 | 607 | public.branchmergeproposaljob = SELECT, INSERT | 608 | public.branchmergeproposaljob = SELECT, INSERT |
66 | 608 | public.branchrevision = SELECT, INSERT, UPDATE, DELETE | 609 | public.branchrevision = SELECT, INSERT, UPDATE, DELETE |
67 | 609 | public.branchsubscription = SELECT | 610 | public.branchsubscription = SELECT |
68 | 610 | public.branchvisibilitypolicy = SELECT | 611 | public.branchvisibilitypolicy = SELECT |
69 | 611 | public.bugactivity = SELECT, INSERT | 612 | public.bugactivity = SELECT, INSERT |
70 | 612 | public.bugaffectsperson = SELECT, INSERT, UPDATE, DELETE | 613 | public.bugaffectsperson = SELECT, INSERT, UPDATE, DELETE |
71 | 613 | public.bugbranch = SELECT, INSERT, UPDATE | 614 | public.bugbranch = SELECT, INSERT, UPDATE |
72 | 614 | public.bugnotification = SELECT, INSERT | 615 | public.bugnotification = SELECT, INSERT |
73 | 615 | public.bugnotificationfilter = SELECT, INSERT | 616 | public.bugnotificationfilter = SELECT, INSERT |
74 | 616 | public.bugnotificationrecipient = SELECT, INSERT | 617 | public.bugnotificationrecipient = SELECT, INSERT |
75 | 617 | public.bugsubscription = SELECT | 618 | public.bugsubscription = SELECT |
76 | 618 | public.bugsubscriptionfilter = SELECT | 619 | public.bugsubscriptionfilter = SELECT |
77 | 619 | public.bugsubscriptionfilterimportance = SELECT | 620 | public.bugsubscriptionfilterimportance = SELECT |
78 | 620 | public.bugsubscriptionfilterstatus = SELECT | 621 | public.bugsubscriptionfilterstatus = SELECT |
79 | 621 | public.bugsubscriptionfiltertag = SELECT | 622 | public.bugsubscriptionfiltertag = SELECT |
80 | 622 | public.bugtag = SELECT | 623 | public.bugtag = SELECT |
81 | 623 | public.codereviewmessage = SELECT | 624 | public.codereviewmessage = SELECT |
82 | 624 | public.codereviewvote = SELECT | 625 | public.codereviewvote = SELECT |
83 | 625 | public.diff = SELECT, INSERT, DELETE | 626 | public.diff = SELECT, INSERT, DELETE |
84 | 626 | public.distribution = SELECT | 627 | public.distribution = SELECT |
85 | 627 | public.distributionsourcepackage = SELECT, UPDATE | 628 | public.distributionsourcepackage = SELECT, UPDATE |
86 | 628 | public.distroseries = SELECT | 629 | public.distroseries = SELECT |
87 | 629 | public.emailaddress = SELECT | 630 | public.emailaddress = SELECT |
88 | 630 | public.incrementaldiff = SELECT | 631 | public.incrementaldiff = SELECT |
89 | 631 | public.job = SELECT, INSERT, UPDATE, DELETE | 632 | public.job = SELECT, INSERT, UPDATE, DELETE |
90 | 632 | public.karma = SELECT, INSERT | 633 | public.karma = SELECT, INSERT |
91 | 633 | public.karmaaction = SELECT | 634 | public.karmaaction = SELECT |
92 | 634 | public.message = SELECT, INSERT | 635 | public.message = SELECT, INSERT |
93 | 635 | public.messagechunk = SELECT, INSERT | 636 | public.messagechunk = SELECT, INSERT |
94 | 636 | public.person = SELECT | 637 | public.person = SELECT |
95 | 637 | public.revision = SELECT, INSERT, UPDATE | 638 | public.revision = SELECT, INSERT, UPDATE |
96 | 638 | public.revisionauthor = SELECT, INSERT, UPDATE | 639 | public.revisionauthor = SELECT, INSERT, UPDATE |
97 | 639 | public.revisioncache = SELECT, INSERT | 640 | public.revisioncache = SELECT, INSERT |
98 | 640 | public.revisionparent = SELECT, INSERT | 641 | public.revisionparent = SELECT, INSERT |
99 | 641 | public.revisionproperty = SELECT, INSERT | 642 | public.revisionproperty = SELECT, INSERT |
100 | 642 | public.seriessourcepackagebranch = SELECT | 643 | public.seriessourcepackagebranch = SELECT |
101 | 643 | public.sourcepackagename = SELECT | 644 | public.sourcepackagename = SELECT |
102 | 644 | public.sourcepackagerecipe = SELECT, UPDATE | 645 | public.sourcepackagerecipe = SELECT, UPDATE |
103 | 645 | public.sourcepackagerecipedata = SELECT | 646 | public.sourcepackagerecipedata = SELECT |
104 | 646 | public.sourcepackagerecipedatainstruction = SELECT | 647 | public.sourcepackagerecipedatainstruction = SELECT |
110 | 647 | public.staticdiff = SELECT, INSERT, DELETE | 648 | public.staticdiff = SELECT, INSERT, DELETE |
111 | 648 | public.structuralsubscription = SELECT | 649 | public.structuralsubscription = SELECT |
112 | 649 | public.translationtemplatesbuild = SELECT, INSERT | 650 | public.translationtemplatesbuild = SELECT, INSERT |
113 | 650 | public.validpersoncache = SELECT | 651 | public.validpersoncache = SELECT |
114 | 651 | public.validpersonorteamcache = SELECT | 652 | public.validpersonorteamcache = SELECT |
115 | 652 | type=user | 653 | type=user |
116 | 653 | 654 | ||
117 | 654 | [branch-distro] | 655 | [branch-distro] |
118 | @@ -682,36 +683,36 @@ | |||
119 | 682 | 683 | ||
120 | 683 | [distributionmirror] | 684 | [distributionmirror] |
121 | 684 | groups=script | 685 | groups=script |
152 | 685 | public.account = SELECT | 686 | public.account = SELECT |
153 | 686 | public.archive = SELECT | 687 | public.archive = SELECT |
154 | 687 | public.archivearch = SELECT | 688 | public.archivearch = SELECT |
155 | 688 | public.binarypackagebuild = SELECT | 689 | public.binarypackagebuild = SELECT |
156 | 689 | public.binarypackagefile = SELECT | 690 | public.binarypackagefile = SELECT |
157 | 690 | public.binarypackagename = SELECT | 691 | public.binarypackagename = SELECT |
158 | 691 | public.binarypackagepublishinghistory = SELECT | 692 | public.binarypackagepublishinghistory = SELECT |
159 | 692 | public.binarypackagerelease = SELECT | 693 | public.binarypackagerelease = SELECT |
160 | 693 | public.buildfarmjob = SELECT | 694 | public.buildfarmjob = SELECT |
161 | 694 | public.component = SELECT | 695 | public.component = SELECT |
162 | 695 | public.componentselection = SELECT | 696 | public.componentselection = SELECT |
163 | 696 | public.distribution = SELECT | 697 | public.distribution = SELECT |
164 | 697 | public.distributionmirror = SELECT, UPDATE | 698 | public.distributionmirror = SELECT, UPDATE |
165 | 698 | public.distroarchseries = SELECT | 699 | public.distroarchseries = SELECT |
166 | 699 | public.distroseries = SELECT | 700 | public.distroseries = SELECT |
167 | 700 | public.emailaddress = SELECT | 701 | public.emailaddress = SELECT |
168 | 701 | public.libraryfilealias = SELECT, INSERT | 702 | public.libraryfilealias = SELECT, INSERT |
169 | 702 | public.libraryfilecontent = SELECT, INSERT | 703 | public.libraryfilecontent = SELECT, INSERT |
170 | 703 | public.mirrorcdimagedistroseries = SELECT, INSERT, UPDATE, DELETE | 704 | public.mirrorcdimagedistroseries = SELECT, INSERT, UPDATE, DELETE |
171 | 704 | public.mirrordistroarchseries = SELECT, UPDATE, DELETE, INSERT | 705 | public.mirrordistroarchseries = SELECT, UPDATE, DELETE, INSERT |
172 | 705 | public.mirrordistroseriessource = SELECT, UPDATE, DELETE, INSERT | 706 | public.mirrordistroseriessource = SELECT, UPDATE, DELETE, INSERT |
173 | 706 | public.mirrorproberecord = SELECT, INSERT | 707 | public.mirrorproberecord = SELECT, INSERT |
174 | 707 | public.packagebuild = SELECT | 708 | public.packagebuild = SELECT |
175 | 708 | public.person = SELECT | 709 | public.person = SELECT |
176 | 709 | public.processorfamily = SELECT | 710 | public.processorfamily = SELECT |
177 | 710 | public.sourcepackagename = SELECT | 711 | public.sourcepackagename = SELECT |
178 | 711 | public.sourcepackagepublishinghistory = SELECT | 712 | public.sourcepackagepublishinghistory = SELECT |
179 | 712 | public.sourcepackagerelease = SELECT | 713 | public.sourcepackagerelease = SELECT |
180 | 713 | public.sourcepackagereleasefile = SELECT | 714 | public.sourcepackagereleasefile = SELECT |
181 | 714 | public.teammembership = SELECT | 715 | public.teammembership = SELECT |
182 | 715 | type=user | 716 | type=user |
183 | 716 | 717 | ||
184 | 717 | [teammembership] | 718 | [teammembership] |
185 | @@ -726,16 +727,16 @@ | |||
186 | 726 | 727 | ||
187 | 727 | [karma] | 728 | [karma] |
188 | 728 | groups=script | 729 | groups=script |
199 | 729 | public.emailaddress = SELECT | 730 | public.emailaddress = SELECT |
200 | 730 | public.karma = SELECT | 731 | public.karma = SELECT |
201 | 731 | public.karmaaction = SELECT | 732 | public.karmaaction = SELECT |
202 | 732 | public.karmacache = SELECT, INSERT, UPDATE, DELETE | 733 | public.karmacache = SELECT, INSERT, UPDATE, DELETE |
203 | 733 | public.karmacategory = SELECT | 734 | public.karmacategory = SELECT |
204 | 734 | public.karmatotalcache = SELECT, INSERT, UPDATE, DELETE | 735 | public.karmatotalcache = SELECT, INSERT, UPDATE, DELETE |
205 | 735 | public.person = SELECT | 736 | public.person = SELECT |
206 | 736 | public.product = SELECT | 737 | public.product = SELECT |
207 | 737 | public.validpersoncache = SELECT | 738 | public.validpersoncache = SELECT |
208 | 738 | public.validpersonorteamcache = SELECT | 739 | public.validpersonorteamcache = SELECT |
209 | 739 | type=user | 740 | type=user |
210 | 740 | 741 | ||
211 | 741 | [request-daily-builds] | 742 | [request-daily-builds] |
212 | @@ -783,33 +784,33 @@ | |||
213 | 783 | 784 | ||
214 | 784 | [cve] | 785 | [cve] |
215 | 785 | groups=script | 786 | groups=script |
218 | 786 | public.cve = SELECT, INSERT, UPDATE | 787 | public.cve = SELECT, INSERT, UPDATE |
219 | 787 | public.cvereference = SELECT, INSERT, UPDATE, DELETE | 788 | public.cvereference = SELECT, INSERT, UPDATE, DELETE |
220 | 788 | type=user | 789 | type=user |
221 | 789 | 790 | ||
222 | 790 | [gina] | 791 | [gina] |
223 | 791 | groups=write,script | 792 | groups=write,script |
234 | 792 | public.account = SELECT, INSERT | 793 | public.account = SELECT, INSERT |
235 | 793 | public.accountpassword = SELECT, INSERT | 794 | public.accountpassword = SELECT, INSERT |
236 | 794 | public.archive = SELECT, UPDATE | 795 | public.archive = SELECT, UPDATE |
237 | 795 | public.archivearch = SELECT, UPDATE | 796 | public.archivearch = SELECT, UPDATE |
238 | 796 | public.binarypackagepublishinghistory = SELECT, INSERT, UPDATE, DELETE | 797 | public.binarypackagepublishinghistory = SELECT, INSERT, UPDATE, DELETE |
239 | 797 | public.distribution = SELECT | 798 | public.distribution = SELECT |
240 | 798 | public.distributionjob = SELECT, INSERT | 799 | public.distributionjob = SELECT, INSERT |
241 | 799 | public.distributionsourcepackage = SELECT, INSERT | 800 | public.distributionsourcepackage = SELECT, INSERT |
242 | 800 | public.packagediff = SELECT, INSERT, UPDATE | 801 | public.packagediff = SELECT, INSERT, UPDATE |
243 | 801 | public.sourcepackagepublishinghistory = SELECT, INSERT, UPDATE, DELETE | 802 | public.sourcepackagepublishinghistory = SELECT, INSERT, UPDATE, DELETE |
244 | 802 | type=user | 803 | type=user |
245 | 803 | 804 | ||
246 | 804 | [archivepublisher] | 805 | [archivepublisher] |
247 | 805 | groups=write,script | 806 | groups=write,script |
248 | 806 | public.answercontact = SELECT | 807 | public.answercontact = SELECT |
255 | 807 | public.archive = SELECT, UPDATE | 808 | public.archive = SELECT, UPDATE |
256 | 808 | public.archivearch = SELECT | 809 | public.archivearch = SELECT |
257 | 809 | public.archiveauthtoken = SELECT, UPDATE | 810 | public.archiveauthtoken = SELECT, UPDATE |
258 | 810 | public.archivepermission = SELECT, INSERT | 811 | public.archivepermission = SELECT, INSERT |
259 | 811 | public.archivesubscriber = SELECT, UPDATE | 812 | public.archivesubscriber = SELECT, UPDATE |
260 | 812 | public.binarypackagepublishinghistory = SELECT, INSERT, UPDATE, DELETE | 813 | public.binarypackagepublishinghistory = SELECT, INSERT, UPDATE, DELETE |
261 | 813 | public.bug = SELECT, UPDATE | 814 | public.bug = SELECT, UPDATE |
262 | 814 | public.bugactivity = SELECT, INSERT | 815 | public.bugactivity = SELECT, INSERT |
263 | 815 | public.bugaffectsperson = SELECT, INSERT, UPDATE, DELETE | 816 | public.bugaffectsperson = SELECT, INSERT, UPDATE, DELETE |
264 | @@ -830,10 +831,11 @@ | |||
265 | 830 | public.bugtrackeralias = SELECT, INSERT | 831 | public.bugtrackeralias = SELECT, INSERT |
266 | 831 | public.bugwatch = SELECT, INSERT | 832 | public.bugwatch = SELECT, INSERT |
267 | 832 | public.cve = SELECT, INSERT | 833 | public.cve = SELECT, INSERT |
272 | 833 | public.distributionjob = SELECT, INSERT, DELETE | 834 | public.distributionjob = SELECT, INSERT, DELETE |
273 | 834 | public.distributionsourcepackage = SELECT, INSERT, UPDATE | 835 | public.distributionsourcepackage = SELECT, INSERT, UPDATE |
274 | 835 | public.flatpackagesetinclusion = SELECT, INSERT, UPDATE, DELETE | 836 | public.flatpackagesetinclusion = SELECT, INSERT, UPDATE, DELETE |
275 | 836 | public.gpgkey = SELECT, INSERT, UPDATE | 837 | public.gpgkey = SELECT, INSERT, UPDATE |
276 | 838 | public.job = SELECT, INSERT, UPDATE | ||
277 | 837 | public.karma = SELECT, INSERT | 839 | public.karma = SELECT, INSERT |
278 | 838 | public.karmaaction = SELECT | 840 | public.karmaaction = SELECT |
279 | 839 | public.language = SELECT | 841 | public.language = SELECT |
280 | @@ -841,21 +843,22 @@ | |||
281 | 841 | public.messagechunk = SELECT, INSERT | 843 | public.messagechunk = SELECT, INSERT |
282 | 842 | public.milestone = SELECT | 844 | public.milestone = SELECT |
283 | 843 | public.packagebugsupervisor = SELECT | 845 | public.packagebugsupervisor = SELECT |
290 | 844 | public.packagecopyrequest = SELECT, INSERT, UPDATE | 846 | public.packagecopyrequest = SELECT, INSERT, UPDATE |
291 | 845 | public.packagediff = SELECT, INSERT, UPDATE | 847 | public.packagediff = SELECT, INSERT, UPDATE |
292 | 846 | public.packageset = SELECT, INSERT | 848 | public.packageset = SELECT, INSERT |
293 | 847 | public.packagesetgroup = SELECT | 849 | public.packagesetgroup = SELECT |
294 | 848 | public.packagesetinclusion = SELECT, INSERT, UPDATE, DELETE | 850 | public.packagesetinclusion = SELECT, INSERT, UPDATE, DELETE |
295 | 849 | public.packagesetsources = SELECT, INSERT, UPDATE, DELETE | 851 | public.packagesetsources = SELECT, INSERT, UPDATE, DELETE |
296 | 850 | public.personlanguage = SELECT | 852 | public.personlanguage = SELECT |
297 | 851 | public.product = SELECT | 853 | public.product = SELECT |
298 | 852 | public.productseries = SELECT | 854 | public.productseries = SELECT |
299 | 853 | public.project = SELECT | 855 | public.project = SELECT |
301 | 854 | public.publisherconfig = SELECT, INSERT | 856 | public.publisherconfig = SELECT, INSERT |
302 | 855 | public.question = SELECT | 857 | public.question = SELECT |
303 | 858 | public.questionjob = SELECT, INSERT | ||
304 | 856 | public.questionbug = SELECT | 859 | public.questionbug = SELECT |
305 | 857 | public.questionsubscription = SELECT | 860 | public.questionsubscription = SELECT |
307 | 858 | public.sourcepackagepublishinghistory = SELECT, INSERT, UPDATE, DELETE | 861 | public.sourcepackagepublishinghistory = SELECT, INSERT, UPDATE, DELETE |
308 | 859 | public.structuralsubscription = SELECT | 862 | public.structuralsubscription = SELECT |
309 | 860 | public.validpersoncache = SELECT | 863 | public.validpersoncache = SELECT |
310 | 861 | public.validpersonorteamcache = SELECT | 864 | public.validpersonorteamcache = SELECT |
311 | @@ -863,58 +866,58 @@ | |||
312 | 863 | 866 | ||
313 | 864 | [fiera] | 867 | [fiera] |
314 | 865 | groups=script,translations_approval | 868 | groups=script,translations_approval |
367 | 866 | public.account = SELECT | 869 | public.account = SELECT |
368 | 867 | public.archive = SELECT, UPDATE | 870 | public.archive = SELECT, UPDATE |
369 | 868 | public.archivearch = SELECT, UPDATE | 871 | public.archivearch = SELECT, UPDATE |
370 | 869 | public.archivedependency = SELECT | 872 | public.archivedependency = SELECT |
371 | 870 | public.binarypackagebuild = SELECT, INSERT, UPDATE | 873 | public.binarypackagebuild = SELECT, INSERT, UPDATE |
372 | 871 | public.binarypackagefile = SELECT | 874 | public.binarypackagefile = SELECT |
373 | 872 | public.binarypackagename = SELECT | 875 | public.binarypackagename = SELECT |
374 | 873 | public.binarypackagepublishinghistory = SELECT | 876 | public.binarypackagepublishinghistory = SELECT |
375 | 874 | public.binarypackagerelease = SELECT | 877 | public.binarypackagerelease = SELECT |
376 | 875 | public.branch = SELECT | 878 | public.branch = SELECT |
377 | 876 | public.branchjob = SELECT, DELETE | 879 | public.branchjob = SELECT, DELETE |
378 | 877 | public.builder = SELECT, INSERT, UPDATE | 880 | public.builder = SELECT, INSERT, UPDATE |
379 | 878 | public.buildfarmjob = SELECT, INSERT, UPDATE | 881 | public.buildfarmjob = SELECT, INSERT, UPDATE |
380 | 879 | public.buildpackagejob = SELECT, INSERT, UPDATE, DELETE | 882 | public.buildpackagejob = SELECT, INSERT, UPDATE, DELETE |
381 | 880 | public.buildqueue = SELECT, INSERT, UPDATE, DELETE | 883 | public.buildqueue = SELECT, INSERT, UPDATE, DELETE |
382 | 881 | public.component = SELECT | 884 | public.component = SELECT |
383 | 882 | public.distribution = SELECT, UPDATE | 885 | public.distribution = SELECT, UPDATE |
384 | 883 | public.distroarchseries = SELECT, UPDATE | 886 | public.distroarchseries = SELECT, UPDATE |
385 | 884 | public.distroseries = SELECT, UPDATE | 887 | public.distroseries = SELECT, UPDATE |
386 | 885 | public.emailaddress = SELECT | 888 | public.emailaddress = SELECT |
387 | 886 | public.flatpackagesetinclusion = SELECT | 889 | public.flatpackagesetinclusion = SELECT |
388 | 887 | public.gpgkey = SELECT | 890 | public.gpgkey = SELECT |
389 | 888 | public.job = SELECT, INSERT, UPDATE, DELETE | 891 | public.job = SELECT, INSERT, UPDATE, DELETE |
390 | 889 | public.libraryfilealias = SELECT, INSERT | 892 | public.libraryfilealias = SELECT, INSERT |
391 | 890 | public.libraryfilecontent = SELECT, INSERT | 893 | public.libraryfilecontent = SELECT, INSERT |
392 | 891 | public.packagebuild = SELECT, INSERT, UPDATE | 894 | public.packagebuild = SELECT, INSERT, UPDATE |
393 | 892 | public.packageset = SELECT | 895 | public.packageset = SELECT |
394 | 893 | public.packagesetgroup = SELECT | 896 | public.packagesetgroup = SELECT |
395 | 894 | public.packagesetinclusion = SELECT | 897 | public.packagesetinclusion = SELECT |
396 | 895 | public.packagesetsources = SELECT | 898 | public.packagesetsources = SELECT |
397 | 896 | public.person = SELECT | 899 | public.person = SELECT |
398 | 897 | public.pocketchroot = SELECT, INSERT, UPDATE | 900 | public.pocketchroot = SELECT, INSERT, UPDATE |
399 | 898 | public.processor = SELECT | 901 | public.processor = SELECT |
400 | 899 | public.processorfamily = SELECT | 902 | public.processorfamily = SELECT |
401 | 900 | public.product = SELECT | 903 | public.product = SELECT |
402 | 901 | public.productseries = SELECT | 904 | public.productseries = SELECT |
403 | 902 | public.publisherconfig = SELECT | 905 | public.publisherconfig = SELECT |
404 | 903 | public.section = SELECT | 906 | public.section = SELECT |
405 | 904 | public.seriessourcepackagebranch = SELECT | 907 | public.seriessourcepackagebranch = SELECT |
406 | 905 | public.sourcepackagename = SELECT | 908 | public.sourcepackagename = SELECT |
407 | 906 | public.sourcepackagepublishinghistory = SELECT | 909 | public.sourcepackagepublishinghistory = SELECT |
408 | 907 | public.sourcepackagerecipe = SELECT | 910 | public.sourcepackagerecipe = SELECT |
409 | 908 | public.sourcepackagerecipebuild = SELECT, UPDATE | 911 | public.sourcepackagerecipebuild = SELECT, UPDATE |
410 | 909 | public.sourcepackagerecipebuildjob = SELECT, INSERT, UPDATE, DELETE | 912 | public.sourcepackagerecipebuildjob = SELECT, INSERT, UPDATE, DELETE |
411 | 910 | public.sourcepackagerecipedata = SELECT | 913 | public.sourcepackagerecipedata = SELECT |
412 | 911 | public.sourcepackagerecipedatainstruction = SELECT | 914 | public.sourcepackagerecipedatainstruction = SELECT |
413 | 912 | public.sourcepackagerelease = SELECT | 915 | public.sourcepackagerelease = SELECT |
414 | 913 | public.sourcepackagereleasefile = SELECT | 916 | public.sourcepackagereleasefile = SELECT |
415 | 914 | public.teammembership = SELECT | 917 | public.teammembership = SELECT |
416 | 915 | public.teamparticipation = SELECT | 918 | public.teamparticipation = SELECT |
417 | 916 | public.translationimportqueueentry = SELECT, INSERT, UPDATE | 919 | public.translationimportqueueentry = SELECT, INSERT, UPDATE |
418 | 917 | public.translationtemplatesbuild = SELECT, INSERT | 920 | public.translationtemplatesbuild = SELECT, INSERT |
419 | 918 | type=user | 921 | type=user |
420 | 919 | 922 | ||
421 | 920 | [ppa-apache-log-parser] | 923 | [ppa-apache-log-parser] |
422 | @@ -973,45 +976,45 @@ | |||
423 | 973 | 976 | ||
424 | 974 | [sync_packages] | 977 | [sync_packages] |
425 | 975 | groups=script | 978 | groups=script |
465 | 976 | public.archive = SELECT | 979 | public.archive = SELECT |
466 | 977 | public.archivepermission = SELECT, INSERT | 980 | public.archivepermission = SELECT, INSERT |
467 | 978 | public.binarypackagebuild = SELECT, INSERT | 981 | public.binarypackagebuild = SELECT, INSERT |
468 | 979 | public.binarypackagefile = SELECT, INSERT | 982 | public.binarypackagefile = SELECT, INSERT |
469 | 980 | public.binarypackagename = SELECT | 983 | public.binarypackagename = SELECT |
470 | 981 | public.binarypackagepublishinghistory = SELECT, INSERT | 984 | public.binarypackagepublishinghistory = SELECT, INSERT |
471 | 982 | public.binarypackagerelease = SELECT | 985 | public.binarypackagerelease = SELECT |
472 | 983 | public.buildfarmjob = SELECT, INSERT | 986 | public.buildfarmjob = SELECT, INSERT |
473 | 984 | public.buildpackagejob = SELECT, INSERT, UPDATE, DELETE | 987 | public.buildpackagejob = SELECT, INSERT, UPDATE, DELETE |
474 | 985 | public.buildqueue = SELECT, INSERT, UPDATE | 988 | public.buildqueue = SELECT, INSERT, UPDATE |
475 | 986 | public.component = SELECT | 989 | public.component = SELECT |
476 | 987 | public.componentselection = SELECT, INSERT | 990 | public.componentselection = SELECT, INSERT |
477 | 988 | public.distribution = SELECT | 991 | public.distribution = SELECT |
478 | 989 | public.distributionjob = SELECT | 992 | public.distributionjob = SELECT |
479 | 990 | public.distroarchseries = SELECT, INSERT | 993 | public.distroarchseries = SELECT, INSERT |
480 | 991 | public.distroseries = SELECT, UPDATE | 994 | public.distroseries = SELECT, UPDATE |
481 | 992 | public.flatpackagesetinclusion = SELECT, INSERT | 995 | public.flatpackagesetinclusion = SELECT, INSERT |
482 | 993 | public.gpgkey = SELECT | 996 | public.gpgkey = SELECT |
483 | 994 | public.job = SELECT, INSERT, UPDATE, DELETE | 997 | public.job = SELECT, INSERT, UPDATE, DELETE |
484 | 995 | public.libraryfilealias = SELECT, INSERT, UPDATE, DELETE | 998 | public.libraryfilealias = SELECT, INSERT, UPDATE, DELETE |
485 | 996 | public.libraryfilecontent = SELECT, INSERT | 999 | public.libraryfilecontent = SELECT, INSERT |
486 | 997 | public.packagebuild = SELECT, INSERT | 1000 | public.packagebuild = SELECT, INSERT |
487 | 998 | public.packageset = SELECT, INSERT | 1001 | public.packageset = SELECT, INSERT |
488 | 999 | public.packagesetgroup = SELECT, INSERT | 1002 | public.packagesetgroup = SELECT, INSERT |
489 | 1000 | public.packagesetinclusion = SELECT, INSERT | 1003 | public.packagesetinclusion = SELECT, INSERT |
490 | 1001 | public.packagesetsources = SELECT, INSERT | 1004 | public.packagesetsources = SELECT, INSERT |
491 | 1002 | public.packageupload = SELECT | 1005 | public.packageupload = SELECT |
492 | 1003 | public.packaging = SELECT, INSERT | 1006 | public.packaging = SELECT, INSERT |
493 | 1004 | public.person = SELECT | 1007 | public.person = SELECT |
494 | 1005 | public.pocketchroot = SELECT | 1008 | public.pocketchroot = SELECT |
495 | 1006 | public.processor = SELECT | 1009 | public.processor = SELECT |
496 | 1007 | public.processorfamily = SELECT | 1010 | public.processorfamily = SELECT |
497 | 1008 | public.section = SELECT | 1011 | public.section = SELECT |
498 | 1009 | public.sectionselection = SELECT, INSERT | 1012 | public.sectionselection = SELECT, INSERT |
499 | 1010 | public.sourcepackageformatselection = SELECT, INSERT | 1013 | public.sourcepackageformatselection = SELECT, INSERT |
500 | 1011 | public.sourcepackagename = SELECT | 1014 | public.sourcepackagename = SELECT |
501 | 1012 | public.sourcepackagepublishinghistory = SELECT, INSERT | 1015 | public.sourcepackagepublishinghistory = SELECT, INSERT |
502 | 1013 | public.sourcepackagerelease = SELECT | 1016 | public.sourcepackagerelease = SELECT |
503 | 1014 | public.sourcepackagereleasefile = SELECT, INSERT, UPDATE | 1017 | public.sourcepackagereleasefile = SELECT, INSERT, UPDATE |
504 | 1015 | type=user | 1018 | type=user |
505 | 1016 | 1019 | ||
506 | 1017 | [distroseriesdifferencejob] | 1020 | [distroseriesdifferencejob] |
507 | @@ -1183,7 +1186,7 @@ | |||
508 | 1183 | public.distribution = SELECT | 1186 | public.distribution = SELECT |
509 | 1184 | public.emailaddress = SELECT | 1187 | public.emailaddress = SELECT |
510 | 1185 | public.faq = SELECT | 1188 | public.faq = SELECT |
512 | 1186 | public.job = SELECT, UPDATE | 1189 | public.job = SELECT, INSERT, UPDATE |
513 | 1187 | public.language = SELECT | 1190 | public.language = SELECT |
514 | 1188 | public.message = SELECT, INSERT | 1191 | public.message = SELECT, INSERT |
515 | 1189 | public.messagechunk = SELECT, INSERT | 1192 | public.messagechunk = SELECT, INSERT |
516 | @@ -1192,7 +1195,7 @@ | |||
517 | 1192 | public.product = SELECT | 1195 | public.product = SELECT |
518 | 1193 | public.question = SELECT, UPDATE | 1196 | public.question = SELECT, UPDATE |
519 | 1194 | public.questionbug = SELECT | 1197 | public.questionbug = SELECT |
521 | 1195 | public.questionjob = SELECT | 1198 | public.questionjob = SELECT, INSERT |
522 | 1196 | public.questionmessage = SELECT, INSERT | 1199 | public.questionmessage = SELECT, INSERT |
523 | 1197 | public.questionsubscription = SELECT | 1200 | public.questionsubscription = SELECT |
524 | 1198 | public.sourcepackagename = SELECT | 1201 | public.sourcepackagename = SELECT |
525 | @@ -1285,6 +1288,7 @@ | |||
526 | 1285 | public.project = SELECT, UPDATE | 1288 | public.project = SELECT, UPDATE |
527 | 1286 | public.question = SELECT | 1289 | public.question = SELECT |
528 | 1287 | public.questionbug = SELECT | 1290 | public.questionbug = SELECT |
529 | 1291 | public.questionjob = SELECT, INSERT | ||
530 | 1288 | public.questionsubscription = SELECT | 1292 | public.questionsubscription = SELECT |
531 | 1289 | public.section = SELECT, INSERT | 1293 | public.section = SELECT, INSERT |
532 | 1290 | public.sectionselection = SELECT | 1294 | public.sectionselection = SELECT |
533 | @@ -1389,6 +1393,7 @@ | |||
534 | 1389 | public.publisherconfig = SELECT | 1393 | public.publisherconfig = SELECT |
535 | 1390 | public.question = SELECT | 1394 | public.question = SELECT |
536 | 1391 | public.questionbug = SELECT | 1395 | public.questionbug = SELECT |
537 | 1396 | public.questionjob = SELECT, INSERT | ||
538 | 1392 | public.questionsubscription = SELECT | 1397 | public.questionsubscription = SELECT |
539 | 1393 | public.section = SELECT | 1398 | public.section = SELECT |
540 | 1394 | public.sectionselection = SELECT | 1399 | public.sectionselection = SELECT |
541 | @@ -1464,6 +1469,7 @@ | |||
542 | 1464 | public.project = SELECT, UPDATE | 1469 | public.project = SELECT, UPDATE |
543 | 1465 | public.question = SELECT | 1470 | public.question = SELECT |
544 | 1466 | public.questionbug = SELECT | 1471 | public.questionbug = SELECT |
545 | 1472 | public.questionjob = SELECT, INSERT | ||
546 | 1467 | public.questionsubscription = SELECT | 1473 | public.questionsubscription = SELECT |
547 | 1468 | public.section = SELECT | 1474 | public.section = SELECT |
548 | 1469 | public.sourcepackagename = SELECT | 1475 | public.sourcepackagename = SELECT |
549 | @@ -1667,6 +1673,7 @@ | |||
550 | 1667 | public.project = SELECT, UPDATE | 1673 | public.project = SELECT, UPDATE |
551 | 1668 | public.question = SELECT, UPDATE | 1674 | public.question = SELECT, UPDATE |
552 | 1669 | public.questionbug = SELECT | 1675 | public.questionbug = SELECT |
553 | 1676 | public.questionjob = SELECT, INSERT | ||
554 | 1670 | public.questionmessage = SELECT, INSERT | 1677 | public.questionmessage = SELECT, INSERT |
555 | 1671 | public.questionsubscription = SELECT | 1678 | public.questionsubscription = SELECT |
556 | 1672 | public.section = SELECT | 1679 | public.section = SELECT |
557 | @@ -2026,6 +2033,7 @@ | |||
558 | 2026 | public.project = SELECT, UPDATE | 2033 | public.project = SELECT, UPDATE |
559 | 2027 | public.pushmirroraccess = SELECT, UPDATE | 2034 | public.pushmirroraccess = SELECT, UPDATE |
560 | 2028 | public.question = SELECT, UPDATE | 2035 | public.question = SELECT, UPDATE |
561 | 2036 | public.questionjob = SELECT, UPDATE | ||
562 | 2029 | public.questionreopening = SELECT, UPDATE | 2037 | public.questionreopening = SELECT, UPDATE |
563 | 2030 | public.questionsubscription = SELECT, UPDATE, DELETE | 2038 | public.questionsubscription = SELECT, UPDATE, DELETE |
564 | 2031 | public.revisionauthor = SELECT, UPDATE | 2039 | public.revisionauthor = SELECT, UPDATE |
565 | 2032 | 2040 | ||
566 | === modified file 'lib/lp/answers/browser/tests/views.txt' | |||
567 | --- lib/lp/answers/browser/tests/views.txt 2011-04-27 13:59:57 +0000 | |||
568 | +++ lib/lp/answers/browser/tests/views.txt 2011-05-04 17:17:01 +0000 | |||
569 | @@ -19,22 +19,6 @@ | |||
570 | 19 | >>> firefox_question.subscribe(firefox_question.owner) | 19 | >>> firefox_question.subscribe(firefox_question.owner) |
571 | 20 | <QuestionSubscription...> | 20 | <QuestionSubscription...> |
572 | 21 | 21 | ||
573 | 22 | # Let's define a helper function which commits the transaction, so | ||
574 | 23 | # that the notifications are queued in stub.test_emails and pops these | ||
575 | 24 | # notifications from the queue. | ||
576 | 25 | |||
577 | 26 | >>> from lp.services.mail import stub | ||
578 | 27 | >>> import email | ||
579 | 28 | >>> import transaction | ||
580 | 29 | >>> def pop_notifications(): | ||
581 | 30 | ... transaction.commit() | ||
582 | 31 | ... notifications = [ | ||
583 | 32 | ... email.message_from_string(raw_message) | ||
584 | 33 | ... for fromaddr, toaddrs, raw_message in sorted(stub.test_emails) | ||
585 | 34 | ... ] | ||
586 | 35 | ... stub.test_emails = [] | ||
587 | 36 | ... return notifications | ||
588 | 37 | |||
589 | 38 | 22 | ||
590 | 39 | QuestionSubscriptionView | 23 | QuestionSubscriptionView |
591 | 40 | ------------------------ | 24 | ------------------------ |
592 | @@ -84,11 +68,6 @@ | |||
593 | 84 | >>> view.request.response.getHeader('Location') | 68 | >>> view.request.response.getHeader('Location') |
594 | 85 | '.../+question/3' | 69 | '.../+question/3' |
595 | 86 | 70 | ||
596 | 87 | These two actions didn't generate any notification mails: | ||
597 | 88 | |||
598 | 89 | >>> len(pop_notifications()) | ||
599 | 90 | 0 | ||
600 | 91 | |||
601 | 92 | 71 | ||
602 | 93 | QuestionWorkflowView | 72 | QuestionWorkflowView |
603 | 94 | -------------------- | 73 | -------------------- |
604 | @@ -155,13 +134,6 @@ | |||
605 | 155 | >>> workflow_harness.redirectionTarget() | 134 | >>> workflow_harness.redirectionTarget() |
606 | 156 | '.../+question/2' | 135 | '.../+question/2' |
607 | 157 | 136 | ||
608 | 158 | Workflow actions like these will send out notifications to subscribers. | ||
609 | 159 | (Complete notifications testing will be found in answer-tracker- | ||
610 | 160 | notifications.txt) | ||
611 | 161 | |||
612 | 162 | >>> len(pop_notifications()) | ||
613 | 163 | 1 | ||
614 | 164 | |||
615 | 165 | The available actions for that other user are still comment, give an | 137 | The available actions for that other user are still comment, give an |
616 | 166 | answer or request more information: | 138 | answer or request more information: |
617 | 167 | 139 | ||
618 | @@ -347,10 +319,6 @@ | |||
619 | 347 | >>> workflow_harness.redirectionTarget() | 319 | >>> workflow_harness.redirectionTarget() |
620 | 348 | '.../+question/2' | 320 | '.../+question/2' |
621 | 349 | 321 | ||
622 | 350 | # Clear all notifications. | ||
623 | 351 | |||
624 | 352 | >>> notifications = pop_notifications() | ||
625 | 353 | |||
626 | 354 | 322 | ||
627 | 355 | QuestionMakeBugView | 323 | QuestionMakeBugView |
628 | 356 | ------------------- | 324 | ------------------- |
629 | @@ -359,9 +327,6 @@ | |||
630 | 359 | question. In addition to creating a bug, this operation will also link | 327 | question. In addition to creating a bug, this operation will also link |
631 | 360 | the bug to the question. | 328 | the bug to the question. |
632 | 361 | 329 | ||
633 | 362 | If the user creates a bug, a "Linked to bug" notification is sent and | ||
634 | 363 | the user is subscribed to the bug. | ||
635 | 364 | |||
636 | 365 | >>> login('foo.bar@canonical.com') | 330 | >>> login('foo.bar@canonical.com') |
637 | 366 | >>> request = LaunchpadTestRequest( | 331 | >>> request = LaunchpadTestRequest( |
638 | 367 | ... form={'field.actions.create': 'Create', | 332 | ... form={'field.actions.create': 'Create', |
639 | @@ -393,19 +358,6 @@ | |||
640 | 393 | >>> 'Bug #%s created.' % new_bug_id in message[0] | 358 | >>> 'Bug #%s created.' % new_bug_id in message[0] |
641 | 394 | True | 359 | True |
642 | 395 | 360 | ||
643 | 396 | >>> notifications = pop_notifications() | ||
644 | 397 | >>> len(notifications) | ||
645 | 398 | 1 | ||
646 | 399 | |||
647 | 400 | >>> print notifications[0].get_payload(decode=True) | ||
648 | 401 | Your question #3... | ||
649 | 402 | ... | ||
650 | 403 | Linked to bug: #... | ||
651 | 404 | |||
652 | 405 | http://bugs.launchpad.dev/bugs/... | ||
653 | 406 | "Bug title" | ||
654 | 407 | ... | ||
655 | 408 | |||
656 | 409 | If the question already has bugs linked to it, no new bug can be | 361 | If the question already has bugs linked to it, no new bug can be |
657 | 410 | created. | 362 | created. |
658 | 411 | 363 | ||
659 | @@ -471,10 +423,6 @@ | |||
660 | 471 | >>> print firefox_question.status.title | 423 | >>> print firefox_question.status.title |
661 | 472 | Solved | 424 | Solved |
662 | 473 | 425 | ||
663 | 474 | # Clear the notification. | ||
664 | 475 | |||
665 | 476 | >>> notifications = pop_notifications() | ||
666 | 477 | |||
667 | 478 | 426 | ||
668 | 479 | QuestionEditView | 427 | QuestionEditView |
669 | 480 | ---------------- | 428 | ---------------- |
670 | @@ -586,10 +534,6 @@ | |||
671 | 586 | >>> print question_three.product.name | 534 | >>> print question_three.product.name |
672 | 587 | firefox | 535 | firefox |
673 | 588 | 536 | ||
674 | 589 | # Clear out the pending notifications. | ||
675 | 590 | |||
676 | 591 | >>> notifications = pop_notifications() | ||
677 | 592 | |||
678 | 593 | # Reassign back the question to ubuntu | 537 | # Reassign back the question to ubuntu |
679 | 594 | 538 | ||
680 | 595 | >>> question_three.target = ubuntu | 539 | >>> question_three.target = ubuntu |
681 | 596 | 540 | ||
682 | === modified file 'lib/lp/answers/doc/notifications.txt' | |||
683 | --- lib/lp/answers/doc/notifications.txt 2011-04-27 13:59:57 +0000 | |||
684 | +++ lib/lp/answers/doc/notifications.txt 2011-05-04 17:17:01 +0000 | |||
685 | @@ -7,9 +7,9 @@ | |||
686 | 7 | notification looks like: | 7 | notification looks like: |
687 | 8 | 8 | ||
688 | 9 | >>> from zope.event import notify | 9 | >>> from zope.event import notify |
690 | 10 | >>> from lazr.lifecycle.event import ObjectCreatedEvent | 10 | >>> from lp.answers.tests.test_question_notifications import ( |
691 | 11 | ... pop_questionemailjobs) | ||
692 | 11 | >>> from lp.registry.interfaces.distribution import IDistributionSet | 12 | >>> from lp.registry.interfaces.distribution import IDistributionSet |
693 | 12 | >>> from lp.testing.mail_helpers import pop_notifications | ||
694 | 13 | >>> login('test@canonical.com') | 13 | >>> login('test@canonical.com') |
695 | 14 | >>> sample_person = getUtility(ILaunchBag).user | 14 | >>> sample_person = getUtility(ILaunchBag).user |
696 | 15 | >>> ubuntu = getUtility(IDistributionSet).getByName('ubuntu') | 15 | >>> ubuntu = getUtility(IDistributionSet).getByName('ubuntu') |
697 | @@ -29,7 +29,7 @@ | |||
698 | 29 | >>> [sub.person.displayname for sub in ubuntu_question.subscriptions] | 29 | >>> [sub.person.displayname for sub in ubuntu_question.subscriptions] |
699 | 30 | [u'Sample Person'] | 30 | [u'Sample Person'] |
700 | 31 | 31 | ||
702 | 32 | >>> notifications = pop_notifications() | 32 | >>> notifications = pop_questionemailjobs() |
703 | 33 | >>> len(notifications) | 33 | >>> len(notifications) |
704 | 34 | 1 | 34 | 1 |
705 | 35 | 35 | ||
706 | @@ -41,44 +41,26 @@ | |||
707 | 41 | Danilo have a story worth telling. | 41 | Danilo have a story worth telling. |
708 | 42 | 42 | ||
709 | 43 | >>> add_notification = notifications[0] | 43 | >>> add_notification = notifications[0] |
721 | 44 | >>> add_notification['From'] | 44 | |
722 | 45 | 'Sample Person <question...@answers.launchpad.net>' | 45 | >>> print add_notification.subject |
723 | 46 | 46 | [Question #...]: Can't install Ubuntu | |
713 | 47 | >>> add_notification['Reply-To'] | ||
714 | 48 | 'question...@answers.launchpad.net' | ||
715 | 49 | |||
716 | 50 | >>> add_notification['To'] | ||
717 | 51 | 'test@canonical.com' | ||
718 | 52 | |||
719 | 53 | >>> add_notification['Subject'] | ||
720 | 54 | "[Question #...]: Can't install Ubuntu" | ||
724 | 55 | 47 | ||
725 | 56 | Like all Launchpad notifications should, the message contain in the | 48 | Like all Launchpad notifications should, the message contain in the |
726 | 57 | footer the reason why the user is receiving the notification. | 49 | footer the reason why the user is receiving the notification. |
727 | 58 | 50 | ||
730 | 59 | >>> notification_body = add_notification.get_payload(decode=True) | 51 | >>> print add_notification.body |
729 | 60 | >>> print notification_body #doctest: -NORMALIZE_WHITESPACE | ||
731 | 61 | New question #... on Ubuntu: | 52 | New question #... on Ubuntu: |
732 | 62 | http://.../ubuntu/+question/... | 53 | http://.../ubuntu/+question/... |
733 | 63 | <BLANKLINE> | 54 | <BLANKLINE> |
734 | 64 | I insert the install CD in the CD-ROM drive, but it won't boot. | 55 | I insert the install CD in the CD-ROM drive, but it won't boot. |
735 | 65 | <BLANKLINE> | ||
736 | 66 | --... | ||
737 | 67 | You received this question notification because you asked the question. | ||
738 | 68 | 56 | ||
739 | 69 | The notification also includes a 'X-Launchpad-Question' header that | 57 | The notification also includes a 'X-Launchpad-Question' header that |
740 | 70 | contains information about the question. | 58 | contains information about the question. |
741 | 71 | 59 | ||
743 | 72 | >>> print add_notification['X-Launchpad-Question'] | 60 | >>> print add_notification.headers['X-Launchpad-Question'] |
744 | 73 | distribution=ubuntu; sourcepackage=None; status=Open; | 61 | distribution=ubuntu; sourcepackage=None; status=Open; |
745 | 74 | assignee=None; priority=Normal; language=en | 62 | assignee=None; priority=Normal; language=en |
746 | 75 | 63 | ||
747 | 76 | As well as the standard 'X-Launchpad-Message-Rationale' header that | ||
748 | 77 | contains in short format the reason for the user to be contacted. | ||
749 | 78 | |||
750 | 79 | >>> print add_notification['X-Launchpad-Message-Rationale'] | ||
751 | 80 | Asker | ||
752 | 81 | |||
753 | 82 | Register the Ubuntu Team as Ubuntu's answer contact, so that they get | 64 | Register the Ubuntu Team as Ubuntu's answer contact, so that they get |
754 | 83 | notified about the changes as well: | 65 | notified about the changes as well: |
755 | 84 | 66 | ||
756 | @@ -124,17 +106,12 @@ | |||
757 | 124 | Three copies of the notification got sent, one to Sample Person, one to | 106 | Three copies of the notification got sent, one to Sample Person, one to |
758 | 125 | Foo Bar, and one to Ubuntu Team: | 107 | Foo Bar, and one to Ubuntu Team: |
759 | 126 | 108 | ||
768 | 127 | >>> from operator import itemgetter | 109 | >>> notifications = pop_questionemailjobs() |
769 | 128 | >>> notifications = sorted(pop_notifications(), key=itemgetter('To')) | 110 | >>> edit_notification = notifications[1] |
770 | 129 | >>> [notification['To'] for notification in notifications] | 111 | >>> print edit_notification.subject |
763 | 130 | ['foo.bar@canonical.com', 'support@ubuntu.com', 'test@canonical.com'] | ||
764 | 131 | |||
765 | 132 | >>> edit_notification = notifications[0] | ||
766 | 133 | >>> notification_body = edit_notification.get_payload(decode=True) | ||
767 | 134 | >>> print edit_notification['Subject'] | ||
771 | 135 | Re: [Question #...]: Installer doesn't work on a Mac | 112 | Re: [Question #...]: Installer doesn't work on a Mac |
772 | 136 | 113 | ||
774 | 137 | >>> print notification_body #doctest: -NORMALIZE_WHITESPACE | 114 | >>> print edit_notification.body |
775 | 138 | Question #... libstdc++ in Ubuntu changed: | 115 | Question #... libstdc++ in Ubuntu changed: |
776 | 139 | http://.../ubuntu/+source/libstdc++/+question/... | 116 | http://.../ubuntu/+source/libstdc++/+question/... |
777 | 140 | <BLANKLINE> | 117 | <BLANKLINE> |
778 | @@ -148,10 +125,6 @@ | |||
779 | 148 | drive, but it won't boot. | 125 | drive, but it won't boot. |
780 | 149 | <BLANKLINE> | 126 | <BLANKLINE> |
781 | 150 | It boots straight into MacOS 9. | 127 | It boots straight into MacOS 9. |
782 | 151 | <BLANKLINE> | ||
783 | 152 | --... | ||
784 | 153 | You received this question notification because you are the assignee for | ||
785 | 154 | this question. | ||
786 | 155 | 128 | ||
787 | 156 | # XXX flacoste 2006-09-19: Add checks for notification of change to # | 129 | # XXX flacoste 2006-09-19: Add checks for notification of change to # |
788 | 157 | status whiteboard, priority. For example, if a question is # transferred | 130 | status whiteboard, priority. For example, if a question is # transferred |
789 | @@ -163,21 +136,13 @@ | |||
790 | 163 | >>> ubuntu_question.target = ubuntu | 136 | >>> ubuntu_question.target = ubuntu |
791 | 164 | >>> notify(ObjectModifiedEvent( | 137 | >>> notify(ObjectModifiedEvent( |
792 | 165 | ... ubuntu_question, unmodified_question, ['target'])) | 138 | ... ubuntu_question, unmodified_question, ['target'])) |
800 | 166 | >>> notifications = sorted(pop_notifications(), key=itemgetter('To')) | 139 | >>> notifications = pop_questionemailjobs() |
801 | 167 | >>> [notification['To'] for notification in notifications] | 140 | >>> edit_notification = notifications[1] |
802 | 168 | ['foo.bar@canonical.com', 'support@ubuntu.com', 'test@canonical.com'] | 141 | >>> print edit_notification.body |
796 | 169 | |||
797 | 170 | >>> edit_notification = notifications[0] | ||
798 | 171 | >>> notification_body = edit_notification.get_payload(decode=True) | ||
799 | 172 | >>> print notification_body #doctest: -NORMALIZE_WHITESPACE | ||
803 | 173 | Question #... Ubuntu changed: | 142 | Question #... Ubuntu changed: |
804 | 174 | http://.../ubuntu/+question/... | 143 | http://.../ubuntu/+question/... |
805 | 175 | <BLANKLINE> | 144 | <BLANKLINE> |
806 | 176 | Project: libstdc++ in Ubuntu => Ubuntu | 145 | Project: libstdc++ in Ubuntu => Ubuntu |
807 | 177 | <BLANKLINE> | ||
808 | 178 | --... | ||
809 | 179 | You received this question notification because you are the assignee for | ||
810 | 180 | this question. | ||
811 | 181 | 146 | ||
812 | 182 | Changing the assignee will trigger a notification. | 147 | Changing the assignee will trigger a notification. |
813 | 183 | 148 | ||
814 | @@ -187,21 +152,13 @@ | |||
815 | 187 | >>> ubuntu_question.assignee = no_priv | 152 | >>> ubuntu_question.assignee = no_priv |
816 | 188 | >>> notify(ObjectModifiedEvent( | 153 | >>> notify(ObjectModifiedEvent( |
817 | 189 | ... ubuntu_question, unmodified_question, ['assignee'])) | 154 | ... ubuntu_question, unmodified_question, ['assignee'])) |
825 | 190 | >>> notifications = sorted(pop_notifications(), key=itemgetter('To')) | 155 | >>> notifications = pop_questionemailjobs() |
826 | 191 | >>> [notification['To'] for notification in notifications] | 156 | >>> edit_notification = notifications[1] |
827 | 192 | ['no-priv@canonical.com', 'support@ubuntu.com', 'test@canonical.com'] | 157 | >>> print edit_notification.body |
821 | 193 | |||
822 | 194 | >>> edit_notification = notifications[0] | ||
823 | 195 | >>> notification_body = edit_notification.get_payload(decode=True) | ||
824 | 196 | >>> print notification_body | ||
828 | 197 | Question #... Ubuntu changed: | 158 | Question #... Ubuntu changed: |
829 | 198 | http://.../ubuntu/+question/... | 159 | http://.../ubuntu/+question/... |
830 | 199 | <BLANKLINE> | 160 | <BLANKLINE> |
831 | 200 | Assignee: Foo Bar => No Privileges Person | 161 | Assignee: Foo Bar => No Privileges Person |
832 | 201 | <BLANKLINE> | ||
833 | 202 | --... | ||
834 | 203 | You received this question notification because you are the assignee for | ||
835 | 204 | this question. | ||
836 | 205 | 162 | ||
837 | 206 | If we trigger a modification event when no changes worth notifying about | 163 | If we trigger a modification event when no changes worth notifying about |
838 | 207 | was made, no notification is sent: | 164 | was made, no notification is sent: |
839 | @@ -211,7 +168,7 @@ | |||
840 | 211 | >>> notify(ObjectModifiedEvent( | 168 | >>> notify(ObjectModifiedEvent( |
841 | 212 | ... ubuntu_question, unmodified_question, ['status'])) | 169 | ... ubuntu_question, unmodified_question, ['status'])) |
842 | 213 | 170 | ||
844 | 214 | >>> notifications = pop_notifications() | 171 | >>> notifications = pop_questionemailjobs() |
845 | 215 | >>> len(notifications) | 172 | >>> len(notifications) |
846 | 216 | 0 | 173 | 0 |
847 | 217 | 174 | ||
848 | @@ -245,23 +202,18 @@ | |||
849 | 245 | >>> notify(ObjectModifiedEvent( | 202 | >>> notify(ObjectModifiedEvent( |
850 | 246 | ... ubuntu_question, unmodified_question, ['bugs'])) | 203 | ... ubuntu_question, unmodified_question, ['bugs'])) |
851 | 247 | 204 | ||
853 | 248 | >>> notifications = pop_notifications() | 205 | >>> notifications = pop_questionemailjobs() |
854 | 249 | >>> len(notifications) | 206 | >>> len(notifications) |
855 | 250 | 2 | 207 | 2 |
856 | 251 | 208 | ||
860 | 252 | >>> edit_notification = notifications[0] | 209 | >>> edit_notification = notifications[1] |
861 | 253 | >>> notification_body = edit_notification.get_payload(decode=True) | 210 | >>> print edit_notification.body |
859 | 254 | >>> print notification_body #doctest: -NORMALIZE_WHITESPACE | ||
862 | 255 | Question #... on Ubuntu changed: | 211 | Question #... on Ubuntu changed: |
863 | 256 | http://.../ubuntu/+question/... | 212 | http://.../ubuntu/+question/... |
864 | 257 | <BLANKLINE> | 213 | <BLANKLINE> |
865 | 258 | Linked to bug: #... | 214 | Linked to bug: #... |
866 | 259 | http://.../bugs/... | 215 | http://.../bugs/... |
867 | 260 | "Installer fails on a Mac PPC" | 216 | "Installer fails on a Mac PPC" |
868 | 261 | <BLANKLINE> | ||
869 | 262 | --... | ||
870 | 263 | You received this question notification because you are a member of | ||
871 | 264 | Ubuntu Team, which is an answer contact for Ubuntu. | ||
872 | 265 | 217 | ||
873 | 266 | 218 | ||
874 | 267 | Bug Unlinked Notification | 219 | Bug Unlinked Notification |
875 | @@ -277,23 +229,18 @@ | |||
876 | 277 | >>> notify(ObjectModifiedEvent( | 229 | >>> notify(ObjectModifiedEvent( |
877 | 278 | ... ubuntu_question, unmodified_question, ['bugs'])) | 230 | ... ubuntu_question, unmodified_question, ['bugs'])) |
878 | 279 | 231 | ||
880 | 280 | >>> notifications = pop_notifications() | 232 | >>> notifications = pop_questionemailjobs() |
881 | 281 | >>> len(notifications) | 233 | >>> len(notifications) |
882 | 282 | 2 | 234 | 2 |
883 | 283 | 235 | ||
887 | 284 | >>> edit_notification = notifications[0] | 236 | >>> edit_notification = notifications[1] |
888 | 285 | >>> notification_body = edit_notification.get_payload(decode=True) | 237 | >>> print edit_notification.body |
886 | 286 | >>> print notification_body #doctest: -NORMALIZE_WHITESPACE | ||
889 | 287 | Question #... on Ubuntu changed: | 238 | Question #... on Ubuntu changed: |
890 | 288 | http://.../ubuntu/+question/... | 239 | http://.../ubuntu/+question/... |
891 | 289 | <BLANKLINE> | 240 | <BLANKLINE> |
892 | 290 | Removed link to bug: #... | 241 | Removed link to bug: #... |
893 | 291 | http://.../bugs/... | 242 | http://.../bugs/... |
894 | 292 | "Installer fails on a Mac PPC" | 243 | "Installer fails on a Mac PPC" |
895 | 293 | <BLANKLINE> | ||
896 | 294 | --... | ||
897 | 295 | You received this question notification because you are a member of | ||
898 | 296 | Ubuntu Team, which is an answer contact for Ubuntu. | ||
899 | 297 | 244 | ||
900 | 298 | 245 | ||
901 | 299 | Linked Bug Status Changed Notification | 246 | Linked Bug Status Changed Notification |
902 | @@ -314,13 +261,10 @@ | |||
903 | 314 | >>> request_message = ubuntu_question.requestInfo( | 261 | >>> request_message = ubuntu_question.requestInfo( |
904 | 315 | ... no_priv, "What is your Mac model?") | 262 | ... no_priv, "What is your Mac model?") |
905 | 316 | 263 | ||
913 | 317 | >>> notifications = pop_notifications() | 264 | >>> notifications = pop_questionemailjobs() |
914 | 318 | >>> [email_msg['To'] for email_msg in notifications] | 265 | >>> support_notification = notifications[1] |
915 | 319 | ['support@ubuntu.com', 'test@canonical.com'] | 266 | >>> print support_notification.subject |
916 | 320 | 267 | Re: [Question #...]: Installer doesn't work on a Mac | |
910 | 321 | >>> support_notification = notifications[0] | ||
911 | 322 | >>> support_notification['Subject'] | ||
912 | 323 | "Re: [Question #...]: Installer doesn't work on a Mac" | ||
917 | 324 | 268 | ||
918 | 325 | For workflow notifications, the content of the notification is slightly | 269 | For workflow notifications, the content of the notification is slightly |
919 | 326 | different based on whether you are the question owner or somebody else. | 270 | different based on whether you are the question owner or somebody else. |
920 | @@ -328,8 +272,7 @@ | |||
921 | 328 | For example, the notification to the answer contacts and every other | 272 | For example, the notification to the answer contacts and every other |
922 | 329 | subscribers except the question owner will look like this: | 273 | subscribers except the question owner will look like this: |
923 | 330 | 274 | ||
926 | 331 | >>> notification_body = support_notification.get_payload(decode=True) | 275 | >>> print support_notification.body |
925 | 332 | >>> print notification_body #doctest: -NORMALIZE_WHITESPACE | ||
927 | 333 | Question #... on Ubuntu changed: | 276 | Question #... on Ubuntu changed: |
928 | 334 | http://.../ubuntu/+question/... | 277 | http://.../ubuntu/+question/... |
929 | 335 | <BLANKLINE> | 278 | <BLANKLINE> |
930 | @@ -337,16 +280,11 @@ | |||
931 | 337 | <BLANKLINE> | 280 | <BLANKLINE> |
932 | 338 | No Privileges Person requested more information: | 281 | No Privileges Person requested more information: |
933 | 339 | What is your Mac model? | 282 | What is your Mac model? |
934 | 340 | <BLANKLINE> | ||
935 | 341 | --... | ||
936 | 342 | You received this question notification because you are a member of | ||
937 | 343 | Ubuntu Team, which is an answer contact for Ubuntu. | ||
938 | 344 | 283 | ||
939 | 345 | But the owner notification has a slightly different preamble and has an | 284 | But the owner notification has a slightly different preamble and has an |
940 | 346 | extra footer. | 285 | extra footer. |
941 | 347 | 286 | ||
944 | 348 | >>> notification_body = notifications[1].get_payload(decode=True) | 287 | >>> print notifications[0].body |
943 | 349 | >>> print notification_body #doctest: -NORMALIZE_WHITESPACE | ||
945 | 350 | Your question #... on Ubuntu changed: | 288 | Your question #... on Ubuntu changed: |
946 | 351 | http://.../ubuntu/+question/... | 289 | http://.../ubuntu/+question/... |
947 | 352 | <BLANKLINE> | 290 | <BLANKLINE> |
948 | @@ -359,8 +297,6 @@ | |||
949 | 359 | To answer this request for more information, you can either reply to | 297 | To answer this request for more information, you can either reply to |
950 | 360 | this email or enter your reply at the following page: | 298 | this email or enter your reply at the following page: |
951 | 361 | http://.../ubuntu/+question/... | 299 | http://.../ubuntu/+question/... |
952 | 362 | <BLANKLINE> | ||
953 | 363 | You received this question notification because you asked the question. | ||
954 | 364 | 300 | ||
955 | 365 | Of course, if the owner unsubscribe from the question, he won't receives | 301 | Of course, if the owner unsubscribe from the question, he won't receives |
956 | 366 | a notification. | 302 | a notification. |
957 | @@ -369,12 +305,8 @@ | |||
958 | 369 | >>> ubuntu_question.unsubscribe(sample_person) | 305 | >>> ubuntu_question.unsubscribe(sample_person) |
959 | 370 | >>> message = ubuntu_question.giveInfo('A PowerMac 7200.') | 306 | >>> message = ubuntu_question.giveInfo('A PowerMac 7200.') |
960 | 371 | 307 | ||
967 | 372 | >>> notifications = pop_notifications() | 308 | >>> notifications = pop_questionemailjobs() |
968 | 373 | >>> [email_msg['To'] for email_msg in notifications] | 309 | >>> print notifications[1].body |
963 | 374 | ['support@ubuntu.com'] | ||
964 | 375 | |||
965 | 376 | >>> notification_body = notifications[0].get_payload(decode=True) | ||
966 | 377 | >>> print notification_body #doctest: -NORMALIZE_WHITESPACE | ||
969 | 378 | Question #... on Ubuntu changed: | 310 | Question #... on Ubuntu changed: |
970 | 379 | http://.../ubuntu/+question/... | 311 | http://.../ubuntu/+question/... |
971 | 380 | <BLANKLINE> | 312 | <BLANKLINE> |
972 | @@ -382,15 +314,11 @@ | |||
973 | 382 | <BLANKLINE> | 314 | <BLANKLINE> |
974 | 383 | Sample Person gave more information on the question: | 315 | Sample Person gave more information on the question: |
975 | 384 | A PowerMac 7200. | 316 | A PowerMac 7200. |
976 | 385 | <BLANKLINE> | ||
977 | 386 | --... | ||
978 | 387 | You received this question notification because you are a member of | ||
979 | 388 | Ubuntu Team, which is an answer contact for Ubuntu. | ||
980 | 389 | 317 | ||
981 | 390 | The notification for new messages on the question contain a 'References' | 318 | The notification for new messages on the question contain a 'References' |
982 | 391 | header to the previous message for threading purpose. | 319 | header to the previous message for threading purpose. |
983 | 392 | 320 | ||
985 | 393 | >>> references = notifications[0]['References'] | 321 | >>> references = notifications[0].headers['References'] |
986 | 394 | >>> print references | 322 | >>> print references |
987 | 395 | <...> | 323 | <...> |
988 | 396 | 324 | ||
989 | @@ -413,15 +341,11 @@ | |||
990 | 413 | >>> login('no-priv@canonical.com') | 341 | >>> login('no-priv@canonical.com') |
991 | 414 | >>> message = ubuntu_question.expireQuestion( | 342 | >>> message = ubuntu_question.expireQuestion( |
992 | 415 | ... no_priv, "Expired because of no recent activity.") | 343 | ... no_priv, "Expired because of no recent activity.") |
997 | 416 | 344 | >>> notifications = pop_questionemailjobs() | |
994 | 417 | >>> notifications = pop_notifications() | ||
995 | 418 | >>> [email_msg['To'] for email_msg in notifications] | ||
996 | 419 | ['support@ubuntu.com', 'test@canonical.com'] | ||
998 | 420 | 345 | ||
999 | 421 | Default notification when the question is expired: | 346 | Default notification when the question is expired: |
1000 | 422 | 347 | ||
1003 | 423 | >>> notification_body = notifications[0].get_payload(decode=True) | 348 | >>> print notifications[1].body |
1002 | 424 | >>> print notification_body #doctest: -NORMALIZE_WHITESPACE | ||
1004 | 425 | Question #... on Ubuntu changed: | 349 | Question #... on Ubuntu changed: |
1005 | 426 | http://.../ubuntu/+question/... | 350 | http://.../ubuntu/+question/... |
1006 | 427 | <BLANKLINE> | 351 | <BLANKLINE> |
1007 | @@ -430,14 +354,10 @@ | |||
1008 | 430 | No Privileges Person expired the question: | 354 | No Privileges Person expired the question: |
1009 | 431 | Expired because of no recent activity. | 355 | Expired because of no recent activity. |
1010 | 432 | <BLANKLINE> | 356 | <BLANKLINE> |
1011 | 433 | --... | ||
1012 | 434 | You received this question notification because you are a member of | ||
1013 | 435 | Ubuntu Team, which is an answer contact for Ubuntu. | ||
1014 | 436 | 357 | ||
1015 | 437 | Notification received by the owner: | 358 | Notification received by the owner: |
1016 | 438 | 359 | ||
1019 | 439 | >>> notification_body = notifications[1].get_payload(decode=True) | 360 | >>> print notifications[0].body |
1018 | 440 | >>> print notification_body #doctest: -NORMALIZE_WHITESPACE | ||
1020 | 441 | Your question #... on Ubuntu changed: | 361 | Your question #... on Ubuntu changed: |
1021 | 442 | http://.../ubuntu/+question/... | 362 | http://.../ubuntu/+question/... |
1022 | 443 | <BLANKLINE> | 363 | <BLANKLINE> |
1023 | @@ -451,8 +371,6 @@ | |||
1024 | 451 | by replying to this email or by going to the following page and | 371 | by replying to this email or by going to the following page and |
1025 | 452 | entering more information about your problem: | 372 | entering more information about your problem: |
1026 | 453 | http://.../ubuntu/+question/... | 373 | http://.../ubuntu/+question/... |
1027 | 454 | <BLANKLINE> | ||
1028 | 455 | You received this question notification because you asked the question. | ||
1029 | 456 | 374 | ||
1030 | 457 | 375 | ||
1031 | 458 | Notifications for reopen() | 376 | Notifications for reopen() |
1032 | @@ -473,20 +391,16 @@ | |||
1033 | 473 | ... "newbie."), | 391 | ... "newbie."), |
1034 | 474 | ... owner=sample_person) | 392 | ... owner=sample_person) |
1035 | 475 | >>> message = ubuntu_question.reopen(email_msg) | 393 | >>> message = ubuntu_question.reopen(email_msg) |
1040 | 476 | 394 | >>> notifications = pop_questionemailjobs() | |
1037 | 477 | >>> notifications = pop_notifications() | ||
1038 | 478 | >>> [email_msg['To'] for email_msg in notifications] | ||
1039 | 479 | ['support@ubuntu.com', 'test@canonical.com'] | ||
1041 | 480 | 395 | ||
1042 | 481 | Notice also how the 'Re' handling is handled nicely: | 396 | Notice also how the 'Re' handling is handled nicely: |
1043 | 482 | 397 | ||
1045 | 483 | >>> print notifications[0]['Subject'] | 398 | >>> print notifications[0].subject |
1046 | 484 | Re: [Question #...]: Installer doesn't work on a Mac | 399 | Re: [Question #...]: Installer doesn't work on a Mac |
1047 | 485 | 400 | ||
1048 | 486 | Default notification when the owner reopens the question: | 401 | Default notification when the owner reopens the question: |
1049 | 487 | 402 | ||
1052 | 488 | >>> notification_body = notifications[0].get_payload(decode=True) | 403 | >>> print notifications[1].body |
1051 | 489 | >>> print notification_body #doctest: -NORMALIZE_WHITESPACE | ||
1053 | 490 | Question #... on Ubuntu changed: | 404 | Question #... on Ubuntu changed: |
1054 | 491 | http://.../ubuntu/+question/... | 405 | http://.../ubuntu/+question/... |
1055 | 492 | <BLANKLINE> | 406 | <BLANKLINE> |
1056 | @@ -497,15 +411,10 @@ | |||
1057 | 497 | useful. | 411 | useful. |
1058 | 498 | <BLANKLINE> | 412 | <BLANKLINE> |
1059 | 499 | Please provide some help to a newbie. | 413 | Please provide some help to a newbie. |
1060 | 500 | <BLANKLINE> | ||
1061 | 501 | --... | ||
1062 | 502 | You received this question notification because you are a member of | ||
1063 | 503 | Ubuntu Team, which is an answer contact for Ubuntu. | ||
1064 | 504 | 414 | ||
1065 | 505 | Notification received by the owner: | 415 | Notification received by the owner: |
1066 | 506 | 416 | ||
1069 | 507 | >>> notification_body = notifications[1].get_payload(decode=True) | 417 | >>> print notifications[0].body |
1068 | 508 | >>> print notification_body #doctest: -NORMALIZE_WHITESPACE | ||
1070 | 509 | Your question #... on Ubuntu changed: | 418 | Your question #... on Ubuntu changed: |
1071 | 510 | http://.../ubuntu/+question/... | 419 | http://.../ubuntu/+question/... |
1072 | 511 | <BLANKLINE> | 420 | <BLANKLINE> |
1073 | @@ -516,9 +425,6 @@ | |||
1074 | 516 | useful. | 425 | useful. |
1075 | 517 | <BLANKLINE> | 426 | <BLANKLINE> |
1076 | 518 | Please provide some help to a newbie. | 427 | Please provide some help to a newbie. |
1077 | 519 | <BLANKLINE> | ||
1078 | 520 | --... | ||
1079 | 521 | You received this question notification because you asked the question. | ||
1080 | 522 | 428 | ||
1081 | 523 | 429 | ||
1082 | 524 | Notifications for giveAnswer() | 430 | Notifications for giveAnswer() |
1083 | @@ -533,14 +439,11 @@ | |||
1084 | 533 | ... "https://help.ubuntu.com/community/Installation/OldWorldMacs " | 439 | ... "https://help.ubuntu.com/community/Installation/OldWorldMacs " |
1085 | 534 | ... "for all the details.") | 440 | ... "for all the details.") |
1086 | 535 | 441 | ||
1090 | 536 | >>> notifications = pop_notifications() | 442 | >>> notifications = pop_questionemailjobs() |
1088 | 537 | >>> [email_msg['To'] for email_msg in notifications] | ||
1089 | 538 | ['support@ubuntu.com', 'test@canonical.com'] | ||
1091 | 539 | 443 | ||
1092 | 540 | Default notification when an answer is proposed: | 444 | Default notification when an answer is proposed: |
1093 | 541 | 445 | ||
1096 | 542 | >>> notification_body = notifications[0].get_payload(decode=True) | 446 | >>> print notifications[1].body |
1095 | 543 | >>> print notification_body #doctest: -NORMALIZE_WHITESPACE | ||
1097 | 544 | Question #... on Ubuntu changed: | 447 | Question #... on Ubuntu changed: |
1098 | 545 | http://.../ubuntu/+question/... | 448 | http://.../ubuntu/+question/... |
1099 | 546 | <BLANKLINE> | 449 | <BLANKLINE> |
1100 | @@ -553,15 +456,10 @@ | |||
1101 | 553 | <BLANKLINE> | 456 | <BLANKLINE> |
1102 | 554 | Consult https://help.ubuntu.com/community/Installation/OldWorldMacs for | 457 | Consult https://help.ubuntu.com/community/Installation/OldWorldMacs for |
1103 | 555 | all the details. | 458 | all the details. |
1104 | 556 | <BLANKLINE> | ||
1105 | 557 | --... | ||
1106 | 558 | You received this question notification because you are a member of | ||
1107 | 559 | Ubuntu Team, which is an answer contact for Ubuntu. | ||
1108 | 560 | 459 | ||
1109 | 561 | Notification received by the owner: | 460 | Notification received by the owner: |
1110 | 562 | 461 | ||
1113 | 563 | >>> notification_body = notifications[1].get_payload(decode=True) | 462 | >>> print notifications[0].body |
1112 | 564 | >>> print notification_body #doctest: -NORMALIZE_WHITESPACE | ||
1114 | 565 | Your question #... on Ubuntu changed: | 463 | Your question #... on Ubuntu changed: |
1115 | 566 | http://.../ubuntu/+question/... | 464 | http://.../ubuntu/+question/... |
1116 | 567 | <BLANKLINE> | 465 | <BLANKLINE> |
1117 | @@ -583,8 +481,6 @@ | |||
1118 | 583 | If you still need help, you can reply to this email or go to the | 481 | If you still need help, you can reply to this email or go to the |
1119 | 584 | following page to enter your feedback: | 482 | following page to enter your feedback: |
1120 | 585 | http://.../ubuntu/+question/... | 483 | http://.../ubuntu/+question/... |
1121 | 586 | <BLANKLINE> | ||
1122 | 587 | You received this question notification because you asked the question. | ||
1123 | 588 | 484 | ||
1124 | 589 | 485 | ||
1125 | 590 | Notifications for confirm() | 486 | Notifications for confirm() |
1126 | @@ -595,14 +491,11 @@ | |||
1127 | 595 | ... "I've installed BootX and the installer CD is now booting. " | 491 | ... "I've installed BootX and the installer CD is now booting. " |
1128 | 596 | ... "Thanks!", answer=answer_message) | 492 | ... "Thanks!", answer=answer_message) |
1129 | 597 | 493 | ||
1133 | 598 | >>> notifications = pop_notifications() | 494 | >>> notifications = pop_questionemailjobs() |
1131 | 599 | >>> [email_msg['To'] for email_msg in notifications] | ||
1132 | 600 | ['support@ubuntu.com', 'test@canonical.com'] | ||
1134 | 601 | 495 | ||
1135 | 602 | Default notification when the owner confirms an answer: | 496 | Default notification when the owner confirms an answer: |
1136 | 603 | 497 | ||
1139 | 604 | >>> notification_body = notifications[0].get_payload(decode=True) | 498 | >>> print notifications[1].body |
1138 | 605 | >>> print notification_body #doctest: -NORMALIZE_WHITESPACE | ||
1140 | 606 | Question #... on Ubuntu changed: | 499 | Question #... on Ubuntu changed: |
1141 | 607 | http://.../ubuntu/+question/... | 500 | http://.../ubuntu/+question/... |
1142 | 608 | <BLANKLINE> | 501 | <BLANKLINE> |
1143 | @@ -610,15 +503,10 @@ | |||
1144 | 610 | <BLANKLINE> | 503 | <BLANKLINE> |
1145 | 611 | Sample Person confirmed that the question is solved: | 504 | Sample Person confirmed that the question is solved: |
1146 | 612 | I've installed BootX and the installer CD is now booting. Thanks! | 505 | I've installed BootX and the installer CD is now booting. Thanks! |
1147 | 613 | <BLANKLINE> | ||
1148 | 614 | --... | ||
1149 | 615 | You received this question notification because you are a member of | ||
1150 | 616 | Ubuntu Team, which is an answer contact for Ubuntu. | ||
1151 | 617 | 506 | ||
1152 | 618 | Notification received by the owner: | 507 | Notification received by the owner: |
1153 | 619 | 508 | ||
1156 | 620 | >>> notification_body = notifications[1].get_payload(decode=True) | 509 | >>> print notifications[0].body |
1155 | 621 | >>> print notification_body #doctest: -NORMALIZE_WHITESPACE | ||
1157 | 622 | Your question #... on Ubuntu changed: | 510 | Your question #... on Ubuntu changed: |
1158 | 623 | http://.../ubuntu/+question/... | 511 | http://.../ubuntu/+question/... |
1159 | 624 | <BLANKLINE> | 512 | <BLANKLINE> |
1160 | @@ -626,9 +514,6 @@ | |||
1161 | 626 | <BLANKLINE> | 514 | <BLANKLINE> |
1162 | 627 | You confirmed that the question is solved: | 515 | You confirmed that the question is solved: |
1163 | 628 | I've installed BootX and the installer CD is now booting. Thanks! | 516 | I've installed BootX and the installer CD is now booting. Thanks! |
1164 | 629 | <BLANKLINE> | ||
1165 | 630 | --... | ||
1166 | 631 | You received this question notification because you asked the question. | ||
1167 | 632 | 517 | ||
1168 | 633 | 518 | ||
1169 | 634 | Notifications for addComment() | 519 | Notifications for addComment() |
1170 | @@ -639,38 +524,27 @@ | |||
1171 | 639 | ... no_priv, "Unless you have lots of RAM... and even then, the " | 524 | ... no_priv, "Unless you have lots of RAM... and even then, the " |
1172 | 640 | ... "system will probably be very slow.") | 525 | ... "system will probably be very slow.") |
1173 | 641 | 526 | ||
1177 | 642 | >>> notifications = pop_notifications() | 527 | >>> notifications = pop_questionemailjobs() |
1175 | 643 | >>> [email_msg['To'] for email_msg in notifications] | ||
1176 | 644 | ['support@ubuntu.com', 'test@canonical.com'] | ||
1178 | 645 | 528 | ||
1179 | 646 | Default notification when a comment is posted: | 529 | Default notification when a comment is posted: |
1180 | 647 | 530 | ||
1183 | 648 | >>> notification_body = notifications[0].get_payload(decode=True) | 531 | >>> print notifications[1].body |
1182 | 649 | >>> print notification_body #doctest: -NORMALIZE_WHITESPACE | ||
1184 | 650 | Question #... on Ubuntu changed: | 532 | Question #... on Ubuntu changed: |
1185 | 651 | http://.../ubuntu/+question/... | 533 | http://.../ubuntu/+question/... |
1186 | 652 | <BLANKLINE> | 534 | <BLANKLINE> |
1187 | 653 | No Privileges Person posted a new comment: | 535 | No Privileges Person posted a new comment: |
1188 | 654 | Unless you have lots of RAM... and even then, the system will probably | 536 | Unless you have lots of RAM... and even then, the system will probably |
1189 | 655 | be very slow. | 537 | be very slow. |
1190 | 656 | <BLANKLINE> | ||
1191 | 657 | --... | ||
1192 | 658 | You received this question notification because you are a member of | ||
1193 | 659 | Ubuntu Team, which is an answer contact for Ubuntu. | ||
1194 | 660 | 538 | ||
1195 | 661 | Notification received by the owner: | 539 | Notification received by the owner: |
1196 | 662 | 540 | ||
1199 | 663 | >>> notification_body = notifications[1].get_payload(decode=True) | 541 | >>> print notifications[0].body |
1198 | 664 | >>> print notification_body #doctest: -NORMALIZE_WHITESPACE | ||
1200 | 665 | Your question #... on Ubuntu changed: | 542 | Your question #... on Ubuntu changed: |
1201 | 666 | http://.../ubuntu/+question/... | 543 | http://.../ubuntu/+question/... |
1202 | 667 | <BLANKLINE> | 544 | <BLANKLINE> |
1203 | 668 | No Privileges Person posted a new comment: | 545 | No Privileges Person posted a new comment: |
1204 | 669 | Unless you have lots of RAM... and even then, the system will probably | 546 | Unless you have lots of RAM... and even then, the system will probably |
1205 | 670 | be very slow. | 547 | be very slow. |
1206 | 671 | <BLANKLINE> | ||
1207 | 672 | --... | ||
1208 | 673 | You received this question notification because you asked the question. | ||
1209 | 674 | 548 | ||
1210 | 675 | 549 | ||
1211 | 676 | Notifications for reject() | 550 | Notifications for reject() |
1212 | @@ -681,14 +555,11 @@ | |||
1213 | 681 | >>> message = ubuntu_question.reject( | 555 | >>> message = ubuntu_question.reject( |
1214 | 682 | ... foo_bar, "Yeah! It will be awfully slow.") | 556 | ... foo_bar, "Yeah! It will be awfully slow.") |
1215 | 683 | 557 | ||
1219 | 684 | >>> notifications = pop_notifications() | 558 | >>> notifications = pop_questionemailjobs() |
1217 | 685 | >>> [email_msg['To'] for email_msg in notifications] | ||
1218 | 686 | ['support@ubuntu.com', 'test@canonical.com'] | ||
1220 | 687 | 559 | ||
1221 | 688 | Default notification when the question is rejected: | 560 | Default notification when the question is rejected: |
1222 | 689 | 561 | ||
1225 | 690 | >>> notification_body = notifications[0].get_payload(decode=True) | 562 | >>> print notifications[1].body |
1224 | 691 | >>> print notification_body #doctest: -NORMALIZE_WHITESPACE | ||
1226 | 692 | Question #... on Ubuntu changed: | 563 | Question #... on Ubuntu changed: |
1227 | 693 | http://.../ubuntu/+question/... | 564 | http://.../ubuntu/+question/... |
1228 | 694 | <BLANKLINE> | 565 | <BLANKLINE> |
1229 | @@ -697,14 +568,10 @@ | |||
1230 | 697 | Foo Bar rejected the question: | 568 | Foo Bar rejected the question: |
1231 | 698 | Yeah! It will be awfully slow. | 569 | Yeah! It will be awfully slow. |
1232 | 699 | <BLANKLINE> | 570 | <BLANKLINE> |
1233 | 700 | --... | ||
1234 | 701 | You received this question notification because you are a member of | ||
1235 | 702 | Ubuntu Team, which is an answer contact for Ubuntu. | ||
1236 | 703 | 571 | ||
1237 | 704 | Notification received by the owner: | 572 | Notification received by the owner: |
1238 | 705 | 573 | ||
1241 | 706 | >>> notification_body = notifications[1].get_payload(decode=True) | 574 | >>> print notifications[0].body |
1240 | 707 | >>> print notification_body #doctest: -NORMALIZE_WHITESPACE | ||
1242 | 708 | Your question #... on Ubuntu changed: | 575 | Your question #... on Ubuntu changed: |
1243 | 709 | http://.../ubuntu/+question/... | 576 | http://.../ubuntu/+question/... |
1244 | 710 | <BLANKLINE> | 577 | <BLANKLINE> |
1245 | @@ -718,8 +585,6 @@ | |||
1246 | 718 | explaining your point of view either by replying to this email or at | 585 | explaining your point of view either by replying to this email or at |
1247 | 719 | the following page: | 586 | the following page: |
1248 | 720 | http://.../ubuntu/+question/... | 587 | http://.../ubuntu/+question/... |
1249 | 721 | <BLANKLINE> | ||
1250 | 722 | You received this question notification because you asked the question. | ||
1251 | 723 | 588 | ||
1252 | 724 | 589 | ||
1253 | 725 | Notifications for setStatus() | 590 | Notifications for setStatus() |
1254 | @@ -730,14 +595,11 @@ | |||
1255 | 730 | >>> message = ubuntu_question.setStatus( | 595 | >>> message = ubuntu_question.setStatus( |
1256 | 731 | ... foo_bar, QuestionStatus.SOLVED, "The rejection was a mistake.") | 596 | ... foo_bar, QuestionStatus.SOLVED, "The rejection was a mistake.") |
1257 | 732 | 597 | ||
1261 | 733 | >>> notifications = pop_notifications() | 598 | >>> notifications = pop_questionemailjobs() |
1259 | 734 | >>> [email_msg['To'] for email_msg in notifications] | ||
1260 | 735 | ['support@ubuntu.com', 'test@canonical.com'] | ||
1262 | 736 | 599 | ||
1263 | 737 | Default notification when somebody changes the status: | 600 | Default notification when somebody changes the status: |
1264 | 738 | 601 | ||
1267 | 739 | >>> notification_body = notifications[0].get_payload(decode=True) | 602 | >>> print notifications[1].body |
1266 | 740 | >>> print notification_body #doctest: -NORMALIZE_WHITESPACE | ||
1268 | 741 | Question #... on Ubuntu changed: | 603 | Question #... on Ubuntu changed: |
1269 | 742 | http://.../ubuntu/+question/... | 604 | http://.../ubuntu/+question/... |
1270 | 743 | <BLANKLINE> | 605 | <BLANKLINE> |
1271 | @@ -745,15 +607,10 @@ | |||
1272 | 745 | <BLANKLINE> | 607 | <BLANKLINE> |
1273 | 746 | Foo Bar changed the question status: | 608 | Foo Bar changed the question status: |
1274 | 747 | The rejection was a mistake. | 609 | The rejection was a mistake. |
1275 | 748 | <BLANKLINE> | ||
1276 | 749 | --... | ||
1277 | 750 | You received this question notification because you are a member of | ||
1278 | 751 | Ubuntu Team, which is an answer contact for Ubuntu. | ||
1279 | 752 | 610 | ||
1280 | 753 | Notification received by the owner: | 611 | Notification received by the owner: |
1281 | 754 | 612 | ||
1284 | 755 | >>> notification_body = notifications[1].get_payload(decode=True) | 613 | >>> print notifications[0].body |
1283 | 756 | >>> print notification_body #doctest: -NORMALIZE_WHITESPACE | ||
1285 | 757 | Your question #... on Ubuntu changed: | 614 | Your question #... on Ubuntu changed: |
1286 | 758 | http://.../ubuntu/+question/... | 615 | http://.../ubuntu/+question/... |
1287 | 759 | <BLANKLINE> | 616 | <BLANKLINE> |
1288 | @@ -761,9 +618,6 @@ | |||
1289 | 761 | <BLANKLINE> | 618 | <BLANKLINE> |
1290 | 762 | Foo Bar changed the question status: | 619 | Foo Bar changed the question status: |
1291 | 763 | The rejection was a mistake. | 620 | The rejection was a mistake. |
1292 | 764 | <BLANKLINE> | ||
1293 | 765 | --... | ||
1294 | 766 | You received this question notification because you asked the question. | ||
1295 | 767 | 621 | ||
1296 | 768 | 622 | ||
1297 | 769 | Notifications for linkFAQ() | 623 | Notifications for linkFAQ() |
1298 | @@ -777,10 +631,7 @@ | |||
1299 | 777 | >>> firefox = getUtility(IProductSet).getByName('firefox') | 631 | >>> firefox = getUtility(IProductSet).getByName('firefox') |
1300 | 778 | >>> firefox_question = firefox.newQuestion( | 632 | >>> firefox_question = firefox.newQuestion( |
1301 | 779 | ... no_priv, 'How can I play Flash?', 'I want Flash!') | 633 | ... no_priv, 'How can I play Flash?', 'I want Flash!') |
1306 | 780 | 634 | >>> ignore = pop_questionemailjobs() | |
1303 | 781 | # Discard notifications. | ||
1304 | 782 | |||
1305 | 783 | >>> notifications = pop_notifications() | ||
1307 | 784 | 635 | ||
1308 | 785 | >>> login('test@canonical.com') | 636 | >>> login('test@canonical.com') |
1309 | 786 | >>> firefox_faq = firefox.getFAQ(10) | 637 | >>> firefox_faq = firefox.getFAQ(10) |
1310 | @@ -789,13 +640,9 @@ | |||
1311 | 789 | 640 | ||
1312 | 790 | >>> message = firefox_question.linkFAQ( | 641 | >>> message = firefox_question.linkFAQ( |
1313 | 791 | ... sample_person, firefox_faq, "Read the FAQ.") | 642 | ... sample_person, firefox_faq, "Read the FAQ.") |
1321 | 792 | 643 | >>> notifications = pop_questionemailjobs() | |
1322 | 793 | >>> notifications = pop_notifications() | 644 | |
1323 | 794 | >>> [email_msg['To'] for email_msg in notifications] | 645 | >>> print notifications[0].body |
1317 | 795 | ['no-priv@canonical.com'] | ||
1318 | 796 | |||
1319 | 797 | >>> notification_body = notifications[0].get_payload(decode=True) | ||
1320 | 798 | >>> print notification_body #doctest: -NORMALIZE_WHITESPACE | ||
1324 | 799 | Your question #... on Mozilla Firefox changed: | 646 | Your question #... on Mozilla Firefox changed: |
1325 | 800 | http://answers.launchpad.dev/firefox/+question/... | 647 | http://answers.launchpad.dev/firefox/+question/... |
1326 | 801 | <BLANKLINE> | 648 | <BLANKLINE> |
1327 | @@ -814,13 +661,9 @@ | |||
1328 | 814 | 661 | ||
1329 | 815 | >>> message = firefox_question.linkFAQ( | 662 | >>> message = firefox_question.linkFAQ( |
1330 | 816 | ... sample_person, None, "Sorry, this wasn't so useful.") | 663 | ... sample_person, None, "Sorry, this wasn't so useful.") |
1338 | 817 | 664 | >>> notifications = pop_questionemailjobs() | |
1339 | 818 | >>> notifications = pop_notifications() | 665 | |
1340 | 819 | >>> [email_msg['To'] for email_msg in notifications] | 666 | >>> print notifications[0].body |
1334 | 820 | ['no-priv@canonical.com'] | ||
1335 | 821 | |||
1336 | 822 | >>> notification_body = notifications[0].get_payload(decode=True) | ||
1337 | 823 | >>> print notification_body #doctest: -NORMALIZE_WHITESPACE | ||
1341 | 824 | Your question #... on Mozilla Firefox changed: | 667 | Your question #... on Mozilla Firefox changed: |
1342 | 825 | http://answers.launchpad.dev/firefox/+question/... | 668 | http://answers.launchpad.dev/firefox/+question/... |
1343 | 826 | <BLANKLINE> | 669 | <BLANKLINE> |
1344 | @@ -841,49 +684,9 @@ | |||
1345 | 841 | from bugs just like when a question is normally created. | 684 | from bugs just like when a question is normally created. |
1346 | 842 | 685 | ||
1347 | 843 | >>> bug_question = ubuntu.createQuestionFromBug(bug) | 686 | >>> bug_question = ubuntu.createQuestionFromBug(bug) |
1349 | 844 | >>> notifications = pop_notifications() | 687 | >>> notifications = pop_questionemailjobs() |
1350 | 845 | >>> len(notifications) | 688 | >>> len(notifications) |
1392 | 846 | 4 | 689 | 3 |
1352 | 847 | |||
1353 | 848 | >>> [email_msg['To'] for email_msg in notifications] | ||
1354 | 849 | ['no-priv@canonical.com', 'no-priv@canonical.com', | ||
1355 | 850 | 'support@ubuntu.com', 'support@ubuntu.com'] | ||
1356 | 851 | |||
1357 | 852 | |||
1358 | 853 | Notifications and Teams | ||
1359 | 854 | ----------------------- | ||
1360 | 855 | |||
1361 | 856 | When a team is subscribed to a question, there are two cases two | ||
1362 | 857 | consider. The first one is if the team has an email address set, a | ||
1363 | 858 | notification will only be sent to that address. (That email address is | ||
1364 | 859 | assumed to be a mailing list reaching all the team members.) We already | ||
1365 | 860 | saw an example of that case with the Ubuntu Team in the examples above. | ||
1366 | 861 | |||
1367 | 862 | The other case is when the team doesn't have an email address set. In | ||
1368 | 863 | that case, all the team members will be notified individually. | ||
1369 | 864 | |||
1370 | 865 | >>> launchpad_devs = getUtility(IPersonSet).getByName('launchpad') | ||
1371 | 866 | >>> ubuntu_question.subscribe(launchpad_devs) | ||
1372 | 867 | <QuestionSubscription...> | ||
1373 | 868 | |||
1374 | 869 | >>> login('test@canonical.com') | ||
1375 | 870 | >>> message = ubuntu_question.addComment(sample_person, 'A comment.') | ||
1376 | 871 | |||
1377 | 872 | >>> notifications = pop_notifications() | ||
1378 | 873 | >>> [email_msg['To'] for email_msg in notifications] | ||
1379 | 874 | ['foo.bar@canonical.com', 'support@ubuntu.com', 'test@canonical.com'] | ||
1380 | 875 | |||
1381 | 876 | Of course, if the user is also individually subscribed to the question, | ||
1382 | 877 | he will receives only one notification: | ||
1383 | 878 | |||
1384 | 879 | >>> ubuntu_question.subscribe(foo_bar) | ||
1385 | 880 | <QuestionSubscription...> | ||
1386 | 881 | |||
1387 | 882 | >>> message = ubuntu_question.addComment(sample_person, 'A comment.') | ||
1388 | 883 | |||
1389 | 884 | >>> notifications = pop_notifications() | ||
1390 | 885 | >>> [email_msg['To'] for email_msg in notifications] | ||
1391 | 886 | ['foo.bar@canonical.com', 'support@ubuntu.com', 'test@canonical.com'] | ||
1393 | 887 | 690 | ||
1394 | 888 | 691 | ||
1395 | 889 | Notifications and Localized Questions | 692 | Notifications and Localized Questions |
1396 | @@ -915,13 +718,10 @@ | |||
1397 | 915 | ... u'corretamente e mostra a minha versao do java. No entanto, ' | 718 | ... u'corretamente e mostra a minha versao do java. No entanto, ' |
1398 | 916 | ... u'mover o mouse na pagina faz com que o firefox quebre.'), | 719 | ... u'mover o mouse na pagina faz com que o firefox quebre.'), |
1399 | 917 | ... language=getUtility(ILanguageSet)['pt_BR']) | 720 | ... language=getUtility(ILanguageSet)['pt_BR']) |
1403 | 918 | >>> notifications = pop_notifications() | 721 | >>> notifications = pop_questionemailjobs() |
1401 | 919 | >>> [email_msg['To'] for email_msg in notifications] | ||
1402 | 920 | ['guilherme.salgado@canonical.com', 'test@canonical.com'] | ||
1404 | 921 | 722 | ||
1408 | 922 | >>> from email.Header import decode_header, make_header | 723 | >>> print notifications[0].subject.encode('ASCII', 'backslashreplace') |
1409 | 923 | >>> unicode(make_header(decode_header(notifications[0]['Subject']))) | 724 | [Question #...]: Abrir uma p\xe1gina que requer java quebra o firefox |
1407 | 924 | u'[Question #...]: Abrir uma p\xe1gina que requer java quebra o firefox' | ||
1410 | 925 | 725 | ||
1411 | 926 | Similarly, when a question in a non-English language is modified or its | 726 | Similarly, when a question in a non-English language is modified or its |
1412 | 927 | status changed, only the subscribers speaking that language will receive | 727 | status changed, only the subscribers speaking that language will receive |
1413 | @@ -931,9 +731,7 @@ | |||
1414 | 931 | ... "Veja o screenshot: http://tinyurl.com/y8jq8z") | 731 | ... "Veja o screenshot: http://tinyurl.com/y8jq8z") |
1415 | 932 | <QuestionMessage...> | 732 | <QuestionMessage...> |
1416 | 933 | 733 | ||
1420 | 934 | >>> notifications = pop_notifications() | 734 | >>> ignore = pop_questionemailjobs() |
1418 | 935 | >>> [email_msg['To'] for email_msg in notifications] | ||
1419 | 936 | ['guilherme.salgado@canonical.com', 'test@canonical.com'] | ||
1421 | 937 | 735 | ||
1422 | 938 | The exception to these general rules is that when a question is created | 736 | The exception to these general rules is that when a question is created |
1423 | 939 | in language spoken by none of the answer contacts, each one will receive | 737 | in language spoken by none of the answer contacts, each one will receive |
1424 | @@ -949,43 +747,35 @@ | |||
1425 | 949 | ... sample_person, title="Impossible d'installer Ubuntu", | 747 | ... sample_person, title="Impossible d'installer Ubuntu", |
1426 | 950 | ... description=u"Le CD ne semble pas fonctionn\xe9.", | 748 | ... description=u"Le CD ne semble pas fonctionn\xe9.", |
1427 | 951 | ... language=french) | 749 | ... language=french) |
1432 | 952 | >>> notifications = pop_notifications() | 750 | >>> notifications = pop_questionemailjobs() |
1429 | 953 | >>> [email_msg['To'] for email_msg in notifications] | ||
1430 | 954 | ['guilherme.salgado@canonical.com', 'support@ubuntu.com', | ||
1431 | 955 | 'test@canonical.com'] | ||
1433 | 956 | 751 | ||
1436 | 957 | >>> notifications[0]['Subject'] | 752 | >>> print notifications[1].subject |
1437 | 958 | "[Question #...]: (French) Impossible d'installer Ubuntu" | 753 | [Question #...]: (French) Impossible d'installer Ubuntu |
1438 | 959 | 754 | ||
1439 | 960 | # Define a function that will replace non-ascii character with | 755 | # Define a function that will replace non-ascii character with |
1440 | 961 | # its unicoded encoded value. | 756 | # its unicoded encoded value. |
1441 | 962 | # Effectively replace u'\xe9' by '\\e9'. | 757 | # Effectively replace u'\xe9' by '\\e9'. |
1442 | 963 | 758 | ||
1447 | 964 | >>> def escape_utf8_payload(message): | 759 | >>> def recode_text(notification): |
1448 | 965 | ... charset = message.get_content_charset() | 760 | ... return notification.body.encode('ASCII', 'backslashreplace') |
1445 | 966 | ... content = unicode(message.get_payload(decode=True), charset) | ||
1446 | 967 | ... return content.encode('us-ascii', 'backslashreplace') | ||
1449 | 968 | 761 | ||
1452 | 969 | >>> notification_body = escape_utf8_payload(notifications[0]) | 762 | >>> notification_body = recode_text(notifications[1]) |
1453 | 970 | >>> print notification_body #doctest: -NORMALIZE_WHITESPACE | 763 | >>> print notification_body |
1454 | 971 | A question was asked in a language (French) spoken by | 764 | A question was asked in a language (French) spoken by |
1455 | 972 | none of the registered Ubuntu answer contacts. | 765 | none of the registered Ubuntu answer contacts. |
1456 | 973 | <BLANKLINE> | 766 | <BLANKLINE> |
1457 | 974 | http://.../ubuntu/+question/... | 767 | http://.../ubuntu/+question/... |
1458 | 975 | <BLANKLINE> | 768 | <BLANKLINE> |
1459 | 976 | Le CD ne semble pas fonctionn\xe9... | 769 | Le CD ne semble pas fonctionn\xe9... |
1460 | 977 | --... | ||
1461 | 978 | You received this question notification because you are an answer | ||
1462 | 979 | contact for Ubuntu. | ||
1463 | 980 | 770 | ||
1464 | 981 | The notification received by the question owner contain a warning that | 771 | The notification received by the question owner contain a warning that |
1465 | 982 | the question is in a language spoken by none of the answer contacts: | 772 | the question is in a language spoken by none of the answer contacts: |
1466 | 983 | 773 | ||
1469 | 984 | >>> notifications[-1]['Subject'] | 774 | >>> print notifications[0].subject |
1470 | 985 | "[Question #...]: Impossible d'installer Ubuntu" | 775 | [Question #...]: Impossible d'installer Ubuntu |
1471 | 986 | 776 | ||
1474 | 987 | >>> notification_body = escape_utf8_payload(notifications[-1]) | 777 | >>> notification_body = recode_text(notifications[0]) |
1475 | 988 | >>> print notification_body #doctest: -NORMALIZE_WHITESPACE | 778 | >>> print notification_body |
1476 | 989 | New question #... on Ubuntu: | 779 | New question #... on Ubuntu: |
1477 | 990 | http://.../ubuntu/+question/... | 780 | http://.../ubuntu/+question/... |
1478 | 991 | <BLANKLINE> | 781 | <BLANKLINE> |
1479 | @@ -993,9 +783,6 @@ | |||
1480 | 993 | <BLANKLINE> | 783 | <BLANKLINE> |
1481 | 994 | WARNING: This question is asked in a language (French) | 784 | WARNING: This question is asked in a language (French) |
1482 | 995 | spoken by none of the registered Ubuntu answer contacts. | 785 | spoken by none of the registered Ubuntu answer contacts. |
1483 | 996 | <BLANKLINE> | ||
1484 | 997 | --... | ||
1485 | 998 | You received this question notification because you asked the question. | ||
1486 | 999 | 786 | ||
1487 | 1000 | No notification will be sent to the answer contacts when this question | 787 | No notification will be sent to the answer contacts when this question |
1488 | 1001 | is modified. Only the owner will receive a modification notification | 788 | is modified. Only the owner will receive a modification notification |
1489 | @@ -1006,13 +793,10 @@ | |||
1490 | 1006 | >>> french_question.title = u"CD d'Ubuntu ne d\xe9marre pas" | 793 | >>> french_question.title = u"CD d'Ubuntu ne d\xe9marre pas" |
1491 | 1007 | >>> notify(ObjectModifiedEvent( | 794 | >>> notify(ObjectModifiedEvent( |
1492 | 1008 | ... french_question, unmodified_question, ['title'])) | 795 | ... french_question, unmodified_question, ['title'])) |
1500 | 1009 | 796 | >>> notifications = pop_questionemailjobs() | |
1501 | 1010 | >>> notifications = pop_notifications() | 797 | |
1502 | 1011 | >>> [email_msg['To'] for email_msg in notifications] | 798 | >>> notification_body = recode_text(notifications[0]) |
1503 | 1012 | ['test@canonical.com'] | 799 | >>> print notification_body |
1497 | 1013 | |||
1498 | 1014 | >>> notification_body = escape_utf8_payload(notifications[0]) | ||
1499 | 1015 | >>> print notification_body #doctest: -NORMALIZE_WHITESPACE | ||
1504 | 1016 | Your question #... on Ubuntu changed: | 800 | Your question #... on Ubuntu changed: |
1505 | 1017 | http://.../ubuntu/+question/... | 801 | http://.../ubuntu/+question/... |
1506 | 1018 | <BLANKLINE> | 802 | <BLANKLINE> |
1507 | @@ -1021,89 +805,3 @@ | |||
1508 | 1021 | <BLANKLINE> | 805 | <BLANKLINE> |
1509 | 1022 | WARNING: This question is asked in a language (French) | 806 | WARNING: This question is asked in a language (French) |
1510 | 1023 | spoken by none of the registered Ubuntu answer contacts. | 807 | spoken by none of the registered Ubuntu answer contacts. |
1511 | 1024 | <BLANKLINE> | ||
1512 | 1025 | --... | ||
1513 | 1026 | You received this question notification because you asked the question. | ||
1514 | 1027 | |||
1515 | 1028 | |||
1516 | 1029 | Localized Questions and Teams | ||
1517 | 1030 | ............................. | ||
1518 | 1031 | |||
1519 | 1032 | We will notify the team only if the question language is in one of the | ||
1520 | 1033 | team's preferred languages. The languages spoken by the team members is | ||
1521 | 1034 | unimportant. | ||
1522 | 1035 | |||
1523 | 1036 | For example, the rosetta admins team becomes an Answer contact for | ||
1524 | 1037 | English questions. Carlos speaks Spanish, and he is an answer contact | ||
1525 | 1038 | for Ubuntu. He is also a member of the rosetta admins team. The team | ||
1526 | 1039 | wont receive emails because of his membership when they become answer | ||
1527 | 1040 | contacts too. | ||
1528 | 1041 | |||
1529 | 1042 | >>> rosetta_admins = getUtility(IPersonSet).getByName('rosetta-admins') | ||
1530 | 1043 | >>> [lang.code for lang in rosetta_admins.languages] | ||
1531 | 1044 | [] | ||
1532 | 1045 | |||
1533 | 1046 | >>> rosetta_admins.addLanguage(getUtility(ILanguageSet)['en']) | ||
1534 | 1047 | >>> carlos = getUtility(IPersonSet).getByName('carlos') | ||
1535 | 1048 | >>> carlos.inTeam(rosetta_admins) | ||
1536 | 1049 | True | ||
1537 | 1050 | |||
1538 | 1051 | >>> spanish = getUtility(ILanguageSet)['es'] | ||
1539 | 1052 | >>> spanish in carlos.languages | ||
1540 | 1053 | True | ||
1541 | 1054 | |||
1542 | 1055 | >>> ubuntu.addAnswerContact(carlos) | ||
1543 | 1056 | True | ||
1544 | 1057 | |||
1545 | 1058 | >>> ubuntu.addAnswerContact(rosetta_admins) | ||
1546 | 1059 | True | ||
1547 | 1060 | |||
1548 | 1061 | >>> spanish_question = ubuntu.newQuestion( | ||
1549 | 1062 | ... sample_person, title="Necesidad ayuda con Firefox", | ||
1550 | 1063 | ... description="No puedo acceso al Internet en Firefox.", | ||
1551 | 1064 | ... language=spanish) | ||
1552 | 1065 | >>> notifications = pop_notifications() | ||
1553 | 1066 | >>> [email_msg['To'] for email_msg in notifications] | ||
1554 | 1067 | ['carlos@canonical.com', 'test@canonical.com'] | ||
1555 | 1068 | |||
1556 | 1069 | >>> ubuntu.removeAnswerContact(carlos) | ||
1557 | 1070 | True | ||
1558 | 1071 | |||
1559 | 1072 | But if the team languages attribute is set, this set of languages will | ||
1560 | 1073 | be used. So, if the team only officially speaks French, it will only | ||
1561 | 1074 | receive notifications about French (and English) questions. | ||
1562 | 1075 | |||
1563 | 1076 | >>> rosetta_admins.addLanguage(french) | ||
1564 | 1077 | |||
1565 | 1078 | # Resend the new message notification | ||
1566 | 1079 | |||
1567 | 1080 | >>> notify(ObjectCreatedEvent(french_question)) | ||
1568 | 1081 | >>> notifications = pop_notifications() | ||
1569 | 1082 | >>> [email_msg['To'] for email_msg in notifications] | ||
1570 | 1083 | ['rosetta@launchpad.net', 'test@canonical.com'] | ||
1571 | 1084 | |||
1572 | 1085 | When the team doesn't use an explicit address. All team members will be | ||
1573 | 1086 | contacted if the question language is supported. For example, the | ||
1574 | 1087 | Launchpad Developers team doesn't have any preferred email address set. | ||
1575 | 1088 | Its only member, Foo Bar will receive a notification if the team | ||
1576 | 1089 | supported languages includes the question language: | ||
1577 | 1090 | |||
1578 | 1091 | >>> launchpad_devs = getUtility(IPersonSet).getByName('launchpad') | ||
1579 | 1092 | >>> list(launchpad_devs.languages) | ||
1580 | 1093 | [] | ||
1581 | 1094 | |||
1582 | 1095 | >>> [member.name for member in launchpad_devs.activemembers] | ||
1583 | 1096 | [u'name16'] | ||
1584 | 1097 | |||
1585 | 1098 | >>> launchpad_devs.addLanguage(spanish) | ||
1586 | 1099 | >>> ubuntu.addAnswerContact(launchpad_devs) | ||
1587 | 1100 | True | ||
1588 | 1101 | |||
1589 | 1102 | # Resend the new message notification | ||
1590 | 1103 | |||
1591 | 1104 | >>> notify(ObjectCreatedEvent(spanish_question)) | ||
1592 | 1105 | >>> notifications = pop_notifications() | ||
1593 | 1106 | >>> [email_msg['To'] for email_msg in notifications] | ||
1594 | 1107 | ['foo.bar@canonical.com', 'test@canonical.com'] | ||
1595 | 1108 | |||
1596 | 1109 | |||
1597 | 1110 | 808 | ||
1598 | === modified file 'lib/lp/answers/model/questionjob.py' | |||
1599 | --- lib/lp/answers/model/questionjob.py 2011-04-28 18:40:45 +0000 | |||
1600 | +++ lib/lp/answers/model/questionjob.py 2011-05-04 17:17:01 +0000 | |||
1601 | @@ -51,6 +51,7 @@ | |||
1602 | 51 | from lp.services.job.runner import BaseRunnableJob | 51 | from lp.services.job.runner import BaseRunnableJob |
1603 | 52 | from lp.services.mail.mailwrapper import MailWrapper | 52 | from lp.services.mail.mailwrapper import MailWrapper |
1604 | 53 | from lp.services.mail.notificationrecipientset import NotificationRecipientSet | 53 | from lp.services.mail.notificationrecipientset import NotificationRecipientSet |
1605 | 54 | from lp.services.mail.sendmail import format_address_for_person | ||
1606 | 54 | from lp.services.propertycache import cachedproperty | 55 | from lp.services.propertycache import cachedproperty |
1607 | 55 | 56 | ||
1608 | 56 | 57 | ||
1609 | @@ -170,7 +171,7 @@ | |||
1610 | 170 | 171 | ||
1611 | 171 | def getErrorRecipients(self): | 172 | def getErrorRecipients(self): |
1612 | 172 | """See `IRunnableJob`.""" | 173 | """See `IRunnableJob`.""" |
1614 | 173 | return self.user | 174 | return [format_address_for_person(self.user)] |
1615 | 174 | 175 | ||
1616 | 175 | @property | 176 | @property |
1617 | 176 | def from_address(self): | 177 | def from_address(self): |
1618 | 177 | 178 | ||
1619 | === modified file 'lib/lp/answers/notification.py' | |||
1620 | --- lib/lp/answers/notification.py 2011-04-27 13:59:57 +0000 | |||
1621 | +++ lib/lp/answers/notification.py 2011-05-04 17:17:01 +0000 | |||
1622 | @@ -10,16 +10,17 @@ | |||
1623 | 10 | 10 | ||
1624 | 11 | import os | 11 | import os |
1625 | 12 | 12 | ||
1626 | 13 | from zope.component import getUtility | ||
1627 | 14 | |||
1628 | 13 | from canonical.config import config | 15 | from canonical.config import config |
1632 | 14 | from canonical.launchpad.mail import ( | 16 | from canonical.launchpad.webapp.publisher import canonical_url |
1633 | 15 | format_address, | 17 | from lp.answers.enums import ( |
1634 | 16 | simple_sendmail, | 18 | QuestionAction, |
1635 | 19 | QuestionRecipientSet, | ||
1636 | 17 | ) | 20 | ) |
1639 | 18 | from canonical.launchpad.webapp.publisher import canonical_url | 21 | from lp.answers.interfaces.questionjob import IQuestionEmailJobSource |
1638 | 19 | from lp.answers.enums import QuestionAction | ||
1640 | 20 | from lp.registry.interfaces.person import IPerson | 22 | from lp.registry.interfaces.person import IPerson |
1641 | 21 | from lp.services.mail.mailwrapper import MailWrapper | 23 | from lp.services.mail.mailwrapper import MailWrapper |
1642 | 22 | from lp.services.mail.notificationrecipientset import NotificationRecipientSet | ||
1643 | 23 | from lp.services.propertycache import cachedproperty | 24 | from lp.services.propertycache import cachedproperty |
1644 | 24 | 25 | ||
1645 | 25 | 26 | ||
1646 | @@ -41,6 +42,8 @@ | |||
1647 | 41 | QuestionNotification can be registered as event subscribers. | 42 | QuestionNotification can be registered as event subscribers. |
1648 | 42 | """ | 43 | """ |
1649 | 43 | 44 | ||
1650 | 45 | recipient_set = QuestionRecipientSet.ASKER_SUBSCRIBER | ||
1651 | 46 | |||
1652 | 44 | def __init__(self, question, event): | 47 | def __init__(self, question, event): |
1653 | 45 | """Base constructor. | 48 | """Base constructor. |
1654 | 46 | 49 | ||
1655 | @@ -51,25 +54,15 @@ | |||
1656 | 51 | self.event = event | 54 | self.event = event |
1657 | 52 | self._user = IPerson(self.event.user) | 55 | self._user = IPerson(self.event.user) |
1658 | 53 | self.initialize() | 56 | self.initialize() |
1659 | 57 | self.job = None | ||
1660 | 54 | if self.shouldNotify(): | 58 | if self.shouldNotify(): |
1662 | 55 | self.send() | 59 | self.job = self.enqueue() |
1663 | 56 | 60 | ||
1664 | 57 | @property | 61 | @property |
1665 | 58 | def user(self): | 62 | def user(self): |
1666 | 59 | """Return the user from the event. """ | 63 | """Return the user from the event. """ |
1667 | 60 | return self._user | 64 | return self._user |
1668 | 61 | 65 | ||
1669 | 62 | def getFromAddress(self): | ||
1670 | 63 | """Return a formatted email address suitable for user in the From | ||
1671 | 64 | header of the question notification. | ||
1672 | 65 | |||
1673 | 66 | Default is Event Person Display Name <question#@answertracker_domain> | ||
1674 | 67 | """ | ||
1675 | 68 | return format_address( | ||
1676 | 69 | self.user.displayname, | ||
1677 | 70 | 'question%s@%s' % ( | ||
1678 | 71 | self.question.id, config.answertracker.email_domain)) | ||
1679 | 72 | |||
1680 | 73 | def getSubject(self): | 66 | def getSubject(self): |
1681 | 74 | """Return the subject of the notification. | 67 | """Return the subject of the notification. |
1682 | 75 | 68 | ||
1683 | @@ -114,18 +107,6 @@ | |||
1684 | 114 | 107 | ||
1685 | 115 | return headers | 108 | return headers |
1686 | 116 | 109 | ||
1687 | 117 | def getRecipients(self): | ||
1688 | 118 | """Return the recipient of the notification. | ||
1689 | 119 | |||
1690 | 120 | Default to the question's subscribers that speaks the request | ||
1691 | 121 | languages. If the question owner is subscribed, he's always consider | ||
1692 | 122 | to speak the language. | ||
1693 | 123 | |||
1694 | 124 | :return: A `INotificationRecipientSet` containing the recipients and | ||
1695 | 125 | rationale. | ||
1696 | 126 | """ | ||
1697 | 127 | return self.question.getRecipients() | ||
1698 | 128 | |||
1699 | 129 | def initialize(self): | 110 | def initialize(self): |
1700 | 130 | """Initialization hook for subclasses. | 111 | """Initialization hook for subclasses. |
1701 | 131 | 112 | ||
1702 | @@ -144,32 +125,16 @@ | |||
1703 | 144 | """ | 125 | """ |
1704 | 145 | return True | 126 | return True |
1705 | 146 | 127 | ||
1722 | 147 | def buildBody(self, body, rationale): | 128 | def enqueue(self): |
1723 | 148 | """Wrap the body and ensure the rationale is is separated.""" | 129 | """Create a job to send email about the event.""" |
1708 | 149 | wrapper = MailWrapper() | ||
1709 | 150 | body_parts = [body, wrapper.format(rationale)] | ||
1710 | 151 | if '\n-- ' not in body: | ||
1711 | 152 | body_parts.insert(1, '-- ') | ||
1712 | 153 | return '\n'.join(body_parts) | ||
1713 | 154 | |||
1714 | 155 | def send(self): | ||
1715 | 156 | """Sends the notification to all the notification recipients. | ||
1716 | 157 | |||
1717 | 158 | This method takes care of adding the rationale for contacting each | ||
1718 | 159 | recipient and also sets the X-Launchpad-Message-Rationale header on | ||
1719 | 160 | each message. | ||
1720 | 161 | """ | ||
1721 | 162 | from_address = self.getFromAddress() | ||
1724 | 163 | subject = self.getSubject() | 130 | subject = self.getSubject() |
1725 | 164 | body = self.getBody() | 131 | body = self.getBody() |
1726 | 165 | headers = self.getHeaders() | 132 | headers = self.getHeaders() |
1734 | 166 | recipients = self.getRecipients() | 133 | job_source = getUtility(IQuestionEmailJobSource) |
1735 | 167 | for email in recipients.getEmails(): | 134 | job = job_source.create( |
1736 | 168 | rationale, header = recipients.getReason(email) | 135 | self.question, self.user, self.recipient_set, |
1737 | 169 | headers['X-Launchpad-Message-Rationale'] = header | 136 | subject, body, headers) |
1738 | 170 | formatted_body = self.buildBody(body, rationale) | 137 | return job |
1732 | 171 | simple_sendmail( | ||
1733 | 172 | from_address, email, subject, formatted_body, headers) | ||
1739 | 173 | 138 | ||
1740 | 174 | @property | 139 | @property |
1741 | 175 | def unsupported_language(self): | 140 | def unsupported_language(self): |
1742 | @@ -215,6 +180,7 @@ | |||
1743 | 215 | class QuestionModifiedDefaultNotification(QuestionNotification): | 180 | class QuestionModifiedDefaultNotification(QuestionNotification): |
1744 | 216 | """Base implementation of a notification when a question is modified.""" | 181 | """Base implementation of a notification when a question is modified.""" |
1745 | 217 | 182 | ||
1746 | 183 | recipient_set = QuestionRecipientSet.SUBSCRIBER | ||
1747 | 218 | # Email template used to render the body. | 184 | # Email template used to render the body. |
1748 | 219 | body_template = "question-modified-notification.txt" | 185 | body_template = "question-modified-notification.txt" |
1749 | 220 | 186 | ||
1750 | @@ -347,18 +313,6 @@ | |||
1751 | 347 | 313 | ||
1752 | 348 | return get_email_template(self.body_template) % replacements | 314 | return get_email_template(self.body_template) % replacements |
1753 | 349 | 315 | ||
1754 | 350 | def getRecipients(self): | ||
1755 | 351 | """The default notification goes to all question subscribers that | ||
1756 | 352 | speak the request language, except the owner. | ||
1757 | 353 | """ | ||
1758 | 354 | original_recipients = QuestionNotification.getRecipients(self) | ||
1759 | 355 | recipients = NotificationRecipientSet() | ||
1760 | 356 | for person in original_recipients: | ||
1761 | 357 | if person != self.question.owner: | ||
1762 | 358 | rationale, header = original_recipients.getReason(person) | ||
1763 | 359 | recipients.add(person, rationale, header) | ||
1764 | 360 | return recipients | ||
1765 | 361 | |||
1766 | 362 | # Header template used when a new message is added to the question. | 316 | # Header template used when a new message is added to the question. |
1767 | 363 | action_header_template = { | 317 | action_header_template = { |
1768 | 364 | QuestionAction.REQUESTINFO: | 318 | QuestionAction.REQUESTINFO: |
1769 | @@ -397,6 +351,7 @@ | |||
1770 | 397 | class QuestionModifiedOwnerNotification(QuestionModifiedDefaultNotification): | 351 | class QuestionModifiedOwnerNotification(QuestionModifiedDefaultNotification): |
1771 | 398 | """Notification sent to the owner when his question is modified.""" | 352 | """Notification sent to the owner when his question is modified.""" |
1772 | 399 | 353 | ||
1773 | 354 | recipient_set = QuestionRecipientSet.ASKER | ||
1774 | 400 | # These actions will be done by the owner, so use the second person. | 355 | # These actions will be done by the owner, so use the second person. |
1775 | 401 | action_header_template = dict( | 356 | action_header_template = dict( |
1776 | 402 | QuestionModifiedDefaultNotification.action_header_template) | 357 | QuestionModifiedDefaultNotification.action_header_template) |
1777 | @@ -426,16 +381,6 @@ | |||
1778 | 426 | self.body_template = self.body_template_by_action.get( | 381 | self.body_template = self.body_template_by_action.get( |
1779 | 427 | self.new_message.action, self.body_template) | 382 | self.new_message.action, self.body_template) |
1780 | 428 | 383 | ||
1781 | 429 | def getRecipients(self): | ||
1782 | 430 | """Return the owner of the question if he's still subscribed.""" | ||
1783 | 431 | recipients = NotificationRecipientSet() | ||
1784 | 432 | owner = self.question.owner | ||
1785 | 433 | original_recipients = self.question.direct_recipients | ||
1786 | 434 | if owner in self.question.direct_recipients: | ||
1787 | 435 | rationale, header = original_recipients.getReason(owner) | ||
1788 | 436 | recipients.add(owner, rationale, header) | ||
1789 | 437 | return recipients | ||
1790 | 438 | |||
1791 | 439 | def getBody(self): | 384 | def getBody(self): |
1792 | 440 | """See QuestionNotification.""" | 385 | """See QuestionNotification.""" |
1793 | 441 | body = QuestionModifiedDefaultNotification.getBody(self) | 386 | body = QuestionModifiedDefaultNotification.getBody(self) |
1794 | @@ -447,6 +392,8 @@ | |||
1795 | 447 | class QuestionUnsupportedLanguageNotification(QuestionNotification): | 392 | class QuestionUnsupportedLanguageNotification(QuestionNotification): |
1796 | 448 | """Notification sent to answer contacts for unsupported languages.""" | 393 | """Notification sent to answer contacts for unsupported languages.""" |
1797 | 449 | 394 | ||
1798 | 395 | recipient_set = QuestionRecipientSet.CONTACT | ||
1799 | 396 | |||
1800 | 450 | def getSubject(self): | 397 | def getSubject(self): |
1801 | 451 | """See QuestionNotification.""" | 398 | """See QuestionNotification.""" |
1802 | 452 | return '[Question #%s]: (%s) %s' % ( | 399 | return '[Question #%s]: (%s) %s' % ( |
1803 | @@ -457,10 +404,6 @@ | |||
1804 | 457 | """Return True when the question is in an unsupported language.""" | 404 | """Return True when the question is in an unsupported language.""" |
1805 | 458 | return self.unsupported_language | 405 | return self.unsupported_language |
1806 | 459 | 406 | ||
1807 | 460 | def getRecipients(self): | ||
1808 | 461 | """Notify only the answer contacts.""" | ||
1809 | 462 | return self.question.target.getAnswerContactRecipients(None) | ||
1810 | 463 | |||
1811 | 464 | def getBody(self): | 407 | def getBody(self): |
1812 | 465 | """See QuestionNotification.""" | 408 | """See QuestionNotification.""" |
1813 | 466 | question = self.question | 409 | question = self.question |
1814 | 467 | 410 | ||
1815 | === removed file 'lib/lp/answers/stories/question-confirm-url.txt' | |||
1816 | --- lib/lp/answers/stories/question-confirm-url.txt 2009-11-11 22:17:17 +0000 | |||
1817 | +++ lib/lp/answers/stories/question-confirm-url.txt 1970-01-01 00:00:00 +0000 | |||
1818 | @@ -1,105 +0,0 @@ | |||
1819 | 1 | = Confirming an Answer using the Link in the Notification Email = | ||
1820 | 2 | |||
1821 | 3 | When an answer is posted on a question, its owner will usually receive a | ||
1822 | 4 | notification by email. That email includes a link that can be used by | ||
1823 | 5 | the owner to confirm that the answer solved his problem. | ||
1824 | 6 | |||
1825 | 7 | # We will use one browser objects for the owner, and one for the user | ||
1826 | 8 | # providing support, 'No Privileges Person' here. | ||
1827 | 9 | |||
1828 | 10 | >>> owner_browser = setupBrowser(auth='Basic test@canonical.com:test') | ||
1829 | 11 | >>> support_browser = setupBrowser( | ||
1830 | 12 | ... auth='Basic no-priv@canonical.com:test') | ||
1831 | 13 | |||
1832 | 14 | When the URL is used when the question isn't in the right state, the user | ||
1833 | 15 | will be redirected to the question page and a notification will be | ||
1834 | 16 | displayed: | ||
1835 | 17 | |||
1836 | 18 | >>> owner_browser.open( | ||
1837 | 19 | ... 'http://launchpad.dev/firefox/+question/2/+confirm?' | ||
1838 | 20 | ... 'answer_id=1') | ||
1839 | 21 | >>> owner_browser.url | ||
1840 | 22 | 'http://.../firefox/+question/2' | ||
1841 | 23 | |||
1842 | 24 | >>> soup = find_main_content(owner_browser.contents) | ||
1843 | 25 | >>> print soup.first('div', 'error message').renderContents() | ||
1844 | 26 | The question is not in a state where you can confirm an | ||
1845 | 27 | answer. | ||
1846 | 28 | |||
1847 | 29 | Posting an answer on the question will send an email notification | ||
1848 | 30 | containing a link to confirm that answer. | ||
1849 | 31 | |||
1850 | 32 | # First subscribe the owner, so that he receives the notification. | ||
1851 | 33 | >>> owner_browser.open( | ||
1852 | 34 | ... 'http://launchpad.dev/firefox/+question/2/+subscribe') | ||
1853 | 35 | >>> owner_browser.getControl('Subscribe').click() | ||
1854 | 36 | |||
1855 | 37 | # Post the answer... | ||
1856 | 38 | >>> support_browser.open('http://launchpad.dev/firefox/+question/2') | ||
1857 | 39 | >>> support_browser.getControl('Message').value = ( | ||
1858 | 40 | ... 'SVG is supported out of the box in recent versions of Firefox. ' | ||
1859 | 41 | ... 'I suggest you upgrade your browser.') | ||
1860 | 42 | >>> support_browser.getControl('Add Answer').click() | ||
1861 | 43 | |||
1862 | 44 | # ... and get the confirmation URL from the notification | ||
1863 | 45 | >>> import email | ||
1864 | 46 | >>> import re | ||
1865 | 47 | >>> from lp.services.mail import stub | ||
1866 | 48 | >>> notification = email.message_from_string(stub.test_emails[-1][2]) | ||
1867 | 49 | >>> urls = re.findall('(http:[^\s]+)+', notification.get_payload()) | ||
1868 | 50 | >>> confirm_url = urls[-2].decode('quoted-printable') | ||
1869 | 51 | >>> print confirm_url | ||
1870 | 52 | http://answers.launchpad.dev/firefox/+question/2/+confirm?answer_id=... | ||
1871 | 53 | |||
1872 | 54 | If a cropped URL or forged URL is used, an UnexpectedFormData error will | ||
1873 | 55 | be displayed. In the following example, the answer_id parameter refers | ||
1874 | 56 | to an answer not part of that question, it mimics a badly handcrafted | ||
1875 | 57 | URL: | ||
1876 | 58 | |||
1877 | 59 | >>> owner_browser.open( | ||
1878 | 60 | ... 'http://launchpad.dev/firefox/+question/2/+confirm?' | ||
1879 | 61 | ... 'answer_id=3') | ||
1880 | 62 | Traceback (most recent call last): | ||
1881 | 63 | ... | ||
1882 | 64 | UnexpectedFormData... | ||
1883 | 65 | |||
1884 | 66 | >>> owner_browser.open( | ||
1885 | 67 | ... 'http://launchpad.dev/firefox/+question/2/+confirm') | ||
1886 | 68 | Traceback (most recent call last): | ||
1887 | 69 | ... | ||
1888 | 70 | UnexpectedFormData... | ||
1889 | 71 | |||
1890 | 72 | The page is only accessible to the question owner: | ||
1891 | 73 | |||
1892 | 74 | >>> support_browser.open(confirm_url) | ||
1893 | 75 | Traceback (most recent call last): | ||
1894 | 76 | ... | ||
1895 | 77 | Unauthorized... | ||
1896 | 78 | |||
1897 | 79 | On the confirmation page, the user can see the answer that he is | ||
1898 | 80 | confirming. | ||
1899 | 81 | |||
1900 | 82 | >>> owner_browser.open(confirm_url) | ||
1901 | 83 | >>> soup = find_main_content(owner_browser.contents) | ||
1902 | 84 | >>> for comment in soup.fetch('div', 'boardCommentBody'): | ||
1903 | 85 | ... print comment.renderContents() | ||
1904 | 86 | <p>SVG is supported out of the box in recent versions of Firefox. I | ||
1905 | 87 | suggest you upgrade your browser.</p> | ||
1906 | 88 | |||
1907 | 89 | To confirm the answer, he needs to click the 'This Solved My Problem' | ||
1908 | 90 | button. He can enter an optional message along his confirmation. | ||
1909 | 91 | |||
1910 | 92 | >>> owner_browser.getControl('Message').value = ( | ||
1911 | 93 | ... "Thanks! This indeed solved the problem.") | ||
1912 | 94 | >>> owner_browser.getControl('This Solved My Problem').click() | ||
1913 | 95 | |||
1914 | 96 | This adds his comment to the question and mark it as 'Solved.' | ||
1915 | 97 | |||
1916 | 98 | >>> print extract_text( | ||
1917 | 99 | ... find_tag_by_id(owner_browser.contents, 'question-status')) | ||
1918 | 100 | Status: Solved ... | ||
1919 | 101 | >>> print find_tags_by_class( | ||
1920 | 102 | ... owner_browser.contents, 'boardCommentBody')[-1].renderContents() | ||
1921 | 103 | <p>Thanks! This indeed solved the problem.</p> | ||
1922 | 104 | |||
1923 | 105 | |||
1924 | 106 | 0 | ||
1925 | === modified file 'lib/lp/answers/tests/test_question_notifications.py' | |||
1926 | --- lib/lp/answers/tests/test_question_notifications.py 2011-04-23 01:31:22 +0000 | |||
1927 | +++ lib/lp/answers/tests/test_question_notifications.py 2011-05-04 17:17:01 +0000 | |||
1928 | @@ -5,18 +5,43 @@ | |||
1929 | 5 | 5 | ||
1930 | 6 | __metaclass__ = type | 6 | __metaclass__ = type |
1931 | 7 | 7 | ||
1932 | 8 | __all__ = [ | ||
1933 | 9 | 'pop_questionemailjobs', | ||
1934 | 10 | ] | ||
1935 | 11 | |||
1936 | 8 | from unittest import TestCase | 12 | from unittest import TestCase |
1937 | 9 | 13 | ||
1938 | 14 | from zope.component import getUtility | ||
1939 | 10 | from zope.interface import implements | 15 | from zope.interface import implements |
1940 | 16 | from zope.security.proxy import removeSecurityProxy | ||
1941 | 11 | 17 | ||
1942 | 18 | from canonical.testing import DatabaseFunctionalLayer | ||
1943 | 19 | from lp.answers.enums import QuestionRecipientSet | ||
1944 | 20 | from lp.answers.interfaces.questioncollection import IQuestionSet | ||
1945 | 21 | from lp.answers.model.questionjob import QuestionEmailJob | ||
1946 | 12 | from lp.answers.notification import ( | 22 | from lp.answers.notification import ( |
1947 | 13 | QuestionAddedNotification, | 23 | QuestionAddedNotification, |
1948 | 14 | QuestionModifiedDefaultNotification, | 24 | QuestionModifiedDefaultNotification, |
1949 | 25 | QuestionModifiedOwnerNotification, | ||
1950 | 26 | QuestionNotification, | ||
1951 | 27 | QuestionUnsupportedLanguageNotification, | ||
1952 | 15 | ) | 28 | ) |
1953 | 16 | from lp.registry.interfaces.person import IPerson | 29 | from lp.registry.interfaces.person import IPerson |
1957 | 17 | 30 | from lp.services.worlddata.interfaces.language import ILanguageSet | |
1958 | 18 | 31 | from lp.testing import TestCaseWithFactory | |
1959 | 19 | class TestQuestionModifiedNotification(QuestionModifiedDefaultNotification): | 32 | |
1960 | 33 | |||
1961 | 34 | def pop_questionemailjobs(): | ||
1962 | 35 | jobs = sorted( | ||
1963 | 36 | QuestionEmailJob.iterReady(), | ||
1964 | 37 | key=lambda job: job.metadata["recipient_set"]) | ||
1965 | 38 | for job in jobs: | ||
1966 | 39 | job.start() | ||
1967 | 40 | job.complete() | ||
1968 | 41 | return jobs | ||
1969 | 42 | |||
1970 | 43 | |||
1971 | 44 | class FakeQuestionModifiedNotification(QuestionModifiedDefaultNotification): | ||
1972 | 20 | """Subclass that do not send emails and with simpler initialization. | 45 | """Subclass that do not send emails and with simpler initialization. |
1973 | 21 | 46 | ||
1974 | 22 | Since notifications are handlers that accomplish their action on | 47 | Since notifications are handlers that accomplish their action on |
1975 | @@ -39,6 +64,7 @@ | |||
1976 | 39 | self.id = id | 64 | self.id = id |
1977 | 40 | self.title = title | 65 | self.title = title |
1978 | 41 | self.owner = FakeUser() | 66 | self.owner = FakeUser() |
1979 | 67 | self.messages = [] | ||
1980 | 42 | 68 | ||
1981 | 43 | 69 | ||
1982 | 44 | class StubQuestionMessage: | 70 | class StubQuestionMessage: |
1983 | @@ -56,6 +82,7 @@ | |||
1984 | 56 | class FakeEvent: | 82 | class FakeEvent: |
1985 | 57 | """A fake event.""" | 83 | """A fake event.""" |
1986 | 58 | user = FakeUser() | 84 | user = FakeUser() |
1987 | 85 | object_before_modification = StubQuestion() | ||
1988 | 59 | 86 | ||
1989 | 60 | 87 | ||
1990 | 61 | class QuestionModifiedDefaultNotificationTestCase(TestCase): | 88 | class QuestionModifiedDefaultNotificationTestCase(TestCase): |
1991 | @@ -63,22 +90,13 @@ | |||
1992 | 63 | 90 | ||
1993 | 64 | def setUp(self): | 91 | def setUp(self): |
1994 | 65 | """Create a notification with a fake question.""" | 92 | """Create a notification with a fake question.""" |
1996 | 66 | self.notification = TestQuestionModifiedNotification( | 93 | self.notification = FakeQuestionModifiedNotification( |
1997 | 67 | StubQuestion(), FakeEvent()) | 94 | StubQuestion(), FakeEvent()) |
1998 | 68 | 95 | ||
2012 | 69 | def test_buildBody_with_separator(self): | 96 | def test_recipient_set(self): |
2013 | 70 | # A body with a separator is preserved. | 97 | self.assertEqual( |
2014 | 71 | formatted_body = self.notification.buildBody( | 98 | QuestionRecipientSet.SUBSCRIBER, |
2015 | 72 | "body\n-- ", "rationale") | 99 | self.notification.recipient_set) |
2003 | 73 | self.assertEqual( | ||
2004 | 74 | "body\n-- \nrationale", formatted_body) | ||
2005 | 75 | |||
2006 | 76 | def test_buildBody_without_separator(self): | ||
2007 | 77 | # A separator will added to body if one is not present. | ||
2008 | 78 | formatted_body = self.notification.buildBody( | ||
2009 | 79 | "body -- mdash", "rationale") | ||
2010 | 80 | self.assertEqual( | ||
2011 | 81 | "body -- mdash\n-- \nrationale", formatted_body) | ||
2016 | 82 | 100 | ||
2017 | 83 | def test_getSubject(self): | 101 | def test_getSubject(self): |
2018 | 84 | """getSubject() when there is no message added to the question.""" | 102 | """getSubject() when there is no message added to the question.""" |
2019 | @@ -90,25 +108,120 @@ | |||
2020 | 90 | """The notification user is always the event user.""" | 108 | """The notification user is always the event user.""" |
2021 | 91 | question = StubQuestion() | 109 | question = StubQuestion() |
2022 | 92 | event = FakeEvent() | 110 | event = FakeEvent() |
2024 | 93 | notification = TestQuestionModifiedNotification(question, event) | 111 | notification = FakeQuestionModifiedNotification(question, event) |
2025 | 94 | self.assertEqual(event.user, notification.user) | 112 | self.assertEqual(event.user, notification.user) |
2026 | 95 | self.assertNotEqual(question.owner, notification.user) | 113 | self.assertNotEqual(question.owner, notification.user) |
2027 | 96 | 114 | ||
2028 | 97 | 115 | ||
2037 | 98 | class TestQuestionAddedNotification(QuestionAddedNotification): | 116 | class FakeQuestionModifiedOwnerNotification( |
2038 | 99 | """A subclass that does not send emails.""" | 117 | QuestionModifiedOwnerNotification): |
2039 | 100 | 118 | """A subclass that does not send emails.""" | |
2040 | 101 | def shouldNotify(self): | 119 | |
2041 | 102 | return False | 120 | def shouldNotify(self): |
2042 | 103 | 121 | return False | |
2043 | 104 | 122 | ||
2044 | 105 | class QuestionCreatedTestCase(TestCase): | 123 | |
2045 | 124 | class QuestionModifiedOwnerNotificationTestCase(TestCase): | ||
2046 | 125 | """Test cases for mail notifications about owner modified questions.""" | ||
2047 | 126 | |||
2048 | 127 | def setUp(self): | ||
2049 | 128 | self.question = StubQuestion() | ||
2050 | 129 | self.event = FakeEvent() | ||
2051 | 130 | self.notification = FakeQuestionModifiedOwnerNotification( | ||
2052 | 131 | self.question, self.event) | ||
2053 | 132 | |||
2054 | 133 | def test_recipient_set(self): | ||
2055 | 134 | self.assertEqual( | ||
2056 | 135 | QuestionRecipientSet.ASKER, | ||
2057 | 136 | self.notification.recipient_set) | ||
2058 | 137 | |||
2059 | 138 | |||
2060 | 139 | class FakeQuestionAddedNotification(QuestionAddedNotification): | ||
2061 | 140 | """A subclass that does not send emails.""" | ||
2062 | 141 | |||
2063 | 142 | def shouldNotify(self): | ||
2064 | 143 | return False | ||
2065 | 144 | |||
2066 | 145 | |||
2067 | 146 | class QuestionAddedNotificationTestCase(TestCase): | ||
2068 | 106 | """Test cases for mail notifications about created questions.""" | 147 | """Test cases for mail notifications about created questions.""" |
2069 | 107 | 148 | ||
2070 | 149 | def setUp(self): | ||
2071 | 150 | self.question = StubQuestion() | ||
2072 | 151 | self.event = FakeEvent() | ||
2073 | 152 | self.notification = FakeQuestionAddedNotification( | ||
2074 | 153 | self.question, self.event) | ||
2075 | 154 | |||
2076 | 155 | def test_recipient_set(self): | ||
2077 | 156 | self.assertEqual( | ||
2078 | 157 | QuestionRecipientSet.ASKER_SUBSCRIBER, | ||
2079 | 158 | self.notification.recipient_set) | ||
2080 | 159 | |||
2081 | 108 | def test_user_is_question_owner(self): | 160 | def test_user_is_question_owner(self): |
2082 | 109 | """The notification user is always the question owner.""" | 161 | """The notification user is always the question owner.""" |
2084 | 110 | question = StubQuestion() | 162 | self.assertEqual(self.question.owner, self.notification.user) |
2085 | 163 | self.assertNotEqual(self.event.user, self.notification.user) | ||
2086 | 164 | |||
2087 | 165 | |||
2088 | 166 | class FakeQuestionUnsupportedLanguageNotification( | ||
2089 | 167 | QuestionUnsupportedLanguageNotification): | ||
2090 | 168 | """A subclass that does not send emails.""" | ||
2091 | 169 | |||
2092 | 170 | def shouldNotify(self): | ||
2093 | 171 | return False | ||
2094 | 172 | |||
2095 | 173 | |||
2096 | 174 | class QuestionUnsupportedLanguageNotificationTestCase(TestCase): | ||
2097 | 175 | """Test notifications about questions with unsupported languages.""" | ||
2098 | 176 | |||
2099 | 177 | def setUp(self): | ||
2100 | 178 | self.question = StubQuestion() | ||
2101 | 179 | self.event = FakeEvent() | ||
2102 | 180 | self.notification = FakeQuestionUnsupportedLanguageNotification( | ||
2103 | 181 | self.question, self.event) | ||
2104 | 182 | |||
2105 | 183 | def test_recipient_set(self): | ||
2106 | 184 | self.assertEqual( | ||
2107 | 185 | QuestionRecipientSet.CONTACT, | ||
2108 | 186 | self.notification.recipient_set) | ||
2109 | 187 | |||
2110 | 188 | |||
2111 | 189 | class FakeQuestionNotification(QuestionNotification): | ||
2112 | 190 | """A subclass to exercise question notifcations.""" | ||
2113 | 191 | |||
2114 | 192 | recipient_set = QuestionRecipientSet.ASKER_SUBSCRIBER | ||
2115 | 193 | |||
2116 | 194 | def getBody(self): | ||
2117 | 195 | return 'body' | ||
2118 | 196 | |||
2119 | 197 | |||
2120 | 198 | class QuestionNotificationTestCase(TestCaseWithFactory): | ||
2121 | 199 | """Test common question notification behavior.""" | ||
2122 | 200 | |||
2123 | 201 | layer = DatabaseFunctionalLayer | ||
2124 | 202 | |||
2125 | 203 | def makeQuestion(self): | ||
2126 | 204 | """Create question that does not trigger a notification.""" | ||
2127 | 205 | asker = self.factory.makePerson() | ||
2128 | 206 | product = self.factory.makeProduct() | ||
2129 | 207 | naked_question_set = removeSecurityProxy(getUtility(IQuestionSet)) | ||
2130 | 208 | question = naked_question_set.new( | ||
2131 | 209 | title='title', description='description', owner=asker, | ||
2132 | 210 | language=getUtility(ILanguageSet)['en'], | ||
2133 | 211 | product=product, distribution=None, sourcepackagename=None) | ||
2134 | 212 | return question | ||
2135 | 213 | |||
2136 | 214 | def test_init_enqueue(self): | ||
2137 | 215 | # Creating a question notification creates a queation email job. | ||
2138 | 216 | question = self.makeQuestion() | ||
2139 | 111 | event = FakeEvent() | 217 | event = FakeEvent() |
2143 | 112 | notification = TestQuestionAddedNotification(question, event) | 218 | event.user = self.factory.makePerson() |
2144 | 113 | self.assertEqual(question.owner, notification.user) | 219 | notification = FakeQuestionNotification(question, event) |
2145 | 114 | self.assertNotEqual(event.user, notification.user) | 220 | self.assertEqual( |
2146 | 221 | notification.recipient_set.name, | ||
2147 | 222 | notification.job.metadata['recipient_set']) | ||
2148 | 223 | self.assertEqual(notification.question, notification.job.question) | ||
2149 | 224 | self.assertEqual(notification.user, notification.job.user) | ||
2150 | 225 | self.assertEqual(notification.getSubject(), notification.job.subject) | ||
2151 | 226 | self.assertEqual(notification.getBody(), notification.job.body) | ||
2152 | 227 | self.assertEqual(notification.getHeaders(), notification.job.headers) | ||
2153 | 115 | 228 | ||
2154 | === modified file 'lib/lp/answers/tests/test_questionjob.py' | |||
2155 | --- lib/lp/answers/tests/test_questionjob.py 2011-04-28 22:25:45 +0000 | |||
2156 | +++ lib/lp/answers/tests/test_questionjob.py 2011-05-04 17:17:01 +0000 | |||
2157 | @@ -11,6 +11,7 @@ | |||
2158 | 11 | from testtools.content_type import UTF8_TEXT | 11 | from testtools.content_type import UTF8_TEXT |
2159 | 12 | 12 | ||
2160 | 13 | from zope.component import getUtility | 13 | from zope.component import getUtility |
2161 | 14 | from zope.security.proxy import removeSecurityProxy | ||
2162 | 14 | 15 | ||
2163 | 15 | from canonical.launchpad.interfaces.lpstorm import IStore | 16 | from canonical.launchpad.interfaces.lpstorm import IStore |
2164 | 16 | from canonical.launchpad.mail import format_address | 17 | from canonical.launchpad.mail import format_address |
2165 | @@ -20,6 +21,7 @@ | |||
2166 | 20 | QuestionJobType, | 21 | QuestionJobType, |
2167 | 21 | QuestionRecipientSet, | 22 | QuestionRecipientSet, |
2168 | 22 | ) | 23 | ) |
2169 | 24 | from lp.answers.interfaces.questioncollection import IQuestionSet | ||
2170 | 23 | from lp.answers.interfaces.questionjob import IQuestionEmailJobSource | 25 | from lp.answers.interfaces.questionjob import IQuestionEmailJobSource |
2171 | 24 | from lp.answers.model.questionjob import ( | 26 | from lp.answers.model.questionjob import ( |
2172 | 25 | QuestionJob, | 27 | QuestionJob, |
2173 | @@ -28,6 +30,7 @@ | |||
2174 | 28 | from lp.services.job.interfaces.job import JobStatus | 30 | from lp.services.job.interfaces.job import JobStatus |
2175 | 29 | from lp.services.log.logger import BufferLogger | 31 | from lp.services.log.logger import BufferLogger |
2176 | 30 | from lp.services.mail import stub | 32 | from lp.services.mail import stub |
2177 | 33 | from lp.services.mail.sendmail import format_address_for_person | ||
2178 | 31 | from lp.services.worlddata.interfaces.language import ILanguageSet | 34 | from lp.services.worlddata.interfaces.language import ILanguageSet |
2179 | 32 | from lp.testing import ( | 35 | from lp.testing import ( |
2180 | 33 | run_script, | 36 | run_script, |
2181 | @@ -107,7 +110,14 @@ | |||
2182 | 107 | 110 | ||
2183 | 108 | def test_iterReady(self): | 111 | def test_iterReady(self): |
2184 | 109 | # Jobs in the ready state are returned by the iterator. | 112 | # Jobs in the ready state are returned by the iterator. |
2186 | 110 | question = self.factory.makeQuestion() | 113 | # Creating a question implicitly created an question email job. |
2187 | 114 | asker = self.factory.makePerson() | ||
2188 | 115 | product = self.factory.makeProduct() | ||
2189 | 116 | naked_question_set = removeSecurityProxy(getUtility(IQuestionSet)) | ||
2190 | 117 | question = naked_question_set.new( | ||
2191 | 118 | title='title', description='description', owner=asker, | ||
2192 | 119 | language=getUtility(ILanguageSet)['en'], | ||
2193 | 120 | product=product, distribution=None, sourcepackagename=None) | ||
2194 | 111 | user, subject, ignore, headers = self.makeUserSubjectBodyHeaders() | 121 | user, subject, ignore, headers = self.makeUserSubjectBodyHeaders() |
2195 | 112 | job_1 = QuestionEmailJob.create( | 122 | job_1 = QuestionEmailJob.create( |
2196 | 113 | question, user, QuestionRecipientSet.SUBSCRIBER, | 123 | question, user, QuestionRecipientSet.SUBSCRIBER, |
2197 | @@ -198,7 +208,8 @@ | |||
2198 | 198 | job = QuestionEmailJob.create( | 208 | job = QuestionEmailJob.create( |
2199 | 199 | question, user, QuestionRecipientSet.SUBSCRIBER, | 209 | question, user, QuestionRecipientSet.SUBSCRIBER, |
2200 | 200 | subject, body, headers) | 210 | subject, body, headers) |
2202 | 201 | self.assertEqual(user, job.getErrorRecipients()) | 211 | self.assertEqual( |
2203 | 212 | [format_address_for_person(job.user)], job.getErrorRecipients()) | ||
2204 | 202 | 213 | ||
2205 | 203 | def test_recipients_asker(self): | 214 | def test_recipients_asker(self): |
2206 | 204 | # The recipients property contains the question owner. | 215 | # The recipients property contains the question owner. |
2207 | @@ -323,8 +334,18 @@ | |||
2208 | 323 | def test_run_cronscript(self): | 334 | def test_run_cronscript(self): |
2209 | 324 | # The cronscript is configured: schema-lazr.conf and security.cfg. | 335 | # The cronscript is configured: schema-lazr.conf and security.cfg. |
2210 | 325 | question = self.factory.makeQuestion() | 336 | question = self.factory.makeQuestion() |
2211 | 337 | with person_logged_in(question.target.owner): | ||
2212 | 338 | question.linkBug(self.factory.makeBug(product=question.target)) | ||
2213 | 339 | question.linkFAQ( | ||
2214 | 340 | question.target.owner, | ||
2215 | 341 | self.factory.makeFAQ(target=question.target), | ||
2216 | 342 | 'test FAQ link') | ||
2217 | 326 | self.addAnswerContact(question) | 343 | self.addAnswerContact(question) |
2218 | 327 | user, subject, body, headers = self.makeUserSubjectBodyHeaders() | 344 | user, subject, body, headers = self.makeUserSubjectBodyHeaders() |
2219 | 345 | with person_logged_in(user): | ||
2220 | 346 | lang_set = getUtility(ILanguageSet) | ||
2221 | 347 | user.addLanguage(lang_set['en']) | ||
2222 | 348 | question.target.addAnswerContact(user) | ||
2223 | 328 | job = QuestionEmailJob.create( | 349 | job = QuestionEmailJob.create( |
2224 | 329 | question, user, QuestionRecipientSet.ASKER_SUBSCRIBER, | 350 | question, user, QuestionRecipientSet.ASKER_SUBSCRIBER, |
2225 | 330 | subject, body, headers) | 351 | subject, body, headers) |
2226 | @@ -336,6 +357,8 @@ | |||
2227 | 336 | self.addDetail("stdout", Content(UTF8_TEXT, lambda: out)) | 357 | self.addDetail("stdout", Content(UTF8_TEXT, lambda: out)) |
2228 | 337 | self.addDetail("stderr", Content(UTF8_TEXT, lambda: err)) | 358 | self.addDetail("stderr", Content(UTF8_TEXT, lambda: err)) |
2229 | 338 | self.assertEqual(0, exit_code) | 359 | self.assertEqual(0, exit_code) |
2230 | 360 | self.assertTrue( | ||
2231 | 361 | 'Traceback (most recent call last)' not in err) | ||
2232 | 339 | message = ( | 362 | message = ( |
2233 | 340 | 'QuestionEmailJob has sent email for question %s.' % question.id) | 363 | 'QuestionEmailJob has sent email for question %s.' % question.id) |
2234 | 341 | self.assertTrue( | 364 | self.assertTrue( |
2235 | 342 | 365 | ||
2236 | === modified file 'lib/lp/bugs/tests/test_bugnotification.py' | |||
2237 | --- lib/lp/bugs/tests/test_bugnotification.py 2011-04-05 22:34:35 +0000 | |||
2238 | +++ lib/lp/bugs/tests/test_bugnotification.py 2011-05-04 17:17:01 +0000 | |||
2239 | @@ -6,6 +6,7 @@ | |||
2240 | 6 | __metaclass__ = type | 6 | __metaclass__ = type |
2241 | 7 | 7 | ||
2242 | 8 | from itertools import chain | 8 | from itertools import chain |
2243 | 9 | import transaction | ||
2244 | 9 | import unittest | 10 | import unittest |
2245 | 10 | 11 | ||
2246 | 11 | from lazr.lifecycle.event import ObjectModifiedEvent | 12 | from lazr.lifecycle.event import ObjectModifiedEvent |
2247 | @@ -24,6 +25,7 @@ | |||
2248 | 24 | LaunchpadFunctionalLayer, | 25 | LaunchpadFunctionalLayer, |
2249 | 25 | LaunchpadZopelessLayer, | 26 | LaunchpadZopelessLayer, |
2250 | 26 | ) | 27 | ) |
2251 | 28 | from lp.answers.tests.test_question_notifications import pop_questionemailjobs | ||
2252 | 27 | from lp.bugs.interfaces.bugtask import ( | 29 | from lp.bugs.interfaces.bugtask import ( |
2253 | 28 | BugTaskStatus, | 30 | BugTaskStatus, |
2254 | 29 | IUpstreamBugTask, | 31 | IUpstreamBugTask, |
2255 | @@ -36,7 +38,6 @@ | |||
2256 | 36 | from lp.bugs.model.bugsubscriptionfilter import BugSubscriptionFilterMute | 38 | from lp.bugs.model.bugsubscriptionfilter import BugSubscriptionFilterMute |
2257 | 37 | from lp.testing import TestCaseWithFactory | 39 | from lp.testing import TestCaseWithFactory |
2258 | 38 | from lp.testing.factory import LaunchpadObjectFactory | 40 | from lp.testing.factory import LaunchpadObjectFactory |
2259 | 39 | from lp.testing.mail_helpers import pop_notifications | ||
2260 | 40 | from lp.testing.matchers import Contains | 41 | from lp.testing.matchers import Contains |
2261 | 41 | 42 | ||
2262 | 42 | 43 | ||
2263 | @@ -120,8 +121,9 @@ | |||
2264 | 120 | self.subscriber = self.factory.makePerson() | 121 | self.subscriber = self.factory.makePerson() |
2265 | 121 | question.subscribe(self.subscriber) | 122 | question.subscribe(self.subscriber) |
2266 | 122 | question.linkBug(self.bug) | 123 | question.linkBug(self.bug) |
2269 | 123 | # Flush pending notifications for question creation. | 124 | # Flush pending jobs for question creation. |
2270 | 124 | pop_notifications() | 125 | pop_questionemailjobs() |
2271 | 126 | transaction.commit() | ||
2272 | 125 | self.layer.switchDbUser(config.malone.expiration_dbuser) | 127 | self.layer.switchDbUser(config.malone.expiration_dbuser) |
2273 | 126 | 128 | ||
2274 | 127 | def test_notifications_for_question_subscribers(self): | 129 | def test_notifications_for_question_subscribers(self): |
2275 | @@ -134,10 +136,10 @@ | |||
2276 | 134 | bug_modified = ObjectModifiedEvent( | 136 | bug_modified = ObjectModifiedEvent( |
2277 | 135 | bugtask, bugtask_before_modification, ["status"]) | 137 | bugtask, bugtask_before_modification, ["status"]) |
2278 | 136 | notify(bug_modified) | 138 | notify(bug_modified) |
2279 | 139 | recipients = [ | ||
2280 | 140 | job.metadata['recipient_set'] for job in pop_questionemailjobs()] | ||
2281 | 137 | self.assertContentEqual( | 141 | self.assertContentEqual( |
2285 | 138 | [self.product.owner.preferredemail.email, | 142 | ['ASKER_SUBSCRIBER'], recipients) |
2283 | 139 | self.subscriber.preferredemail.email], | ||
2284 | 140 | [mail['To'] for mail in pop_notifications()]) | ||
2286 | 141 | 143 | ||
2287 | 142 | 144 | ||
2288 | 143 | class TestNotificationsLinkToFilters(TestCaseWithFactory): | 145 | class TestNotificationsLinkToFilters(TestCaseWithFactory): |
2289 | @@ -254,7 +256,7 @@ | |||
2290 | 254 | self.assertEqual( | 256 | self.assertEqual( |
2291 | 255 | {self.subscriber: {'sources': sources, | 257 | {self.subscriber: {'sources': sources, |
2292 | 256 | 'filter descriptions': []}, | 258 | 'filter descriptions': []}, |
2294 | 257 | subscriber2: {'sources': sources2, | 259 | subscriber2: {'sources': sources2, |
2295 | 258 | 'filter descriptions': [u'Special Filter!']}}, | 260 | 'filter descriptions': [u'Special Filter!']}}, |
2296 | 259 | BugNotificationSet().getRecipientFilterData( | 261 | BugNotificationSet().getRecipientFilterData( |
2297 | 260 | {self.subscriber: sources, subscriber2: sources2}, | 262 | {self.subscriber: sources, subscriber2: sources2}, |
2298 | @@ -278,7 +280,7 @@ | |||
2299 | 278 | # Perform the test. | 280 | # Perform the test. |
2300 | 279 | sources = list(self.notification.recipients) | 281 | sources = list(self.notification.recipients) |
2301 | 280 | sources.extend(self.notification2.recipients) | 282 | sources.extend(self.notification2.recipients) |
2303 | 281 | assert(len(sources)==2) | 283 | assert(len(sources) == 2) |
2304 | 282 | self.assertEqual( | 284 | self.assertEqual( |
2305 | 283 | {self.subscriber: {'sources': sources, | 285 | {self.subscriber: {'sources': sources, |
2306 | 284 | 'filter descriptions': ['Another Filter!', 'Special Filter!']}}, | 286 | 'filter descriptions': ['Another Filter!', 'Special Filter!']}}, |
2307 | @@ -316,7 +318,7 @@ | |||
2308 | 316 | sources = list(self.notification.recipients) | 318 | sources = list(self.notification.recipients) |
2309 | 317 | sources2 = list(notification2.recipients) | 319 | sources2 = list(notification2.recipients) |
2310 | 318 | self.assertEqual( | 320 | self.assertEqual( |
2312 | 319 | {subscriber2: {'sources': sources2, | 321 | {subscriber2: {'sources': sources2, |
2313 | 320 | 'filter descriptions': [u'Special Filter!']}}, | 322 | 'filter descriptions': [u'Special Filter!']}}, |
2314 | 321 | BugNotificationSet().getRecipientFilterData( | 323 | BugNotificationSet().getRecipientFilterData( |
2315 | 322 | {self.subscriber: sources, subscriber2: sources2}, | 324 | {self.subscriber: sources, subscriber2: sources2}, |
2316 | 323 | 325 | ||
2317 | === modified file 'lib/lp/coop/answersbugs/tests/notifications-linked-bug.txt' | |||
2318 | --- lib/lp/coop/answersbugs/tests/notifications-linked-bug.txt 2010-10-18 22:24:59 +0000 | |||
2319 | +++ lib/lp/coop/answersbugs/tests/notifications-linked-bug.txt 2011-05-04 17:17:01 +0000 | |||
2320 | @@ -1,4 +1,5 @@ | |||
2322 | 1 | = Linked Bug Status Changed Notification = | 1 | Linked Bug Status Changed Notification |
2323 | 2 | ====================================== | ||
2324 | 2 | 3 | ||
2325 | 3 | While a bug is linked to a question , its subscribers will be notified | 4 | While a bug is linked to a question , its subscribers will be notified |
2326 | 4 | of changes to the bug status: | 5 | of changes to the bug status: |
2327 | @@ -7,6 +8,8 @@ | |||
2328 | 7 | >>> from zope.interface import providedBy | 8 | >>> from zope.interface import providedBy |
2329 | 8 | >>> from lazr.lifecycle.event import ObjectModifiedEvent | 9 | >>> from lazr.lifecycle.event import ObjectModifiedEvent |
2330 | 9 | >>> from lazr.lifecycle.snapshot import Snapshot | 10 | >>> from lazr.lifecycle.snapshot import Snapshot |
2331 | 11 | >>> from lp.answers.tests.test_question_notifications import ( | ||
2332 | 12 | ... pop_questionemailjobs) | ||
2333 | 10 | >>> from lp.bugs.interfaces.bugtask import BugTaskStatus | 13 | >>> from lp.bugs.interfaces.bugtask import BugTaskStatus |
2334 | 11 | >>> from lp.registry.interfaces.person import IPersonSet | 14 | >>> from lp.registry.interfaces.person import IPersonSet |
2335 | 12 | 15 | ||
2336 | @@ -15,20 +18,22 @@ | |||
2337 | 15 | >>> original_bugtask = Snapshot(bugtask, providing=providedBy(bugtask)) | 18 | >>> original_bugtask = Snapshot(bugtask, providing=providedBy(bugtask)) |
2338 | 16 | >>> bugtask.transitionToStatus(BugTaskStatus.CONFIRMED, no_priv) | 19 | >>> bugtask.transitionToStatus(BugTaskStatus.CONFIRMED, no_priv) |
2339 | 17 | >>> bugtask.statusexplanation = 'This bug really happened to me.' | 20 | >>> bugtask.statusexplanation = 'This bug really happened to me.' |
2340 | 21 | >>> ignore = pop_questionemailjobs() | ||
2341 | 18 | >>> notify(ObjectModifiedEvent( | 22 | >>> notify(ObjectModifiedEvent( |
2342 | 19 | ... bugtask, original_bugtask, ['status', 'statusexplanation'], | 23 | ... bugtask, original_bugtask, ['status', 'statusexplanation'], |
2343 | 20 | ... user=no_priv)) | 24 | ... user=no_priv)) |
2344 | 21 | 25 | ||
2347 | 22 | >>> from lp.testing.mail_helpers import pop_notifications | 26 | >>> notifications = pop_questionemailjobs() |
2346 | 23 | >>> notifications = pop_notifications() | ||
2348 | 24 | >>> len(notifications) | 27 | >>> len(notifications) |
2354 | 25 | 2 | 28 | 1 |
2355 | 26 | >>> [notification['To'] for notification in notifications] | 29 | |
2356 | 27 | ['support@ubuntu.com', 'test@canonical.com'] | 30 | >>> print notifications[0].metadata['recipient_set'] |
2357 | 28 | >>> notification_body = notifications[0].get_payload(decode=True) | 31 | ASKER_SUBSCRIBER |
2358 | 29 | >>> print notifications[0]['Subject'] | 32 | |
2359 | 33 | >>> print notifications[0].subject | ||
2360 | 30 | [Question #...]: Status of bug #... changed to 'Confirmed' in Ubuntu | 34 | [Question #...]: Status of bug #... changed to 'Confirmed' in Ubuntu |
2362 | 31 | >>> print notification_body #doctest: -NORMALIZE_WHITESPACE | 35 | |
2363 | 36 | >>> print notifications[0].body | ||
2364 | 32 | Bug #... status changed in Ubuntu: | 37 | Bug #... status changed in Ubuntu: |
2365 | 33 | <BLANKLINE> | 38 | <BLANKLINE> |
2366 | 34 | New => Confirmed | 39 | New => Confirmed |
2367 | @@ -43,15 +48,12 @@ | |||
2368 | 43 | This bug is linked to #15. | 48 | This bug is linked to #15. |
2369 | 44 | Can't install Ubuntu | 49 | Can't install Ubuntu |
2370 | 45 | http://.../ubuntu/+question/... | 50 | http://.../ubuntu/+question/... |
2371 | 46 | <BLANKLINE> | ||
2372 | 47 | --... | ||
2373 | 48 | You received this question notification because you are a member of | ||
2374 | 49 | Ubuntu Team, which is an answer contact for Ubuntu. | ||
2375 | 50 | 51 | ||
2376 | 51 | Only a change in status triggers a notification. | 52 | Only a change in status triggers a notification. |
2377 | 52 | 53 | ||
2378 | 53 | >>> from lp.testing import login_person | 54 | >>> from lp.testing import login_person |
2380 | 54 | >>> sample_person = getUtility(IPersonSet).getByEmail('test@canonical.com') | 55 | >>> sample_person = getUtility(IPersonSet).getByEmail( |
2381 | 56 | ... 'test@canonical.com') | ||
2382 | 55 | >>> login_person(sample_person) | 57 | >>> login_person(sample_person) |
2383 | 56 | >>> original_bugtask = Snapshot(bugtask, providing=providedBy(bugtask)) | 58 | >>> original_bugtask = Snapshot(bugtask, providing=providedBy(bugtask)) |
2384 | 57 | >>> bugtask.transitionToAssignee(sample_person) | 59 | >>> bugtask.transitionToAssignee(sample_person) |
2385 | @@ -59,6 +61,5 @@ | |||
2386 | 59 | ... bugtask, original_bugtask, ['assignee', 'dateassigned'], | 61 | ... bugtask, original_bugtask, ['assignee', 'dateassigned'], |
2387 | 60 | ... user=sample_person)) | 62 | ... user=sample_person)) |
2388 | 61 | 63 | ||
2390 | 62 | >>> len(pop_notifications()) | 64 | >>> len(pop_questionemailjobs()) |
2391 | 63 | 0 | 65 | 0 |
2392 | 64 | |||
2393 | 65 | 66 | ||
2394 | === modified file 'lib/lp/coop/answersbugs/tests/notifications-linked-private-bug.txt' | |||
2395 | --- lib/lp/coop/answersbugs/tests/notifications-linked-private-bug.txt 2010-10-10 15:39:28 +0000 | |||
2396 | +++ lib/lp/coop/answersbugs/tests/notifications-linked-private-bug.txt 2011-05-04 17:17:01 +0000 | |||
2397 | @@ -1,4 +1,5 @@ | |||
2399 | 1 | = Linked Bug Status Changed Notification (Private) = | 1 | Linked Bug Status Changed Notification (Private) |
2400 | 2 | ================================================ | ||
2401 | 2 | 3 | ||
2402 | 3 | See `answer-tracker-notifications-linked-bug.txt` for public bug behavior. | 4 | See `answer-tracker-notifications-linked-bug.txt` for public bug behavior. |
2403 | 4 | 5 | ||
2404 | @@ -9,9 +10,10 @@ | |||
2405 | 9 | >>> from zope.interface import providedBy | 10 | >>> from zope.interface import providedBy |
2406 | 10 | >>> from lazr.lifecycle.event import ObjectModifiedEvent | 11 | >>> from lazr.lifecycle.event import ObjectModifiedEvent |
2407 | 11 | >>> from lazr.lifecycle.snapshot import Snapshot | 12 | >>> from lazr.lifecycle.snapshot import Snapshot |
2408 | 13 | >>> from lp.answers.tests.test_question_notifications import ( | ||
2409 | 14 | ... pop_questionemailjobs) | ||
2410 | 12 | >>> from lp.bugs.interfaces.bugtask import BugTaskStatus | 15 | >>> from lp.bugs.interfaces.bugtask import BugTaskStatus |
2411 | 13 | >>> from lp.registry.interfaces.person import IPersonSet | 16 | >>> from lp.registry.interfaces.person import IPersonSet |
2412 | 14 | >>> from lp.testing.mail_helpers import pop_notifications | ||
2413 | 15 | 17 | ||
2414 | 16 | >>> no_priv = getUtility(IPersonSet).getByName('no-priv') | 18 | >>> no_priv = getUtility(IPersonSet).getByName('no-priv') |
2415 | 17 | >>> bugtask = get_bugtask_linked_to_question() | 19 | >>> bugtask = get_bugtask_linked_to_question() |
2416 | @@ -20,8 +22,9 @@ | |||
2417 | 20 | True | 22 | True |
2418 | 21 | >>> original_bugtask = Snapshot(bugtask, providing=providedBy(bugtask)) | 23 | >>> original_bugtask = Snapshot(bugtask, providing=providedBy(bugtask)) |
2419 | 22 | >>> bugtask.transitionToStatus(BugTaskStatus.FIXCOMMITTED, no_priv) | 24 | >>> bugtask.transitionToStatus(BugTaskStatus.FIXCOMMITTED, no_priv) |
2420 | 25 | >>> ignore = pop_questionemailjobs() | ||
2421 | 23 | >>> notify(ObjectModifiedEvent( | 26 | >>> notify(ObjectModifiedEvent( |
2422 | 24 | ... bugtask, original_bugtask, ['status'], user=no_priv)) | 27 | ... bugtask, original_bugtask, ['status'], user=no_priv)) |
2424 | 25 | >>> notifications = pop_notifications() | 28 | >>> notifications = pop_questionemailjobs() |
2425 | 26 | >>> len(notifications) | 29 | >>> len(notifications) |
2426 | 27 | 0 | 30 | 0 |
2427 | 28 | 31 | ||
2428 | === added directory 'lib/lp/services/mail/doc' | |||
2429 | === renamed file 'lib/canonical/launchpad/doc/notification-recipient-set.txt' => 'lib/lp/services/mail/doc/notification-recipient-set.txt' | |||
2430 | --- lib/canonical/launchpad/doc/notification-recipient-set.txt 2010-12-06 22:10:11 +0000 | |||
2431 | +++ lib/lp/services/mail/doc/notification-recipient-set.txt 2011-05-04 17:17:01 +0000 | |||
2432 | @@ -1,8 +1,9 @@ | |||
2434 | 1 | = INotificationRecipientSet = | 1 | INotificationRecipientSet |
2435 | 2 | ========================= | ||
2436 | 2 | 3 | ||
2437 | 3 | It is part of Launchpad policy that all email notifications contain in | 4 | It is part of Launchpad policy that all email notifications contain in |
2440 | 4 | the footer an explanation of why the email was sent. A simpler string | 5 | the footer an explanation of why the email was sent. A simpler string is |
2441 | 5 | is also usually added to a X-Launchpad-Message-Rationale header to allow | 6 | also usually added to a X-Launchpad-Message-Rationale header to allow |
2442 | 6 | easy filtering. | 7 | easy filtering. |
2443 | 7 | 8 | ||
2444 | 8 | The easiest way to implement that policy is for methods returning a list | 9 | The easiest way to implement that policy is for methods returning a list |
2445 | @@ -11,11 +12,12 @@ | |||
2446 | 11 | recipient lists with the rationale for contacting them. | 12 | recipient lists with the rationale for contacting them. |
2447 | 12 | 13 | ||
2448 | 13 | There is a base implementation of the interface available as | 14 | There is a base implementation of the interface available as |
2452 | 14 | canonical.launchpad.mailnotification.NotificationRecipientSet. | 15 | canonical.launchpad.mailnotification.NotificationRecipientSet. You can |
2453 | 15 | You can use it as is or derive from it | 16 | use it as is or derive from it (see bugnotificationrecipients.txt for an |
2454 | 16 | (see bugnotificationrecipients.txt for an example of a derivation). | 17 | example of a derivation). |
2455 | 17 | 18 | ||
2457 | 18 | >>> from canonical.launchpad.interfaces.launchpad import INotificationRecipientSet | 19 | >>> from canonical.launchpad.interfaces.launchpad import ( |
2458 | 20 | ... INotificationRecipientSet) | ||
2459 | 19 | >>> from canonical.launchpad.webapp.testing import verifyObject | 21 | >>> from canonical.launchpad.webapp.testing import verifyObject |
2460 | 20 | >>> from canonical.launchpad.mailnotification import ( | 22 | >>> from canonical.launchpad.mailnotification import ( |
2461 | 21 | ... NotificationRecipientSet) | 23 | ... NotificationRecipientSet) |
2462 | @@ -24,10 +26,12 @@ | |||
2463 | 24 | >>> verifyObject(INotificationRecipientSet, recipients) | 26 | >>> verifyObject(INotificationRecipientSet, recipients) |
2464 | 25 | True | 27 | True |
2465 | 26 | 28 | ||
2470 | 27 | == Populating the set == | 29 | |
2471 | 28 | 30 | Populating the set | |
2472 | 29 | You add recipients to the set using the add() method. The method takes the | 31 | ------------------ |
2473 | 30 | IPerson to add along the notification rationale and header code. | 32 | |
2474 | 33 | You add recipients to the set using the add() method. The method takes | ||
2475 | 34 | the IPerson to add along the notification rationale and header code. | ||
2476 | 31 | 35 | ||
2477 | 32 | >>> from lp.registry.interfaces.person import IPersonSet | 36 | >>> from lp.registry.interfaces.person import IPersonSet |
2478 | 33 | >>> person_set = getUtility(IPersonSet) | 37 | >>> person_set = getUtility(IPersonSet) |
2479 | @@ -43,7 +47,8 @@ | |||
2480 | 43 | value is only used as an example. In practice, you should try to reuse | 47 | value is only used as an example. In practice, you should try to reuse |
2481 | 44 | existing values if they apply to your context. | 48 | existing values if they apply to your context. |
2482 | 45 | 49 | ||
2484 | 46 | The getPersons() method returns the list of recipients sorted by display name. | 50 | The getPersons() method returns the list of recipients sorted by display |
2485 | 51 | name. | ||
2486 | 47 | 52 | ||
2487 | 48 | >>> [person.displayname for person in recipients.getRecipients()] | 53 | >>> [person.displayname for person in recipients.getRecipients()] |
2488 | 49 | [u'Celso Providelo', u'Sample Person'] | 54 | [u'Celso Providelo', u'Sample Person'] |
2489 | @@ -55,19 +60,21 @@ | |||
2490 | 55 | Celso Providelo | 60 | Celso Providelo |
2491 | 56 | Sample Person | 61 | Sample Person |
2492 | 57 | 62 | ||
2495 | 58 | The getEmails() methods return the emails of all the recipients, also sorted | 63 | The getEmails() methods return the emails of all the recipients, also |
2496 | 59 | alphabetically: | 64 | sorted alphabetically: |
2497 | 60 | 65 | ||
2498 | 61 | >>> recipients.getEmails() | 66 | >>> recipients.getEmails() |
2499 | 62 | ['celso.providelo@canonical.com', 'test@canonical.com'] | 67 | ['celso.providelo@canonical.com', 'test@canonical.com'] |
2500 | 63 | 68 | ||
2503 | 64 | You can test if an IPerson or an email is part of the recipients using the | 69 | You can test if an IPerson or an email is part of the recipients using |
2504 | 65 | standard `in` operator: | 70 | the standard `in` operator: |
2505 | 66 | 71 | ||
2506 | 67 | >>> cprov in recipients | 72 | >>> cprov in recipients |
2507 | 68 | True | 73 | True |
2508 | 74 | |||
2509 | 69 | >>> 'celso.providelo@canonical.com' in recipients | 75 | >>> 'celso.providelo@canonical.com' in recipients |
2510 | 70 | True | 76 | True |
2511 | 77 | |||
2512 | 71 | >>> u'test@canonical.com' in recipients | 78 | >>> u'test@canonical.com' in recipients |
2513 | 72 | True | 79 | True |
2514 | 73 | 80 | ||
2515 | @@ -85,9 +92,12 @@ | |||
2516 | 85 | >>> bool(NotificationRecipientSet()) | 92 | >>> bool(NotificationRecipientSet()) |
2517 | 86 | False | 93 | False |
2518 | 87 | 94 | ||
2522 | 88 | == Obtaining the rationale == | 95 | |
2523 | 89 | 96 | Obtaining the rationale | |
2524 | 90 | You can obtain the rationale, header tuple by using the getReason() method: | 97 | ----------------------- |
2525 | 98 | |||
2526 | 99 | You can obtain the rationale, header tuple by using the getReason() | ||
2527 | 100 | method: | ||
2528 | 91 | 101 | ||
2529 | 92 | >>> recipients.getReason(cprov) | 102 | >>> recipients.getReason(cprov) |
2530 | 93 | ('You are notified for no reason.', 'Why not') | 103 | ('You are notified for no reason.', 'Why not') |
2531 | @@ -117,24 +127,29 @@ | |||
2532 | 117 | ... | 127 | ... |
2533 | 118 | AssertionError: ... | 128 | AssertionError: ... |
2534 | 119 | 129 | ||
2539 | 120 | == Team as recipient == | 130 | |
2540 | 121 | 131 | Team as recipient | |
2541 | 122 | Adding a team with a preferred email address works like adding any | 132 | ----------------- |
2542 | 123 | other person: | 133 | |
2543 | 134 | Adding a team with a preferred email address works like adding any other | ||
2544 | 135 | person: | ||
2545 | 124 | 136 | ||
2546 | 125 | >>> ubuntu_team = person_set.getByName('ubuntu-team') | 137 | >>> ubuntu_team = person_set.getByName('ubuntu-team') |
2547 | 126 | >>> login_person(ubuntu_team.teamowner) | 138 | >>> login_person(ubuntu_team.teamowner) |
2548 | 127 | >>> print ubuntu_team.preferredemail.email | 139 | >>> print ubuntu_team.preferredemail.email |
2549 | 128 | support@ubuntu.com | 140 | support@ubuntu.com |
2550 | 141 | |||
2551 | 129 | >>> recipients.add(ubuntu_team, 'You are notified for fun.', 'Fun') | 142 | >>> recipients.add(ubuntu_team, 'You are notified for fun.', 'Fun') |
2552 | 130 | 143 | ||
2553 | 131 | >>> ubuntu_team in recipients | 144 | >>> ubuntu_team in recipients |
2554 | 132 | True | 145 | True |
2555 | 146 | |||
2556 | 133 | >>> 'support@ubuntu.com' in recipients | 147 | >>> 'support@ubuntu.com' in recipients |
2557 | 134 | True | 148 | True |
2558 | 135 | 149 | ||
2559 | 136 | >>> [person.displayname for person in recipients] | 150 | >>> [person.displayname for person in recipients] |
2560 | 137 | [u'Celso Providelo', u'Sample Person', u'Ubuntu Team'] | 151 | [u'Celso Providelo', u'Sample Person', u'Ubuntu Team'] |
2561 | 152 | |||
2562 | 138 | >>> recipients.getEmails() | 153 | >>> recipients.getEmails() |
2563 | 139 | ['celso.providelo@canonical.com', 'support@ubuntu.com', | 154 | ['celso.providelo@canonical.com', 'support@ubuntu.com', |
2564 | 140 | 'test@canonical.com'] | 155 | 'test@canonical.com'] |
2565 | @@ -146,11 +161,13 @@ | |||
2566 | 146 | >>> ubuntu_gnome_team = person_set.getByName('name18') | 161 | >>> ubuntu_gnome_team = person_set.getByName('name18') |
2567 | 147 | >>> print ubuntu_gnome_team.preferredemail | 162 | >>> print ubuntu_gnome_team.preferredemail |
2568 | 148 | None | 163 | None |
2569 | 164 | |||
2570 | 149 | >>> recipients.add( | 165 | >>> recipients.add( |
2571 | 150 | ... ubuntu_gnome_team, | 166 | ... ubuntu_gnome_team, |
2572 | 151 | ... 'Notified because a member of the team', 'Team') | 167 | ... 'Notified because a member of the team', 'Team') |
2573 | 152 | >>> ubuntu_gnome_team in recipients | 168 | >>> ubuntu_gnome_team in recipients |
2574 | 153 | True | 169 | True |
2575 | 170 | |||
2576 | 154 | >>> recipients.getEmails() | 171 | >>> recipients.getEmails() |
2577 | 155 | ['andrew.bennetts@ubuntulinux.com', 'foo.bar@canonical.com', | 172 | ['andrew.bennetts@ubuntulinux.com', 'foo.bar@canonical.com', |
2578 | 156 | 'limi@plone.org', 'steve.alexander@ubuntulinux.com', | 173 | 'limi@plone.org', 'steve.alexander@ubuntulinux.com', |
2579 | @@ -162,21 +179,25 @@ | |||
2580 | 162 | [u'Ubuntu Gnome Team'] | 179 | [u'Ubuntu Gnome Team'] |
2581 | 163 | 180 | ||
2582 | 164 | So Sample Person is not in the recipients list, even if his email will | 181 | So Sample Person is not in the recipients list, even if his email will |
2585 | 165 | be notified for he's a member of Warty Security Team, itself a member | 182 | be notified for he's a member of Warty Security Team, itself a member of |
2586 | 166 | of Ubuntu Gnome Team: | 183 | Ubuntu Gnome Team: |
2587 | 167 | 184 | ||
2588 | 168 | >>> warty_security_team = person_set.getByName('name20') | 185 | >>> warty_security_team = person_set.getByName('name20') |
2589 | 169 | >>> print warty_security_team.displayname | 186 | >>> print warty_security_team.displayname |
2590 | 170 | Warty Security Team | 187 | Warty Security Team |
2591 | 188 | |||
2592 | 171 | >>> sample_person.inTeam(warty_security_team) | 189 | >>> sample_person.inTeam(warty_security_team) |
2593 | 172 | True | 190 | True |
2594 | 191 | |||
2595 | 173 | >>> warty_security_team.inTeam(ubuntu_gnome_team) | 192 | >>> warty_security_team.inTeam(ubuntu_gnome_team) |
2596 | 174 | True | 193 | True |
2597 | 194 | |||
2598 | 175 | >>> sample_person in ubuntu_gnome_team.activemembers | 195 | >>> sample_person in ubuntu_gnome_team.activemembers |
2599 | 176 | False | 196 | False |
2600 | 177 | 197 | ||
2601 | 178 | >>> sample_person in recipients | 198 | >>> sample_person in recipients |
2602 | 179 | False | 199 | False |
2603 | 200 | |||
2604 | 180 | >>> 'test@canonical.com' in recipients | 201 | >>> 'test@canonical.com' in recipients |
2605 | 181 | True | 202 | True |
2606 | 182 | 203 | ||
2607 | @@ -184,10 +205,13 @@ | |||
2608 | 184 | 205 | ||
2609 | 185 | >>> recipients.getReason(ubuntu_gnome_team) | 206 | >>> recipients.getReason(ubuntu_gnome_team) |
2610 | 186 | ('Notified because a member of the team', 'Team') | 207 | ('Notified because a member of the team', 'Team') |
2611 | 208 | |||
2612 | 187 | >>> recipients.getReason('test@canonical.com') | 209 | >>> recipients.getReason('test@canonical.com') |
2613 | 188 | ('Notified because a member of the team', 'Team') | 210 | ('Notified because a member of the team', 'Team') |
2614 | 189 | 211 | ||
2616 | 190 | == Adding many persons at the same time == | 212 | |
2617 | 213 | Adding many persons at the same time | ||
2618 | 214 | ------------------------------------ | ||
2619 | 191 | 215 | ||
2620 | 192 | If you pass an iterable sequence to the add() method, all members will | 216 | If you pass an iterable sequence to the add() method, all members will |
2621 | 193 | be added with the same rationale: | 217 | be added with the same rationale: |
2622 | @@ -200,27 +224,37 @@ | |||
2623 | 200 | 224 | ||
2624 | 201 | >>> recipients.getReason(no_priv) | 225 | >>> recipients.getReason(no_priv) |
2625 | 202 | ('Notified for fun.', 'Fun') | 226 | ('Notified for fun.', 'Fun') |
2626 | 227 | |||
2627 | 203 | >>> recipients.getReason(sample_person) | 228 | >>> recipients.getReason(sample_person) |
2628 | 204 | ('Notified for fun.', 'Fun') | 229 | ('Notified for fun.', 'Fun') |
2629 | 205 | 230 | ||
2633 | 206 | == Removing recipients == | 231 | |
2634 | 207 | 232 | Removing recipients | |
2635 | 208 | It is also possible to remove a person from the NotificationRecipientSet(): | 233 | ------------------- |
2636 | 234 | |||
2637 | 235 | It is also possible to remove a person from the | ||
2638 | 236 | NotificationRecipientSet(): | ||
2639 | 209 | 237 | ||
2640 | 210 | >>> recipients = NotificationRecipientSet() | 238 | >>> recipients = NotificationRecipientSet() |
2641 | 211 | >>> recipients.add( | 239 | >>> recipients.add( |
2642 | 212 | ... [sample_person, no_priv, cprov], 'Notified for fun.', 'Fun') | 240 | ... [sample_person, no_priv, cprov], 'Notified for fun.', 'Fun') |
2643 | 213 | >>> [person.displayname for person in recipients.getRecipients()] | 241 | >>> [person.displayname for person in recipients.getRecipients()] |
2644 | 214 | [u'Celso Providelo', u'No Privileges Person', u'Sample Person'] | 242 | [u'Celso Providelo', u'No Privileges Person', u'Sample Person'] |
2645 | 243 | |||
2646 | 215 | >>> recipients.remove([sample_person, cprov]) | 244 | >>> recipients.remove([sample_person, cprov]) |
2647 | 216 | >>> [person.displayname for person in recipients.getRecipients()] | 245 | >>> [person.displayname for person in recipients.getRecipients()] |
2648 | 217 | [u'No Privileges Person'] | 246 | [u'No Privileges Person'] |
2649 | 218 | 247 | ||
2655 | 219 | == A person's first impression sticks == | 248 | >>> recipients.getEmails() |
2656 | 220 | 249 | ['no-priv@canonical.com'] | |
2657 | 221 | In general, the most specific rationale is used for a given email. | 250 | |
2658 | 222 | A rationale given for a person is considered more | 251 | |
2659 | 223 | specific than one obtained through team membership. | 252 | A person's first impression sticks |
2660 | 253 | ---------------------------------- | ||
2661 | 254 | |||
2662 | 255 | In general, the most specific rationale is used for a given email. A | ||
2663 | 256 | rationale given for a person is considered more specific than one | ||
2664 | 257 | obtained through team membership. | ||
2665 | 224 | 258 | ||
2666 | 225 | So, if a person is added more than once to the set, the first reason | 259 | So, if a person is added more than once to the set, the first reason |
2667 | 226 | will be the one returned. | 260 | will be the one returned. |
2668 | @@ -265,7 +299,8 @@ | |||
2669 | 265 | ('Sample Person', 'Person') | 299 | ('Sample Person', 'Person') |
2670 | 266 | 300 | ||
2671 | 267 | 301 | ||
2673 | 268 | == Merging recipients set == | 302 | Merging recipients set |
2674 | 303 | ---------------------- | ||
2675 | 269 | 304 | ||
2676 | 270 | You can merge two recipients set by using the update() method. It will | 305 | You can merge two recipients set by using the update() method. It will |
2677 | 271 | add all the recipients in the second set along their rationale. If the | 306 | add all the recipients in the second set along their rationale. If the |
2678 | @@ -283,3 +318,5 @@ | |||
2679 | 283 | Celso Providelo: B (Reason B) | 318 | Celso Providelo: B (Reason B) |
2680 | 284 | No Privileges Person: B (Reason B) | 319 | No Privileges Person: B (Reason B) |
2681 | 285 | Sample Person: A (Reason A) | 320 | Sample Person: A (Reason A) |
2682 | 321 | |||
2683 | 322 | |||
2684 | 286 | 323 | ||
2685 | === modified file 'lib/lp/services/mail/notificationrecipientset.py' | |||
2686 | --- lib/lp/services/mail/notificationrecipientset.py 2011-03-30 20:08:42 +0000 | |||
2687 | +++ lib/lp/services/mail/notificationrecipientset.py 2011-05-04 17:17:01 +0000 | |||
2688 | @@ -131,6 +131,7 @@ | |||
2689 | 131 | removed_person.preferredemail) | 131 | removed_person.preferredemail) |
2690 | 132 | email = str(preferred_email.email) | 132 | email = str(preferred_email.email) |
2691 | 133 | self._receiving_people.discard((email, removed_person)) | 133 | self._receiving_people.discard((email, removed_person)) |
2692 | 134 | del self._emailToPerson[email] | ||
2693 | 134 | 135 | ||
2694 | 135 | def update(self, recipient_set): | 136 | def update(self, recipient_set): |
2695 | 136 | """See `INotificationRecipientSet`.""" | 137 | """See `INotificationRecipientSet`.""" |
2696 | 137 | 138 | ||
2697 | === added file 'lib/lp/services/mail/tests/test_doc.py' | |||
2698 | --- lib/lp/services/mail/tests/test_doc.py 1970-01-01 00:00:00 +0000 | |||
2699 | +++ lib/lp/services/mail/tests/test_doc.py 2011-05-04 17:17:01 +0000 | |||
2700 | @@ -0,0 +1,19 @@ | |||
2701 | 1 | # Copyright 2011 Canonical Ltd. This software is licensed under the | ||
2702 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
2703 | 3 | |||
2704 | 4 | """Test mail documentation.""" | ||
2705 | 5 | |||
2706 | 6 | __metaclass__ = type | ||
2707 | 7 | |||
2708 | 8 | import os | ||
2709 | 9 | |||
2710 | 10 | from canonical.testing.layers import DatabaseFunctionalLayer | ||
2711 | 11 | from lp.services.testing import build_test_suite | ||
2712 | 12 | |||
2713 | 13 | |||
2714 | 14 | here = os.path.dirname(os.path.realpath(__file__)) | ||
2715 | 15 | |||
2716 | 16 | |||
2717 | 17 | def test_suite(): | ||
2718 | 18 | suite = build_test_suite(here, {}, layer=DatabaseFunctionalLayer) | ||
2719 | 19 | return suite |
Someone else is already lined up to do this review.