Merge ~cjwatson/launchpad:oci-git-listing into launchpad:master

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: cf28f0901693e444f865ddc088b5b5815a9abb35
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~cjwatson/launchpad:oci-git-listing
Merge into: launchpad:master
Prerequisite: ~cjwatson/launchpad:oci-git-owner-default
Diff against target: 441 lines (+232/-14)
8 files modified
lib/lp/code/browser/configure.zcml (+22/-0)
lib/lp/code/browser/gitlisting.py (+15/-0)
lib/lp/code/browser/tests/test_gitlisting.py (+51/-7)
lib/lp/code/browser/tests/test_vcslisting.py (+56/-0)
lib/lp/code/browser/vcslisting.py (+18/-4)
lib/lp/code/templates/gitlisting.pt (+3/-3)
lib/lp/registry/browser/configure.zcml (+4/-0)
lib/lp/registry/browser/personociproject.py (+63/-0)
Reviewer Review Type Date Requested Status
Tom Wardill (community) Approve
Review via email: mp+376146@code.launchpad.net

Commit message

Add git listing views for OCI projects

To post a comment you must log in.
Revision history for this message
Tom Wardill (twom) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/code/browser/configure.zcml b/lib/lp/code/browser/configure.zcml
2index cbcd9ea..499a86a 100644
3--- a/lib/lp/code/browser/configure.zcml
4+++ b/lib/lp/code/browser/configure.zcml
5@@ -1047,6 +1047,12 @@
6 name="+git"
7 template="../templates/gitlisting.pt"/>
8 <browser:page
9+ for="lp.registry.interfaces.ociproject.IOCIProject"
10+ class="lp.code.browser.gitlisting.OCIProjectGitListingView"
11+ permission="zope.Public"
12+ name="+git"
13+ template="../templates/gitlisting.pt"/>
14+ <browser:page
15 for="lp.registry.interfaces.personproduct.IPersonProduct"
16 class="lp.code.browser.gitlisting.PersonTargetGitListingView"
17 permission="zope.Public"
18@@ -1059,6 +1065,12 @@
19 name="+git"
20 template="../templates/gitlisting.pt"/>
21 <browser:page
22+ for="lp.registry.interfaces.personociproject.IPersonOCIProject"
23+ class="lp.code.browser.gitlisting.PersonOCIProjectGitListingView"
24+ permission="zope.Public"
25+ name="+git"
26+ template="../templates/gitlisting.pt"/>
27+ <browser:page
28 for="lp.registry.interfaces.person.IPerson"
29 class="lp.code.browser.gitlisting.PlainGitListingView"
30 permission="zope.Public"
31@@ -1096,6 +1108,16 @@
32 name="+code"/>
33
34 <browser:defaultView
35+ for="lp.registry.interfaces.ociproject.IOCIProject"
36+ layer="lp.code.publisher.CodeLayer"
37+ name="+code"/>
38+
39+ <browser:defaultView
40+ for="lp.registry.interfaces.personociproject.IPersonOCIProject"
41+ layer="lp.code.publisher.CodeLayer"
42+ name="+code"/>
43+
44+ <browser:defaultView
45 for="lp.registry.interfaces.distribution.IDistribution"
46 layer="lp.code.publisher.CodeLayer"
47 name="+code"/>
48diff --git a/lib/lp/code/browser/gitlisting.py b/lib/lp/code/browser/gitlisting.py
49index 8e51d71..3827f73 100644
50--- a/lib/lp/code/browser/gitlisting.py
51+++ b/lib/lp/code/browser/gitlisting.py
52@@ -28,6 +28,7 @@ from lp.code.interfaces.gitrepository import IGitRepositorySet
53 from lp.registry.interfaces.persondistributionsourcepackage import (
54 IPersonDistributionSourcePackage,
55 )
56+from lp.registry.interfaces.personociproject import IPersonOCIProject
57 from lp.registry.interfaces.personproduct import IPersonProduct
58 from lp.services.config import config
59 from lp.services.propertycache import cachedproperty
60@@ -143,6 +144,8 @@ class PersonTargetGitListingView(BaseGitListingView):
61 return self.context.product
62 elif IPersonDistributionSourcePackage.providedBy(self.context):
63 return self.context.distro_source_package
64+ elif IPersonOCIProject.providedBy(self.context):
65+ return self.context.oci_project
66 else:
67 raise Exception("Unknown context: %r" % self.context)
68
69@@ -158,6 +161,12 @@ class PersonTargetGitListingView(BaseGitListingView):
70 return None
71
72
73+class OCIProjectGitListingView(TargetGitListingView):
74+
75+ # OCIProject:+branches doesn't exist.
76+ show_bzr_link = False
77+
78+
79 class PersonDistributionSourcePackageGitListingView(
80 PersonTargetGitListingView):
81
82@@ -165,6 +174,12 @@ class PersonDistributionSourcePackageGitListingView(
83 show_bzr_link = False
84
85
86+class PersonOCIProjectGitListingView(PersonTargetGitListingView):
87+
88+ # PersonOCIProject:+branches doesn't exist.
89+ show_bzr_link = False
90+
91+
92 class PlainGitListingView(BaseGitListingView):
93
94 page_title = 'Git'
95diff --git a/lib/lp/code/browser/tests/test_gitlisting.py b/lib/lp/code/browser/tests/test_gitlisting.py
96index 2552f73..72954ad 100644
97--- a/lib/lp/code/browser/tests/test_gitlisting.py
98+++ b/lib/lp/code/browser/tests/test_gitlisting.py
99@@ -13,6 +13,7 @@ from lp.app.enums import InformationType
100 from lp.code.enums import BranchMergeProposalStatus
101 from lp.code.interfaces.gitrepository import IGitRepositorySet
102 from lp.registry.enums import VCSType
103+from lp.registry.interfaces.personociproject import IPersonOCIProjectFactory
104 from lp.registry.model.persondistributionsourcepackage import (
105 PersonDistributionSourcePackage,
106 )
107@@ -34,6 +35,10 @@ class TestTargetGitListingView:
108
109 layer = DatabaseFunctionalLayer
110
111+ def setDefaultRepository(self, target, repository):
112+ getUtility(IGitRepositorySet).setDefaultRepository(
113+ target=target, repository=repository)
114+
115 def test_rendering(self):
116 main_repo = self.factory.makeGitRepository(
117 owner=self.owner, target=self.target, name="foo")
118@@ -53,8 +58,7 @@ class TestTargetGitListingView:
119 target=self.target, name="bar")
120
121 with admin_logged_in():
122- getUtility(IGitRepositorySet).setDefaultRepository(
123- target=self.target, repository=main_repo)
124+ self.setDefaultRepository(target=self.target, repository=main_repo)
125 getUtility(IGitRepositorySet).setDefaultRepositoryForOwner(
126 owner=other_repo.owner, target=self.target,
127 repository=other_repo, user=other_repo.owner)
128@@ -115,8 +119,7 @@ class TestTargetGitListingView:
129 self.factory.makeGitRefs(other_repo)
130
131 with admin_logged_in():
132- getUtility(IGitRepositorySet).setDefaultRepository(
133- target=self.target, repository=main_repo)
134+ self.setDefaultRepository(target=self.target, repository=main_repo)
135 getUtility(IGitRepositorySet).setDefaultRepositoryForOwner(
136 owner=other_repo.owner, target=self.target,
137 repository=other_repo, user=other_repo.owner)
138@@ -155,7 +158,7 @@ class TestTargetGitListingView:
139 other_repo = self.factory.makeGitRepository(
140 target=self.target, information_type=InformationType.PUBLIC)
141 with admin_logged_in():
142- getUtility(IGitRepositorySet).setDefaultRepository(
143+ self.setDefaultRepository(
144 target=self.target, repository=invisible_repo)
145
146 # An anonymous user can't see the default.
147@@ -339,8 +342,7 @@ class TestProductGitListingView(TestTargetGitListingView,
148 paths=["refs/heads/master", "refs/heads/1.0", "refs/tags/1.1"])
149
150 with admin_logged_in():
151- getUtility(IGitRepositorySet).setDefaultRepository(
152- target=self.target, repository=main_repo)
153+ self.setDefaultRepository(target=self.target, repository=main_repo)
154
155 self.factory.makeBranchMergeProposalForGit(
156 target_ref=git_refs[0],
157@@ -415,6 +417,48 @@ class TestPersonDistributionSourcePackageGitListingView(
158 self.assertNotIn('View Bazaar branches', view())
159
160
161+class TestOCIProjectGitListingView(
162+ TestTargetGitListingView, TestCaseWithFactory):
163+
164+ def setUp(self):
165+ super(TestOCIProjectGitListingView, self).setUp()
166+ self.owner = self.factory.makePerson(name="foowner")
167+ distro = self.factory.makeDistribution(name="foo", owner=self.owner)
168+ self.target = self.factory.makeOCIProject(
169+ pillar=distro, ociprojectname="bar")
170+ self.target_path = "foo/+oci/bar"
171+
172+ def setDefaultRepository(self, target, repository):
173+ getUtility(IGitRepositorySet).setDefaultRepository(
174+ target=target, repository=repository, force_oci=True)
175+
176+ def test_bzr_link(self):
177+ # There's no OCIProject:+branches, nor any ability to create Bazaar
178+ # branches for OCI projects.
179+ view = create_initialized_view(self.target, '+git')
180+ self.assertNotIn('View Bazaar branches', view())
181+
182+
183+class TestPersonOCIProjectGitListingView(
184+ TestPersonTargetGitListingView, TestCaseWithFactory):
185+
186+ def setUp(self):
187+ super(TestPersonOCIProjectGitListingView, self).setUp()
188+ self.owner = self.factory.makePerson(name="dev")
189+ distro = self.factory.makeDistribution(name="foo", owner=self.owner)
190+ self.target = self.factory.makeOCIProject(
191+ pillar=distro, ociprojectname="bar")
192+ self.target_path = "foo/+oci/bar"
193+ self.owner_target = getUtility(IPersonOCIProjectFactory).create(
194+ self.owner, self.target)
195+
196+ def test_bzr_link(self):
197+ # There's no PersonOCIProject:+branches, nor any ability to create
198+ # Bazaar branches for OCI projects.
199+ view = create_initialized_view(self.owner_target, '+git')
200+ self.assertNotIn('View Bazaar branches', view())
201+
202+
203 class TestPlainGitListingView:
204
205 layer = DatabaseFunctionalLayer
206diff --git a/lib/lp/code/browser/tests/test_vcslisting.py b/lib/lp/code/browser/tests/test_vcslisting.py
207index 2d7741d..127d9d3 100644
208--- a/lib/lp/code/browser/tests/test_vcslisting.py
209+++ b/lib/lp/code/browser/tests/test_vcslisting.py
210@@ -126,6 +126,62 @@ class TestPersonDistributionSourcePackageDefaultVCSView(TestCaseWithFactory):
211 self.assertCodeViewClass(VCSType.GIT, PersonTargetGitListingView)
212
213
214+class TestOCIProjectDefaultVCSView(TestCaseWithFactory):
215+ """Tests that OCIProject:+code delegates to +git.
216+
217+ This is regardless of the distribution's preferred VCS. It can't delegate
218+ to +branches, as OCIProject:+branches doesn't exist.
219+ """
220+
221+ layer = DatabaseFunctionalLayer
222+
223+ def assertCodeViewClass(self, vcs, cls):
224+ distro = self.factory.makeDistribution(vcs=vcs)
225+ oci_project = self.factory.makeOCIProject(pillar=distro)
226+ self.assertEqual(vcs, distro.vcs)
227+ view = test_traverse(
228+ '/%s/+oci/%s/+code' % (distro.name, oci_project.name))[1]
229+ self.assertIsInstance(view, cls)
230+
231+ def test_default_unset(self):
232+ self.assertCodeViewClass(None, TargetGitListingView)
233+
234+ def test_default_bzr(self):
235+ self.assertCodeViewClass(VCSType.BZR, TargetGitListingView)
236+
237+ def test_git(self):
238+ self.assertCodeViewClass(VCSType.GIT, TargetGitListingView)
239+
240+
241+class TestPersonOCIProjectDefaultVCSView(TestCaseWithFactory):
242+ """Tests that OCIProject:+code delegates to +git.
243+
244+ This is regardless of the distribution's preferred VCS. It can't
245+ delegate to +branches, as PersonOCIProject:+branches doesn't exist.
246+ """
247+
248+ layer = DatabaseFunctionalLayer
249+
250+ def assertCodeViewClass(self, vcs, cls):
251+ person = self.factory.makePerson()
252+ distro = self.factory.makeDistribution(vcs=vcs)
253+ oci_project = self.factory.makeOCIProject(pillar=distro)
254+ self.assertEqual(vcs, distro.vcs)
255+ view = test_traverse(
256+ '~%s/%s/+oci/%s/+code'
257+ % (person.name, distro.name, oci_project.name))[1]
258+ self.assertIsInstance(view, cls)
259+
260+ def test_default_unset(self):
261+ self.assertCodeViewClass(None, PersonTargetGitListingView)
262+
263+ def test_default_bzr(self):
264+ self.assertCodeViewClass(VCSType.BZR, PersonTargetGitListingView)
265+
266+ def test_git(self):
267+ self.assertCodeViewClass(VCSType.GIT, PersonTargetGitListingView)
268+
269+
270 class TestDistributionDefaultVCSView(TestCaseWithFactory):
271 """Tests that Distribution:+code delegates to +git or +branches."""
272
273diff --git a/lib/lp/code/browser/vcslisting.py b/lib/lp/code/browser/vcslisting.py
274index bc76a38..5291234 100644
275--- a/lib/lp/code/browser/vcslisting.py
276+++ b/lib/lp/code/browser/vcslisting.py
277@@ -8,9 +8,11 @@ __metaclass__ = type
278 from zope.component import queryMultiAdapter
279
280 from lp.registry.enums import VCSType
281+from lp.registry.interfaces.ociproject import IOCIProject
282 from lp.registry.interfaces.persondistributionsourcepackage import (
283 IPersonDistributionSourcePackage,
284 )
285+from lp.registry.interfaces.personociproject import IPersonOCIProject
286 from lp.registry.interfaces.personproduct import IPersonProduct
287 from lp.services.webapp import stepto
288
289@@ -19,9 +21,14 @@ class TargetDefaultVCSNavigationMixin:
290
291 @stepto("+code")
292 def traverse_code_view(self):
293- if self.context.pillar.vcs in (VCSType.BZR, None):
294+ if IOCIProject.providedBy(self.context):
295+ # OCI projects only support Git.
296+ vcs = VCSType.GIT
297+ else:
298+ vcs = self.context.pillar.vcs
299+ if vcs in (VCSType.BZR, None):
300 view_name = '+branches'
301- elif self.context.pillar.vcs == VCSType.GIT:
302+ elif vcs == VCSType.GIT:
303 view_name = '+git'
304 else:
305 raise AssertionError("Unknown VCS")
306@@ -37,11 +44,18 @@ class PersonTargetDefaultVCSNavigationMixin:
307 target = self.context.product
308 elif IPersonDistributionSourcePackage.providedBy(self.context):
309 target = self.context.distro_source_package
310+ elif IPersonOCIProject.providedBy(self.context):
311+ target = self.context.oci_project
312 else:
313 raise AssertionError("Unknown target: %r" % self.context)
314- if target.pillar.vcs in (VCSType.BZR, None):
315+ if IOCIProject.providedBy(target):
316+ # OCI projects only support Git.
317+ vcs = VCSType.GIT
318+ else:
319+ vcs = target.pillar.vcs
320+ if vcs in (VCSType.BZR, None):
321 view_name = '+branches'
322- elif target.pillar.vcs == VCSType.GIT:
323+ elif vcs == VCSType.GIT:
324 view_name = '+git'
325 else:
326 raise AssertionError("Unknown VCS")
327diff --git a/lib/lp/code/templates/gitlisting.pt b/lib/lp/code/templates/gitlisting.pt
328index 8afb6e1..583deef 100644
329--- a/lib/lp/code/templates/gitlisting.pt
330+++ b/lib/lp/code/templates/gitlisting.pt
331@@ -26,7 +26,7 @@
332 <span tal:condition="not: view/default_information_type"
333 id="privacy-text">
334 You can't create new repositories for
335- <tal:name replace="context/displayname"/>.
336+ <tal:name replace="context/display_name"/>.
337 <tal:sharing-link condition="context/required:launchpad.Edit">
338 <br/>This can be fixed by changing the branch sharing policy on the
339 <a tal:attributes="href string:${view/target/fmt:url:mainsite}/+sharing">sharing page</a>.
340@@ -36,7 +36,7 @@
341 <span tal:condition="view/default_information_type"
342 tal:attributes="class string:sprite ${private_class}"
343 id="privacy-text">
344- New repositories for <tal:name replace="view/target/displayname"/> are
345+ New repositories for <tal:name replace="view/target/display_name"/> are
346 <strong tal:content="view/default_information_type_title" />.
347 </span>
348 </div>
349@@ -78,7 +78,7 @@ git push --set-upstream origin master
350 tal:define="count context/menu:branches/active_review_count|nothing;
351 link context/menu:branches/active_reviews|nothing"
352 tal:condition="python: count &gt; 0">
353- <tal:project replace="context/displayname"/> has
354+ <tal:project replace="context/display_name"/> has
355 <tal:active-count replace="count"/>
356 <tal:link replace="structure python: link.render().lower()"/>.
357 </p>
358diff --git a/lib/lp/registry/browser/configure.zcml b/lib/lp/registry/browser/configure.zcml
359index e277e94..2c12cc0 100644
360--- a/lib/lp/registry/browser/configure.zcml
361+++ b/lib/lp/registry/browser/configure.zcml
362@@ -2562,6 +2562,10 @@
363 path_expression="string:${oci_project/pillar/name}/+oci/${oci_project/name}"
364 attribute_to_parent="person"
365 />
366+ <browser:navigation
367+ module="lp.registry.browser.personociproject"
368+ classes="PersonOCIProjectNavigation"
369+ />
370 <browser:url
371 for="lp.registry.interfaces.personproduct.IPersonProduct"
372 path_expression="product/name"
373diff --git a/lib/lp/registry/browser/personociproject.py b/lib/lp/registry/browser/personociproject.py
374new file mode 100644
375index 0000000..aa991eb
376--- /dev/null
377+++ b/lib/lp/registry/browser/personociproject.py
378@@ -0,0 +1,63 @@
379+# Copyright 2019 Canonical Ltd. This software is licensed under the
380+# GNU Affero General Public License version 3 (see the file LICENSE).
381+
382+"""Views, menus, and traversal related to `PersonOCIProject`s."""
383+
384+from __future__ import absolute_import, print_function, unicode_literals
385+
386+__metaclass__ = type
387+__all__ = [
388+ 'PersonOCIProjectNavigation',
389+ ]
390+
391+from zope.component import queryAdapter
392+from zope.interface import implementer
393+from zope.traversing.interfaces import IPathAdapter
394+
395+from lp.code.browser.vcslisting import PersonTargetDefaultVCSNavigationMixin
396+from lp.registry.interfaces.personociproject import IPersonOCIProject
397+from lp.services.webapp import (
398+ canonical_url,
399+ Navigation,
400+ StandardLaunchpadFacets,
401+ )
402+from lp.services.webapp.breadcrumb import Breadcrumb
403+from lp.services.webapp.interfaces import IMultiFacetedBreadcrumb
404+
405+
406+class PersonOCIProjectNavigation(
407+ PersonTargetDefaultVCSNavigationMixin, Navigation):
408+
409+ usedfor = IPersonOCIProject
410+
411+
412+# XXX cjwatson 2019-11-26: Do we need two breadcrumbs, one for the
413+# distribution and one for the OCI project?
414+@implementer(IMultiFacetedBreadcrumb)
415+class PersonOCIProjectBreadcrumb(Breadcrumb):
416+ """Breadcrumb for an `IPersonOCIProject`."""
417+
418+ @property
419+ def text(self):
420+ return self.context.oci_project.display_name
421+
422+ @property
423+ def url(self):
424+ if self._url is None:
425+ return canonical_url(
426+ self.context.oci_project, rootsite=self.rootsite)
427+ else:
428+ return self._url
429+
430+ @property
431+ def icon(self):
432+ return queryAdapter(
433+ self.context.oci_project, IPathAdapter, name='image').icon()
434+
435+
436+class PersonOCIProjectFacets(StandardLaunchpadFacets):
437+ """The links that will appear in the facet menu for an `IPersonOCIProject`.
438+ """
439+
440+ usedfor = IPersonOCIProject
441+ enable_only = ['branches']

Subscribers

People subscribed via source and target branches

to status/vote changes: