← Back to team overview

launchpad-reviewers team mailing list archive

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

 

Tim Penhey has proposed merging lp:~thumper/launchpad/blueprint-update-lifecycle-status-with-event into lp:launchpad/devel.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)


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

-- 
https://code.launchpad.net/~thumper/launchpad/blueprint-update-lifecycle-status-with-event/+merge/40048
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~thumper/launchpad/blueprint-update-lifecycle-status-with-event into lp:launchpad/devel.
=== modified file 'lib/lp/blueprints/browser/configure.zcml'
--- lib/lp/blueprints/browser/configure.zcml	2010-10-03 15:30:06 +0000
+++ lib/lp/blueprints/browser/configure.zcml	2010-11-04 04:01:17 +0000
@@ -8,6 +8,21 @@
     xmlns:i18n="http://namespaces.zope.org/i18n";
     xmlns:xmlrpc="http://namespaces.zope.org/xmlrpc";
     i18n_domain="launchpad">
+
+  <adapter
+      name="blueprints"
+      provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
+      for="lp.blueprints.interfaces.specificationtarget.IHasSpecifications"
+      factory="lp.blueprints.browser.specificationtarget.BlueprintsVHostBreadcrumb"
+      permission="zope.Public"/>
+  <adapter
+      name="blueprints"
+      provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
+      for="lp.registry.interfaces.person.IPerson"
+      factory="lp.blueprints.browser.specificationtarget.BlueprintsVHostBreadcrumb"
+      permission="zope.Public"/>
+
+
     <browser:navigation
         module="lp.blueprints.browser.sprint"
         classes="

=== modified file 'lib/lp/blueprints/browser/specification.py'
--- lib/lp/blueprints/browser/specification.py	2010-11-01 03:32:29 +0000
+++ lib/lp/blueprints/browser/specification.py	2010-11-04 04:01:17 +0000
@@ -536,13 +536,14 @@
 
     @action(_('Change'), name='change')
     def change_action(self, action, data):
+        old_status = self.context.lifecycle_status
         self.updateContextFromData(data)
         # We need to ensure that resolution is recorded if the spec is now
         # resolved.
-        newstate = self.context.updateLifecycleStatus(self.user)
-        if newstate is not None:
+        new_status = self.context.lifecycle_status
+        if new_status != old_status:
             self.request.response.addNotification(
-                'blueprint is now considered "%s".' % newstate.title)
+                'Blueprint is now considered "%s".' % new_status.title)
         self.next_url = canonical_url(self.context)
 
 
@@ -735,7 +736,7 @@
 class SpecificationSupersedingView(LaunchpadFormView):
     schema = ISpecification
     field_names = ['superseded_by']
-    label = _('Mark specification superseded')
+    label = _('Mark blueprint superseded')
     custom_widget('superseded_by', SupersededByWidget)
 
     @property
@@ -762,7 +763,7 @@
                 description=_(
                     "The blueprint which supersedes this one. Note "
                     "that selecting a blueprint here and pressing "
-                    "Continue will change the specification status "
+                    "Continue will change the blueprint status "
                     "to Superseded.")),
             render_context=self.render_context)
 
@@ -784,7 +785,7 @@
         newstate = self.context.updateLifecycleStatus(self.user)
         if newstate is not None:
             self.request.response.addNotification(
-                'Specification is now considered "%s".' % newstate.title)
+                'Blueprint is now considered "%s".' % newstate.title)
         self.next_url = canonical_url(self.context)
 
     @property

=== modified file 'lib/lp/blueprints/browser/tests/test_specification.py'
--- lib/lp/blueprints/browser/tests/test_specification.py	2010-08-20 20:31:18 +0000
+++ lib/lp/blueprints/browser/tests/test_specification.py	2010-11-04 04:01:17 +0000
@@ -8,10 +8,13 @@
 from lazr.restful.testing.webservice import FakeRequest
 from zope.publisher.interfaces import NotFound
 
+from canonical.launchpad.webapp.interfaces import BrowserNotificationLevel
 from canonical.launchpad.webapp.servers import StepsToGo
 from canonical.testing.layers import DatabaseFunctionalLayer
 from lp.blueprints.browser import specification
-from lp.testing import TestCaseWithFactory
+from lp.blueprints.enums import SpecificationImplementationStatus
+from lp.testing import login_person, TestCaseWithFactory
+from lp.testing.views import create_initialized_view
 
 
 class LocalFakeRequest(FakeRequest):
@@ -108,6 +111,82 @@
             self.specification.getBranchLink(branch), self.traverse(segments))
 
 
+class TestSpecificationEditStatusView(TestCaseWithFactory):
+    """Test the SpecificationEditStatusView."""
+
+    layer = DatabaseFunctionalLayer
+
+    def test_records_started(self):
+        spec = self.factory.makeSpecification(
+            implementation_status=SpecificationImplementationStatus.NOTSTARTED)
+        login_person(spec.owner)
+        form = {
+            'field.implementation_status': 'STARTED',
+            'field.actions.change': 'Change',
+            }
+        view = create_initialized_view(spec, name='+status', form=form)
+        self.assertEqual(
+            SpecificationImplementationStatus.STARTED, spec.implementation_status)
+        self.assertEqual(spec.owner, spec.starter)
+        [notification] = view.request.notifications
+        self.assertEqual(BrowserNotificationLevel.NOTICE, notification.level)
+        self.assertEqual(
+            'Blueprint is now considered "Started".', notification.message)
+
+    def test_unchanged_lifecycle_has_no_notification(self):
+        spec = self.factory.makeSpecification(
+            implementation_status=SpecificationImplementationStatus.STARTED)
+        login_person(spec.owner)
+        form = {
+            'field.implementation_status': 'SLOW',
+            'field.actions.change': 'Change',
+            }
+        view = create_initialized_view(spec, name='+status', form=form)
+        self.assertEqual(
+            SpecificationImplementationStatus.SLOW, spec.implementation_status)
+        self.assertEqual(0, len(view.request.notifications))
+
+    def test_records_unstarting(self):
+        # If a spec was started, and is changed to not started, a notice is shown.
+        # Also the spec.starter is cleared out.
+        spec = self.factory.makeSpecification(
+            implementation_status=SpecificationImplementationStatus.STARTED)
+        login_person(spec.owner)
+        form = {
+            'field.implementation_status': 'NOTSTARTED',
+            'field.actions.change': 'Change',
+            }
+        view = create_initialized_view(spec, name='+status', form=form)
+        self.assertEqual(
+            SpecificationImplementationStatus.NOTSTARTED,
+            spec.implementation_status)
+        self.assertIs(None, spec.starter)
+        [notification] = view.request.notifications
+        self.assertEqual(BrowserNotificationLevel.NOTICE, notification.level)
+        self.assertEqual(
+            'Blueprint is now considered "Not started".', notification.message)
+
+    def test_records_completion(self):
+        # If a spec is marked as implemented the user is notifiec it is now
+        # complete.
+        spec = self.factory.makeSpecification(
+            implementation_status=SpecificationImplementationStatus.STARTED)
+        login_person(spec.owner)
+        form = {
+            'field.implementation_status': 'IMPLEMENTED',
+            'field.actions.change': 'Change',
+            }
+        view = create_initialized_view(spec, name='+status', form=form)
+        self.assertEqual(
+            SpecificationImplementationStatus.IMPLEMENTED,
+            spec.implementation_status)
+        self.assertEqual(spec.owner, spec.completer)
+        [notification] = view.request.notifications
+        self.assertEqual(BrowserNotificationLevel.NOTICE, notification.level)
+        self.assertEqual(
+            'Blueprint is now considered "Complete".', notification.message)
+
+
 class TestSecificationHelpers(unittest.TestCase):
     """Test specification helper functions."""
 

=== modified file 'lib/lp/blueprints/configure.zcml'
--- lib/lp/blueprints/configure.zcml	2010-08-25 04:12:59 +0000
+++ lib/lp/blueprints/configure.zcml	2010-11-04 04:01:17 +0000
@@ -22,258 +22,220 @@
       name="blueprints" />
 
 
-    <lp:help-folder
-        folder="help" type="lp.blueprints.publisher.BlueprintsLayer" />
-
-    <!-- Sprint -->
-
-    <class
-        class="lp.blueprints.model.sprint.Sprint">
-        <allow
-            interface="lp.blueprints.interfaces.sprint.ISprint"/>
-        <require
-            permission="launchpad.AnyPerson"
-            set_schema="lp.blueprints.interfaces.sprint.ISprint"/>
-    </class>
-    <adapter
-        provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
-        for="lp.blueprints.interfaces.sprint.ISprint"
-        factory="canonical.launchpad.webapp.breadcrumb.TitleBreadcrumb"
-        permission="zope.Public"/>
-    <adapter
-        provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
-        for="lp.blueprints.interfaces.sprint.ISprintSet"
-        factory="lp.blueprints.browser.sprint.SprintSetBreadcrumb"
-        permission="zope.Public"/>
-    <adapter
-        provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
-        for="lp.blueprints.interfaces.specification.ISpecification"
-        factory="canonical.launchpad.webapp.breadcrumb.TitleBreadcrumb"
-        permission="zope.Public"/>
-
-    <!-- This is a view used to export data needed by the sprint scheduler.
-                         As there are no API stability guarantees, the view name starts
-                         with "temp" to discourage people from relying on it. -->
-
-
-    <!-- SprintSet -->
-
-    <class
-        class="lp.blueprints.model.sprint.SprintSet">
-        <allow
-            interface="lp.blueprints.interfaces.sprint.ISprintSet"/>
-    </class>
-    <securedutility
-        class="lp.blueprints.model.sprint.SprintSet"
-        provides="lp.blueprints.interfaces.sprint.ISprintSet">
-        <allow
-            interface="lp.blueprints.interfaces.sprint.ISprintSet"/>
-    </securedutility>
-
-    <!-- SprintSpecification -->
-
-    <class
-        class="lp.blueprints.model.sprintspecification.SprintSpecification">
-        <allow
-            interface="lp.blueprints.interfaces.sprintspecification.ISprintSpecification"/>
-        <require
-            permission="launchpad.Edit"
-            set_attributes="whiteboard"/>
-    </class>
-
-    <!-- SpecificationDependency -->
-
-    <class
-        class="lp.blueprints.model.specificationdependency.SpecificationDependency">
-        <allow
-            interface="lp.blueprints.interfaces.specificationdependency.ISpecificationDependency"/>
-        <require
-            permission="zope.Public"
-            set_schema="lp.blueprints.interfaces.specificationdependency.ISpecificationDependency"/>
-    </class>
-    <adapter
-        for="lp.blueprints.interfaces.specificationdependency.ISpecificationDependency"
-        factory="lp.blueprints.interfaces.specificationdependency.SpecDependencyIsAlsoRemoval"
-        provides="lp.blueprints.interfaces.specificationdependency.ISpecificationDependencyRemoval"/>
-
-    <!-- SpecificationSubscription -->
-
-    <class
-        class="lp.blueprints.model.specificationsubscription.SpecificationSubscription">
-        <allow
-            interface="lp.blueprints.interfaces.specificationsubscription.ISpecificationSubscription"/>
-        <require
-            permission="launchpad.Edit"
-            set_attributes="essential"/>
-    </class>
-    <subscriber
-        for="lp.blueprints.interfaces.specificationsubscription.ISpecificationSubscription                                                lazr.lifecycle.interfaces.IObjectCreatedEvent"
-        handler="canonical.launchpad.mailnotification.notify_specification_subscription_created"/>
-    <subscriber
-        for="lp.blueprints.interfaces.specificationsubscription.ISpecificationSubscription                                                lazr.lifecycle.interfaces.IObjectModifiedEvent"
-        handler="canonical.launchpad.mailnotification.notify_specification_subscription_modified"/>
-
-    <!-- SpecificationFeedback -->
-
-    <class
-        class="lp.blueprints.model.specificationfeedback.SpecificationFeedback">
-        <allow
-            interface="lp.blueprints.interfaces.specificationfeedback.ISpecificationFeedback"/>
-        <require
-            permission="zope.Public"
-            set_schema="lp.blueprints.interfaces.specificationfeedback.ISpecificationFeedback"/>
-    </class>
-
-    <!-- SprintAttendance -->
-
-    <class
-        class="lp.blueprints.model.sprintattendance.SprintAttendance">
-        <allow
-            interface="lp.blueprints.interfaces.sprintattendance.ISprintAttendance"/>
-        <require
-            permission="launchpad.Edit"
-            set_attributes="time_starts time_ends"/>
-    </class>
-
-    <!-- ISpecificationBranch -->
-
-    <class
-        class="lp.blueprints.model.specificationbranch.SpecificationBranch">
-        <allow
-            interface="lp.blueprints.interfaces.specificationbranch.ISpecificationBranch"/>
-        <require
-            permission="launchpad.AnyPerson"
-            set_attributes="summary"/>
-    </class>
-    <subscriber
-        for="lp.blueprints.interfaces.specificationbranch.ISpecificationBranch                                                lazr.lifecycle.interfaces.IObjectCreatedEvent"
-        handler="canonical.launchpad.subscribers.karma.spec_branch_created"/>
-    <facet
-        facet="specifications"/>
-
-    <!-- SpecificationBranchSet -->
-
-    <securedutility
-        class="lp.blueprints.model.specificationbranch.SpecificationBranchSet"
-        provides="lp.blueprints.interfaces.specificationbranch.ISpecificationBranchSet">
-        <allow
-            interface="lp.blueprints.interfaces.specificationbranch.ISpecificationBranchSet"/>
-    </securedutility>
-    <facet
-        facet="specifications">
-
-        <!-- Specification -->
-
-        <class
-            class="lp.blueprints.model.specification.Specification">
-            <allow
-                interface="lp.blueprints.interfaces.specification.ISpecification"/>
-
-            <!-- We allow any authenticated person to update the whiteboard -->
-
-            <require
-                permission="launchpad.AnyPerson"
-                set_attributes="whiteboard"/>
-
-            <!-- NB: goals and goalstatus are not to be set directly, it should
-                                 only be set through the proposeGoal / acceptBy / declineBy
-                                 methods
-                                 -->
-
-            <require
-                permission="launchpad.Edit"
-                set_attributes="name title summary definition_status specurl                                                                                         superseded_by milestone                                                                                         product distribution approver assignee drafter                                                                                         man_days implementation_status"/>
-            <require
-                permission="launchpad.Admin"
-                set_attributes="priority direction_approved"/>
-            <allow
-                attributes="
-                    bugs
+  <lp:help-folder
+      folder="help" type="lp.blueprints.publisher.BlueprintsLayer" />
+
+  <!-- Sprint -->
+
+  <class
+      class="lp.blueprints.model.sprint.Sprint">
+    <allow
+        interface="lp.blueprints.interfaces.sprint.ISprint"/>
+    <require
+        permission="launchpad.AnyPerson"
+        set_schema="lp.blueprints.interfaces.sprint.ISprint"/>
+  </class>
+  <adapter
+      provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
+      for="lp.blueprints.interfaces.sprint.ISprint"
+      factory="canonical.launchpad.webapp.breadcrumb.TitleBreadcrumb"
+      permission="zope.Public"/>
+  <adapter
+      provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
+      for="lp.blueprints.interfaces.sprint.ISprintSet"
+      factory="lp.blueprints.browser.sprint.SprintSetBreadcrumb"
+      permission="zope.Public"/>
+  <adapter
+      provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
+      for="lp.blueprints.interfaces.specification.ISpecification"
+      factory="canonical.launchpad.webapp.breadcrumb.TitleBreadcrumb"
+      permission="zope.Public"/>
+
+  <!-- This is a view used to export data needed by the sprint scheduler.
+       As there are no API stability guarantees, the view name starts
+       with "temp" to discourage people from relying on it. -->
+
+  <!-- SprintSet -->
+
+  <class
+      class="lp.blueprints.model.sprint.SprintSet">
+    <allow interface="lp.blueprints.interfaces.sprint.ISprintSet"/>
+  </class>
+  <securedutility
+      class="lp.blueprints.model.sprint.SprintSet"
+      provides="lp.blueprints.interfaces.sprint.ISprintSet">
+    <allow interface="lp.blueprints.interfaces.sprint.ISprintSet"/>
+  </securedutility>
+
+  <!-- SprintSpecification -->
+
+  <class
+      class="lp.blueprints.model.sprintspecification.SprintSpecification">
+    <allow
+        interface="lp.blueprints.interfaces.sprintspecification.ISprintSpecification"/>
+    <require
+        permission="launchpad.Edit"
+        set_attributes="whiteboard"/>
+  </class>
+
+  <!-- SpecificationDependency -->
+
+  <class class="lp.blueprints.model.specificationdependency.SpecificationDependency">
+    <allow interface="lp.blueprints.interfaces.specificationdependency.ISpecificationDependency"/>
+    <require
+        permission="zope.Public"
+        set_schema="lp.blueprints.interfaces.specificationdependency.ISpecificationDependency"/>
+  </class>
+  <adapter
+      for="lp.blueprints.interfaces.specificationdependency.ISpecificationDependency"
+      factory="lp.blueprints.interfaces.specificationdependency.SpecDependencyIsAlsoRemoval"
+      provides="lp.blueprints.interfaces.specificationdependency.ISpecificationDependencyRemoval"/>
+
+  <!-- SpecificationSubscription -->
+
+  <class class="lp.blueprints.model.specificationsubscription.SpecificationSubscription">
+    <allow
+        interface="lp.blueprints.interfaces.specificationsubscription.ISpecificationSubscription"/>
+    <require
+        permission="launchpad.Edit"
+        set_attributes="essential"/>
+  </class>
+  <subscriber
+      for="lp.blueprints.interfaces.specificationsubscription.ISpecificationSubscription
+           lazr.lifecycle.interfaces.IObjectCreatedEvent"
+      handler="canonical.launchpad.mailnotification.notify_specification_subscription_created"/>
+  <subscriber
+      for="lp.blueprints.interfaces.specificationsubscription.ISpecificationSubscription
+           lazr.lifecycle.interfaces.IObjectModifiedEvent"
+      handler="canonical.launchpad.mailnotification.notify_specification_subscription_modified"/>
+
+  <!-- SpecificationFeedback -->
+
+  <class class="lp.blueprints.model.specificationfeedback.SpecificationFeedback">
+    <allow interface="lp.blueprints.interfaces.specificationfeedback.ISpecificationFeedback"/>
+    <require
+        permission="zope.Public"
+        set_schema="lp.blueprints.interfaces.specificationfeedback.ISpecificationFeedback"/>
+  </class>
+
+  <!-- SprintAttendance -->
+
+  <class class="lp.blueprints.model.sprintattendance.SprintAttendance">
+    <allow interface="lp.blueprints.interfaces.sprintattendance.ISprintAttendance"/>
+    <require
+        permission="launchpad.Edit"
+        set_attributes="time_starts time_ends"/>
+  </class>
+
+  <!-- ISpecificationBranch -->
+
+  <class class="lp.blueprints.model.specificationbranch.SpecificationBranch">
+    <allow interface="lp.blueprints.interfaces.specificationbranch.ISpecificationBranch"/>
+    <require
+        permission="launchpad.AnyPerson"
+        set_attributes="summary"/>
+  </class>
+  <subscriber
+      for="lp.blueprints.interfaces.specificationbranch.ISpecificationBranch
+           lazr.lifecycle.interfaces.IObjectCreatedEvent"
+      handler="canonical.launchpad.subscribers.karma.spec_branch_created"/>
+
+  <!-- SpecificationBranchSet -->
+
+  <securedutility
+      class="lp.blueprints.model.specificationbranch.SpecificationBranchSet"
+      provides="lp.blueprints.interfaces.specificationbranch.ISpecificationBranchSet">
+    <allow interface="lp.blueprints.interfaces.specificationbranch.ISpecificationBranchSet"/>
+  </securedutility>
+
+  <!-- Specification -->
+
+  <class class="lp.blueprints.model.specification.Specification">
+    <allow interface="lp.blueprints.interfaces.specification.ISpecification"/>
+    <!-- We allow any authenticated person to update the whiteboard -->
+    <require
+        permission="launchpad.AnyPerson"
+        set_attributes="whiteboard"/>
+    <!-- NB: goals and goalstatus are not to be set directly, it should
+         only be set through the proposeGoal / acceptBy / declineBy
+         methods -->
+    <require
+        permission="launchpad.Edit"
+        set_attributes="name title summary definition_status specurl
+                        superseded_by milestone product distribution
+                        approver assignee drafter man_days
+                        implementation_status"/>
+    <require
+        permission="launchpad.Admin"
+        set_attributes="priority direction_approved"/>
+    <allow
+        attributes="bugs
                     bug_links"/>
-            <require
-                permission="launchpad.AnyPerson"
-                attributes="
-                    linkBug
+    <require
+        permission="launchpad.AnyPerson"
+        attributes="linkBug
                     unlinkBug"/>
-        </class>
-        <class
-            class="lp.blueprints.model.specificationbug.SpecificationBug">
-            <allow
-                interface="lp.blueprints.interfaces.specificationbug.ISpecificationBug"/>
-        </class>
-        <subscriber
-            for="lp.blueprints.interfaces.specification.ISpecification                                        lazr.lifecycle.interfaces.IObjectCreatedEvent"
-            handler="canonical.launchpad.subscribers.karma.spec_created"/>
-        <subscriber
-            for="lp.blueprints.interfaces.specification.ISpecification                                        lazr.lifecycle.interfaces.IObjectModifiedEvent"
-            handler="canonical.launchpad.subscribers.karma.spec_modified"/>
-
-        <!-- these pages are simple enough they don't need browser code -->
-
-
-        <!-- these pages require special browser code -->
-
-
-        <!-- SpecificationSet -->
-
-        <class
-            class="lp.blueprints.model.specification.SpecificationSet">
-            <allow
-                interface="lp.blueprints.interfaces.specification.ISpecificationSet"/>
-        </class>
-        <securedutility
-            class="lp.blueprints.model.specification.SpecificationSet"
-            provides="lp.blueprints.interfaces.specification.ISpecificationSet">
-            <allow
-                interface="lp.blueprints.interfaces.specification.ISpecificationSet"/>
-        </securedutility>
-
-        <!-- SpecificationDelta -->
-
-        <class
-            class="lp.blueprints.adapters.SpecificationDelta">
-            <allow
-                interface="lp.blueprints.interfaces.specification.ISpecificationDelta"/>
-        </class>
-
-        <!-- SpecificationMessage -->
-
-        <class
-            class="lp.blueprints.model.specificationmessage.SpecificationMessage">
-            <allow
-                interface="lp.blueprints.interfaces.specificationmessage.ISpecificationMessage"/>
-        </class>
-
-        <!-- SpecificationMessageSet -->
-
-        <class
-            class="lp.blueprints.model.specificationmessage.SpecificationMessageSet">
-            <allow
-                interface="lp.blueprints.interfaces.specificationmessage.ISpecificationMessageSet"/>
-        </class>
-        <securedutility
-            class="lp.blueprints.model.specificationmessage.SpecificationMessageSet"
-            provides="lp.blueprints.interfaces.specificationmessage.ISpecificationMessageSet">
-            <allow
-                interface="lp.blueprints.interfaces.specificationmessage.ISpecificationMessageSet"/>
-        </securedutility>
-    </facet>
-    <subscriber
-        for="lp.blueprints.interfaces.specification.ISpecification                                 lazr.lifecycle.interfaces.IObjectModifiedEvent"
-        handler="lp.blueprints.subscribers.specification_goalstatus"/>
-    <subscriber
-        for="lp.blueprints.interfaces.specification.ISpecification                                 lazr.lifecycle.interfaces.IObjectModifiedEvent"
-        handler="canonical.launchpad.mailnotification.notify_specification_modified"/>
-    <adapter
-        name="blueprints"
-        provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
-        for="lp.blueprints.interfaces.specificationtarget.IHasSpecifications"
-        factory="lp.blueprints.browser.specificationtarget.BlueprintsVHostBreadcrumb"
-        permission="zope.Public"/>
-    <adapter
-        name="blueprints"
-        provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
-        for="lp.registry.interfaces.person.IPerson"
-        factory="lp.blueprints.browser.specificationtarget.BlueprintsVHostBreadcrumb"
-        permission="zope.Public"/>
+  </class>
+
+  <class class="lp.blueprints.model.specificationbug.SpecificationBug">
+    <allow interface="lp.blueprints.interfaces.specificationbug.ISpecificationBug"/>
+  </class>
+
+  <subscriber
+      for="lp.blueprints.interfaces.specification.ISpecification
+           lazr.lifecycle.interfaces.IObjectCreatedEvent"
+      handler="canonical.launchpad.subscribers.karma.spec_created"/>
+  <subscriber
+      for="lp.blueprints.interfaces.specification.ISpecification
+           lazr.lifecycle.interfaces.IObjectModifiedEvent"
+      handler="canonical.launchpad.subscribers.karma.spec_modified"/>
+  <subscriber
+      for="lp.blueprints.interfaces.specification.ISpecification
+           lazr.lifecycle.interfaces.IObjectModifiedEvent"
+      handler="lp.blueprints.subscribers.specification_update_lifecycle_status"/>
+  <subscriber
+      for="lp.blueprints.interfaces.specification.ISpecification
+           lazr.lifecycle.interfaces.IObjectModifiedEvent"
+      handler="lp.blueprints.subscribers.specification_goalstatus"/>
+  <subscriber
+      for="lp.blueprints.interfaces.specification.ISpecification
+           lazr.lifecycle.interfaces.IObjectModifiedEvent"
+      handler="canonical.launchpad.mailnotification.notify_specification_modified"/>
+
+  <!-- SpecificationSet -->
+
+  <class class="lp.blueprints.model.specification.SpecificationSet">
+    <allow interface="lp.blueprints.interfaces.specification.ISpecificationSet"/>
+  </class>
+
+  <securedutility
+      class="lp.blueprints.model.specification.SpecificationSet"
+      provides="lp.blueprints.interfaces.specification.ISpecificationSet">
+    <allow interface="lp.blueprints.interfaces.specification.ISpecificationSet"/>
+  </securedutility>
+
+  <!-- SpecificationDelta -->
+
+  <class class="lp.blueprints.adapters.SpecificationDelta">
+    <allow interface="lp.blueprints.interfaces.specification.ISpecificationDelta"/>
+  </class>
+
+  <!-- SpecificationMessage -->
+
+  <class class="lp.blueprints.model.specificationmessage.SpecificationMessage">
+    <allow interface="lp.blueprints.interfaces.specificationmessage.ISpecificationMessage"/>
+  </class>
+
+  <!-- SpecificationMessageSet -->
+
+  <class class="lp.blueprints.model.specificationmessage.SpecificationMessageSet">
+    <allow interface="lp.blueprints.interfaces.specificationmessage.ISpecificationMessageSet"/>
+  </class>
+
+  <securedutility
+      class="lp.blueprints.model.specificationmessage.SpecificationMessageSet"
+      provides="lp.blueprints.interfaces.specificationmessage.ISpecificationMessageSet">
+    <allow interface="lp.blueprints.interfaces.specificationmessage.ISpecificationMessageSet"/>
+  </securedutility>
+
 </configure>

=== modified file 'lib/lp/blueprints/interfaces/specification.py'
--- lib/lp/blueprints/interfaces/specification.py	2010-11-02 20:10:56 +0000
+++ lib/lp/blueprints/interfaces/specification.py	2010-11-04 04:01:17 +0000
@@ -41,6 +41,7 @@
     SpecificationDefinitionStatus,
     SpecificationGoalStatus,
     SpecificationImplementationStatus,
+    SpecificationLifecycleStatus,
     SpecificationPriority,
     )
 from lp.blueprints.interfaces.specificationtarget import IHasSpecifications
@@ -337,6 +338,12 @@
         'attribute, and also considers informational specs to be '
         'started when they are approved.')
 
+    lifecycle_status = Choice(
+        title=_('Lifecycle Status'),
+        vocabulary=SpecificationLifecycleStatus,
+        default=SpecificationLifecycleStatus.NOTSTARTED,
+        readonly=True)
+
     def retarget(product=None, distribution=None):
         """Retarget the spec to a new product or distribution. One of
         product or distribution must be None (but not both).

=== modified file 'lib/lp/blueprints/model/specification.py'
--- lib/lp/blueprints/model/specification.py	2010-11-02 20:10:56 +0000
+++ lib/lp/blueprints/model/specification.py	2010-11-04 04:01:17 +0000
@@ -12,7 +12,6 @@
 
 from lazr.lifecycle.event import (
     ObjectCreatedEvent,
-    ObjectDeletedEvent,
     ObjectModifiedEvent,
     )
 from lazr.lifecycle.objectdelta import ObjectDelta
@@ -24,11 +23,7 @@
     SQLRelatedJoin,
     StringCol,
     )
-from storm.expr import (
-    LeftJoin,
-    )
 from storm.locals import (
-    ClassAlias,
     Desc,
     SQL,
     )
@@ -386,6 +381,16 @@
                     (self.definition_status ==
                      SpecificationDefinitionStatus.APPROVED)))
 
+    @property
+    def lifecycle_status(self):
+        """Combine the is_complete and is_started emergent properties."""
+        if self.is_complete:
+            return SpecificationLifecycleStatus.COMPLETE
+        elif self.is_started:
+            return SpecificationLifecycleStatus.STARTED
+        else:
+            return SpecificationLifecycleStatus.NOTSTARTED
+
     def updateLifecycleStatus(self, user):
         """See ISpecification."""
         newstatus = None

=== modified file 'lib/lp/blueprints/subscribers.py'
--- lib/lp/blueprints/subscribers.py	2010-11-01 03:43:58 +0000
+++ lib/lp/blueprints/subscribers.py	2010-11-04 04:01:17 +0000
@@ -18,3 +18,13 @@
         return
     if delta.productseries is not None or delta.distroseries is not None:
         spec.goalstatus = SpecificationGoalStatus.PROPOSED
+
+
+def specification_update_lifecycle_status(spec, event):
+    """Mark the specification as started and/or complete if appropriate.
+
+    Does nothing if there is no user associated with the event.
+    """
+    if event.user is None:
+        return
+    spec.updateLifecycleStatus(IPerson(event.user))

=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py	2010-11-03 23:44:56 +0000
+++ lib/lp/testing/factory.py	2010-11-04 04:01:17 +0000
@@ -1623,7 +1623,8 @@
 
     def makeSpecification(self, product=None, title=None, distribution=None,
                           name=None, summary=None, owner=None,
-                          status=SpecificationDefinitionStatus.NEW):
+                          status=SpecificationDefinitionStatus.NEW,
+                          implementation_status=None):
         """Create and return a new, arbitrary Blueprint.
 
         :param product: The product to make the blueprint on.  If one is
@@ -1639,7 +1640,7 @@
             title = self.getUniqueString('title')
         if owner is None:
             owner = self.makePerson()
-        return getUtility(ISpecificationSet).new(
+        spec = getUtility(ISpecificationSet).new(
             name=name,
             title=title,
             specurl=None,
@@ -1648,6 +1649,11 @@
             owner=owner,
             product=product,
             distribution=distribution)
+        if implementation_status is not None:
+            naked_spec = removeSecurityProxy(spec)
+            naked_spec.implementation_status = implementation_status
+            naked_spec.updateLifecycleStatus(owner)
+        return spec
 
     def makeQuestion(self, target=None, title=None):
         """Create and return a new, arbitrary Question.


Follow ups