Merge lp:~wgrant/launchpad/no-team-admin-restrictions into lp:launchpad
- no-team-admin-restrictions
- Merge into devel
Proposed by
William Grant
Status: | Merged |
---|---|
Approved by: | Curtis Hovey |
Approved revision: | no longer in the source branch. |
Merged at revision: | 14625 |
Proposed branch: | lp:~wgrant/launchpad/no-team-admin-restrictions |
Merge into: | lp:launchpad |
Diff against target: |
685 lines (+125/-162) 5 files modified
lib/lp/registry/browser/teammembership.py (+1/-15) lib/lp/registry/doc/teammembership-email-notification.txt (+109/-106) lib/lp/registry/doc/teammembership.txt (+2/-12) lib/lp/registry/model/teammembership.py (+7/-17) lib/lp/registry/templates/teammembership-index.pt (+6/-12) |
To merge this branch: | bzr merge lp:~wgrant/launchpad/no-team-admin-restrictions |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Curtis Hovey (community) | code | Approve | |
Review via email: mp+84571@code.launchpad.net |
Commit message
[r=sinzui][bug=102180,206058] Let team admins promote others to admins and change their own expiration dates.
Description of the change
This branch removes a longstanding restriction preventing team admins from promoting other members to admins or changing their own expiration date. It confuses people (eg. bug #206058), is fairly pointless (admins can do just about any other damage to the team), and is easy to socially engineer your way around (teams are frequently handed over to their admins upon request when their owner is inactive).
sinzui says we should do it, and lifeless says he has no intrinsic objection.
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/lp/registry/browser/teammembership.py' |
2 | --- lib/lp/registry/browser/teammembership.py 2012-01-01 02:58:52 +0000 |
3 | +++ lib/lp/registry/browser/teammembership.py 2012-01-03 22:41:25 +0000 |
4 | @@ -15,13 +15,11 @@ |
5 | import pytz |
6 | from zope.app.form import CustomWidgetFactory |
7 | from zope.app.form.interfaces import InputErrors |
8 | -from zope.component import getUtility |
9 | from zope.formlib import form |
10 | from zope.schema import Date |
11 | |
12 | from lp import _ |
13 | from lp.app.errors import UnexpectedFormData |
14 | -from lp.app.interfaces.launchpad import ILaunchpadCelebrities |
15 | from lp.app.widgets.date import DateWidget |
16 | from lp.registry.interfaces.teammembership import TeamMembershipStatus |
17 | from lp.services.webapp import ( |
18 | @@ -96,13 +94,6 @@ |
19 | return '%s member %s' % (prefix, self.context.person.displayname) |
20 | |
21 | # Boolean helpers |
22 | - def userIsTeamOwnerOrLPAdmin(self): |
23 | - return (self.user.inTeam(self.context.team.teamowner) or |
24 | - self.user.inTeam(getUtility(ILaunchpadCelebrities).admin)) |
25 | - |
26 | - def allowChangeAdmin(self): |
27 | - return self.userIsTeamOwnerOrLPAdmin() or self.isAdmin() |
28 | - |
29 | def isActive(self): |
30 | return self.context.status in [TeamMembershipStatus.APPROVED, |
31 | TeamMembershipStatus.ADMIN] |
32 | @@ -225,12 +216,7 @@ |
33 | context.status == TeamMembershipStatus.ADMIN): |
34 | new_status = TeamMembershipStatus.APPROVED |
35 | elif (form.get('admin') == "yes" and |
36 | - context.status == TeamMembershipStatus.APPROVED |
37 | - # XXX: salgado 2005-03-15: The clause below is a hack |
38 | - # to make sure only the teamowner can promote a given |
39 | - # member to admin, while we don't have a specific |
40 | - # permission setup for this. |
41 | - and self.userIsTeamOwnerOrLPAdmin()): |
42 | + context.status == TeamMembershipStatus.APPROVED): |
43 | new_status = TeamMembershipStatus.ADMIN |
44 | else: |
45 | # No status change will happen |
46 | |
47 | === modified file 'lib/lp/registry/doc/teammembership-email-notification.txt' |
48 | --- lib/lp/registry/doc/teammembership-email-notification.txt 2011-11-15 01:01:55 +0000 |
49 | +++ lib/lp/registry/doc/teammembership-email-notification.txt 2012-01-03 22:41:25 +0000 |
50 | @@ -1,10 +1,11 @@ |
51 | -= Mail notifications for membership changes = |
52 | +Mail notifications for membership changes |
53 | +========================================= |
54 | |
55 | -Whenever a membership status is changed, we should notify the team admins and |
56 | -the member whose membership changed. There's a few cases where we might want |
57 | -to notify only the team admins, but in most of the cases we'll be sending two |
58 | -similar (but not identical) notifications: one for all team admins and another |
59 | -for the member. |
60 | +Whenever a membership status is changed, we should notify the team |
61 | +admins and the member whose membership changed. There's a few cases |
62 | +where we might want to notify only the team admins, but in most of the |
63 | +cases we'll be sending two similar (but not identical) notifications: |
64 | +one for all team admins and another for the member. |
65 | |
66 | >>> def by_to_addrs(a, b): |
67 | ... return cmp(a[1], b[1]) |
68 | @@ -47,7 +48,6 @@ |
69 | >>> from lp.testing.sampledata import ADMIN_EMAIL |
70 | >>> admin_person = personset.getByEmail(ADMIN_EMAIL) |
71 | |
72 | - |
73 | In open teams joining and leaving the team generates no notifications. |
74 | |
75 | >>> login_person(admin_person) |
76 | @@ -58,15 +58,16 @@ |
77 | >>> membership = membershipset.getByPersonAndTeam(new_person, open_team) |
78 | >>> membership.status.title |
79 | 'Approved' |
80 | + |
81 | >>> run_mail_jobs() |
82 | >>> len(stub.test_emails) - base_mails |
83 | 0 |
84 | + |
85 | >>> new_person.leave(open_team) |
86 | >>> run_mail_jobs() |
87 | >>> len(stub.test_emails) - base_mails |
88 | 0 |
89 | |
90 | - |
91 | Now Robert Collins proposes himself as a member of the Ubuntu Team. This |
92 | generates a notification email only to Ubuntu Team administrators. |
93 | |
94 | @@ -80,6 +81,7 @@ |
95 | >>> run_mail_jobs() |
96 | >>> len(stub.test_emails) |
97 | 5 |
98 | + |
99 | >>> print_distinct_emails(include_reply_to=True) |
100 | From: Ubuntu Team <noreply@launchpad.net> |
101 | To: colin.watson@ubuntulinux.com, foo.bar@canonical.com, |
102 | @@ -108,11 +110,12 @@ |
103 | You received this email because you are the owner of the Ubuntu Team team. |
104 | ---------------------------------------- |
105 | |
106 | -Declining a proposed member should generate notifications for both the member |
107 | -and each of the team's admins. |
108 | +Declining a proposed member should generate notifications for both the |
109 | +member and each of the team's admins. |
110 | |
111 | # Need to be logged in as a team admin to be able to change memberships of |
112 | # that team. |
113 | + |
114 | >>> login('mark@example.com') |
115 | >>> setStatus(membership, TeamMembershipStatus.DECLINED, reviewer=mark) |
116 | |
117 | @@ -153,6 +156,7 @@ |
118 | |
119 | # Remove notification of daf's membership pending approval from |
120 | # stub.test_emails |
121 | + |
122 | >>> transaction.commit() |
123 | >>> dummy = pop_notifications() |
124 | |
125 | @@ -162,6 +166,7 @@ |
126 | >>> stub.test_emails.sort(by_to_addrs) |
127 | >>> len(stub.test_emails) |
128 | 6 |
129 | + |
130 | >>> print_distinct_emails() |
131 | From: Ubuntu Team <noreply@launchpad.net> |
132 | To: colin.watson@ubuntulinux.com, foo.bar@canonical.com, |
133 | @@ -196,6 +201,7 @@ |
134 | >>> stub.test_emails.sort(by_to_addrs) |
135 | >>> len(stub.test_emails) |
136 | 6 |
137 | + |
138 | >>> print_distinct_emails() |
139 | From: Ubuntu Team <noreply@launchpad.net> |
140 | To: colin.watson@ubuntulinux.com, foo.bar@canonical.com, |
141 | @@ -216,15 +222,17 @@ |
142 | <http://launchpad.dev/~ubuntu-team> |
143 | ---------------------------------------- |
144 | |
145 | -Team admins can propose their teams using the join() method as well, but in |
146 | -that case we'll use the requester's (the person proposing the team as the |
147 | -other's member) email address in the 'Reply-To' header of the message sent. |
148 | +Team admins can propose their teams using the join() method as well, but |
149 | +in that case we'll use the requester's (the person proposing the team as |
150 | +the other's member) email address in the 'Reply-To' header of the |
151 | +message sent. |
152 | |
153 | >>> admins = personset.getByName('admins') |
154 | >>> admins.join(ubuntu_team, requester=mark) |
155 | >>> run_mail_jobs() |
156 | >>> len(stub.test_emails) |
157 | 5 |
158 | + |
159 | >>> print_distinct_emails(include_reply_to=True) |
160 | From: Ubuntu Team <noreply@launchpad.net> |
161 | To: colin.watson@ubuntulinux.com, foo.bar@canonical.com, |
162 | @@ -254,11 +262,12 @@ |
163 | ---------------------------------------- |
164 | |
165 | |
166 | -== Adding new members == |
167 | +Adding new members |
168 | +------------------ |
169 | |
170 | When a person is added as a member of a team by one of that team's |
171 | -administrators, an email is sent to all team administrators and to the new |
172 | -member. |
173 | +administrators, an email is sent to all team administrators and to the |
174 | +new member. |
175 | |
176 | >>> cprov = personset.getByName('cprov') |
177 | >>> marilize = personset.getByName('marilize') |
178 | @@ -269,6 +278,7 @@ |
179 | |
180 | >>> len(stub.test_emails) |
181 | 6 |
182 | + |
183 | >>> print_distinct_emails() |
184 | From: Ubuntu Team <noreply@launchpad.net> |
185 | To: marilize@hbd.com |
186 | @@ -307,17 +317,19 @@ |
187 | You received this email because you are the owner of the Ubuntu Team team. |
188 | ---------------------------------------- |
189 | |
190 | -By default, if the newly added member is actually a team, we'll only send |
191 | -an invitation to the team's admins, telling them that the membership will |
192 | -only be activated if they accept the invitation. |
193 | +By default, if the newly added member is actually a team, we'll only |
194 | +send an invitation to the team's admins, telling them that the |
195 | +membership will only be activated if they accept the invitation. |
196 | |
197 | >>> mirror_admins = personset.getByName('ubuntu-mirror-admins') |
198 | >>> mirror_admins.getTeamAdminsEmailAddresses() |
199 | ['mark@example.com'] |
200 | + |
201 | >>> ignored = ubuntu_team.addMember(mirror_admins, reviewer=cprov) |
202 | >>> run_mail_jobs() |
203 | >>> len(stub.test_emails) |
204 | 1 |
205 | + |
206 | >>> print_distinct_emails() |
207 | From: Ubuntu Team <noreply@launchpad.net> |
208 | To: mark@example.com |
209 | @@ -336,8 +348,9 @@ |
210 | The Launchpad team |
211 | ---------------------------------------- |
212 | |
213 | -If one of the admins accept the invitation, then a notification is sent to the |
214 | -team which just became a member and to the admins of the hosting team. |
215 | +If one of the admins accept the invitation, then a notification is sent |
216 | +to the team which just became a member and to the admins of the hosting |
217 | +team. |
218 | |
219 | >>> comment = "Of course I want to be part of ubuntu!" |
220 | >>> mirror_admins.acceptInvitationToBeMemberOf(ubuntu_team, comment) |
221 | @@ -346,6 +359,7 @@ |
222 | |
223 | >>> len(stub.test_emails) |
224 | 6 |
225 | + |
226 | >>> print_distinct_emails() |
227 | From: Ubuntu Team <noreply@launchpad.net> |
228 | To: colin.watson@ubuntulinux.com, foo.bar@canonical.com, |
229 | @@ -369,6 +383,7 @@ |
230 | |
231 | # Reset stub.test_emails as we don't care about the notification triggered |
232 | # by the addMember() call. |
233 | + |
234 | >>> transaction.commit() |
235 | >>> stub.test_emails = [] |
236 | |
237 | @@ -379,6 +394,7 @@ |
238 | |
239 | >>> len(stub.test_emails) |
240 | 7 |
241 | + |
242 | >>> print_distinct_emails() |
243 | From: Ubuntu Team <noreply@launchpad.net> |
244 | To: colin.watson@ubuntulinux.com, foo.bar@canonical.com, |
245 | @@ -404,6 +420,7 @@ |
246 | >>> run_mail_jobs() |
247 | >>> len(stub.test_emails) |
248 | 5 |
249 | + |
250 | >>> print_distinct_emails() |
251 | From: Ubuntu Team <noreply@launchpad.net> |
252 | To: foo.bar@canonical.com |
253 | @@ -437,60 +454,24 @@ |
254 | ---------------------------------------- |
255 | |
256 | |
257 | -== Membership expiration warnings == |
258 | +Membership expiration warnings |
259 | +------------------------------ |
260 | |
261 | -When we get close to the expiration date of a given membership, an expiration |
262 | -warning is sent to the member, so that he can contact the team's |
263 | -administrators (or renew it himself when he has necessary rights) in case |
264 | -he wants to retain that membership. This is done by the |
265 | -flag-expired-memberships cronscript, which uses |
266 | +When we get close to the expiration date of a given membership, an |
267 | +expiration warning is sent to the member, so that he can contact the |
268 | +team's administrators (or renew it himself when he has necessary rights) |
269 | +in case he wants to retain that membership. This is done by the flag- |
270 | +expired-memberships cronscript, which uses |
271 | ITeamMembership.sendExpirationWarningEmail to do its job. |
272 | |
273 | >>> import pytz |
274 | >>> from datetime import datetime, timedelta |
275 | >>> utc_now = datetime.now(pytz.timezone('UTC')) |
276 | - >>> kamion_on_ubuntu_team = membershipset.getByPersonAndTeam( |
277 | - ... kamion, ubuntu_team) |
278 | - >>> kamion_on_ubuntu_team.setExpirationDate( |
279 | - ... utc_now + timedelta(days=9), mark) |
280 | - >>> flush_database_updates() |
281 | - |
282 | -Kamion is an admin of the Ubuntu team, but team admins can't change the |
283 | -expiration date of their own memberships, so he still has to contact one of |
284 | -the other team admins. |
285 | - |
286 | - >>> kamion_on_ubuntu_team.status.name |
287 | - 'ADMIN' |
288 | - >>> kamion_on_ubuntu_team.sendExpirationWarningEmail() |
289 | - >>> transaction.commit() |
290 | - >>> print_distinct_emails() |
291 | - From: Ubuntu Team <noreply@launchpad.net> |
292 | - To: colin.watson@ubuntulinux.com |
293 | - Subject: Your membership in ubuntu-team is about to expire |
294 | - <BLANKLINE> |
295 | - On ..., 9 days from now, your membership |
296 | - in the Ubuntu Team (ubuntu-team) Launchpad team |
297 | - is due to expire. |
298 | - <http://launchpad.dev/~ubuntu-team> |
299 | - <BLANKLINE> |
300 | - To prevent this membership from expiring, you should get in touch |
301 | - with one of the team's administrators: |
302 | - Alexander Limi (limi) <http://launchpad.dev/~limi> |
303 | - Foo Bar (name16) <http://launchpad.dev/~name16> |
304 | - Jeff Waugh (jdub) <http://launchpad.dev/~jdub> |
305 | - Mark Shuttleworth (mark) <http://launchpad.dev/~mark> |
306 | - <BLANKLINE> |
307 | - If your membership does expire, we'll send you one more message to let |
308 | - you know it's happened. |
309 | - <BLANKLINE> |
310 | - Thanks for using Launchpad! |
311 | - <BLANKLINE> |
312 | - ---------------------------------------- |
313 | |
314 | In the case of the beta-testers team, the email is sent only to the |
315 | team's owner, which doesn't have the necessary rights to renew the |
316 | -membership of his team, so he's instructed to contact one of the |
317 | -ubuntu-team's admins. |
318 | +membership of his team, so he's instructed to contact one of the ubuntu- |
319 | +team's admins. |
320 | |
321 | >>> beta_testers = personset.getByName('launchpad-beta-testers') |
322 | >>> beta_testers_on_ubuntu_team = membershipset.getByPersonAndTeam( |
323 | @@ -526,11 +507,15 @@ |
324 | <BLANKLINE> |
325 | ---------------------------------------- |
326 | |
327 | -If the team's renewal policy is ONDEMAND, though, the member is invited to |
328 | -renew his own membership. |
329 | +If the team's renewal policy is ONDEMAND, though, the member is invited |
330 | +to renew his own membership. |
331 | |
332 | >>> ubuntu_team.renewal_policy = TeamMembershipRenewalPolicy.ONDEMAND |
333 | >>> ubuntu_team.defaultrenewalperiod = 365 |
334 | + >>> kamion_on_ubuntu_team = membershipset.getByPersonAndTeam( |
335 | + ... kamion, ubuntu_team) |
336 | + >>> kamion_on_ubuntu_team.setExpirationDate( |
337 | + ... utc_now + timedelta(days=9), mark) |
338 | >>> flush_database_updates() |
339 | >>> kamion_on_ubuntu_team.sendExpirationWarningEmail() |
340 | >>> transaction.commit() |
341 | @@ -577,14 +562,15 @@ |
342 | <BLANKLINE> |
343 | ---------------------------------------- |
344 | |
345 | -If the team's renewal policy is NONE but the member has the necessary rights |
346 | -to change the expiration date of his own membership (i.e. by being the team's |
347 | -owner), the notification he gets will contain a link to his memberhip page, |
348 | -where he can extend it. |
349 | +If the team's renewal policy is NONE but the member has the necessary |
350 | +rights to change the expiration date of his own membership (i.e. by |
351 | +being the team's owner), the notification he gets will contain a link to |
352 | +his memberhip page, where he can extend it. |
353 | |
354 | >>> landscape.renewal_policy = TeamMembershipRenewalPolicy.NONE |
355 | >>> landscape.teamowner.preferredemail.email |
356 | u'test@canonical.com' |
357 | + |
358 | >>> sampleperson_on_landscape = membershipset.getByPersonAndTeam( |
359 | ... sampleperson, landscape) |
360 | >>> sampleperson_on_landscape.setExpirationDate( |
361 | @@ -613,15 +599,16 @@ |
362 | ---------------------------------------- |
363 | |
364 | |
365 | -== Membership expiration notification == |
366 | +Membership expiration notification |
367 | +---------------------------------- |
368 | |
369 | -For teams with a renewal policy other than AUTOMATIC, if a membership is not |
370 | -renewed before its expiration date it'll be flagged as expired and a |
371 | -notification is sent to the team admins and to the member whose membership |
372 | -expired. If the renewal policy is AUTOMATIC, though, the memberships that |
373 | -should expire will retain their status and have their dateexpires update. A |
374 | -notification is also sent to the member and to team admins when a membership |
375 | -is automatically renewed. |
376 | +For teams with a renewal policy other than AUTOMATIC, if a membership is |
377 | +not renewed before its expiration date it'll be flagged as expired and a |
378 | +notification is sent to the team admins and to the member whose |
379 | +membership expired. If the renewal policy is AUTOMATIC, though, the |
380 | +memberships that should expire will retain their status and have their |
381 | +dateexpires update. A notification is also sent to the member and to |
382 | +team admins when a membership is automatically renewed. |
383 | |
384 | >>> from zope.security.proxy import removeSecurityProxy |
385 | >>> utc_now = datetime.now(pytz.timezone('UTC')) |
386 | @@ -633,6 +620,7 @@ |
387 | |
388 | # Need to cheat here and set the expiry date manually because the expiry |
389 | # date given to setExpirationDate() must be in the future. |
390 | + |
391 | >>> removeSecurityProxy(mark_on_admins).dateexpires = utc_now |
392 | |
393 | >>> ubuntu_team = personset.getByName('ubuntu-team') |
394 | @@ -701,12 +689,13 @@ |
395 | ---------------------------------------- |
396 | |
397 | |
398 | -== Memberships renewed by the members themselves == |
399 | +Memberships renewed by the members themselves |
400 | +--------------------------------------------- |
401 | |
402 | -Another possible renewal policy for teams is ONDEMAND, which means that team |
403 | -members are invited to renew their membership once it gets close to their |
404 | -expiration date. When a member renew his own membership, a notification is |
405 | -sent to all team admins. |
406 | +Another possible renewal policy for teams is ONDEMAND, which means that |
407 | +team members are invited to renew their membership once it gets close to |
408 | +their expiration date. When a member renew his own membership, a |
409 | +notification is sent to all team admins. |
410 | |
411 | >>> karl = personset.getByName('karl') |
412 | >>> mirror_admins = personset.getByName('ubuntu-mirror-admins') |
413 | @@ -715,6 +704,7 @@ |
414 | >>> tomorrow = datetime.now(pytz.timezone('UTC')) + timedelta(days=1) |
415 | >>> print karl_on_mirroradmins.status.title |
416 | Approved |
417 | + |
418 | >>> print karl_on_mirroradmins.dateexpires |
419 | None |
420 | |
421 | @@ -745,10 +735,12 @@ |
422 | The Launchpad team |
423 | ---------------------------------------- |
424 | |
425 | -== Some special cases == |
426 | - |
427 | -When creating a new team, the owner has his membership's status changed from |
428 | -approved to admin, but he won't get a notification of that. |
429 | + |
430 | +Some special cases |
431 | +------------------ |
432 | + |
433 | +When creating a new team, the owner has his membership's status changed |
434 | +from approved to admin, but he won't get a notification of that. |
435 | |
436 | >>> team = personset.newTeam(mark, 'testteam', 'Test') |
437 | >>> run_mail_jobs() |
438 | @@ -758,19 +750,22 @@ |
439 | # Other tests expect an empty stub.test_emails, but if this one above |
440 | # fails, I don't want a non-empty stub.test_emails to cause the tests |
441 | # below to fail too. |
442 | + |
443 | >>> stub.test_emails = [] |
444 | |
445 | -If cprov is made an administrator of ubuntu_team, he'll only get one email |
446 | -notification. |
447 | +If cprov is made an administrator of ubuntu_team, he'll only get one |
448 | +email notification. |
449 | |
450 | >>> cprov = personset.getByName('cprov') |
451 | - >>> cprov_membership = membershipset.getByPersonAndTeam(cprov, ubuntu_team) |
452 | + >>> cprov_membership = membershipset.getByPersonAndTeam( |
453 | + ... cprov, ubuntu_team) |
454 | >>> login('mark@example.com') |
455 | >>> setStatus( |
456 | ... cprov_membership, TeamMembershipStatus.ADMIN, reviewer=mark) |
457 | >>> run_mail_jobs() |
458 | >>> len(stub.test_emails) |
459 | 6 |
460 | + |
461 | >>> print_distinct_emails() |
462 | From: Ubuntu Team <noreply@launchpad.net> |
463 | To: colin.watson@ubuntulinux.com, foo.bar@canonical.com, |
464 | @@ -791,9 +786,9 @@ |
465 | <http://launchpad.dev/~ubuntu-team> |
466 | ---------------------------------------- |
467 | |
468 | -If a team admin changes his own membership, the notification sent will clearly |
469 | -say that the change was performed by the user himself, and it will only be |
470 | -sent to the team administrators. |
471 | +If a team admin changes his own membership, the notification sent will |
472 | +clearly say that the change was performed by the user himself, and it |
473 | +will only be sent to the team administrators. |
474 | |
475 | >>> jdub = getUtility(IPersonSet).getByName('jdub') |
476 | >>> jdub_membership = membershipset.getByPersonAndTeam(jdub, ubuntu_team) |
477 | @@ -802,6 +797,7 @@ |
478 | >>> run_mail_jobs() |
479 | >>> len(stub.test_emails) |
480 | 5 |
481 | + |
482 | >>> print_distinct_emails() |
483 | From: Ubuntu Team <noreply@launchpad.net> |
484 | To: celso.providelo@canonical.com, colin.watson@ubuntulinux.com, |
485 | @@ -814,11 +810,11 @@ |
486 | <http://launchpad.dev/~ubuntu-team> |
487 | ---------------------------------------- |
488 | |
489 | -Deactivating the membership of a team also generates notifications for the |
490 | -team which had the membership deactivated and to the administrators of the |
491 | -hosting team. Note that the notification sent to the team whose membership |
492 | -was deactivated will not talk about "your membership" as it wouldn't make |
493 | -sense to the members of the team reading it. |
494 | +Deactivating the membership of a team also generates notifications for |
495 | +the team which had the membership deactivated and to the administrators |
496 | +of the hosting team. Note that the notification sent to the team whose |
497 | +membership was deactivated will not talk about "your membership" as it |
498 | +wouldn't make sense to the members of the team reading it. |
499 | |
500 | >>> mirror_admins_membership = membershipset.getByPersonAndTeam( |
501 | ... mirror_admins, ubuntu_team) |
502 | @@ -841,8 +837,8 @@ |
503 | <http://launchpad.dev/~ubuntu-team> |
504 | ---------------------------------------- |
505 | |
506 | -Deactivating memberships can also be done silently (no email notifications |
507 | -sent) by Launchpad Administrators. |
508 | +Deactivating memberships can also be done silently (no email |
509 | +notifications sent) by Launchpad Administrators. |
510 | |
511 | >>> dumper = getUtility(IPersonSet).getByName('dumper') |
512 | >>> hwdb_admins = personset.getByName('hwdb-team') |
513 | @@ -850,17 +846,19 @@ |
514 | ... hwdb_admins) |
515 | >>> print dumper_hwdb_membership.status.title |
516 | Approved |
517 | + |
518 | >>> login_person(admin_person) |
519 | >>> setStatus(dumper_hwdb_membership, TeamMembershipStatus.DEACTIVATED, |
520 | ... reviewer=admin_person, silent=True) |
521 | >>> run_mail_jobs() |
522 | >>> len(stub.test_emails) |
523 | 0 |
524 | + |
525 | >>> print dumper_hwdb_membership.status.title |
526 | Deactivated |
527 | |
528 | -People who are not Launchpad Administrators, may not change other's membership |
529 | -statues silently. |
530 | +People who are not Launchpad Administrators, may not change other's |
531 | +membership statues silently. |
532 | |
533 | >>> kamion = getUtility(IPersonSet).getByName('kamion') |
534 | >>> stevea = getUtility(IPersonSet).getByName('stevea') |
535 | @@ -872,20 +870,24 @@ |
536 | ... stevea, ubuntu_team) |
537 | >>> print kamion_ubuntu_team_membership.status.title |
538 | Administrator |
539 | + |
540 | >>> print stevea_ubuntu_team_membership.status.title |
541 | Approved |
542 | + |
543 | >>> setStatus(stevea_ubuntu_team_membership, |
544 | ... TeamMembershipStatus.DEACTIVATED, reviewer=kamion, silent=True) |
545 | Traceback (most recent call last): |
546 | UserCannotChangeMembershipSilently: ... |
547 | + |
548 | >>> print stevea_ubuntu_team_membership.status.title |
549 | Approved |
550 | |
551 | + |
552 | Joining a team with a mailing list |
553 | ---------------------------------- |
554 | |
555 | -When a user joins a team with a mailing list, the new member's notification |
556 | -email contain subscription information. |
557 | +When a user joins a team with a mailing list, the new member's |
558 | +notification email contain subscription information. |
559 | |
560 | >>> owner = factory.makePerson(name='team-owner') |
561 | >>> login_person(owner) |
562 | @@ -938,3 +940,4 @@ |
563 | You received this email because team-two is the new member. |
564 | ---------------------------------------- |
565 | |
566 | + |
567 | |
568 | === modified file 'lib/lp/registry/doc/teammembership.txt' |
569 | --- lib/lp/registry/doc/teammembership.txt 2011-12-30 06:14:56 +0000 |
570 | +++ lib/lp/registry/doc/teammembership.txt 2012-01-03 22:41:25 +0000 |
571 | @@ -539,7 +539,8 @@ |
572 | AssertionError: ... |
573 | >>> foobar_on_buildd.setExpirationDate(tomorrow, foobar) |
574 | |
575 | -Team owners can also renew any memberships of the team they own. |
576 | +Team owners and admins can also renew any memberships of the team they |
577 | +own or administer. |
578 | |
579 | >>> landscape = getUtility(IPersonSet).getByName( |
580 | ... 'landscape-developers') |
581 | @@ -553,27 +554,16 @@ |
582 | True |
583 | >>> sampleperson_on_landscape.setExpirationDate(tomorrow, sampleperson) |
584 | |
585 | -In the case of a mere team admin, though, he can only change the expiry |
586 | -date of other's memberships, not his own. |
587 | - |
588 | >>> cprov_on_buildd = membershipset.getByPersonAndTeam( |
589 | ... cprov, buildd_admins) |
590 | >>> buildd_admins.teamowner.name |
591 | u'name16' |
592 | >>> print cprov_on_buildd.status.title |
593 | Administrator |
594 | - |
595 | >>> foobar_on_buildd.canChangeExpirationDate(cprov) |
596 | True |
597 | >>> foobar_on_buildd.setExpirationDate(tomorrow, cprov) |
598 | |
599 | - >>> cprov_on_buildd.canChangeExpirationDate(cprov) |
600 | - False |
601 | - >>> cprov_on_buildd.setExpirationDate(tomorrow, cprov) |
602 | - Traceback (most recent call last): |
603 | - ... |
604 | - AssertionError: ... |
605 | - |
606 | |
607 | Flagging expired memberships |
608 | ---------------------------- |
609 | |
610 | === modified file 'lib/lp/registry/model/teammembership.py' |
611 | --- lib/lp/registry/model/teammembership.py 2011-12-30 06:14:56 +0000 |
612 | +++ lib/lp/registry/model/teammembership.py 2012-01-03 22:41:25 +0000 |
613 | @@ -40,6 +40,7 @@ |
614 | from lp.registry.interfaces.persontransferjob import ( |
615 | IMembershipNotificationJobSource, |
616 | ) |
617 | +from lp.registry.interfaces.role import IPersonRoles |
618 | from lp.registry.interfaces.teammembership import ( |
619 | ACTIVE_STATES, |
620 | CyclicalTeamMembershipError, |
621 | @@ -199,17 +200,9 @@ |
622 | |
623 | def canChangeExpirationDate(self, person): |
624 | """See `ITeamMembership`.""" |
625 | - person_is_admin = self.team in person.getAdministratedTeams() |
626 | - if (person.inTeam(self.team.teamowner) or |
627 | - person.inTeam(getUtility(ILaunchpadCelebrities).admin)): |
628 | - # The team owner and Launchpad admins can change the expiration |
629 | - # date of anybody's membership. |
630 | - return True |
631 | - elif person_is_admin and person != self.person: |
632 | - # A team admin can only change other member's expiration date. |
633 | - return True |
634 | - else: |
635 | - return False |
636 | + person_is_team_admin = self.team in person.getAdministratedTeams() |
637 | + person_is_lp_admin = IPersonRoles(person).in_admin |
638 | + return person_is_team_admin or person_is_lp_admin |
639 | |
640 | def setExpirationDate(self, date, user): |
641 | """See `ITeamMembership`.""" |
642 | @@ -273,12 +266,9 @@ |
643 | % (admin.unique_displayname, canonical_url(admin))) |
644 | else: |
645 | for admin in admins: |
646 | - # Do not tell the member to contact himself when he can't |
647 | - # extend his membership. |
648 | - if admin != member: |
649 | - admins_names.append( |
650 | - "%s <%s>" % (admin.unique_displayname, |
651 | - canonical_url(admin))) |
652 | + admins_names.append( |
653 | + "%s <%s>" % (admin.unique_displayname, |
654 | + canonical_url(admin))) |
655 | |
656 | how_to_renew = ( |
657 | "To prevent this membership from expiring, you should " |
658 | |
659 | === modified file 'lib/lp/registry/templates/teammembership-index.pt' |
660 | --- lib/lp/registry/templates/teammembership-index.pt 2010-10-10 21:54:16 +0000 |
661 | +++ lib/lp/registry/templates/teammembership-index.pt 2012-01-03 22:41:25 +0000 |
662 | @@ -109,18 +109,12 @@ |
663 | <tr> |
664 | <th>Administrator:</th> |
665 | <td> |
666 | - <tal:can-change condition="view/allowChangeAdmin"> |
667 | - <input tal:attributes="checked view/adminIsSelected" |
668 | - type="radio" value="yes" name="admin" id="admin"/> |
669 | - <label for="admin">Yes</label> |
670 | - <input tal:attributes="checked view/adminIsNotSelected" |
671 | - type="radio" value="no" name="admin" id="notadmin"/> |
672 | - <label for="notadmin">No</label> |
673 | - </tal:can-change> |
674 | - <tal:cant-change condition="not: view/allowChangeAdmin"> |
675 | - <span tal:condition="view/isAdmin">Yes</span> |
676 | - <span tal:condition="not: view/isAdmin">No</span> |
677 | - </tal:cant-change> |
678 | + <input tal:attributes="checked view/adminIsSelected" |
679 | + type="radio" value="yes" name="admin" id="admin"/> |
680 | + <label for="admin">Yes</label> |
681 | + <input tal:attributes="checked view/adminIsNotSelected" |
682 | + type="radio" value="no" name="admin" id="notadmin"/> |
683 | + <label for="notadmin">No</label> |
684 | </td> |
685 | </tr> |
686 |
Wow.Thank you for this branch. I had forgotten that we just hacked the UI to prevent admins from promoting other admins. This branch unblocks bug 4744/