launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #12947
[Merge] lp:~abentley/launchpad/info-type-app-config into lp:launchpad
Aaron Bentley has proposed merging lp:~abentley/launchpad/info-type-app-config into lp:launchpad.
Commit message:
Information type configures applications.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~abentley/launchpad/info-type-app-config/+merge/128064
= Summary =
Information type configures applications.
== Proposed fix ==
Answers and private projects are mutually exclusive. The Product information_type, not the license, determines the sharing policies. Embargoed sets policies to EMBARGOED_OR_PROPRIETARY where applicable.
== Pre-implementation notes ==
Discussed with deryck
== LOC Rationale ==
Part of private projects.
== Implementation details ==
Updated createProduct to use information_type to determine default sharing policies. Added Specification to the list of sharing policies.
Updated answers_usage setter to refuse LAUNCHPAD for proprietary products.
Updated _valid_product_information_type to refuse proprietary types if answers_usage is LAUNCHPAD.
Updated Answers configuration to omit LAUNCHPAD option for proprietary products.
Updated makeProduct factory method to use an appropriate license if information_type is proprietary.
== Tests ==
bin/test test_product
== Demo and Q/A ==
Create a PROPRIETARY product. The branch, bug, specification policies should all be PROPRIETARY.
Create an EMBARGOED product. The branch and specification policies should be EMBARGOED_OR_PROPRIETARY, but the bug policy should be PROPRIETARY.
Configure "support tracker". "Launchpad" should not be listed.
= Launchpad lint =
Checking for conflicts and issues in changed files.
Linting changed files:
lib/lp/app/errors.py
lib/lp/registry/browser/product.py
lib/lp/testing/factory.py
lib/lp/registry/browser/tests/test_product.py
lib/lp/registry/model/product.py
lib/lp/registry/tests/test_product.py
--
https://code.launchpad.net/~abentley/launchpad/info-type-app-config/+merge/128064
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~abentley/launchpad/info-type-app-config into lp:launchpad.
=== modified file 'lib/lp/app/errors.py'
--- lib/lp/app/errors.py 2012-06-06 05:58:44 +0000
+++ lib/lp/app/errors.py 2012-10-04 16:48:24 +0000
@@ -9,6 +9,7 @@
'NameLookupFailed',
'NotFoundError',
'POSTToNonCanonicalURL',
+ 'ServiceUsageForbidden',
'SubscriptionPrivacyViolation',
'TranslationUnavailable',
'UnexpectedFormData',
@@ -81,5 +82,10 @@
"""The subscription would violate privacy policies."""
+@error_status(httplib.BAD_REQUEST)
+class ServiceUsageForbidden(Exception):
+ """The specified ServiceUsage is not allowed."""
+
+
# Slam a 401 response code onto all ForbiddenAttribute errors.
error_status(httplib.UNAUTHORIZED)(ForbiddenAttribute)
=== modified file 'lib/lp/registry/browser/product.py'
--- lib/lp/registry/browser/product.py 2012-10-04 02:18:31 +0000
+++ lib/lp/registry/browser/product.py 2012-10-04 16:48:24 +0000
@@ -69,6 +69,10 @@
Bool,
Choice,
)
+from zope.schema.vocabulary import (
+ SimpleTerm,
+ SimpleVocabulary,
+ )
from lp import _
from lp.answers.browser.faqtarget import FAQTargetNavigationMixin
@@ -101,6 +105,7 @@
from lp.app.enums import (
InformationType,
PUBLIC_PROPRIETARY_INFORMATION_TYPES,
+ PROPRIETARY_INFORMATION_TYPES,
ServiceUsage,
)
from lp.app.errors import NotFoundError
@@ -1326,6 +1331,14 @@
field.description = (
field.description.replace('pillar', 'project'))
usage_field.field = field
+ if (self.usage_fieldname == 'answers_usage' and
+ self.context.information_type in
+ PROPRIETARY_INFORMATION_TYPES):
+ values = usage_field.field.vocabulary.items
+ terms = [SimpleTerm(value, value.name, value.title)
+ for value in values
+ if value != ServiceUsage.LAUNCHPAD]
+ usage_field.field.vocabulary = SimpleVocabulary(terms)
@property
def field_names(self):
=== modified file 'lib/lp/registry/browser/tests/test_product.py'
--- lib/lp/registry/browser/tests/test_product.py 2012-10-04 16:08:27 +0000
+++ lib/lp/registry/browser/tests/test_product.py 2012-10-04 16:48:24 +0000
@@ -6,6 +6,11 @@
__metaclass__ = type
from lazr.restful.interfaces import IJSONRequestCache
+from soupmatchers import (
+ HTMLContains,
+ Tag,
+ )
+from testtools.matchers import Not
import transaction
from zope.component import getUtility
from zope.schema.vocabulary import SimpleVocabulary
@@ -13,6 +18,7 @@
from lp.app.browser.lazrjs import vocabulary_to_choice_edit_items
from lp.app.enums import (
InformationType,
+ PROPRIETARY_INFORMATION_TYPES,
ServiceUsage,
)
from lp.registry.browser.product import (
@@ -47,7 +53,7 @@
)
-class TestProductConfiguration(TestCaseWithFactory):
+class TestProductConfiguration(BrowserTestCase):
"""Tests the configuration links and helpers."""
layer = DatabaseFunctionalLayer
@@ -78,6 +84,24 @@
self.assertTrue(view.registration_done)
+ lp_tag = Tag('lp_tag', 'input', attrs={'value': 'LAUNCHPAD'})
+
+ def test_configure_answers_has_launchpad_for_public(self):
+ # Public projects support LAUNCHPAD for answers.
+ browser = self.getViewBrowser(self.product, '+configure-answers',
+ user=self.product.owner)
+ self.assertThat(browser.contents, HTMLContains(self.lp_tag))
+
+ def test_configure_answers_skips_launchpad_for_proprietary(self):
+ # Proprietary projects forbid LAUNCHPAD for answers.
+ for info_type in PROPRIETARY_INFORMATION_TYPES:
+ product = self.factory.makeProduct(information_type=info_type)
+ with person_logged_in(None):
+ browser = self.getViewBrowser(product, '+configure-answers',
+ user=product.owner)
+ self.assertThat(browser.contents, Not(HTMLContains(self.lp_tag)))
+
+
class TestProductAddView(TestCaseWithFactory):
"""Tests the configuration links and helpers."""
=== modified file 'lib/lp/registry/model/product.py'
--- lib/lp/registry/model/product.py 2012-10-04 08:22:14 +0000
+++ lib/lp/registry/model/product.py 2012-10-04 16:48:24 +0000
@@ -75,7 +75,10 @@
service_uses_launchpad,
ServiceUsage,
)
-from lp.app.errors import NotFoundError
+from lp.app.errors import (
+ NotFoundError,
+ ServiceUsageForbidden,
+ )
from lp.app.interfaces.launchpad import (
IHasIcon,
IHasLogo,
@@ -424,6 +427,9 @@
def _valid_product_information_type(self, attr, value):
if value not in PUBLIC_PROPRIETARY_INFORMATION_TYPES:
raise CannotChangeInformationType('Not supported for Projects.')
+ if value in PROPRIETARY_INFORMATION_TYPES:
+ if self.answers_usage == ServiceUsage.LAUNCHPAD:
+ raise CannotChangeInformationType('Answers is enabled.')
# Proprietary check works only after creation, because during
# creation, has_commercial_subscription cannot give the right value
# and triggers an inappropriate DB flush.
@@ -876,6 +882,10 @@
return self._answers_usage
def _set_answers_usage(self, val):
+ if val == ServiceUsage.LAUNCHPAD:
+ if self.information_type in PROPRIETARY_INFORMATION_TYPES:
+ raise ServiceUsageForbidden(
+ "Answers not allowed for non-public projects.")
self._answers_usage = val
if val == ServiceUsage.LAUNCHPAD:
self.official_answers = True
@@ -1682,17 +1692,25 @@
# Set up the sharing policies and product licence.
bug_sharing_policy_to_use = BugSharingPolicy.PUBLIC
branch_sharing_policy_to_use = BranchSharingPolicy.PUBLIC
+ specification_sharing_policy_to_use = (
+ SpecificationSharingPolicy.PUBLIC)
if len(licenses) > 0:
product._setLicenses(licenses, reset_project_reviewed=False)
- # By default, new non-proprietary projects use public bugs and
- # branches. Proprietary projects are given a complimentary 30 day
- # commercial subscription and so may use proprietary sharing
- # policies.
- if License.OTHER_PROPRIETARY in licenses:
- bug_sharing_policy_to_use = BugSharingPolicy.PROPRIETARY
- branch_sharing_policy_to_use = BranchSharingPolicy.PROPRIETARY
+ if information_type == InformationType.PROPRIETARY:
+ bug_sharing_policy_to_use = BugSharingPolicy.PROPRIETARY
+ branch_sharing_policy_to_use = BranchSharingPolicy.PROPRIETARY
+ specification_sharing_policy_to_use = (
+ SpecificationSharingPolicy.PROPRIETARY)
+ if information_type == InformationType.EMBARGOED:
+ bug_sharing_policy_to_use = BugSharingPolicy.PROPRIETARY
+ branch_sharing_policy_to_use = (
+ BranchSharingPolicy.EMBARGOED_OR_PROPRIETARY)
+ specification_sharing_policy_to_use = (
+ SpecificationSharingPolicy.EMBARGOED_OR_PROPRIETARY)
product.setBugSharingPolicy(bug_sharing_policy_to_use)
product.setBranchSharingPolicy(branch_sharing_policy_to_use)
+ product.setSpecificationSharingPolicy(
+ specification_sharing_policy_to_use)
# Create a default trunk series and set it as the development focus
trunk = product.newSeries(
=== modified file 'lib/lp/registry/tests/test_product.py'
--- lib/lp/registry/tests/test_product.py 2012-10-04 08:22:14 +0000
+++ lib/lp/registry/tests/test_product.py 2012-10-04 16:48:24 +0000
@@ -28,6 +28,7 @@
PROPRIETARY_INFORMATION_TYPES,
ServiceUsage,
)
+from lp.app.errors import ServiceUsageForbidden
from lp.app.interfaces.launchpad import (
IHasIcon,
IHasLogo,
@@ -382,27 +383,73 @@
self.assertEqual(BugSharingPolicy.PUBLIC, product.bug_sharing_policy)
self.assertEqual(
BranchSharingPolicy.PUBLIC, product.branch_sharing_policy)
+ self.assertEqual(
+ SpecificationSharingPolicy.PUBLIC,
+ product.specification_sharing_policy)
aps = getUtility(IAccessPolicySource).findByPillar([product])
expected = [
InformationType.USERDATA, InformationType.PRIVATESECURITY]
self.assertContentEqual(expected, [policy.type for policy in aps])
def test_proprietary_product_creation_sharing_policies(self):
- # Creating a new proprietary product sets the bug and branch sharing
- # polices to proprietary.
+ # Creating a new proprietary product sets the bug, branch, and
+ # specification sharing polices to proprietary.
owner = self.factory.makePerson()
with person_logged_in(owner):
product = getUtility(IProductSet).createProduct(
owner, 'carrot', 'Carrot', 'Carrot', 'testing',
- licenses=[License.OTHER_PROPRIETARY])
+ licenses=[License.OTHER_PROPRIETARY],
+ information_type=InformationType.PROPRIETARY)
self.assertEqual(
BugSharingPolicy.PROPRIETARY, product.bug_sharing_policy)
self.assertEqual(
BranchSharingPolicy.PROPRIETARY, product.branch_sharing_policy)
+ self.assertEqual(
+ SpecificationSharingPolicy.PROPRIETARY,
+ product.specification_sharing_policy)
aps = getUtility(IAccessPolicySource).findByPillar([product])
expected = [InformationType.PROPRIETARY]
self.assertContentEqual(expected, [policy.type for policy in aps])
+ def test_embargoed_product_creation_sharing_policies(self):
+ # Creating a new embargoed product sets the branch and
+ # specification sharing polices to embargoed or proprietary, and the
+ # bug sharing policy to proprietary.
+ owner = self.factory.makePerson()
+ with person_logged_in(owner):
+ product = getUtility(IProductSet).createProduct(
+ owner, 'carrot', 'Carrot', 'Carrot', 'testing',
+ licenses=[License.OTHER_PROPRIETARY],
+ information_type=InformationType.EMBARGOED)
+ self.assertEqual(
+ BugSharingPolicy.PROPRIETARY,
+ product.bug_sharing_policy)
+ self.assertEqual(
+ BranchSharingPolicy.EMBARGOED_OR_PROPRIETARY,
+ product.branch_sharing_policy)
+ self.assertEqual(
+ SpecificationSharingPolicy.EMBARGOED_OR_PROPRIETARY,
+ product.specification_sharing_policy)
+ aps = getUtility(IAccessPolicySource).findByPillar([product])
+ expected = [InformationType.PROPRIETARY, InformationType.EMBARGOED]
+ self.assertContentEqual(expected, [policy.type for policy in aps])
+
+ def test_other_proprietary_product_creation_sharing_policies(self):
+ # Creating a new product with other/proprietary license leaves bug
+ # and branch sharing polices at their default.
+ owner = self.factory.makePerson()
+ with person_logged_in(owner):
+ product = getUtility(IProductSet).createProduct(
+ owner, 'carrot', 'Carrot', 'Carrot', 'testing',
+ licenses=[License.OTHER_PROPRIETARY])
+ self.assertEqual(
+ BugSharingPolicy.PUBLIC, product.bug_sharing_policy)
+ self.assertEqual(
+ BranchSharingPolicy.PUBLIC, product.branch_sharing_policy)
+ aps = getUtility(IAccessPolicySource).findByPillar([product])
+ expected = [InformationType.USERDATA, InformationType.PRIVATESECURITY]
+ self.assertContentEqual(expected, [policy.type for policy in aps])
+
def createProduct(self, information_type=None, license=None):
# convenience method for testing IProductSet.createProduct rather than
# self.factory.makeProduct
@@ -490,6 +537,7 @@
product = self.createProduct(info_type, License.OTHER_PROPRIETARY)
self.assertEqual(info_type, product.information_type)
+<<<<<<< TREE
def check_permissions(self, expected_permissions, used_permissions,
type_):
expected = set(expected_permissions.keys())
@@ -798,6 +846,43 @@
self.assertEqual(
queries_for_first_user_access, len(recorder.queries))
+=======
+ def test_no_answers_for_proprietary(self):
+ for info_type in PROPRIETARY_INFORMATION_TYPES:
+ product = self.factory.makeProduct(information_type=info_type)
+ self.assertEqual(ServiceUsage.UNKNOWN, product.answers_usage)
+ with person_logged_in(product.owner):
+ for usage in ServiceUsage.items:
+ if usage == ServiceUsage.LAUNCHPAD:
+ with ExpectedException(
+ ServiceUsageForbidden,
+ "Answers not allowed for non-public projects."):
+ product.answers_usage = ServiceUsage.LAUNCHPAD
+ else:
+ # all other values are permitted.
+ product.answers_usage = usage
+
+ def test_answers_for_public(self):
+ product = self.factory.makeProduct(
+ information_type=InformationType.PUBLIC)
+ self.assertEqual(ServiceUsage.UNKNOWN, product.answers_usage)
+ with person_logged_in(product.owner):
+ for usage in ServiceUsage.items:
+ # all values are permitted.
+ product.answers_usage = usage
+
+ def test_no_proprietary_if_answers(self):
+ # Information type cannot be set to proprietary while Answers are
+ # enabled.
+ product = self.factory.makeProduct(
+ licenses=[License.OTHER_PROPRIETARY])
+ with person_logged_in(product.owner):
+ product.answers_usage = ServiceUsage.LAUNCHPAD
+ with ExpectedException(
+ CannotChangeInformationType, 'Answers is enabled.'):
+ product.information_type=InformationType.PROPRIETARY
+
+>>>>>>> MERGE-SOURCE
class TestProductBugInformationTypes(TestCaseWithFactory):
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2012-10-03 18:40:53 +0000
+++ lib/lp/testing/factory.py 2012-10-04 16:48:24 +0000
@@ -978,7 +978,10 @@
else:
displayname = name.capitalize()
if licenses is None:
- licenses = [License.GNU_GPL_V2]
+ if information_type in PROPRIETARY_INFORMATION_TYPES:
+ licenses = [License.OTHER_PROPRIETARY]
+ else:
+ licenses = [License.GNU_GPL_V2]
if title is None:
title = self.getUniqueString('title')
if summary is None:
Follow ups