Merge lp:~wgrant/launchpad/bug-1056506 into lp:launchpad

Proposed by William Grant on 2012-09-26
Status: Merged
Merged at revision: 16032
Proposed branch: lp:~wgrant/launchpad/bug-1056506
Merge into: lp:launchpad
Diff against target: 140 lines (+33/-33)
2 files modified
lib/lp/registry/tests/test_user_vocabularies.py (+21/-28)
lib/lp/registry/vocabularies.py (+12/-5)
To merge this branch: bzr merge lp:~wgrant/launchpad/bug-1056506
Reviewer Review Type Date Requested Status
Steve Kowalik (community) code 2012-09-26 Approve on 2012-09-26
Review via email: mp+126358@code.launchpad.net

Commit Message

In AllUserTeamsParticipationVocabulary, join TeamParticipation separately when checking membership so we don't conflict with the private team access check join. Fixes OOPSes when used by a commercial admin.

Description of the Change

AllUserTeamsParticipationVocabulary adds extra constraints on TeamParticipation to restrict results to those teams in which the user participates. But it doesn't join TeamParticipation itself; it relies on the join from the private team access check, which is confusing and breaks if the private team access check isn't there (eg. for commercial admins). I fixed it to constrain against a separately joined TeamParticipation alias, making things clearer and fixing the OOPS.

I added a new test and cleaned up existing ones to achieve LoC neutrality.

To post a comment you must log in.
Steve Kowalik (stevenk) :
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/tests/test_user_vocabularies.py'
2--- lib/lp/registry/tests/test_user_vocabularies.py 2012-08-20 04:49:45 +0000
3+++ lib/lp/registry/tests/test_user_vocabularies.py 2012-09-26 00:57:23 +0000
4@@ -9,13 +9,12 @@
5 from zope.schema.vocabulary import getVocabularyRegistry
6
7 from lp.registry.enums import TeamMembershipPolicy
8-from lp.registry.interfaces.person import PersonVisibility
9+from lp.registry.interfaces.person import (
10+ IPersonSet,
11+ PersonVisibility,
12+ )
13 from lp.registry.model.person import Person
14-from lp.services.webapp.interfaces import (
15- DEFAULT_FLAVOR,
16- IStoreSelector,
17- MAIN_STORE,
18- )
19+from lp.services.database.lpstorm import IStore
20 from lp.testing import (
21 ANONYMOUS,
22 login,
23@@ -140,18 +139,11 @@
24 def _vocabTermValues(self):
25 """Return the token values for the vocab."""
26 # XXX Abel Deuring 2010-05-21, bug 583502: We cannot simply iterate
27- # over the items of AllUserTeamsPariticipationVocabulary as in
28- # class TestUserTeamsParticipationPlusSelfVocabulary.
29- # So we iterate over all Person records and return all terms
30- # returned by vocabulary.searchForTerms(person.name)
31+ # over the items of AllUserTeamsPariticipationVocabulary, so
32+ # so iterate over all Persons and check membership.
33 vocabulary_registry = getVocabularyRegistry()
34 vocab = vocabulary_registry.get(None, 'AllUserTeamsParticipation')
35- store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
36- result = []
37- for person in store.find(Person):
38- result.extend(
39- term.value for term in vocab.searchForTerms(person.name))
40- return result
41+ return [p for p in IStore(Person).find(Person) if p in vocab]
42
43 def test_user_no_team(self):
44 user = self.factory.makePerson()
45@@ -167,22 +159,15 @@
46 def test_user_in_two_teams(self):
47 user = self.factory.makePerson()
48 login_person(user)
49- team1 = self.factory.makeTeam()
50- user.join(team1)
51- team2 = self.factory.makeTeam()
52- user.join(team2)
53- self.assertEqual(set([team1, team2]), set(self._vocabTermValues()))
54+ team1 = self.factory.makeTeam(members=[user])
55+ team2 = self.factory.makeTeam(members=[user])
56+ self.assertContentEqual([team1, team2], set(self._vocabTermValues()))
57
58 def test_user_in_private_teams(self):
59 # Private teams are included in the vocabulary.
60 user = self.factory.makePerson()
61- team_owner = self.factory.makePerson()
62- login_person(team_owner)
63- team = self.factory.makeTeam(owner=team_owner)
64- team.addMember(person=user, reviewer=team_owner)
65- # Launchpad admin rights are needed to create private teams.
66- login('foo.bar@canonical.com')
67- team.visibility = PersonVisibility.PRIVATE
68+ team = self.factory.makeTeam(
69+ members=[user], visibility=PersonVisibility.PRIVATE)
70 login_person(user)
71 self.assertEqual([team], self._vocabTermValues())
72
73@@ -190,3 +175,11 @@
74 # AllUserTeamsPariticipationVocabulary is empty for anoymous users.
75 login(ANONYMOUS)
76 self.assertEqual([], self._vocabTermValues())
77+
78+ def test_commercial_admin(self):
79+ # The vocab does the membership check for commercial admins too.
80+ user = self.factory.makeCommercialAdmin()
81+ com_admins = getUtility(IPersonSet).getByName('commercial-admins')
82+ team1 = self.factory.makeTeam(members=[user])
83+ login_person(user)
84+ self.assertContentEqual([com_admins, team1], self._vocabTermValues())
85
86=== modified file 'lib/lp/registry/vocabularies.py'
87--- lib/lp/registry/vocabularies.py 2012-09-20 13:59:25 +0000
88+++ lib/lp/registry/vocabularies.py 2012-09-26 00:57:23 +0000
89@@ -531,6 +531,7 @@
90 # This is what subclasses must change if they want any extra filtering of
91 # results.
92 extra_clause = True
93+ extra_tables = ()
94
95 # Subclasses should override this property to allow null searches to
96 # return all results. If false, an empty result set is returned.
97@@ -609,6 +610,7 @@
98 SQL("%s.id = Person.id" % self.cache_table_name)),
99 ]
100 tables.extend(private_tables)
101+ tables.extend(self.extra_tables)
102 result = self.store.using(*tables).find(
103 Person,
104 And(
105@@ -716,6 +718,7 @@
106 LeftJoin(EmailAddress, EmailAddress.person == Person.id),
107 LeftJoin(Account, Account.id == Person.accountID),
108 ]
109+ public_tables.extend(self.extra_tables)
110
111 # If private_tables is empty, we are searching for all private
112 # teams. We can simply append the private query component to the
113@@ -834,7 +837,7 @@
114 Person.merged == None
115 )
116
117- tables = [Person] + private_tables
118+ tables = [Person] + private_tables + list(self.extra_tables)
119
120 if not text:
121 query = And(base_query,
122@@ -1016,10 +1019,14 @@
123 if user is None:
124 self.extra_clause = False
125 else:
126- self.extra_clause = AND(
127- super(AllUserTeamsParticipationVocabulary, self).extra_clause,
128- TeamParticipation.person == user.id,
129- TeamParticipation.team == Person.id)
130+ # TeamParticipation might already be used for private team
131+ # access checks, so alias and join it separately here.
132+ tp_alias = ClassAlias(TeamParticipation)
133+ self.extra_tables = [
134+ Join(
135+ tp_alias,
136+ And(tp_alias.teamID == Person.id,
137+ tp_alias.personID == user.id))]
138
139
140 class PersonActiveMembershipVocabulary: