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
1=== modified file 'lib/lp/registry/browser/productrelease.py'
2--- lib/lp/registry/browser/productrelease.py 2012-11-19 23:09:58 +0000
3+++ lib/lp/registry/browser/productrelease.py 2012-12-11 13:43:24 +0000
4@@ -39,6 +39,7 @@
5 LaunchpadEditFormView,
6 LaunchpadFormView,
7 )
8+from lp.app.enums import InformationType
9 from lp.app.widgets.date import DateTimeWidget
10 from lp.registry.browser import (
11 BaseRdfView,
12@@ -135,6 +136,11 @@
13 notify(ObjectCreatedEvent(newrelease))
14
15 @property
16+ def releases_allowed(self):
17+ return (self.context.product.information_type !=
18+ InformationType.PROPRIETARY)
19+
20+ @property
21 def label(self):
22 """The form label."""
23 return smartquote('Create a new release for %s' %
24@@ -160,6 +166,11 @@
25 ]
26
27 def initialize(self):
28+ if (self.context.product.information_type ==
29+ InformationType.EMBARGOED):
30+ self.request.response.addWarningNotification(
31+ _("Any releases added for %s will be PUBLIC." %
32+ self.context.product.displayname))
33 if self.context.product_release is not None:
34 self.request.response.addErrorNotification(
35 _("A project release already exists for this milestone."))
36@@ -191,6 +202,14 @@
37 'changelog',
38 ]
39
40+ def initialize(self):
41+ if (self.context.product.information_type ==
42+ InformationType.EMBARGOED):
43+ self.request.response.addWarningNotification(
44+ _("Any releases added for %s will be PUBLIC." %
45+ self.context.displayname))
46+ super(ProductReleaseFromSeriesAddView, self).initialize()
47+
48 def setUpFields(self):
49 super(ProductReleaseFromSeriesAddView, self).setUpFields()
50 self._prependKeepMilestoneActiveField()
51
52=== modified file 'lib/lp/registry/browser/tests/test_productrelease.py'
53--- lib/lp/registry/browser/tests/test_productrelease.py 2012-12-10 13:43:47 +0000
54+++ lib/lp/registry/browser/tests/test_productrelease.py 2012-12-11 13:43:24 +0000
55@@ -6,8 +6,10 @@
56 __metaclass__ = type
57
58
59+from lp.app.enums import InformationType
60 from lp.services.webapp.escaping import html_escape
61 from lp.testing import (
62+ BrowserTestCase,
63 person_logged_in,
64 TestCaseWithFactory,
65 )
66@@ -15,6 +17,60 @@
67 from lp.testing.views import create_initialized_view
68
69
70+class NonPublicProductReleaseViewTestCase(BrowserTestCase):
71+
72+ layer = LaunchpadFunctionalLayer
73+
74+ def test_proprietary_add_milestone(self):
75+ owner = self.factory.makePerson()
76+ product = self.factory.makeProduct(name='fnord',
77+ owner=owner, information_type=InformationType.PROPRIETARY)
78+ milestone = self.factory.makeMilestone(product=product)
79+ with person_logged_in(owner):
80+ browser = self.getViewBrowser(
81+ milestone, view_name="+addrelease", user=owner)
82+ msg = 'Fnord is PROPRIETARY. It cannot have any releases.'
83+ self.assertTrue(html_escape(msg) in browser.contents)
84+
85+ def test_proprietary_add_series(self):
86+ owner = self.factory.makePerson()
87+ product = self.factory.makeProduct(name='fnord',
88+ owner=owner, information_type=InformationType.PROPRIETARY)
89+ series = self.factory.makeProductSeries(product=product, name='bnord')
90+ with person_logged_in(owner):
91+ browser = self.getViewBrowser(
92+ series, view_name="+addrelease", user=owner)
93+ msg = ('The bnord series of Fnord is PROPRIETARY.'
94+ ' It cannot have any releases.')
95+ self.assertTrue(html_escape(msg) in browser.contents)
96+
97+ def test_embargoed_add_milestone(self):
98+ owner = self.factory.makePerson()
99+ product = self.factory.makeProduct(name='fnord',
100+ owner=owner, information_type=InformationType.EMBARGOED)
101+ milestone = self.factory.makeMilestone(product=product)
102+ with person_logged_in(owner):
103+ view = create_initialized_view(milestone, name="+addrelease")
104+ notifications = [
105+ nm.message for nm in view.request.response.notifications]
106+ self.assertEqual(
107+ [html_escape("Any releases added for Fnord will be PUBLIC.")],
108+ notifications)
109+
110+ def test_embargoed_add_series(self):
111+ owner = self.factory.makePerson()
112+ product = self.factory.makeProduct(name='fnord',
113+ owner=owner, information_type=InformationType.EMBARGOED)
114+ series = self.factory.makeProductSeries(product=product, name='bnord')
115+ with person_logged_in(owner):
116+ view = create_initialized_view(series, name="+addrelease")
117+ notifications = [
118+ nm.message for nm in view.request.response.notifications]
119+ self.assertEqual(
120+ [html_escape("Any releases added for bnord will be PUBLIC.")],
121+ notifications)
122+
123+
124 class ProductReleaseAddDownloadFileViewTestCase(TestCaseWithFactory):
125
126 layer = LaunchpadFunctionalLayer
127
128=== modified file 'lib/lp/registry/model/milestone.py'
129--- lib/lp/registry/model/milestone.py 2012-12-06 13:33:46 +0000
130+++ lib/lp/registry/model/milestone.py 2012-12-11 13:43:24 +0000
131@@ -38,6 +38,7 @@
132 from zope.interface import implements
133
134 from lp.app.errors import NotFoundError
135+from lp.app.enums import InformationType
136 from lp.blueprints.model.specification import (
137 Specification,
138 visible_specification_query,
139@@ -55,6 +56,7 @@
140 from lp.bugs.model.structuralsubscription import (
141 StructuralSubscriptionTargetMixin,
142 )
143+from lp.registry.errors import ProprietaryProduct
144 from lp.registry.interfaces.milestone import (
145 IHasMilestones,
146 IMilestone,
147@@ -274,6 +276,10 @@
148 def createProductRelease(self, owner, datereleased,
149 changelog=None, release_notes=None):
150 """See `IMilestone`."""
151+ info_type = self.product.information_type
152+ if info_type == InformationType.PROPRIETARY:
153+ raise ProprietaryProduct(
154+ "Proprietary products cannot have releases.")
155 if self.product_release is not None:
156 raise MultipleProductReleases()
157 release = ProductRelease(
158
159=== modified file 'lib/lp/registry/templates/productrelease-add-from-series.pt'
160--- lib/lp/registry/templates/productrelease-add-from-series.pt 2012-05-11 21:40:31 +0000
161+++ lib/lp/registry/templates/productrelease-add-from-series.pt 2012-12-11 13:43:24 +0000
162@@ -64,6 +64,7 @@
163 </metal:block>
164
165 <div metal:fill-slot="main">
166+ <tal:releases-allowed condition="view/releases_allowed">
167 <div metal:use-macro="context/@@launchpad_form/form">
168 <div metal:fill-slot="extra_info" tal:condition="context/releases"
169 style="border-bottom: solid 1px black">
170@@ -77,6 +78,12 @@
171 </ul>
172 </div>
173 </div>
174+ </tal:releases-allowed>
175+ <tal:releases-forbidden condition="not: view/releases_allowed">
176+ The <tal:seriesname replace="context/displayname"
177+ /> series of <tal:productname replace="context/product/displayname"
178+ /> is PROPRIETARY. It cannot have any releases.
179+ </tal:releases-forbidden>
180 </div>
181
182 </body>
183
184=== modified file 'lib/lp/registry/templates/productrelease-add.pt'
185--- lib/lp/registry/templates/productrelease-add.pt 2010-10-10 21:54:16 +0000
186+++ lib/lp/registry/templates/productrelease-add.pt 2012-12-11 13:43:24 +0000
187@@ -14,6 +14,7 @@
188 </metal:block>
189
190 <div metal:fill-slot="main">
191+ <tal:releases-allowed condition="view/releases_allowed">
192 <div metal:use-macro="context/@@launchpad_form/form">
193 <div id="other-releases"
194 metal:fill-slot="extra_info"
195@@ -29,6 +30,12 @@
196 </ul>
197 </div>
198 </div>
199+ </tal:releases-allowed>
200+ <tal:releases-forbidden condition="not: view/releases_allowed">
201+ <tal:productname replace="context/product/displayname"
202+ /> is PROPRIETARY. It cannot have any releases.
203+ </tal:releases-forbidden>
204+
205 </div>
206
207 </body>
208
209=== modified file 'lib/lp/registry/tests/test_milestone.py'
210--- lib/lp/registry/tests/test_milestone.py 2012-12-06 16:09:31 +0000
211+++ lib/lp/registry/tests/test_milestone.py 2012-12-11 13:43:24 +0000
212@@ -5,8 +5,8 @@
213
214 __metaclass__ = type
215
216+import datetime
217 from operator import attrgetter
218-import unittest
219
220 from storm.exceptions import NoneError
221 from zope.component import getUtility
222@@ -29,6 +29,7 @@
223 IHasMilestones,
224 IMilestoneSet,
225 )
226+from lp.registry.errors import ProprietaryProduct
227 from lp.registry.interfaces.product import IProductSet
228 from lp.testing import (
229 ANONYMOUS,
230@@ -44,16 +45,18 @@
231 from lp.testing.matchers import DoesNotSnapshot
232
233
234-class MilestoneTest(unittest.TestCase):
235+class MilestoneTest(TestCaseWithFactory):
236 """Milestone tests."""
237
238 layer = LaunchpadFunctionalLayer
239
240 def setUp(self):
241+ super(MilestoneTest, self).setUp()
242 login(ANONYMOUS)
243
244 def tearDown(self):
245 logout()
246+ super(MilestoneTest, self).tearDown()
247
248 def testMilestoneSetIterator(self):
249 """Test of MilestoneSet.__iter__()."""
250@@ -113,6 +116,15 @@
251 all_visible_milestones_ids,
252 [1, 2, 3])
253
254+ def test_proprietary_product_milestones_cannot_have_releases(self):
255+ owner = self.factory.makePerson()
256+ product = self.factory.makeProduct(
257+ owner=owner, information_type=InformationType.PROPRIETARY)
258+ milestone = self.factory.makeMilestone(product=product)
259+ with person_logged_in(owner):
260+ self.assertRaises(ProprietaryProduct,
261+ milestone.createProductRelease, owner, datetime.date.today())
262+
263
264 class MilestoneSecurityAdaperTestCase(TestCaseWithFactory):
265 """A TestCase for the security adapter of milestones."""