Merge ~cjwatson/launchpad:oci-git-listing into launchpad:master
- Git
- lp:~cjwatson/launchpad
- oci-git-listing
- Merge into 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) |
||||
Related bugs: |
|
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
Description of the change
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
1 | diff --git a/lib/lp/code/browser/configure.zcml b/lib/lp/code/browser/configure.zcml |
2 | index 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"/> |
48 | diff --git a/lib/lp/code/browser/gitlisting.py b/lib/lp/code/browser/gitlisting.py |
49 | index 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' |
95 | diff --git a/lib/lp/code/browser/tests/test_gitlisting.py b/lib/lp/code/browser/tests/test_gitlisting.py |
96 | index 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 |
206 | diff --git a/lib/lp/code/browser/tests/test_vcslisting.py b/lib/lp/code/browser/tests/test_vcslisting.py |
207 | index 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 | |
273 | diff --git a/lib/lp/code/browser/vcslisting.py b/lib/lp/code/browser/vcslisting.py |
274 | index 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") |
327 | diff --git a/lib/lp/code/templates/gitlisting.pt b/lib/lp/code/templates/gitlisting.pt |
328 | index 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 > 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> |
358 | diff --git a/lib/lp/registry/browser/configure.zcml b/lib/lp/registry/browser/configure.zcml |
359 | index 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" |
373 | diff --git a/lib/lp/registry/browser/personociproject.py b/lib/lp/registry/browser/personociproject.py |
374 | new file mode 100644 |
375 | index 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'] |