1
= Mail notifications for membership changes =
1
Mail notifications for membership changes
2
=========================================
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
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.
9
10
>>> def by_to_addrs(a, b):
10
11
... return cmp(a[1], b[1])
108
110
You received this email because you are the owner of the Ubuntu Team team.
109
111
----------------------------------------
111
Declining a proposed member should generate notifications for both the member
112
and each of the team's admins.
113
Declining a proposed member should generate notifications for both the
114
member and each of the team's admins.
114
116
# Need to be logged in as a team admin to be able to change memberships of
116
119
>>> login('mark@example.com')
117
120
>>> setStatus(membership, TeamMembershipStatus.DECLINED, reviewer=mark)
216
222
<http://launchpad.dev/~ubuntu-team>
217
223
----------------------------------------
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.
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
223
230
>>> admins = personset.getByName('admins')
224
231
>>> admins.join(ubuntu_team, requester=mark)
225
232
>>> run_mail_jobs()
226
233
>>> len(stub.test_emails)
228
236
>>> print_distinct_emails(include_reply_to=True)
229
237
From: Ubuntu Team <noreply@launchpad.net>
230
238
To: colin.watson@ubuntulinux.com, foo.bar@canonical.com,
307
317
You received this email because you are the owner of the Ubuntu Team team.
308
318
----------------------------------------
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.
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.
314
324
>>> mirror_admins = personset.getByName('ubuntu-mirror-admins')
315
325
>>> mirror_admins.getTeamAdminsEmailAddresses()
316
326
['mark@example.com']
317
328
>>> ignored = ubuntu_team.addMember(mirror_admins, reviewer=cprov)
318
329
>>> run_mail_jobs()
319
330
>>> len(stub.test_emails)
321
333
>>> print_distinct_emails()
322
334
From: Ubuntu Team <noreply@launchpad.net>
323
335
To: mark@example.com
336
348
The Launchpad team
337
349
----------------------------------------
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.
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
342
355
>>> comment = "Of course I want to be part of ubuntu!"
343
356
>>> mirror_admins.acceptInvitationToBeMemberOf(ubuntu_team, comment)
437
454
----------------------------------------
440
== Membership expiration warnings ==
457
Membership expiration warnings
458
------------------------------
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
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
447
465
ITeamMembership.sendExpirationWarningEmail to do its job.
450
468
>>> from datetime import datetime, timedelta
451
469
>>> 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
----------------------------------------
490
471
In the case of the beta-testers team, the email is sent only to the
491
472
team's owner, which doesn't have the necessary rights to renew the
492
membership of his team, so he's instructed to contact one of the
493
ubuntu-team's admins.
473
membership of his team, so he's instructed to contact one of the ubuntu-
495
476
>>> beta_testers = personset.getByName('launchpad-beta-testers')
496
477
>>> beta_testers_on_ubuntu_team = membershipset.getByPersonAndTeam(
527
508
----------------------------------------
529
If the team's renewal policy is ONDEMAND, though, the member is invited to
530
renew his own membership.
510
If the team's renewal policy is ONDEMAND, though, the member is invited
511
to renew his own membership.
532
513
>>> ubuntu_team.renewal_policy = TeamMembershipRenewalPolicy.ONDEMAND
533
514
>>> 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)
534
519
>>> flush_database_updates()
535
520
>>> kamion_on_ubuntu_team.sendExpirationWarningEmail()
536
521
>>> transaction.commit()
578
563
----------------------------------------
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.
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.
585
570
>>> landscape.renewal_policy = TeamMembershipRenewalPolicy.NONE
586
571
>>> landscape.teamowner.preferredemail.email
587
572
u'test@canonical.com'
588
574
>>> sampleperson_on_landscape = membershipset.getByPersonAndTeam(
589
575
... sampleperson, landscape)
590
576
>>> sampleperson_on_landscape.setExpirationDate(
613
599
----------------------------------------
616
== Membership expiration notification ==
602
Membership expiration notification
603
----------------------------------
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.
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.
626
613
>>> from zope.security.proxy import removeSecurityProxy
627
614
>>> utc_now = datetime.now(pytz.timezone('UTC'))
701
689
----------------------------------------
704
== Memberships renewed by the members themselves ==
692
Memberships renewed by the members themselves
693
---------------------------------------------
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.
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.
711
700
>>> karl = personset.getByName('karl')
712
701
>>> mirror_admins = personset.getByName('ubuntu-mirror-admins')
745
735
The Launchpad team
746
736
----------------------------------------
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.
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.
753
745
>>> team = personset.newTeam(mark, 'testteam', 'Test')
754
746
>>> run_mail_jobs()
758
750
# Other tests expect an empty stub.test_emails, but if this one above
759
751
# fails, I don't want a non-empty stub.test_emails to cause the tests
760
752
# below to fail too.
761
754
>>> stub.test_emails = []
763
If cprov is made an administrator of ubuntu_team, he'll only get one email
756
If cprov is made an administrator of ubuntu_team, he'll only get one
766
759
>>> cprov = personset.getByName('cprov')
767
>>> cprov_membership = membershipset.getByPersonAndTeam(cprov, ubuntu_team)
760
>>> cprov_membership = membershipset.getByPersonAndTeam(
761
... cprov, ubuntu_team)
768
762
>>> login('mark@example.com')
770
764
... cprov_membership, TeamMembershipStatus.ADMIN, reviewer=mark)
771
765
>>> run_mail_jobs()
772
766
>>> len(stub.test_emails)
774
769
>>> print_distinct_emails()
775
770
From: Ubuntu Team <noreply@launchpad.net>
776
771
To: colin.watson@ubuntulinux.com, foo.bar@canonical.com,
791
786
<http://launchpad.dev/~ubuntu-team>
792
787
----------------------------------------
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.
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.
798
793
>>> jdub = getUtility(IPersonSet).getByName('jdub')
799
794
>>> jdub_membership = membershipset.getByPersonAndTeam(jdub, ubuntu_team)
814
810
<http://launchpad.dev/~ubuntu-team>
815
811
----------------------------------------
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.
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.
823
819
>>> mirror_admins_membership = membershipset.getByPersonAndTeam(
824
820
... mirror_admins, ubuntu_team)
841
837
<http://launchpad.dev/~ubuntu-team>
842
838
----------------------------------------
844
Deactivating memberships can also be done silently (no email notifications
845
sent) by Launchpad Administrators.
840
Deactivating memberships can also be done silently (no email
841
notifications sent) by Launchpad Administrators.
847
843
>>> dumper = getUtility(IPersonSet).getByName('dumper')
848
844
>>> hwdb_admins = personset.getByName('hwdb-team')
851
847
>>> print dumper_hwdb_membership.status.title
853
850
>>> login_person(admin_person)
854
851
>>> setStatus(dumper_hwdb_membership, TeamMembershipStatus.DEACTIVATED,
855
852
... reviewer=admin_person, silent=True)
856
853
>>> run_mail_jobs()
857
854
>>> len(stub.test_emails)
859
857
>>> print dumper_hwdb_membership.status.title
862
People who are not Launchpad Administrators, may not change other's membership
860
People who are not Launchpad Administrators, may not change other's
861
membership statues silently.
865
863
>>> kamion = getUtility(IPersonSet).getByName('kamion')
866
864
>>> stevea = getUtility(IPersonSet).getByName('stevea')
872
870
... stevea, ubuntu_team)
873
871
>>> print kamion_ubuntu_team_membership.status.title
875
874
>>> 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: ...
881
882
>>> print stevea_ubuntu_team_membership.status.title
884
886
Joining a team with a mailing list
885
887
----------------------------------
887
When a user joins a team with a mailing list, the new member's notification
888
email contain subscription information.
889
When a user joins a team with a mailing list, the new member's
890
notification email contain subscription information.
890
892
>>> owner = factory.makePerson(name='team-owner')
891
893
>>> login_person(owner)