launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #13027
[Merge] lp:~deryck/launchpad/milestone-information-type into lp:launchpad
Deryck Hodge has proposed merging lp:~deryck/launchpad/milestone-information-type into lp:launchpad.
Commit message:
Make Milestone gets its information_type from its related Product. (Part of private projects work.)
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~deryck/launchpad/milestone-information-type/+merge/128227
This branch adapts Milestone to Product for IInformationType so that Milestone has its related Product's information_type. Abel's excellent security adapter work will secure Milestone once it provides IInformationType. So this branch does the adaptation and deals with the remaining UI work for the web UI.
I discussed the approach here with abentley on a couple different occasions.
The primary work is to provide an adapter. I have also added two tests for that. One to check the adapter function itself, and another to ensure that Milestone has IInformationType. The rest of the work is adding the privacy portlet UI. I did not add the edit icon for this because this UI should informational. One could even argue it's un-needed since the privacy banner works, but I added it to be consistent with other pages. The main work for this part was updating the InformationTypePortletMixin to also handle an adapted object. The rest is just templating and zcml to wire up the portlet and a few tests.
I also added information_type_description to LaunchpadView as a small add-on fix. LaunchpadView is providing information_type, so it made since to me to also add the description.
I have a few tests I'm fixing but I thought it worth getting this up for review now. We are lint free, too.
--
https://code.launchpad.net/~deryck/launchpad/milestone-information-type/+merge/128227
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~deryck/launchpad/milestone-information-type into lp:launchpad.
=== modified file 'lib/lp/app/browser/informationtype.py'
--- lib/lp/app/browser/informationtype.py 2012-09-17 15:19:10 +0000
+++ lib/lp/app/browser/informationtype.py 2012-10-05 11:22:24 +0000
@@ -9,28 +9,51 @@
from lazr.restful.interfaces import IJSONRequestCache
from lp.app.enums import PRIVATE_INFORMATION_TYPES
+from lp.app.interfaces.informationtype import IInformationType
from lp.app.utilities import json_dump_information_types
class InformationTypePortletMixin:
def initialize(self):
+ information_typed = IInformationType(self.context, None)
+ if information_typed is None:
+ information_typed = self.context
cache = IJSONRequestCache(self.request)
json_dump_information_types(
cache,
- self.context.getAllowedInformationTypes(self.user))
+ information_typed.getAllowedInformationTypes(self.user))
@property
def information_type(self):
- return self.context.information_type.title
+ information_typed = IInformationType(self.context, None)
+ if information_typed is None:
+ information_typed = self.context
+ return information_typed.information_type.title
@property
def information_type_description(self):
- return self.context.information_type.description
+ information_typed = IInformationType(self.context, None)
+ if information_typed is None:
+ information_typed = self.context
+ return information_typed.information_type.description
@property
def information_type_css(self):
- if self.context.information_type in PRIVATE_INFORMATION_TYPES:
+ information_typed = IInformationType(self.context, None)
+ if information_typed is None:
+ information_typed = self.context
+ if information_typed.information_type in PRIVATE_INFORMATION_TYPES:
return 'sprite private'
else:
return 'sprite public'
+
+ @property
+ def privacy_portlet_css(self):
+ information_typed = IInformationType(self.context, None)
+ if information_typed is None:
+ information_typed = self.context
+ if information_typed.information_type in PRIVATE_INFORMATION_TYPES:
+ return 'portlet private'
+ else:
+ return 'portlet public'
=== modified file 'lib/lp/registry/adapters.py'
--- lib/lp/registry/adapters.py 2012-01-01 02:58:52 +0000
+++ lib/lp/registry/adapters.py 2012-10-05 11:22:24 +0000
@@ -133,3 +133,8 @@
def package_to_sourcepackagename(package):
"""Adapts a package to its `ISourcePackageName`."""
return package.sourcepackagename
+
+
+def information_type_from_product(milestone):
+ """Adapts a milestone to product for information_type."""
+ return milestone.product
=== modified file 'lib/lp/registry/browser/configure.zcml'
--- lib/lp/registry/browser/configure.zcml 2012-08-15 16:05:54 +0000
+++ lib/lp/registry/browser/configure.zcml 2012-10-05 11:22:24 +0000
@@ -1294,6 +1294,9 @@
<browser:page
name="+productrelease-data"
template="../templates/productrelease-portlet-data.pt"/>
+ <browser:page
+ name="+portlet-privacy"
+ template="../templates/milestone-portlet-privacy.pt"/>
</browser:pages>
<browser:page
name="+pillar-table-row"
=== modified file 'lib/lp/registry/browser/milestone.py'
--- lib/lp/registry/browser/milestone.py 2012-06-11 00:03:25 +0000
+++ lib/lp/registry/browser/milestone.py 2012-10-05 11:22:24 +0000
@@ -38,6 +38,7 @@
)
from lp import _
+from lp.app.browser.informationtype import InformationTypePortletMixin
from lp.app.browser.launchpadform import (
action,
custom_widget,
@@ -355,7 +356,8 @@
class MilestoneView(
- LaunchpadView, MilestoneViewMixin, ProductDownloadFileMixin):
+ LaunchpadView, MilestoneViewMixin, ProductDownloadFileMixin,
+ InformationTypePortletMixin):
"""A View for listing milestones and releases."""
# XXX sinzui 2009-05-29 bug=381672: Extract the BugTaskListingItem rules
# to a mixin so that MilestoneView and others can use it.
=== modified file 'lib/lp/registry/browser/tests/test_milestone.py'
--- lib/lp/registry/browser/tests/test_milestone.py 2012-10-02 21:23:52 +0000
+++ lib/lp/registry/browser/tests/test_milestone.py 2012-10-05 11:22:24 +0000
@@ -5,8 +5,10 @@
__metaclass__ = type
+import soupmatchers
from testtools.matchers import LessThan
from zope.component import getUtility
+from zope.security.proxy import removeSecurityProxy
from lp.app.enums import InformationType
from lp.app.interfaces.launchpad import ILaunchpadCelebrities
@@ -20,6 +22,7 @@
from lp.registry.model.milestonetag import ProjectGroupMilestoneTag
from lp.services.webapp import canonical_url
from lp.testing import (
+ BrowserTestCase,
login_person,
login_team,
logout,
@@ -36,7 +39,7 @@
from lp.testing.views import create_initialized_view
-class TestMilestoneViews(TestCaseWithFactory):
+class TestMilestoneViews(BrowserTestCase):
layer = DatabaseFunctionalLayer
@@ -54,6 +57,52 @@
view = create_initialized_view(milestone, '+index')
self.assertIn('some work for this milestone', view.render())
+ def test_information_type_public(self):
+ # A milestone's view should include its information_type,
+ # which defaults to Public for new projects.
+ milestone = self.factory.makeMilestone()
+ view = create_initialized_view(milestone, '+index')
+ self.assertEqual('Public', view.information_type)
+
+ def test_information_type_proprietary(self):
+ # A milestone's view should get its information_type
+ # from the related product even if the product is changed to
+ # PROPRIETARY.
+ product = self.factory.makeProduct()
+ self.factory.makeCommercialSubscription(product)
+ information_type = InformationType.PROPRIETARY
+ removeSecurityProxy(product).information_type = information_type
+ milestone = self.factory.makeMilestone(product=product)
+ view = create_initialized_view(milestone, '+index')
+ self.assertEqual('Proprietary', view.information_type)
+
+ def test_privacy_portlet(self):
+ # A milestone's page should include a privacy portlet that
+ # accurately describes the information_type.
+ owner = self.factory.makePerson()
+ product = self.factory.makeProduct(owner=owner)
+ self.factory.makeCommercialSubscription(product)
+ information_type = InformationType.PROPRIETARY
+ removeSecurityProxy(product).information_type = information_type
+ milestone = self.factory.makeMilestone(product=product)
+ policy = self.factory.makeAccessPolicy(pillar=product)
+ grant = self.factory.makeAccessPolicyGrant(
+ policy=policy, grantee=owner)
+ privacy_portlet = soupmatchers.Tag(
+ 'info-type-portlet', 'span',
+ attrs={'id': 'information-type-summary'})
+ privacy_portlet_proprietary = soupmatchers.Tag(
+ 'info-type-text', 'strong', attrs={'id': 'information-type'},
+ text='Proprietary')
+ browser = self.getViewBrowser(milestone, '+index', user=owner)
+ # First, assert that the portlet exists.
+ self.assertThat(
+ browser.contents, soupmatchers.HTMLContains(privacy_portlet))
+ # Then, assert that the text displayed matches the information_type.
+ self.assertThat(
+ browser.contents, soupmatchers.HTMLContains(
+ privacy_portlet_proprietary))
+
class TestAddMilestoneViews(TestCaseWithFactory):
=== modified file 'lib/lp/registry/configure.zcml'
--- lib/lp/registry/configure.zcml 2012-10-03 18:35:09 +0000
+++ lib/lp/registry/configure.zcml 2012-10-05 11:22:24 +0000
@@ -1077,6 +1077,10 @@
interface="lp.bugs.interfaces.structuralsubscription.IStructuralSubscriptionTargetWrite" />
</class>
+ <adapter
+ provides="lp.app.interfaces.informationtype.IInformationType"
+ for="lp.registry.interfaces.milestone.IMilestone"
+ factory="lp.registry.adapters.information_type_from_product"/>
<!-- IMilestoneSet -->
=== modified file 'lib/lp/registry/templates/milestone-index.pt'
--- lib/lp/registry/templates/milestone-index.pt 2012-07-10 10:33:09 +0000
+++ lib/lp/registry/templates/milestone-index.pt 2012-10-05 11:22:24 +0000
@@ -340,6 +340,7 @@
<tal:side metal:fill-slot="side">
<tal:menu replace="structure view/milestone/@@+global-actions" />
+ <tal:privacy replace="structure context/@@+portlet-privacy" />
</tal:side>
</body>
</html>
=== added file 'lib/lp/registry/templates/milestone-portlet-privacy.pt'
--- lib/lp/registry/templates/milestone-portlet-privacy.pt 1970-01-01 00:00:00 +0000
+++ lib/lp/registry/templates/milestone-portlet-privacy.pt 2012-10-05 11:22:24 +0000
@@ -0,0 +1,15 @@
+<div
+ xmlns:tal="http://xml.zope.org/namespaces/tal"
+ xmlns:metal="http://xml.zope.org/namespaces/metal"
+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+ id="privacy"
+ tal:attributes="class view/privacy_portlet_css"
+>
+ <span id="information-type-summary"
+ tal:attributes="class view/information_type_css;">This milestone
+ contains <strong id="information-type" tal:content="view/information_type">
+ </strong> information</span>
+
+ <div id="information-type-description" style="padding-top: 5px" tal:content="view/information_type_description"></div>
+</div>
+
=== modified file 'lib/lp/registry/tests/test_adapters.py'
--- lib/lp/registry/tests/test_adapters.py 2012-01-01 02:58:52 +0000
+++ lib/lp/registry/tests/test_adapters.py 2012-10-05 11:22:24 +0000
@@ -10,6 +10,7 @@
package_to_sourcepackagename,
productseries_to_product,
sourcepackage_to_distribution,
+ information_type_from_product,
)
from lp.registry.interfaces.distribution import IDistribution
from lp.registry.interfaces.product import IProduct
@@ -73,3 +74,11 @@
self.assertTrue(IProduct.providedBy(product))
self.assertEqual(product_series.product, product)
self.assertEqual(product, IProduct(product_series))
+
+ def test_information_type_from_product(self):
+ # information_type_from_product() returns an IProduct given
+ # an IMilestone.
+ milestone = self.factory.makeMilestone()
+ product = information_type_from_product(milestone)
+ self.assertTrue(IProduct.providedBy(product))
+ self.assertEqual(product, milestone.product)
=== modified file 'lib/lp/registry/tests/test_milestone.py'
--- lib/lp/registry/tests/test_milestone.py 2012-10-02 02:56:32 +0000
+++ lib/lp/registry/tests/test_milestone.py 2012-10-05 11:22:24 +0000
@@ -9,8 +9,11 @@
import unittest
from zope.component import getUtility
+from zope.security.proxy import removeSecurityProxy
+from lp.app.enums import InformationType
from lp.app.errors import NotFoundError
+from lp.app.interfaces.informationtype import IInformationType
from lp.registry.interfaces.distribution import IDistributionSet
from lp.registry.interfaces.milestone import (
IHasMilestones,
@@ -231,3 +234,19 @@
self.factory.makeSpecificationWorkItem(
specification=specification, milestone=milestone, deleted=True)
self.assertContentEqual([], milestone.specifications)
+
+
+class TestMilestoneInformationType(TestCaseWithFactory):
+ """Tests for information_type and Milestone."""
+
+ layer = DatabaseFunctionalLayer
+
+ def test_information_type_from_product(self):
+ # Milestones should inherit information_type from its product."""
+ product = self.factory.makeProduct()
+ self.factory.makeCommercialSubscription(product)
+ information_type = InformationType.PROPRIETARY
+ removeSecurityProxy(product).information_type = information_type
+ milestone = self.factory.makeMilestone(product=product)
+ self.assertEqual(
+ IInformationType(milestone).information_type, information_type)
=== modified file 'lib/lp/services/webapp/publisher.py'
--- lib/lp/services/webapp/publisher.py 2012-09-18 19:41:02 +0000
+++ lib/lp/services/webapp/publisher.py 2012-10-05 11:22:24 +0000
@@ -310,6 +310,14 @@
return None
return information_typed.information_type.title
+ @property
+ def information_type_description(self):
+ """A view has the information_type_description of its context."""
+ information_typed = IInformationType(self.context, None)
+ if information_typed is None:
+ return None
+ return information_typed.information_type.description
+
def __init__(self, context, request):
self.context = context
self.request = request
Follow ups