launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #27863
[Merge] ~lgp171188/launchpad:allow-locking-down-bug-edits into launchpad:master
Guruprasad has proposed merging ~lgp171188/launchpad:allow-locking-down-bug-edits into launchpad:master.
Commit message:
Optimize _has_any_bug_role to do bulk team membership checks
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~lgp171188/launchpad/+git/launchpad/+merge/413580
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~lgp171188/launchpad:allow-locking-down-bug-edits into launchpad:master.
diff --git a/lib/lp/bugs/security.py b/lib/lp/bugs/security.py
index ace8336..a21f10a 100644
--- a/lib/lp/bugs/security.py
+++ b/lib/lp/bugs/security.py
@@ -123,11 +123,8 @@ class AppendBug(AuthorizationBase):
def _has_any_bug_role(user, tasks):
"""Return True if the user has any role on any of these bug tasks."""
- # XXX cjwatson 2019-03-26: This is inefficient for bugs with many
- # targets. However, we only get here if we can't easily establish that
- # the user seems legitimate, so it shouldn't be a big problem in
- # practice. We can optimise this further if it turns out to matter.
targets = [task.pillar for task in tasks]
+ bug_target_roles = {}
for target in targets:
roles = []
if IHasOwner.providedBy(target):
@@ -136,8 +133,10 @@ def _has_any_bug_role(user, tasks):
roles.append('driver')
if IHasBugSupervisor.providedBy(target):
roles.append('bug_supervisor')
- if user.isOneOf(target, roles):
- return True
+ bug_target_roles[target] = roles
+
+ if user.hasAnyRole(bug_target_roles):
+ return True
return False
diff --git a/lib/lp/registry/interfaces/person.py b/lib/lp/registry/interfaces/person.py
index 540e777..02b9a99 100644
--- a/lib/lp/registry/interfaces/person.py
+++ b/lib/lp/registry/interfaces/person.py
@@ -1247,6 +1247,12 @@ class IPersonViewRestricted(IHasBranches, IHasSpecifications,
`False` is returned.
"""
+ # XXX: lgp171188, 2022-01-04: Unexported for the same reasons
+ # as the inTeam() method.
+ def inAnyTeam(teams):
+ """Is this person a member of any of the given `teams`?"""
+
+
def clearInTeamCache():
"""Clears the person's inTeam cache.
@@ -2417,6 +2423,9 @@ class IPersonSetPublic(Interface):
Return None if there is no person with the given name.
"""
+ def getByNames(self, names):
+ """Return the persons with the given names."""
+
def getByAccount(account):
"""Return the `IPerson` with the given account, or None."""
diff --git a/lib/lp/registry/interfaces/role.py b/lib/lp/registry/interfaces/role.py
index caa0e1b..f78276b 100644
--- a/lib/lp/registry/interfaces/role.py
+++ b/lib/lp/registry/interfaces/role.py
@@ -146,3 +146,5 @@ class IPersonRoles(Interface):
:param obj: The object to check the relation to.
:param attributes: A list of attribute names to check with inTeam.
"""
+ def hasAnyRole(target_roles):
+ """Does this person have one of the roles for the given targets?"""
diff --git a/lib/lp/registry/model/person.py b/lib/lp/registry/model/person.py
index 1a0c380..97c68ea 100644
--- a/lib/lp/registry/model/person.py
+++ b/lib/lp/registry/model/person.py
@@ -1342,6 +1342,48 @@ class Person(
self._inTeam_cache[team.id] = in_team
return in_team
+ def inAnyTeam(self, teams):
+ """See `IPerson`."""
+ if not teams:
+ return False
+
+ # Translate each team name to an ITeam if we were passed
+ # a list of teams.
+ if all(isinstance(team, str) for team in teams):
+ teams = PersonSet.getByNames(teams)
+ if not teams:
+ return False
+
+ team_ids = {team.id for team in teams if team.is_team}
+ if self.id in team_ids:
+ # A team is always a member of itself.
+ return True
+
+ if self._inTeam_cache is None:
+ # Initialize cache
+ self._inTeam_cache = {}
+ else:
+ if any(self._inTeam_cache.get(team_id) for team_id in team_ids):
+ return True
+
+ team_participations = IStore(TeamParticipation).find(
+ TeamParticipation,
+ And(
+ TeamParticipation.teamID.is_in(team_ids),
+ TeamParticipation.personID == self.id
+ )
+ ).config(distinct=True)
+
+ in_any_team = False
+
+ for team_participation in team_participations:
+ in_any_team = True
+ if team_participation.team.id not in self._inTeam_cache:
+ self._inTeam_cache[team_participation.team.id] = True
+
+ return in_any_team
+
+
def hasParticipationEntryFor(self, team):
"""See `IPerson`."""
return bool(TeamParticipation.selectOneBy(person=self, team=team))
@@ -3638,6 +3680,9 @@ class PersonSet:
query = And(query, Person.mergedID == None)
return Person.selectOne(query)
+ def getByNames(self, names):
+ return IStore(self).find(Person, Person.name.is_in(names))
+
def getByAccount(self, account):
"""See `IPersonSet`."""
return Person.selectOne(Person.q.accountID == account.id)
diff --git a/lib/lp/registry/model/personroles.py b/lib/lp/registry/model/personroles.py
index f8bf273..bfb631f 100644
--- a/lib/lp/registry/model/personroles.py
+++ b/lib/lp/registry/model/personroles.py
@@ -29,6 +29,7 @@ class PersonRoles:
self._celebrities = getUtility(ILaunchpadCelebrities)
# Use an unproxied inTeam() method for security checks.
self.inTeam = removeSecurityProxy(self.person).inTeam
+ self.inAnyTeam = removeSecurityProxy(self.person).inAnyTeam
def __getattr__(self, name):
"""Handle all in_* attributes."""
@@ -71,3 +72,15 @@ class PersonRoles:
if self.inTeam(role):
return True
return False
+
+ def hasAnyRole(self, bug_target_roles):
+ """See IPersonRoles."""
+ teams = []
+ for bug_target, roles in bug_target_roles.items():
+ for role_ in roles:
+ role = getattr(bug_target, role_)
+ if role:
+ teams.append(role)
+ if self.inAnyTeam(teams):
+ return True
+ return False
Follow ups