1
Mail notifications for membership changes
2
=========================================
1
= Mail notifications for membership changes =
4
Whenever a membership status is changed, we should notify the team
5
admins and the member whose membership changed. There's a few cases
6
where we might want to notify only the team admins, but in most of the
7
cases we'll be sending two similar (but not identical) notifications:
8
one for all team admins and another for the member.
3
Whenever a membership status is changed, we should notify the team admins and
4
the member whose membership changed. There's a few cases where we might want
5
to notify only the team admins, but in most of the cases we'll be sending two
6
similar (but not identical) notifications: one for all team admins and another
10
9
>>> def by_to_addrs(a, b):
11
10
... return cmp(a[1], b[1])
110
108
You received this email because you are the owner of the Ubuntu Team team.
111
109
----------------------------------------
113
Declining a proposed member should generate notifications for both the
114
member and each of the team's admins.
111
Declining a proposed member should generate notifications for both the member
112
and each of the team's admins.
116
114
# Need to be logged in as a team admin to be able to change memberships of
119
116
>>> login('mark@example.com')
120
117
>>> setStatus(membership, TeamMembershipStatus.DECLINED, reviewer=mark)
222
216
<http://launchpad.dev/~ubuntu-team>
223
217
----------------------------------------
225
Team admins can propose their teams using the join() method as well, but
226
in that case we'll use the requester's (the person proposing the team as
227
the other's member) email address in the 'Reply-To' header of the
219
Team admins can propose their teams using the join() method as well, but in
220
that case we'll use the requester's (the person proposing the team as the
221
other's member) email address in the 'Reply-To' header of the message sent.
230
223
>>> admins = personset.getByName('admins')
231
224
>>> admins.join(ubuntu_team, requester=mark)
232
225
>>> run_mail_jobs()
233
226
>>> len(stub.test_emails)
236
228
>>> print_distinct_emails(include_reply_to=True)
237
229
From: Ubuntu Team <noreply@launchpad.net>
238
230
To: colin.watson@ubuntulinux.com, foo.bar@canonical.com,
317
307
You received this email because you are the owner of the Ubuntu Team team.
318
308
----------------------------------------
320
By default, if the newly added member is actually a team, we'll only
321
send an invitation to the team's admins, telling them that the
322
membership will only be activated if they accept the invitation.
310
By default, if the newly added member is actually a team, we'll only send
311
an invitation to the team's admins, telling them that the membership will
312
only be activated if they accept the invitation.
324
314
>>> mirror_admins = personset.getByName('ubuntu-mirror-admins')
325
315
>>> mirror_admins.getTeamAdminsEmailAddresses()
326
316
['mark@example.com']
328
317
>>> ignored = ubuntu_team.addMember(mirror_admins, reviewer=cprov)
329
318
>>> run_mail_jobs()
330
319
>>> len(stub.test_emails)
333
321
>>> print_distinct_emails()
334
322
From: Ubuntu Team <noreply@launchpad.net>
335
323
To: mark@example.com
348
336
The Launchpad team
349
337
----------------------------------------
351
If one of the admins accept the invitation, then a notification is sent
352
to the team which just became a member and to the admins of the hosting
339
If one of the admins accept the invitation, then a notification is sent to the
340
team which just became a member and to the admins of the hosting team.
355
342
>>> comment = "Of course I want to be part of ubuntu!"
356
343
>>> mirror_admins.acceptInvitationToBeMemberOf(ubuntu_team, comment)
454
437
----------------------------------------
457
Membership expiration warnings
458
------------------------------
440
== Membership expiration warnings ==
460
When we get close to the expiration date of a given membership, an
461
expiration warning is sent to the member, so that he can contact the
462
team's administrators (or renew it himself when he has necessary rights)
463
in case he wants to retain that membership. This is done by the flag-
464
expired-memberships cronscript, which uses
442
When we get close to the expiration date of a given membership, an expiration
443
warning is sent to the member, so that he can contact the team's
444
administrators (or renew it himself when he has necessary rights) in case
445
he wants to retain that membership. This is done by the
446
flag-expired-memberships cronscript, which uses
465
447
ITeamMembership.sendExpirationWarningEmail to do its job.
468
450
>>> from datetime import datetime, timedelta
469
451
>>> utc_now = datetime.now(pytz.timezone('UTC'))
452
>>> kamion_on_ubuntu_team = membershipset.getByPersonAndTeam(
453
... kamion, ubuntu_team)
454
>>> kamion_on_ubuntu_team.setExpirationDate(
455
... utc_now + timedelta(days=9), mark)
456
>>> flush_database_updates()
458
Kamion is an admin of the Ubuntu team, but team admins can't change the
459
expiration date of their own memberships, so he still has to contact one of
460
the other team admins.
462
>>> kamion_on_ubuntu_team.status.name
464
>>> kamion_on_ubuntu_team.sendExpirationWarningEmail()
465
>>> transaction.commit()
466
>>> print_distinct_emails()
467
From: Ubuntu Team <noreply@launchpad.net>
468
To: colin.watson@ubuntulinux.com
469
Subject: Your membership in ubuntu-team is about to expire
471
On ..., 9 days from now, your membership
472
in the Ubuntu Team (ubuntu-team) Launchpad team
474
<http://launchpad.dev/~ubuntu-team>
476
To prevent this membership from expiring, you should get in touch
477
with one of the team's administrators:
478
Alexander Limi (limi) <http://launchpad.dev/~limi>
479
Foo Bar (name16) <http://launchpad.dev/~name16>
480
Jeff Waugh (jdub) <http://launchpad.dev/~jdub>
481
Mark Shuttleworth (mark) <http://launchpad.dev/~mark>
483
If your membership does expire, we'll send you one more message to let
484
you know it's happened.
486
Thanks for using Launchpad!
488
----------------------------------------
471
490
In the case of the beta-testers team, the email is sent only to the
472
491
team's owner, which doesn't have the necessary rights to renew the
473
membership of his team, so he's instructed to contact one of the ubuntu-
492
membership of his team, so he's instructed to contact one of the
493
ubuntu-team's admins.
476
495
>>> beta_testers = personset.getByName('launchpad-beta-testers')
477
496
>>> beta_testers_on_ubuntu_team = membershipset.getByPersonAndTeam(
508
527
----------------------------------------
510
If the team's renewal policy is ONDEMAND, though, the member is invited
511
to renew his own membership.
529
If the team's renewal policy is ONDEMAND, though, the member is invited to
530
renew his own membership.
513
532
>>> ubuntu_team.renewal_policy = TeamMembershipRenewalPolicy.ONDEMAND
514
533
>>> ubuntu_team.defaultrenewalperiod = 365
515
>>> kamion_on_ubuntu_team = membershipset.getByPersonAndTeam(
516
... kamion, ubuntu_team)
517
>>> kamion_on_ubuntu_team.setExpirationDate(
518
... utc_now + timedelta(days=9), mark)
519
534
>>> flush_database_updates()
520
535
>>> kamion_on_ubuntu_team.sendExpirationWarningEmail()
521
536
>>> transaction.commit()
563
578
----------------------------------------
565
If the team's renewal policy is NONE but the member has the necessary
566
rights to change the expiration date of his own membership (i.e. by
567
being the team's owner), the notification he gets will contain a link to
568
his memberhip page, where he can extend it.
580
If the team's renewal policy is NONE but the member has the necessary rights
581
to change the expiration date of his own membership (i.e. by being the team's
582
owner), the notification he gets will contain a link to his memberhip page,
583
where he can extend it.
570
585
>>> landscape.renewal_policy = TeamMembershipRenewalPolicy.NONE
571
586
>>> landscape.teamowner.preferredemail.email
572
587
u'test@canonical.com'
574
588
>>> sampleperson_on_landscape = membershipset.getByPersonAndTeam(
575
589
... sampleperson, landscape)
576
590
>>> sampleperson_on_landscape.setExpirationDate(
599
613
----------------------------------------
602
Membership expiration notification
603
----------------------------------
616
== Membership expiration notification ==
605
For teams with a renewal policy other than AUTOMATIC, if a membership is
606
not renewed before its expiration date it'll be flagged as expired and a
607
notification is sent to the team admins and to the member whose
608
membership expired. If the renewal policy is AUTOMATIC, though, the
609
memberships that should expire will retain their status and have their
610
dateexpires update. A notification is also sent to the member and to
611
team admins when a membership is automatically renewed.
618
For teams with a renewal policy other than AUTOMATIC, if a membership is not
619
renewed before its expiration date it'll be flagged as expired and a
620
notification is sent to the team admins and to the member whose membership
621
expired. If the renewal policy is AUTOMATIC, though, the memberships that
622
should expire will retain their status and have their dateexpires update. A
623
notification is also sent to the member and to team admins when a membership
624
is automatically renewed.
613
626
>>> from zope.security.proxy import removeSecurityProxy
614
627
>>> utc_now = datetime.now(pytz.timezone('UTC'))
689
701
----------------------------------------
692
Memberships renewed by the members themselves
693
---------------------------------------------
704
== Memberships renewed by the members themselves ==
695
Another possible renewal policy for teams is ONDEMAND, which means that
696
team members are invited to renew their membership once it gets close to
697
their expiration date. When a member renew his own membership, a
698
notification is sent to all team admins.
706
Another possible renewal policy for teams is ONDEMAND, which means that team
707
members are invited to renew their membership once it gets close to their
708
expiration date. When a member renew his own membership, a notification is
709
sent to all team admins.
700
711
>>> karl = personset.getByName('karl')
701
712
>>> mirror_admins = personset.getByName('ubuntu-mirror-admins')
735
745
The Launchpad team
736
746
----------------------------------------
742
When creating a new team, the owner has his membership's status changed
743
from approved to admin, but he won't get a notification of that.
748
== Some special cases ==
750
When creating a new team, the owner has his membership's status changed from
751
approved to admin, but he won't get a notification of that.
745
753
>>> team = personset.newTeam(mark, 'testteam', 'Test')
746
754
>>> run_mail_jobs()
750
758
# Other tests expect an empty stub.test_emails, but if this one above
751
759
# fails, I don't want a non-empty stub.test_emails to cause the tests
752
760
# below to fail too.
754
761
>>> stub.test_emails = []
756
If cprov is made an administrator of ubuntu_team, he'll only get one
763
If cprov is made an administrator of ubuntu_team, he'll only get one email
759
766
>>> cprov = personset.getByName('cprov')
760
>>> cprov_membership = membershipset.getByPersonAndTeam(
761
... cprov, ubuntu_team)
767
>>> cprov_membership = membershipset.getByPersonAndTeam(cprov, ubuntu_team)
762
768
>>> login('mark@example.com')
764
770
... cprov_membership, TeamMembershipStatus.ADMIN, reviewer=mark)
765
771
>>> run_mail_jobs()
766
772
>>> len(stub.test_emails)
769
774
>>> print_distinct_emails()
770
775
From: Ubuntu Team <noreply@launchpad.net>
771
776
To: colin.watson@ubuntulinux.com, foo.bar@canonical.com,
786
791
<http://launchpad.dev/~ubuntu-team>
787
792
----------------------------------------
789
If a team admin changes his own membership, the notification sent will
790
clearly say that the change was performed by the user himself, and it
791
will only be sent to the team administrators.
794
If a team admin changes his own membership, the notification sent will clearly
795
say that the change was performed by the user himself, and it will only be
796
sent to the team administrators.
793
798
>>> jdub = getUtility(IPersonSet).getByName('jdub')
794
799
>>> jdub_membership = membershipset.getByPersonAndTeam(jdub, ubuntu_team)
810
814
<http://launchpad.dev/~ubuntu-team>
811
815
----------------------------------------
813
Deactivating the membership of a team also generates notifications for
814
the team which had the membership deactivated and to the administrators
815
of the hosting team. Note that the notification sent to the team whose
816
membership was deactivated will not talk about "your membership" as it
817
wouldn't make sense to the members of the team reading it.
817
Deactivating the membership of a team also generates notifications for the
818
team which had the membership deactivated and to the administrators of the
819
hosting team. Note that the notification sent to the team whose membership
820
was deactivated will not talk about "your membership" as it wouldn't make
821
sense to the members of the team reading it.
819
823
>>> mirror_admins_membership = membershipset.getByPersonAndTeam(
820
824
... mirror_admins, ubuntu_team)
837
841
<http://launchpad.dev/~ubuntu-team>
838
842
----------------------------------------
840
Deactivating memberships can also be done silently (no email
841
notifications sent) by Launchpad Administrators.
844
Deactivating memberships can also be done silently (no email notifications
845
sent) by Launchpad Administrators.
843
847
>>> dumper = getUtility(IPersonSet).getByName('dumper')
844
848
>>> hwdb_admins = personset.getByName('hwdb-team')
847
851
>>> print dumper_hwdb_membership.status.title
850
853
>>> login_person(admin_person)
851
854
>>> setStatus(dumper_hwdb_membership, TeamMembershipStatus.DEACTIVATED,
852
855
... reviewer=admin_person, silent=True)
853
856
>>> run_mail_jobs()
854
857
>>> len(stub.test_emails)
857
859
>>> print dumper_hwdb_membership.status.title
860
People who are not Launchpad Administrators, may not change other's
861
membership statues silently.
862
People who are not Launchpad Administrators, may not change other's membership
863
865
>>> kamion = getUtility(IPersonSet).getByName('kamion')
864
866
>>> stevea = getUtility(IPersonSet).getByName('stevea')
870
872
... stevea, ubuntu_team)
871
873
>>> print kamion_ubuntu_team_membership.status.title
874
875
>>> print stevea_ubuntu_team_membership.status.title
877
877
>>> setStatus(stevea_ubuntu_team_membership,
878
878
... TeamMembershipStatus.DEACTIVATED, reviewer=kamion, silent=True)
879
879
Traceback (most recent call last):
880
880
UserCannotChangeMembershipSilently: ...
882
881
>>> print stevea_ubuntu_team_membership.status.title
886
884
Joining a team with a mailing list
887
885
----------------------------------
889
When a user joins a team with a mailing list, the new member's
890
notification email contain subscription information.
887
When a user joins a team with a mailing list, the new member's notification
888
email contain subscription information.
892
890
>>> owner = factory.makePerson(name='team-owner')
893
891
>>> login_person(owner)