launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #19254
[Merge] lp:~cjwatson/launchpad/team-mail into lp:launchpad
Colin Watson has proposed merging lp:~cjwatson/launchpad/team-mail into lp:launchpad.
Commit message:
Convert team membership notifications to BaseMailer.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #296889 in Launchpad itself: "Team membership notification doesn't explain why you're receiving it"
https://bugs.launchpad.net/launchpad/+bug/296889
Bug #508897 in Launchpad itself: "Add X-Launchpad-Membership-Rationale header to team emails"
https://bugs.launchpad.net/launchpad/+bug/508897
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/team-mail/+merge/269382
Convert team membership notifications to BaseMailer.
The main subtlety here is that there may be multiple teams involved (for example, you may be receiving a notification about a change in one team due to your membership of another team), so I had to think quite carefully to make sure that the semantics of X-Launchpad-Message-Rationale construction were followed properly. Also, I've tried to ensure that X-Launchpad-Message-Rationale describes your relation to the object that causes you to receive the message, while X-Launchpad-Notification-Type describes what action was performed. The diff to lib/lp/registry/doc/teammembership-email-notification.txt serves as a fairly good summary.
--
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/team-mail into lp:launchpad.
=== modified file 'lib/lp/code/mail/branchmergeproposal.py'
--- lib/lp/code/mail/branchmergeproposal.py 2015-08-23 22:53:55 +0000
+++ lib/lp/code/mail/branchmergeproposal.py 2015-08-27 14:49:13 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Email notifications related to branch merge proposals."""
@@ -93,7 +93,7 @@
merge_proposal, from_address, message_id=get_msgid(),
preview_diff=merge_proposal.preview_diff, direct_email=True)
- def _getReplyToAddress(self):
+ def _getReplyToAddress(self, email, recipient):
"""Return the address to use for the reply-to header."""
return self.merge_proposal.address
=== modified file 'lib/lp/code/mail/tests/test_codereviewcomment.py'
--- lib/lp/code/mail/tests/test_codereviewcomment.py 2015-08-23 22:53:55 +0000
+++ lib/lp/code/mail/tests/test_codereviewcomment.py 2015-08-27 14:49:13 +0000
@@ -131,7 +131,10 @@
mailer, subscriber = self.makeMailer()
merge_proposal = mailer.code_review_comment.branch_merge_proposal
expected = 'mp+%d@xxxxxxxxxxxxxxxxxx' % merge_proposal.id
- self.assertEqual(expected, mailer._getReplyToAddress())
+ self.assertEqual(
+ expected,
+ mailer._getReplyToAddress(
+ subscriber.preferredemail.email, subscriber))
def test_generateEmail(self):
"""Ensure mailer's generateEmail method produces expected values."""
@@ -155,7 +158,8 @@
'X-Launchpad-Notification-Type': 'code-review',
'X-Launchpad-Project': source_branch.product.name,
'Message-Id': message.rfc822msgid,
- 'Reply-To': mailer._getReplyToAddress(),
+ 'Reply-To': mailer._getReplyToAddress(
+ subscriber.preferredemail.email, subscriber),
'In-Reply-To': message.parent.rfc822msgid}
for header, value in expected.items():
self.assertEqual(value, ctrl.headers[header], header)
=== modified file 'lib/lp/registry/doc/teammembership-email-notification.txt'
--- lib/lp/registry/doc/teammembership-email-notification.txt 2015-02-26 03:00:35 +0000
+++ lib/lp/registry/doc/teammembership-email-notification.txt 2015-08-27 14:49:13 +0000
@@ -84,10 +84,13 @@
>>> print_distinct_emails(include_reply_to=True)
From: Ubuntu Team <noreply@xxxxxxxxxxxxx>
- To: colin.watson@xxxxxxxxxxxxxxx, foo.bar@xxxxxxxxxxxxx,
- jeff.waugh@xxxxxxxxxxxxxxx, limi@xxxxxxxxx
+ To: Alexander Limi <limi@xxxxxxxxx>,
+ Colin Watson <colin.watson@xxxxxxxxxxxxxxx>,
+ Foo Bar <foo.bar@xxxxxxxxxxxxx>,
+ Jeff Waugh <jeff.waugh@xxxxxxxxxxxxxxx>
Reply-To: robertc@xxxxxxxxxxxxxxxxx
X-Launchpad-Message-Rationale: Admin (ubuntu-team)
+ X-Launchpad-Notification-Type: pending-membership-approval
Subject: lifeless wants to join
<BLANKLINE>
Robert Collins (lifeless) wants to be a member of Ubuntu Team (ubuntu-
@@ -100,14 +103,17 @@
-- =
<BLANKLINE>
You received this email because you are an admin of the Ubuntu Team team.
+ <BLANKLINE>
----------------------------------------
From: Ubuntu Team <noreply@xxxxxxxxxxxxx>
- To: mark@xxxxxxxxxxx
+ To: Mark Shuttleworth <mark@xxxxxxxxxxx>
Reply-To: robertc@xxxxxxxxxxxxxxxxx
X-Launchpad-Message-Rationale: Owner (ubuntu-team)
+ X-Launchpad-Notification-Type: pending-membership-approval
Subject: lifeless wants to join
...
You received this email because you are the owner of the Ubuntu Team team.
+ <BLANKLINE>
----------------------------------------
Declining a proposed member should generate notifications for both the
@@ -128,22 +134,39 @@
>>> print_distinct_emails()
From: Ubuntu Team <noreply@xxxxxxxxxxxxx>
- To: colin.watson@xxxxxxxxxxxxxxx, foo.bar@xxxxxxxxxxxxx,
- jeff.waugh@xxxxxxxxxxxxxxx, limi@xxxxxxxxx, mark@xxxxxxxxxxx
+ To: Alexander Limi <limi@xxxxxxxxx>,
+ Colin Watson <colin.watson@xxxxxxxxxxxxxxx>,
+ Foo Bar <foo.bar@xxxxxxxxxxxxx>,
+ Jeff Waugh <jeff.waugh@xxxxxxxxxxxxxxx>,
+ Mark Shuttleworth <mark@xxxxxxxxxxx>
+ X-Launchpad-Message-Rationale: Admin (ubuntu-team)
+ X-Launchpad-Notification-Type: membership-statuschange
Subject: lifeless declined by mark
<BLANKLINE>
The membership status of Robert Collins (lifeless) in the team Ubuntu
Team (ubuntu-team) was changed by Mark Shuttleworth (mark) from
Proposed to Declined.
<http://launchpad.dev/~ubuntu-team>
+ <BLANKLINE>
+ -- =
+ <BLANKLINE>
+ You received this email because you are an admin of the Ubuntu Team team.
+ <BLANKLINE>
----------------------------------------
From: Ubuntu Team <noreply@xxxxxxxxxxxxx>
- To: robertc@xxxxxxxxxxxxxxxxx
+ To: Robert Collins <robertc@xxxxxxxxxxxxxxxxx>
+ X-Launchpad-Message-Rationale: Member (ubuntu-team)
+ X-Launchpad-Notification-Type: membership-statuschange
Subject: lifeless declined by mark
<BLANKLINE>
The status of your membership in the team Ubuntu Team (ubuntu-team) was
changed by Mark Shuttleworth (mark) from Proposed to Declined.
<http://launchpad.dev/~ubuntu-team>
+ <BLANKLINE>
+ -- =
+ <BLANKLINE>
+ You received this email because you are the affected member.
+ <BLANKLINE>
----------------------------------------
The same goes for approving a proposed member.
@@ -169,8 +192,13 @@
>>> print_distinct_emails()
From: Ubuntu Team <noreply@xxxxxxxxxxxxx>
- To: colin.watson@xxxxxxxxxxxxxxx, foo.bar@xxxxxxxxxxxxx,
- jeff.waugh@xxxxxxxxxxxxxxx, limi@xxxxxxxxx, mark@xxxxxxxxxxx
+ To: Alexander Limi <limi@xxxxxxxxx>,
+ Colin Watson <colin.watson@xxxxxxxxxxxxxxx>,
+ Foo Bar <foo.bar@xxxxxxxxxxxxx>,
+ Jeff Waugh <jeff.waugh@xxxxxxxxxxxxxxx>,
+ Mark Shuttleworth <mark@xxxxxxxxxxx>
+ X-Launchpad-Message-Rationale: Admin (ubuntu-team)
+ X-Launchpad-Notification-Type: membership-statuschange
Subject: daf approved by mark
<BLANKLINE>
The membership status of Dafydd Harries (daf) in the team Ubuntu Team
@@ -180,9 +208,15 @@
<BLANKLINE>
Mark Shuttleworth said:
This is a nice guy; I like him
+ -- =
+ <BLANKLINE>
+ You received this email because you are an admin of the Ubuntu Team team.
+ <BLANKLINE>
----------------------------------------
From: Ubuntu Team <noreply@xxxxxxxxxxxxx>
- To: daf@xxxxxxxxxxxxx
+ To: Dafydd Harries <daf@xxxxxxxxxxxxx>
+ X-Launchpad-Message-Rationale: Member (ubuntu-team)
+ X-Launchpad-Notification-Type: membership-statuschange
Subject: daf approved by mark
<BLANKLINE>
The status of your membership in the team Ubuntu Team (ubuntu-team) was
@@ -191,6 +225,10 @@
<BLANKLINE>
Mark Shuttleworth said:
This is a nice guy; I like him
+ -- =
+ <BLANKLINE>
+ You received this email because you are the affected member.
+ <BLANKLINE>
----------------------------------------
The same for deactivating a membership.
@@ -204,22 +242,37 @@
>>> print_distinct_emails()
From: Ubuntu Team <noreply@xxxxxxxxxxxxx>
- To: colin.watson@xxxxxxxxxxxxxxx, foo.bar@xxxxxxxxxxxxx,
- jeff.waugh@xxxxxxxxxxxxxxx, limi@xxxxxxxxx, mark@xxxxxxxxxxx
+ To: Alexander Limi <limi@xxxxxxxxx>,
+ Colin Watson <colin.watson@xxxxxxxxxxxxxxx>,
+ Foo Bar <foo.bar@xxxxxxxxxxxxx>,
+ Jeff Waugh <jeff.waugh@xxxxxxxxxxxxxxx>,
+ Mark Shuttleworth <mark@xxxxxxxxxxx>
+ X-Launchpad-Message-Rationale: Admin (ubuntu-team)
+ X-Launchpad-Notification-Type: membership-statuschange
Subject: daf deactivated by mark
<BLANKLINE>
The membership status of Dafydd Harries (daf) in the team Ubuntu Team
(ubuntu-team) was changed by Mark Shuttleworth (mark) from Approved to
Deactivated.
<http://launchpad.dev/~ubuntu-team>
+ -- =
+ <BLANKLINE>
+ You received this email because you are an admin of the Ubuntu Team team.
+ <BLANKLINE>
----------------------------------------
From: Ubuntu Team <noreply@xxxxxxxxxxxxx>
- To: daf@xxxxxxxxxxxxx
+ To: Dafydd Harries <daf@xxxxxxxxxxxxx>
+ X-Launchpad-Message-Rationale: Member (ubuntu-team)
+ X-Launchpad-Notification-Type: membership-statuschange
Subject: daf deactivated by mark
<BLANKLINE>
The status of your membership in the team Ubuntu Team (ubuntu-team) was
changed by Mark Shuttleworth (mark) from Approved to Deactivated.
<http://launchpad.dev/~ubuntu-team>
+ -- =
+ <BLANKLINE>
+ You received this email because you are the affected member.
+ <BLANKLINE>
----------------------------------------
Team admins can propose their teams using the join() method as well, but
@@ -235,10 +288,13 @@
>>> print_distinct_emails(include_reply_to=True)
From: Ubuntu Team <noreply@xxxxxxxxxxxxx>
- To: colin.watson@xxxxxxxxxxxxxxx, foo.bar@xxxxxxxxxxxxx,
- jeff.waugh@xxxxxxxxxxxxxxx, limi@xxxxxxxxx
+ To: Alexander Limi <limi@xxxxxxxxx>,
+ Colin Watson <colin.watson@xxxxxxxxxxxxxxx>,
+ Foo Bar <foo.bar@xxxxxxxxxxxxx>,
+ Jeff Waugh <jeff.waugh@xxxxxxxxxxxxxxx>
Reply-To: mark@xxxxxxxxxxx
X-Launchpad-Message-Rationale: Admin (ubuntu-team)
+ X-Launchpad-Notification-Type: pending-membership-approval
Subject: admins wants to join
<BLANKLINE>
Mark Shuttleworth (mark) wants to make Launchpad Administrators
@@ -251,14 +307,17 @@
-- =
<BLANKLINE>
You received this email because you are an admin of the Ubuntu Team team.
+ <BLANKLINE>
----------------------------------------
From: Ubuntu Team <noreply@xxxxxxxxxxxxx>
- To: mark@xxxxxxxxxxx
+ To: Mark Shuttleworth <mark@xxxxxxxxxxx>
Reply-To: mark@xxxxxxxxxxx
X-Launchpad-Message-Rationale: Owner (ubuntu-team)
+ X-Launchpad-Notification-Type: pending-membership-approval
Subject: admins wants to join
...
You received this email because you are the owner of the Ubuntu Team team.
+ <BLANKLINE>
----------------------------------------
@@ -281,22 +340,27 @@
>>> print_distinct_emails()
From: Ubuntu Team <noreply@xxxxxxxxxxxxx>
- To: marilize@xxxxxxx
+ To: Marilize Coetzee <marilize@xxxxxxx>
X-Launchpad-Message-Rationale: Member (ubuntu-team)
+ X-Launchpad-Notification-Type: new-member
Subject: You have been added to ubuntu-team
<BLANKLINE>
Celso Providelo (cprov) added you as a member of Ubuntu Team (ubuntu-
team).
- <http://launchpad.dev/~ubuntu-team>
+ <http://launchpad.dev/~ubuntu-team>
<BLANKLINE>
-- =
<BLANKLINE>
You received this email because you are the new member.
+ <BLANKLINE>
----------------------------------------
From: Ubuntu Team <noreply@xxxxxxxxxxxxx>
- To: colin.watson@xxxxxxxxxxxxxxx, foo.bar@xxxxxxxxxxxxx,
- jeff.waugh@xxxxxxxxxxxxxxx, limi@xxxxxxxxx
+ To: Alexander Limi <limi@xxxxxxxxx>,
+ Colin Watson <colin.watson@xxxxxxxxxxxxxxx>,
+ Foo Bar <foo.bar@xxxxxxxxxxxxx>,
+ Jeff Waugh <jeff.waugh@xxxxxxxxxxxxxxx>
X-Launchpad-Message-Rationale: Admin (ubuntu-team)
+ X-Launchpad-Notification-Type: new-member
Subject: marilize joined ubuntu-team
<BLANKLINE>
Marilize Coetzee (marilize) has been added as a member of Ubuntu Team
@@ -308,13 +372,16 @@
-- =
<BLANKLINE>
You received this email because you are an admin of the Ubuntu Team team.
+ <BLANKLINE>
----------------------------------------
From: Ubuntu Team <noreply@xxxxxxxxxxxxx>
- To: mark@xxxxxxxxxxx
+ To: Mark Shuttleworth <mark@xxxxxxxxxxx>
X-Launchpad-Message-Rationale: Owner (ubuntu-team)
+ X-Launchpad-Notification-Type: new-member
Subject: marilize joined ubuntu-team
...
You received this email because you are the owner of the Ubuntu Team team.
+ <BLANKLINE>
----------------------------------------
By default, if the newly added member is actually a team, we'll only
@@ -332,7 +399,9 @@
>>> print_distinct_emails()
From: Ubuntu Team <noreply@xxxxxxxxxxxxx>
- To: mark@xxxxxxxxxxx
+ To: Mark Shuttleworth <mark@xxxxxxxxxxx>
+ X-Launchpad-Message-Rationale: Admin (ubuntu-mirror-admins)
+ X-Launchpad-Notification-Type: membership-invitation
Subject: Invitation for ubuntu-mirror-admins to join
<BLANKLINE>
Celso Providelo (cprov) has invited Mirror Administrators (ubuntu-
@@ -346,6 +415,13 @@
<BLANKLINE>
Regards,
The Launchpad team
+ <BLANKLINE>
+ -- =
+ <BLANKLINE>
+ You received this email because you are an admin of the Mirror
+ Administrato=
+ rs team.
+ <BLANKLINE>
----------------------------------------
If one of the admins accept the invitation, then a notification is sent
@@ -362,18 +438,48 @@
>>> print_distinct_emails()
From: Ubuntu Team <noreply@xxxxxxxxxxxxx>
- To: colin.watson@xxxxxxxxxxxxxxx, foo.bar@xxxxxxxxxxxxx,
- jeff.waugh@xxxxxxxxxxxxxxx, karl@xxxxxxxxxxxxx, limi@xxxxxxxxx,
- mark@xxxxxxxxxxx
- Subject: Invitation to ubuntu-mirror-admins accepted by mark
- <BLANKLINE>
- Mark Shuttleworth (mark) has accepted the invitation to make Mirror
- Administrators (ubuntu-mirror-admins) a member of Ubuntu Team (ubuntu-
- team).
- <http://launchpad.dev/~ubuntu-team>
- <BLANKLINE>
- Mark Shuttleworth said:
- Of course I want to be part of ubuntu!
+ To: Alexander Limi <limi@xxxxxxxxx>,
+ Colin Watson <colin.watson@xxxxxxxxxxxxxxx>,
+ Foo Bar <foo.bar@xxxxxxxxxxxxx>,
+ Jeff Waugh <jeff.waugh@xxxxxxxxxxxxxxx>
+ X-Launchpad-Message-Rationale: Admin (ubuntu-team)
+ X-Launchpad-Notification-Type: membership-invitation-accepted
+ Subject: Invitation to ubuntu-mirror-admins accepted by mark
+ <BLANKLINE>
+ Mark Shuttleworth (mark) has accepted the invitation to make Mirror
+ Administrators (ubuntu-mirror-admins) a member of Ubuntu Team (ubuntu-
+ team).
+ <http://launchpad.dev/~ubuntu-team>
+ <BLANKLINE>
+ Mark Shuttleworth said:
+ Of course I want to be part of ubuntu!
+ <BLANKLINE>
+ -- =
+ <BLANKLINE>
+ You received this email because you are an admin of the Ubuntu Team team.
+ <BLANKLINE>
+ ----------------------------------------
+ From: Ubuntu Team <noreply@xxxxxxxxxxxxx>
+ To: Karl Tilbury <karl@xxxxxxxxxxxxx>,
+ Mark Shuttleworth <mark@xxxxxxxxxxx>
+ X-Launchpad-Message-Rationale: Member (ubuntu-team) @ubuntu-mirror-admins
+ X-Launchpad-Notification-Type: membership-invitation-accepted
+ Subject: Invitation to ubuntu-mirror-admins accepted by mark
+ <BLANKLINE>
+ Mark Shuttleworth (mark) has accepted the invitation to make Mirror
+ Administrators (ubuntu-mirror-admins) a member of Ubuntu Team (ubuntu-
+ team).
+ <http://launchpad.dev/~ubuntu-team>
+ <BLANKLINE>
+ Mark Shuttleworth said:
+ Of course I want to be part of ubuntu!
+ <BLANKLINE>
+ -- =
+ <BLANKLINE>
+ You received this email because your team Mirror Administrators is the
+ affe=
+ cted member.
+ <BLANKLINE>
----------------------------------------
Similarly, a notification is sent if the invitation is declined.
@@ -397,17 +503,47 @@
>>> print_distinct_emails()
From: Ubuntu Team <noreply@xxxxxxxxxxxxx>
- To: colin.watson@xxxxxxxxxxxxxxx, foo.bar@xxxxxxxxxxxxx,
- guilherme.salgado@xxxxxxxxxxxxx, jeff.waugh@xxxxxxxxxxxxxxx,
- limi@xxxxxxxxx, mark@xxxxxxxxxxx, test@xxxxxxxxxxxxx
- Subject: Invitation to landscape-developers declined by mark
- <BLANKLINE>
- Mark Shuttleworth (mark) has declined the invitation to make Landscape
- Developers (landscape-developers) a member of Ubuntu Team (ubuntu-team).
- <http://launchpad.dev/~ubuntu-team>
- <BLANKLINE>
- Mark Shuttleworth said:
- Landscape has nothing to do with ubuntu, unfortunately.
+ To: Alexander Limi <limi@xxxxxxxxx>,
+ Colin Watson <colin.watson@xxxxxxxxxxxxxxx>,
+ Foo Bar <foo.bar@xxxxxxxxxxxxx>,
+ Jeff Waugh <jeff.waugh@xxxxxxxxxxxxxxx>,
+ Mark Shuttleworth <mark@xxxxxxxxxxx>
+ X-Launchpad-Message-Rationale: Admin (ubuntu-team)
+ X-Launchpad-Notification-Type: membership-invitation-declined
+ Subject: Invitation to landscape-developers declined by mark
+ <BLANKLINE>
+ Mark Shuttleworth (mark) has declined the invitation to make Landscape
+ Developers (landscape-developers) a member of Ubuntu Team (ubuntu-team).
+ <http://launchpad.dev/~ubuntu-team>
+ <BLANKLINE>
+ Mark Shuttleworth said:
+ Landscape has nothing to do with ubuntu, unfortunately.
+ <BLANKLINE>
+ -- =
+ <BLANKLINE>
+ You received this email because you are an admin of the Ubuntu Team team.
+ <BLANKLINE>
+ ----------------------------------------
+ From: Ubuntu Team <noreply@xxxxxxxxxxxxx>
+ To: Guilherme Salgado <guilherme.salgado@xxxxxxxxxxxxx>,
+ Sample Person <test@xxxxxxxxxxxxx>
+ X-Launchpad-Message-Rationale: Member (ubuntu-team) @landscape-developers
+ X-Launchpad-Notification-Type: membership-invitation-declined
+ Subject: Invitation to landscape-developers declined by mark
+ <BLANKLINE>
+ Mark Shuttleworth (mark) has declined the invitation to make Landscape
+ Developers (landscape-developers) a member of Ubuntu Team (ubuntu-team).
+ <http://launchpad.dev/~ubuntu-team>
+ <BLANKLINE>
+ Mark Shuttleworth said:
+ Landscape has nothing to do with ubuntu, unfortunately.
+ <BLANKLINE>
+ -- =
+ <BLANKLINE>
+ You received this email because your team Landscape Developers is the
+ affec=
+ ted member.
+ <BLANKLINE>
----------------------------------------
It's also possible to forcibly add a team as a member of another one, by
@@ -423,16 +559,22 @@
>>> print_distinct_emails()
From: Ubuntu Team <noreply@xxxxxxxxxxxxx>
- To: foo.bar@xxxxxxxxxxxxx
- X-Launchpad-Message-Rationale: Indirect member (ubuntu-team)
+ To: Foo Bar <foo.bar@xxxxxxxxxxxxx>
+ X-Launchpad-Message-Rationale: Member (ubuntu-team) @launchpad
+ X-Launchpad-Notification-Type: new-member
Subject: launchpad joined ubuntu-team
...
- You received this email because launchpad is the new member.
+ You received this email because your team Launchpad Developers is the
+ new m=
+ ember.
+ <BLANKLINE>
----------------------------------------
From: Ubuntu Team <noreply@xxxxxxxxxxxxx>
- To: colin.watson@xxxxxxxxxxxxxxx, jeff.waugh@xxxxxxxxxxxxxxx,
- limi@xxxxxxxxx
+ To: Alexander Limi <limi@xxxxxxxxx>,
+ Colin Watson <colin.watson@xxxxxxxxxxxxxxx>,
+ Jeff Waugh <jeff.waugh@xxxxxxxxxxxxxxx>
X-Launchpad-Message-Rationale: Admin (ubuntu-team)
+ X-Launchpad-Notification-Type: new-member
Subject: launchpad joined ubuntu-team
<BLANKLINE>
Launchpad Developers (launchpad) has been added as a member of Ubuntu
@@ -444,13 +586,16 @@
-- =
<BLANKLINE>
You received this email because you are an admin of the Ubuntu Team team.
+ <BLANKLINE>
----------------------------------------
From: Ubuntu Team <noreply@xxxxxxxxxxxxx>
- To: mark@xxxxxxxxxxx
+ To: Mark Shuttleworth <mark@xxxxxxxxxxx>
X-Launchpad-Message-Rationale: Owner (ubuntu-team)
+ X-Launchpad-Notification-Type: new-member
Subject: launchpad joined ubuntu-team
...
You received this email because you are the owner of the Ubuntu Team team.
+ <BLANKLINE>
----------------------------------------
@@ -483,13 +628,16 @@
>>> transaction.commit()
>>> print_distinct_emails()
From: Ubuntu Team <noreply@xxxxxxxxxxxxx>
- To: beta-admin@xxxxxxxxxxxxx
+ To: Launchpad Beta Testers Owner <beta-admin@xxxxxxxxxxxxx>
+ X-Launchpad-Message-Rationale: Member (ubuntu-team)
+ @launchpad-beta-testers
+ X-Launchpad-Notification-Type: membership-expiration-warning
Subject: launchpad-beta-testers will expire soon from ubuntu-team
<BLANKLINE>
On ..., 9 days from now, the membership
- of Launchpad Beta Testers (launchpad-beta-testers) (which you are
- the owner of) in the Ubuntu Team (ubuntu-team) Launchpad team
- is due to expire.
+ of Launchpad Beta Testers (launchpad-beta-testers) (which you are the
+ owner=
+ of) in the Ubuntu Team (ubuntu-team) Launchpad team is due to expire.
<http://launchpad.dev/~ubuntu-team>
<BLANKLINE>
To prevent this membership from expiring, you should get in touch
@@ -505,6 +653,12 @@
<BLANKLINE>
Thanks for using Launchpad!
<BLANKLINE>
+ -- =
+ <BLANKLINE>
+ You received this email because your team Launchpad Beta Testers is the
+ aff=
+ ected member.
+ <BLANKLINE>
----------------------------------------
If the team's renewal policy is ONDEMAND, though, the member is invited
@@ -521,7 +675,9 @@
>>> transaction.commit()
>>> print_distinct_emails()
From: Ubuntu Team <noreply@xxxxxxxxxxxxx>
- To: colin.watson@xxxxxxxxxxxxxxx
+ To: Colin Watson <colin.watson@xxxxxxxxxxxxxxx>
+ X-Launchpad-Message-Rationale: Member (ubuntu-team)
+ X-Launchpad-Notification-Type: membership-expiration-warning
Subject: Your membership in ubuntu-team is about to expire
<BLANKLINE>
On ..., 9 days from now, your membership
@@ -537,19 +693,26 @@
<BLANKLINE>
Thanks for using Launchpad!
<BLANKLINE>
+ -- =
+ <BLANKLINE>
+ You received this email because you are the affected member.
+ <BLANKLINE>
----------------------------------------
>>> beta_testers_on_ubuntu_team.sendExpirationWarningEmail()
>>> transaction.commit()
>>> print_distinct_emails()
From: Ubuntu Team <noreply@xxxxxxxxxxxxx>
- To: beta-admin@xxxxxxxxxxxxx
+ To: Launchpad Beta Testers Owner <beta-admin@xxxxxxxxxxxxx>
+ X-Launchpad-Message-Rationale: Member (ubuntu-team)
+ @launchpad-beta-testers
+ X-Launchpad-Notification-Type: membership-expiration-warning
Subject: launchpad-beta-testers will expire soon from ubuntu-team
<BLANKLINE>
On ..., 9 days from now, the membership
- of Launchpad Beta Testers (launchpad-beta-testers) (which you are
- the owner of) in the Ubuntu Team (ubuntu-team) Launchpad team
- is due to expire.
+ of Launchpad Beta Testers (launchpad-beta-testers) (which you are the
+ owner=
+ of) in the Ubuntu Team (ubuntu-team) Launchpad team is due to expire.
<http://launchpad.dev/~ubuntu-team>
<BLANKLINE>
If you want, you can renew this membership at
@@ -560,6 +723,12 @@
<BLANKLINE>
Thanks for using Launchpad!
<BLANKLINE>
+ -- =
+ <BLANKLINE>
+ You received this email because your team Launchpad Beta Testers is the
+ aff=
+ ected member.
+ <BLANKLINE>
----------------------------------------
If the team's renewal policy is NONE but the member has the necessary
@@ -580,12 +749,15 @@
>>> transaction.commit()
>>> print_distinct_emails()
From: Landscape Developers <noreply@xxxxxxxxxxxxx>
- To: test@xxxxxxxxxxxxx
+ To: Sample Person <test@xxxxxxxxxxxxx>
+ X-Launchpad-Message-Rationale: Member (landscape-developers)
+ X-Launchpad-Notification-Type: membership-expiration-warning
Subject: Your membership in landscape-developers is about to expire
<BLANKLINE>
On ..., 9 days from now, your membership
- in the Landscape Developers (landscape-developers) Launchpad team
- is due to expire.
+ in the Landscape Developers (landscape-developers) Launchpad team is due
+ to=
+ expire.
<http://launchpad.dev/~landscape-developers>
<BLANKLINE>
To stay a member of this team you should extend your membership at
@@ -596,6 +768,10 @@
<BLANKLINE>
Thanks for using Launchpad!
<BLANKLINE>
+ -- =
+ <BLANKLINE>
+ You received this email because you are the affected member.
+ <BLANKLINE>
----------------------------------------
@@ -634,7 +810,9 @@
>>> print_distinct_emails()
From: Mirror Administrators <noreply@xxxxxxxxxxxxx>
- To: mark@xxxxxxxxxxx
+ To: Mark Shuttleworth <mark@xxxxxxxxxxx>
+ X-Launchpad-Message-Rationale: Admin (ubuntu-mirror-admins)
+ X-Launchpad-Notification-Type: membership-member-renewed
Subject: karl extended their membership
<BLANKLINE>
Karl Tilbury (karl) renewed their own membership in the Mirror
@@ -643,6 +821,13 @@
<BLANKLINE>
Regards,
The Launchpad team
+ <BLANKLINE>
+ -- =
+ <BLANKLINE>
+ You received this email because you are an admin of the Mirror
+ Administrato=
+ rs team.
+ <BLANKLINE>
----------------------------------------
@@ -678,22 +863,37 @@
>>> print_distinct_emails()
From: Ubuntu Team <noreply@xxxxxxxxxxxxx>
- To: colin.watson@xxxxxxxxxxxxxxx, foo.bar@xxxxxxxxxxxxx,
- jeff.waugh@xxxxxxxxxxxxxxx, limi@xxxxxxxxx, mark@xxxxxxxxxxx
+ To: Alexander Limi <limi@xxxxxxxxx>,
+ Colin Watson <colin.watson@xxxxxxxxxxxxxxx>,
+ Foo Bar <foo.bar@xxxxxxxxxxxxx>,
+ Jeff Waugh <jeff.waugh@xxxxxxxxxxxxxxx>,
+ Mark Shuttleworth <mark@xxxxxxxxxxx>
+ X-Launchpad-Message-Rationale: Admin (ubuntu-team)
+ X-Launchpad-Notification-Type: membership-statuschange
Subject: cprov made admin by mark
<BLANKLINE>
The membership status of Celso Providelo (cprov) in the team Ubuntu Team
(ubuntu-team) was changed by Mark Shuttleworth (mark) from Approved to
Administrator.
<http://launchpad.dev/~ubuntu-team>
+ <BLANKLINE>
+ -- =
+ You received this email because you are an admin of the Ubuntu Team team.
+ <BLANKLINE>
----------------------------------------
From: Ubuntu Team <noreply@xxxxxxxxxxxxx>
- To: celso.providelo@xxxxxxxxxxxxx
+ To: Celso Providelo <celso.providelo@xxxxxxxxxxxxx>
+ X-Launchpad-Message-Rationale: Member (ubuntu-team)
+ X-Launchpad-Notification-Type: membership-statuschange
Subject: cprov made admin by mark
<BLANKLINE>
The status of your membership in the team Ubuntu Team (ubuntu-team) was
changed by Mark Shuttleworth (mark) from Approved to Administrator.
<http://launchpad.dev/~ubuntu-team>
+ <BLANKLINE>
+ -- =
+ You received this email because you are the affected member.
+ <BLANKLINE>
----------------------------------------
If a team admin changes his own membership, the notification sent will
@@ -710,14 +910,23 @@
>>> print_distinct_emails()
From: Ubuntu Team <noreply@xxxxxxxxxxxxx>
- To: celso.providelo@xxxxxxxxxxxxx, colin.watson@xxxxxxxxxxxxxxx,
- foo.bar@xxxxxxxxxxxxx, limi@xxxxxxxxx, mark@xxxxxxxxxxx
+ To: Alexander Limi <limi@xxxxxxxxx>,
+ Celso Providelo <celso.providelo@xxxxxxxxxxxxx>,
+ Colin Watson <colin.watson@xxxxxxxxxxxxxxx>,
+ Foo Bar <foo.bar@xxxxxxxxxxxxx>,
+ Mark Shuttleworth <mark@xxxxxxxxxxx>
+ X-Launchpad-Message-Rationale: Admin (ubuntu-team)
+ X-Launchpad-Notification-Type: membership-statuschange
Subject: Membership change: jdub in ubuntu-team
<BLANKLINE>
The membership status of Jeff Waugh (jdub) in the team Ubuntu Team
(ubuntu-team) was changed by the user from Administrator to
Approved.
<http://launchpad.dev/~ubuntu-team>
+ <BLANKLINE>
+ -- =
+ You received this email because you are an admin of the Ubuntu Team team.
+ <BLANKLINE>
----------------------------------------
Deactivating the membership of a team also generates notifications for
@@ -736,15 +945,38 @@
>>> print_distinct_emails()
From: Ubuntu Team <noreply@xxxxxxxxxxxxx>
- To: celso.providelo@xxxxxxxxxxxxx, colin.watson@xxxxxxxxxxxxxxx,
- foo.bar@xxxxxxxxxxxxx, karl@xxxxxxxxxxxxx, limi@xxxxxxxxx,
- mark@xxxxxxxxxxx
- Subject: ubuntu-mirror-admins deactivated by mark
- <BLANKLINE>
- The membership status of Mirror Administrators (ubuntu-mirror-admins) in
- the team Ubuntu Team (ubuntu-team) was changed by Mark Shuttleworth
- (mark) from Approved to Deactivated.
- <http://launchpad.dev/~ubuntu-team>
+ To: Alexander Limi <limi@xxxxxxxxx>,
+ Celso Providelo <celso.providelo@xxxxxxxxxxxxx>,
+ Colin Watson <colin.watson@xxxxxxxxxxxxxxx>,
+ Foo Bar <foo.bar@xxxxxxxxxxxxx>
+ X-Launchpad-Message-Rationale: Admin (ubuntu-team)
+ X-Launchpad-Notification-Type: membership-statuschange
+ Subject: ubuntu-mirror-admins deactivated by mark
+ <BLANKLINE>
+ The membership status of Mirror Administrators (ubuntu-mirror-admins) in
+ the team Ubuntu Team (ubuntu-team) was changed by Mark Shuttleworth
+ (mark) from Approved to Deactivated.
+ <http://launchpad.dev/~ubuntu-team>
+ <BLANKLINE>
+ -- =
+ You received this email because you are an admin of the Ubuntu Team team.
+ ----------------------------------------
+ From: Ubuntu Team <noreply@xxxxxxxxxxxxx>
+ To: Karl Tilbury <karl@xxxxxxxxxxxxx>,
+ Mark Shuttleworth <mark@xxxxxxxxxxx>
+ X-Launchpad-Message-Rationale: Member (ubuntu-team) @ubuntu-mirror-admins
+ X-Launchpad-Notification-Type: membership-statuschange
+ Subject: ubuntu-mirror-admins deactivated by mark
+ <BLANKLINE>
+ The membership status of Mirror Administrators (ubuntu-mirror-admins) in
+ the team Ubuntu Team (ubuntu-team) was changed by Mark Shuttleworth
+ (mark) from Approved to Deactivated.
+ <http://launchpad.dev/~ubuntu-team>
+ <BLANKLINE>
+ -- =
+ You received this email because your team Mirror Administrators is the
+ affe=
+ cted member.
----------------------------------------
Deactivating memberships can also be done silently (no email
@@ -809,8 +1041,9 @@
>>> ignored = team_one.addMember(member, owner)
>>> print_distinct_emails()
From: Team One ...
- To: team-member...
+ To: Team-member <team-member...>
X-Launchpad-Message-Rationale: Member (team-one)
+ X-Launchpad-Notification-Type: new-member
Subject: You have been added to team-one
<BLANKLINE>
Team-owner (team-owner) added you as a member of Team One (team-one).
@@ -818,11 +1051,12 @@
<BLANKLINE>
If you would like to subscribe to the team list, use the link below
to update your Mailing List Subscription preferences.
- <http://launchpad.dev/people/+me/+editmailinglists>
+ <http://launchpad.dev/~/+editmailinglists>
<BLANKLINE>
-- =
<BLANKLINE>
You received this email because you are the new member.
+ <BLANKLINE>
----------------------------------------
When a team join a team with a mailing list, the new member notification
@@ -833,8 +1067,9 @@
>>> ignored = team_one.addMember(team_two, owner, force_team_add=True)
>>> print_distinct_emails()
From: Team One ...
- To: team-two...
- X-Launchpad-Message-Rationale: Indirect member (team-one)
+ To: Team Two <team-two...>
+ X-Launchpad-Message-Rationale: Member (team-one) @team-two
+ X-Launchpad-Notification-Type: new-member
Subject: team-two joined team-one
<BLANKLINE>
Team-owner (team-owner) added Team Two (team-two) (which you are a
@@ -843,11 +1078,12 @@
<BLANKLINE>
If you would like to subscribe to the team list, use the link below
to update your Mailing List Subscription preferences.
- <http://launchpad.dev/people/+me/+editmailinglists>
+ <http://launchpad.dev/~/+editmailinglists>
<BLANKLINE>
-- =
<BLANKLINE>
- You received this email because team-two is the new member.
+ You received this email because your team Team Two is the new member.
+ <BLANKLINE>
----------------------------------------
=== modified file 'lib/lp/registry/emailtemplates/membership-expiration-warning-bulk.txt'
--- lib/lp/registry/emailtemplates/membership-expiration-warning-bulk.txt 2011-03-09 18:18:02 +0000
+++ lib/lp/registry/emailtemplates/membership-expiration-warning-bulk.txt 2015-08-27 14:49:13 +0000
@@ -1,8 +1,7 @@
-Hello %(recipient_name)s,
+Hello %(recipient)s,
On %(expiration_date)s, %(approximate_duration)s from now, the membership
-of %(member_name)s (which you are
-the owner of) in the %(team_name)s Launchpad team
+of %(member)s (which you are the owner of) in the %(team)s Launchpad team
is due to expire.
<%(team_url)s>
=== modified file 'lib/lp/registry/emailtemplates/membership-expiration-warning-personal.txt'
--- lib/lp/registry/emailtemplates/membership-expiration-warning-personal.txt 2011-03-09 18:18:02 +0000
+++ lib/lp/registry/emailtemplates/membership-expiration-warning-personal.txt 2015-08-27 14:49:13 +0000
@@ -1,8 +1,7 @@
-Hello %(recipient_name)s,
+Hello %(recipient)s,
On %(expiration_date)s, %(approximate_duration)s from now, your membership
-in the %(team_name)s Launchpad team
-is due to expire.
+in the %(team)s Launchpad team is due to expire.
<%(team_url)s>
%(how_to_renew)s
=== modified file 'lib/lp/registry/emailtemplates/membership-expired-bulk.txt'
--- lib/lp/registry/emailtemplates/membership-expired-bulk.txt 2011-03-09 18:18:02 +0000
+++ lib/lp/registry/emailtemplates/membership-expired-bulk.txt 2015-08-27 14:49:13 +0000
@@ -1,6 +1,6 @@
-Hello %(recipient_name)s,
+Hello %(recipient)s,
-The membership of %(member_name)s in the %(team_name)s team has expired.
+The membership of %(member)s in the %(team)s team has expired.
<%(team_url)s>
Regards,
=== modified file 'lib/lp/registry/emailtemplates/membership-expired-personal.txt'
--- lib/lp/registry/emailtemplates/membership-expired-personal.txt 2011-03-09 18:18:02 +0000
+++ lib/lp/registry/emailtemplates/membership-expired-personal.txt 2015-08-27 14:49:13 +0000
@@ -1,6 +1,6 @@
-Hello %(recipient_name)s,
+Hello %(recipient)s,
-Your membership in the %(team_name)s team has expired.
+Your membership in the %(team)s team has expired.
<%(team_url)s>
Regards,
=== modified file 'lib/lp/registry/emailtemplates/membership-invitation-accepted-bulk.txt'
--- lib/lp/registry/emailtemplates/membership-invitation-accepted-bulk.txt 2011-03-09 18:18:02 +0000
+++ lib/lp/registry/emailtemplates/membership-invitation-accepted-bulk.txt 2015-08-27 14:49:13 +0000
@@ -1,5 +1,5 @@
-Hello %(recipient_name)s,
+Hello %(recipient)s,
-%(reviewer_name)s has accepted the invitation to make %(member_name)s a member of %(team_name)s.
+%(reviewer)s has accepted the invitation to make %(member)s a member of %(team)s.
<%(team_url)s>
%(comment)s
=== modified file 'lib/lp/registry/emailtemplates/membership-invitation-declined-bulk.txt'
--- lib/lp/registry/emailtemplates/membership-invitation-declined-bulk.txt 2011-03-09 18:18:02 +0000
+++ lib/lp/registry/emailtemplates/membership-invitation-declined-bulk.txt 2015-08-27 14:49:13 +0000
@@ -1,5 +1,5 @@
-Hello %(recipient_name)s,
+Hello %(recipient)s,
-%(reviewer_name)s has declined the invitation to make %(member_name)s a member of %(team_name)s.
+%(reviewer)s has declined the invitation to make %(member)s a member of %(team)s.
<%(team_url)s>
%(comment)s
=== modified file 'lib/lp/registry/emailtemplates/membership-invitation.txt'
--- lib/lp/registry/emailtemplates/membership-invitation.txt 2011-03-09 17:51:28 +0000
+++ lib/lp/registry/emailtemplates/membership-invitation.txt 2015-08-27 14:49:13 +0000
@@ -1,4 +1,4 @@
-Hello %(recipient_name)s,
+Hello %(recipient)s,
%(reviewer)s has invited %(member)s (which you are an administrator of) to join %(team)s.
<%(team_url)s>
=== modified file 'lib/lp/registry/emailtemplates/membership-member-renewed.txt'
--- lib/lp/registry/emailtemplates/membership-member-renewed.txt 2011-03-09 18:18:02 +0000
+++ lib/lp/registry/emailtemplates/membership-member-renewed.txt 2015-08-27 14:49:13 +0000
@@ -1,6 +1,6 @@
-Hello %(recipient_name)s,
+Hello %(recipient)s,
-%(member_name)s renewed their own membership in the %(team_name)s team until %(dateexpires)s.
+%(member)s renewed their own membership in the %(team)s team until %(dateexpires)s.
<%(team_url)s>
Regards,
=== modified file 'lib/lp/registry/emailtemplates/membership-statuschange-bulk.txt'
--- lib/lp/registry/emailtemplates/membership-statuschange-bulk.txt 2011-03-09 18:18:02 +0000
+++ lib/lp/registry/emailtemplates/membership-statuschange-bulk.txt 2015-08-27 14:49:13 +0000
@@ -1,5 +1,5 @@
-Hello %(recipient_name)s,
+Hello %(recipient)s,
-The membership status of %(member_name)s in the team %(team_name)s was changed by %(reviewer_name)s from %(old_status)s to %(new_status)s.
+The membership status of %(member)s in the team %(team)s was changed by %(reviewer)s from %(old_status)s to %(new_status)s.
<%(team_url)s>
%(comment)s
=== modified file 'lib/lp/registry/emailtemplates/membership-statuschange-personal.txt'
--- lib/lp/registry/emailtemplates/membership-statuschange-personal.txt 2011-03-09 18:18:02 +0000
+++ lib/lp/registry/emailtemplates/membership-statuschange-personal.txt 2015-08-27 14:49:13 +0000
@@ -1,5 +1,5 @@
-Hello %(recipient_name)s,
+Hello %(recipient)s,
-The status of your membership in the team %(team_name)s was changed by %(reviewer_name)s from %(old_status)s to %(new_status)s.
+The status of your membership in the team %(team)s was changed by %(reviewer)s from %(old_status)s to %(new_status)s.
<%(team_url)s>
%(comment)s
=== modified file 'lib/lp/registry/emailtemplates/new-member-notification-for-admins.txt'
--- lib/lp/registry/emailtemplates/new-member-notification-for-admins.txt 2011-03-09 17:51:28 +0000
+++ lib/lp/registry/emailtemplates/new-member-notification-for-admins.txt 2015-08-27 14:49:13 +0000
@@ -1,5 +1,5 @@
-Hello %(recipient_name)s,
-
-%(person_name)s has been added as a member of %(team_name)s by %(reviewer_name)s. Follow the link below for more details.
-
- %(url)s
+Hello %(recipient)s,
+
+%(member)s has been added as a member of %(team)s by %(reviewer)s. Follow the link below for more details.
+
+ %(membership_url)s
=== modified file 'lib/lp/registry/emailtemplates/new-member-notification-for-teams.txt'
--- lib/lp/registry/emailtemplates/new-member-notification-for-teams.txt 2011-03-09 17:51:28 +0000
+++ lib/lp/registry/emailtemplates/new-member-notification-for-teams.txt 2015-08-27 14:49:13 +0000
@@ -1,4 +1,4 @@
-Hello %(recipient_name)s,
+Hello %(recipient)s,
%(reviewer)s added %(member)s (which you are a member of) as a member of %(team)s.
<%(team_url)s>
=== modified file 'lib/lp/registry/emailtemplates/new-member-notification.txt'
--- lib/lp/registry/emailtemplates/new-member-notification.txt 2011-03-09 17:51:28 +0000
+++ lib/lp/registry/emailtemplates/new-member-notification.txt 2015-08-27 14:49:13 +0000
@@ -1,4 +1,4 @@
-Hello %(recipient_name)s,
+Hello %(recipient)s,
%(reviewer)s added you as a member of %(team)s.
<%(team_url)s>
=== modified file 'lib/lp/registry/emailtemplates/pending-membership-approval-for-third-party.txt'
--- lib/lp/registry/emailtemplates/pending-membership-approval-for-third-party.txt 2011-03-09 17:51:28 +0000
+++ lib/lp/registry/emailtemplates/pending-membership-approval-for-third-party.txt 2015-08-27 14:49:13 +0000
@@ -1,5 +1,5 @@
-Hello %(recipient_name)s,
-
-%(reviewer_name)s wants to make %(person_name)s a member of %(team_name)s, but this is a moderated team, so that membership has to be approved. You can approve, decline or leave it as proposed by following the link below.
-
- %(url)s
+Hello %(recipient)s,
+
+%(reviewer)s wants to make %(member)s a member of %(team)s, but this is a moderated team, so that membership has to be approved. You can approve, decline or leave it as proposed by following the link below.
+
+ %(membership_url)s
=== modified file 'lib/lp/registry/emailtemplates/pending-membership-approval.txt'
--- lib/lp/registry/emailtemplates/pending-membership-approval.txt 2011-03-09 17:51:28 +0000
+++ lib/lp/registry/emailtemplates/pending-membership-approval.txt 2015-08-27 14:49:13 +0000
@@ -1,5 +1,5 @@
-Hello %(recipient_name)s,
-
-%(person_name)s wants to be a member of %(team_name)s, but this is a moderated team, so that membership has to be approved. You can approve, decline or leave it as proposed by following the link below.
-
- %(url)s
+Hello %(recipient)s,
+
+%(member)s wants to be a member of %(team)s, but this is a moderated team, so that membership has to be approved. You can approve, decline or leave it as proposed by following the link below.
+
+ %(membership_url)s
=== modified file 'lib/lp/registry/mail/notification.py'
--- lib/lp/registry/mail/notification.py 2015-03-13 19:05:50 +0000
+++ lib/lp/registry/mail/notification.py 2015-08-27 14:49:13 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Event handlers that send email notifications."""
@@ -17,19 +17,12 @@
getUtility,
)
-from lp.registry.enums import TeamMembershipPolicy
from lp.registry.interfaces.mailinglist import IHeldMessageDetails
from lp.registry.interfaces.person import IPersonSet
-from lp.registry.interfaces.teammembership import (
- ITeamMembershipSet,
- TeamMembershipStatus,
- )
+from lp.registry.mail.team import TeamMailer
from lp.services.config import config
from lp.services.database.sqlbase import block_implicit_flushes
-from lp.services.mail.helpers import (
- get_contact_email_addresses,
- get_email_template,
- )
+from lp.services.mail.helpers import get_email_template
from lp.services.mail.mailwrapper import MailWrapper
from lp.services.mail.notificationrecipientset import NotificationRecipientSet
from lp.services.mail.sendmail import (
@@ -41,9 +34,7 @@
IDirectEmailAuthorization,
QuotaReachedError,
)
-from lp.services.webapp.interfaces import ILaunchpadRoot
from lp.services.webapp.publisher import canonical_url
-from lp.services.webapp.url import urlappend
# Silence lint warnings.
NotificationRecipientSet
@@ -57,48 +48,8 @@
The notification will include a link to a page in which any team admin can
accept the invitation.
-
- XXX: Guilherme Salgado 2007-05-08:
- At some point we may want to extend this functionality to allow invites
- to be sent to users as well, but for now we only use it for teams.
"""
- member = event.member
- assert member.is_team
- team = event.team
- membership = getUtility(ITeamMembershipSet).getByPersonAndTeam(
- member, team)
- assert membership is not None
-
- reviewer = membership.proposed_by
- admin_addrs = member.getTeamAdminsEmailAddresses()
- from_addr = format_address(
- team.displayname, config.canonical.noreply_from_address)
- subject = 'Invitation for %s to join' % member.name
- templatename = 'membership-invitation.txt'
- template = get_email_template(templatename, app='registry')
- replacements = {
- 'reviewer': '%s (%s)' % (reviewer.displayname, reviewer.name),
- 'member': '%s (%s)' % (member.displayname, member.name),
- 'team': '%s (%s)' % (team.displayname, team.name),
- 'team_url': canonical_url(team),
- 'membership_invitations_url':
- "%s/+invitation/%s" % (canonical_url(member), team.name)}
- for address in admin_addrs:
- recipient = getUtility(IPersonSet).getByEmail(address)
- replacements['recipient_name'] = recipient.displayname
- msg = MailWrapper().format(template % replacements, force_wrap=True)
- simple_sendmail(from_addr, address, subject, msg)
-
-
-def send_team_email(from_addr, address, subject, template, replacements,
- rationale, headers=None):
- """Send a team message with a rationale."""
- if headers is None:
- headers = {}
- body = MailWrapper().format(template % replacements, force_wrap=True)
- footer = "-- \n%s" % rationale
- message = '%s\n\n%s' % (body, footer)
- simple_sendmail(from_addr, address, subject, message, headers)
+ TeamMailer.forInvitationToJoinTeam(event.member, event.team).sendAll()
@block_implicit_flushes
@@ -109,130 +60,7 @@
is pending approval. Otherwise it'll say that the person has joined the
team and who added that person to the team.
"""
- person = event.person
- team = event.team
- membership = getUtility(ITeamMembershipSet).getByPersonAndTeam(
- person, team)
- assert membership is not None
- approved, admin, proposed = [
- TeamMembershipStatus.APPROVED, TeamMembershipStatus.ADMIN,
- TeamMembershipStatus.PROPOSED]
- admin_addrs = team.getTeamAdminsEmailAddresses()
- from_addr = format_address(
- team.displayname, config.canonical.noreply_from_address)
-
- reviewer = membership.proposed_by
- if reviewer != person and membership.status in [approved, admin]:
- reviewer = membership.reviewed_by
- # Somebody added this person as a member, we better send a
- # notification to the person too.
- member_addrs = get_contact_email_addresses(person)
-
- headers = {}
- if person.is_team:
- templatename = 'new-member-notification-for-teams.txt'
- subject = '%s joined %s' % (person.name, team.name)
- header_rational = "Indirect member (%s)" % team.name
- footer_rationale = (
- "You received this email because "
- "%s is the new member." % person.name)
- else:
- templatename = 'new-member-notification.txt'
- subject = 'You have been added to %s' % team.name
- header_rational = "Member (%s)" % team.name
- footer_rationale = (
- "You received this email because you are the new member.")
-
- if team.mailing_list is not None:
- template = get_email_template(
- 'team-list-subscribe-block.txt', app='registry')
- editemails_url = urlappend(
- canonical_url(getUtility(ILaunchpadRoot)),
- 'people/+me/+editmailinglists')
- list_instructions = template % dict(editemails_url=editemails_url)
- else:
- list_instructions = ''
-
- template = get_email_template(templatename, app='registry')
- replacements = {
- 'reviewer': '%s (%s)' % (reviewer.displayname, reviewer.name),
- 'team_url': canonical_url(team),
- 'member': '%s (%s)' % (person.displayname, person.name),
- 'team': '%s (%s)' % (team.displayname, team.name),
- 'list_instructions': list_instructions,
- }
- headers = {'X-Launchpad-Message-Rationale': header_rational}
- for address in member_addrs:
- recipient = getUtility(IPersonSet).getByEmail(address)
- replacements['recipient_name'] = recipient.displayname
- send_team_email(
- from_addr, address, subject, template, replacements,
- footer_rationale, headers)
-
- # The member's email address may be in admin_addrs too; let's remove
- # it so the member don't get two notifications.
- admin_addrs = set(admin_addrs).difference(set(member_addrs))
-
- # Yes, we can have teams with no members; not even admins.
- if not admin_addrs:
- return
-
- # Open teams do not notify admins about new members.
- if team.membership_policy == TeamMembershipPolicy.OPEN:
- return
-
- replacements = {
- 'person_name': "%s (%s)" % (person.displayname, person.name),
- 'team_name': "%s (%s)" % (team.displayname, team.name),
- 'reviewer_name': "%s (%s)" % (reviewer.displayname, reviewer.name),
- 'url': canonical_url(membership)}
-
- headers = {}
- if membership.status in [approved, admin]:
- template = get_email_template(
- 'new-member-notification-for-admins.txt', app='registry')
- subject = '%s joined %s' % (person.name, team.name)
- elif membership.status == proposed:
- # In the UI, a user can only propose himself or a team he
- # admins. Some users of the REST API have a workflow, where
- # they propose users that are designated as mentees (Bug 498181).
- if reviewer != person:
- headers = {"Reply-To": reviewer.preferredemail.email}
- template = get_email_template(
- 'pending-membership-approval-for-third-party.txt',
- app='registry')
- else:
- headers = {"Reply-To": person.preferredemail.email}
- template = get_email_template(
- 'pending-membership-approval.txt', app='registry')
- subject = "%s wants to join" % person.name
- else:
- raise AssertionError(
- "Unexpected membership status: %s" % membership.status)
-
- for address in admin_addrs:
- recipient = getUtility(IPersonSet).getByEmail(address)
- replacements['recipient_name'] = recipient.displayname
- if recipient.is_team:
- header_rationale = 'Admin (%s via %s)' % (
- team.name, recipient.name)
- footer_rationale = (
- "you are an admin of the %s team\n"
- "via the %s team." % (
- team.displayname, recipient.displayname))
- elif recipient == team.teamowner:
- header_rationale = 'Owner (%s)' % team.name
- footer_rationale = (
- "you are the owner of the %s team." % team.displayname)
- else:
- header_rationale = 'Admin (%s)' % team.name
- footer_rationale = (
- "you are an admin of the %s team." % team.displayname)
- footer = 'You received this email because %s' % footer_rationale
- headers['X-Launchpad-Message-Rationale'] = header_rationale
- send_team_email(
- from_addr, address, subject, template, replacements,
- footer, headers)
+ TeamMailer.forTeamJoin(event.person, event.team).sendAll()
def notify_mailinglist_activated(mailinglist, event):
=== added file 'lib/lp/registry/mail/team.py'
--- lib/lp/registry/mail/team.py 1970-01-01 00:00:00 +0000
+++ lib/lp/registry/mail/team.py 2015-08-27 14:49:13 +0000
@@ -0,0 +1,420 @@
+# Copyright 2015 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+__metaclass__ = type
+__all__ = [
+ 'TeamMailer',
+ ]
+
+from collections import OrderedDict
+from datetime import datetime
+
+import pytz
+from zope.component import getUtility
+
+from lp.app.browser.tales import DurationFormatterAPI
+from lp.registry.enums import (
+ TeamMembershipPolicy,
+ TeamMembershipRenewalPolicy,
+ )
+from lp.registry.interfaces.teammembership import (
+ ITeamMembershipSet,
+ TeamMembershipStatus,
+ )
+from lp.registry.model.person import get_recipients
+from lp.services.config import config
+from lp.services.mail.basemailer import (
+ BaseMailer,
+ RecipientReason,
+ )
+from lp.services.mail.helpers import get_email_template
+from lp.services.mail.sendmail import format_address
+from lp.services.webapp.interfaces import ILaunchpadRoot
+from lp.services.webapp.publisher import canonical_url
+from lp.services.webapp.url import urlappend
+
+
+class TeamRecipientReason(RecipientReason):
+
+ @classmethod
+ def forInvitation(cls, admin, team, recipient, proposed_member, **kwargs):
+ header = cls.makeRationale(
+ "Invitation (%s)" % team.name, proposed_member)
+ reason = (
+ "You received this email because %%(lc_entity_is)s an admin of "
+ "the %s team." % proposed_member.displayname)
+ return cls(admin, recipient, header, reason, **kwargs)
+
+ @classmethod
+ def forMember(cls, member, team, recipient, **kwargs):
+ header = cls.makeRationale("Member (%s)" % team.name, member)
+ reason = (
+ "You received this email because %(lc_entity_is)s the affected "
+ "member.")
+ return cls(member, recipient, header, reason, **kwargs)
+
+ @classmethod
+ def forNewMember(cls, new_member, team, recipient, **kwargs):
+ # From a filtering point of view, this is identical to forMember;
+ # filtering on X-Launchpad-Notification-Type is more useful for
+ # determining the type of notification sent to a particular member.
+ # It's worth having a footer that makes a little more sense, though.
+ header = cls.makeRationale("Member (%s)" % team.name, new_member)
+ reason = (
+ "You received this email because %(lc_entity_is)s the new member.")
+ return cls(new_member, recipient, header, reason, **kwargs)
+
+ @classmethod
+ def forAdmin(cls, admin, team, recipient, **kwargs):
+ header = cls.makeRationale("Admin (%s)" % team.name, admin)
+ reason = (
+ "You received this email because %%(lc_entity_is)s an admin of "
+ "the %s team." % team.displayname)
+ return cls(admin, recipient, header, reason, **kwargs)
+
+ @classmethod
+ def forOwner(cls, owner, team, recipient, **kwargs):
+ header = cls.makeRationale("Owner (%s)" % team.name, owner)
+ reason = (
+ "You received this email because %%(lc_entity_is)s the owner "
+ "of the %s team." % team.displayname)
+ return cls(owner, recipient, header, reason, **kwargs)
+
+ def __init__(self, subscriber, recipient, mail_header, reason_template,
+ subject=None, template_name=None, reply_to=None,
+ recipient_class=None):
+ super(TeamRecipientReason, self).__init__(
+ subscriber, recipient, mail_header, reason_template)
+ self.subject = subject
+ self.template_name = template_name
+ self.reply_to = reply_to
+ self.recipient_class = recipient_class
+
+
+class TeamMailer(BaseMailer):
+
+ app = 'registry'
+
+ @classmethod
+ def forInvitationToJoinTeam(cls, member, team):
+ """Create a mailer for notifying about team joining invitations.
+
+ XXX: Guilherme Salgado 2007-05-08:
+ At some point we may want to extend this functionality to allow
+ invites to be sent to users as well, but for now we only use it for
+ teams.
+ """
+ assert member.is_team
+ membership = getUtility(ITeamMembershipSet).getByPersonAndTeam(
+ member, team)
+ assert membership is not None
+ recipients = OrderedDict()
+ for admin in member.adminmembers:
+ for recipient in get_recipients(admin):
+ recipients[recipient] = TeamRecipientReason.forAdmin(
+ admin, member, recipient)
+ from_addr = format_address(
+ team.displayname, config.canonical.noreply_from_address)
+ subject = "Invitation for %s to join" % member.name
+ return cls(
+ subject, "membership-invitation.txt", recipients, from_addr,
+ "membership-invitation", member, team, membership.proposed_by,
+ membership=membership)
+
+ @classmethod
+ def forTeamJoin(cls, member, team):
+ """Create a mailer for notifying about a new member joining a team."""
+ membership = getUtility(ITeamMembershipSet).getByPersonAndTeam(
+ member, team)
+ assert membership is not None
+ subject = None
+ template_name = None
+ notification_type = "new-member"
+ recipients = OrderedDict()
+ reviewer = membership.proposed_by
+ if reviewer != member and membership.status in [
+ TeamMembershipStatus.APPROVED, TeamMembershipStatus.ADMIN]:
+ reviewer = membership.reviewed_by
+ # Somebody added this person as a member, we better send a
+ # notification to the person too.
+ if member.is_team:
+ template_name = "new-member-notification-for-teams.txt"
+ subject = "%s joined %s" % (member.name, team.name)
+ else:
+ template_name = "new-member-notification.txt"
+ subject = "You have been added to %s" % team.name
+ for recipient in get_recipients(member):
+ recipients[recipient] = TeamRecipientReason.forNewMember(
+ member, team, recipient, subject=subject,
+ template_name=template_name)
+ # Open teams do not notify admins about new members.
+ if team.membership_policy != TeamMembershipPolicy.OPEN:
+ reply_to = None
+ if membership.status in [
+ TeamMembershipStatus.APPROVED, TeamMembershipStatus.ADMIN]:
+ template_name = "new-member-notification-for-admins.txt"
+ subject = "%s joined %s" % (member.name, team.name)
+ elif membership.status == TeamMembershipStatus.PROPOSED:
+ # In the UI, a user can only propose themselves or a team
+ # they admin. Some users of the REST API have a workflow
+ # where they propose users that are designated as undergoing
+ # mentorship (Bug 498181).
+ if reviewer != member:
+ reply_to = reviewer.preferredemail.email
+ template_name = (
+ "pending-membership-approval-for-third-party.txt")
+ else:
+ reply_to = member.preferredemail.email
+ template_name = "pending-membership-approval.txt"
+ notification_type = "pending-membership-approval"
+ subject = "%s wants to join" % member.name
+ else:
+ raise AssertionError(
+ "Unexpected membership status: %s" % membership.status)
+ for admin in team.adminmembers:
+ for recipient in get_recipients(admin):
+ # The new member may also be a team admin; don't send
+ # two notifications in that case.
+ if recipient not in recipients:
+ if recipient == team.teamowner:
+ reason_factory = TeamRecipientReason.forOwner
+ else:
+ reason_factory = TeamRecipientReason.forAdmin
+ recipients[recipient] = reason_factory(
+ admin, team, recipient, subject=subject,
+ template_name=template_name, reply_to=reply_to)
+ from_addr = format_address(
+ team.displayname, config.canonical.noreply_from_address)
+ return cls(
+ subject, template_name, recipients, from_addr, notification_type,
+ member, team, membership.proposed_by, membership=membership)
+
+ @classmethod
+ def forMembershipStatusChange(cls, member, team, reviewer,
+ old_status, new_status, last_change_comment):
+ """Create a mailer for a membership status change."""
+ notification_type = 'membership-statuschange'
+ subject = (
+ 'Membership change: %(member)s in %(team)s' %
+ {'member': member.name, 'team': team.name})
+ if new_status == TeamMembershipStatus.EXPIRED:
+ notification_type = 'membership-expired'
+ subject = '%s expired from team' % member.name
+ elif (new_status == TeamMembershipStatus.APPROVED and
+ old_status != TeamMembershipStatus.ADMIN):
+ if old_status == TeamMembershipStatus.INVITED:
+ notification_type = 'membership-invitation-accepted'
+ subject = (
+ 'Invitation to %s accepted by %s' %
+ (member.name, reviewer.name))
+ elif old_status == TeamMembershipStatus.PROPOSED:
+ subject = '%s approved by %s' % (member.name, reviewer.name)
+ else:
+ subject = '%s added by %s' % (member.name, reviewer.name)
+ elif new_status == TeamMembershipStatus.INVITATION_DECLINED:
+ notification_type = 'membership-invitation-declined'
+ subject = (
+ 'Invitation to %s declined by %s' %
+ (member.name, reviewer.name))
+ elif new_status == TeamMembershipStatus.DEACTIVATED:
+ subject = '%s deactivated by %s' % (member.name, reviewer.name)
+ elif new_status == TeamMembershipStatus.ADMIN:
+ subject = '%s made admin by %s' % (member.name, reviewer.name)
+ elif new_status == TeamMembershipStatus.DECLINED:
+ subject = '%s declined by %s' % (member.name, reviewer.name)
+ else:
+ # Use the default template and subject.
+ pass
+ template_name = notification_type + "-%(recipient_class)s.txt"
+
+ if last_change_comment:
+ comment = "\n%s said:\n %s\n" % (
+ reviewer.displayname, last_change_comment.strip())
+ else:
+ comment = ""
+
+ recipients = OrderedDict()
+ if reviewer != member:
+ for recipient in get_recipients(member):
+ if member.is_team:
+ recipient_class = "bulk"
+ else:
+ recipient_class = "personal"
+ recipients[recipient] = TeamRecipientReason.forMember(
+ member, team, recipient, recipient_class=recipient_class)
+ # Don't send admin notifications for open teams: they're
+ # unrestricted, so notifications on join/leave do not help the
+ # admins.
+ if team.membership_policy != TeamMembershipPolicy.OPEN:
+ for admin in team.adminmembers:
+ for recipient in get_recipients(admin):
+ # The new member may also be a team admin; don't send
+ # two notifications in that case.
+ if recipient not in recipients:
+ recipients[recipient] = TeamRecipientReason.forAdmin(
+ admin, team, recipient, recipient_class="bulk")
+
+ extra_params = {
+ "old_status": old_status,
+ "new_status": new_status,
+ "comment": comment,
+ }
+ from_addr = format_address(
+ team.displayname, config.canonical.noreply_from_address)
+ return cls(
+ subject, template_name, recipients, from_addr, notification_type,
+ member, team, reviewer, extra_params=extra_params)
+
+ @classmethod
+ def forExpiringMembership(cls, member, team, membership, dateexpires):
+ """Create a mailer for warning about expiring membership."""
+ if member.is_team:
+ target = member.teamowner
+ template_name = "membership-expiration-warning-bulk.txt"
+ subject = "%s will expire soon from %s" % (member.name, team.name)
+ else:
+ target = member
+ template_name = "membership-expiration-warning-personal.txt"
+ subject = "Your membership in %s is about to expire" % team.name
+
+ if team.renewal_policy == TeamMembershipRenewalPolicy.ONDEMAND:
+ how_to_renew = (
+ "If you want, you can renew this membership at\n"
+ "<%s/+expiringmembership/%s>" %
+ (canonical_url(member), team.name))
+ elif not membership.canChangeExpirationDate(target):
+ admins_names = []
+ admins = team.getDirectAdministrators()
+ assert admins.count() >= 1
+ if admins.count() == 1:
+ admin = admins[0]
+ how_to_renew = (
+ "To prevent this membership from expiring, you should "
+ "contact the\nteam's administrator, %s.\n<%s>"
+ % (admin.unique_displayname, canonical_url(admin)))
+ else:
+ for admin in admins:
+ admins_names.append(
+ "%s <%s>" % (admin.unique_displayname,
+ canonical_url(admin)))
+
+ how_to_renew = (
+ "To prevent this membership from expiring, you should "
+ "get in touch\nwith one of the team's administrators:\n")
+ how_to_renew += "\n".join(admins_names)
+ else:
+ how_to_renew = (
+ "To stay a member of this team you should extend your "
+ "membership at\n<%s/+member/%s>"
+ % (canonical_url(team), member.name))
+
+ recipients = OrderedDict()
+ for recipient in get_recipients(target):
+ recipients[recipient] = TeamRecipientReason.forMember(
+ member, team, recipient)
+
+ formatter = DurationFormatterAPI(dateexpires - datetime.now(pytz.UTC))
+ extra_params = {
+ "how_to_renew": how_to_renew,
+ "expiration_date": dateexpires.strftime("%Y-%m-%d"),
+ "approximate_duration": formatter.approximateduration(),
+ }
+
+ from_addr = format_address(
+ team.displayname, config.canonical.noreply_from_address)
+ return cls(
+ subject, template_name, recipients, from_addr,
+ "membership-expiration-warning", member, team,
+ membership.proposed_by, membership=membership,
+ extra_params=extra_params, wrap=False, force_wrap=False)
+
+ @classmethod
+ def forSelfRenewal(cls, member, team, dateexpires):
+ """Create a mailer for notifying about a self-renewal."""
+ assert team.renewal_policy == TeamMembershipRenewalPolicy.ONDEMAND
+ template_name = "membership-member-renewed.txt"
+ subject = "%s extended their membership" % member.name
+ recipients = OrderedDict()
+ for admin in team.adminmembers:
+ for recipient in get_recipients(admin):
+ recipients[recipient] = TeamRecipientReason.forAdmin(
+ admin, team, recipient)
+ extra_params = {"dateexpires": dateexpires.strftime("%Y-%m-%d")}
+ from_addr = format_address(
+ team.displayname, config.canonical.noreply_from_address)
+ return cls(
+ subject, template_name, recipients, from_addr,
+ "membership-member-renewed", member, team, None,
+ extra_params=extra_params)
+
+ def __init__(self, subject, template_name, recipients, from_address,
+ notification_type, member, team, reviewer, membership=None,
+ extra_params={}, wrap=True, force_wrap=True):
+ """See `BaseMailer`."""
+ super(TeamMailer, self).__init__(
+ subject, template_name, recipients, from_address,
+ notification_type=notification_type, wrap=wrap,
+ force_wrap=force_wrap)
+ self.member = member
+ self.team = team
+ self.reviewer = reviewer
+ self.membership = membership
+ self.extra_params = extra_params
+
+ def _getSubject(self, email, recipient):
+ """See `BaseMailer`."""
+ reason, _ = self._recipients.getReason(email)
+ if reason.subject is not None:
+ subject_template = reason.subject
+ else:
+ subject_template = self._subject_template
+ return subject_template % self._getTemplateParams(email, recipient)
+
+ def _getReplyToAddress(self, email, recipient):
+ """See `BaseMailer`."""
+ reason, _ = self._recipients.getReason(email)
+ return reason.reply_to
+
+ def _getTemplateName(self, email, recipient):
+ """See `BaseMailer`."""
+ reason, _ = self._recipients.getReason(email)
+ if reason.template_name is not None:
+ template_name = reason.template_name
+ else:
+ template_name = self._template_name
+ return template_name % self._getTemplateParams(email, recipient)
+
+ def _getTemplateParams(self, email, recipient):
+ """See `BaseMailer`."""
+ params = super(TeamMailer, self)._getTemplateParams(email, recipient)
+ params["recipient"] = recipient.displayname
+ reason, _ = self._recipients.getReason(email)
+ if reason.recipient_class is not None:
+ params["recipient_class"] = reason.recipient_class
+ params["member"] = self.member.unique_displayname
+ params["membership_invitations_url"] = "%s/+invitation/%s" % (
+ canonical_url(self.member), self.team.name)
+ params["team"] = self.team.unique_displayname
+ params["team_url"] = canonical_url(self.team)
+ if self.membership is not None:
+ params["membership_url"] = canonical_url(self.membership)
+ if reason.recipient_class == "bulk" and self.reviewer == self.member:
+ params["reviewer"] = "the user"
+ elif self.reviewer is not None:
+ params["reviewer"] = self.reviewer.unique_displayname
+ if self.team.mailing_list is not None:
+ template = get_email_template(
+ "team-list-subscribe-block.txt", app="registry")
+ editemails_url = urlappend(
+ canonical_url(getUtility(ILaunchpadRoot)),
+ "~/+editmailinglists")
+ list_instructions = template % {"editemails_url": editemails_url}
+ else:
+ list_instructions = ""
+ params["list_instructions"] = list_instructions
+ params.update(self.extra_params)
+ return params
+
+ def _getFooter(self, email, recipient, params):
+ """See `BaseMailer`."""
+ return "%(reason)s\n" % params
=== modified file 'lib/lp/registry/model/persontransferjob.py'
--- lib/lp/registry/model/persontransferjob.py 2015-07-09 20:06:17 +0000
+++ lib/lp/registry/model/persontransferjob.py 2015-08-27 14:49:13 +0000
@@ -1,4 +1,4 @@
-# Copyright 2010-2013 Canonical Ltd. This software is licensed under the
+# Copyright 2010-2015 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Job classes related to PersonTransferJob."""
@@ -27,10 +27,7 @@
)
from lp.app.interfaces.launchpad import ILaunchpadCelebrities
-from lp.registry.enums import (
- PersonTransferJobType,
- TeamMembershipPolicy,
- )
+from lp.registry.enums import PersonTransferJobType
from lp.registry.interfaces.person import (
IPerson,
IPersonSet,
@@ -47,6 +44,7 @@
IPersonTransferJobSource,
)
from lp.registry.interfaces.teammembership import TeamMembershipStatus
+from lp.registry.mail.team import TeamMailer
from lp.registry.model.person import Person
from lp.registry.personmerge import merge_people
from lp.services.config import config
@@ -62,17 +60,7 @@
Job,
)
from lp.services.job.runner import BaseRunnableJob
-from lp.services.mail.helpers import (
- get_contact_email_addresses,
- get_email_template,
- )
-from lp.services.mail.mailwrapper import MailWrapper
-from lp.services.mail.sendmail import (
- format_address,
- format_address_for_person,
- simple_sendmail,
- )
-from lp.services.webapp import canonical_url
+from lp.services.mail.sendmail import format_address_for_person
@implementer(IPersonTransferJob)
@@ -241,106 +229,9 @@
def run(self):
"""See `IMembershipNotificationJob`."""
from lp.services.scripts import log
- from_addr = format_address(
- self.team.displayname, config.canonical.noreply_from_address)
- admin_emails = self.team.getTeamAdminsEmailAddresses()
- # person might be a self.team, so we can't rely on its preferredemail.
- self.member_email = get_contact_email_addresses(self.member)
- # Make sure we don't send the same notification twice to anybody.
- for email in self.member_email:
- if email in admin_emails:
- admin_emails.remove(email)
-
- if self.reviewer != self.member:
- self.reviewer_name = self.reviewer.unique_displayname
- else:
- self.reviewer_name = 'the user'
-
- if self.last_change_comment:
- comment = ("\n%s said:\n %s\n" % (
- self.reviewer.displayname, self.last_change_comment.strip()))
- else:
- comment = ""
-
- replacements = {
- 'member_name': self.member.unique_displayname,
- 'recipient_name': self.member.displayname,
- 'team_name': self.team.unique_displayname,
- 'team_url': canonical_url(self.team),
- 'old_status': self.old_status.title,
- 'new_status': self.new_status.title,
- 'reviewer_name': self.reviewer_name,
- 'comment': comment}
-
- template_name = 'membership-statuschange'
- subject = (
- 'Membership change: %(member)s in %(team)s'
- % {
- 'member': self.member.name,
- 'team': self.team.name,
- })
- if self.new_status == TeamMembershipStatus.EXPIRED:
- template_name = 'membership-expired'
- subject = '%s expired from team' % self.member.name
- elif (self.new_status == TeamMembershipStatus.APPROVED and
- self.old_status != TeamMembershipStatus.ADMIN):
- if self.old_status == TeamMembershipStatus.INVITED:
- subject = ('Invitation to %s accepted by %s'
- % (self.member.name, self.reviewer.name))
- template_name = 'membership-invitation-accepted'
- elif self.old_status == TeamMembershipStatus.PROPOSED:
- subject = '%s approved by %s' % (
- self.member.name, self.reviewer.name)
- else:
- subject = '%s added by %s' % (
- self.member.name, self.reviewer.name)
- elif self.new_status == TeamMembershipStatus.INVITATION_DECLINED:
- subject = ('Invitation to %s declined by %s'
- % (self.member.name, self.reviewer.name))
- template_name = 'membership-invitation-declined'
- elif self.new_status == TeamMembershipStatus.DEACTIVATED:
- subject = '%s deactivated by %s' % (
- self.member.name, self.reviewer.name)
- elif self.new_status == TeamMembershipStatus.ADMIN:
- subject = '%s made admin by %s' % (
- self.member.name, self.reviewer.name)
- elif self.new_status == TeamMembershipStatus.DECLINED:
- subject = '%s declined by %s' % (
- self.member.name, self.reviewer.name)
- else:
- # Use the default template and subject.
- pass
-
- # Must have someone to mail, and be a non-open team (because open
- # teams are unrestricted, notifications on join/ leave do not help the
- # admins.
- if (len(admin_emails) != 0 and
- self.team.membership_policy != TeamMembershipPolicy.OPEN):
- admin_template = get_email_template(
- "%s-bulk.txt" % template_name, app='registry')
- for address in admin_emails:
- recipient = getUtility(IPersonSet).getByEmail(address)
- replacements['recipient_name'] = recipient.displayname
- msg = MailWrapper().format(
- admin_template % replacements, force_wrap=True)
- simple_sendmail(from_addr, address, subject, msg)
-
- # The self.member can be a self.self.team without any
- # self.members, and in this case we won't have a single email
- # address to send this notification to.
- if self.member_email and self.reviewer != self.member:
- if self.member.is_team:
- template = '%s-bulk.txt' % template_name
- else:
- template = '%s-personal.txt' % template_name
- self.member_template = get_email_template(
- template, app='registry')
- for address in self.member_email:
- recipient = getUtility(IPersonSet).getByEmail(address)
- replacements['recipient_name'] = recipient.displayname
- msg = MailWrapper().format(
- self.member_template % replacements, force_wrap=True)
- simple_sendmail(from_addr, address, subject, msg)
+ TeamMailer.forMembershipStatusChange(
+ self.member, self.team, self.reviewer, self.old_status,
+ self.new_status, self.last_change_comment).sendAll()
log.debug('MembershipNotificationJob sent email')
def __repr__(self):
=== modified file 'lib/lp/registry/model/teammembership.py'
--- lib/lp/registry/model/teammembership.py 2015-07-08 16:05:11 +0000
+++ lib/lp/registry/model/teammembership.py 2015-08-27 14:49:13 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2012 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
__metaclass__ = type
@@ -24,7 +24,6 @@
from zope.component import getUtility
from zope.interface import implementer
-from lp.app.browser.tales import DurationFormatterAPI
from lp.app.interfaces.launchpad import ILaunchpadCelebrities
from lp.registry.enums import TeamMembershipRenewalPolicy
from lp.registry.errors import (
@@ -32,7 +31,6 @@
UserCannotChangeMembershipSilently,
)
from lp.registry.interfaces.person import (
- IPersonSet,
validate_person,
validate_public_person,
)
@@ -52,7 +50,6 @@
ITeamParticipation,
TeamMembershipStatus,
)
-from lp.services.config import config
from lp.services.database.constants import UTC_NOW
from lp.services.database.datetimecol import UtcDateTimeCol
from lp.services.database.enumcol import EnumCol
@@ -63,16 +60,6 @@
SQLBase,
sqlvalues,
)
-from lp.services.mail.helpers import (
- get_contact_email_addresses,
- get_email_template,
- )
-from lp.services.mail.mailwrapper import MailWrapper
-from lp.services.mail.sendmail import (
- format_address,
- simple_sendmail,
- )
-from lp.services.webapp import canonical_url
@implementer(ITeamMembership)
@@ -132,26 +119,10 @@
def sendSelfRenewalNotification(self):
"""See `ITeamMembership`."""
- team = self.team
- member = self.person
- assert team.renewal_policy == TeamMembershipRenewalPolicy.ONDEMAND
-
- from_addr = format_address(
- team.displayname, config.canonical.noreply_from_address)
- replacements = {'member_name': member.unique_displayname,
- 'team_name': team.unique_displayname,
- 'team_url': canonical_url(team),
- 'dateexpires': self.dateexpires.strftime('%Y-%m-%d')}
- subject = '%s extended their membership' % member.name
- template = get_email_template(
- 'membership-member-renewed.txt', app='registry')
- admins_addrs = self.team.getTeamAdminsEmailAddresses()
- for address in admins_addrs:
- recipient = getUtility(IPersonSet).getByEmail(address)
- replacements['recipient_name'] = recipient.displayname
- msg = MailWrapper().format(
- template % replacements, force_wrap=True)
- simple_sendmail(from_addr, address, subject, msg)
+ # Circular import.
+ from lp.registry.mail.team import TeamMailer
+ TeamMailer.forSelfRenewal(
+ self.person, self.team, self.dateexpires).sendAll()
def canChangeStatusSilently(self, user):
"""Ensure that the user is in the Launchpad Administrators group.
@@ -185,6 +156,8 @@
def sendExpirationWarningEmail(self):
"""See `ITeamMembership`."""
+ # Circular import.
+ from lp.registry.mail.team import TeamMailer
if self.dateexpires is None:
raise AssertionError(
'%s in team %s has no membership expiration date.' %
@@ -194,68 +167,8 @@
# there is nothing to do. The member will have received emails
# from previous calls by flag-expired-memberships.py
return
- member = self.person
- team = self.team
- if member.is_team:
- recipient = member.teamowner
- templatename = 'membership-expiration-warning-bulk.txt'
- subject = '%s will expire soon from %s' % (member.name, team.name)
- else:
- recipient = member
- templatename = 'membership-expiration-warning-personal.txt'
- subject = 'Your membership in %s is about to expire' % team.name
-
- if team.renewal_policy == TeamMembershipRenewalPolicy.ONDEMAND:
- how_to_renew = (
- "If you want, you can renew this membership at\n"
- "<%s/+expiringmembership/%s>"
- % (canonical_url(member), team.name))
- elif not self.canChangeExpirationDate(recipient):
- admins_names = []
- admins = team.getDirectAdministrators()
- assert admins.count() >= 1
- if admins.count() == 1:
- admin = admins[0]
- how_to_renew = (
- "To prevent this membership from expiring, you should "
- "contact the\nteam's administrator, %s.\n<%s>"
- % (admin.unique_displayname, canonical_url(admin)))
- else:
- for admin in admins:
- admins_names.append(
- "%s <%s>" % (admin.unique_displayname,
- canonical_url(admin)))
-
- how_to_renew = (
- "To prevent this membership from expiring, you should "
- "get in touch\nwith one of the team's administrators:\n")
- how_to_renew += "\n".join(admins_names)
- else:
- how_to_renew = (
- "To stay a member of this team you should extend your "
- "membership at\n<%s/+member/%s>"
- % (canonical_url(team), member.name))
-
- to_addrs = get_contact_email_addresses(recipient)
- if len(to_addrs) == 0:
- # The user does not have a preferred email address, he was
- # probably suspended.
- return
- formatter = DurationFormatterAPI(
- self.dateexpires - datetime.now(pytz.timezone('UTC')))
- replacements = {
- 'recipient_name': recipient.displayname,
- 'member_name': member.unique_displayname,
- 'team_url': canonical_url(team),
- 'how_to_renew': how_to_renew,
- 'team_name': team.unique_displayname,
- 'expiration_date': self.dateexpires.strftime('%Y-%m-%d'),
- 'approximate_duration': formatter.approximateduration()}
-
- msg = get_email_template(templatename, app='registry') % replacements
- from_addr = format_address(
- team.displayname, config.canonical.noreply_from_address)
- simple_sendmail(from_addr, to_addrs, subject, msg)
+ TeamMailer.forExpiringMembership(
+ self.person, self.team, self, self.dateexpires).sendAll()
def setStatus(self, status, user, comment=None, silent=False):
"""See `ITeamMembership`."""
=== modified file 'lib/lp/registry/tests/test_teammembership.py'
--- lib/lp/registry/tests/test_teammembership.py 2013-06-20 05:50:00 +0000
+++ lib/lp/registry/tests/test_teammembership.py 2015-08-27 14:49:13 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
__metaclass__ = type
@@ -61,6 +61,7 @@
from lp.services.features.testing import FeatureFixture
from lp.services.job.tests import block_on_job
from lp.services.log.logger import BufferLogger
+from lp.services.mail.sendmail import format_address_for_person
from lp.testing import (
login,
login_celebrity,
@@ -1085,8 +1086,7 @@
message = notifications[0]
self.assertEqual(
'Your membership in red is about to expire', message['subject'])
- self.assertEqual(
- self.member.preferredemail.email, message['to'])
+ self.assertEqual(format_address_for_person(self.member), message['to'])
def test_no_message_sent_for_expired_memberships(self):
# Members whose membership has expired do not get a message.
=== modified file 'lib/lp/services/mail/basemailer.py'
--- lib/lp/services/mail/basemailer.py 2015-08-25 14:05:24 +0000
+++ lib/lp/services/mail/basemailer.py 2015-08-27 14:49:13 +0000
@@ -16,6 +16,7 @@
from zope.error.interfaces import IErrorReportingUtility
from lp.services.mail.helpers import get_email_template
+from lp.services.mail.mailwrapper import MailWrapper
from lp.services.mail.notificationrecipientset import NotificationRecipientSet
from lp.services.mail.sendmail import (
append_footer,
@@ -39,7 +40,8 @@
def __init__(self, subject, template_name, recipients, from_address,
delta=None, message_id=None, notification_type=None,
- mail_controller_class=None, request=None):
+ mail_controller_class=None, request=None, wrap=False,
+ force_wrap=False):
"""Constructor.
:param subject: A Python dict-replacement template for the subject
@@ -55,6 +57,8 @@
use to send the mails. Defaults to `MailController`.
:param request: An optional `IErrorReportRequest` to use when
logging OOPSes.
+ :param wrap: Wrap body text using `MailWrapper`.
+ :param force_wrap: See `MailWrapper.format`.
"""
self._subject_template = subject
self._template_name = template_name
@@ -70,6 +74,8 @@
mail_controller_class = MailController
self._mail_controller_class = mail_controller_class
self.request = request
+ self._wrap = wrap
+ self._force_wrap = force_wrap
def _getFromAddress(self, email, recipient):
return self.from_address
@@ -108,7 +114,7 @@
return (self._subject_template %
self._getTemplateParams(email, recipient))
- def _getReplyToAddress(self):
+ def _getReplyToAddress(self, email, recipient):
"""Return the address to use for the reply-to header."""
return None
@@ -119,7 +125,7 @@
headers['X-Launchpad-Message-Rationale'] = reason.mail_header
if self.notification_type is not None:
headers['X-Launchpad-Notification-Type'] = self.notification_type
- reply_to = self._getReplyToAddress()
+ reply_to = self._getReplyToAddress(email, recipient)
if reply_to is not None:
headers['Reply-To'] = reply_to
if self.message_id is not None:
@@ -158,6 +164,9 @@
self._getTemplateName(email, recipient), app=self.app)
params = self._getTemplateParams(email, recipient)
body = template % params
+ if self._wrap:
+ body = MailWrapper().format(
+ body, force_wrap=self._force_wrap) + "\n"
footer = self._getFooter(email, recipient, params)
if footer is not None:
body = append_footer(body, footer)
=== modified file 'lib/lp/testing/mail_helpers.py'
--- lib/lp/testing/mail_helpers.py 2015-07-21 09:04:01 +0000
+++ lib/lp/testing/mail_helpers.py 2015-08-27 14:49:13 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Helper functions dealing with emails in tests.
@@ -54,7 +54,8 @@
def print_emails(include_reply_to=False, group_similar=False,
- include_rationale=False, notifications=None):
+ include_rationale=False, notifications=None,
+ include_notification_type=False):
"""Pop all messages from stub.test_emails and print them with
their recipients.
@@ -71,6 +72,8 @@
header.
:param notifications: Use the provided list of notifications instead of
the stack.
+ :param include_notification_type: Include the
+ X-Launchpad-Notification-Type header.
"""
distinct_bodies = {}
if notifications is None:
@@ -99,16 +102,22 @@
if include_rationale and rationale_header in message:
print (
'%s: %s' % (rationale_header, message[rationale_header]))
+ notification_type_header = 'X-Launchpad-Notification-Type'
+ if include_notification_type and notification_type_header in message:
+ print '%s: %s' % (
+ notification_type_header, message[notification_type_header])
print 'Subject:', message['Subject']
print body
print "-" * 40
-def print_distinct_emails(include_reply_to=False, include_rationale=True):
+def print_distinct_emails(include_reply_to=False, include_rationale=True,
+ include_notification_type=True):
"""A convenient shortcut for `print_emails`(group_similar=True)."""
return print_emails(group_similar=True,
include_reply_to=include_reply_to,
- include_rationale=include_rationale)
+ include_rationale=include_rationale,
+ include_notification_type=include_notification_type)
def run_mail_jobs():
Follow ups