Merge lp:~thumper/launchpad/blueprint-update-lifecycle-status-with-event into lp:launchpad

Proposed by Tim Penhey on 2010-11-04
Status: Merged
Approved by: Robert Collins on 2010-11-04
Approved revision: no longer in the source branch.
Merged at revision: 11869
Proposed branch: lp:~thumper/launchpad/blueprint-update-lifecycle-status-with-event
Merge into: lp:launchpad
Diff against target: 772 lines (+351/-266)
8 files modified
lib/lp/blueprints/browser/configure.zcml (+15/-0)
lib/lp/blueprints/browser/specification.py (+7/-6)
lib/lp/blueprints/browser/tests/test_specification.py (+80/-1)
lib/lp/blueprints/configure.zcml (+214/-252)
lib/lp/blueprints/interfaces/specification.py (+7/-0)
lib/lp/blueprints/model/specification.py (+10/-5)
lib/lp/blueprints/subscribers.py (+10/-0)
lib/lp/testing/factory.py (+8/-2)
To merge this branch: bzr merge lp:~thumper/launchpad/blueprint-update-lifecycle-status-with-event
Reviewer Review Type Date Requested Status
Robert Collins (community) 2010-11-04 Approve on 2010-11-04
Review via email: mp+40048@code.launchpad.net

Commit Message

Have the blueprint lifecycle status updated through the event system on modification.

Description of the Change

More yak shaving for blueprints.

On this yak:
 * Fixed the indentation of the primary configure.zcml file
 * Moved some browser configuration into the browser/configure.zcml
 * Change the updateLifecycleStatus to be called due to an object modified event
 * Added a test to show that the lifecycle status is being updated
 * Some lint cleanup in model/specification.py

tests:
 lp.blueprints

To post a comment you must log in.
Robert Collins (lifeless) wrote :

Ugh, single object change notifications - death by queries will build up on this.

I realise you're fixing a bug in the most direct fashion, so its ok - but its not sustainable (we have terrible trouble in bugs due to this pattern).

I have a suggestion for you while you're here, which I think will save you a great deal of pain in the arc you are : add a HasQueryCount scaling test (before you add your event) to capture the current performance profile of the page.

review: Approve
Tim Penhey (thumper) wrote :

Can you give an example of the HasQueryCount type test you are referring to?

Robert Collins (lifeless) wrote :

Just grep for it - theres only a 1/2 dozen tests.

Essentially they run a page twice with different data and compare the
query counts. Scalable pages don't change the query counts.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/blueprints/browser/configure.zcml'
2--- lib/lp/blueprints/browser/configure.zcml 2010-10-03 15:30:06 +0000
3+++ lib/lp/blueprints/browser/configure.zcml 2010-11-04 04:01:31 +0000
4@@ -8,6 +8,21 @@
5 xmlns:i18n="http://namespaces.zope.org/i18n"
6 xmlns:xmlrpc="http://namespaces.zope.org/xmlrpc"
7 i18n_domain="launchpad">
8+
9+ <adapter
10+ name="blueprints"
11+ provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
12+ for="lp.blueprints.interfaces.specificationtarget.IHasSpecifications"
13+ factory="lp.blueprints.browser.specificationtarget.BlueprintsVHostBreadcrumb"
14+ permission="zope.Public"/>
15+ <adapter
16+ name="blueprints"
17+ provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
18+ for="lp.registry.interfaces.person.IPerson"
19+ factory="lp.blueprints.browser.specificationtarget.BlueprintsVHostBreadcrumb"
20+ permission="zope.Public"/>
21+
22+
23 <browser:navigation
24 module="lp.blueprints.browser.sprint"
25 classes="
26
27=== modified file 'lib/lp/blueprints/browser/specification.py'
28--- lib/lp/blueprints/browser/specification.py 2010-11-01 03:32:29 +0000
29+++ lib/lp/blueprints/browser/specification.py 2010-11-04 04:01:31 +0000
30@@ -536,13 +536,14 @@
31
32 @action(_('Change'), name='change')
33 def change_action(self, action, data):
34+ old_status = self.context.lifecycle_status
35 self.updateContextFromData(data)
36 # We need to ensure that resolution is recorded if the spec is now
37 # resolved.
38- newstate = self.context.updateLifecycleStatus(self.user)
39- if newstate is not None:
40+ new_status = self.context.lifecycle_status
41+ if new_status != old_status:
42 self.request.response.addNotification(
43- 'blueprint is now considered "%s".' % newstate.title)
44+ 'Blueprint is now considered "%s".' % new_status.title)
45 self.next_url = canonical_url(self.context)
46
47
48@@ -735,7 +736,7 @@
49 class SpecificationSupersedingView(LaunchpadFormView):
50 schema = ISpecification
51 field_names = ['superseded_by']
52- label = _('Mark specification superseded')
53+ label = _('Mark blueprint superseded')
54 custom_widget('superseded_by', SupersededByWidget)
55
56 @property
57@@ -762,7 +763,7 @@
58 description=_(
59 "The blueprint which supersedes this one. Note "
60 "that selecting a blueprint here and pressing "
61- "Continue will change the specification status "
62+ "Continue will change the blueprint status "
63 "to Superseded.")),
64 render_context=self.render_context)
65
66@@ -784,7 +785,7 @@
67 newstate = self.context.updateLifecycleStatus(self.user)
68 if newstate is not None:
69 self.request.response.addNotification(
70- 'Specification is now considered "%s".' % newstate.title)
71+ 'Blueprint is now considered "%s".' % newstate.title)
72 self.next_url = canonical_url(self.context)
73
74 @property
75
76=== modified file 'lib/lp/blueprints/browser/tests/test_specification.py'
77--- lib/lp/blueprints/browser/tests/test_specification.py 2010-08-20 20:31:18 +0000
78+++ lib/lp/blueprints/browser/tests/test_specification.py 2010-11-04 04:01:31 +0000
79@@ -8,10 +8,13 @@
80 from lazr.restful.testing.webservice import FakeRequest
81 from zope.publisher.interfaces import NotFound
82
83+from canonical.launchpad.webapp.interfaces import BrowserNotificationLevel
84 from canonical.launchpad.webapp.servers import StepsToGo
85 from canonical.testing.layers import DatabaseFunctionalLayer
86 from lp.blueprints.browser import specification
87-from lp.testing import TestCaseWithFactory
88+from lp.blueprints.enums import SpecificationImplementationStatus
89+from lp.testing import login_person, TestCaseWithFactory
90+from lp.testing.views import create_initialized_view
91
92
93 class LocalFakeRequest(FakeRequest):
94@@ -108,6 +111,82 @@
95 self.specification.getBranchLink(branch), self.traverse(segments))
96
97
98+class TestSpecificationEditStatusView(TestCaseWithFactory):
99+ """Test the SpecificationEditStatusView."""
100+
101+ layer = DatabaseFunctionalLayer
102+
103+ def test_records_started(self):
104+ spec = self.factory.makeSpecification(
105+ implementation_status=SpecificationImplementationStatus.NOTSTARTED)
106+ login_person(spec.owner)
107+ form = {
108+ 'field.implementation_status': 'STARTED',
109+ 'field.actions.change': 'Change',
110+ }
111+ view = create_initialized_view(spec, name='+status', form=form)
112+ self.assertEqual(
113+ SpecificationImplementationStatus.STARTED, spec.implementation_status)
114+ self.assertEqual(spec.owner, spec.starter)
115+ [notification] = view.request.notifications
116+ self.assertEqual(BrowserNotificationLevel.NOTICE, notification.level)
117+ self.assertEqual(
118+ 'Blueprint is now considered "Started".', notification.message)
119+
120+ def test_unchanged_lifecycle_has_no_notification(self):
121+ spec = self.factory.makeSpecification(
122+ implementation_status=SpecificationImplementationStatus.STARTED)
123+ login_person(spec.owner)
124+ form = {
125+ 'field.implementation_status': 'SLOW',
126+ 'field.actions.change': 'Change',
127+ }
128+ view = create_initialized_view(spec, name='+status', form=form)
129+ self.assertEqual(
130+ SpecificationImplementationStatus.SLOW, spec.implementation_status)
131+ self.assertEqual(0, len(view.request.notifications))
132+
133+ def test_records_unstarting(self):
134+ # If a spec was started, and is changed to not started, a notice is shown.
135+ # Also the spec.starter is cleared out.
136+ spec = self.factory.makeSpecification(
137+ implementation_status=SpecificationImplementationStatus.STARTED)
138+ login_person(spec.owner)
139+ form = {
140+ 'field.implementation_status': 'NOTSTARTED',
141+ 'field.actions.change': 'Change',
142+ }
143+ view = create_initialized_view(spec, name='+status', form=form)
144+ self.assertEqual(
145+ SpecificationImplementationStatus.NOTSTARTED,
146+ spec.implementation_status)
147+ self.assertIs(None, spec.starter)
148+ [notification] = view.request.notifications
149+ self.assertEqual(BrowserNotificationLevel.NOTICE, notification.level)
150+ self.assertEqual(
151+ 'Blueprint is now considered "Not started".', notification.message)
152+
153+ def test_records_completion(self):
154+ # If a spec is marked as implemented the user is notifiec it is now
155+ # complete.
156+ spec = self.factory.makeSpecification(
157+ implementation_status=SpecificationImplementationStatus.STARTED)
158+ login_person(spec.owner)
159+ form = {
160+ 'field.implementation_status': 'IMPLEMENTED',
161+ 'field.actions.change': 'Change',
162+ }
163+ view = create_initialized_view(spec, name='+status', form=form)
164+ self.assertEqual(
165+ SpecificationImplementationStatus.IMPLEMENTED,
166+ spec.implementation_status)
167+ self.assertEqual(spec.owner, spec.completer)
168+ [notification] = view.request.notifications
169+ self.assertEqual(BrowserNotificationLevel.NOTICE, notification.level)
170+ self.assertEqual(
171+ 'Blueprint is now considered "Complete".', notification.message)
172+
173+
174 class TestSecificationHelpers(unittest.TestCase):
175 """Test specification helper functions."""
176
177
178=== modified file 'lib/lp/blueprints/configure.zcml'
179--- lib/lp/blueprints/configure.zcml 2010-08-25 04:12:59 +0000
180+++ lib/lp/blueprints/configure.zcml 2010-11-04 04:01:31 +0000
181@@ -22,258 +22,220 @@
182 name="blueprints" />
183
184
185- <lp:help-folder
186- folder="help" type="lp.blueprints.publisher.BlueprintsLayer" />
187-
188- <!-- Sprint -->
189-
190- <class
191- class="lp.blueprints.model.sprint.Sprint">
192- <allow
193- interface="lp.blueprints.interfaces.sprint.ISprint"/>
194- <require
195- permission="launchpad.AnyPerson"
196- set_schema="lp.blueprints.interfaces.sprint.ISprint"/>
197- </class>
198- <adapter
199- provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
200- for="lp.blueprints.interfaces.sprint.ISprint"
201- factory="canonical.launchpad.webapp.breadcrumb.TitleBreadcrumb"
202- permission="zope.Public"/>
203- <adapter
204- provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
205- for="lp.blueprints.interfaces.sprint.ISprintSet"
206- factory="lp.blueprints.browser.sprint.SprintSetBreadcrumb"
207- permission="zope.Public"/>
208- <adapter
209- provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
210- for="lp.blueprints.interfaces.specification.ISpecification"
211- factory="canonical.launchpad.webapp.breadcrumb.TitleBreadcrumb"
212- permission="zope.Public"/>
213-
214- <!-- This is a view used to export data needed by the sprint scheduler.
215- As there are no API stability guarantees, the view name starts
216- with "temp" to discourage people from relying on it. -->
217-
218-
219- <!-- SprintSet -->
220-
221- <class
222- class="lp.blueprints.model.sprint.SprintSet">
223- <allow
224- interface="lp.blueprints.interfaces.sprint.ISprintSet"/>
225- </class>
226- <securedutility
227- class="lp.blueprints.model.sprint.SprintSet"
228- provides="lp.blueprints.interfaces.sprint.ISprintSet">
229- <allow
230- interface="lp.blueprints.interfaces.sprint.ISprintSet"/>
231- </securedutility>
232-
233- <!-- SprintSpecification -->
234-
235- <class
236- class="lp.blueprints.model.sprintspecification.SprintSpecification">
237- <allow
238- interface="lp.blueprints.interfaces.sprintspecification.ISprintSpecification"/>
239- <require
240- permission="launchpad.Edit"
241- set_attributes="whiteboard"/>
242- </class>
243-
244- <!-- SpecificationDependency -->
245-
246- <class
247- class="lp.blueprints.model.specificationdependency.SpecificationDependency">
248- <allow
249- interface="lp.blueprints.interfaces.specificationdependency.ISpecificationDependency"/>
250- <require
251- permission="zope.Public"
252- set_schema="lp.blueprints.interfaces.specificationdependency.ISpecificationDependency"/>
253- </class>
254- <adapter
255- for="lp.blueprints.interfaces.specificationdependency.ISpecificationDependency"
256- factory="lp.blueprints.interfaces.specificationdependency.SpecDependencyIsAlsoRemoval"
257- provides="lp.blueprints.interfaces.specificationdependency.ISpecificationDependencyRemoval"/>
258-
259- <!-- SpecificationSubscription -->
260-
261- <class
262- class="lp.blueprints.model.specificationsubscription.SpecificationSubscription">
263- <allow
264- interface="lp.blueprints.interfaces.specificationsubscription.ISpecificationSubscription"/>
265- <require
266- permission="launchpad.Edit"
267- set_attributes="essential"/>
268- </class>
269- <subscriber
270- for="lp.blueprints.interfaces.specificationsubscription.ISpecificationSubscription lazr.lifecycle.interfaces.IObjectCreatedEvent"
271- handler="canonical.launchpad.mailnotification.notify_specification_subscription_created"/>
272- <subscriber
273- for="lp.blueprints.interfaces.specificationsubscription.ISpecificationSubscription lazr.lifecycle.interfaces.IObjectModifiedEvent"
274- handler="canonical.launchpad.mailnotification.notify_specification_subscription_modified"/>
275-
276- <!-- SpecificationFeedback -->
277-
278- <class
279- class="lp.blueprints.model.specificationfeedback.SpecificationFeedback">
280- <allow
281- interface="lp.blueprints.interfaces.specificationfeedback.ISpecificationFeedback"/>
282- <require
283- permission="zope.Public"
284- set_schema="lp.blueprints.interfaces.specificationfeedback.ISpecificationFeedback"/>
285- </class>
286-
287- <!-- SprintAttendance -->
288-
289- <class
290- class="lp.blueprints.model.sprintattendance.SprintAttendance">
291- <allow
292- interface="lp.blueprints.interfaces.sprintattendance.ISprintAttendance"/>
293- <require
294- permission="launchpad.Edit"
295- set_attributes="time_starts time_ends"/>
296- </class>
297-
298- <!-- ISpecificationBranch -->
299-
300- <class
301- class="lp.blueprints.model.specificationbranch.SpecificationBranch">
302- <allow
303- interface="lp.blueprints.interfaces.specificationbranch.ISpecificationBranch"/>
304- <require
305- permission="launchpad.AnyPerson"
306- set_attributes="summary"/>
307- </class>
308- <subscriber
309- for="lp.blueprints.interfaces.specificationbranch.ISpecificationBranch lazr.lifecycle.interfaces.IObjectCreatedEvent"
310- handler="canonical.launchpad.subscribers.karma.spec_branch_created"/>
311- <facet
312- facet="specifications"/>
313-
314- <!-- SpecificationBranchSet -->
315-
316- <securedutility
317- class="lp.blueprints.model.specificationbranch.SpecificationBranchSet"
318- provides="lp.blueprints.interfaces.specificationbranch.ISpecificationBranchSet">
319- <allow
320- interface="lp.blueprints.interfaces.specificationbranch.ISpecificationBranchSet"/>
321- </securedutility>
322- <facet
323- facet="specifications">
324-
325- <!-- Specification -->
326-
327- <class
328- class="lp.blueprints.model.specification.Specification">
329- <allow
330- interface="lp.blueprints.interfaces.specification.ISpecification"/>
331-
332- <!-- We allow any authenticated person to update the whiteboard -->
333-
334- <require
335- permission="launchpad.AnyPerson"
336- set_attributes="whiteboard"/>
337-
338- <!-- NB: goals and goalstatus are not to be set directly, it should
339- only be set through the proposeGoal / acceptBy / declineBy
340- methods
341- -->
342-
343- <require
344- permission="launchpad.Edit"
345- set_attributes="name title summary definition_status specurl superseded_by milestone product distribution approver assignee drafter man_days implementation_status"/>
346- <require
347- permission="launchpad.Admin"
348- set_attributes="priority direction_approved"/>
349- <allow
350- attributes="
351- bugs
352+ <lp:help-folder
353+ folder="help" type="lp.blueprints.publisher.BlueprintsLayer" />
354+
355+ <!-- Sprint -->
356+
357+ <class
358+ class="lp.blueprints.model.sprint.Sprint">
359+ <allow
360+ interface="lp.blueprints.interfaces.sprint.ISprint"/>
361+ <require
362+ permission="launchpad.AnyPerson"
363+ set_schema="lp.blueprints.interfaces.sprint.ISprint"/>
364+ </class>
365+ <adapter
366+ provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
367+ for="lp.blueprints.interfaces.sprint.ISprint"
368+ factory="canonical.launchpad.webapp.breadcrumb.TitleBreadcrumb"
369+ permission="zope.Public"/>
370+ <adapter
371+ provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
372+ for="lp.blueprints.interfaces.sprint.ISprintSet"
373+ factory="lp.blueprints.browser.sprint.SprintSetBreadcrumb"
374+ permission="zope.Public"/>
375+ <adapter
376+ provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
377+ for="lp.blueprints.interfaces.specification.ISpecification"
378+ factory="canonical.launchpad.webapp.breadcrumb.TitleBreadcrumb"
379+ permission="zope.Public"/>
380+
381+ <!-- This is a view used to export data needed by the sprint scheduler.
382+ As there are no API stability guarantees, the view name starts
383+ with "temp" to discourage people from relying on it. -->
384+
385+ <!-- SprintSet -->
386+
387+ <class
388+ class="lp.blueprints.model.sprint.SprintSet">
389+ <allow interface="lp.blueprints.interfaces.sprint.ISprintSet"/>
390+ </class>
391+ <securedutility
392+ class="lp.blueprints.model.sprint.SprintSet"
393+ provides="lp.blueprints.interfaces.sprint.ISprintSet">
394+ <allow interface="lp.blueprints.interfaces.sprint.ISprintSet"/>
395+ </securedutility>
396+
397+ <!-- SprintSpecification -->
398+
399+ <class
400+ class="lp.blueprints.model.sprintspecification.SprintSpecification">
401+ <allow
402+ interface="lp.blueprints.interfaces.sprintspecification.ISprintSpecification"/>
403+ <require
404+ permission="launchpad.Edit"
405+ set_attributes="whiteboard"/>
406+ </class>
407+
408+ <!-- SpecificationDependency -->
409+
410+ <class class="lp.blueprints.model.specificationdependency.SpecificationDependency">
411+ <allow interface="lp.blueprints.interfaces.specificationdependency.ISpecificationDependency"/>
412+ <require
413+ permission="zope.Public"
414+ set_schema="lp.blueprints.interfaces.specificationdependency.ISpecificationDependency"/>
415+ </class>
416+ <adapter
417+ for="lp.blueprints.interfaces.specificationdependency.ISpecificationDependency"
418+ factory="lp.blueprints.interfaces.specificationdependency.SpecDependencyIsAlsoRemoval"
419+ provides="lp.blueprints.interfaces.specificationdependency.ISpecificationDependencyRemoval"/>
420+
421+ <!-- SpecificationSubscription -->
422+
423+ <class class="lp.blueprints.model.specificationsubscription.SpecificationSubscription">
424+ <allow
425+ interface="lp.blueprints.interfaces.specificationsubscription.ISpecificationSubscription"/>
426+ <require
427+ permission="launchpad.Edit"
428+ set_attributes="essential"/>
429+ </class>
430+ <subscriber
431+ for="lp.blueprints.interfaces.specificationsubscription.ISpecificationSubscription
432+ lazr.lifecycle.interfaces.IObjectCreatedEvent"
433+ handler="canonical.launchpad.mailnotification.notify_specification_subscription_created"/>
434+ <subscriber
435+ for="lp.blueprints.interfaces.specificationsubscription.ISpecificationSubscription
436+ lazr.lifecycle.interfaces.IObjectModifiedEvent"
437+ handler="canonical.launchpad.mailnotification.notify_specification_subscription_modified"/>
438+
439+ <!-- SpecificationFeedback -->
440+
441+ <class class="lp.blueprints.model.specificationfeedback.SpecificationFeedback">
442+ <allow interface="lp.blueprints.interfaces.specificationfeedback.ISpecificationFeedback"/>
443+ <require
444+ permission="zope.Public"
445+ set_schema="lp.blueprints.interfaces.specificationfeedback.ISpecificationFeedback"/>
446+ </class>
447+
448+ <!-- SprintAttendance -->
449+
450+ <class class="lp.blueprints.model.sprintattendance.SprintAttendance">
451+ <allow interface="lp.blueprints.interfaces.sprintattendance.ISprintAttendance"/>
452+ <require
453+ permission="launchpad.Edit"
454+ set_attributes="time_starts time_ends"/>
455+ </class>
456+
457+ <!-- ISpecificationBranch -->
458+
459+ <class class="lp.blueprints.model.specificationbranch.SpecificationBranch">
460+ <allow interface="lp.blueprints.interfaces.specificationbranch.ISpecificationBranch"/>
461+ <require
462+ permission="launchpad.AnyPerson"
463+ set_attributes="summary"/>
464+ </class>
465+ <subscriber
466+ for="lp.blueprints.interfaces.specificationbranch.ISpecificationBranch
467+ lazr.lifecycle.interfaces.IObjectCreatedEvent"
468+ handler="canonical.launchpad.subscribers.karma.spec_branch_created"/>
469+
470+ <!-- SpecificationBranchSet -->
471+
472+ <securedutility
473+ class="lp.blueprints.model.specificationbranch.SpecificationBranchSet"
474+ provides="lp.blueprints.interfaces.specificationbranch.ISpecificationBranchSet">
475+ <allow interface="lp.blueprints.interfaces.specificationbranch.ISpecificationBranchSet"/>
476+ </securedutility>
477+
478+ <!-- Specification -->
479+
480+ <class class="lp.blueprints.model.specification.Specification">
481+ <allow interface="lp.blueprints.interfaces.specification.ISpecification"/>
482+ <!-- We allow any authenticated person to update the whiteboard -->
483+ <require
484+ permission="launchpad.AnyPerson"
485+ set_attributes="whiteboard"/>
486+ <!-- NB: goals and goalstatus are not to be set directly, it should
487+ only be set through the proposeGoal / acceptBy / declineBy
488+ methods -->
489+ <require
490+ permission="launchpad.Edit"
491+ set_attributes="name title summary definition_status specurl
492+ superseded_by milestone product distribution
493+ approver assignee drafter man_days
494+ implementation_status"/>
495+ <require
496+ permission="launchpad.Admin"
497+ set_attributes="priority direction_approved"/>
498+ <allow
499+ attributes="bugs
500 bug_links"/>
501- <require
502- permission="launchpad.AnyPerson"
503- attributes="
504- linkBug
505+ <require
506+ permission="launchpad.AnyPerson"
507+ attributes="linkBug
508 unlinkBug"/>
509- </class>
510- <class
511- class="lp.blueprints.model.specificationbug.SpecificationBug">
512- <allow
513- interface="lp.blueprints.interfaces.specificationbug.ISpecificationBug"/>
514- </class>
515- <subscriber
516- for="lp.blueprints.interfaces.specification.ISpecification lazr.lifecycle.interfaces.IObjectCreatedEvent"
517- handler="canonical.launchpad.subscribers.karma.spec_created"/>
518- <subscriber
519- for="lp.blueprints.interfaces.specification.ISpecification lazr.lifecycle.interfaces.IObjectModifiedEvent"
520- handler="canonical.launchpad.subscribers.karma.spec_modified"/>
521-
522- <!-- these pages are simple enough they don't need browser code -->
523-
524-
525- <!-- these pages require special browser code -->
526-
527-
528- <!-- SpecificationSet -->
529-
530- <class
531- class="lp.blueprints.model.specification.SpecificationSet">
532- <allow
533- interface="lp.blueprints.interfaces.specification.ISpecificationSet"/>
534- </class>
535- <securedutility
536- class="lp.blueprints.model.specification.SpecificationSet"
537- provides="lp.blueprints.interfaces.specification.ISpecificationSet">
538- <allow
539- interface="lp.blueprints.interfaces.specification.ISpecificationSet"/>
540- </securedutility>
541-
542- <!-- SpecificationDelta -->
543-
544- <class
545- class="lp.blueprints.adapters.SpecificationDelta">
546- <allow
547- interface="lp.blueprints.interfaces.specification.ISpecificationDelta"/>
548- </class>
549-
550- <!-- SpecificationMessage -->
551-
552- <class
553- class="lp.blueprints.model.specificationmessage.SpecificationMessage">
554- <allow
555- interface="lp.blueprints.interfaces.specificationmessage.ISpecificationMessage"/>
556- </class>
557-
558- <!-- SpecificationMessageSet -->
559-
560- <class
561- class="lp.blueprints.model.specificationmessage.SpecificationMessageSet">
562- <allow
563- interface="lp.blueprints.interfaces.specificationmessage.ISpecificationMessageSet"/>
564- </class>
565- <securedutility
566- class="lp.blueprints.model.specificationmessage.SpecificationMessageSet"
567- provides="lp.blueprints.interfaces.specificationmessage.ISpecificationMessageSet">
568- <allow
569- interface="lp.blueprints.interfaces.specificationmessage.ISpecificationMessageSet"/>
570- </securedutility>
571- </facet>
572- <subscriber
573- for="lp.blueprints.interfaces.specification.ISpecification lazr.lifecycle.interfaces.IObjectModifiedEvent"
574- handler="lp.blueprints.subscribers.specification_goalstatus"/>
575- <subscriber
576- for="lp.blueprints.interfaces.specification.ISpecification lazr.lifecycle.interfaces.IObjectModifiedEvent"
577- handler="canonical.launchpad.mailnotification.notify_specification_modified"/>
578- <adapter
579- name="blueprints"
580- provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
581- for="lp.blueprints.interfaces.specificationtarget.IHasSpecifications"
582- factory="lp.blueprints.browser.specificationtarget.BlueprintsVHostBreadcrumb"
583- permission="zope.Public"/>
584- <adapter
585- name="blueprints"
586- provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
587- for="lp.registry.interfaces.person.IPerson"
588- factory="lp.blueprints.browser.specificationtarget.BlueprintsVHostBreadcrumb"
589- permission="zope.Public"/>
590+ </class>
591+
592+ <class class="lp.blueprints.model.specificationbug.SpecificationBug">
593+ <allow interface="lp.blueprints.interfaces.specificationbug.ISpecificationBug"/>
594+ </class>
595+
596+ <subscriber
597+ for="lp.blueprints.interfaces.specification.ISpecification
598+ lazr.lifecycle.interfaces.IObjectCreatedEvent"
599+ handler="canonical.launchpad.subscribers.karma.spec_created"/>
600+ <subscriber
601+ for="lp.blueprints.interfaces.specification.ISpecification
602+ lazr.lifecycle.interfaces.IObjectModifiedEvent"
603+ handler="canonical.launchpad.subscribers.karma.spec_modified"/>
604+ <subscriber
605+ for="lp.blueprints.interfaces.specification.ISpecification
606+ lazr.lifecycle.interfaces.IObjectModifiedEvent"
607+ handler="lp.blueprints.subscribers.specification_update_lifecycle_status"/>
608+ <subscriber
609+ for="lp.blueprints.interfaces.specification.ISpecification
610+ lazr.lifecycle.interfaces.IObjectModifiedEvent"
611+ handler="lp.blueprints.subscribers.specification_goalstatus"/>
612+ <subscriber
613+ for="lp.blueprints.interfaces.specification.ISpecification
614+ lazr.lifecycle.interfaces.IObjectModifiedEvent"
615+ handler="canonical.launchpad.mailnotification.notify_specification_modified"/>
616+
617+ <!-- SpecificationSet -->
618+
619+ <class class="lp.blueprints.model.specification.SpecificationSet">
620+ <allow interface="lp.blueprints.interfaces.specification.ISpecificationSet"/>
621+ </class>
622+
623+ <securedutility
624+ class="lp.blueprints.model.specification.SpecificationSet"
625+ provides="lp.blueprints.interfaces.specification.ISpecificationSet">
626+ <allow interface="lp.blueprints.interfaces.specification.ISpecificationSet"/>
627+ </securedutility>
628+
629+ <!-- SpecificationDelta -->
630+
631+ <class class="lp.blueprints.adapters.SpecificationDelta">
632+ <allow interface="lp.blueprints.interfaces.specification.ISpecificationDelta"/>
633+ </class>
634+
635+ <!-- SpecificationMessage -->
636+
637+ <class class="lp.blueprints.model.specificationmessage.SpecificationMessage">
638+ <allow interface="lp.blueprints.interfaces.specificationmessage.ISpecificationMessage"/>
639+ </class>
640+
641+ <!-- SpecificationMessageSet -->
642+
643+ <class class="lp.blueprints.model.specificationmessage.SpecificationMessageSet">
644+ <allow interface="lp.blueprints.interfaces.specificationmessage.ISpecificationMessageSet"/>
645+ </class>
646+
647+ <securedutility
648+ class="lp.blueprints.model.specificationmessage.SpecificationMessageSet"
649+ provides="lp.blueprints.interfaces.specificationmessage.ISpecificationMessageSet">
650+ <allow interface="lp.blueprints.interfaces.specificationmessage.ISpecificationMessageSet"/>
651+ </securedutility>
652+
653 </configure>
654
655=== modified file 'lib/lp/blueprints/interfaces/specification.py'
656--- lib/lp/blueprints/interfaces/specification.py 2010-11-02 20:10:56 +0000
657+++ lib/lp/blueprints/interfaces/specification.py 2010-11-04 04:01:31 +0000
658@@ -41,6 +41,7 @@
659 SpecificationDefinitionStatus,
660 SpecificationGoalStatus,
661 SpecificationImplementationStatus,
662+ SpecificationLifecycleStatus,
663 SpecificationPriority,
664 )
665 from lp.blueprints.interfaces.specificationtarget import IHasSpecifications
666@@ -337,6 +338,12 @@
667 'attribute, and also considers informational specs to be '
668 'started when they are approved.')
669
670+ lifecycle_status = Choice(
671+ title=_('Lifecycle Status'),
672+ vocabulary=SpecificationLifecycleStatus,
673+ default=SpecificationLifecycleStatus.NOTSTARTED,
674+ readonly=True)
675+
676 def retarget(product=None, distribution=None):
677 """Retarget the spec to a new product or distribution. One of
678 product or distribution must be None (but not both).
679
680=== modified file 'lib/lp/blueprints/model/specification.py'
681--- lib/lp/blueprints/model/specification.py 2010-11-02 20:10:56 +0000
682+++ lib/lp/blueprints/model/specification.py 2010-11-04 04:01:31 +0000
683@@ -12,7 +12,6 @@
684
685 from lazr.lifecycle.event import (
686 ObjectCreatedEvent,
687- ObjectDeletedEvent,
688 ObjectModifiedEvent,
689 )
690 from lazr.lifecycle.objectdelta import ObjectDelta
691@@ -24,11 +23,7 @@
692 SQLRelatedJoin,
693 StringCol,
694 )
695-from storm.expr import (
696- LeftJoin,
697- )
698 from storm.locals import (
699- ClassAlias,
700 Desc,
701 SQL,
702 )
703@@ -386,6 +381,16 @@
704 (self.definition_status ==
705 SpecificationDefinitionStatus.APPROVED)))
706
707+ @property
708+ def lifecycle_status(self):
709+ """Combine the is_complete and is_started emergent properties."""
710+ if self.is_complete:
711+ return SpecificationLifecycleStatus.COMPLETE
712+ elif self.is_started:
713+ return SpecificationLifecycleStatus.STARTED
714+ else:
715+ return SpecificationLifecycleStatus.NOTSTARTED
716+
717 def updateLifecycleStatus(self, user):
718 """See ISpecification."""
719 newstatus = None
720
721=== modified file 'lib/lp/blueprints/subscribers.py'
722--- lib/lp/blueprints/subscribers.py 2010-11-01 03:43:58 +0000
723+++ lib/lp/blueprints/subscribers.py 2010-11-04 04:01:31 +0000
724@@ -18,3 +18,13 @@
725 return
726 if delta.productseries is not None or delta.distroseries is not None:
727 spec.goalstatus = SpecificationGoalStatus.PROPOSED
728+
729+
730+def specification_update_lifecycle_status(spec, event):
731+ """Mark the specification as started and/or complete if appropriate.
732+
733+ Does nothing if there is no user associated with the event.
734+ """
735+ if event.user is None:
736+ return
737+ spec.updateLifecycleStatus(IPerson(event.user))
738
739=== modified file 'lib/lp/testing/factory.py'
740--- lib/lp/testing/factory.py 2010-11-03 23:44:56 +0000
741+++ lib/lp/testing/factory.py 2010-11-04 04:01:31 +0000
742@@ -1623,7 +1623,8 @@
743
744 def makeSpecification(self, product=None, title=None, distribution=None,
745 name=None, summary=None, owner=None,
746- status=SpecificationDefinitionStatus.NEW):
747+ status=SpecificationDefinitionStatus.NEW,
748+ implementation_status=None):
749 """Create and return a new, arbitrary Blueprint.
750
751 :param product: The product to make the blueprint on. If one is
752@@ -1639,7 +1640,7 @@
753 title = self.getUniqueString('title')
754 if owner is None:
755 owner = self.makePerson()
756- return getUtility(ISpecificationSet).new(
757+ spec = getUtility(ISpecificationSet).new(
758 name=name,
759 title=title,
760 specurl=None,
761@@ -1648,6 +1649,11 @@
762 owner=owner,
763 product=product,
764 distribution=distribution)
765+ if implementation_status is not None:
766+ naked_spec = removeSecurityProxy(spec)
767+ naked_spec.implementation_status = implementation_status
768+ naked_spec.updateLifecycleStatus(owner)
769+ return spec
770
771 def makeQuestion(self, target=None, title=None):
772 """Create and return a new, arbitrary Question.