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

Proposed by Mattias Backman
Status: Merged
Approved by: j.c.sackett
Approved revision: no longer in the source branch.
Merged at revision: 11383
Proposed branch: lp:~linaro-infrastructure/launchpad/workitems-model-classes
Merge into: lp:launchpad/db-devel
Diff against target: 391 lines (+262/-1)
9 files modified
lib/lp/blueprints/configure.zcml (+6/-0)
lib/lp/blueprints/enums.py (+29/-0)
lib/lp/blueprints/interfaces/specification.py (+6/-0)
lib/lp/blueprints/interfaces/specificationworkitem.py (+76/-0)
lib/lp/blueprints/model/specification.py (+10/-0)
lib/lp/blueprints/model/specificationworkitem.py (+62/-0)
lib/lp/blueprints/model/tests/test_specification.py (+43/-1)
lib/lp/blueprints/tests/test_implements.py (+13/-0)
lib/lp/testing/factory.py (+17/-0)
To merge this branch: bzr merge lp:~linaro-infrastructure/launchpad/workitems-model-classes
Reviewer Review Type Date Requested Status
j.c.sackett (community) Approve
Richard Harding (community) code* Approve
Review via email: mp+92174@code.launchpad.net

Commit message

[r=jcsackett,rharding][bug=933592] Work Items models.

Description of the change

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

To post a comment you must log in.
Revision history for this message
Richard Harding (rharding) wrote :

Mattias, thanks. We should be using storm vs sqlobject for moving forward. I think you can switch from sqlbase to stormbase and update the columns without too much change.

Aside from that everything looks good.

review: Needs Fixing (code*)
Revision history for this message
Mattias Backman (mabac) wrote :

On Thu, Feb 9, 2012 at 7:17 AM, Richard Harding
<email address hidden> wrote:
> Review: Needs Fixing code*
>
> Mattias, thanks. We should be using storm vs sqlobject for moving forward. I think you can switch from sqlbase to stormbase and update the columns without too much change.

Thanks. I'm not entirely sure what needs changing. I have pushed
another revision with my best guess.

>
> Aside from that everything looks good.
> --
> https://code.launchpad.net/~linaro-infrastructure/launchpad/workitems-model-classes/+merge/92174
> Your team Linaro Infrastructure is subscribed to branch lp:~linaro-infrastructure/launchpad/workitems-model-classes.

Revision history for this message
Richard Harding (rharding) wrote :

Mattias, so with the change over to using Storm, the model definition needs to be updated to use the Storm column definitions. You can see an example in the sprintattendance.py file there.

    from storm.locals import (
    Bool,
    Int,
    Reference,
    Store,
    )

There's a sample model there that uses StormBase and defines the models and such.

review: Needs Fixing (code*)
Revision history for this message
Mattias Backman (mabac) wrote :

We need to fix my typo and add some tests. Guilherme will pick that up tomorrow.

Revision history for this message
Guilherme Salgado (salgado) wrote :

Okay, this branch should be good to go now. We were missing a zcml declaration to allow ISpecificationWorkItem on SpecificationWorkItem so I added that and another test to ensure it works.
(I've done this myself because Mattias is not around today)

Revision history for this message
Richard Harding (rharding) wrote :

Thanks, this looks much better using the newer storm vs sqlobject code. I appreciate the effort to rework it.

review: Approve (code*)
Revision history for this message
j.c.sackett (jcsackett) wrote :

I have only one thing to add to Rick's review, in the diff below. As soon as that change is made we can send this out to land.

> === 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-10 17:51:24 +0000
> @@ -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.
> + """)

For enums like this, the first line of the comment describing shouldn't
be in caps. If you look at other enums, the following diff would be better:

=== 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-10 17:51:24 +0000
@@ -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, """
+ In progress
+
+ A work item that is inprogress.
+ """)
+ BLOCKED = DBItem(4, """
+ Blocked
+
+ A work item that is blocked.
+ """)

review: Needs Fixing
Revision history for this message
j.c.sackett (jcsackett) wrote :

Because this branch has taken awhile to get review, I've made making that change as easy as possible. If you just do

`bzr merge lp:~jcsackett/launchpad/quick-fix-for-linaro`

you'll get that change made.

Revision history for this message
Mattias Backman (mabac) wrote :

On Tue, Feb 14, 2012 at 6:57 PM, j.c.sackett <email address hidden> wrote:
> Because this branch has taken awhile to get review, I've made making that change as easy as possible. If you just do
>
> `bzr merge lp:~jcsackett/launchpad/quick-fix-for-linaro`
>
> you'll get that change made.

Incredibly nice of you to offer that, I didn't get anything changed
when merging though so I changed it manually, I think it matches your
change. Please have a look at the new revision.

> --
> https://code.launchpad.net/~linaro-infrastructure/launchpad/workitems-model-classes/+merge/92174
> Your team Linaro Infrastructure is subscribed to branch lp:~linaro-infrastructure/launchpad/workitems-model-classes.

Revision history for this message
j.c.sackett (jcsackett) wrote :

Alas, I did a bad push. Thus, my help was less than helpful. :-P

Thanks for making those changes.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/blueprints/configure.zcml'
2--- lib/lp/blueprints/configure.zcml 2011-12-24 17:49:30 +0000
3+++ lib/lp/blueprints/configure.zcml 2012-02-15 21:39:22 +0000
4@@ -78,6 +78,12 @@
5 set_attributes="whiteboard"/>
6 </class>
7
8+ <!-- SpecificationWorkItem -->
9+ <class class="lp.blueprints.model.specificationworkitem.SpecificationWorkItem">
10+ <allow
11+ interface="lp.blueprints.interfaces.specificationworkitem.ISpecificationWorkItem"/>
12+ </class>
13+
14 <!-- SpecificationDependency -->
15
16 <class class="lp.blueprints.model.specificationdependency.SpecificationDependency">
17
18=== modified file 'lib/lp/blueprints/enums.py'
19--- lib/lp/blueprints/enums.py 2011-03-02 04:25:06 +0000
20+++ lib/lp/blueprints/enums.py 2012-02-15 21:39:22 +0000
21@@ -14,6 +14,7 @@
22 'SpecificationPriority',
23 'SpecificationSort',
24 'SprintSpecificationStatus',
25+ 'SpecificationWorkItemStatus',
26 ]
27
28
29@@ -511,3 +512,31 @@
30 organisers. It has not yet been accepted or declined for the
31 agenda.
32 """)
33+
34+
35+class SpecificationWorkItemStatus(DBEnumeratedType):
36+ TODO = DBItem(0, """
37+ Todo
38+
39+ A work item that's not done yet.
40+ """)
41+ DONE = DBItem(1, """
42+ Done
43+
44+ A work item that's done.
45+ """)
46+ POSTPONED = DBItem(2, """
47+ Postponed
48+
49+ A work item that has been postponed.
50+ """)
51+ INPROGRESS = DBItem(3, """
52+ In progress
53+
54+ A work item that is inprogress.
55+ """)
56+ BLOCKED = DBItem(4, """
57+ Blocked
58+
59+ A work item that is blocked.
60+ """)
61
62=== modified file 'lib/lp/blueprints/interfaces/specification.py'
63--- lib/lp/blueprints/interfaces/specification.py 2012-01-01 02:58:52 +0000
64+++ lib/lp/blueprints/interfaces/specification.py 2012-02-15 21:39:22 +0000
65@@ -54,6 +54,7 @@
66 SpecificationImplementationStatus,
67 SpecificationLifecycleStatus,
68 SpecificationPriority,
69+ SpecificationWorkItemStatus,
70 )
71 from lp.blueprints.interfaces.specificationsubscription import (
72 ISpecificationSubscription,
73@@ -569,6 +570,11 @@
74 def setImplementationStatus(implementation_status, user):
75 """Mutator for implementation_status that calls updateLifeCycle."""
76
77+ def newWorkItem(title, sequence,
78+ status=SpecificationWorkItemStatus.TODO, assignee=None,
79+ milestone=None):
80+ """Create a new SpecificationWorkItem."""
81+
82 def setTarget(target):
83 """Set this specification's target.
84
85
86=== added file 'lib/lp/blueprints/interfaces/specificationworkitem.py'
87--- lib/lp/blueprints/interfaces/specificationworkitem.py 1970-01-01 00:00:00 +0000
88+++ lib/lp/blueprints/interfaces/specificationworkitem.py 2012-02-15 21:39:22 +0000
89@@ -0,0 +1,76 @@
90+# Copyright 2012 Canonical Ltd. This software is licensed under the
91+# GNU Affero General Public License version 3 (see the file LICENSE).
92+
93+"""SpecificationWorkItem interfaces."""
94+
95+__metaclass__ = type
96+
97+__all__ = [
98+ 'ISpecificationWorkItem',
99+ ]
100+
101+
102+from zope.interface import Interface
103+from zope.schema import (
104+ Bool,
105+ Choice,
106+ Datetime,
107+ Int,
108+ )
109+
110+from lp import _
111+from lp.blueprints.enums import SpecificationWorkItemStatus
112+from lp.services.fields import (
113+ PublicPersonChoice,
114+ Title,
115+ )
116+
117+
118+class ISpecificationWorkItem(Interface):
119+ """SpecificationWorkItem's public attributes and methods."""
120+
121+ id = Int(title=_("Database ID"), required=True, readonly=True)
122+
123+ title = Title(
124+ title=_('Title'), required=True, readonly=False,
125+ description=_("Work item title."))
126+
127+ assignee = PublicPersonChoice(
128+ title=_('Assignee'), required=False, readonly=False,
129+ description=_(
130+ "The person responsible for implementing the work item."),
131+ vocabulary='ValidPersonOrTeam')
132+
133+ date_created = Datetime(
134+ title=_('Date Created'), required=True, readonly=True)
135+
136+ milestone = Choice(
137+ title=_('Milestone'), required=False, readonly=False,
138+ vocabulary='Milestone',
139+ description=_(
140+ "The milestone to which this work item is targetted. If this "
141+ "is not set, then the target is the specification's "
142+ "milestone."))
143+
144+ status = Choice(
145+ title=_("Work Item Status"), required=True, readonly=False,
146+ default=SpecificationWorkItemStatus.TODO,
147+ vocabulary=SpecificationWorkItemStatus,
148+ description=_(
149+ "The state of progress being made on the actual "
150+ "implementation of this work item."))
151+
152+ specification = Choice(
153+ title=_('The specification that the work item is linked to.'),
154+ required=True, readonly=True, vocabulary='Specification')
155+
156+ deleted = Bool(
157+ title=_('Is this work item deleted?'),
158+ required=True, readonly=False, default=False,
159+ description=_("Marks the work item as deleted."))
160+
161+ sequence = Int(
162+ title=_("Work Item Sequence."),
163+ required=True, description=_(
164+ "The sequence in which the work items are to be displayed in the "
165+ "UI."))
166
167=== modified file 'lib/lp/blueprints/model/specification.py'
168--- lib/lp/blueprints/model/specification.py 2011-12-30 06:14:56 +0000
169+++ lib/lp/blueprints/model/specification.py 2012-02-15 21:39:22 +0000
170@@ -45,6 +45,7 @@
171 SpecificationLifecycleStatus,
172 SpecificationPriority,
173 SpecificationSort,
174+ SpecificationWorkItemStatus,
175 )
176 from lp.blueprints.errors import TargetAlreadyHasSpecification
177 from lp.blueprints.interfaces.specification import (
178@@ -60,6 +61,7 @@
179 from lp.blueprints.model.specificationsubscription import (
180 SpecificationSubscription,
181 )
182+from lp.blueprints.model.specificationworkitem import SpecificationWorkItem
183 from lp.bugs.interfaces.buglink import IBugLinkTarget
184 from lp.bugs.interfaces.bugtask import (
185 BugTaskSearchParams,
186@@ -228,6 +230,14 @@
187 return self.product
188 return self.distribution
189
190+ def newWorkItem(self, title, sequence,
191+ status=SpecificationWorkItemStatus.TODO, assignee=None,
192+ milestone=None):
193+ """See ISpecification."""
194+ return SpecificationWorkItem(
195+ title=title, status=status, specification=self, assignee=assignee,
196+ milestone=milestone, sequence=sequence)
197+
198 def setTarget(self, target):
199 """See ISpecification."""
200 if IProduct.providedBy(target):
201
202=== added file 'lib/lp/blueprints/model/specificationworkitem.py'
203--- lib/lp/blueprints/model/specificationworkitem.py 1970-01-01 00:00:00 +0000
204+++ lib/lp/blueprints/model/specificationworkitem.py 2012-02-15 21:39:22 +0000
205@@ -0,0 +1,62 @@
206+# Copyright 2012 Canonical Ltd. This software is licensed under the
207+# GNU Affero General Public License version 3 (see the file LICENSE).
208+
209+__metaclass__ = type
210+__all__ = [
211+ 'SpecificationWorkItem',
212+ ]
213+
214+from zope.interface import implements
215+
216+from storm.locals import (
217+ Bool,
218+ Int,
219+ Reference,
220+ Unicode,
221+ )
222+
223+from lp.services.database.constants import DEFAULT
224+from lp.services.database.datetimecol import UtcDateTimeCol
225+from lp.services.database.enumcol import EnumCol
226+from lp.services.database.stormbase import StormBase
227+
228+from lp.blueprints.enums import SpecificationWorkItemStatus
229+from lp.blueprints.interfaces.specificationworkitem import (
230+ ISpecificationWorkItem,
231+ )
232+from lp.registry.interfaces.person import validate_public_person
233+
234+
235+class SpecificationWorkItem(StormBase):
236+ implements(ISpecificationWorkItem)
237+
238+ __storm_table__ = 'SpecificationWorkItem'
239+
240+ id = Int(primary=True)
241+ title = Unicode(allow_none=False)
242+ specification_id = Int(name='specification')
243+ specification = Reference(specification_id, 'Specification.id')
244+ assignee_id = Int(name='assignee', validator=validate_public_person)
245+ assignee = Reference(assignee_id, 'Person.id')
246+ milestone_id = Int(name='milestone')
247+ milestone = Reference(milestone_id, 'Milestone.id')
248+ status = EnumCol(
249+ schema=SpecificationWorkItemStatus,
250+ notNull=True, default=SpecificationWorkItemStatus.TODO)
251+ date_created = UtcDateTimeCol(notNull=True, default=DEFAULT)
252+ sequence = Int(allow_none=False)
253+ deleted = Bool(allow_none=False, default=False)
254+
255+ def __repr__(self):
256+ title = self.title.encode('ASCII', 'backslashreplace')
257+ return '<SpecificationWorkItem [%s] %s: %s of %s>' % (
258+ self.assignee, title, self.status, self.specification)
259+
260+ def __init__(self, title, status, specification, assignee, milestone,
261+ sequence):
262+ self.title=title
263+ self.status=status
264+ self.specification = specification
265+ self.assignee=assignee
266+ self.milestone=milestone
267+ self.sequence=sequence
268
269=== modified file 'lib/lp/blueprints/model/tests/test_specification.py'
270--- lib/lp/blueprints/model/tests/test_specification.py 2012-01-01 02:58:52 +0000
271+++ lib/lp/blueprints/model/tests/test_specification.py 2012-02-15 21:39:22 +0000
272@@ -9,9 +9,19 @@
273
274 from lp.app.validators import LaunchpadValidationError
275 from lp.blueprints.interfaces.specification import ISpecification
276+from lp.blueprints.interfaces.specificationworkitem import (
277+ SpecificationWorkItemStatus,
278+ )
279+from lp.blueprints.model.specificationworkitem import SpecificationWorkItem
280 from lp.services.webapp import canonical_url
281-from lp.testing import TestCaseWithFactory
282+from lp.testing import (
283+ TestCaseWithFactory,
284+ ANONYMOUS,
285+ login,
286+ login_person,
287+ )
288 from lp.testing.layers import DatabaseFunctionalLayer
289+from zope.security.interfaces import Unauthorized
290
291
292 class TestSpecificationDependencies(TestCaseWithFactory):
293@@ -132,3 +142,35 @@
294 self.assertEqual(
295 '%s is already registered by <a href="%s">%s</a>.'
296 % (u'http://ubuntu.com/foo', url, cleaned_title), str(e))
297+
298+
299+class TestSpecificationWorkItems(TestCaseWithFactory):
300+
301+ layer = DatabaseFunctionalLayer
302+
303+ def test_anonymous_newworkitem_not_allowed(self):
304+ spec = self.factory.makeSpecification()
305+ login(ANONYMOUS)
306+ self.assertRaises(Unauthorized, getattr, spec, 'newWorkItem')
307+
308+ def test_owner_newworkitem_allowed(self):
309+ spec = self.factory.makeSpecification()
310+ login_person(spec.owner)
311+ work_item = spec.newWorkItem(title=u'new-work-item', sequence=0)
312+ self.assertIsInstance(work_item, SpecificationWorkItem)
313+
314+ def test_newworkitem_uses_passed_arguments(self):
315+ title = u'new-work-item'
316+ spec = self.factory.makeSpecification()
317+ assignee = self.factory.makePerson()
318+ milestone = self.factory.makeMilestone()
319+ status = SpecificationWorkItemStatus.DONE
320+ login_person(spec.owner)
321+ work_item = spec.newWorkItem(
322+ title=title, assignee=assignee, milestone=milestone,
323+ status=status, sequence=0)
324+ self.assertEqual(spec, work_item.specification)
325+ self.assertEqual(assignee, work_item.assignee)
326+ self.assertEqual(status, work_item.status)
327+ self.assertEqual(title, work_item.title)
328+ self.assertEqual(milestone, work_item.milestone)
329
330=== modified file 'lib/lp/blueprints/tests/test_implements.py'
331--- lib/lp/blueprints/tests/test_implements.py 2012-01-01 02:58:52 +0000
332+++ lib/lp/blueprints/tests/test_implements.py 2012-02-15 21:39:22 +0000
333@@ -9,6 +9,9 @@
334 IHasSpecifications,
335 ISpecificationTarget,
336 )
337+from lp.blueprints.interfaces.specificationworkitem import (
338+ ISpecificationWorkItem,
339+ )
340 from lp.testing import TestCaseWithFactory
341 from lp.testing.layers import DatabaseFunctionalLayer
342
343@@ -65,3 +68,13 @@
344 def test_distroseries_implements_ISpecificationTarget(self):
345 distroseries = self.factory.makeDistroSeries()
346 self.assertProvides(distroseries, ISpecificationTarget)
347+
348+
349+class ImplementsISpecificationWorkItemTests(TestCaseWithFactory):
350+ """Test that various objects implement ISpecificationWorkItem."""
351+
352+ layer = DatabaseFunctionalLayer
353+
354+ def test_specificationworkitem_implements_ISpecificationTarget(self):
355+ specificationworkitem = self.factory.makeSpecificationWorkItem()
356+ self.assertProvides(specificationworkitem, ISpecificationWorkItem)
357
358=== modified file 'lib/lp/testing/factory.py'
359--- lib/lp/testing/factory.py 2012-02-14 02:22:59 +0000
360+++ lib/lp/testing/factory.py 2012-02-15 21:39:22 +0000
361@@ -73,6 +73,7 @@
362 NewSpecificationDefinitionStatus,
363 SpecificationDefinitionStatus,
364 SpecificationPriority,
365+ SpecificationWorkItemStatus,
366 )
367 from lp.blueprints.interfaces.specification import ISpecificationSet
368 from lp.blueprints.interfaces.sprint import ISprintSet
369@@ -2104,6 +2105,22 @@
370
371 makeBlueprint = makeSpecification
372
373+ def makeSpecificationWorkItem(self, title=None, specification=None,
374+ assignee=None, milestone=None, deleted=False,
375+ status=SpecificationWorkItemStatus.TODO,
376+ sequence=None):
377+ if title is None:
378+ title = self.getUniqueString(u'title')
379+ if specification is None:
380+ specification = self.makeSpecification()
381+ if sequence is None:
382+ sequence = self.getUniqueInteger()
383+ work_item = removeSecurityProxy(specification).newWorkItem(
384+ title=title, sequence=sequence, status=status, assignee=assignee,
385+ milestone=milestone)
386+ work_item.deleted = deleted
387+ return work_item
388+
389 def makeQuestion(self, target=None, title=None,
390 owner=None, description=None, language=None):
391 """Create and return a new, arbitrary Question.

Subscribers

People subscribed via source and target branches

to status/vote changes: