Merge lp:~abentley/launchpad/no-proprietary-packagings into lp:launchpad

Proposed by Aaron Bentley
Status: Merged
Approved by: Aaron Bentley
Approved revision: no longer in the source branch.
Merged at revision: 16240
Proposed branch: lp:~abentley/launchpad/no-proprietary-packagings
Merge into: lp:launchpad
Diff against target: 229 lines (+83/-14)
6 files modified
lib/lp/registry/browser/product.py (+9/-7)
lib/lp/registry/browser/sourcepackage.py (+5/-2)
lib/lp/registry/browser/tests/test_product.py (+3/-2)
lib/lp/registry/browser/tests/test_sourcepackage_views.py (+62/-1)
lib/lp/registry/vocabularies.py (+3/-1)
lib/lp/services/webapp/vocabulary.py (+1/-1)
To merge this branch: bzr merge lp:~abentley/launchpad/no-proprietary-packagings
Reviewer Review Type Date Requested Status
Benji York (community) code Approve
Review via email: mp+132960@code.launchpad.net

Commit message

No proprietary products for Upstream connections portlet.

Description of the change

= Summary =
Fix bug #1066905: OOPS on "Upstream connections" portlet with proprietary product

== Proposed fix ==
Ensure that the vocabulary lists only Public products, disable information type slection if registering a new product.

== Pre-implementation notes ==
None

== LOC Rationale ==
Part of Private Products

== Implementation details ==
Removing non-Public products from the vocabulary has two effects: it prevents the item from being listed, and it also prevents it from being accepted if submitted. (This could happen due to a race, as per test_link_upstream_handles_proprietary.)

The presence of a source_package_name becomes a criterion for hiding the information_type, in addition to the feature flag.

Fixed handling of vocab_filter for SQLObjectVocabularyBase and ProductVocabulary, so that I could specify Product._information_type == InformationType.PUBLIC as that filter.

Fixed name of "test_owner_is_requried_without_disclaim_maitainer" as a drive-by.

== Tests ==
bin/test -t test_register_upstream_forbids_proprietary -t test_link_upstream_handles_initial_proprietary -t test_link_upstream_handles_proprietary

== Demo and Q/A ==
Find a source package with no Upstream connection (or remove its connection). Register a product from the portal. You should not see the "Information Type" field.

Disconnect the product from the source package.

On the Upstream connections portal, the product should be listed as a potential connection. In another window, make the product proprietary.

Click "Link to Upstream Project". You should get "Invalid value", and the product should no longer be listed under "Upstream connections".

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/registry/vocabularies.py
  lib/lp/registry/browser/sourcepackage.py
  lib/lp/services/webapp/vocabulary.py
  lib/lp/registry/browser/product.py
  lib/lp/registry/browser/tests/test_product.py
  lib/lp/registry/browser/tests/test_sourcepackage_views.py

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

This branch looks good. Considering the race condition was a good touch.

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/registry/browser/product.py'
2--- lib/lp/registry/browser/product.py 2012-10-26 01:32:43 +0000
3+++ lib/lp/registry/browser/product.py 2012-11-05 20:20:29 +0000
4@@ -1984,14 +1984,18 @@
5 'information_type': InformationType.PUBLIC,
6 }
7
8+ @property
9+ def enable_information_type(self):
10+ private_projects = bool(getFeatureFlag(PRIVATE_PROJECTS_FLAG))
11+ return private_projects and not self.source_package_name
12+
13 def setUpFields(self):
14 """See `LaunchpadFormView`."""
15 super(ProjectAddStepTwo, self).setUpFields()
16 hidden_names = ['__visited_steps__', 'license_info']
17 hidden_fields = self.form_fields.select(*hidden_names)
18
19- private_projects = bool(getFeatureFlag(PRIVATE_PROJECTS_FLAG))
20- if not private_projects:
21+ if not self.enable_information_type:
22 hidden_names.extend([
23 'information_type', 'bug_supervisor', 'driver'])
24
25@@ -2033,9 +2037,8 @@
26 self.widgets['source_package_name'].visible = False
27 self.widgets['distroseries'].visible = False
28
29- private_projects = bool(getFeatureFlag(PRIVATE_PROJECTS_FLAG))
30-
31- if private_projects and IProductSet.providedBy(self.context):
32+ if (self.enable_information_type and
33+ IProductSet.providedBy(self.context)):
34 self.widgets['information_type'].value = InformationType.PUBLIC
35
36 # Set the source_package_release attribute on the licenses
37@@ -2105,8 +2108,7 @@
38 for error in errors:
39 self.errors.remove(error)
40
41- private_projects = bool(getFeatureFlag(PRIVATE_PROJECTS_FLAG))
42- if private_projects:
43+ if self.enable_information_type:
44 if data.get('information_type') != InformationType.PUBLIC:
45 for required_field in ('bug_supervisor', 'driver'):
46 if data.get(required_field) is None:
47
48=== modified file 'lib/lp/registry/browser/sourcepackage.py'
49--- lib/lp/registry/browser/sourcepackage.py 2012-10-12 18:12:20 +0000
50+++ lib/lp/registry/browser/sourcepackage.py 2012-11-05 20:20:29 +0000
51@@ -67,7 +67,7 @@
52 StepView,
53 )
54 from lp.app.browser.tales import CustomizableFormatter
55-from lp.app.enums import ServiceUsage
56+from lp.app.enums import InformationType, ServiceUsage
57 from lp.app.widgets.itemswidgets import LaunchpadRadioWidget
58 from lp.bugs.browser.bugtask import BugTargetTraversalMixin
59 from lp.registry.browser.product import ProjectAddStepOne
60@@ -80,6 +80,7 @@
61 from lp.registry.interfaces.productseries import IProductSeries
62 from lp.registry.interfaces.series import SeriesStatus
63 from lp.registry.interfaces.sourcepackage import ISourcePackage
64+from lp.registry.model.product import Product
65 from lp.services.webapp import (
66 ApplicationMenu,
67 canonical_url,
68@@ -577,7 +578,9 @@
69 # Find registered products that are similarly named to the source
70 # package.
71 product_vocab = getVocabularyRegistry().get(None, 'Product')
72- matches = product_vocab.searchForTerms(self.context.name)
73+ matches = product_vocab.searchForTerms(self.context.name,
74+ vocab_filter=[Product._information_type ==
75+ InformationType.PUBLIC])
76 # Based upon the matching products, create a new vocabulary with
77 # term descriptions that include a link to the product.
78 self.product_suggestions = []
79
80=== modified file 'lib/lp/registry/browser/tests/test_product.py'
81--- lib/lp/registry/browser/tests/test_product.py 2012-10-26 01:32:43 +0000
82+++ lib/lp/registry/browser/tests/test_product.py 2012-11-05 20:20:29 +0000
83@@ -248,7 +248,7 @@
84 product = self.product_set.getByName('fnord')
85 self.assertEqual('registry', product.owner.name)
86
87- def test_owner_is_requried_without_disclaim_maitainer(self):
88+ def test_owner_is_requried_without_disclaim_maintainer(self):
89 # A valid owner name is required if disclaim_maintainer is
90 # not selected.
91 registrant = self.factory.makePerson()
92@@ -496,7 +496,8 @@
93 product,
94 '+edit',
95 principal=product.owner)
96- info_types = [t.name for t in view.widgets['information_type'].vocabulary]
97+ vocabulary = view.widgets['information_type'].vocabulary
98+ info_types = [t.name for t in vocabulary]
99 expected = ['PUBLIC', 'PROPRIETARY', 'EMBARGOED']
100 self.assertEqual(expected, info_types)
101
102
103=== modified file 'lib/lp/registry/browser/tests/test_sourcepackage_views.py'
104--- lib/lp/registry/browser/tests/test_sourcepackage_views.py 2012-10-18 12:40:40 +0000
105+++ lib/lp/registry/browser/tests/test_sourcepackage_views.py 2012-11-05 20:20:29 +0000
106@@ -8,6 +8,12 @@
107 import cgi
108 import urllib
109
110+from soupmatchers import (
111+ HTMLContains,
112+ Tag,
113+ )
114+from testtools.matchers import Not
115+from testtools.testcase import ExpectedException
116 from zope.component import getUtility
117 from zope.interface import implements
118 from zope.security.proxy import removeSecurityProxy
119@@ -24,6 +30,7 @@
120 IDistroSeriesSet,
121 )
122 from lp.registry.interfaces.sourcepackage import ISourcePackage
123+from lp.services.features.testing import FeatureFixture
124 from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
125 from lp.testing import (
126 BrowserTestCase,
127@@ -152,6 +159,60 @@
128 url, 'field.homepageurl', 'http://eg.dom/bonkers')
129
130
131+class TestSourcePackageView(BrowserTestCase):
132+
133+ layer = DatabaseFunctionalLayer
134+
135+ def test_register_upstream_forbids_proprietary(self):
136+ # Cannot specify information_type if registering for sourcepackage.
137+ self.useFixture(FeatureFixture({'disclosure.private_projects.enabled':
138+ 'on'}))
139+ sourcepackage = self.factory.makeSourcePackage()
140+ browser = self.getViewBrowser(sourcepackage)
141+ browser.getControl("Register the upstream project").click()
142+ browser.getControl("Link to Upstream Project").click()
143+ browser.getControl("Summary").value = "summary"
144+ browser.getControl("Continue").click()
145+ t = Tag('info_type', 'input', attrs={'name': 'field.information_type'})
146+ self.assertThat(browser.contents, Not(HTMLContains(t)))
147+
148+ def test_link_upstream_handles_initial_proprietary(self):
149+ # Proprietary product is not listed as an option.
150+ owner = self.factory.makePerson()
151+ sourcepackage = self.factory.makeSourcePackage()
152+ product_name = sourcepackage.name
153+ product_displayname = self.factory.getUniqueString()
154+ self.factory.makeProduct(
155+ name=product_name, owner=owner,
156+ information_type=InformationType.PROPRIETARY,
157+ displayname=product_displayname)
158+ browser = self.getViewBrowser(sourcepackage, user=owner)
159+ with ExpectedException(LookupError):
160+ browser.getControl(product_displayname)
161+
162+ def test_link_upstream_handles_proprietary(self):
163+ # Proprietary products produce an 'invalid value' error.
164+ owner = self.factory.makePerson()
165+ product = self.factory.makeProduct(owner=owner)
166+ product_name = product.name
167+ product_displayname = product.displayname
168+ sourcepackage = self.factory.makeSourcePackage(
169+ sourcepackagename=product_name)
170+ with person_logged_in(None):
171+ browser = self.getViewBrowser(sourcepackage, user=owner)
172+ with person_logged_in(owner):
173+ product.information_type = InformationType.PROPRIETARY
174+ browser.getControl(product_displayname).click()
175+ browser.getControl("Link to Upstream Project").click()
176+ error = Tag(
177+ 'error', 'div', attrs={'class': 'message'},
178+ text='Invalid value')
179+ self.assertThat(browser.contents, HTMLContains(error))
180+ self.assertNotIn(
181+ 'The project %s was linked to this source package.' %
182+ str(product_displayname), browser.contents)
183+
184+
185 class TestSourcePackageUpstreamConnectionsView(TestCaseWithFactory):
186
187 layer = DatabaseFunctionalLayer
188@@ -299,7 +360,7 @@
189 """Packaging cannot be created for PROPRIETARY products"""
190 product_owner = self.factory.makePerson()
191 product_name = 'proprietary-product'
192- product = self.factory.makeProduct(
193+ self.factory.makeProduct(
194 name=product_name, owner=product_owner,
195 information_type=InformationType.PROPRIETARY)
196 ubuntu_series = self.factory.makeUbuntuDistroSeries()
197
198=== modified file 'lib/lp/registry/vocabularies.py'
199--- lib/lp/registry/vocabularies.py 2012-10-19 10:34:55 +0000
200+++ lib/lp/registry/vocabularies.py 2012-11-05 20:20:29 +0000
201@@ -296,12 +296,14 @@
202 like_query = query.lower()
203 like_query = "'%%%%' || %s || '%%%%'" % quote_like(like_query)
204 fti_query = quote(query)
205+ if vocab_filter is None:
206+ vocab_filter = []
207 where_clause = And(
208 SQL(
209 "active = 't' AND (name LIKE %s OR fti @@ ftq(%s))" % (
210 like_query, fti_query)),
211 ProductSet.getProductPrivacyFilter(
212- getUtility(ILaunchBag).user))
213+ getUtility(ILaunchBag).user), *vocab_filter)
214 order_by = SQL(
215 '(CASE name WHEN %s THEN 1 '
216 ' ELSE rank(fti, ftq(%s)) END) DESC, displayname, name'
217
218=== modified file 'lib/lp/services/webapp/vocabulary.py'
219--- lib/lp/services/webapp/vocabulary.py 2011-12-30 06:14:56 +0000
220+++ lib/lp/services/webapp/vocabulary.py 2012-11-05 20:20:29 +0000
221@@ -307,7 +307,7 @@
222 # search functionality produce a new vocabulary restricted to the
223 # desired subset.
224 def searchForTerms(self, query=None, vocab_filter=None):
225- results = self.search(query)
226+ results = self.search(query, vocab_filter)
227 return CountableIterator(results.count(), results, self.toTerm)
228
229 def search(self, query, vocab_filter=None):