Merge ~cjwatson/launchpad:refactor-searchPPAs into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: cb167cf493fc9be53375cc7efa52e4a84dfe3ffe
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:refactor-searchPPAs
Merge into: launchpad:master
Diff against target: 188 lines (+91/-36)
2 files modified
lib/lp/registry/model/distribution.py (+21/-36)
lib/lp/registry/tests/test_distribution.py (+70/-0)
Reviewer Review Type Date Requested Status
Ines Almeida Approve
Review via email: mp+450704@code.launchpad.net

Commit message

Refactor Distribution.searchPPAs to use get_enabled_archive_filter

Description of the change

This doesn't behave quite identically to the previous code, but I think the changes are slight improvements: search results for commercial admins include private archives to reflect their view privileges, and search results for ordinary users include archives where they have upload permissions even if they don't own them.

Those are niche changes, though, and this is mainly just intended as a refactoring. My motivations here are to get rid of some more SQLObject-flavoured code, and that it's easier to understand code when it uses well-established helper functions for moderately complex tasks rather than having open-coded near-equivalents for them.

To post a comment you must log in.
Revision history for this message
Ines Almeida (ines-almeida) wrote :

The changes make sense to me, and look good code wise.

The only thing I see "missing" is test changes, which suggests that maybe we don't really have a lot of coverage on the `searchPPAs` function specifically. I think it would benefit from some edge cases tests that reflects these changes.

review: Approve
cb167cf... by Colin Watson

Add a few unit tests for Distribution.searchPPAs

Revision history for this message
Colin Watson (cjwatson) wrote :

Yeah, that's a reasonable point. There are some doctests, but those are annoying to extend. How's this?

Revision history for this message
Ines Almeida (ines-almeida) wrote :

Tests look good to me, thanks!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/registry/model/distribution.py b/lib/lp/registry/model/distribution.py
2index 44085f9..bd3e4fe 100644
3--- a/lib/lp/registry/model/distribution.py
4+++ b/lib/lp/registry/model/distribution.py
5@@ -17,6 +17,7 @@ from storm.expr import (
6 SQL,
7 And,
8 Coalesce,
9+ Column,
10 Desc,
11 Exists,
12 Func,
13@@ -26,6 +27,7 @@ from storm.expr import (
14 Not,
15 Or,
16 Select,
17+ Table,
18 )
19 from storm.info import ClassAlias
20 from storm.locals import Int, List, Reference
21@@ -183,7 +185,7 @@ from lp.soyuz.interfaces.archivepermission import IArchivePermissionSet
22 from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuildSet
23 from lp.soyuz.interfaces.binarypackagename import IBinaryPackageNameSet
24 from lp.soyuz.interfaces.publishing import active_publishing_status
25-from lp.soyuz.model.archive import Archive
26+from lp.soyuz.model.archive import Archive, get_enabled_archive_filter
27 from lp.soyuz.model.archivefile import ArchiveFile
28 from lp.soyuz.model.distributionsourcepackagerelease import (
29 DistributionSourcePackageRelease,
30@@ -1847,55 +1849,38 @@ class Distribution(
31
32 def searchPPAs(self, text=None, show_inactive=False, user=None):
33 """See `IDistribution`."""
34+ ValidPersonOrTeamCache = Table("ValidPersonOrTeamCache")
35 clauses = [
36- """
37- Archive.purpose = %s AND
38- Archive.distribution = %s AND
39- Archive.owner = ValidPersonOrTeamCache.id
40- """
41- % sqlvalues(ArchivePurpose.PPA, self)
42+ Archive.distribution == self,
43+ Archive.owner == Column("id", ValidPersonOrTeamCache),
44 ]
45
46- clauseTables = ["ValidPersonOrTeamCache"]
47- orderBy = ["Archive.displayname"]
48+ order_by = [Archive.displayname]
49
50 if not show_inactive:
51 clauses.append(
52- """
53- Archive.id IN (
54- SELECT archive FROM SourcepackagePublishingHistory
55- WHERE status IN %s)
56- """
57- % sqlvalues(active_publishing_status)
58+ Archive.id.is_in(
59+ Select(
60+ SourcePackagePublishingHistory.archive_id,
61+ SourcePackagePublishingHistory.status.is_in(
62+ active_publishing_status
63+ ),
64+ )
65+ )
66 )
67
68 if text:
69- orderBy.insert(0, rank_by_fti(Archive, text))
70+ order_by.insert(0, rank_by_fti(Archive, text))
71 clauses.append(fti_search(Archive, text))
72
73- if user is not None:
74- if not user.inTeam(getUtility(ILaunchpadCelebrities).admin):
75- clauses.append(
76- """
77- ((Archive.private = FALSE AND Archive.enabled = TRUE) OR
78- Archive.owner = %s OR
79- %s IN (SELECT TeamParticipation.person
80- FROM TeamParticipation
81- WHERE TeamParticipation.person = %s AND
82- TeamParticipation.team = Archive.owner)
83- )
84- """
85- % sqlvalues(user, user, user)
86- )
87- else:
88- clauses.append(
89- "Archive.private = FALSE AND Archive.enabled = TRUE"
90+ clauses.append(
91+ get_enabled_archive_filter(
92+ user, purpose=ArchivePurpose.PPA, include_public=True
93 )
94-
95- return Archive.select(
96- And(*clauses), orderBy=orderBy, clauseTables=clauseTables
97 )
98
99+ return IStore(Archive).find(Archive, *clauses).order_by(order_by)
100+
101 def getPendingAcceptancePPAs(self):
102 """See `IDistribution`."""
103 query = """
104diff --git a/lib/lp/registry/tests/test_distribution.py b/lib/lp/registry/tests/test_distribution.py
105index cce72bf..10810e7 100644
106--- a/lib/lp/registry/tests/test_distribution.py
107+++ b/lib/lp/registry/tests/test_distribution.py
108@@ -79,6 +79,7 @@ from lp.services.webapp.interfaces import OAuthPermission
109 from lp.services.worlddata.interfaces.country import ICountrySet
110 from lp.soyuz.enums import ArchivePurpose, PackagePublishingStatus
111 from lp.soyuz.interfaces.archive import IArchiveSet
112+from lp.soyuz.interfaces.archivepermission import IArchivePermissionSet
113 from lp.soyuz.interfaces.distributionsourcepackagerelease import (
114 IDistributionSourcePackageRelease,
115 )
116@@ -2848,3 +2849,72 @@ class TestDistributionPublishedSources(TestCaseWithFactory):
117 # source packages to them should not affect the number of queries
118 # executed here.
119 self.assertThat(recorder2, HasQueryCount.byEquality(recorder1))
120+
121+
122+class TestDistributionSearchPPAs(TestCaseWithFactory):
123+ # XXX cjwatson 2023-09-11: See also lib/lp/soyuz/doc/distribution.rst
124+ # and lib/lp/soyuz/doc/distribution.rst for related doctests which
125+ # haven't yet been turned into unit tests.
126+
127+ layer = DatabaseFunctionalLayer
128+
129+ def test_private_invisible_by_unprivileged_user(self):
130+ distribution = self.factory.makeDistribution()
131+ archive = self.factory.makeArchive(
132+ distribution=distribution, private=True
133+ )
134+ with person_logged_in(self.factory.makePerson()) as user:
135+ self.assertNotIn(
136+ archive, distribution.searchPPAs(user=user, show_inactive=True)
137+ )
138+
139+ def test_private_visible_by_owner(self):
140+ distribution = self.factory.makeDistribution()
141+ owner = self.factory.makePerson()
142+ archive = self.factory.makeArchive(
143+ distribution=distribution, owner=owner, private=True
144+ )
145+ with person_logged_in(owner):
146+ self.assertIn(
147+ archive,
148+ distribution.searchPPAs(user=owner, show_inactive=True),
149+ )
150+
151+ def test_private_visible_by_uploader(self):
152+ distribution = self.factory.makeDistribution()
153+ archive = self.factory.makeArchive(
154+ distribution=distribution, private=True
155+ )
156+ uploader = self.factory.makePerson()
157+ getUtility(IArchivePermissionSet).newComponentUploader(
158+ archive, uploader, "main"
159+ )
160+ with person_logged_in(uploader):
161+ self.assertIn(
162+ archive,
163+ distribution.searchPPAs(user=uploader, show_inactive=True),
164+ )
165+
166+ def test_private_visible_by_commercial_admin(self):
167+ distribution = self.factory.makeDistribution()
168+ archive = self.factory.makeArchive(
169+ distribution=distribution, private=True
170+ )
171+ with celebrity_logged_in("commercial_admin") as commercial_admin:
172+ self.assertIn(
173+ archive,
174+ distribution.searchPPAs(
175+ user=commercial_admin, show_inactive=True
176+ ),
177+ )
178+
179+ def test_private_visible_by_admin(self):
180+ distribution = self.factory.makeDistribution()
181+ archive = self.factory.makeArchive(
182+ distribution=distribution, private=True
183+ )
184+ with celebrity_logged_in("admin") as admin:
185+ self.assertIn(
186+ archive,
187+ distribution.searchPPAs(user=admin, show_inactive=True),
188+ )

Subscribers

People subscribed via source and target branches

to status/vote changes: