Merge lp:~jcsackett/launchpad/no-private-releases into lp:launchpad

Proposed by j.c.sackett
Status: Merged
Approved by: j.c.sackett
Approved revision: no longer in the source branch.
Merged at revision: 16362
Proposed branch: lp:~jcsackett/launchpad/no-private-releases
Merge into: lp:launchpad
Diff against target: 265 lines (+109/-2)
6 files modified
lib/lp/registry/browser/productrelease.py (+19/-0)
lib/lp/registry/browser/tests/test_productrelease.py (+56/-0)
lib/lp/registry/model/milestone.py (+6/-0)
lib/lp/registry/templates/productrelease-add-from-series.pt (+7/-0)
lib/lp/registry/templates/productrelease-add.pt (+7/-0)
lib/lp/registry/tests/test_milestone.py (+14/-2)
To merge this branch: bzr merge lp:~jcsackett/launchpad/no-private-releases
Reviewer Review Type Date Requested Status
Benji York (community) code Approve
Review via email: mp+139092@code.launchpad.net

Commit message

Blocks making releases for private products and adds warning about public release for embargoed ones.

Description of the change

Summary
=======
Releases must be blocked for proprietary products as releases must be public.

For embargoed products, where releases may be part of unembargoing a project,
they are allowed but warn the user.

Preimp
======
Spoke with Deryck.

Implementation
==============
A check is made to forbid release creation on private products at the model
level, to prevent it happening over the API.

The AddRelease views gain a new method to check if releases are allowed; if
they are not, the template renders a message saying they are not rather than
rendering the form.

The AddRelease views, on initialize, check if the product is EMBARGOED. If so,
they add a warning notification explaining that the release will be public.

Tests
=====
bin/test -vvct NonPublicProductReleaseViewTestCase

QA
==
Attempt creating releases for EMBARGOED and PROPRIETARY products. On
EMBARGOED, you should get a warning, but be able to create the release. On
PROPRIETARY, you should not get the form.

LoC
===
Part of private projects.

Lint
====

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/registry/templates/productrelease-add.pt
  lib/lp/registry/browser/productrelease.py
  lib/lp/registry/tests/test_milestone.py
  lib/lp/registry/templates/productrelease-add-from-series.pt
  lib/lp/registry/browser/tests/test_productrelease.py
  lib/lp/registry/model/milestone.py

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

Looks good.

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/productrelease.py'
--- lib/lp/registry/browser/productrelease.py 2012-11-19 23:09:58 +0000
+++ lib/lp/registry/browser/productrelease.py 2012-12-11 13:43:24 +0000
@@ -39,6 +39,7 @@
39 LaunchpadEditFormView,39 LaunchpadEditFormView,
40 LaunchpadFormView,40 LaunchpadFormView,
41 )41 )
42from lp.app.enums import InformationType
42from lp.app.widgets.date import DateTimeWidget43from lp.app.widgets.date import DateTimeWidget
43from lp.registry.browser import (44from lp.registry.browser import (
44 BaseRdfView,45 BaseRdfView,
@@ -135,6 +136,11 @@
135 notify(ObjectCreatedEvent(newrelease))136 notify(ObjectCreatedEvent(newrelease))
136137
137 @property138 @property
139 def releases_allowed(self):
140 return (self.context.product.information_type !=
141 InformationType.PROPRIETARY)
142
143 @property
138 def label(self):144 def label(self):
139 """The form label."""145 """The form label."""
140 return smartquote('Create a new release for %s' %146 return smartquote('Create a new release for %s' %
@@ -160,6 +166,11 @@
160 ]166 ]
161167
162 def initialize(self):168 def initialize(self):
169 if (self.context.product.information_type ==
170 InformationType.EMBARGOED):
171 self.request.response.addWarningNotification(
172 _("Any releases added for %s will be PUBLIC." %
173 self.context.product.displayname))
163 if self.context.product_release is not None:174 if self.context.product_release is not None:
164 self.request.response.addErrorNotification(175 self.request.response.addErrorNotification(
165 _("A project release already exists for this milestone."))176 _("A project release already exists for this milestone."))
@@ -191,6 +202,14 @@
191 'changelog',202 'changelog',
192 ]203 ]
193204
205 def initialize(self):
206 if (self.context.product.information_type ==
207 InformationType.EMBARGOED):
208 self.request.response.addWarningNotification(
209 _("Any releases added for %s will be PUBLIC." %
210 self.context.displayname))
211 super(ProductReleaseFromSeriesAddView, self).initialize()
212
194 def setUpFields(self):213 def setUpFields(self):
195 super(ProductReleaseFromSeriesAddView, self).setUpFields()214 super(ProductReleaseFromSeriesAddView, self).setUpFields()
196 self._prependKeepMilestoneActiveField()215 self._prependKeepMilestoneActiveField()
197216
=== modified file 'lib/lp/registry/browser/tests/test_productrelease.py'
--- lib/lp/registry/browser/tests/test_productrelease.py 2012-12-10 13:43:47 +0000
+++ lib/lp/registry/browser/tests/test_productrelease.py 2012-12-11 13:43:24 +0000
@@ -6,8 +6,10 @@
6__metaclass__ = type6__metaclass__ = type
77
88
9from lp.app.enums import InformationType
9from lp.services.webapp.escaping import html_escape10from lp.services.webapp.escaping import html_escape
10from lp.testing import (11from lp.testing import (
12 BrowserTestCase,
11 person_logged_in,13 person_logged_in,
12 TestCaseWithFactory,14 TestCaseWithFactory,
13 )15 )
@@ -15,6 +17,60 @@
15from lp.testing.views import create_initialized_view17from lp.testing.views import create_initialized_view
1618
1719
20class NonPublicProductReleaseViewTestCase(BrowserTestCase):
21
22 layer = LaunchpadFunctionalLayer
23
24 def test_proprietary_add_milestone(self):
25 owner = self.factory.makePerson()
26 product = self.factory.makeProduct(name='fnord',
27 owner=owner, information_type=InformationType.PROPRIETARY)
28 milestone = self.factory.makeMilestone(product=product)
29 with person_logged_in(owner):
30 browser = self.getViewBrowser(
31 milestone, view_name="+addrelease", user=owner)
32 msg = 'Fnord is PROPRIETARY. It cannot have any releases.'
33 self.assertTrue(html_escape(msg) in browser.contents)
34
35 def test_proprietary_add_series(self):
36 owner = self.factory.makePerson()
37 product = self.factory.makeProduct(name='fnord',
38 owner=owner, information_type=InformationType.PROPRIETARY)
39 series = self.factory.makeProductSeries(product=product, name='bnord')
40 with person_logged_in(owner):
41 browser = self.getViewBrowser(
42 series, view_name="+addrelease", user=owner)
43 msg = ('The bnord series of Fnord is PROPRIETARY.'
44 ' It cannot have any releases.')
45 self.assertTrue(html_escape(msg) in browser.contents)
46
47 def test_embargoed_add_milestone(self):
48 owner = self.factory.makePerson()
49 product = self.factory.makeProduct(name='fnord',
50 owner=owner, information_type=InformationType.EMBARGOED)
51 milestone = self.factory.makeMilestone(product=product)
52 with person_logged_in(owner):
53 view = create_initialized_view(milestone, name="+addrelease")
54 notifications = [
55 nm.message for nm in view.request.response.notifications]
56 self.assertEqual(
57 [html_escape("Any releases added for Fnord will be PUBLIC.")],
58 notifications)
59
60 def test_embargoed_add_series(self):
61 owner = self.factory.makePerson()
62 product = self.factory.makeProduct(name='fnord',
63 owner=owner, information_type=InformationType.EMBARGOED)
64 series = self.factory.makeProductSeries(product=product, name='bnord')
65 with person_logged_in(owner):
66 view = create_initialized_view(series, name="+addrelease")
67 notifications = [
68 nm.message for nm in view.request.response.notifications]
69 self.assertEqual(
70 [html_escape("Any releases added for bnord will be PUBLIC.")],
71 notifications)
72
73
18class ProductReleaseAddDownloadFileViewTestCase(TestCaseWithFactory):74class ProductReleaseAddDownloadFileViewTestCase(TestCaseWithFactory):
1975
20 layer = LaunchpadFunctionalLayer76 layer = LaunchpadFunctionalLayer
2177
=== modified file 'lib/lp/registry/model/milestone.py'
--- lib/lp/registry/model/milestone.py 2012-12-06 13:33:46 +0000
+++ lib/lp/registry/model/milestone.py 2012-12-11 13:43:24 +0000
@@ -38,6 +38,7 @@
38from zope.interface import implements38from zope.interface import implements
3939
40from lp.app.errors import NotFoundError40from lp.app.errors import NotFoundError
41from lp.app.enums import InformationType
41from lp.blueprints.model.specification import (42from lp.blueprints.model.specification import (
42 Specification,43 Specification,
43 visible_specification_query,44 visible_specification_query,
@@ -55,6 +56,7 @@
55from lp.bugs.model.structuralsubscription import (56from lp.bugs.model.structuralsubscription import (
56 StructuralSubscriptionTargetMixin,57 StructuralSubscriptionTargetMixin,
57 )58 )
59from lp.registry.errors import ProprietaryProduct
58from lp.registry.interfaces.milestone import (60from lp.registry.interfaces.milestone import (
59 IHasMilestones,61 IHasMilestones,
60 IMilestone,62 IMilestone,
@@ -274,6 +276,10 @@
274 def createProductRelease(self, owner, datereleased,276 def createProductRelease(self, owner, datereleased,
275 changelog=None, release_notes=None):277 changelog=None, release_notes=None):
276 """See `IMilestone`."""278 """See `IMilestone`."""
279 info_type = self.product.information_type
280 if info_type == InformationType.PROPRIETARY:
281 raise ProprietaryProduct(
282 "Proprietary products cannot have releases.")
277 if self.product_release is not None:283 if self.product_release is not None:
278 raise MultipleProductReleases()284 raise MultipleProductReleases()
279 release = ProductRelease(285 release = ProductRelease(
280286
=== modified file 'lib/lp/registry/templates/productrelease-add-from-series.pt'
--- lib/lp/registry/templates/productrelease-add-from-series.pt 2012-05-11 21:40:31 +0000
+++ lib/lp/registry/templates/productrelease-add-from-series.pt 2012-12-11 13:43:24 +0000
@@ -64,6 +64,7 @@
64</metal:block>64</metal:block>
6565
66<div metal:fill-slot="main">66<div metal:fill-slot="main">
67 <tal:releases-allowed condition="view/releases_allowed">
67 <div metal:use-macro="context/@@launchpad_form/form">68 <div metal:use-macro="context/@@launchpad_form/form">
68 <div metal:fill-slot="extra_info" tal:condition="context/releases"69 <div metal:fill-slot="extra_info" tal:condition="context/releases"
69 style="border-bottom: solid 1px black">70 style="border-bottom: solid 1px black">
@@ -77,6 +78,12 @@
77 </ul>78 </ul>
78 </div>79 </div>
79 </div>80 </div>
81 </tal:releases-allowed>
82 <tal:releases-forbidden condition="not: view/releases_allowed">
83 The <tal:seriesname replace="context/displayname"
84 /> series of <tal:productname replace="context/product/displayname"
85 /> is PROPRIETARY. It cannot have any releases.
86 </tal:releases-forbidden>
80</div>87</div>
8188
82</body>89</body>
8390
=== modified file 'lib/lp/registry/templates/productrelease-add.pt'
--- lib/lp/registry/templates/productrelease-add.pt 2010-10-10 21:54:16 +0000
+++ lib/lp/registry/templates/productrelease-add.pt 2012-12-11 13:43:24 +0000
@@ -14,6 +14,7 @@
14 </metal:block>14 </metal:block>
1515
16<div metal:fill-slot="main">16<div metal:fill-slot="main">
17 <tal:releases-allowed condition="view/releases_allowed">
17 <div metal:use-macro="context/@@launchpad_form/form">18 <div metal:use-macro="context/@@launchpad_form/form">
18 <div id="other-releases"19 <div id="other-releases"
19 metal:fill-slot="extra_info"20 metal:fill-slot="extra_info"
@@ -29,6 +30,12 @@
29 </ul>30 </ul>
30 </div>31 </div>
31 </div>32 </div>
33 </tal:releases-allowed>
34 <tal:releases-forbidden condition="not: view/releases_allowed">
35 <tal:productname replace="context/product/displayname"
36 /> is PROPRIETARY. It cannot have any releases.
37 </tal:releases-forbidden>
38
32</div>39</div>
3340
34</body>41</body>
3542
=== modified file 'lib/lp/registry/tests/test_milestone.py'
--- lib/lp/registry/tests/test_milestone.py 2012-12-06 16:09:31 +0000
+++ lib/lp/registry/tests/test_milestone.py 2012-12-11 13:43:24 +0000
@@ -5,8 +5,8 @@
55
6__metaclass__ = type6__metaclass__ = type
77
8import datetime
8from operator import attrgetter9from operator import attrgetter
9import unittest
1010
11from storm.exceptions import NoneError11from storm.exceptions import NoneError
12from zope.component import getUtility12from zope.component import getUtility
@@ -29,6 +29,7 @@
29 IHasMilestones,29 IHasMilestones,
30 IMilestoneSet,30 IMilestoneSet,
31 )31 )
32from lp.registry.errors import ProprietaryProduct
32from lp.registry.interfaces.product import IProductSet33from lp.registry.interfaces.product import IProductSet
33from lp.testing import (34from lp.testing import (
34 ANONYMOUS,35 ANONYMOUS,
@@ -44,16 +45,18 @@
44from lp.testing.matchers import DoesNotSnapshot45from lp.testing.matchers import DoesNotSnapshot
4546
4647
47class MilestoneTest(unittest.TestCase):48class MilestoneTest(TestCaseWithFactory):
48 """Milestone tests."""49 """Milestone tests."""
4950
50 layer = LaunchpadFunctionalLayer51 layer = LaunchpadFunctionalLayer
5152
52 def setUp(self):53 def setUp(self):
54 super(MilestoneTest, self).setUp()
53 login(ANONYMOUS)55 login(ANONYMOUS)
5456
55 def tearDown(self):57 def tearDown(self):
56 logout()58 logout()
59 super(MilestoneTest, self).tearDown()
5760
58 def testMilestoneSetIterator(self):61 def testMilestoneSetIterator(self):
59 """Test of MilestoneSet.__iter__()."""62 """Test of MilestoneSet.__iter__()."""
@@ -113,6 +116,15 @@
113 all_visible_milestones_ids,116 all_visible_milestones_ids,
114 [1, 2, 3])117 [1, 2, 3])
115118
119 def test_proprietary_product_milestones_cannot_have_releases(self):
120 owner = self.factory.makePerson()
121 product = self.factory.makeProduct(
122 owner=owner, information_type=InformationType.PROPRIETARY)
123 milestone = self.factory.makeMilestone(product=product)
124 with person_logged_in(owner):
125 self.assertRaises(ProprietaryProduct,
126 milestone.createProductRelease, owner, datetime.date.today())
127
116128
117class MilestoneSecurityAdaperTestCase(TestCaseWithFactory):129class MilestoneSecurityAdaperTestCase(TestCaseWithFactory):
118 """A TestCase for the security adapter of milestones."""130 """A TestCase for the security adapter of milestones."""