Merge ~cjwatson/launchpad:more-distribution-traversal-policies into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: 7c07182750a79cbf0e4958d3a6df8aa306fc6bb8
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:more-distribution-traversal-policies
Merge into: launchpad:master
Prerequisite: ~cjwatson/launchpad:distribution-traversal
Diff against target: 363 lines (+219/-19)
6 files modified
lib/lp/registry/browser/configure.zcml (+2/-4)
lib/lp/registry/browser/distribution.py (+32/-12)
lib/lp/registry/browser/distributionsourcepackage.py (+34/-1)
lib/lp/registry/browser/ociproject.py (+34/-1)
lib/lp/registry/browser/tests/test_distribution.py (+103/-1)
lib/lp/registry/enums.py (+14/-0)
Reviewer Review Type Date Requested Status
Thiago F. Pappacena (community) Approve
Review via email: mp+393809@code.launchpad.net

Commit message

Add distro traversal policies for source packages and OCI projects

Description of the change

These allow distributions to be configured so that their default traversal resolves to a DistributionSourcePackage or to an OCIProject, depending on the kinds of structures that the distribution most commonly contains.

To post a comment you must log in.
Revision history for this message
Thiago F. Pappacena (pappacena) wrote :

LGTM

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/browser/configure.zcml b/lib/lp/registry/browser/configure.zcml
2index 777652f..7c97dc5 100644
3--- a/lib/lp/registry/browser/configure.zcml
4+++ b/lib/lp/registry/browser/configure.zcml
5@@ -543,8 +543,7 @@
6 />
7 <browser:url
8 for="lp.registry.interfaces.distributionsourcepackage.IDistributionSourcePackage"
9- path_expression="string:+source/${name}"
10- attribute_to_parent="distribution"
11+ urldata="lp.registry.browser.distributionsourcepackage.DistributionSourcePackageURL"
12 />
13 <browser:navigation
14 module="lp.registry.browser.distributionsourcepackage"
15@@ -607,8 +606,7 @@
16 />
17 <browser:url
18 for="lp.registry.interfaces.ociproject.IOCIProject"
19- path_expression="string:+oci/${name}"
20- attribute_to_parent="pillar"
21+ urldata="lp.registry.browser.ociproject.OCIProjectURL"
22 />
23 <browser:url
24 for="lp.registry.interfaces.ociprojectseries.IOCIProjectSeries"
25diff --git a/lib/lp/registry/browser/distribution.py b/lib/lp/registry/browser/distribution.py
26index 04d6dc1..e401ab8 100644
27--- a/lib/lp/registry/browser/distribution.py
28+++ b/lib/lp/registry/browser/distribution.py
29@@ -159,11 +159,25 @@ class DistributionNavigation(
30
31 @stepthrough('+source')
32 def traverse_sources(self, name):
33- return self.context.getSourcePackage(name)
34+ dsp = self.context.getSourcePackage(name)
35+ policy = self.context.default_traversal_policy
36+ if (policy == DistributionDefaultTraversalPolicy.SOURCE_PACKAGE and
37+ not self.context.redirect_default_traversal):
38+ return self.redirectSubTree(
39+ canonical_url(dsp, request=self.request), status=303)
40+ else:
41+ return dsp
42
43 @stepthrough('+oci')
44 def traverse_oci(self, name):
45- return self.context.getOCIProject(name)
46+ oci_project = self.context.getOCIProject(name)
47+ policy = self.context.default_traversal_policy
48+ if (policy == DistributionDefaultTraversalPolicy.OCI_PROJECT and
49+ not self.context.redirect_default_traversal):
50+ return self.redirectSubTree(
51+ canonical_url(oci_project, request=self.request), status=303)
52+ else:
53+ return oci_project
54
55 @stepthrough('+milestone')
56 def traverse_milestone(self, name):
57@@ -203,19 +217,25 @@ class DistributionNavigation(
58 return series
59
60 def traverse(self, name):
61- series, redirect = self._resolveSeries(name)
62- if series is None:
63+ policy = self.context.default_traversal_policy
64+ if policy == DistributionDefaultTraversalPolicy.SERIES:
65+ obj, redirect = self._resolveSeries(name)
66+ elif policy == DistributionDefaultTraversalPolicy.SOURCE_PACKAGE:
67+ obj = self.context.getSourcePackage(name)
68+ redirect = False
69+ elif policy == DistributionDefaultTraversalPolicy.OCI_PROJECT:
70+ obj = self.context.getOCIProject(name)
71+ redirect = False
72+ else:
73+ raise AssertionError(
74+ "Unknown default traversal policy %r" % policy)
75+ if obj is None:
76 return None
77- if not redirect:
78- policy = self.context.default_traversal_policy
79- if (policy == DistributionDefaultTraversalPolicy.SERIES and
80- self.context.redirect_default_traversal):
81- redirect = True
82- if redirect:
83+ if redirect or self.context.redirect_default_traversal:
84 return self.redirectSubTree(
85- canonical_url(series, request=self.request), status=303)
86+ canonical_url(obj, request=self.request), status=303)
87 else:
88- return series
89+ return obj
90
91
92 class DistributionSetNavigation(Navigation):
93diff --git a/lib/lp/registry/browser/distributionsourcepackage.py b/lib/lp/registry/browser/distributionsourcepackage.py
94index d70467b..df3cd55 100644
95--- a/lib/lp/registry/browser/distributionsourcepackage.py
96+++ b/lib/lp/registry/browser/distributionsourcepackage.py
97@@ -13,6 +13,7 @@ __all__ = [
98 'DistributionSourcePackageNavigation',
99 'DistributionSourcePackageOverviewMenu',
100 'DistributionSourcePackagePublishingHistoryView',
101+ 'DistributionSourcePackageURL',
102 'DistributionSourcePackageView',
103 'PublishingHistoryViewMixin',
104 ]
105@@ -53,6 +54,7 @@ from lp.bugs.interfaces.bugtasksearch import BugTaskSearchParams
106 from lp.code.browser.vcslisting import TargetDefaultVCSNavigationMixin
107 from lp.registry.browser import add_subscribe_link
108 from lp.registry.browser.pillar import PillarBugsMenu
109+from lp.registry.enums import DistributionDefaultTraversalPolicy
110 from lp.registry.interfaces.distributionsourcepackage import (
111 IDistributionSourcePackage,
112 )
113@@ -69,7 +71,10 @@ from lp.services.webapp import (
114 )
115 from lp.services.webapp.batching import BatchNavigator
116 from lp.services.webapp.breadcrumb import Breadcrumb
117-from lp.services.webapp.interfaces import IMultiFacetedBreadcrumb
118+from lp.services.webapp.interfaces import (
119+ ICanonicalUrlData,
120+ IMultiFacetedBreadcrumb,
121+ )
122 from lp.services.webapp.menu import (
123 ApplicationMenu,
124 enabled_with_permission,
125@@ -89,6 +94,34 @@ from lp.translations.browser.customlanguagecode import (
126 )
127
128
129+@implementer(ICanonicalUrlData)
130+class DistributionSourcePackageURL:
131+ """Distribution source package URL creation rules.
132+
133+ The canonical URL for a distribution source package depends on the
134+ values of `default_traversal_policy` and `redirect_default_traversal` on
135+ the context distribution.
136+ """
137+
138+ rootsite = None
139+
140+ def __init__(self, context):
141+ self.context = context
142+
143+ @property
144+ def inside(self):
145+ return self.context.distribution
146+
147+ @property
148+ def path(self):
149+ policy = self.context.distribution.default_traversal_policy
150+ if (policy == DistributionDefaultTraversalPolicy.SOURCE_PACKAGE and
151+ not self.context.distribution.redirect_default_traversal):
152+ return self.context.name
153+ else:
154+ return u"+source/%s" % self.context.name
155+
156+
157 class DistributionSourcePackageFormatterAPI(CustomizableFormatter):
158 """Adapt IDistributionSourcePackage objects to a formatted string."""
159
160diff --git a/lib/lp/registry/browser/ociproject.py b/lib/lp/registry/browser/ociproject.py
161index 0d23a15..fe5f8b4 100644
162--- a/lib/lp/registry/browser/ociproject.py
163+++ b/lib/lp/registry/browser/ociproject.py
164@@ -12,6 +12,7 @@ __all__ = [
165 'OCIProjectFacets',
166 'OCIProjectNavigation',
167 'OCIProjectNavigationMenu',
168+ 'OCIProjectURL',
169 ]
170
171 from zope.component import getUtility
172@@ -28,6 +29,7 @@ from lp.app.browser.tales import CustomizableFormatter
173 from lp.app.errors import NotFoundError
174 from lp.code.browser.vcslisting import TargetDefaultVCSNavigationMixin
175 from lp.oci.interfaces.ocirecipe import IOCIRecipeSet
176+from lp.registry.enums import DistributionDefaultTraversalPolicy
177 from lp.registry.interfaces.distribution import IDistribution
178 from lp.registry.interfaces.ociproject import (
179 IOCIProject,
180@@ -55,7 +57,38 @@ from lp.services.webapp import (
181 )
182 from lp.services.webapp.batching import BatchNavigator
183 from lp.services.webapp.breadcrumb import Breadcrumb
184-from lp.services.webapp.interfaces import IMultiFacetedBreadcrumb
185+from lp.services.webapp.interfaces import (
186+ ICanonicalUrlData,
187+ IMultiFacetedBreadcrumb,
188+ )
189+
190+
191+@implementer(ICanonicalUrlData)
192+class OCIProjectURL:
193+ """OCI project URL creation rules.
194+
195+ The canonical URL for an OCI project in a distribution depends on the
196+ values of `default_traversal_policy` and `redirect_default_traversal` on
197+ the context distribution.
198+ """
199+
200+ rootsite = None
201+
202+ def __init__(self, context):
203+ self.context = context
204+
205+ @property
206+ def inside(self):
207+ return self.context.pillar
208+
209+ @property
210+ def path(self):
211+ if self.context.distribution is not None:
212+ policy = self.context.distribution.default_traversal_policy
213+ if (policy == DistributionDefaultTraversalPolicy.OCI_PROJECT and
214+ not self.context.distribution.redirect_default_traversal):
215+ return self.context.name
216+ return u"+oci/%s" % self.context.name
217
218
219 def getPillarFieldName(pillar):
220diff --git a/lib/lp/registry/browser/tests/test_distribution.py b/lib/lp/registry/browser/tests/test_distribution.py
221index b92d7b2..d465e16 100644
222--- a/lib/lp/registry/browser/tests/test_distribution.py
223+++ b/lib/lp/registry/browser/tests/test_distribution.py
224@@ -25,7 +25,10 @@ from zope.schema.vocabulary import SimpleVocabulary
225 from zope.security.proxy import removeSecurityProxy
226
227 from lp.app.browser.lazrjs import vocabulary_to_choice_edit_items
228-from lp.registry.enums import EXCLUSIVE_TEAM_POLICY
229+from lp.registry.enums import (
230+ DistributionDefaultTraversalPolicy,
231+ EXCLUSIVE_TEAM_POLICY,
232+ )
233 from lp.registry.interfaces.distroseries import IDistroSeries
234 from lp.registry.interfaces.ociproject import OCI_PROJECT_ALLOW_CREATE
235 from lp.registry.interfaces.series import SeriesStatus
236@@ -224,6 +227,105 @@ class TestDistributionNavigation(TestCaseWithFactory):
237 distroarchseries_url, distroarchseries,
238 environ={"HTTPS": "on", "SERVER_URL": None})
239
240+ def test_short_source_url(self):
241+ dsp = self.factory.makeDistributionSourcePackage()
242+ dsp.distribution.default_traversal_policy = (
243+ DistributionDefaultTraversalPolicy.SOURCE_PACKAGE)
244+ obj, _, _ = test_traverse(
245+ "http://launchpad.test/%s/%s" % (
246+ dsp.distribution.name, dsp.name))
247+ self.assertEqual(dsp, obj)
248+
249+ def test_short_source_url_redirects(self):
250+ dsp = self.factory.makeDistributionSourcePackage()
251+ dsp.distribution.default_traversal_policy = (
252+ DistributionDefaultTraversalPolicy.SOURCE_PACKAGE)
253+ dsp.distribution.redirect_default_traversal = True
254+ self.assertRedirects(
255+ "http://launchpad.test/%s/%s" % (
256+ dsp.distribution.name, dsp.name),
257+ "http://launchpad.test/%s/+source/%s" % (
258+ dsp.distribution.name, dsp.name))
259+
260+ def test_long_non_default_source_url(self):
261+ dsp = self.factory.makeDistributionSourcePackage()
262+ obj, _, _ = test_traverse(
263+ "http://launchpad.test/%s/+source/%s" % (
264+ dsp.distribution.name, dsp.name))
265+ self.assertEqual(dsp, obj)
266+
267+ def test_long_default_source_url(self):
268+ dsp = self.factory.makeDistributionSourcePackage()
269+ dsp.distribution.default_traversal_policy = (
270+ DistributionDefaultTraversalPolicy.SOURCE_PACKAGE)
271+ dsp.distribution.redirect_default_traversal = True
272+ obj, _, _ = test_traverse(
273+ "http://launchpad.test/%s/+source/%s" % (
274+ dsp.distribution.name, dsp.name))
275+ self.assertEqual(dsp, obj)
276+
277+ def test_long_default_source_url_redirects(self):
278+ dsp = self.factory.makeDistributionSourcePackage()
279+ dsp.distribution.default_traversal_policy = (
280+ DistributionDefaultTraversalPolicy.SOURCE_PACKAGE)
281+ self.assertRedirects(
282+ "http://launchpad.test/%s/+source/%s" % (
283+ dsp.distribution.name, dsp.name),
284+ "http://launchpad.test/%s/%s" % (
285+ dsp.distribution.name, dsp.name))
286+
287+ def test_short_oci_url(self):
288+ oci_project = self.factory.makeOCIProject(
289+ pillar=self.factory.makeDistribution())
290+ oci_project.distribution.default_traversal_policy = (
291+ DistributionDefaultTraversalPolicy.OCI_PROJECT)
292+ obj, _, _ = test_traverse(
293+ "http://launchpad.test/%s/%s" % (
294+ oci_project.distribution.name, oci_project.name))
295+ self.assertEqual(oci_project, obj)
296+
297+ def test_short_oci_url_redirects(self):
298+ oci_project = self.factory.makeOCIProject(
299+ pillar=self.factory.makeDistribution())
300+ oci_project.distribution.default_traversal_policy = (
301+ DistributionDefaultTraversalPolicy.OCI_PROJECT)
302+ oci_project.distribution.redirect_default_traversal = True
303+ self.assertRedirects(
304+ "http://launchpad.test/%s/%s" % (
305+ oci_project.distribution.name, oci_project.name),
306+ "http://launchpad.test/%s/+oci/%s" % (
307+ oci_project.distribution.name, oci_project.name))
308+
309+ def test_long_non_default_oci_url(self):
310+ oci_project = self.factory.makeOCIProject(
311+ pillar=self.factory.makeDistribution())
312+ obj, _, _ = test_traverse(
313+ "http://launchpad.test/%s/+oci/%s" % (
314+ oci_project.distribution.name, oci_project.name))
315+ self.assertEqual(oci_project, obj)
316+
317+ def test_long_default_oci_url(self):
318+ oci_project = self.factory.makeOCIProject(
319+ pillar=self.factory.makeDistribution())
320+ oci_project.distribution.default_traversal_policy = (
321+ DistributionDefaultTraversalPolicy.OCI_PROJECT)
322+ oci_project.distribution.redirect_default_traversal = True
323+ obj, _, _ = test_traverse(
324+ "http://launchpad.test/%s/+oci/%s" % (
325+ oci_project.distribution.name, oci_project.name))
326+ self.assertEqual(oci_project, obj)
327+
328+ def test_long_default_oci_url_redirects(self):
329+ oci_project = self.factory.makeOCIProject(
330+ pillar=self.factory.makeDistribution())
331+ oci_project.distribution.default_traversal_policy = (
332+ DistributionDefaultTraversalPolicy.OCI_PROJECT)
333+ self.assertRedirects(
334+ "http://launchpad.test/%s/+oci/%s" % (
335+ oci_project.distribution.name, oci_project.name),
336+ "http://launchpad.test/%s/%s" % (
337+ oci_project.distribution.name, oci_project.name))
338+
339
340 class TestDistributionPage(TestCaseWithFactory):
341 """A TestCase for the distribution index page."""
342diff --git a/lib/lp/registry/enums.py b/lib/lp/registry/enums.py
343index 30f4a79..b71dec6 100644
344--- a/lib/lp/registry/enums.py
345+++ b/lib/lp/registry/enums.py
346@@ -442,3 +442,17 @@ class DistributionDefaultTraversalPolicy(DBEnumeratedType):
347 The default traversal from a distribution is used for series of that
348 distribution.
349 """)
350+
351+ SOURCE_PACKAGE = DBItem(1, """
352+ Source package
353+
354+ The default traversal from a distribution is used for source
355+ packages in that distribution.
356+ """)
357+
358+ OCI_PROJECT = DBItem(2, """
359+ OCI project
360+
361+ The default traversal from a distribution is used for OCI projects
362+ in that distribution.
363+ """)

Subscribers

People subscribed via source and target branches

to status/vote changes: