Merge lp:~abentley/launchpad/person-product-merges into lp:launchpad

Proposed by Aaron Bentley
Status: Merged
Merged at revision: 13008
Proposed branch: lp:~abentley/launchpad/person-product-merges
Merge into: lp:launchpad
Diff against target: 269 lines (+97/-15)
7 files modified
lib/lp/code/adapters/branchcollection.py (+7/-0)
lib/lp/code/adapters/tests/test_branchcollection.py (+37/-0)
lib/lp/code/browser/tests/test_branchmergeproposallisting.py (+25/-11)
lib/lp/code/configure.zcml (+4/-0)
lib/lp/code/model/tests/test_hasmergeproposals.py (+8/-1)
lib/lp/registry/interfaces/personproduct.py (+7/-2)
lib/lp/registry/model/personproduct.py (+9/-1)
To merge this branch: bzr merge lp:~abentley/launchpad/person-product-merges
Reviewer Review Type Date Requested Status
Benji York (community) code Approve
Review via email: mp+60417@code.launchpad.net

Commit message

Support +merges on PersonProduct.

Description of the change

= Summary =
Fix bug #739921: The link "see all merge proposals" on person/product/+activereviews 404s Remove

== Proposed fix ==
Provide an adapter to convert PersonProduct into an IBranchCollection, and implement IHasMergeProposals on PersonProduct.

== Pre-implementation notes ==
None

== Implementation details ==
Implemented displayname on PersonProduct to support the view classes.

Various drive-by lint fixes.

== Tests ==
bin/test -t test_PersonProduct_implements_hasmergeproposals -t person_product

== Demo and Q/A ==
Create a merge proposal on a Product. Go to lpn/people/+me/PRODUCT/+merges. It should be displayed.

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/code/model/tests/test_hasmergeproposals.py
  lib/lp/code/configure.zcml
  lib/lp/registry/interfaces/personproduct.py
  lib/lp/code/adapters/branchcollection.py
  lib/lp/code/browser/tests/test_branchmergeproposallisting.py
  lib/lp/code/adapters/tests/test_branchcollection.py
  lib/lp/registry/model/personproduct.py

To post a comment you must log in.
Revision history for this message
Benji York (benji) wrote :

This branch looks good. A couple of small points:

It took me a minute to figure out the intent of test_person_product. A
docstring or comment would probably help.

The HasMergeProposalsMixin import in
lib/lp/registry/model/personproduct.py doesn't have to be a mult-line
import.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/code/adapters/branchcollection.py'
2--- lib/lp/code/adapters/branchcollection.py 2009-09-02 10:36:33 +0000
3+++ lib/lp/code/adapters/branchcollection.py 2011-05-09 19:41:38 +0000
4@@ -35,6 +35,13 @@
5 return getUtility(IAllBranches).ownedBy(person)
6
7
8+def branch_collection_for_person_product(person_product):
9+ """Adapt a PersonProduct to a branch collection."""
10+ collection = getUtility(IAllBranches).ownedBy(person_product.person)
11+ collection = collection.inProduct(person_product.product)
12+ return collection
13+
14+
15 def branch_collection_for_distribution(distribution):
16 """Adapt a distribution to a branch collection."""
17 return getUtility(IAllBranches).inDistribution(distribution)
18
19=== added file 'lib/lp/code/adapters/tests/test_branchcollection.py'
20--- lib/lp/code/adapters/tests/test_branchcollection.py 1970-01-01 00:00:00 +0000
21+++ lib/lp/code/adapters/tests/test_branchcollection.py 2011-05-09 19:41:38 +0000
22@@ -0,0 +1,37 @@
23+# Copyright 2011 Canonical Ltd. This software is licensed under the
24+# GNU Affero General Public License version 3 (see the file LICENSE).
25+
26+"""Functional tests for BranchCollection adapters."""
27+
28+__metaclass__ = type
29+
30+
31+from lp.code.interfaces.branchcollection import IBranchCollection
32+from lp.testing import TestCaseWithFactory
33+from lp.registry.model.personproduct import PersonProduct
34+from canonical.testing.layers import DatabaseFunctionalLayer
35+
36+
37+class TestPersonProduct(TestCaseWithFactory):
38+
39+ layer = DatabaseFunctionalLayer
40+
41+ def test_person_product(self):
42+ """A PersonProduct can be adapted to a collection.
43+
44+ The collection will only find branches matching both the person and
45+ the product.
46+ """
47+ product = self.factory.makeProduct()
48+ person = self.factory.makePerson()
49+ person_product = PersonProduct(person, product)
50+ random_branch = self.factory.makeBranch()
51+ product_branch = self.factory.makeProductBranch(product=product)
52+ person_branch = self.factory.makeBranch(owner=person)
53+ person_product_branch = self.factory.makeProductBranch(
54+ owner=person, product=product)
55+ branches = IBranchCollection(person_product).getBranches()
56+ self.assertNotIn(random_branch, branches)
57+ self.assertNotIn(product_branch, branches)
58+ self.assertNotIn(person_branch, branches)
59+ self.assertIn(person_product_branch, branches)
60
61=== modified file 'lib/lp/code/browser/tests/test_branchmergeproposallisting.py'
62--- lib/lp/code/browser/tests/test_branchmergeproposallisting.py 2010-12-02 16:13:51 +0000
63+++ lib/lp/code/browser/tests/test_branchmergeproposallisting.py 2011-05-09 19:41:38 +0000
64@@ -23,8 +23,10 @@
65 BranchMergeProposalStatus,
66 CodeReviewVote,
67 )
68+from lp.registry.model.personproduct import PersonProduct
69 from lp.testing import (
70 ANONYMOUS,
71+ BrowserTestCase,
72 login,
73 login_person,
74 TestCaseWithFactory,
75@@ -179,6 +181,18 @@
76 self.assertEqual(4, comment_count)
77
78
79+class TestMerges(BrowserTestCase):
80+
81+ layer = DatabaseFunctionalLayer
82+
83+ def test_person_product(self):
84+ """The merges view should be enabled for PersonProduct."""
85+ personproduct = PersonProduct(
86+ self.factory.makePerson(), self.factory.makeProduct())
87+ self.getViewBrowser(personproduct, '+merges',
88+ rootsite='code')
89+
90+
91 class ActiveReviewGroupsTest(TestCaseWithFactory):
92 """Tests for groupings used in for active reviews."""
93
94@@ -276,9 +290,9 @@
95 # If the proposal is in needs review, the sort_key will be the
96 # date_review_requested.
97 bmp = self.factory.makeBranchMergeProposal(
98- date_created=datetime(2009,6,1,tzinfo=pytz.UTC))
99+ date_created=datetime(2009, 6, 1, tzinfo=pytz.UTC))
100 login_person(bmp.registrant)
101- request_date = datetime(2009,7,1,tzinfo=pytz.UTC)
102+ request_date = datetime(2009, 7, 1, tzinfo=pytz.UTC)
103 bmp.requestReview(request_date)
104 item = BranchMergeProposalListingItem(bmp, None, None)
105 self.assertEqual(request_date, item.sort_key)
106@@ -287,13 +301,13 @@
107 # If the proposal is approved, the sort_key will default to the
108 # date_review_requested.
109 bmp = self.factory.makeBranchMergeProposal(
110- date_created=datetime(2009,6,1,tzinfo=pytz.UTC))
111+ date_created=datetime(2009, 6, 1, tzinfo=pytz.UTC))
112 login_person(bmp.target_branch.owner)
113- request_date = datetime(2009,7,1,tzinfo=pytz.UTC)
114+ request_date = datetime(2009, 7, 1, tzinfo=pytz.UTC)
115 bmp.requestReview(request_date)
116 bmp.approveBranch(
117 bmp.target_branch.owner, 'rev-id',
118- datetime(2009,8,1,tzinfo=pytz.UTC))
119+ datetime(2009, 8, 1, tzinfo=pytz.UTC))
120 item = BranchMergeProposalListingItem(bmp, None, None)
121 self.assertEqual(request_date, item.sort_key)
122
123@@ -301,9 +315,9 @@
124 # If the proposal is approved and the review has been bypassed, the
125 # date_reviewed is used.
126 bmp = self.factory.makeBranchMergeProposal(
127- date_created=datetime(2009,6,1,tzinfo=pytz.UTC))
128+ date_created=datetime(2009, 6, 1, tzinfo=pytz.UTC))
129 login_person(bmp.target_branch.owner)
130- review_date = datetime(2009,8,1,tzinfo=pytz.UTC)
131+ review_date = datetime(2009, 8, 1, tzinfo=pytz.UTC)
132 bmp.approveBranch(
133 bmp.target_branch.owner, 'rev-id', review_date)
134 item = BranchMergeProposalListingItem(bmp, None, None)
135@@ -312,7 +326,7 @@
136 def test_sort_key_wip(self):
137 # If the proposal is a work in progress, the date_created is used.
138 bmp = self.factory.makeBranchMergeProposal(
139- date_created=datetime(2009,6,1,tzinfo=pytz.UTC))
140+ date_created=datetime(2009, 6, 1, tzinfo=pytz.UTC))
141 login_person(bmp.target_branch.owner)
142 item = BranchMergeProposalListingItem(bmp, None, None)
143 self.assertEqual(bmp.date_created, item.sort_key)
144@@ -328,13 +342,13 @@
145 product = self.factory.makeProduct()
146 bmp1 = self.factory.makeBranchMergeProposal(product=product)
147 login_person(bmp1.source_branch.owner)
148- bmp1.requestReview(datetime(2009,6,1,tzinfo=pytz.UTC))
149+ bmp1.requestReview(datetime(2009, 6, 1, tzinfo=pytz.UTC))
150 bmp2 = self.factory.makeBranchMergeProposal(product=product)
151 login_person(bmp2.source_branch.owner)
152- bmp2.requestReview(datetime(2009,3,1,tzinfo=pytz.UTC))
153+ bmp2.requestReview(datetime(2009, 3, 1, tzinfo=pytz.UTC))
154 bmp3 = self.factory.makeBranchMergeProposal(product=product)
155 login_person(bmp3.source_branch.owner)
156- bmp3.requestReview(datetime(2009,1,1,tzinfo=pytz.UTC))
157+ bmp3.requestReview(datetime(2009, 1, 1, tzinfo=pytz.UTC))
158 login(ANONYMOUS)
159 view = create_initialized_view(
160 product, name='+activereviews', rootsite='code')
161
162=== modified file 'lib/lp/code/configure.zcml'
163--- lib/lp/code/configure.zcml 2011-05-04 04:10:58 +0000
164+++ lib/lp/code/configure.zcml 2011-05-09 19:41:38 +0000
165@@ -122,6 +122,10 @@
166 provides="lp.code.interfaces.branchcollection.IBranchCollection"
167 factory="lp.code.adapters.branchcollection.branch_collection_for_product"/>
168 <adapter
169+ for="lp.registry.interfaces.personproduct.IPersonProduct"
170+ provides="lp.code.interfaces.branchcollection.IBranchCollection"
171+ factory="lp.code.adapters.branchcollection.branch_collection_for_person_product"/>
172+ <adapter
173 for="lp.registry.interfaces.projectgroup.IProjectGroup"
174 provides="lp.code.interfaces.branchcollection.IBranchCollection"
175 factory="lp.code.adapters.branchcollection.branch_collection_for_project"/>
176
177=== modified file 'lib/lp/code/model/tests/test_hasmergeproposals.py'
178--- lib/lp/code/model/tests/test_hasmergeproposals.py 2010-10-04 19:50:45 +0000
179+++ lib/lp/code/model/tests/test_hasmergeproposals.py 2011-05-09 19:41:38 +0000
180@@ -7,8 +7,10 @@
181
182 import unittest
183
184+from canonical.launchpad.webapp.testing import verifyObject
185 from canonical.testing.layers import DatabaseFunctionalLayer
186 from lp.code.interfaces.hasbranches import IHasMergeProposals
187+from lp.registry.model.personproduct import PersonProduct
188 from lp.testing import TestCaseWithFactory
189
190
191@@ -32,7 +34,12 @@
192 project = self.factory.makeProject()
193 self.assertProvides(project, IHasMergeProposals)
194
195+ def test_PersonProduct_implements_hasmergeproposals(self):
196+ # PersonProducts should implement IHasMergeProposals.
197+ product = self.factory.makeProduct()
198+ person_product = PersonProduct(product.owner, product)
199+ verifyObject(IHasMergeProposals, person_product)
200+
201
202 def test_suite():
203 return unittest.TestLoader().loadTestsFromName(__name__)
204-
205
206=== modified file 'lib/lp/registry/interfaces/personproduct.py'
207--- lib/lp/registry/interfaces/personproduct.py 2010-08-20 20:31:18 +0000
208+++ lib/lp/registry/interfaces/personproduct.py 2011-05-09 19:41:38 +0000
209@@ -13,22 +13,27 @@
210
211 from lazr.restful.fields import Reference
212 from zope.interface import Interface
213+from zope.schema import (
214+ TextLine,
215+)
216
217+from lp.code.interfaces.hasbranches import IHasMergeProposals
218 from lp.registry.interfaces.person import IPerson
219 from lp.registry.interfaces.product import IProduct
220
221
222-class IPersonProduct(Interface):
223+class IPersonProduct(IHasMergeProposals):
224 """A person's view on a product."""
225
226 person = Reference(IPerson)
227
228 product = Reference(IProduct)
229
230+ displayname = TextLine()
231+
232
233 class IPersonProductFactory(Interface):
234 """Creates `IPersonProduct`s."""
235
236 def create(person, product):
237 """Create and return an `IPersonProduct`."""
238-
239
240=== modified file 'lib/lp/registry/model/personproduct.py'
241--- lib/lp/registry/model/personproduct.py 2010-08-20 20:31:18 +0000
242+++ lib/lp/registry/model/personproduct.py 2011-05-09 19:41:38 +0000
243@@ -13,13 +13,16 @@
244 implements,
245 )
246
247+from lp.code.model.hasbranches import (
248+ HasMergeProposalsMixin,
249+ )
250 from lp.registry.interfaces.personproduct import (
251 IPersonProduct,
252 IPersonProductFactory,
253 )
254
255
256-class PersonProduct:
257+class PersonProduct(HasMergeProposalsMixin):
258
259 implements(IPersonProduct)
260
261@@ -32,3 +35,8 @@
262 @staticmethod
263 def create(person, product):
264 return PersonProduct(person, product)
265+
266+ @property
267+ def displayname(self):
268+ return '%s in %s' % (
269+ self.person.displayname, self.product.displayname)