~launchpad-pqm/launchpad/devel

« back to all changes in this revision

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

  • Committer: Launchpad Patch Queue Manager
  • Date: 2012-01-03 22:43:32 UTC
  • mfrom: (14443.3.8 no-team-admin-restrictions)
  • Revision ID: launchpad@pqm.canonical.com-20120103224332-5zi4vcvry7vg9l8h
[r=sinzui][bug=102180,
        206058] Let team admins promote others to admins and change their own
        expiration dates.

Show diffs side-by-side

added added

removed removed

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