Merge lp:~cjwatson/launchpad/person-dsp into lp:launchpad

Proposed by Colin Watson
Status: Merged
Approved by: Colin Watson
Approved revision: no longer in the source branch.
Merged at revision: 17340
Proposed branch: lp:~cjwatson/launchpad/person-dsp
Merge into: lp:launchpad
Diff against target: 336 lines (+247/-4)
7 files modified
lib/lp/registry/browser/configure.zcml (+14/-1)
lib/lp/registry/browser/person.py (+27/-2)
lib/lp/registry/browser/persondistributionsourcepackage.py (+69/-0)
lib/lp/registry/configure.zcml (+22/-1)
lib/lp/registry/interfaces/persondistributionsourcepackage.py (+34/-0)
lib/lp/registry/model/persondistributionsourcepackage.py (+39/-0)
lib/lp/registry/tests/test_persondistributionsourcepackage.py (+42/-0)
To merge this branch: bzr merge lp:~cjwatson/launchpad/person-dsp
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+248911@code.launchpad.net

Commit message

Introduce PersonDistributionSourcePackage, which will later serve as a target for Git repositories.

Description of the change

Introduce PersonDistributionSourcePackage, which will later serve as a target for Git repositories.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) wrote :

This looks fine apart from some potential breadcrumb improvements.

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

I tend to agree that we'll likely need a PersonDistribution, but I'm finding it a little hard to tell right now because my Git repository browser code isn't far enough along. I'm going to go ahead and land this for now so that I can make progress on the model, and introduce PersonDistribution a little later if it indeed becomes necessary for the browser.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/lp/registry/browser/configure.zcml'
--- lib/lp/registry/browser/configure.zcml 2015-01-29 10:35:21 +0000
+++ lib/lp/registry/browser/configure.zcml 2015-02-09 17:43:47 +0000
@@ -1,4 +1,4 @@
1<!-- Copyright 2009-2014 Canonical Ltd. This software is licensed under the1<!-- Copyright 2009-2015 Canonical Ltd. This software is licensed under the
2 GNU Affero General Public License version 3 (see the file LICENSE).2 GNU Affero General Public License version 3 (see the file LICENSE).
3-->3-->
44
@@ -2154,6 +2154,19 @@
2154 />2154 />
21552155
2156 <browser:url2156 <browser:url
2157 for="lp.registry.interfaces.persondistributionsourcepackage.IPersonDistributionSourcePackage"
2158 path_expression="string:${distro_source_package/distribution/name}/+source/${distro_source_package/sourcepackagename/name}"
2159 attribute_to_parent="person"/>
2160 <browser:navigation
2161 module="lp.registry.browser.persondistributionsourcepackage"
2162 classes="
2163 PersonDistributionSourcePackageNavigation"/>
2164 <browser:menus
2165 classes="
2166 PersonDistributionSourcePackageFacets"
2167 module="lp.registry.browser.persondistributionsourcepackage"/>
2168
2169 <browser:url
2157 for="lp.registry.interfaces.personproduct.IPersonProduct"2170 for="lp.registry.interfaces.personproduct.IPersonProduct"
2158 path_expression="product/name"2171 path_expression="product/name"
2159 attribute_to_parent="person"/>2172 attribute_to_parent="person"/>
21602173
=== modified file 'lib/lp/registry/browser/person.py'
--- lib/lp/registry/browser/person.py 2015-01-06 10:47:37 +0000
+++ lib/lp/registry/browser/person.py 2015-02-09 17:43:47 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2014 Canonical Ltd. This software is licensed under the1# Copyright 2009-2015 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Person-related view classes."""4"""Person-related view classes."""
@@ -154,6 +154,7 @@
154from lp.registry.enums import PersonVisibility154from lp.registry.enums import PersonVisibility
155from lp.registry.errors import VoucherAlreadyRedeemed155from lp.registry.errors import VoucherAlreadyRedeemed
156from lp.registry.interfaces.codeofconduct import ISignedCodeOfConductSet156from lp.registry.interfaces.codeofconduct import ISignedCodeOfConductSet
157from lp.registry.interfaces.distribution import IDistribution
157from lp.registry.interfaces.gpg import IGPGKeySet158from lp.registry.interfaces.gpg import IGPGKeySet
158from lp.registry.interfaces.irc import IIrcIDSet159from lp.registry.interfaces.irc import IIrcIDSet
159from lp.registry.interfaces.jabber import (160from lp.registry.interfaces.jabber import (
@@ -172,6 +173,9 @@
172 IPersonClaim,173 IPersonClaim,
173 IPersonSet,174 IPersonSet,
174 )175 )
176from lp.registry.interfaces.persondistributionsourcepackage import (
177 IPersonDistributionSourcePackageFactory,
178 )
175from lp.registry.interfaces.personproduct import IPersonProductFactory179from lp.registry.interfaces.personproduct import IPersonProductFactory
176from lp.registry.interfaces.persontransferjob import (180from lp.registry.interfaces.persontransferjob import (
177 IPersonDeactivateJobSource,181 IPersonDeactivateJobSource,
@@ -357,7 +361,9 @@
357 raise NotFoundError361 raise NotFoundError
358362
359 def traverse(self, pillar_name):363 def traverse(self, pillar_name):
360 # If the pillar is a product, then return the PersonProduct.364 # If the pillar is a product, then return the PersonProduct; if it
365 # is a distribution and further segments provide a source package,
366 # then return the PersonDistributionSourcePackage.
361 pillar = getUtility(IPillarNameSet).getByName(pillar_name)367 pillar = getUtility(IPillarNameSet).getByName(pillar_name)
362 if IProduct.providedBy(pillar):368 if IProduct.providedBy(pillar):
363 person_product = getUtility(IPersonProductFactory).create(369 person_product = getUtility(IPersonProductFactory).create(
@@ -369,6 +375,25 @@
369 status=301)375 status=301)
370 getUtility(IOpenLaunchBag).add(pillar)376 getUtility(IOpenLaunchBag).add(pillar)
371 return person_product377 return person_product
378 elif IDistribution.providedBy(pillar):
379 if (len(self.request.stepstogo) >= 2 and
380 self.request.stepstogo.peek() == "+source"):
381 self.request.stepstogo.consume()
382 spn_name = self.request.stepstogo.consume()
383 dsp = IDistribution(pillar).getSourcePackage(spn_name)
384 if dsp is not None:
385 factory = getUtility(
386 IPersonDistributionSourcePackageFactory)
387 person_dsp = factory.create(self.context, dsp)
388 # If accessed through an alias, redirect to the proper
389 # name.
390 if pillar.name != pillar_name:
391 return self.redirectSubTree(
392 canonical_url(person_dsp, request=self.request),
393 status=301)
394 getUtility(IOpenLaunchBag).add(pillar)
395 return person_dsp
396
372 # Otherwise look for a branch.397 # Otherwise look for a branch.
373 try:398 try:
374 branch = getUtility(IBranchNamespaceSet).traverse(399 branch = getUtility(IBranchNamespaceSet).traverse(
375400
=== added file 'lib/lp/registry/browser/persondistributionsourcepackage.py'
--- lib/lp/registry/browser/persondistributionsourcepackage.py 1970-01-01 00:00:00 +0000
+++ lib/lp/registry/browser/persondistributionsourcepackage.py 2015-02-09 17:43:47 +0000
@@ -0,0 +1,69 @@
1# Copyright 2015 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Views, menus and traversal related to PersonDistributionSourcePackages."""
5
6__metaclass__ = type
7__all__ = [
8 'PersonDistributionSourcePackageBreadcrumb',
9 'PersonDistributionSourcePackageFacets',
10 'PersonDistributionSourcePackageNavigation',
11 ]
12
13
14from zope.component import queryAdapter
15from zope.interface import implements
16from zope.traversing.interfaces import IPathAdapter
17
18from lp.app.errors import NotFoundError
19from lp.registry.interfaces.persondistributionsourcepackage import (
20 IPersonDistributionSourcePackage,
21 )
22from lp.services.webapp import (
23 canonical_url,
24 Navigation,
25 StandardLaunchpadFacets,
26 )
27from lp.services.webapp.breadcrumb import Breadcrumb
28from lp.services.webapp.interfaces import IMultiFacetedBreadcrumb
29
30
31class PersonDistributionSourcePackageNavigation(Navigation):
32 usedfor = IPersonDistributionSourcePackage
33
34 def traverse(self, branch_name):
35 # XXX cjwatson 2015-02-06: This will look for Git repositories in
36 # the person/DSP namespace, but for now it does nothing.
37 raise NotFoundError
38
39
40# XXX cjwatson 2015-01-29: Do we need two breadcrumbs, one for the
41# distribution and one for the source package?
42class PersonDistributionSourcePackageBreadcrumb(Breadcrumb):
43 """Breadcrumb for an `IPersonDistributionSourcePackage`."""
44 implements(IMultiFacetedBreadcrumb)
45
46 @property
47 def text(self):
48 return self.context.distro_source_package.displayname
49
50 @property
51 def url(self):
52 if self._url is None:
53 return canonical_url(
54 self.context.distro_source_package, rootsite=self.rootsite)
55 else:
56 return self._url
57
58 @property
59 def icon(self):
60 return queryAdapter(
61 self.context.distro_source_package, IPathAdapter,
62 name='image').icon()
63
64
65class PersonDistributionSourcePackageFacets(StandardLaunchpadFacets):
66 """The links that will appear in the facet menu for an IPersonDSP."""
67
68 usedfor = IPersonDistributionSourcePackage
69 enable_only = ['branches']
070
=== modified file 'lib/lp/registry/configure.zcml'
--- lib/lp/registry/configure.zcml 2015-01-29 16:28:30 +0000
+++ lib/lp/registry/configure.zcml 2015-02-09 17:43:47 +0000
@@ -1,4 +1,4 @@
1<!-- Copyright 2009-2014 Canonical Ltd. This software is licensed under the1<!-- Copyright 2009-2015 Canonical Ltd. This software is licensed under the
2 GNU Affero General Public License version 3 (see the file LICENSE).2 GNU Affero General Public License version 3 (see the file LICENSE).
3-->3-->
44
@@ -2065,6 +2065,27 @@
2065 interface="lp.registry.interfaces.packaging.IPackagingUtil"/>2065 interface="lp.registry.interfaces.packaging.IPackagingUtil"/>
2066 </securedutility>2066 </securedutility>
20672067
2068 <!-- PersonDistributionSourcePackage -->
2069
2070 <class
2071 class="lp.registry.model.persondistributionsourcepackage.PersonDistributionSourcePackage">
2072 <allow
2073 interface="lp.registry.interfaces.persondistributionsourcepackage.IPersonDistributionSourcePackage" />
2074 </class>
2075
2076 <adapter
2077 provides="lp.services.webapp.interfaces.IBreadcrumb"
2078 for="lp.registry.interfaces.persondistributionsourcepackage.IPersonDistributionSourcePackage"
2079 factory="lp.registry.browser.persondistributionsourcepackage.PersonDistributionSourcePackageBreadcrumb"
2080 permission="zope.Public"/>
2081
2082 <securedutility
2083 component="lp.registry.model.persondistributionsourcepackage.PersonDistributionSourcePackage"
2084 provides="lp.registry.interfaces.persondistributionsourcepackage.IPersonDistributionSourcePackageFactory">
2085 <allow
2086 interface="lp.registry.interfaces.persondistributionsourcepackage.IPersonDistributionSourcePackageFactory"/>
2087 </securedutility>
2088
2068 <!-- PersonNotification -->2089 <!-- PersonNotification -->
20692090
2070 <class2091 <class
20712092
=== added file 'lib/lp/registry/interfaces/persondistributionsourcepackage.py'
--- lib/lp/registry/interfaces/persondistributionsourcepackage.py 1970-01-01 00:00:00 +0000
+++ lib/lp/registry/interfaces/persondistributionsourcepackage.py 2015-02-09 17:43:47 +0000
@@ -0,0 +1,34 @@
1# Copyright 2015 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""A person's view on a source package in a distribution."""
5
6__metaclass__ = type
7__all__ = [
8 'IPersonDistributionSourcePackage',
9 'IPersonDistributionSourcePackageFactory',
10 ]
11
12from lazr.restful.fields import Reference
13from zope.interface import Interface
14from zope.schema import TextLine
15
16from lp.registry.interfaces.person import IPerson
17from lp.registry.interfaces.distributionsourcepackage import (
18 IDistributionSourcePackage,
19 )
20
21
22class IPersonDistributionSourcePackage(Interface):
23 """A person's view on a source package in a distribution."""
24
25 person = Reference(IPerson)
26 distro_source_package = Reference(IDistributionSourcePackage)
27 displayname = TextLine()
28
29
30class IPersonDistributionSourcePackageFactory(Interface):
31 """Creates `IPersonDistributionSourcePackage`s."""
32
33 def create(person, distro_source_package):
34 """Create and return an `IPersonDistributionSourcePackage`."""
035
=== added file 'lib/lp/registry/model/persondistributionsourcepackage.py'
--- lib/lp/registry/model/persondistributionsourcepackage.py 1970-01-01 00:00:00 +0000
+++ lib/lp/registry/model/persondistributionsourcepackage.py 2015-02-09 17:43:47 +0000
@@ -0,0 +1,39 @@
1# Copyright 2015 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""A person's view on a source package in a distribution."""
5
6__metaclass__ = type
7__all__ = [
8 'PersonDistributionSourcePackage',
9 ]
10
11from zope.interface import (
12 classProvides,
13 implements,
14 )
15
16from lp.registry.interfaces.persondistributionsourcepackage import (
17 IPersonDistributionSourcePackage,
18 IPersonDistributionSourcePackageFactory,
19 )
20
21
22class PersonDistributionSourcePackage:
23
24 implements(IPersonDistributionSourcePackage)
25
26 classProvides(IPersonDistributionSourcePackageFactory)
27
28 def __init__(self, person, distro_source_package):
29 self.person = person
30 self.distro_source_package = distro_source_package
31
32 @staticmethod
33 def create(person, distro_source_package):
34 return PersonDistributionSourcePackage(person, distro_source_package)
35
36 @property
37 def displayname(self):
38 return '%s in %s' % (
39 self.person.displayname, self.distro_source_package.displayname)
040
=== added file 'lib/lp/registry/tests/test_persondistributionsourcepackage.py'
--- lib/lp/registry/tests/test_persondistributionsourcepackage.py 1970-01-01 00:00:00 +0000
+++ lib/lp/registry/tests/test_persondistributionsourcepackage.py 2015-02-09 17:43:47 +0000
@@ -0,0 +1,42 @@
1# Copyright 2015 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Test the Person/DistributionSourcePackage non-database class."""
5
6__metaclass__ = type
7
8from lp.registry.model.persondistributionsourcepackage import (
9 PersonDistributionSourcePackage,
10 )
11from lp.services.webapp.interfaces import IBreadcrumb
12from lp.services.webapp.publisher import canonical_url
13from lp.testing import TestCaseWithFactory
14from lp.testing.layers import DatabaseFunctionalLayer
15
16
17class TestPersonDistributionSourcePackage(TestCaseWithFactory):
18 """Tests for `IPersonDistributionSourcePackage`s."""
19
20 layer = DatabaseFunctionalLayer
21
22 def _makePersonDistributionSourcePackage(self):
23 person = self.factory.makePerson()
24 dsp = self.factory.makeDistributionSourcePackage()
25 return PersonDistributionSourcePackage(person, dsp)
26
27 def test_canonical_url(self):
28 # The canonical_url of a person DSP is
29 # ~person/distribution/+source/sourcepackagename.
30 pdsp = self._makePersonDistributionSourcePackage()
31 dsp = pdsp.distro_source_package
32 expected = 'http://launchpad.dev/~%s/%s/+source/%s' % (
33 pdsp.person.name, dsp.distribution.name,
34 dsp.sourcepackagename.name)
35 self.assertEqual(expected, canonical_url(pdsp))
36
37 def test_breadcrumb(self):
38 # Person DSPs give the DSP as their breadcrumb url.
39 pdsp = self._makePersonDistributionSourcePackage()
40 breadcrumb = IBreadcrumb(pdsp, None)
41 self.assertEqual(
42 canonical_url(pdsp.distro_source_package), breadcrumb.url)