Merge lp:~linaro-infrastructure/launchpad/workitems-model-classes into lp:launchpad/db-devel
- workitems-model-classes
- Merge into db-devel
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 | ||||
Related bugs: |
|
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,
Description of the change
Hi,
This branch adds models for blueprint Work Items.
The topic has been discussed here https:/
Thanks,
Mattias
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:/
> Your team Linaro Infrastructure is subscribed to branch lp:~linaro-infrastructure/launchpad/workitems-model-classes.
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.
Mattias Backman (mabac) wrote : | # |
We need to fix my typo and add some tests. Guilherme will pick that up tomorrow.
Guilherme Salgado (salgado) wrote : | # |
Okay, this branch should be good to go now. We were missing a zcml declaration to allow ISpecificationW
(I've done this myself because Mattias is not around today)
Richard Harding (rharding) wrote : | # |
Thanks, this looks much better using the newer storm vs sqlobject code. I appreciate the effort to rework it.
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/
> --- lib/lp/
> +++ lib/lp/
> @@ -511,3 +512,31 @@
> organisers. It has not yet been accepted or declined for the
> agenda.
> """)
> +
> +
> +class SpecificationWo
> + 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/
--- lib/lp/
+++ lib/lp/
@@ -511,3 +512,31 @@
agenda.
""")
+
+
+class SpecificationWo
+ 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.
+ """)
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.
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:/
> Your team Linaro Infrastructure is subscribed to branch lp:~linaro-infrastructure/launchpad/workitems-model-classes.
j.c.sackett (jcsackett) wrote : | # |
Alas, I did a bad push. Thus, my help was less than helpful. :-P
Thanks for making those changes.
Preview Diff
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. |
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.