~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/registry/doc/teammembership-email-notification.txt

  • Committer: William Grant
  • Date: 2012-01-03 07:30:21 UTC
  • mto: This revision was merged to the branch mainline in revision 14621.
  • Revision ID: william.grant@canonical.com-20120103073021-qvprj6kbnpg0h1qv
Update a few templates.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
Mail notifications for membership changes
2
 
=========================================
 
1
= Mail notifications for membership changes =
3
2
 
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
 
7
for the member.
9
8
 
10
9
    >>> def by_to_addrs(a, b):
11
10
    ...     return cmp(a[1], b[1])
48
47
    >>> from lp.testing.sampledata import ADMIN_EMAIL
49
48
    >>> admin_person = personset.getByEmail(ADMIN_EMAIL)
50
49
 
 
50
 
51
51
In open teams joining and leaving the team generates no notifications.
52
52
 
53
53
    >>> login_person(admin_person)
58
58
    >>> membership = membershipset.getByPersonAndTeam(new_person, open_team)
59
59
    >>> membership.status.title
60
60
    'Approved'
61
 
 
62
61
    >>> run_mail_jobs()
63
62
    >>> len(stub.test_emails) - base_mails
64
63
    0
65
 
 
66
64
    >>> new_person.leave(open_team)
67
65
    >>> run_mail_jobs()
68
66
    >>> len(stub.test_emails) - base_mails
69
67
    0
70
68
 
 
69
 
71
70
Now Robert Collins proposes himself as a member of the Ubuntu Team. This
72
71
generates a notification email only to Ubuntu Team administrators.
73
72
 
81
80
    >>> run_mail_jobs()
82
81
    >>> len(stub.test_emails)
83
82
    5
84
 
 
85
83
    >>> print_distinct_emails(include_reply_to=True)
86
84
    From: Ubuntu Team <noreply@launchpad.net>
87
85
    To: colin.watson@ubuntulinux.com, foo.bar@canonical.com,
110
108
    You received this email because you are the owner of the Ubuntu Team team.
111
109
    ----------------------------------------
112
110
 
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.
115
113
 
116
114
    # Need to be logged in as a team admin to be able to change memberships of
117
115
    # that team.
118
 
 
119
116
    >>> login('mark@example.com')
120
117
    >>> setStatus(membership, TeamMembershipStatus.DECLINED, reviewer=mark)
121
118
 
156
153
 
157
154
    # Remove notification of daf's membership pending approval from
158
155
    # stub.test_emails
159
 
 
160
156
    >>> transaction.commit()
161
157
    >>> dummy = pop_notifications()
162
158
 
166
162
    >>> stub.test_emails.sort(by_to_addrs)
167
163
    >>> len(stub.test_emails)
168
164
    6
169
 
 
170
165
    >>> print_distinct_emails()
171
166
    From: Ubuntu Team <noreply@launchpad.net>
172
167
    To: colin.watson@ubuntulinux.com, foo.bar@canonical.com,
201
196
    >>> stub.test_emails.sort(by_to_addrs)
202
197
    >>> len(stub.test_emails)
203
198
    6
204
 
 
205
199
    >>> print_distinct_emails()
206
200
    From: Ubuntu Team <noreply@launchpad.net>
207
201
    To: colin.watson@ubuntulinux.com, foo.bar@canonical.com,
222
216
    <http://launchpad.dev/~ubuntu-team>
223
217
    ----------------------------------------
224
218
 
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
228
 
message sent.
 
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.
229
222
 
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)
234
227
    5
235
 
 
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,
262
254
    ----------------------------------------
263
255
 
264
256
 
265
 
Adding new members
266
 
------------------
 
257
== Adding new members ==
267
258
 
268
259
When a person is added as a member of a team by one of that team's
269
 
administrators, an email is sent to all team administrators and to the
270
 
new member.
 
260
administrators, an email is sent to all team administrators and to the new
 
261
member.
271
262
 
272
263
    >>> cprov = personset.getByName('cprov')
273
264
    >>> marilize = personset.getByName('marilize')
278
269
 
279
270
    >>> len(stub.test_emails)
280
271
    6
281
 
 
282
272
    >>> print_distinct_emails()
283
273
    From: Ubuntu Team <noreply@launchpad.net>
284
274
    To: marilize@hbd.com
317
307
    You received this email because you are the owner of the Ubuntu Team team.
318
308
    ----------------------------------------
319
309
 
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.
323
313
 
324
314
    >>> mirror_admins = personset.getByName('ubuntu-mirror-admins')
325
315
    >>> mirror_admins.getTeamAdminsEmailAddresses()
326
316
    ['mark@example.com']
327
 
 
328
317
    >>> ignored = ubuntu_team.addMember(mirror_admins, reviewer=cprov)
329
318
    >>> run_mail_jobs()
330
319
    >>> len(stub.test_emails)
331
320
    1
332
 
 
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
    ----------------------------------------
350
338
 
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
353
 
team.
 
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.
354
341
 
355
342
    >>> comment = "Of course I want to be part of ubuntu!"
356
343
    >>> mirror_admins.acceptInvitationToBeMemberOf(ubuntu_team, comment)
359
346
 
360
347
    >>> len(stub.test_emails)
361
348
    6
362
 
 
363
349
    >>> print_distinct_emails()
364
350
    From: Ubuntu Team <noreply@launchpad.net>
365
351
    To: colin.watson@ubuntulinux.com, foo.bar@canonical.com,
383
369
 
384
370
    # Reset stub.test_emails as we don't care about the notification triggered
385
371
    # by the addMember() call.
386
 
 
387
372
    >>> transaction.commit()
388
373
    >>> stub.test_emails = []
389
374
 
394
379
 
395
380
    >>> len(stub.test_emails)
396
381
    7
397
 
 
398
382
    >>> print_distinct_emails()
399
383
    From: Ubuntu Team <noreply@launchpad.net>
400
384
    To: colin.watson@ubuntulinux.com, foo.bar@canonical.com,
420
404
    >>> run_mail_jobs()
421
405
    >>> len(stub.test_emails)
422
406
    5
423
 
 
424
407
    >>> print_distinct_emails()
425
408
    From: Ubuntu Team <noreply@launchpad.net>
426
409
    To: foo.bar@canonical.com
454
437
    ----------------------------------------
455
438
 
456
439
 
457
 
Membership expiration warnings
458
 
------------------------------
 
440
== Membership expiration warnings ==
459
441
 
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.
466
448
 
467
449
    >>> import pytz
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()
 
457
 
 
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.
 
461
 
 
462
    >>> kamion_on_ubuntu_team.status.name
 
463
    'ADMIN'
 
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
 
470
    <BLANKLINE>
 
471
    On ..., 9 days from now, your membership
 
472
    in the Ubuntu Team (ubuntu-team) Launchpad team
 
473
    is due to expire.
 
474
    <http://launchpad.dev/~ubuntu-team>
 
475
    <BLANKLINE>
 
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>
 
482
    <BLANKLINE>
 
483
    If your membership does expire, we'll send you one more message to let
 
484
    you know it's happened.
 
485
    <BLANKLINE>
 
486
    Thanks for using Launchpad!
 
487
    <BLANKLINE>
 
488
    ----------------------------------------
470
489
 
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-
474
 
team's admins.
 
492
membership of his team, so he's instructed to contact one of the
 
493
ubuntu-team's admins.
475
494
 
476
495
    >>> beta_testers = personset.getByName('launchpad-beta-testers')
477
496
    >>> beta_testers_on_ubuntu_team = membershipset.getByPersonAndTeam(
507
526
    <BLANKLINE>
508
527
    ----------------------------------------
509
528
 
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.
512
531
 
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()
562
577
    <BLANKLINE>
563
578
    ----------------------------------------
564
579
 
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.
569
584
 
570
585
    >>> landscape.renewal_policy = TeamMembershipRenewalPolicy.NONE
571
586
    >>> landscape.teamowner.preferredemail.email
572
587
    u'test@canonical.com'
573
 
 
574
588
    >>> sampleperson_on_landscape = membershipset.getByPersonAndTeam(
575
589
    ...     sampleperson, landscape)
576
590
    >>> sampleperson_on_landscape.setExpirationDate(
599
613
    ----------------------------------------
600
614
 
601
615
 
602
 
Membership expiration notification
603
 
----------------------------------
 
616
== Membership expiration notification ==
604
617
 
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.
612
625
 
613
626
    >>> from zope.security.proxy import removeSecurityProxy
614
627
    >>> utc_now = datetime.now(pytz.timezone('UTC'))
620
633
 
621
634
    # Need to cheat here and set the expiry date manually because the expiry
622
635
    # date given to setExpirationDate() must be in the future.
623
 
 
624
636
    >>> removeSecurityProxy(mark_on_admins).dateexpires = utc_now
625
637
 
626
638
    >>> ubuntu_team = personset.getByName('ubuntu-team')
689
701
    ----------------------------------------
690
702
 
691
703
 
692
 
Memberships renewed by the members themselves
693
 
---------------------------------------------
 
704
== Memberships renewed by the members themselves ==
694
705
 
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.
699
710
 
700
711
    >>> karl = personset.getByName('karl')
701
712
    >>> mirror_admins = personset.getByName('ubuntu-mirror-admins')
704
715
    >>> tomorrow = datetime.now(pytz.timezone('UTC')) + timedelta(days=1)
705
716
    >>> print karl_on_mirroradmins.status.title
706
717
    Approved
707
 
 
708
718
    >>> print karl_on_mirroradmins.dateexpires
709
719
    None
710
720
 
735
745
    The Launchpad team
736
746
    ----------------------------------------
737
747
 
738
 
 
739
 
Some special cases
740
 
------------------
741
 
 
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 ==
 
749
 
 
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.
744
752
 
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.
753
 
 
754
761
    >>> stub.test_emails = []
755
762
 
756
 
If cprov is made an administrator of ubuntu_team, he'll only get one
757
 
email notification.
 
763
If cprov is made an administrator of ubuntu_team, he'll only get one email
 
764
notification.
758
765
 
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')
763
769
    >>> setStatus(
764
770
    ...     cprov_membership, TeamMembershipStatus.ADMIN, reviewer=mark)
765
771
    >>> run_mail_jobs()
766
772
    >>> len(stub.test_emails)
767
773
    6
768
 
 
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
    ----------------------------------------
788
793
 
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.
792
797
 
793
798
    >>> jdub = getUtility(IPersonSet).getByName('jdub')
794
799
    >>> jdub_membership = membershipset.getByPersonAndTeam(jdub, ubuntu_team)
797
802
    >>> run_mail_jobs()
798
803
    >>> len(stub.test_emails)
799
804
    5
800
 
 
801
805
    >>> print_distinct_emails()
802
806
    From: Ubuntu Team <noreply@launchpad.net>
803
807
    To: celso.providelo@canonical.com, colin.watson@ubuntulinux.com,
810
814
    <http://launchpad.dev/~ubuntu-team>
811
815
    ----------------------------------------
812
816
 
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.
818
822
 
819
823
    >>> mirror_admins_membership = membershipset.getByPersonAndTeam(
820
824
    ...     mirror_admins, ubuntu_team)
837
841
    <http://launchpad.dev/~ubuntu-team>
838
842
    ----------------------------------------
839
843
 
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.
842
846
 
843
847
    >>> dumper = getUtility(IPersonSet).getByName('dumper')
844
848
    >>> hwdb_admins = personset.getByName('hwdb-team')
846
850
    ...     hwdb_admins)
847
851
    >>> print dumper_hwdb_membership.status.title
848
852
    Approved
849
 
 
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)
855
858
    0
856
 
 
857
859
    >>> print dumper_hwdb_membership.status.title
858
860
    Deactivated
859
861
 
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
statues silently.
862
864
 
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
872
874
    Administrator
873
 
 
874
875
    >>> print stevea_ubuntu_team_membership.status.title
875
876
    Approved
876
 
 
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
881
    >>> print stevea_ubuntu_team_membership.status.title
883
882
    Approved
884
883
 
885
 
 
886
884
Joining a team with a mailing list
887
885
----------------------------------
888
886
 
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.
891
889
 
892
890
    >>> owner = factory.makePerson(name='team-owner')
893
891
    >>> login_person(owner)
940
938
    You received this email because team-two is the new member.
941
939
    ----------------------------------------
942
940
 
943