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
=== modified file 'lib/lp/registry/browser/product.py'
--- lib/lp/registry/browser/product.py 2012-10-26 01:32:43 +0000
+++ lib/lp/registry/browser/product.py 2012-11-05 20:20:29 +0000
@@ -1984,14 +1984,18 @@
1984 'information_type': InformationType.PUBLIC,1984 'information_type': InformationType.PUBLIC,
1985 }1985 }
19861986
1987 @property
1988 def enable_information_type(self):
1989 private_projects = bool(getFeatureFlag(PRIVATE_PROJECTS_FLAG))
1990 return private_projects and not self.source_package_name
1991
1987 def setUpFields(self):1992 def setUpFields(self):
1988 """See `LaunchpadFormView`."""1993 """See `LaunchpadFormView`."""
1989 super(ProjectAddStepTwo, self).setUpFields()1994 super(ProjectAddStepTwo, self).setUpFields()
1990 hidden_names = ['__visited_steps__', 'license_info']1995 hidden_names = ['__visited_steps__', 'license_info']
1991 hidden_fields = self.form_fields.select(*hidden_names)1996 hidden_fields = self.form_fields.select(*hidden_names)
19921997
1993 private_projects = bool(getFeatureFlag(PRIVATE_PROJECTS_FLAG))1998 if not self.enable_information_type:
1994 if not private_projects:
1995 hidden_names.extend([1999 hidden_names.extend([
1996 'information_type', 'bug_supervisor', 'driver'])2000 'information_type', 'bug_supervisor', 'driver'])
19972001
@@ -2033,9 +2037,8 @@
2033 self.widgets['source_package_name'].visible = False2037 self.widgets['source_package_name'].visible = False
2034 self.widgets['distroseries'].visible = False2038 self.widgets['distroseries'].visible = False
20352039
2036 private_projects = bool(getFeatureFlag(PRIVATE_PROJECTS_FLAG))2040 if (self.enable_information_type and
20372041 IProductSet.providedBy(self.context)):
2038 if private_projects and IProductSet.providedBy(self.context):
2039 self.widgets['information_type'].value = InformationType.PUBLIC2042 self.widgets['information_type'].value = InformationType.PUBLIC
20402043
2041 # Set the source_package_release attribute on the licenses2044 # Set the source_package_release attribute on the licenses
@@ -2105,8 +2108,7 @@
2105 for error in errors:2108 for error in errors:
2106 self.errors.remove(error)2109 self.errors.remove(error)
21072110
2108 private_projects = bool(getFeatureFlag(PRIVATE_PROJECTS_FLAG))2111 if self.enable_information_type:
2109 if private_projects:
2110 if data.get('information_type') != InformationType.PUBLIC:2112 if data.get('information_type') != InformationType.PUBLIC:
2111 for required_field in ('bug_supervisor', 'driver'):2113 for required_field in ('bug_supervisor', 'driver'):
2112 if data.get(required_field) is None:2114 if data.get(required_field) is None:
21132115
=== modified file 'lib/lp/registry/browser/sourcepackage.py'
--- lib/lp/registry/browser/sourcepackage.py 2012-10-12 18:12:20 +0000
+++ lib/lp/registry/browser/sourcepackage.py 2012-11-05 20:20:29 +0000
@@ -67,7 +67,7 @@
67 StepView,67 StepView,
68 )68 )
69from lp.app.browser.tales import CustomizableFormatter69from lp.app.browser.tales import CustomizableFormatter
70from lp.app.enums import ServiceUsage70from lp.app.enums import InformationType, ServiceUsage
71from lp.app.widgets.itemswidgets import LaunchpadRadioWidget71from lp.app.widgets.itemswidgets import LaunchpadRadioWidget
72from lp.bugs.browser.bugtask import BugTargetTraversalMixin72from lp.bugs.browser.bugtask import BugTargetTraversalMixin
73from lp.registry.browser.product import ProjectAddStepOne73from lp.registry.browser.product import ProjectAddStepOne
@@ -80,6 +80,7 @@
80from lp.registry.interfaces.productseries import IProductSeries80from lp.registry.interfaces.productseries import IProductSeries
81from lp.registry.interfaces.series import SeriesStatus81from lp.registry.interfaces.series import SeriesStatus
82from lp.registry.interfaces.sourcepackage import ISourcePackage82from lp.registry.interfaces.sourcepackage import ISourcePackage
83from lp.registry.model.product import Product
83from lp.services.webapp import (84from lp.services.webapp import (
84 ApplicationMenu,85 ApplicationMenu,
85 canonical_url,86 canonical_url,
@@ -577,7 +578,9 @@
577 # Find registered products that are similarly named to the source578 # Find registered products that are similarly named to the source
578 # package.579 # package.
579 product_vocab = getVocabularyRegistry().get(None, 'Product')580 product_vocab = getVocabularyRegistry().get(None, 'Product')
580 matches = product_vocab.searchForTerms(self.context.name)581 matches = product_vocab.searchForTerms(self.context.name,
582 vocab_filter=[Product._information_type ==
583 InformationType.PUBLIC])
581 # Based upon the matching products, create a new vocabulary with584 # Based upon the matching products, create a new vocabulary with
582 # term descriptions that include a link to the product.585 # term descriptions that include a link to the product.
583 self.product_suggestions = []586 self.product_suggestions = []
584587
=== modified file 'lib/lp/registry/browser/tests/test_product.py'
--- lib/lp/registry/browser/tests/test_product.py 2012-10-26 01:32:43 +0000
+++ lib/lp/registry/browser/tests/test_product.py 2012-11-05 20:20:29 +0000
@@ -248,7 +248,7 @@
248 product = self.product_set.getByName('fnord')248 product = self.product_set.getByName('fnord')
249 self.assertEqual('registry', product.owner.name)249 self.assertEqual('registry', product.owner.name)
250250
251 def test_owner_is_requried_without_disclaim_maitainer(self):251 def test_owner_is_requried_without_disclaim_maintainer(self):
252 # A valid owner name is required if disclaim_maintainer is252 # A valid owner name is required if disclaim_maintainer is
253 # not selected.253 # not selected.
254 registrant = self.factory.makePerson()254 registrant = self.factory.makePerson()
@@ -496,7 +496,8 @@
496 product,496 product,
497 '+edit',497 '+edit',
498 principal=product.owner)498 principal=product.owner)
499 info_types = [t.name for t in view.widgets['information_type'].vocabulary]499 vocabulary = view.widgets['information_type'].vocabulary
500 info_types = [t.name for t in vocabulary]
500 expected = ['PUBLIC', 'PROPRIETARY', 'EMBARGOED']501 expected = ['PUBLIC', 'PROPRIETARY', 'EMBARGOED']
501 self.assertEqual(expected, info_types)502 self.assertEqual(expected, info_types)
502503
503504
=== modified file 'lib/lp/registry/browser/tests/test_sourcepackage_views.py'
--- lib/lp/registry/browser/tests/test_sourcepackage_views.py 2012-10-18 12:40:40 +0000
+++ lib/lp/registry/browser/tests/test_sourcepackage_views.py 2012-11-05 20:20:29 +0000
@@ -8,6 +8,12 @@
8import cgi8import cgi
9import urllib9import urllib
1010
11from soupmatchers import (
12 HTMLContains,
13 Tag,
14 )
15from testtools.matchers import Not
16from testtools.testcase import ExpectedException
11from zope.component import getUtility17from zope.component import getUtility
12from zope.interface import implements18from zope.interface import implements
13from zope.security.proxy import removeSecurityProxy19from zope.security.proxy import removeSecurityProxy
@@ -24,6 +30,7 @@
24 IDistroSeriesSet,30 IDistroSeriesSet,
25 )31 )
26from lp.registry.interfaces.sourcepackage import ISourcePackage32from lp.registry.interfaces.sourcepackage import ISourcePackage
33from lp.services.features.testing import FeatureFixture
27from lp.soyuz.tests.test_publishing import SoyuzTestPublisher34from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
28from lp.testing import (35from lp.testing import (
29 BrowserTestCase,36 BrowserTestCase,
@@ -152,6 +159,60 @@
152 url, 'field.homepageurl', 'http://eg.dom/bonkers')159 url, 'field.homepageurl', 'http://eg.dom/bonkers')
153160
154161
162class TestSourcePackageView(BrowserTestCase):
163
164 layer = DatabaseFunctionalLayer
165
166 def test_register_upstream_forbids_proprietary(self):
167 # Cannot specify information_type if registering for sourcepackage.
168 self.useFixture(FeatureFixture({'disclosure.private_projects.enabled':
169 'on'}))
170 sourcepackage = self.factory.makeSourcePackage()
171 browser = self.getViewBrowser(sourcepackage)
172 browser.getControl("Register the upstream project").click()
173 browser.getControl("Link to Upstream Project").click()
174 browser.getControl("Summary").value = "summary"
175 browser.getControl("Continue").click()
176 t = Tag('info_type', 'input', attrs={'name': 'field.information_type'})
177 self.assertThat(browser.contents, Not(HTMLContains(t)))
178
179 def test_link_upstream_handles_initial_proprietary(self):
180 # Proprietary product is not listed as an option.
181 owner = self.factory.makePerson()
182 sourcepackage = self.factory.makeSourcePackage()
183 product_name = sourcepackage.name
184 product_displayname = self.factory.getUniqueString()
185 self.factory.makeProduct(
186 name=product_name, owner=owner,
187 information_type=InformationType.PROPRIETARY,
188 displayname=product_displayname)
189 browser = self.getViewBrowser(sourcepackage, user=owner)
190 with ExpectedException(LookupError):
191 browser.getControl(product_displayname)
192
193 def test_link_upstream_handles_proprietary(self):
194 # Proprietary products produce an 'invalid value' error.
195 owner = self.factory.makePerson()
196 product = self.factory.makeProduct(owner=owner)
197 product_name = product.name
198 product_displayname = product.displayname
199 sourcepackage = self.factory.makeSourcePackage(
200 sourcepackagename=product_name)
201 with person_logged_in(None):
202 browser = self.getViewBrowser(sourcepackage, user=owner)
203 with person_logged_in(owner):
204 product.information_type = InformationType.PROPRIETARY
205 browser.getControl(product_displayname).click()
206 browser.getControl("Link to Upstream Project").click()
207 error = Tag(
208 'error', 'div', attrs={'class': 'message'},
209 text='Invalid value')
210 self.assertThat(browser.contents, HTMLContains(error))
211 self.assertNotIn(
212 'The project %s was linked to this source package.' %
213 str(product_displayname), browser.contents)
214
215
155class TestSourcePackageUpstreamConnectionsView(TestCaseWithFactory):216class TestSourcePackageUpstreamConnectionsView(TestCaseWithFactory):
156217
157 layer = DatabaseFunctionalLayer218 layer = DatabaseFunctionalLayer
@@ -299,7 +360,7 @@
299 """Packaging cannot be created for PROPRIETARY products"""360 """Packaging cannot be created for PROPRIETARY products"""
300 product_owner = self.factory.makePerson()361 product_owner = self.factory.makePerson()
301 product_name = 'proprietary-product'362 product_name = 'proprietary-product'
302 product = self.factory.makeProduct(363 self.factory.makeProduct(
303 name=product_name, owner=product_owner,364 name=product_name, owner=product_owner,
304 information_type=InformationType.PROPRIETARY)365 information_type=InformationType.PROPRIETARY)
305 ubuntu_series = self.factory.makeUbuntuDistroSeries()366 ubuntu_series = self.factory.makeUbuntuDistroSeries()
306367
=== modified file 'lib/lp/registry/vocabularies.py'
--- lib/lp/registry/vocabularies.py 2012-10-19 10:34:55 +0000
+++ lib/lp/registry/vocabularies.py 2012-11-05 20:20:29 +0000
@@ -296,12 +296,14 @@
296 like_query = query.lower()296 like_query = query.lower()
297 like_query = "'%%%%' || %s || '%%%%'" % quote_like(like_query)297 like_query = "'%%%%' || %s || '%%%%'" % quote_like(like_query)
298 fti_query = quote(query)298 fti_query = quote(query)
299 if vocab_filter is None:
300 vocab_filter = []
299 where_clause = And(301 where_clause = And(
300 SQL(302 SQL(
301 "active = 't' AND (name LIKE %s OR fti @@ ftq(%s))" % (303 "active = 't' AND (name LIKE %s OR fti @@ ftq(%s))" % (
302 like_query, fti_query)),304 like_query, fti_query)),
303 ProductSet.getProductPrivacyFilter(305 ProductSet.getProductPrivacyFilter(
304 getUtility(ILaunchBag).user))306 getUtility(ILaunchBag).user), *vocab_filter)
305 order_by = SQL(307 order_by = SQL(
306 '(CASE name WHEN %s THEN 1 '308 '(CASE name WHEN %s THEN 1 '
307 ' ELSE rank(fti, ftq(%s)) END) DESC, displayname, name'309 ' ELSE rank(fti, ftq(%s)) END) DESC, displayname, name'
308310
=== modified file 'lib/lp/services/webapp/vocabulary.py'
--- lib/lp/services/webapp/vocabulary.py 2011-12-30 06:14:56 +0000
+++ lib/lp/services/webapp/vocabulary.py 2012-11-05 20:20:29 +0000
@@ -307,7 +307,7 @@
307 # search functionality produce a new vocabulary restricted to the307 # search functionality produce a new vocabulary restricted to the
308 # desired subset.308 # desired subset.
309 def searchForTerms(self, query=None, vocab_filter=None):309 def searchForTerms(self, query=None, vocab_filter=None):
310 results = self.search(query)310 results = self.search(query, vocab_filter)
311 return CountableIterator(results.count(), results, self.toTerm)311 return CountableIterator(results.count(), results, self.toTerm)
312312
313 def search(self, query, vocab_filter=None):313 def search(self, query, vocab_filter=None):