← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~linaro-infrastructure/launchpad/workitems-model-classes into lp:launchpad/db-devel

 

Mattias Backman has proposed merging lp:~linaro-infrastructure/launchpad/workitems-model-classes into lp:launchpad/db-devel.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~linaro-infrastructure/launchpad/workitems-model-classes/+merge/92174

Hi,

This branch adds models for blueprint Work Items.

The topic has been discussed here https://lists.launchpad.net/launchpad-dev/msg08782.html . The gist of it is that we would like Work Items to be proper Launchpad objects to make status.ubuntu.org and status.linaro.org development easier and enable new status pages for teams and individuals.

Thanks,

Mattias
-- 
https://code.launchpad.net/~linaro-infrastructure/launchpad/workitems-model-classes/+merge/92174
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~linaro-infrastructure/launchpad/workitems-model-classes into lp:launchpad/db-devel.
=== modified file 'lib/lp/blueprints/enums.py'
--- lib/lp/blueprints/enums.py	2011-03-02 04:25:06 +0000
+++ lib/lp/blueprints/enums.py	2012-02-09 00:55:22 +0000
@@ -14,6 +14,7 @@
     'SpecificationPriority',
     'SpecificationSort',
     'SprintSpecificationStatus',
+    'SpecificationWorkItemStatus',
     ]
 
 
@@ -511,3 +512,31 @@
         organisers. It has not yet been accepted or declined for the
         agenda.
         """)
+
+
+class SpecificationWorkItemStatus(DBEnumeratedType):
+    TODO = DBItem(0, """
+        TODO
+
+        A work item that's not done yet.
+        """)
+    DONE = DBItem(1, """
+        DONE
+
+        A work item that's done.
+        """)
+    POSTPONED = DBItem(2, """
+        POSTPONED
+
+        A work item that has been postponed.
+        """)
+    INPROGRESS = DBItem(3, """
+        INPROGRESS
+
+        A work item that is inprogress.
+        """)
+    BLOCKED = DBItem(4, """
+        BLOCKED
+
+        A work item that is blocked.
+        """)

=== modified file 'lib/lp/blueprints/interfaces/specification.py'
--- lib/lp/blueprints/interfaces/specification.py	2012-01-01 02:58:52 +0000
+++ lib/lp/blueprints/interfaces/specification.py	2012-02-09 00:55:22 +0000
@@ -54,6 +54,7 @@
     SpecificationImplementationStatus,
     SpecificationLifecycleStatus,
     SpecificationPriority,
+    SpecificationWorkItemStatus,
     )
 from lp.blueprints.interfaces.specificationsubscription import (
     ISpecificationSubscription,
@@ -569,6 +570,10 @@
     def setImplementationStatus(implementation_status, user):
         """Mutator for implementation_status that calls updateLifeCycle."""
 
+    def newWorkItem(title, status=SpecificationWorkItemStatus.TODO,
+                    assignee=None, milestone=None, sequence=None):
+        """Create a new SpecificationWorkItem."""
+
     def setTarget(target):
         """Set this specification's target.
 

=== added file 'lib/lp/blueprints/interfaces/specificationworkitem.py'
--- lib/lp/blueprints/interfaces/specificationworkitem.py	1970-01-01 00:00:00 +0000
+++ lib/lp/blueprints/interfaces/specificationworkitem.py	2012-02-09 00:55:22 +0000
@@ -0,0 +1,76 @@
+# Copyright 2012 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""SpecificationWorkItem interfaces."""
+
+__metaclass__ = type
+
+__all__ = [
+    'ISpecificationWorkItem',
+    ]
+
+
+from zope.interface import Interface
+from zope.schema import (
+    Bool,
+    Choice,
+    Datetime,
+    Int,
+    )
+
+from lp import _
+from lp.blueprints.enums import SpecificationWorkItemStatus
+from lp.services.fields import (
+    PublicPersonChoice,
+    Title,
+    )
+
+
+class ISpecificationWorkItem(Interface):
+    """SpecificationWorkItem's public attributes and methods."""
+
+    id = Int(title=_("Database ID"), required=True, readonly=True)
+
+    title = Title(
+        title=_('Title'), required=True, readonly=False,
+        description=_("Work item title."))
+
+    assignee = PublicPersonChoice(
+        title=_('Assignee'), required=False, readonly=False,
+        description=_(
+            "The person responsible for implementing the work item."),
+        vocabulary='ValidPersonOrTeam')
+
+    date_created = Datetime(
+        title=_('Date Created'), required=True, readonly=True)
+
+    milestone = Choice(
+        title=_('Milestone'), required=False, readonly=False,
+        vocabulary='Milestone',
+        description=_(
+            "The milestone to which this work item is targetted. If this "
+            "is not set, then the target is the specification's "
+            "milestone."))
+
+    status = Choice(
+        title=_("Work Item Status"), required=True, readonly=False,
+        default=SpecificationWorkItemStatus.TODO,
+        vocabulary=SpecificationWorkItemStatus,
+        description=_(
+            "The state of progress being made on the actual "
+            "implementation of this work item."))
+
+    specification = Choice(
+        title=_('The specification that the work item is linked to.'),
+        required=True, readonly=True, vocabulary='Specification')
+
+    deleted = Bool(
+        title=_('Is this work item deleted?'),
+        required=True, readonly=False, default=False,
+        description=_("Marks the work item as deleted."))
+
+    sequence = Int(
+        title=_("Work Item Sequence."),
+        required=True, description=_(
+            "The sequence in which the work items are to be displayed in the "
+            "UI."))

=== modified file 'lib/lp/blueprints/model/specification.py'
--- lib/lp/blueprints/model/specification.py	2011-12-30 06:14:56 +0000
+++ lib/lp/blueprints/model/specification.py	2012-02-09 00:55:22 +0000
@@ -45,6 +45,7 @@
     SpecificationLifecycleStatus,
     SpecificationPriority,
     SpecificationSort,
+    SpecificationWorkItemStatus,
     )
 from lp.blueprints.errors import TargetAlreadyHasSpecification
 from lp.blueprints.interfaces.specification import (
@@ -60,6 +61,7 @@
 from lp.blueprints.model.specificationsubscription import (
     SpecificationSubscription,
     )
+from lp.blueprints.model.specificationworkitem import SpecificationWorkItem
 from lp.bugs.interfaces.buglink import IBugLinkTarget
 from lp.bugs.interfaces.bugtask import (
     BugTaskSearchParams,
@@ -228,6 +230,14 @@
             return self.product
         return self.distribution
 
+    def newWorkItem(self, title, sequence,
+                    status=SpecificationWorkItemStatus.TODO, assignee=None,
+                    milestone=None):
+        """See ISpecification."""
+        return SpecificationWorkItem(
+            title=title, status=status, specification=self, assignee=assignee,
+            milestone=milestone, sequence=sequence)
+
     def setTarget(self, target):
         """See ISpecification."""
         if IProduct.providedBy(target):

=== added file 'lib/lp/blueprints/model/specificationworkitem.py'
--- lib/lp/blueprints/model/specificationworkitem.py	1970-01-01 00:00:00 +0000
+++ lib/lp/blueprints/model/specificationworkitem.py	2012-02-09 00:55:22 +0000
@@ -0,0 +1,50 @@
+# Copyright 2012 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+__metaclass__ = type
+__all__ = [
+    'SpecificationWorkItem',
+    ]
+
+from zope.interface import implements
+
+from sqlobject import (
+    BoolCol,
+    ForeignKey,
+    IntCol,
+    StringCol,
+    )
+
+from lp.services.database.constants import DEFAULT
+from lp.services.database.datetimecol import UtcDateTimeCol
+from lp.services.database.enumcol import EnumCol
+from lp.services.database.sqlbase import SQLBase
+
+from lp.blueprints.enums import SpecificationWorkItemStatus
+from lp.blueprints.interfaces.specificationworkitem import (
+    ISpecificationWorkItem,
+    )
+from lp.registry.interfaces.person import validate_public_person
+
+
+class SpecificationWorkItem(SQLBase):
+    implements(ISpecificationWorkItem)
+
+    title = StringCol(notNull=True)
+    specification = ForeignKey(foreignKey='Specification', notNull=True)
+    assignee = ForeignKey(
+        notNull=False, foreignKey='Person',
+        storm_validator=validate_public_person, default=None)
+    milestone = ForeignKey(
+        foreignKey='Milestone', notNull=False, default=None)
+    status = EnumCol(
+        schema=SpecificationWorkItemStatus,
+        notNull=True, default=SpecificationWorkItemStatus.TODO)
+    date_created = UtcDateTimeCol(notNull=True, default=DEFAULT)
+    sequence = IntCol(notNull=True)
+    deleted = BoolCol(notNull=True, default=False)
+
+    def __repr__(self):
+        title = self.title.encode('ASCII', 'backslashreplace')
+        return '<SpecificationWorkItem [%s] %s: %s of %s>' % (
+            self.assignee, title, self.status, self.specification)

=== modified file 'lib/lp/blueprints/model/tests/test_specification.py'
--- lib/lp/blueprints/model/tests/test_specification.py	2012-01-01 02:58:52 +0000
+++ lib/lp/blueprints/model/tests/test_specification.py	2012-02-09 00:55:22 +0000
@@ -9,9 +9,16 @@
 
 from lp.app.validators import LaunchpadValidationError
 from lp.blueprints.interfaces.specification import ISpecification
+from lp.blueprints.model.specificationworkitem import SpecificationWorkItem
 from lp.services.webapp import canonical_url
-from lp.testing import TestCaseWithFactory
+from lp.testing import (
+    TestCaseWithFactory,
+    ANONYMOUS,
+    login,
+    login_person,
+    )
 from lp.testing.layers import DatabaseFunctionalLayer
+from zope.security.interfaces import Unauthorized
 
 
 class TestSpecificationDependencies(TestCaseWithFactory):
@@ -132,3 +139,19 @@
         self.assertEqual(
             '%s is already registered by <a href="%s">%s</a>.'
             % (u'http://ubuntu.com/foo', url, cleaned_title), str(e))
+
+
+class TestSpecificationWorkItems(TestCaseWithFactory):
+
+    layer = DatabaseFunctionalLayer
+
+    def test_anonymous_newworkitem_not_allowed(self):
+        spec = self.factory.makeSpecification()
+        login(ANONYMOUS)
+        self.assertRaises(Unauthorized, getattr, spec, 'newWorkItem')
+
+    def test_owner_newworkitem_allowed(self):
+        spec = self.factory.makeSpecification()
+        login_person(spec.owner)
+        work_item = spec.newWorkItem(title='new-work-item', sequence=0)
+        self.assertIsInstance(work_item, SpecificationWorkItem)

=== modified file 'lib/lp/blueprints/tests/test_implements.py'
--- lib/lp/blueprints/tests/test_implements.py	2012-01-01 02:58:52 +0000
+++ lib/lp/blueprints/tests/test_implements.py	2012-02-09 00:55:22 +0000
@@ -9,6 +9,9 @@
     IHasSpecifications,
     ISpecificationTarget,
     )
+from lp.blueprints.interfaces.specificationworkitem import (
+    ISpecificationWorkItem,
+    )
 from lp.testing import TestCaseWithFactory
 from lp.testing.layers import DatabaseFunctionalLayer
 
@@ -65,3 +68,13 @@
     def test_distroseries_implements_ISpecificationTarget(self):
         distroseries = self.factory.makeDistroSeries()
         self.assertProvides(distroseries, ISpecificationTarget)
+
+
+class ImplementsISpecificationWorkItemTests(TestCaseWithFactory):
+    """Test that various objects implement ISpecificationWorkItem."""
+
+    layer = DatabaseFunctionalLayer
+
+    def test_specificationworkitem_implements_ISpecificationTarget(self):
+        specificationworkitem = self.factory.makeSpecificationWorkItem()
+        self.assertProvides(specificationworkitem, ISpecificationWorkItem)

=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py	2012-02-03 22:03:43 +0000
+++ lib/lp/testing/factory.py	2012-02-09 00:55:22 +0000
@@ -1,3 +1,4 @@
+
 # -*- coding: utf-8 -*-
 # NOTE: The first line above must stay first; do not move the copyright
 # notice to the top.  See http://www.python.org/dev/peps/pep-0263/.
@@ -73,6 +74,7 @@
     NewSpecificationDefinitionStatus,
     SpecificationDefinitionStatus,
     SpecificationPriority,
+    SpecificationWorkItemStatus,
     )
 from lp.blueprints.interfaces.specification import ISpecificationSet
 from lp.blueprints.interfaces.sprint import ISprintSet
@@ -2105,6 +2107,22 @@
 
     makeBlueprint = makeSpecification
 
+    def makeSpecificationWorkItem(self, title=None, specification=None,
+                                  assignee=None, milestone=None, deleted=False,
+                                  status=SpecificationWorkItemStatus.TODO,
+                                  sequence=None):
+        if title is None:
+            title = self.getUniqueString('title')
+        if specification is None:
+            specification = self.makeSpecification()
+        if sequence is None:
+            sequence = self.getUniqueInteger()
+        work_item = removeSecurityProxy(specification).newWorkItem(
+            title=title, sequence=sequence, status=status, assignee=assignee,
+            milestone=milestone)
+        work_item.deleted = deleted
+        return work_item
+
     def makeQuestion(self, target=None, title=None,
                      owner=None, description=None, language=None):
         """Create and return a new, arbitrary Question.