~launchpad-pqm/launchpad/devel

11515.2.6 by Curtis Hovey
Quiet lint.
1
Team Membership/Participation
2
=============================
1374 by Canonical.com Patch Queue Manager
Join/Unjoin teams working as specified in TeamMembership/TeamMembershipPolicies. Got TeamParticipation correctly filled/cleaned when a Person Join/Unjoin a team. Doctests and pagetests for everything.
3
2021 by Canonical.com Patch Queue Manager
New vocabulary: ValidTeamMember, to avoid loops when adding new members (also make sure that the API to join teams wont allow these loops). Rewrote system doctests for team membership/participation and this forced me to find what was wrong with my code and fix it. :-) r=spiv
4
When a person joins a team, we store the relationship in the TeamMembership
5
table. In this table we store the membership status, the join date and the
7495.2.1 by Curtis Hovey
Trivial formatting clean up for lint before the real work starts.
6
expiry date. TeamMembership stores only direct members. However, when a
2021 by Canonical.com Patch Queue Manager
New vocabulary: ValidTeamMember, to avoid loops when adding new members (also make sure that the API to join teams wont allow these loops). Rewrote system doctests for team membership/participation and this forced me to find what was wrong with my code and fix it. :-) r=spiv
7
member of a team is in fact another team (in the case of Team Y is a member
7495.2.1 by Curtis Hovey
Trivial formatting clean up for lint before the real work starts.
8
of Team X), the membership is transitive (members of Team Y are also a
2021 by Canonical.com Patch Queue Manager
New vocabulary: ValidTeamMember, to avoid loops when adding new members (also make sure that the API to join teams wont allow these loops). Rewrote system doctests for team membership/participation and this forced me to find what was wrong with my code and fix it. :-) r=spiv
9
member of team X). For this reason the TeamParticipation table exists: it
10
represents all the people who are /effective members/ of the team.
1374 by Canonical.com Patch Queue Manager
Join/Unjoin teams working as specified in TeamMembership/TeamMembershipPolicies. Got TeamParticipation correctly filled/cleaned when a Person Join/Unjoin a team. Doctests and pagetests for everything.
11
12
First of all, create some teams:
13
3691.151.7 by kiko
More random fixage, getting rid of two XXXs in database/person.py; one of them changes EmailAddressSet.new() to take a person, not an ID. Improve some test coverage.
14
    >>> import pytz
15
    >>> from datetime import datetime, timedelta
11716.1.15 by Curtis Hovey
Fixed multiline import statements in doctests.
16
    >>> from lp.registry.interfaces.person import (
11716.1.12 by Curtis Hovey
Sorted imports in doctests.
17
    ...     TeamMembershipRenewalPolicy,
18
    ...     TeamSubscriptionPolicy,
19
    ...     )
11716.1.6 by Curtis Hovey
Converted glob imports in doctests to import for the true module.
20
    >>> from lp.registry.interfaces.teammembership import TeamMembershipStatus
3691.151.7 by kiko
More random fixage, getting rid of two XXXs in database/person.py; one of them changes EmailAddressSet.new() to take a person, not an ID. Improve some test coverage.
21
3691.272.12 by Guilherme Salgado
Some cleaning up on teammembership.txt. It was creating lots of teams without using the correct API and thus some changes I did broke lots of tests.
22
XXX: This doctest needs a lot of cleanups!
23
-- Guilherme Salgado, 2006-12-15
24
25
    >>> from zope.component import getUtility
11692.6.2 by Curtis Hovey
Use deglober to fixing simple glob imports in doctests.
26
    >>> from lp.registry.interfaces.person import IPersonSet
3691.272.12 by Guilherme Salgado
Some cleaning up on teammembership.txt. It was creating lots of teams without using the correct API and thus some changes I did broke lots of tests.
27
    >>> personset = getUtility(IPersonSet)
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
28
    >>> jblack = personset.getByName('jblack')
29
    >>> nopriv = personset.getByName('no-priv')
30
    >>> jdub = personset.getByName('jdub')
31
    >>> reviewer = nopriv
3691.272.12 by Guilherme Salgado
Some cleaning up on teammembership.txt. It was creating lots of teams without using the correct API and thus some changes I did broke lots of tests.
32
    >>> t1 = personset.newTeam(
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
33
    ...     jblack, 't1', 't1',
3691.272.12 by Guilherme Salgado
Some cleaning up on teammembership.txt. It was creating lots of teams without using the correct API and thus some changes I did broke lots of tests.
34
    ...     subscriptionpolicy=TeamSubscriptionPolicy.OPEN)
35
    >>> t2 = personset.newTeam(
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
36
    ...     nopriv, 't2', 't2',
3691.272.12 by Guilherme Salgado
Some cleaning up on teammembership.txt. It was creating lots of teams without using the correct API and thus some changes I did broke lots of tests.
37
    ...     subscriptionpolicy=TeamSubscriptionPolicy.OPEN)
38
    >>> t3 = personset.newTeam(
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
39
    ...     jdub, 't3', 't3',
3691.272.12 by Guilherme Salgado
Some cleaning up on teammembership.txt. It was creating lots of teams without using the correct API and thus some changes I did broke lots of tests.
40
    ...     subscriptionpolicy=TeamSubscriptionPolicy.MODERATED)
41
    >>> t4 = personset.newTeam(
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
42
    ...     nopriv, 't4', 't4',
3691.272.12 by Guilherme Salgado
Some cleaning up on teammembership.txt. It was creating lots of teams without using the correct API and thus some changes I did broke lots of tests.
43
    ...     subscriptionpolicy=TeamSubscriptionPolicy.OPEN)
44
    >>> t5 = personset.newTeam(
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
45
    ...     nopriv, 't5', 't5',
3691.272.12 by Guilherme Salgado
Some cleaning up on teammembership.txt. It was creating lots of teams without using the correct API and thus some changes I did broke lots of tests.
46
    ...     subscriptionpolicy=TeamSubscriptionPolicy.OPEN)
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
47
    >>> t6 = personset.newTeam(
48
    ...     jdub, 't6', 't6',
49
    ...     subscriptionpolicy=TeamSubscriptionPolicy.MODERATED)
3691.272.12 by Guilherme Salgado
Some cleaning up on teammembership.txt. It was creating lots of teams without using the correct API and thus some changes I did broke lots of tests.
50
4073.5.5 by Guilherme Salgado
Update IPerson.findPathToTeam's docstring to not say that it can't be used for finding the path from a team to another team and add a test to prove that now it CAN.
51
    # Make sure the teams have predictable (and different) creation dates as
52
    # some of our tests depend on that.
53
    >>> from zope.security.proxy import removeSecurityProxy
54
    >>> oldest = t1.datecreated
55
    >>> removeSecurityProxy(t2).datecreated = oldest + timedelta(hours=1)
56
    >>> removeSecurityProxy(t3).datecreated = oldest + timedelta(hours=2)
57
    >>> removeSecurityProxy(t4).datecreated = oldest + timedelta(hours=3)
58
    >>> removeSecurityProxy(t5).datecreated = oldest + timedelta(hours=4)
14606.3.1 by William Grant
Merge canonical.database into lp.services.database.
59
    >>> from lp.services.database.sqlbase import flush_database_updates
4073.5.5 by Guilherme Salgado
Update IPerson.findPathToTeam's docstring to not say that it can't be used for finding the path from a team to another team and add a test to prove that now it CAN.
60
    >>> flush_database_updates()
61
7495.2.1 by Curtis Hovey
Trivial formatting clean up for lint before the real work starts.
62
11515.2.6 by Curtis Hovey
Quiet lint.
63
Adding new members
64
------------------
3859.1.2 by Guilherme Salgado
Fix https://beta.launchpad.net/launchpad/+bug/88398 and do a bunch of other cleanups on teammembership code.
65
66
One way of adding new members to a team is by having the user himself join the
67
team he wants.
3691.272.12 by Guilherme Salgado
Some cleaning up on teammembership.txt. It was creating lots of teams without using the correct API and thus some changes I did broke lots of tests.
68
69
    >>> salgado = personset.getByName('salgado')
6596.1.1 by Guilherme Salgado
Fix https://launchpad.net/bugs/241332
70
    >>> login_person(salgado)
3691.272.12 by Guilherme Salgado
Some cleaning up on teammembership.txt. It was creating lots of teams without using the correct API and thus some changes I did broke lots of tests.
71
    >>> salgado.join(t3)
72
    >>> salgado.join(t4)
73
3859.1.2 by Guilherme Salgado
Fix https://beta.launchpad.net/launchpad/+bug/88398 and do a bunch of other cleanups on teammembership code.
74
Note that since t3 is a MODERATED team, Salgado is not really added as a
75
member of that team --somebody has to approve his membership first:
3691.272.12 by Guilherme Salgado
Some cleaning up on teammembership.txt. It was creating lots of teams without using the correct API and thus some changes I did broke lots of tests.
76
2770.1.47 by Guilherme Salgado
New cronscript to flag expired team memberships and some other cleanups.
77
    >>> [m.displayname for m in t4.allmembers]
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
78
    [u'Guilherme Salgado', u'No Privileges Person']
2021 by Canonical.com Patch Queue Manager
New vocabulary: ValidTeamMember, to avoid loops when adding new members (also make sure that the API to join teams wont allow these loops). Rewrote system doctests for team membership/participation and this forced me to find what was wrong with my code and fix it. :-) r=spiv
79
2770.1.47 by Guilherme Salgado
New cronscript to flag expired team memberships and some other cleanups.
80
    >>> [m.displayname for m in t3.allmembers]
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
81
    [u'Jeff Waugh']
6596.1.1 by Guilherme Salgado
Fix https://launchpad.net/bugs/241332
82
    >>> login_person(t3.teamowner)
3691.272.12 by Guilherme Salgado
Some cleaning up on teammembership.txt. It was creating lots of teams without using the correct API and thus some changes I did broke lots of tests.
83
    >>> t3.setMembershipData(salgado, TeamMembershipStatus.APPROVED, reviewer)
4197.3.2 by Guilherme Salgado
A bunch of small changes suggested by Tim plus some changes to make it possible to forcibly add a team as a member of another one without going through the invitation process
84
    >>> flush_database_updates()
3691.272.12 by Guilherme Salgado
Some cleaning up on teammembership.txt. It was creating lots of teams without using the correct API and thus some changes I did broke lots of tests.
85
    >>> [m.displayname for m in t3.allmembers]
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
86
    [u'Guilherme Salgado', u'Jeff Waugh']
1420 by Canonical.com Patch Queue Manager
New page to edit proposed members of a team and other one to add members. Fixed pagetests so them do not reference foaf/ anylonger. A last fix when setting the preferred email: we should set only if the email we're validating is the first validated.
87
3859.1.2 by Guilherme Salgado
Fix https://beta.launchpad.net/launchpad/+bug/88398 and do a bunch of other cleanups on teammembership code.
88
The join() method is not allowed for teams whose subscription policy is
89
RESTRICTED. And it'll be a no-op in case the user has already joined the
90
given team.
91
92
    >>> launchpad = personset.getByName('launchpad')
93
    >>> launchpad.subscriptionpolicy == TeamSubscriptionPolicy.RESTRICTED
94
    True
6596.1.1 by Guilherme Salgado
Fix https://launchpad.net/bugs/241332
95
    >>> login_person(salgado)
3859.1.2 by Guilherme Salgado
Fix https://beta.launchpad.net/launchpad/+bug/88398 and do a bunch of other cleanups on teammembership code.
96
    >>> salgado.join(launchpad)
97
    Traceback (most recent call last):
98
    ...
99
    JoinNotAllowed: This is a restricted team
100
101
    >>> salgado.join(t3)
102
    >>> salgado in t3.activemembers
103
    True
104
    >>> salgado.join(t4)
105
    >>> salgado in t4.activemembers
106
    True
107
5643.1.1 by Guilherme Salgado
Allow the join() method to be called on teams so that team admins can propose/add their teams as members of other teams.
108
Team admins can make any of their teams join other teams as well.
109
Just like for people, if the team is MODERATED, the membership will
110
be PENDING, whereas for OPEN teams the membership will be automatically
111
approved.  Note, though, that in the case of teams we need to pass a
5643.1.5 by Guilherme Salgado
A few fixes suggested by Danilo and Tom.
112
requester to the join() method.
5643.1.1 by Guilherme Salgado
Allow the join() method to be called on teams so that team admins can propose/add their teams as members of other teams.
113
114
    >>> ubuntu_team = personset.getByName('ubuntu-team')
6596.1.1 by Guilherme Salgado
Fix https://launchpad.net/bugs/241332
115
    >>> login_person(ubuntu_team.teamowner)
5643.1.1 by Guilherme Salgado
Allow the join() method to be called on teams so that team admins can propose/add their teams as members of other teams.
116
    >>> ubuntu_team.join(t3, ubuntu_team.teamowner)
117
    >>> t3.subscriptionpolicy
118
    <DBItem TeamSubscriptionPolicy.MODERATED...
119
    >>> ubuntu_team in t3.proposedmembers
120
    True
121
122
    >>> t2.subscriptionpolicy
123
    <DBItem TeamSubscriptionPolicy.OPEN...
124
    >>> ubuntu_team.join(t2, ubuntu_team.teamowner)
125
    >>> ubuntu_team in t2.activemembers
126
    True
127
128
    # Clean things up to not upset the other tests.
6596.1.1 by Guilherme Salgado
Fix https://launchpad.net/bugs/241332
129
    >>> login_person(t2.teamowner)
5643.1.1 by Guilherme Salgado
Allow the join() method to be called on teams so that team admins can propose/add their teams as members of other teams.
130
    >>> t2.setMembershipData(
131
    ...     ubuntu_team, TeamMembershipStatus.DEACTIVATED, t2.teamowner)
6914.2.1 by Guilherme Salgado
Attempt to fix the bug but not all tests are passing.
132
    >>> ubuntu_team in t2.activemembers
133
    False
12019.11.1 by Brad Crittenden
Rework cleanTeamParticipation. Works but is still inefficient.
134
    >>> [m.displayname for m in t2.allmembers]
135
    [u'No Privileges Person']
5643.1.1 by Guilherme Salgado
Allow the join() method to be called on teams so that team admins can propose/add their teams as members of other teams.
136
    >>> login(ANONYMOUS)
2021 by Canonical.com Patch Queue Manager
New vocabulary: ValidTeamMember, to avoid loops when adding new members (also make sure that the API to join teams wont allow these loops). Rewrote system doctests for team membership/participation and this forced me to find what was wrong with my code and fix it. :-) r=spiv
137
3859.1.2 by Guilherme Salgado
Fix https://beta.launchpad.net/launchpad/+bug/88398 and do a bunch of other cleanups on teammembership code.
138
Another API for adding members is ITeam.addMember(), which ensures the given
139
person has a membership entry for that team, regardless of whether the person
140
was already an active/inactive member or has never been a member before.
141
5126.3.8 by Edwin Grubbs
Fixed unit tests.
142
Only the team owner or a launchpad admin can call the addMember method.
143
Other users must use the join method if they are going to add themselves
144
to a team.
145
9105.3.1 by Brad Crittenden
Changed sample data to remove sabdfl.
146
    >>> mark = personset.getByName('mark')
11515.2.6 by Curtis Hovey
Quiet lint.
147
    >>> t3.addMember(salgado, reviewer=mark,
148
    ...     status=TeamMembershipStatus.ADMIN)
9678.8.1 by Guilherme Salgado
Fix teammembership.txt
149
    Traceback (most recent call last):
150
    ...
151
    Unauthorized:...
5126.3.8 by Edwin Grubbs
Fixed unit tests.
152
5825.2.9 by Guilherme Salgado
Few fixes suggested by Brad/Francis
153
    # Log in as the team owner.
6596.1.1 by Guilherme Salgado
Fix https://launchpad.net/bugs/241332
154
    >>> login_person(t3.teamowner)
5825.2.9 by Guilherme Salgado
Few fixes suggested by Brad/Francis
155
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
156
If the member was added (i.e. he wasn't already a member of the team),
157
addMember returns a tuple with True plus the new membership status.
9778.4.3 by Bjorn Tillenius
Person.addMember now returns whether the member got added, or not.
158
3859.1.2 by Guilherme Salgado
Fix https://beta.launchpad.net/launchpad/+bug/88398 and do a bunch of other cleanups on teammembership code.
159
    >>> t3.addMember(
9105.3.1 by Brad Crittenden
Changed sample data to remove sabdfl.
160
    ...     salgado, reviewer=mark, status=TeamMembershipStatus.ADMIN)
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
161
    (True, <DBItem TeamMembershipStatus.ADMIN...)
11692.6.2 by Curtis Hovey
Use deglober to fixing simple glob imports in doctests.
162
    >>> from lp.registry.interfaces.teammembership import ITeamMembershipSet
3859.1.2 by Guilherme Salgado
Fix https://beta.launchpad.net/launchpad/+bug/88398 and do a bunch of other cleanups on teammembership code.
163
    >>> membershipset = getUtility(ITeamMembershipSet)
164
    >>> flush_database_updates()
165
    >>> membership = membershipset.getByPersonAndTeam(salgado, t3)
9105.3.1 by Brad Crittenden
Changed sample data to remove sabdfl.
166
    >>> membership.last_changed_by == mark
3859.1.2 by Guilherme Salgado
Fix https://beta.launchpad.net/launchpad/+bug/88398 and do a bunch of other cleanups on teammembership code.
167
    True
168
    >>> membership.status == TeamMembershipStatus.ADMIN
169
    True
170
    >>> salgado in t3.activemembers
171
    True
172
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
173
addMember returns (True, PROPOSED) also when the member is added as a
174
proposed member.
9778.4.3 by Bjorn Tillenius
Person.addMember now returns whether the member got added, or not.
175
3859.1.2 by Guilherme Salgado
Fix https://beta.launchpad.net/launchpad/+bug/88398 and do a bunch of other cleanups on teammembership code.
176
    >>> marilize = personset.getByName('marilize')
177
    >>> t3.addMember(
9105.3.1 by Brad Crittenden
Changed sample data to remove sabdfl.
178
    ...     marilize, reviewer=mark, status=TeamMembershipStatus.PROPOSED)
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
179
    (True, <DBItem TeamMembershipStatus.PROPOSED...)
3859.1.2 by Guilherme Salgado
Fix https://beta.launchpad.net/launchpad/+bug/88398 and do a bunch of other cleanups on teammembership code.
180
    >>> flush_database_updates()
181
    >>> marilize in t3.activemembers
182
    False
183
9778.4.3 by Bjorn Tillenius
Person.addMember now returns whether the member got added, or not.
184
If addMember is called with a person that is already a member, it
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
185
returns a tuple with False and the current status of the membership.
9778.4.3 by Bjorn Tillenius
Person.addMember now returns whether the member got added, or not.
186
187
    >>> t3.addMember(
188
    ...     salgado, reviewer=mark, status=TeamMembershipStatus.ADMIN)
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
189
    (False, <DBItem TeamMembershipStatus.ADMIN...)
9778.4.3 by Bjorn Tillenius
Person.addMember now returns whether the member got added, or not.
190
    >>> t3.addMember(
191
    ...     marilize, reviewer=mark, status=TeamMembershipStatus.PROPOSED)
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
192
    (False, <DBItem TeamMembershipStatus.PROPOSED...)
9778.4.3 by Bjorn Tillenius
Person.addMember now returns whether the member got added, or not.
193
5825.2.9 by Guilherme Salgado
Few fixes suggested by Brad/Francis
194
As expected, the membership object implements ITeamMembership.
195
14600.2.2 by Curtis Hovey
Moved webapp to lp.services.
196
    >>> from lp.services.webapp.testing import verifyObject
11692.6.2 by Curtis Hovey
Use deglober to fixing simple glob imports in doctests.
197
    >>> from lp.registry.interfaces.teammembership import ITeamMembership
5825.2.9 by Guilherme Salgado
Few fixes suggested by Brad/Francis
198
    >>> verifyObject(ITeamMembership, membership)
199
    True
200
4197.2.8 by Guilherme Salgado
New infrastructure to allow team admins to decline invitations and a first draft of the page in which they'll be able to accept/decline the invitations
201
Note that, by default, the ITeam.addMember() API works slightly different
202
when the added member is a team. In that case the team will actually be
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
203
invited to be a member and one of the team's admins will have to accept the
4197.2.8 by Guilherme Salgado
New infrastructure to allow team admins to decline invitations and a first draft of the page in which they'll be able to accept/decline the invitations
204
invitation before the team is made a member.
4197.2.1 by Guilherme Salgado
Change the way teams are added as members of other teams: it's now necessary to invite the team you want to add as a member and one of that team's admins have to accept the invitation. Also fix some membership notifications to be more specifc (and thus clearer/simpler) for some changes.
205
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
206
    >>> login_person(t1.teamowner)
207
208
    # If the reviewer were also an admin of the team being added,
209
    # the status would go to APPROVED instead of INVITED.
210
    >>> t2.teamowner != t1.teamowner
9778.4.3 by Bjorn Tillenius
Person.addMember now returns whether the member got added, or not.
211
    True
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
212
    >>> t1.addMember(t2, reviewer=t1.teamowner)
213
    (True, <DBItem TeamMembershipStatus.INVITED...)
4197.2.1 by Guilherme Salgado
Change the way teams are added as members of other teams: it's now necessary to invite the team you want to add as a member and one of that team's admins have to accept the invitation. Also fix some membership notifications to be more specifc (and thus clearer/simpler) for some changes.
214
    >>> membership = membershipset.getByPersonAndTeam(t2, t1)
215
    >>> membership.status == TeamMembershipStatus.INVITED
216
    True
4197.2.8 by Guilherme Salgado
New infrastructure to allow team admins to decline invitations and a first draft of the page in which they'll be able to accept/decline the invitations
217
    >>> [m.displayname for m in t1.allmembers]
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
218
    [u'James Blackwell']
4197.2.1 by Guilherme Salgado
Change the way teams are added as members of other teams: it's now necessary to invite the team you want to add as a member and one of that team's admins have to accept the invitation. Also fix some membership notifications to be more specifc (and thus clearer/simpler) for some changes.
219
220
Once one of the t2 admins approve the membership, t2 is shown as a member
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
221
of t1 and the owner of t2 is an indirect member.
4197.2.1 by Guilherme Salgado
Change the way teams are added as members of other teams: it's now necessary to invite the team you want to add as a member and one of that team's admins have to accept the invitation. Also fix some membership notifications to be more specifc (and thus clearer/simpler) for some changes.
222
6596.1.1 by Guilherme Salgado
Fix https://launchpad.net/bugs/241332
223
    >>> login_person(t2.teamowner)
4197.2.17 by Guilherme Salgado
Fix a couple tests that were using the acceptInvitationToBeMemberOf() API incorrectly.
224
    >>> t2.acceptInvitationToBeMemberOf(t1, comment='something')
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
225
    >>> [m.displayname for m in t1.activemembers]
226
    [u'James Blackwell', u't2']
4197.2.1 by Guilherme Salgado
Change the way teams are added as members of other teams: it's now necessary to invite the team you want to add as a member and one of that team's admins have to accept the invitation. Also fix some membership notifications to be more specifc (and thus clearer/simpler) for some changes.
227
    >>> [m.displayname for m in t1.allmembers]
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
228
    [u'James Blackwell', u'No Privileges Person', u't2']
4197.2.1 by Guilherme Salgado
Change the way teams are added as members of other teams: it's now necessary to invite the team you want to add as a member and one of that team's admins have to accept the invitation. Also fix some membership notifications to be more specifc (and thus clearer/simpler) for some changes.
229
4197.2.8 by Guilherme Salgado
New infrastructure to allow team admins to decline invitations and a first draft of the page in which they'll be able to accept/decline the invitations
230
A team admin can also decline an invitation made to his team.
4197.2.1 by Guilherme Salgado
Change the way teams are added as members of other teams: it's now necessary to invite the team you want to add as a member and one of that team's admins have to accept the invitation. Also fix some membership notifications to be more specifc (and thus clearer/simpler) for some changes.
231
9105.3.1 by Brad Crittenden
Changed sample data to remove sabdfl.
232
    >>> t2.addMember(t3, reviewer=mark)
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
233
    (True, <DBItem TeamMembershipStatus.INVITED...)
6596.1.1 by Guilherme Salgado
Fix https://launchpad.net/bugs/241332
234
    >>> login_person(t3.teamowner)
4197.2.17 by Guilherme Salgado
Fix a couple tests that were using the acceptInvitationToBeMemberOf() API incorrectly.
235
    >>> t3.declineInvitationToBeMemberOf(t2, comment='something')
4197.2.8 by Guilherme Salgado
New infrastructure to allow team admins to decline invitations and a first draft of the page in which they'll be able to accept/decline the invitations
236
    >>> membership = membershipset.getByPersonAndTeam(t3, t2)
237
    >>> membership.status == TeamMembershipStatus.INVITATION_DECLINED
238
    True
239
240
In some cases it's necessary to bypass the invitation workflow and directly
241
add teams as members of other teams. We can do that by passing an extra
242
force_team_add=True to addMember(). We'll use that to add t3 as a member of
243
t2, thus making all t3 members be considered members of t2 as well.
244
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
245
    >>> login_person(t2.teamowner)
246
247
    # If the reviewer is also an admin of the team being added,
248
    # force_team_add is unnecessary, and we can't prove that that
249
    # argument works.
250
    >>> t3.teamowner != t2.teamowner
9778.4.3 by Bjorn Tillenius
Person.addMember now returns whether the member got added, or not.
251
    True
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
252
    >>> t2.addMember(t3, reviewer=t2.teamowner, force_team_add=True)
253
    (True, <DBItem TeamMembershipStatus.APPROVED...)
2770.1.47 by Guilherme Salgado
New cronscript to flag expired team memberships and some other cleanups.
254
    >>> [m.displayname for m in t2.allmembers]
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
255
    [u'Guilherme Salgado', u'Jeff Waugh', u'No Privileges Person', u't3']
2021 by Canonical.com Patch Queue Manager
New vocabulary: ValidTeamMember, to avoid loops when adding new members (also make sure that the API to join teams wont allow these loops). Rewrote system doctests for team membership/participation and this forced me to find what was wrong with my code and fix it. :-) r=spiv
256
4197.2.1 by Guilherme Salgado
Change the way teams are added as members of other teams: it's now necessary to invite the team you want to add as a member and one of that team's admins have to accept the invitation. Also fix some membership notifications to be more specifc (and thus clearer/simpler) for some changes.
257
And members of t1 as well, since t2 is a member of t1.
258
259
    >>> [m.displayname for m in t1.allmembers]
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
260
    [u'Guilherme Salgado', u'James Blackwell', u'Jeff Waugh',
261
    u'No Privileges Person', u't2', u't3']
262
263
264
Passing in force_team_add=True is not necessary if the reviewer is the
265
admin of the team being added.
266
267
    >>> login_person(t3.teamowner)
268
    >>> t6.addMember(t3, reviewer=t3.teamowner)
269
    (True, <DBItem TeamMembershipStatus.APPROVED...)
270
    >>> [m.displayname for m in t6.allmembers]
271
    [u'Guilherme Salgado', u'Jeff Waugh', u't3']
4197.2.1 by Guilherme Salgado
Change the way teams are added as members of other teams: it's now necessary to invite the team you want to add as a member and one of that team's admins have to accept the invitation. Also fix some membership notifications to be more specifc (and thus clearer/simpler) for some changes.
272
2021 by Canonical.com Patch Queue Manager
New vocabulary: ValidTeamMember, to avoid loops when adding new members (also make sure that the API to join teams wont allow these loops). Rewrote system doctests for team membership/participation and this forced me to find what was wrong with my code and fix it. :-) r=spiv
273
Can we add t2 as a member of t3? No, we prevent this kind of loop, and users
274
can't do this because our vocabularies won't allow members that would cause
275
loops.
276
10002.2.32 by Edwin Grubbs
Addressed reviewer comments.
277
    >>> foobar = personset.getByEmail('foo.bar@canonical.com')
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
278
    >>> login_person(foobar)
3691.272.9 by Guilherme Salgado
A lot of refactorings, cleanups and improved tests
279
    >>> t3.addMember(t2, reviewer)
2021 by Canonical.com Patch Queue Manager
New vocabulary: ValidTeamMember, to avoid loops when adding new members (also make sure that the API to join teams wont allow these loops). Rewrote system doctests for team membership/participation and this forced me to find what was wrong with my code and fix it. :-) r=spiv
280
    Traceback (most recent call last):
281
    ...
4197.2.1 by Guilherme Salgado
Change the way teams are added as members of other teams: it's now necessary to invite the team you want to add as a member and one of that team's admins have to accept the invitation. Also fix some membership notifications to be more specifc (and thus clearer/simpler) for some changes.
282
    AssertionError: Team 't3' is a member of 't2'. As a consequence, 't2'
283
    can't be added as a member of 't3'
2021 by Canonical.com Patch Queue Manager
New vocabulary: ValidTeamMember, to avoid loops when adding new members (also make sure that the API to join teams wont allow these loops). Rewrote system doctests for team membership/participation and this forced me to find what was wrong with my code and fix it. :-) r=spiv
284
285
Adding t2 as a member of t5 will add all t2 members as t5 members too.
286
4197.2.8 by Guilherme Salgado
New infrastructure to allow team admins to decline invitations and a first draft of the page in which they'll be able to accept/decline the invitations
287
    >>> t5.addMember(t2, reviewer, force_team_add=True)
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
288
    (True, <DBItem TeamMembershipStatus.APPROVED...)
2770.1.47 by Guilherme Salgado
New cronscript to flag expired team memberships and some other cleanups.
289
    >>> [m.displayname for m in t5.allmembers]
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
290
    [u'Guilherme Salgado', u'Jeff Waugh', u'No Privileges Person',
291
    u't2', u't3']
2021 by Canonical.com Patch Queue Manager
New vocabulary: ValidTeamMember, to avoid loops when adding new members (also make sure that the API to join teams wont allow these loops). Rewrote system doctests for team membership/participation and this forced me to find what was wrong with my code and fix it. :-) r=spiv
292
7495.2.1 by Curtis Hovey
Trivial formatting clean up for lint before the real work starts.
293
Adding t5 and t1 as members of t4 will add all t5 and t1 members as t4
2021 by Canonical.com Patch Queue Manager
New vocabulary: ValidTeamMember, to avoid loops when adding new members (also make sure that the API to join teams wont allow these loops). Rewrote system doctests for team membership/participation and this forced me to find what was wrong with my code and fix it. :-) r=spiv
294
members too.
295
4197.2.8 by Guilherme Salgado
New infrastructure to allow team admins to decline invitations and a first draft of the page in which they'll be able to accept/decline the invitations
296
    >>> t4.addMember(t5, reviewer, force_team_add=True)
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
297
    (True, <DBItem TeamMembershipStatus.APPROVED...)
4197.2.8 by Guilherme Salgado
New infrastructure to allow team admins to decline invitations and a first draft of the page in which they'll be able to accept/decline the invitations
298
    >>> t4.addMember(t1, reviewer, force_team_add=True)
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
299
    (True, <DBItem TeamMembershipStatus.APPROVED...)
2770.1.47 by Guilherme Salgado
New cronscript to flag expired team memberships and some other cleanups.
300
    >>> [m.displayname for m in t4.allmembers]
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
301
    [u'Guilherme Salgado', u'James Blackwell', u'Jeff Waugh',
302
    u'No Privileges Person', u't1', u't2', u't3', u't5']
2021 by Canonical.com Patch Queue Manager
New vocabulary: ValidTeamMember, to avoid loops when adding new members (also make sure that the API to join teams wont allow these loops). Rewrote system doctests for team membership/participation and this forced me to find what was wrong with my code and fix it. :-) r=spiv
303
3691.326.9 by Guilherme Salgado
On a team page, show the path from the logged in user to that team when the user participates in that team. (fix https://launchpad.net/launchpad/+bug/48342)
304
    >>> flush_database_updates()
305
2021 by Canonical.com Patch Queue Manager
New vocabulary: ValidTeamMember, to avoid loops when adding new members (also make sure that the API to join teams wont allow these loops). Rewrote system doctests for team membership/participation and this forced me to find what was wrong with my code and fix it. :-) r=spiv
306
After adding all this mess, this is what we have:
307
3691.272.12 by Guilherme Salgado
Some cleaning up on teammembership.txt. It was creating lots of teams without using the correct API and thus some changes I did broke lots of tests.
308
(This table doesn't include the team owner (Foo Bar), but since he's the
309
owner he's also a direct member of all teams)
310
=============================================================
311
||  Team      ||  Direct Members   ||  Indirect Members    ||
312
=============================================================
313
||   T1       ||  T2               ||  T3, Salgado         ||
314
||   T2       ||  T3               ||  Salgado             ||
315
||   T3       ||  Salgado          ||                      ||
316
||   T4       ||  T5, T1, Salgado  ||  T2, T3              ||
317
||   T5       ||  T2               ||  T3, Salgado         ||
2021 by Canonical.com Patch Queue Manager
New vocabulary: ValidTeamMember, to avoid loops when adding new members (also make sure that the API to join teams wont allow these loops). Rewrote system doctests for team membership/participation and this forced me to find what was wrong with my code and fix it. :-) r=spiv
318
319
3691.326.9 by Guilherme Salgado
On a team page, show the path from the logged in user to that team when the user participates in that team. (fix https://launchpad.net/launchpad/+bug/48342)
320
We can use IPerson.findPathToTeam() to check some of the relationships drawn
4073.5.9 by Guilherme Salgado
Last changes requested by Barry
321
above, either from a person to a given team ...
3691.326.9 by Guilherme Salgado
On a team page, show the path from the logged in user to that team when the user participates in that team. (fix https://launchpad.net/launchpad/+bug/48342)
322
323
    >>> [team.name for team in salgado.findPathToTeam(t1)]
324
    [u't3', u't2', u't1']
325
    >>> [team.name for team in salgado.findPathToTeam(t5)]
326
    [u't3', u't2', u't5']
327
    >>> [team.name for team in salgado.findPathToTeam(t3)]
328
    [u't3']
329
4073.5.8 by Guilherme Salgado
Some changes Barry asked for in his review
330
... or from a team to another one:
331
4073.5.5 by Guilherme Salgado
Update IPerson.findPathToTeam's docstring to not say that it can't be used for finding the path from a team to another team and add a test to prove that now it CAN.
332
    >>> [team.name for team in t3.findPathToTeam(t4)]
333
    [u't2', u't1', u't4']
334
2021 by Canonical.com Patch Queue Manager
New vocabulary: ValidTeamMember, to avoid loops when adding new members (also make sure that the API to join teams wont allow these loops). Rewrote system doctests for team membership/participation and this forced me to find what was wrong with my code and fix it. :-) r=spiv
335
t2 can't use its leave() method to leave t5 because it's a team and teams
336
take no actions. One of t5 administrators have to go and remove t2 from t5
337
if t2 shouldn't be a member of t5 anymore.
338
6596.1.1 by Guilherme Salgado
Fix https://launchpad.net/bugs/241332
339
    >>> login_person(t5.teamowner)
3691.272.9 by Guilherme Salgado
A lot of refactorings, cleanups and improved tests
340
    >>> t5.setMembershipData(t2, TeamMembershipStatus.DEACTIVATED, reviewer)
2021 by Canonical.com Patch Queue Manager
New vocabulary: ValidTeamMember, to avoid loops when adding new members (also make sure that the API to join teams wont allow these loops). Rewrote system doctests for team membership/participation and this forced me to find what was wrong with my code and fix it. :-) r=spiv
341
342
Removing t2 from t5 will have implications in all teams that have t5 as a
343
(direct or indirect) member.
344
345
t5 had only one member and two other indirect members. Now that t2 is not its
3691.272.12 by Guilherme Salgado
Some cleaning up on teammembership.txt. It was creating lots of teams without using the correct API and thus some changes I did broke lots of tests.
346
member anymore, it doesn't have any members apart from its owner.
2178 by Canonical.com Patch Queue Manager
[trivial] add branch pages and portlets
347
2770.1.47 by Guilherme Salgado
New cronscript to flag expired team memberships and some other cleanups.
348
    >>> [m.displayname for m in t5.allmembers]
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
349
    [u'No Privileges Person']
2021 by Canonical.com Patch Queue Manager
New vocabulary: ValidTeamMember, to avoid loops when adding new members (also make sure that the API to join teams wont allow these loops). Rewrote system doctests for team membership/participation and this forced me to find what was wrong with my code and fix it. :-) r=spiv
350
351
Removing t2 from t5 won't remove it from t4, because t2 is also a member of
352
t1, which is a member of t4.
4197.2.1 by Guilherme Salgado
Change the way teams are added as members of other teams: it's now necessary to invite the team you want to add as a member and one of that team's admins have to accept the invitation. Also fix some membership notifications to be more specifc (and thus clearer/simpler) for some changes.
353
2770.1.47 by Guilherme Salgado
New cronscript to flag expired team memberships and some other cleanups.
354
    >>> [m.displayname for m in t4.allmembers]
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
355
    [u'Guilherme Salgado', u'James Blackwell', u'Jeff Waugh',
356
    u'No Privileges Person', u't1', u't2', u't3', u't5']
2021 by Canonical.com Patch Queue Manager
New vocabulary: ValidTeamMember, to avoid loops when adding new members (also make sure that the API to join teams wont allow these loops). Rewrote system doctests for team membership/participation and this forced me to find what was wrong with my code and fix it. :-) r=spiv
357
358
Nothing changes in t1, because t5 wasn't one of its members.
4197.2.1 by Guilherme Salgado
Change the way teams are added as members of other teams: it's now necessary to invite the team you want to add as a member and one of that team's admins have to accept the invitation. Also fix some membership notifications to be more specifc (and thus clearer/simpler) for some changes.
359
2770.1.47 by Guilherme Salgado
New cronscript to flag expired team memberships and some other cleanups.
360
    >>> [m.displayname for m in t1.allmembers]
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
361
    [u'Guilherme Salgado', u'James Blackwell', u'Jeff Waugh',
362
    u'No Privileges Person', u't2', u't3']
2021 by Canonical.com Patch Queue Manager
New vocabulary: ValidTeamMember, to avoid loops when adding new members (also make sure that the API to join teams wont allow these loops). Rewrote system doctests for team membership/participation and this forced me to find what was wrong with my code and fix it. :-) r=spiv
363
3691.272.12 by Guilherme Salgado
Some cleaning up on teammembership.txt. It was creating lots of teams without using the correct API and thus some changes I did broke lots of tests.
364
If 'Guilherme Salgado' decides to leave t3, he'll also be removed from t1
365
and t2, but not from t4, because he's a direct member of t4.
4197.2.1 by Guilherme Salgado
Change the way teams are added as members of other teams: it's now necessary to invite the team you want to add as a member and one of that team's admins have to accept the invitation. Also fix some membership notifications to be more specifc (and thus clearer/simpler) for some changes.
366
6596.1.1 by Guilherme Salgado
Fix https://launchpad.net/bugs/241332
367
    >>> login_person(salgado)
3691.272.12 by Guilherme Salgado
Some cleaning up on teammembership.txt. It was creating lots of teams without using the correct API and thus some changes I did broke lots of tests.
368
    >>> salgado.leave(t3)
369
    >>> salgado in t1.allmembers
370
    False
7495.2.1 by Curtis Hovey
Trivial formatting clean up for lint before the real work starts.
371
    >>> salgado in t2.allmembers
3691.272.12 by Guilherme Salgado
Some cleaning up on teammembership.txt. It was creating lots of teams without using the correct API and thus some changes I did broke lots of tests.
372
    False
373
    >>> salgado in t4.allmembers
2021 by Canonical.com Patch Queue Manager
New vocabulary: ValidTeamMember, to avoid loops when adding new members (also make sure that the API to join teams wont allow these loops). Rewrote system doctests for team membership/participation and this forced me to find what was wrong with my code and fix it. :-) r=spiv
374
    True
375
376
3691.272.12 by Guilherme Salgado
Some cleaning up on teammembership.txt. It was creating lots of teams without using the correct API and thus some changes I did broke lots of tests.
377
This is what we have now, after removing t2 from t5 and Salgado from t3.
2021 by Canonical.com Patch Queue Manager
New vocabulary: ValidTeamMember, to avoid loops when adding new members (also make sure that the API to join teams wont allow these loops). Rewrote system doctests for team membership/participation and this forced me to find what was wrong with my code and fix it. :-) r=spiv
378
3691.272.12 by Guilherme Salgado
Some cleaning up on teammembership.txt. It was creating lots of teams without using the correct API and thus some changes I did broke lots of tests.
379
(This table doesn't include the team owner (Foo Bar), but since he's the
380
owner he's also a direct member of all teams)
2021 by Canonical.com Patch Queue Manager
New vocabulary: ValidTeamMember, to avoid loops when adding new members (also make sure that the API to join teams wont allow these loops). Rewrote system doctests for team membership/participation and this forced me to find what was wrong with my code and fix it. :-) r=spiv
381
=============================================================
382
||  Team      ||  Members          ||  Indirect Members    ||
383
=============================================================
3691.272.12 by Guilherme Salgado
Some cleaning up on teammembership.txt. It was creating lots of teams without using the correct API and thus some changes I did broke lots of tests.
384
||   T1       ||  T2               ||  T3                  ||
385
||   T2       ||  T3               ||                      ||
386
||   T3       ||                   ||                      ||
387
||   T4       ||  T5, T1, Salgado  ||  T2, T3              ||
2021 by Canonical.com Patch Queue Manager
New vocabulary: ValidTeamMember, to avoid loops when adding new members (also make sure that the API to join teams wont allow these loops). Rewrote system doctests for team membership/participation and this forced me to find what was wrong with my code and fix it. :-) r=spiv
388
||   T5       ||                   ||                      ||
389
390
391
Now, if I add a new member to t3, will it be added to t2, t1 and t4 as well?
392
Let's see...
393
3691.272.12 by Guilherme Salgado
Some cleaning up on teammembership.txt. It was creating lots of teams without using the correct API and thus some changes I did broke lots of tests.
394
    >>> cprov = getUtility(IPersonSet).getByName('cprov')
395
    >>> t3.addMember(cprov, reviewer)
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
396
    (True, <DBItem TeamMembershipStatus.APPROVED...)
2770.1.47 by Guilherme Salgado
New cronscript to flag expired team memberships and some other cleanups.
397
    >>> [m.displayname for m in t3.allmembers]
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
398
    [...u'Celso Providelo'...
2021 by Canonical.com Patch Queue Manager
New vocabulary: ValidTeamMember, to avoid loops when adding new members (also make sure that the API to join teams wont allow these loops). Rewrote system doctests for team membership/participation and this forced me to find what was wrong with my code and fix it. :-) r=spiv
399
2770.1.47 by Guilherme Salgado
New cronscript to flag expired team memberships and some other cleanups.
400
    >>> [m.displayname for m in t2.allmembers]
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
401
    [...u'Celso Providelo'...
2021 by Canonical.com Patch Queue Manager
New vocabulary: ValidTeamMember, to avoid loops when adding new members (also make sure that the API to join teams wont allow these loops). Rewrote system doctests for team membership/participation and this forced me to find what was wrong with my code and fix it. :-) r=spiv
402
2770.1.47 by Guilherme Salgado
New cronscript to flag expired team memberships and some other cleanups.
403
    >>> [m.displayname for m in t1.allmembers]
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
404
    [...u'Celso Providelo'...
2021 by Canonical.com Patch Queue Manager
New vocabulary: ValidTeamMember, to avoid loops when adding new members (also make sure that the API to join teams wont allow these loops). Rewrote system doctests for team membership/participation and this forced me to find what was wrong with my code and fix it. :-) r=spiv
405
2770.1.47 by Guilherme Salgado
New cronscript to flag expired team memberships and some other cleanups.
406
    >>> [m.displayname for m in t4.allmembers]
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
407
    [...u'Celso Providelo'...
408
409
410
It's important to note that even if the owner leaves the team, which
411
removes his membership, he will still be the team's owner and retain his
412
rights over it. This ensures we'll never have teams which can't be
13139.2.2 by Robert Collins
Update doc/membership.txt to show how owners are not members of teams at all once they leave - but they can still administrate the team enough to put themselves back in it.
413
managed. This does not imply that the owner will be a member of the team.
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
414
415
    >>> login_person(t5.teamowner)
416
    >>> t5.teamowner.leave(t5)
2021 by Canonical.com Patch Queue Manager
New vocabulary: ValidTeamMember, to avoid loops when adding new members (also make sure that the API to join teams wont allow these loops). Rewrote system doctests for team membership/participation and this forced me to find what was wrong with my code and fix it. :-) r=spiv
417
    >>> flush_database_updates()
3691.272.13 by Guilherme Salgado
Some small test fixes
418
    >>> [m.displayname for m in t5.allmembers]
419
    []
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
420
    >>> t5.teamowner.inTeam(t5)
13139.2.2 by Robert Collins
Update doc/membership.txt to show how owners are not members of teams at all once they leave - but they can still administrate the team enough to put themselves back in it.
421
    False
422
423
The team owner can make themselves a member again even if the team is
424
restricted:
425
426
    >>> t5.teamowner.join(t5, requester=t5.teamowner)
427
    >>> flush_database_updates()
428
    >>> t5.teamowner in t5.allmembers
429
    True
430
    >>> t5.teamowner.inTeam(t5)
431
    True
432
433
And escalate their privileges back to administrator:
434
435
    >>> membership = membershipset.getByPersonAndTeam(t5.teamowner, t5)
436
    >>> membership.setStatus(TeamMembershipStatus.ADMIN, t5.teamowner)
437
    True
2770.1.47 by Guilherme Salgado
New cronscript to flag expired team memberships and some other cleanups.
438
11515.2.6 by Curtis Hovey
Quiet lint.
439
Changing membership data
440
------------------------
3691.326.10 by Guilherme Salgado
Fix https://beta.launchpad.net/launchpad/+bug/74569
441
3691.9.81 by Guilherme Salgado
Move the logic to prevent team admins from changing the expiration date of their own memberships from browser code to database code
442
The only bits of a TeamMembership that can be changed are its status, expiry
443
date, reviewer[comment] and the date the user joined. From these ones, the
444
most interesting ones are the status and expiry date, which can only be set
445
through a specific API (setStatus() and setExpirationDate()) protected with
446
the launchpad.Edit permission. Also, since we don't want team admins to change
447
the expiry date of their own memberships, the setExpirationDate() method does
448
an extra check to ensure that doesn't happen.
3691.326.10 by Guilherme Salgado
Fix https://beta.launchpad.net/launchpad/+bug/74569
449
450
    # Foo Bar is a launchpad admin, but even so he can't change a membership's
3691.9.81 by Guilherme Salgado
Move the logic to prevent team admins from changing the expiration date of their own memberships from browser code to database code
451
    # status/expiry-date by hand.
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
452
    >>> login_person(foobar)
11347.13.28 by j.c.sackett
Missed some myactivememberships callsites.
453
    >>> membership = foobar.team_memberships[0]
3691.326.10 by Guilherme Salgado
Fix https://beta.launchpad.net/launchpad/+bug/74569
454
    >>> membership.status = None
455
    Traceback (most recent call last):
456
    ...
457
    ForbiddenAttribute: ...
458
3691.9.81 by Guilherme Salgado
Move the logic to prevent team admins from changing the expiration date of their own memberships from browser code to database code
459
    >>> membership.dateexpires = None
460
    Traceback (most recent call last):
461
    ...
462
    ForbiddenAttribute: ...
463
3691.326.10 by Guilherme Salgado
Fix https://beta.launchpad.net/launchpad/+bug/74569
464
Foo Bar asked to join Warty Security Team on 2006-01-26 and he's been doing
465
good work, so we'll approve his membership.
466
467
    >>> warty_team = getUtility(IPersonSet).getByName('name20')
468
    >>> membership = membershipset.getByPersonAndTeam(foobar, warty_team)
469
    >>> print membership.status.title
470
    Proposed
5825.2.3 by Guilherme Salgado
Fix the damn thing
471
    >>> print membership.date_created.strftime("%Y-%m-%d")
3691.326.10 by Guilherme Salgado
Fix https://beta.launchpad.net/launchpad/+bug/74569
472
    2006-01-26
5825.2.3 by Guilherme Salgado
Fix the damn thing
473
    >>> print membership.datejoined
474
    None
3691.326.10 by Guilherme Salgado
Fix https://beta.launchpad.net/launchpad/+bug/74569
475
476
When we approve his membership, the datejoined will contain the date that it
9778.4.2 by Bjorn Tillenius
Make TeamMembership.setStatus return whether the status actually changed.
477
was approved. It returns True to indicate that the status was changed.
3691.326.10 by Guilherme Salgado
Fix https://beta.launchpad.net/launchpad/+bug/74569
478
5825.2.3 by Guilherme Salgado
Fix the damn thing
479
    >>> membership.setStatus(TeamMembershipStatus.APPROVED, foobar)
9778.4.2 by Bjorn Tillenius
Make TeamMembership.setStatus return whether the status actually changed.
480
    True
3691.326.10 by Guilherme Salgado
Fix https://beta.launchpad.net/launchpad/+bug/74569
481
    >>> print membership.status.title
482
    Approved
483
    >>> utc_now = datetime.now(pytz.timezone('UTC'))
484
    >>> membership.datejoined.date() == utc_now.date()
7495.2.1 by Curtis Hovey
Trivial formatting clean up for lint before the real work starts.
485
    True
3691.326.10 by Guilherme Salgado
Fix https://beta.launchpad.net/launchpad/+bug/74569
486
9778.4.2 by Bjorn Tillenius
Make TeamMembership.setStatus return whether the status actually changed.
487
If setStatus is called again with the same status, it returns False,
488
to indicate that the status didn't change.
489
490
    >>> membership.setStatus(TeamMembershipStatus.APPROVED, foobar)
491
    False
492
5825.2.3 by Guilherme Salgado
Fix the damn thing
493
Other status updates won't change datejoined, regardless of the status.
494
That's because datejoined stores the date in which the membership was first
495
made active.
3691.326.10 by Guilherme Salgado
Fix https://beta.launchpad.net/launchpad/+bug/74569
496
497
    >>> buildd_admins = getUtility(IPersonSet).getByName(
498
    ...     'launchpad-buildd-admins')
3691.9.81 by Guilherme Salgado
Move the logic to prevent team admins from changing the expiration date of their own memberships from browser code to database code
499
    >>> foobar_on_buildd = membershipset.getByPersonAndTeam(
500
    ...     foobar, buildd_admins)
501
    >>> print foobar_on_buildd.status.title
3691.326.10 by Guilherme Salgado
Fix https://beta.launchpad.net/launchpad/+bug/74569
502
    Administrator
5825.2.3 by Guilherme Salgado
Fix the damn thing
503
    >>> foobar_on_buildd.datejoined <= utc_now
504
    True
3691.326.10 by Guilherme Salgado
Fix https://beta.launchpad.net/launchpad/+bug/74569
505
3691.9.81 by Guilherme Salgado
Move the logic to prevent team admins from changing the expiration date of their own memberships from browser code to database code
506
    >>> foobar_on_buildd.setStatus(
5825.2.3 by Guilherme Salgado
Fix the damn thing
507
    ...     TeamMembershipStatus.DEACTIVATED, foobar)
9778.4.2 by Bjorn Tillenius
Make TeamMembership.setStatus return whether the status actually changed.
508
    True
3691.9.81 by Guilherme Salgado
Move the logic to prevent team admins from changing the expiration date of their own memberships from browser code to database code
509
    >>> print foobar_on_buildd.status.title
3691.326.10 by Guilherme Salgado
Fix https://beta.launchpad.net/launchpad/+bug/74569
510
    Deactivated
5825.2.3 by Guilherme Salgado
Fix the damn thing
511
    >>> foobar_on_buildd.datejoined <= utc_now
512
    True
3691.326.11 by Guilherme Salgado
One last test asked by kiko
513
3691.9.81 by Guilherme Salgado
Move the logic to prevent team admins from changing the expiration date of their own memberships from browser code to database code
514
    >>> foobar_on_buildd.setStatus(
5825.2.3 by Guilherme Salgado
Fix the damn thing
515
    ...     TeamMembershipStatus.APPROVED, foobar)
9778.4.2 by Bjorn Tillenius
Make TeamMembership.setStatus return whether the status actually changed.
516
    True
3691.9.81 by Guilherme Salgado
Move the logic to prevent team admins from changing the expiration date of their own memberships from browser code to database code
517
    >>> print foobar_on_buildd.status.title
3691.326.11 by Guilherme Salgado
One last test asked by kiko
518
    Approved
5825.2.3 by Guilherme Salgado
Fix the damn thing
519
    >>> foobar_on_buildd.datejoined <= utc_now
520
    True
3691.326.11 by Guilherme Salgado
One last test asked by kiko
521
3691.9.81 by Guilherme Salgado
Move the logic to prevent team admins from changing the expiration date of their own memberships from browser code to database code
522
When changing the expiry date we need to provide a date in the future and,
523
as mentioned above, the change can't be done by a team admin to his own
524
membership.
525
526
We're still logged in as Foo Bar, which is a launchpad admin and thus
527
can change any membership's expiry date (even his own), as long as
528
the new expiry date is not in the past.
529
530
    >>> foobar == foobar_on_buildd.team.teamowner
531
    True
532
    >>> foobar_on_buildd.canChangeExpirationDate(foobar)
533
    True
534
    >>> one_day_ago = datetime.now(pytz.timezone('UTC')) - timedelta(days=1)
535
    >>> tomorrow = datetime.now(pytz.timezone('UTC')) + timedelta(days=1)
536
    >>> foobar_on_buildd.setExpirationDate(one_day_ago, foobar)
537
    Traceback (most recent call last):
538
    ...
539
    AssertionError: ...
540
    >>> foobar_on_buildd.setExpirationDate(tomorrow, foobar)
541
14443.3.1 by William Grant
Let team admins change their own expiration dates.
542
Team owners and admins can also renew any memberships of the team they
543
own or administer.
3691.9.81 by Guilherme Salgado
Move the logic to prevent team admins from changing the expiration date of their own memberships from browser code to database code
544
545
    >>> landscape = getUtility(IPersonSet).getByName(
546
    ...     'landscape-developers')
547
    >>> sampleperson = getUtility(IPersonSet).getByName(
548
    ...     'name12')
549
    >>> sampleperson_on_landscape = membershipset.getByPersonAndTeam(
550
    ...     sampleperson, landscape)
551
    >>> landscape.teamowner.name
552
    u'name12'
553
    >>> sampleperson_on_landscape.canChangeExpirationDate(sampleperson)
554
    True
555
    >>> sampleperson_on_landscape.setExpirationDate(tomorrow, sampleperson)
556
557
    >>> cprov_on_buildd = membershipset.getByPersonAndTeam(
558
    ...     cprov, buildd_admins)
559
    >>> buildd_admins.teamowner.name
560
    u'name16'
561
    >>> print cprov_on_buildd.status.title
562
    Administrator
563
    >>> foobar_on_buildd.canChangeExpirationDate(cprov)
564
    True
565
    >>> foobar_on_buildd.setExpirationDate(tomorrow, cprov)
566
7495.2.1 by Curtis Hovey
Trivial formatting clean up for lint before the real work starts.
567
11515.2.6 by Curtis Hovey
Quiet lint.
568
Flagging expired memberships
569
----------------------------
2770.1.47 by Guilherme Salgado
New cronscript to flag expired team memberships and some other cleanups.
570
571
The expired memberships are flagged by a cronscript that runs daily. This
572
script simply flags all active memberships which reached their expiry date as
573
expired.
574
3691.272.22 by Guilherme Salgado
Fix https://launchpad.net/launchpad/+bug/70518: Notify team members when their membership is going to expire
575
To find out which memberships are already expired, we use
576
TeamMembershipSet.getMembershipsToExpire(). As you can see, we don't have any
577
membership to expire right now.
578
579
    >>> [(membership.person.name, membership.team.name)
580
    ...  for membership in membershipset.getMembershipsToExpire()]
581
    []
582
583
Let's change the expiry date of an active membership, so we have something
3691.9.81 by Guilherme Salgado
Move the logic to prevent team admins from changing the expiration date of their own memberships from browser code to database code
584
that should be expired. Since we can't set an expiry date in the past for a
585
membership using setExpirationDate(), we'll have to cheat and access the
586
dateexpires attribute directly.
3691.272.22 by Guilherme Salgado
Fix https://launchpad.net/launchpad/+bug/70518: Notify team members when their membership is going to expire
587
588
    >>> foobar_on_admins = membershipset.getByPersonAndTeam(
589
    ...     personset.getByName('name16'), personset.getByName('admins'))
590
    >>> foobar_on_admins.dateexpires is None
591
    True
592
    >>> foobar_on_admins.status.title
593
    'Administrator'
2770.1.47 by Guilherme Salgado
New cronscript to flag expired team memberships and some other cleanups.
594
    >>> login('foo.bar@canonical.com')
3691.9.81 by Guilherme Salgado
Move the logic to prevent team admins from changing the expiration date of their own memberships from browser code to database code
595
    >>> removeSecurityProxy(foobar_on_admins).dateexpires = one_day_ago
3691.272.22 by Guilherme Salgado
Fix https://launchpad.net/launchpad/+bug/70518: Notify team members when their membership is going to expire
596
    >>> flush_database_updates()
597
598
    >>> [(membership.person.name, membership.team.name)
599
    ...  for membership in membershipset.getMembershipsToExpire()]
600
    [(u'name16', u'admins')]
601
602
And here we change the expiry date of a membership that's already
603
deactivated, so it should not be flagged as expired.
604
605
    >>> sp_on_ubuntu_translators = membershipset.getByPersonAndTeam(
3691.272.13 by Guilherme Salgado
Some small test fixes
606
    ...     personset.getByName('name12'),
607
    ...     personset.getByName('ubuntu-translators'))
2770.1.47 by Guilherme Salgado
New cronscript to flag expired team memberships and some other cleanups.
608
    >>> sp_on_ubuntu_translators.dateexpires is None
609
    True
610
    >>> sp_on_ubuntu_translators.status.title
611
    'Deactivated'
7495.2.1 by Curtis Hovey
Trivial formatting clean up for lint before the real work starts.
612
    >>> removeSecurityProxy(
613
    ...     sp_on_ubuntu_translators).dateexpires = one_day_ago
3691.272.22 by Guilherme Salgado
Fix https://launchpad.net/launchpad/+bug/70518: Notify team members when their membership is going to expire
614
    >>> flush_database_updates()
615
616
    >>> [(membership.person.name, membership.team.name)
617
    ...  for membership in membershipset.getMembershipsToExpire()]
618
    [(u'name16', u'admins')]
619
620
The getMembershipsToExpire() method also accepts an optional 'when' argument.
621
When that argument is provided, we get the memberships that are supposed to
622
expire on that date or before.
623
9105.3.1 by Brad Crittenden
Changed sample data to remove sabdfl.
624
    >>> mark_on_ubuntu_team = membershipset.getByPersonAndTeam(
625
    ...     personset.getByName('mark'),
3691.272.22 by Guilherme Salgado
Fix https://launchpad.net/launchpad/+bug/70518: Notify team members when their membership is going to expire
626
    ...     personset.getByName('ubuntu-team'))
9105.3.1 by Brad Crittenden
Changed sample data to remove sabdfl.
627
    >>> mark_on_ubuntu_team.dateexpires is not None
3691.272.22 by Guilherme Salgado
Fix https://launchpad.net/launchpad/+bug/70518: Notify team members when their membership is going to expire
628
    True
9105.3.1 by Brad Crittenden
Changed sample data to remove sabdfl.
629
    >>> mark_on_ubuntu_team.status.title
3691.272.22 by Guilherme Salgado
Fix https://launchpad.net/launchpad/+bug/70518: Notify team members when their membership is going to expire
630
    'Administrator'
631
9105.3.1 by Brad Crittenden
Changed sample data to remove sabdfl.
632
    >>> when = mark_on_ubuntu_team.dateexpires + timedelta(days=1)
3691.272.22 by Guilherme Salgado
Fix https://launchpad.net/launchpad/+bug/70518: Notify team members when their membership is going to expire
633
    >>> [(membership.person.name, membership.team.name)
634
    ...  for membership in membershipset.getMembershipsToExpire(when=when)]
9105.3.1 by Brad Crittenden
Changed sample data to remove sabdfl.
635
    [(u'mark', u'ubuntu-team'), (u'name16', u'admins'),
3691.9.81 by Guilherme Salgado
Move the logic to prevent team admins from changing the expiration date of their own memberships from browser code to database code
636
     (u'ubuntu-team', u'guadamen'), (u'name16', u'launchpad-buildd-admins'),
637
     (u'name12', u'landscape-developers')]
2770.1.47 by Guilherme Salgado
New cronscript to flag expired team memberships and some other cleanups.
638
11128.7.1 by Edwin Grubbs
Do not send membership expiration emails for teams with automatic membership renewal.
639
640
The getMembershipsToExpire() method also accepts an optional
641
'exclude_autorewals' argument.  When that argument is provided,
642
memberships in teams that are configured to renew the membership
643
automatically will be excluded. In that case, there is no reason to send
644
a warning email several days before the expiration. The user will just
645
be notified that the membership has already been automatically renewed
646
on the expiration day.
647
648
    >>> autorenewal_team = factory.makeTeam(name="autorenewal-team")
649
    >>> autorenewal_team.renewal_policy = (
650
    ...     TeamMembershipRenewalPolicy.AUTOMATIC)
651
    >>> autorenewal_team.defaultrenewalperiod = 73
652
    >>> otto = factory.makePerson(name='otto')
653
    >>> ignore = autorenewal_team.addMember(otto, salgado)
11347.13.28 by j.c.sackett
Missed some myactivememberships callsites.
654
    >>> otto_membership = otto.team_memberships[0]
11128.7.1 by Edwin Grubbs
Do not send membership expiration emails for teams with automatic membership renewal.
655
    >>> otto_membership.setExpirationDate(utc_now, salgado)
656
    >>> original_otto_date_expires = otto_membership.dateexpires
657
658
    >>> do_not_warn = factory.makePerson(name='do-not-warn')
659
    >>> ignore = autorenewal_team.addMember(do_not_warn, salgado)
11347.13.28 by j.c.sackett
Missed some myactivememberships callsites.
660
    >>> do_not_warn.team_memberships[0].setExpirationDate(
11128.7.1 by Edwin Grubbs
Do not send membership expiration emails for teams with automatic membership renewal.
661
    ...     tomorrow, salgado)
662
663
    # Not excluding teams with automatic renewals.
664
    >>> [(membership.person.name, membership.team.name)
665
    ...     for membership in membershipset.getMembershipsToExpire()
666
    ...     if membership.team.name == 'autorenewal-team']
667
    [(u'otto', u'autorenewal-team')]
668
669
    # Excluding teams with automatic renewals.
670
    >>> [(membership.person.name, membership.team.name)
671
    ...     for membership in membershipset.getMembershipsToExpire(
672
    ...         exclude_autorenewals=True)
673
    ...     if membership.team.name == 'autorenewal-team']
674
    []
675
2770.1.47 by Guilherme Salgado
New cronscript to flag expired team memberships and some other cleanups.
676
Now we commit the changes and run the cronscript.
3691.57.42 by Stuart Bishop
More post review feedback updates
677
XXX: flush_database_updates() shouldn't be needed. This seems to be
678
Bug 3989 -- StuarBishop 20060713
2770.1.47 by Guilherme Salgado
New cronscript to flag expired team memberships and some other cleanups.
679
3691.57.9 by Stuart Bishop
Fix tests
680
    >>> flush_database_updates()
2770.1.47 by Guilherme Salgado
New cronscript to flag expired team memberships and some other cleanups.
681
    >>> transaction.commit()
3691.57.42 by Stuart Bishop
More post review feedback updates
682
2770.1.47 by Guilherme Salgado
New cronscript to flag expired team memberships and some other cleanups.
683
    >>> import subprocess
684
    >>> process = subprocess.Popen(
11128.7.1 by Edwin Grubbs
Do not send membership expiration emails for teams with automatic membership renewal.
685
    ...     'cronscripts/flag-expired-memberships.py -v', shell=True,
7495.2.1 by Curtis Hovey
Trivial formatting clean up for lint before the real work starts.
686
    ...     stdin=subprocess.PIPE, stdout=subprocess.PIPE,
2770.1.47 by Guilherme Salgado
New cronscript to flag expired team memberships and some other cleanups.
687
    ...     stderr=subprocess.PIPE)
688
    >>> (out, err) = process.communicate()
11128.7.1 by Edwin Grubbs
Do not send membership expiration emails for teams with automatic membership renewal.
689
    >>> print out
690
691
A warning email should not have been sent to otto because it was renewed
692
inside the cronscript, and it should not be sent to do_not_warn, because
693
teams with automatic renewal don't need warnings.
694
695
    >>> print '\n'.join(
696
    ...     line for line in err.split('\n') if 'INFO' not in line)
11300.2.6 by Stuart Bishop
Fix teammembership.txt test
697
    DEBUG   ...
11128.7.1 by Edwin Grubbs
Do not send membership expiration emails for teams with automatic membership renewal.
698
    DEBUG   Sent warning email to name16 in launchpad-buildd-admins team.
699
    DEBUG   Sent warning email to name12 in landscape-developers team.
11515.2.6 by Curtis Hovey
Quiet lint.
700
    DEBUG   Removing lock file: ...launchpad-flag-expired-memberships.lock
2770.1.47 by Guilherme Salgado
New cronscript to flag expired team memberships and some other cleanups.
701
    >>> process.returncode
702
    0
5821.6.5 by James Henstridge
Fix doc/teammembership.txt test.
703
    >>> transaction.abort()
2770.1.47 by Guilherme Salgado
New cronscript to flag expired team memberships and some other cleanups.
704
705
Here we can see that the membership that was active is now flagged as expired
706
while the one that was inactive hasn't changed.
707
3691.272.22 by Guilherme Salgado
Fix https://launchpad.net/launchpad/+bug/70518: Notify team members when their membership is going to expire
708
    >>> foobar_on_admins = membershipset.getByPersonAndTeam(
3691.272.13 by Guilherme Salgado
Some small test fixes
709
    ...     personset.getByName('name16'), personset.getByName('admins'))
2770.1.47 by Guilherme Salgado
New cronscript to flag expired team memberships and some other cleanups.
710
    >>> foobar_on_admins.status.title
711
    'Expired'
5825.2.3 by Guilherme Salgado
Fix the damn thing
712
    >>> foobar_on_admins.last_changed_by.name
4621.5.26 by Curtis Hovey
Renamed launchpad-janitor to janitor per review.
713
    u'janitor'
2770.1.47 by Guilherme Salgado
New cronscript to flag expired team memberships and some other cleanups.
714
3691.272.22 by Guilherme Salgado
Fix https://launchpad.net/launchpad/+bug/70518: Notify team members when their membership is going to expire
715
    >>> sp_on_ubuntu_translators = membershipset.getByPersonAndTeam(
3691.272.13 by Guilherme Salgado
Some small test fixes
716
    ...     personset.getByName('name12'),
717
    ...     personset.getByName('ubuntu-translators'))
2770.1.47 by Guilherme Salgado
New cronscript to flag expired team memberships and some other cleanups.
718
    >>> sp_on_ubuntu_translators.status.title
719
    'Deactivated'
720
11128.7.1 by Edwin Grubbs
Do not send membership expiration emails for teams with automatic membership renewal.
721
    >>> otto_on_autorenewal_team = membershipset.getByPersonAndTeam(
722
    ...     otto, autorenewal_team)
723
    >>> otto_on_autorenewal_team.status.title
724
    'Approved'
725
    >>> otto_on_autorenewal_team.dateexpires - original_otto_date_expires
726
    datetime.timedelta(73)
727
2770.1.48 by Guilherme Salgado
merge from rf
728
11515.2.6 by Curtis Hovey
Quiet lint.
729
Renewing team memberships
730
-------------------------
4108.4.13 by Guilherme Salgado
Second half of the fix for https://launchpad.net/bugs/70519: Allow members of teams with an ONDEMAND renewal policy to renew their own memberships
731
4108.4.15 by Guilherme Salgado
Bunch of changes suggested by Barry/Francis
732
A team membership can be renewed before it has been expired by either
4108.4.13 by Guilherme Salgado
Second half of the fix for https://launchpad.net/bugs/70519: Allow members of teams with an ONDEMAND renewal policy to renew their own memberships
733
changing its dateexpires (which can be done only by admins of the
734
membership's team) or by using IPerson.renewTeamMembership, which is
735
accessible only to the membership's member a few days before it expires.
736
Also, for a member to renew his own membership, it's necessary that the
737
team's renewal policy is set to ONDEMAND and that the membership is
738
still active.
739
740
    >>> karl = personset.getByName('karl')
741
    >>> mirror_admins = personset.getByName('ubuntu-mirror-admins')
742
    >>> karl_on_mirroradmins = membershipset.getByPersonAndTeam(
743
    ...     karl, mirror_admins)
744
    >>> tomorrow = datetime.now(pytz.timezone('UTC')) + timedelta(days=1)
745
    >>> print karl_on_mirroradmins.status.title
746
    Approved
747
    >>> print karl_on_mirroradmins.dateexpires
748
    None
749
750
The member himself can't change the expiration date of his membership.
751
6596.1.1 by Guilherme Salgado
Fix https://launchpad.net/bugs/241332
752
    >>> login_person(karl)
3691.9.81 by Guilherme Salgado
Move the logic to prevent team admins from changing the expiration date of their own memberships from browser code to database code
753
    >>> karl_on_mirroradmins.setExpirationDate(tomorrow, karl)
4108.4.13 by Guilherme Salgado
Second half of the fix for https://launchpad.net/bugs/70519: Allow members of teams with an ONDEMAND renewal policy to renew their own memberships
754
    Traceback (most recent call last):
755
    ...
756
    Unauthorized: ...
757
758
Only a team admin can.
759
6596.1.1 by Guilherme Salgado
Fix https://launchpad.net/bugs/241332
760
    >>> login_person(mirror_admins.teamowner)
3691.9.81 by Guilherme Salgado
Move the logic to prevent team admins from changing the expiration date of their own memberships from browser code to database code
761
    >>> karl_on_mirroradmins.setExpirationDate(
762
    ...     tomorrow, mirror_admins.teamowner)
4108.4.13 by Guilherme Salgado
Second half of the fix for https://launchpad.net/bugs/70519: Allow members of teams with an ONDEMAND renewal policy to renew their own memberships
763
    >>> karl_on_mirroradmins.dateexpires == tomorrow
764
    True
765
766
If the team's renewal policy is ONDEMAND, the membership can be renewed
767
by the member himself. (That is only true because this membership is
4108.4.15 by Guilherme Salgado
Bunch of changes suggested by Barry/Francis
768
active and set to expire tomorrow).
4108.4.13 by Guilherme Salgado
Second half of the fix for https://launchpad.net/bugs/70519: Allow members of teams with an ONDEMAND renewal policy to renew their own memberships
769
770
    >>> print karl_on_mirroradmins.team.renewal_policy.name
771
    NONE
772
    >>> karl_on_mirroradmins.canBeRenewedByMember()
773
    False
774
    >>> ondemand = TeamMembershipRenewalPolicy.ONDEMAND
775
    >>> karl_on_mirroradmins.team.renewal_policy = ondemand
776
777
    # When a user renews his own membership, we use the team's default
778
    # renewal period, so we must specify that for the mirror admins
779
    # team.
780
    >>> mirror_admins.defaultrenewalperiod = 365
781
    >>> flush_database_updates()
782
783
    >>> karl_on_mirroradmins.canBeRenewedByMember()
784
    True
785
6596.1.1 by Guilherme Salgado
Fix https://launchpad.net/bugs/241332
786
    >>> login_person(karl)
4108.4.13 by Guilherme Salgado
Second half of the fix for https://launchpad.net/bugs/70519: Allow members of teams with an ONDEMAND renewal policy to renew their own memberships
787
    >>> karl.renewTeamMembership(mirror_admins)
788
789
790
Now the membership can't be renewed by the member as it's not going to
791
expire soon.
7495.2.1 by Curtis Hovey
Trivial formatting clean up for lint before the real work starts.
792
4108.4.13 by Guilherme Salgado
Second half of the fix for https://launchpad.net/bugs/70519: Allow members of teams with an ONDEMAND renewal policy to renew their own memberships
793
    >>> karl_on_mirroradmins.dateexpires == tomorrow + timedelta(days=365)
794
    True
795
    >>> karl_on_mirroradmins.canBeRenewedByMember()
796
    False
797
    >>> print karl_on_mirroradmins.status.title
798
    Approved
799
800
11515.2.6 by Curtis Hovey
Quiet lint.
801
Querying team memberships
802
-------------------------
3691.151.7 by kiko
More random fixage, getting rid of two XXXs in database/person.py; one of them changes EmailAddressSet.new() to take a person, not an ID. Improve some test coverage.
803
11347.13.28 by j.c.sackett
Missed some myactivememberships callsites.
804
You can check a person's direct memberships by using team_memberships:
3691.151.7 by kiko
More random fixage, getting rid of two XXXs in database/person.py; one of them changes EmailAddressSet.new() to take a person, not an ID. Improve some test coverage.
805
3691.272.12 by Guilherme Salgado
Some cleaning up on teammembership.txt. It was creating lots of teams without using the correct API and thus some changes I did broke lots of tests.
806
    >>> [(membership.team.name, membership.status.title)
11347.13.28 by j.c.sackett
Missed some myactivememberships callsites.
807
    ...  for membership in salgado.team_memberships]
8805.9.6 by Abel Deuring
test failures fixed that were caused by the new team hwdb-team appearing in the test data
808
    [(u'hwdb-team', 'Approved'), (u'landscape-developers', 'Approved'),
4073.5.15 by Guilherme Salgado
Bunch of changes suggested by Barry
809
     (u'admins', 'Administrator'), (u't4', 'Approved')]
3691.151.7 by kiko
More random fixage, getting rid of two XXXs in database/person.py; one of them changes EmailAddressSet.new() to take a person, not an ID. Improve some test coverage.
810
4073.5.12 by Guilherme Salgado
List invited members on a team's +members page plus some cleanup to help with the second part of the fix for bug 53637
811
And you can check which direct memberships a team has by using
6246.3.2 by Guilherme Salgado
Migrate all people-related things exposed on the webservice to use the new declaration mechanism.
812
member_memberships:
3691.151.7 by kiko
More random fixage, getting rid of two XXXs in database/person.py; one of them changes EmailAddressSet.new() to take a person, not an ID. Improve some test coverage.
813
814
    >>> [(membership.person.name, membership.status.title)
6246.3.2 by Guilherme Salgado
Migrate all people-related things exposed on the webservice to use the new declaration mechanism.
815
    ...  for membership in t3.member_memberships]
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
816
    [(u'cprov', 'Approved'), (u'jdub', 'Administrator')]
3691.151.7 by kiko
More random fixage, getting rid of two XXXs in database/person.py; one of them changes EmailAddressSet.new() to take a person, not an ID. Improve some test coverage.
817
3691.397.1 by Guilherme Salgado
Infrastructure needed to add list of recent members/applicants to a team page.
818
A team has a number of other methods that return the people which are members
819
of it, all based on Person.getMembersByStatus:
3691.151.7 by kiko
More random fixage, getting rid of two XXXs in database/person.py; one of them changes EmailAddressSet.new() to take a person, not an ID. Improve some test coverage.
820
3691.397.1 by Guilherme Salgado
Infrastructure needed to add list of recent members/applicants to a team page.
821
    >>> [person.unique_displayname for person in t3.approvedmembers]
822
    [u'Celso Providelo (cprov)']
3691.151.7 by kiko
More random fixage, getting rid of two XXXs in database/person.py; one of them changes EmailAddressSet.new() to take a person, not an ID. Improve some test coverage.
823
824
(which is the same as saying
825
3691.397.1 by Guilherme Salgado
Infrastructure needed to add list of recent members/applicants to a team page.
826
    >>> [person.unique_displayname
827
    ...  for person in t3.getMembersByStatus(TeamMembershipStatus.APPROVED)]
828
    [u'Celso Providelo (cprov)']
3691.151.7 by kiko
More random fixage, getting rid of two XXXs in database/person.py; one of them changes EmailAddressSet.new() to take a person, not an ID. Improve some test coverage.
829
830
except shorter)
831
3691.397.1 by Guilherme Salgado
Infrastructure needed to add list of recent members/applicants to a team page.
832
We can also change the sort order of the results of getMembersByStatus.
833
6596.1.1 by Guilherme Salgado
Fix https://launchpad.net/bugs/241332
834
    >>> login_person(cprov)
3691.397.1 by Guilherme Salgado
Infrastructure needed to add list of recent members/applicants to a team page.
835
    >>> cprov.leave(t3)
836
    >>> flush_database_updates()
837
838
    >>> deactivated = TeamMembershipStatus.DEACTIVATED
839
    >>> [person.unique_displayname
840
    ...  for person in t3.getMembersByStatus(deactivated)]
841
    [u'Celso Providelo (cprov)', u'Guilherme Salgado (salgado)']
842
5825.2.2 by Guilherme Salgado
Remove TeamMembership.statusname, rename datejoined to date_joined and dateexpires to date_expires.
843
    >>> orderBy = '-TeamMembership.date_joined'
3691.397.1 by Guilherme Salgado
Infrastructure needed to add list of recent members/applicants to a team page.
844
    >>> [person.unique_displayname
845
    ...  for person in t3.getMembersByStatus(deactivated, orderBy=orderBy)]
5825.2.3 by Guilherme Salgado
Fix the damn thing
846
    [u'Celso Providelo (cprov)', u'Guilherme Salgado (salgado)']
3691.397.1 by Guilherme Salgado
Infrastructure needed to add list of recent members/applicants to a team page.
847
7495.2.1 by Curtis Hovey
Trivial formatting clean up for lint before the real work starts.
848
11515.2.6 by Curtis Hovey
Quiet lint.
849
Finding team administrators
850
---------------------------
4182.1.7 by Francis J. Lacoste
Rename getEffectiveAdministrators to getDirectAdministrators().
851
852
Another convenient method is getDirectAdministrators(), which returns the
3859.1.1 by Guilherme Salgado
Fix https://beta.launchpad.net/launchpad/+bug/88024
853
admin members plus the owner in case he is not one of the admin members.
854
855
    >>> [admin.unique_displayname for admin in t3.adminmembers]
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
856
    [u'Jeff Waugh (jdub)']
4182.1.7 by Francis J. Lacoste
Rename getEffectiveAdministrators to getDirectAdministrators().
857
    >>> list(t3.getDirectAdministrators()) == list(t3.adminmembers)
3859.1.1 by Guilherme Salgado
Fix https://beta.launchpad.net/launchpad/+bug/88024
858
    True
859
12756.4.3 by William Grant
Fix remaining tests to not use shipit-admins.
860
    >>> from lp.testing import person_logged_in
861
    >>> owner = factory.makePerson()
862
    >>> adminless_team = factory.makeTeam(owner=owner)
863
    >>> with person_logged_in(owner):
864
    ...     owner.leave(adminless_team)
865
    >>> adminless_team.adminmembers.count() == 0
3859.1.1 by Guilherme Salgado
Fix https://beta.launchpad.net/launchpad/+bug/88024
866
    True
12756.4.3 by William Grant
Fix remaining tests to not use shipit-admins.
867
    >>> list(adminless_team.getDirectAdministrators()) == [owner]
4182.1.7 by Francis J. Lacoste
Rename getEffectiveAdministrators to getDirectAdministrators().
868
    True
869
870
Note that the team administrators can contain teams, so if you want to
871
check if a user is an admin of the team, you should use inTeam() to
4182.1.9 by Francis J. Lacoste
Fix typo, add explanation.
872
check if the user is a member of these administrators. For example,
873
cprov isn't a direct administrator of the guadamen team, but he is
874
an indirect administrator by being a member of the Ubuntu team (which
875
is a direct administrator of the guadamen team):
4182.1.7 by Francis J. Lacoste
Rename getEffectiveAdministrators to getDirectAdministrators().
876
877
    >>> guadamen_team = personset.getByName('guadamen')
878
    >>> [person.name for person in guadamen_team.getDirectAdministrators()]
879
    [u'name16', u'ubuntu-team']
880
14600.2.2 by Curtis Hovey
Moved webapp to lp.services.
881
    >>> from lp.services.webapp.authorization import check_permission
4182.1.7 by Francis J. Lacoste
Rename getEffectiveAdministrators to getDirectAdministrators().
882
    >>> ubuntu_team = personset.getByName('ubuntu-team')
883
    >>> cprov.inTeam(ubuntu_team)
884
    True
885
    >>> foobar in guadamen_team.getDirectAdministrators()
886
    True
887
    >>> cprov in guadamen_team.getDirectAdministrators()
888
    False
889
    >>> login('celso.providelo@canonical.com')
890
    >>> check_permission('launchpad.Edit', guadamen_team)
891
    True
892
4182.1.8 by Francis J. Lacoste
Add IPerson.getAdministratedTeams().
893
There is also the getAdministratedTeams() method that returns all the
894
teams for which the person/team has admin rights.
895
9962.6.1 by Brad Crittenden
Change getAdministratedTeams to omit merged teams.
896
    >>> cprov_team = factory.makeTeam(owner=cprov, name="cprov-team")
897
    >>> [team.name for team in cprov.getAdministratedTeams()]
11515.2.6 by Curtis Hovey
Quiet lint.
898
    [u'canonical-partner-dev', u'cprov-team', u'guadamen',
899
     u'launchpad-buildd-admins']
9962.6.1 by Brad Crittenden
Change getAdministratedTeams to omit merged teams.
900
901
If a team is merged it will not show up in the set of administered teams.
902
903
    >>> login('foo.bar@canonical.com')
12617.1.5 by Curtis Hovey
Updated call sites to use ITeamMembershipSet.deactivateActiveMemberships()
904
    >>> membershipset.deactivateActiveMemberships(
905
    ...     cprov_team, "Merging", foobar)
12651.1.16 by Curtis Hovey
Updated test to pass the reviewer arg.
906
    >>> personset.merge(cprov_team, guadamen_team, cprov_team.teamowner)
4182.1.8 by Francis J. Lacoste
Add IPerson.getAdministratedTeams().
907
    >>> [team.name for team in cprov.getAdministratedTeams()]
6286.1.3 by Julian Edwards
Fix stupid test bustage caused by adding a new team.
908
    [u'canonical-partner-dev', u'guadamen', u'launchpad-buildd-admins']
4182.1.8 by Francis J. Lacoste
Add IPerson.getAdministratedTeams().
909
7495.2.1 by Curtis Hovey
Trivial formatting clean up for lint before the real work starts.
910
11515.2.6 by Curtis Hovey
Quiet lint.
911
Querying a person for team participation
912
----------------------------------------
3058.1.8 by Stuart Bishop
Add Person.teams_participated_in for recursive team emblem display (Bug 30306)
913
3691.151.7 by kiko
More random fixage, getting rid of two XXXs in database/person.py; one of them changes EmailAddressSet.new() to take a person, not an ID. Improve some test coverage.
914
Team membership is direct; team participation is indirect, people being
915
participants of teams by virtue of being members of other teams which are in
916
turn members of these teams.
917
3058.1.8 by Stuart Bishop
Add Person.teams_participated_in for recursive team emblem display (Bug 30306)
918
We can ask a person what teams they participate in. The
3859.1.1 by Guilherme Salgado
Fix https://beta.launchpad.net/launchpad/+bug/88024
919
teams_participated_in attribute works recursively, listing all teams the
3058.1.8 by Stuart Bishop
Add Person.teams_participated_in for recursive team emblem display (Bug 30306)
920
person is an active member of as well as teams those teams are an active
921
member of.
922
9962.6.1 by Brad Crittenden
Change getAdministratedTeams to omit merged teams.
923
    >>> login('celso.providelo@canonical.com')
4955.1.8 by Francis J. Lacoste
Update tests that the addition of the new team broke.
924
    >>> print '\n'.join(sorted(
925
    ...     team.name for team in salgado.teams_participated_in))
926
    admins
8805.9.6 by Abel Deuring
test failures fixed that were caused by the new team hwdb-team appearing in the test data
927
    hwdb-team
4073.5.15 by Guilherme Salgado
Bunch of changes suggested by Barry
928
    landscape-developers
4955.1.8 by Francis J. Lacoste
Update tests that the addition of the new team broke.
929
    mailing-list-experts
3691.272.12 by Guilherme Salgado
Some cleaning up on teammembership.txt. It was creating lots of teams without using the correct API and thus some changes I did broke lots of tests.
930
    t4
931
932
Adding admins as a member of t1 will make Salgado a member of t1 as well.
933
934
    >>> admins = getUtility(IPersonSet).getByName('admins')
6596.1.1 by Guilherme Salgado
Fix https://launchpad.net/bugs/241332
935
    >>> login_person(t1.teamowner)
4197.2.8 by Guilherme Salgado
New infrastructure to allow team admins to decline invitations and a first draft of the page in which they'll be able to accept/decline the invitations
936
    >>> t1.addMember(admins, reviewer=t1.teamowner, force_team_add=True)
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
937
    (True, <DBItem TeamMembershipStatus.APPROVED...)
3691.272.12 by Guilherme Salgado
Some cleaning up on teammembership.txt. It was creating lots of teams without using the correct API and thus some changes I did broke lots of tests.
938
    >>> flush_database_updates()
4955.1.8 by Francis J. Lacoste
Update tests that the addition of the new team broke.
939
    >>> print '\n'.join(sorted(
940
    ...     team.name for team in salgado.teams_participated_in))
941
    admins
8805.9.6 by Abel Deuring
test failures fixed that were caused by the new team hwdb-team appearing in the test data
942
    hwdb-team
4073.5.15 by Guilherme Salgado
Bunch of changes suggested by Barry
943
    landscape-developers
4955.1.8 by Francis J. Lacoste
Update tests that the addition of the new team broke.
944
    mailing-list-experts
3691.272.12 by Guilherme Salgado
Some cleaning up on teammembership.txt. It was creating lots of teams without using the correct API and thus some changes I did broke lots of tests.
945
    t1
946
    t4
947
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
948
On the other hand, making t3 a member of admins won't change anything
3691.272.12 by Guilherme Salgado
Some cleaning up on teammembership.txt. It was creating lots of teams without using the correct API and thus some changes I did broke lots of tests.
949
for Salgado.
950
10002.2.25 by Edwin Grubbs
Added test in teammembership.txt.
951
    >>> login_person(foobar)
952
    >>> admins.addMember(t3, reviewer=admins.teamowner, force_team_add=True)
953
    (True, <DBItem TeamMembershipStatus.APPROVED...)
3691.272.12 by Guilherme Salgado
Some cleaning up on teammembership.txt. It was creating lots of teams without using the correct API and thus some changes I did broke lots of tests.
954
    >>> flush_database_updates()
4955.1.8 by Francis J. Lacoste
Update tests that the addition of the new team broke.
955
    >>> print '\n'.join(sorted(
956
    ...     team.name for team in salgado.teams_participated_in))
957
    admins
8805.9.6 by Abel Deuring
test failures fixed that were caused by the new team hwdb-team appearing in the test data
958
    hwdb-team
4073.5.15 by Guilherme Salgado
Bunch of changes suggested by Barry
959
    landscape-developers
4955.1.8 by Francis J. Lacoste
Update tests that the addition of the new team broke.
960
    mailing-list-experts
3691.272.12 by Guilherme Salgado
Some cleaning up on teammembership.txt. It was creating lots of teams without using the correct API and thus some changes I did broke lots of tests.
961
    t1
962
    t4