← Back to team overview

launchpad-reviewers team mailing list archive

[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