Merge lp:~wgrant/launchpad/no-team-admin-restrictions into lp:launchpad

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
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.
Revision history for this message
Curtis Hovey (sinzui) wrote :

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/

review: Approve (code)

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