Merge lp:~salgado/launchpad/expose-blueprints into lp:launchpad
- expose-blueprints
- Merge into devel
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Guilherme Salgado | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 11997 | ||||
Proposed branch: | lp:~salgado/launchpad/expose-blueprints | ||||
Merge into: | lp:launchpad | ||||
Diff against target: |
1054 lines (+690/-99) 11 files modified
lib/canonical/launchpad/interfaces/_schema_circular_imports.py (+14/-0) lib/lp/app/doc/tales.txt (+6/-2) lib/lp/blueprints/interfaces/specification.py (+133/-78) lib/lp/blueprints/interfaces/specificationtarget.py (+42/-6) lib/lp/blueprints/interfaces/webservice.py (+1/-0) lib/lp/blueprints/model/specification.py (+5/-2) lib/lp/blueprints/model/sprint.py (+4/-3) lib/lp/blueprints/stories/standalone/sprint-links.txt (+0/-4) lib/lp/blueprints/tests/test_implements.py (+65/-0) lib/lp/blueprints/tests/test_webservice.py (+395/-0) lib/lp/testing/factory.py (+25/-4) |
||||
To merge this branch: | bzr merge lp:~salgado/launchpad/expose-blueprints | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Hudson-Doyle | Approve | ||
Review via email: mp+41898@code.launchpad.net |
Commit message
[r=mwhudson]
Description of the change
This branch exposes ISpecification attributes on the 'devel' version of the webservice.
I left out some controversial fields (i.e. .productseries, .distroseries) and exported .target as read-only for simplicity and because this diff is already too big.
Most of the changes here come from https:/
Guilherme Salgado (salgado) wrote : | # |
Michael Hudson-Doyle (mwhudson) wrote : | # |
Hi Salgado,
Yay for progress on this issue. I entirely support the postponing of
all the difficult stuff :-)
I have some quibbles about the tests, but nothing serious I hope.
> === modified file 'lib/canonical/
> --- lib/canonical/
> +++ lib/canonical/
> @@ -34,6 +34,10 @@
> )
> from lp.blueprints.
> from lp.blueprints.
> +from lp.blueprints.
> + IHasSpecifications,
> + ISpecificationT
> + )
> from lp.bugs.
> IBug,
> IFrontPageBugAd
> @@ -516,3 +520,13 @@
>
> # IProductSeries
> patch_reference
> +
> +# ISpecificationT
> +patch_
> + ISpecificationT
> +
> +# IHasSpecifications
> +patch_
> + IHasSpecifications, 'all_specificat
> +patch_
> + IHasSpecifications, 'valid_
It's a shame that we don't have the app specific circular import files
yet... oh well.
> === modified file 'lib/lp/
> --- lib/lp/
> +++ lib/lp/
> @@ -19,7 +19,12 @@
> ]
>
>
> -from lazr.restful.
> +from lazr.restful.
> + exported,
> + export_
> + )
> +from lazr.restful.fields import ReferenceChoice
> +
> from zope.component import getUtility
> from zope.interface import (
> Attribute,
> @@ -44,9 +49,13 @@
> SpecificationLi
> SpecificationPr
> )
> -from lp.blueprints.
> +from lp.blueprints.
> + IHasSpecifications,
> + ISpecificationT
> + )
> from lp.blueprints.
> from lp.code.
> +from lp.registry.
> from lp.registry.
> from lp.registry.
> from lp.services.fields import (
> @@ -119,48 +128,69 @@
> class INewSpecificati
> """A schema for a new specification."""
>
> - name = SpecNameField(
> - title=_('Name'), required=True, readonly=False,
> - description=_(
> - "May contain lower-case letters, numbers, and dashes. "
> - "It will be used in the specification url. "
> - "Examples: mozilla-
> - title = Title(
> - title=_('Title'), required=True, description=_(
> - "Describe the feature as clearly as possible in up to 70 "
> - ...
Guilherme Salgado (salgado) wrote : | # |
Hi Michael,
Thanks for the review!
On Fri, 2010-11-26 at 01:12 +0000, Michael Hudson-Doyle wrote:
> Review: Approve
> Hi Salgado,
>
> Yay for progress on this issue. I entirely support the postponing of
> all the difficult stuff :-)
>
> I have some quibbles about the tests, but nothing serious I hope.
I like all your suggestions and since it's Friday and I'm all excited
with the weekend approaching, I'm proposing even more changes. :)
>
> > === modified file 'lib/canonical/
> > --- lib/canonical/
> > +++ lib/canonical/
> > @@ -34,6 +34,10 @@
> > )
> > from lp.blueprints.
> > from lp.blueprints.
> > +from lp.blueprints.
> > + IHasSpecifications,
> > + ISpecificationT
> > + )
> > from lp.bugs.
> > IBug,
> > IFrontPageBugAd
> > @@ -516,3 +520,13 @@
> >
> > # IProductSeries
> > patch_reference
> > +
> > +# ISpecificationT
> > +patch_
> > + ISpecificationT
> > +
> > +# IHasSpecifications
> > +patch_
> > + IHasSpecifications, 'all_specificat
> > +patch_
> > + IHasSpecifications, 'valid_
>
> It's a shame that we don't have the app specific circular import files
> yet... oh well.
That'd be a slight improvement, but it's still a PITA to maintain these
"patches".
> > === added file 'lib/lp/
> > --- lib/lp/
> > +++ lib/lp/
> > @@ -0,0 +1,410 @@
> > +# Copyright 2009 Canonical Ltd. This software is licensed under the
> > +# GNU Affero General Public License version 3 (see the file LICENSE).
> > +
> > +"""Webservice unit tests related to Launchpad blueprints."""
> > +
> > +__metaclass__ = type
> > +
> > +from canonical.testing import DatabaseFunctio
> > +from canonical.
> > +from lp.blueprints.
> > + SpecificationDe
> > + SpecificationPr
> > +from lp.testing import (
> > + launchpadlib_for, TestCaseWithFac
> > +
> > +
> > +class SpecificationWe
> > +
> > + def makeProduct(self):
> > + return self.factory.
> > +
> > + def makeDistributio
> > + return self.factory.
>
> I don't particularly like these helpers. Why can't the tests that
> care about the names specify the names?
Yeah, good catch; I got rid of them.
>
> > + def getLaunchpadlib
> > + user = self.factory.
> > + return launchpadlib_
Guilherme Salgado (salgado) wrote : | # |
I had to do a few other changes because Sprint currently doesn't
implement all of IHasSpecifications as it claims to do.
This became apparent because some code which gets a snapshot of a Sprint
tried to access .all_specifications as part of that. This probably
happened only now because the snapshotting code ignores Attribute fields
so it was ignoring that and other fields of IHasSpecifications.
These changes are in r11984
Guilherme Salgado (salgado) wrote : | # |
And I'd also like to sneak this extra tiny change (r11985) to expose implementation_
Michael Hudson-Doyle (mwhudson) wrote : | # |
On Fri, 26 Nov 2010 13:11:21 -0000, Guilherme Salgado <email address hidden> wrote:
> Hi Michael,
>
> Thanks for the review!
>
> On Fri, 2010-11-26 at 01:12 +0000, Michael Hudson-Doyle wrote:
> > Review: Approve
> > Hi Salgado,
> >
> > Yay for progress on this issue. I entirely support the postponing of
> > all the difficult stuff :-)
> >
> > I have some quibbles about the tests, but nothing serious I hope.
>
> I like all your suggestions and since it's Friday and I'm all excited
> with the weekend approaching, I'm proposing even more changes. :)
Hah!
> > > === modified file 'lib/canonical/
> > > --- lib/canonical/
> > > +++ lib/canonical/
> > > @@ -34,6 +34,10 @@
> > > )
> > > from lp.blueprints.
> > > from lp.blueprints.
> > > +from lp.blueprints.
> > > + IHasSpecifications,
> > > + ISpecificationT
> > > + )
> > > from lp.bugs.
> > > IBug,
> > > IFrontPageBugAd
> > > @@ -516,3 +520,13 @@
> > >
> > > # IProductSeries
> > > patch_reference
> > > +
> > > +# ISpecificationT
> > > +patch_
> > > + ISpecificationT
> > > +
> > > +# IHasSpecifications
> > > +patch_
> > > + IHasSpecifications, 'all_specificat
> > > +patch_
> > > + IHasSpecifications, 'valid_
> >
> > It's a shame that we don't have the app specific circular import files
> > yet... oh well.
>
> That'd be a slight improvement, but it's still a PITA to maintain these
> "patches".
Yeah. I saw what you had to do with SprintSpecification too :/
> > > === added file 'lib/lp/
> > > --- lib/lp/
> > > +++ lib/lp/
> > > @@ -0,0 +1,410 @@
> > > +# Copyright 2009 Canonical Ltd. This software is licensed under the
> > > +# GNU Affero General Public License version 3 (see the file LICENSE).
> > > +
> > > +"""Webservice unit tests related to Launchpad blueprints."""
> > > +
> > > +__metaclass__ = type
> > > +
> > > +from canonical.testing import DatabaseFunctio
> > > +from canonical.
> > > +from lp.blueprints.
> > > + SpecificationDe
> > > + SpecificationPr
> > > +from lp.testing import (
> > > + launchpadlib_for, TestCaseWithFac
> > > +
> > > +
> > > +class SpecificationWe
> > > +
> > > + def makeProduct(self):
> > > + return self.factory.
> > > +
> > > + def makeDistributio
> > > + return self.factory.
Preview Diff
1 | === modified file 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py' |
2 | --- lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-11-17 22:18:34 +0000 |
3 | +++ lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-11-26 18:08:22 +0000 |
4 | @@ -34,6 +34,10 @@ |
5 | ) |
6 | from lp.blueprints.interfaces.specification import ISpecification |
7 | from lp.blueprints.interfaces.specificationbranch import ISpecificationBranch |
8 | +from lp.blueprints.interfaces.specificationtarget import ( |
9 | + IHasSpecifications, |
10 | + ISpecificationTarget, |
11 | + ) |
12 | from lp.bugs.interfaces.bug import ( |
13 | IBug, |
14 | IFrontPageBugAddForm, |
15 | @@ -516,3 +520,13 @@ |
16 | |
17 | # IProductSeries |
18 | patch_reference_property(IProductSeries, 'product', IProduct) |
19 | + |
20 | +# ISpecificationTarget |
21 | +patch_entry_return_type( |
22 | + ISpecificationTarget, 'getSpecification', ISpecification) |
23 | + |
24 | +# IHasSpecifications |
25 | +patch_collection_property( |
26 | + IHasSpecifications, 'all_specifications', ISpecification) |
27 | +patch_collection_property( |
28 | + IHasSpecifications, 'valid_specifications', ISpecification) |
29 | |
30 | === modified file 'lib/lp/app/doc/tales.txt' |
31 | --- lib/lp/app/doc/tales.txt 2010-10-18 22:24:59 +0000 |
32 | +++ lib/lp/app/doc/tales.txt 2010-11-26 18:08:22 +0000 |
33 | @@ -673,8 +673,11 @@ |
34 | Blueprints |
35 | .......... |
36 | |
37 | + >>> from lp.blueprints.interfaces.specification import ( |
38 | + ... SpecificationPriority) |
39 | >>> login('test@canonical.com') |
40 | - >>> specification = factory.makeSpecification() |
41 | + >>> specification = factory.makeSpecification( |
42 | + ... priority=SpecificationPriority.UNDEFINED) |
43 | >>> test_tales("specification/fmt:link", specification=specification) |
44 | u'<a...class="sprite blueprint-undefined">...</a>' |
45 | |
46 | @@ -682,7 +685,8 @@ |
47 | Blueprint branches |
48 | .................. |
49 | |
50 | - >>> specification = factory.makeSpecification() |
51 | + >>> specification = factory.makeSpecification( |
52 | + ... priority=SpecificationPriority.UNDEFINED) |
53 | >>> branch = factory.makeAnyBranch() |
54 | >>> specification_branch = specification.linkBranch(branch, branch.owner) |
55 | >>> test_tales("specification_branch/fmt:link", |
56 | |
57 | === modified file 'lib/lp/blueprints/interfaces/specification.py' |
58 | --- lib/lp/blueprints/interfaces/specification.py 2010-11-23 20:19:24 +0000 |
59 | +++ lib/lp/blueprints/interfaces/specification.py 2010-11-26 18:08:22 +0000 |
60 | @@ -19,7 +19,12 @@ |
61 | ] |
62 | |
63 | |
64 | -from lazr.restful.declarations import export_as_webservice_entry |
65 | +from lazr.restful.declarations import ( |
66 | + exported, |
67 | + export_as_webservice_entry, |
68 | + ) |
69 | +from lazr.restful.fields import ReferenceChoice |
70 | + |
71 | from zope.component import getUtility |
72 | from zope.interface import ( |
73 | Attribute, |
74 | @@ -44,9 +49,13 @@ |
75 | SpecificationLifecycleStatus, |
76 | SpecificationPriority, |
77 | ) |
78 | -from lp.blueprints.interfaces.specificationtarget import IHasSpecifications |
79 | +from lp.blueprints.interfaces.specificationtarget import ( |
80 | + IHasSpecifications, |
81 | + ISpecificationTarget, |
82 | + ) |
83 | from lp.blueprints.interfaces.sprint import ISprint |
84 | from lp.code.interfaces.branchlink import IHasLinkedBranches |
85 | +from lp.registry.interfaces.milestone import IMilestone |
86 | from lp.registry.interfaces.projectgroup import IProjectGroup |
87 | from lp.registry.interfaces.role import IHasOwner |
88 | from lp.services.fields import ( |
89 | @@ -119,48 +128,69 @@ |
90 | class INewSpecification(Interface): |
91 | """A schema for a new specification.""" |
92 | |
93 | - name = SpecNameField( |
94 | - title=_('Name'), required=True, readonly=False, |
95 | - description=_( |
96 | - "May contain lower-case letters, numbers, and dashes. " |
97 | - "It will be used in the specification url. " |
98 | - "Examples: mozilla-type-ahead-find, postgres-smart-serial.")) |
99 | - title = Title( |
100 | - title=_('Title'), required=True, description=_( |
101 | - "Describe the feature as clearly as possible in up to 70 " |
102 | - "characters. This title is displayed in every feature " |
103 | - "list or report.")) |
104 | - specurl = SpecURLField( |
105 | - title=_('Specification URL'), required=False, |
106 | - description=_( |
107 | - "The URL of the specification. This is usually a wiki page."), |
108 | - constraint=valid_webref) |
109 | - summary = Summary( |
110 | - title=_('Summary'), required=True, description=_( |
111 | - "A single-paragraph description of the feature. " |
112 | - "This will also be displayed in most feature listings.")) |
113 | - definition_status = Choice( |
114 | - title=_('Definition Status'), |
115 | - vocabulary=SpecificationDefinitionStatus, |
116 | - default=SpecificationDefinitionStatus.NEW, |
117 | - description=_( |
118 | - "The current status of the process to define the " |
119 | - "feature and get approval for the implementation plan.")) |
120 | - assignee = PublicPersonChoice( |
121 | - title=_('Assignee'), required=False, |
122 | - description=_("The person responsible for implementing the feature."), |
123 | - vocabulary='ValidPersonOrTeam') |
124 | - drafter = PublicPersonChoice( |
125 | - title=_('Drafter'), required=False, |
126 | - description=_( |
127 | - "The person responsible for drafting the specification."), |
128 | - vocabulary='ValidPersonOrTeam') |
129 | - approver = PublicPersonChoice( |
130 | - title=_('Approver'), required=False, |
131 | - description=_( |
132 | - "The person responsible for approving the specification, " |
133 | - "and for reviewing the code when it's ready to be landed."), |
134 | - vocabulary='ValidPersonOrTeam') |
135 | + name = exported( |
136 | + SpecNameField( |
137 | + title=_('Name'), required=True, readonly=False, |
138 | + description=_( |
139 | + "May contain lower-case letters, numbers, and dashes. " |
140 | + "It will be used in the specification url. " |
141 | + "Examples: mozilla-type-ahead-find, postgres-smart-serial.")), |
142 | + ('devel', dict(exported=True)), exported=False) |
143 | + title = exported( |
144 | + Title( |
145 | + title=_('Title'), required=True, description=_( |
146 | + "Describe the feature as clearly as possible in up to 70 " |
147 | + "characters. This title is displayed in every feature " |
148 | + "list or report.")), |
149 | + ('devel', dict(exported=True)), exported=False) |
150 | + specurl = exported( |
151 | + SpecURLField( |
152 | + title=_('Specification URL'), required=False, |
153 | + description=_( |
154 | + "The URL of the specification. This is usually a wiki page."), |
155 | + constraint=valid_webref), |
156 | + ('devel', dict(exported=True, exported_as='specification_url')), |
157 | + exported=False) |
158 | + summary = exported( |
159 | + Summary( |
160 | + title=_('Summary'), required=True, description=_( |
161 | + "A single-paragraph description of the feature. " |
162 | + "This will also be displayed in most feature listings.")), |
163 | + ('devel', dict(exported=True)), exported=False) |
164 | + # XXX: salgado, 2010-11-25, bug=680880: We need a method for changing the |
165 | + # definition_status because when that happens we may need to call |
166 | + # updateLifecycleStatus(). |
167 | + definition_status = exported( |
168 | + Choice( |
169 | + title=_('Definition Status'), |
170 | + vocabulary=SpecificationDefinitionStatus, |
171 | + default=SpecificationDefinitionStatus.NEW, |
172 | + description=_( |
173 | + "The current status of the process to define the " |
174 | + "feature and get approval for the implementation plan.")), |
175 | + ('devel', dict(exported=True, readonly=True)), exported=False) |
176 | + assignee = exported( |
177 | + PublicPersonChoice( |
178 | + title=_('Assignee'), required=False, |
179 | + description=_( |
180 | + "The person responsible for implementing the feature."), |
181 | + vocabulary='ValidPersonOrTeam'), |
182 | + ('devel', dict(exported=True)), exported=False) |
183 | + drafter = exported( |
184 | + PublicPersonChoice( |
185 | + title=_('Drafter'), required=False, |
186 | + description=_( |
187 | + "The person responsible for drafting the specification."), |
188 | + vocabulary='ValidPersonOrTeam'), |
189 | + ('devel', dict(exported=True)), exported=False) |
190 | + approver = exported( |
191 | + PublicPersonChoice( |
192 | + title=_('Approver'), required=False, |
193 | + description=_( |
194 | + "The person responsible for approving the specification, " |
195 | + "and for reviewing the code when it's ready to be landed."), |
196 | + vocabulary='ValidPersonOrTeam'), |
197 | + ('devel', dict(exported=True)), exported=False) |
198 | |
199 | |
200 | class INewSpecificationProjectTarget(Interface): |
201 | @@ -201,10 +231,15 @@ |
202 | |
203 | Requires the user to specify a distribution or a product as a target. |
204 | """ |
205 | - target = Choice(title=_("For"), |
206 | - description=_("The project for which this proposal is " |
207 | - "being made."), |
208 | - required=True, vocabulary='DistributionOrProduct') |
209 | + # Exported as readonly for simplicity, but could be exported as read-write |
210 | + # using setTarget() as the mutator. |
211 | + target = exported( |
212 | + ReferenceChoice( |
213 | + title=_('For'), required=True, vocabulary='DistributionOrProduct', |
214 | + description=_( |
215 | + "The project for which this proposal is being made."), |
216 | + schema=ISpecificationTarget), |
217 | + ('devel', dict(exported=True, readonly=True)), exported=False) |
218 | |
219 | |
220 | class ISpecificationEditRestricted(Interface): |
221 | @@ -235,40 +270,54 @@ |
222 | # referencing it. |
223 | id = Int(title=_("Database ID"), required=True, readonly=True) |
224 | |
225 | - priority = Choice( |
226 | - title=_('Priority'), vocabulary=SpecificationPriority, |
227 | - default=SpecificationPriority.UNDEFINED, required=True) |
228 | - datecreated = Datetime( |
229 | - title=_('Date Created'), required=True, readonly=True) |
230 | - owner = PublicPersonChoice( |
231 | - title=_('Owner'), required=True, readonly=True, |
232 | - vocabulary='ValidPersonOrTeam') |
233 | - # target |
234 | + priority = exported( |
235 | + Choice( |
236 | + title=_('Priority'), vocabulary=SpecificationPriority, |
237 | + default=SpecificationPriority.UNDEFINED, required=True), |
238 | + ('devel', dict(exported=True)), exported=False) |
239 | + datecreated = exported( |
240 | + Datetime( |
241 | + title=_('Date Created'), required=True, readonly=True), |
242 | + ('devel', dict(exported=True, exported_as='date_created')), |
243 | + exported=False) |
244 | + owner = exported( |
245 | + PublicPersonChoice( |
246 | + title=_('Owner'), required=True, readonly=True, |
247 | + vocabulary='ValidPersonOrTeam'), |
248 | + ('devel', dict(exported=True)), exported=False) |
249 | + |
250 | product = Choice(title=_('Project'), required=False, |
251 | - vocabulary='Product') |
252 | + vocabulary='Product') |
253 | distribution = Choice(title=_('Distribution'), required=False, |
254 | - vocabulary='Distribution') |
255 | + vocabulary='Distribution') |
256 | |
257 | - # series |
258 | - productseries = Choice(title=_('Series Goal'), required=False, |
259 | + productseries = Choice( |
260 | + title=_('Series Goal'), required=False, |
261 | vocabulary='FilteredProductSeries', |
262 | description=_( |
263 | - "Choose a series in which you would like to deliver " |
264 | - "this feature. Selecting '(no value)' will clear the goal.")) |
265 | - distroseries = Choice(title=_('Series Goal'), required=False, |
266 | + "Choose a series in which you would like to deliver " |
267 | + "this feature. Selecting '(no value)' will clear the goal.")) |
268 | + distroseries = Choice( |
269 | + title=_('Series Goal'), required=False, |
270 | vocabulary='FilteredDistroSeries', |
271 | description=_( |
272 | "Choose a series in which you would like to deliver " |
273 | "this feature. Selecting '(no value)' will clear the goal.")) |
274 | |
275 | # milestone |
276 | - milestone = Choice( |
277 | - title=_('Milestone'), required=False, vocabulary='Milestone', |
278 | - description=_( |
279 | - "The milestone in which we would like this feature to be " |
280 | - "delivered.")) |
281 | + milestone = exported( |
282 | + ReferenceChoice( |
283 | + title=_('Milestone'), required=False, vocabulary='Milestone', |
284 | + description=_( |
285 | + "The milestone in which we would like this feature to be " |
286 | + "delivered."), |
287 | + schema=IMilestone), |
288 | + ('devel', dict(exported=True)), exported=False) |
289 | |
290 | # nomination to a series for release management |
291 | + # XXX: It'd be nice to export goal as read-only, but it's tricky because |
292 | + # users will need to be aware of goalstatus as what's returned by .goal |
293 | + # may not be the accepted goal. |
294 | goal = Attribute("The series for which this feature is a goal.") |
295 | goalstatus = Choice( |
296 | title=_('Goal Acceptance'), vocabulary=SpecificationGoalStatus, |
297 | @@ -283,10 +332,12 @@ |
298 | date_goal_decided = Attribute("The date the spec was approved " |
299 | "or declined as a goal.") |
300 | |
301 | - whiteboard = Text(title=_('Status Whiteboard'), required=False, |
302 | - description=_( |
303 | - "Any notes on the status of this spec you would like to make. " |
304 | - "Your changes will override the current text.")) |
305 | + whiteboard = exported( |
306 | + Text(title=_('Status Whiteboard'), required=False, |
307 | + description=_( |
308 | + "Any notes on the status of this spec you would like to " |
309 | + "make. Your changes will override the current text.")), |
310 | + ('devel', dict(exported=True)), exported=False) |
311 | direction_approved = Bool(title=_('Basic direction approved?'), |
312 | required=False, default=False, description=_("Check this to " |
313 | "indicate that the drafter and assignee have satisfied the " |
314 | @@ -297,11 +348,15 @@ |
315 | "number of developer days it will take to implement this feature. " |
316 | "Please only provide an estimate if you are relatively confident " |
317 | "in the number.")) |
318 | - implementation_status = Choice(title=_("Implementation Status"), |
319 | - required=True, default=SpecificationImplementationStatus.UNKNOWN, |
320 | - vocabulary=SpecificationImplementationStatus, description=_( |
321 | - "The state of progress being made on the actual implementation or " |
322 | - "delivery of this feature.")) |
323 | + implementation_status = exported( |
324 | + Choice( |
325 | + title=_("Implementation Status"), required=True, |
326 | + default=SpecificationImplementationStatus.UNKNOWN, |
327 | + vocabulary=SpecificationImplementationStatus, |
328 | + description=_( |
329 | + "The state of progress being made on the actual " |
330 | + "implementation or delivery of this feature.")), |
331 | + ('devel', dict(exported=True, readonly=True)), exported=False) |
332 | superseded_by = Choice(title=_("Superseded by"), |
333 | required=False, default=None, |
334 | vocabulary='Specification', description=_("The specification " |
335 | |
336 | === modified file 'lib/lp/blueprints/interfaces/specificationtarget.py' |
337 | --- lib/lp/blueprints/interfaces/specificationtarget.py 2010-08-20 20:31:18 +0000 |
338 | +++ lib/lp/blueprints/interfaces/specificationtarget.py 2010-11-26 18:08:22 +0000 |
339 | @@ -17,6 +17,22 @@ |
340 | Attribute, |
341 | Interface, |
342 | ) |
343 | +from zope.schema import TextLine |
344 | + |
345 | +from lazr.restful.declarations import ( |
346 | + exported, |
347 | + export_as_webservice_entry, |
348 | + export_read_operation, |
349 | + operation_for_version, |
350 | + operation_parameters, |
351 | + operation_returns_entry, |
352 | + ) |
353 | +from lazr.restful.fields import ( |
354 | + CollectionField, |
355 | + Reference, |
356 | + ) |
357 | + |
358 | +from canonical.launchpad import _ |
359 | |
360 | |
361 | class IHasSpecifications(Interface): |
362 | @@ -26,16 +42,30 @@ |
363 | associated with them, and you can use this interface to query those. |
364 | """ |
365 | |
366 | - all_specifications = Attribute( |
367 | - 'A list of all specifications, regardless of status or approval ' |
368 | - 'or completion, for this object.') |
369 | + all_specifications = exported( |
370 | + CollectionField( |
371 | + title=_("All specifications"), |
372 | + value_type=Reference(schema=Interface), # ISpecification, really. |
373 | + readonly=True, |
374 | + description=_( |
375 | + 'A list of all specifications, regardless of status or ' |
376 | + 'approval or completion, for this object.')), |
377 | + ('devel', dict(exported=True)), exported=False) |
378 | |
379 | has_any_specifications = Attribute( |
380 | 'A true or false indicator of whether or not this object has any ' |
381 | 'specifications associated with it, regardless of their status.') |
382 | |
383 | - valid_specifications = Attribute( |
384 | - 'A list of all specifications that are not obsolete.') |
385 | + valid_specifications = exported( |
386 | + CollectionField( |
387 | + title=_("Valid specifications"), |
388 | + value_type=Reference(schema=Interface), # ISpecification, really. |
389 | + readonly=True, |
390 | + description=_( |
391 | + 'All specifications that are not obsolete. When called from ' |
392 | + 'an ISpecificationGoal it will also exclude the ones that ' |
393 | + 'have not been accepted for that goal')), |
394 | + ('devel', dict(exported=True)), exported=False) |
395 | |
396 | latest_specifications = Attribute( |
397 | "The latest 5 specifications registered for this context.") |
398 | @@ -63,12 +93,18 @@ |
399 | """ |
400 | |
401 | |
402 | - |
403 | class ISpecificationTarget(IHasSpecifications): |
404 | """An interface for the objects which actually have unique |
405 | specifications directly attached to them. |
406 | """ |
407 | |
408 | + export_as_webservice_entry() |
409 | + |
410 | + @operation_parameters( |
411 | + name=TextLine(title=_('The name of the specification'))) |
412 | + @operation_returns_entry(Interface) # really ISpecification |
413 | + @export_read_operation() |
414 | + @operation_for_version('devel') |
415 | def getSpecification(name): |
416 | """Returns the specification with the given name, for this target, |
417 | or None. |
418 | |
419 | === modified file 'lib/lp/blueprints/interfaces/webservice.py' |
420 | --- lib/lp/blueprints/interfaces/webservice.py 2010-11-09 16:25:22 +0000 |
421 | +++ lib/lp/blueprints/interfaces/webservice.py 2010-11-26 18:08:22 +0000 |
422 | @@ -16,6 +16,7 @@ |
423 | |
424 | from lp.blueprints.interfaces.specification import ISpecification |
425 | from lp.blueprints.interfaces.specificationbranch import ISpecificationBranch |
426 | +from lp.blueprints.interfaces.specificationtarget import ISpecificationTarget |
427 | # XXX: JonathanLange 2010-11-09 bug=673083: Legacy work-around for circular |
428 | # import bugs. Break this up into a per-package thing. |
429 | from canonical.launchpad.interfaces import _schema_circular_imports |
430 | |
431 | === modified file 'lib/lp/blueprints/model/specification.py' |
432 | --- lib/lp/blueprints/model/specification.py 2010-11-23 19:35:46 +0000 |
433 | +++ lib/lp/blueprints/model/specification.py 2010-11-26 18:08:22 +0000 |
434 | @@ -74,8 +74,6 @@ |
435 | from lp.blueprints.model.specificationsubscription import ( |
436 | SpecificationSubscription, |
437 | ) |
438 | -from lp.blueprints.model.sprint import Sprint |
439 | -from lp.blueprints.model.sprintspecification import SprintSpecification |
440 | from lp.bugs.interfaces.buglink import IBugLinkTarget |
441 | from lp.bugs.model.buglinktarget import BugLinkTargetMixin |
442 | from lp.registry.interfaces.distribution import IDistribution |
443 | @@ -563,6 +561,8 @@ |
444 | # sprint linking |
445 | def linkSprint(self, sprint, user): |
446 | """See ISpecification.""" |
447 | + from lp.blueprints.model.sprintspecification import ( |
448 | + SprintSpecification) |
449 | for sprint_link in self.sprint_links: |
450 | # sprints have unique names |
451 | if sprint_link.sprint.name == sprint.name: |
452 | @@ -575,6 +575,8 @@ |
453 | |
454 | def unlinkSprint(self, sprint): |
455 | """See ISpecification.""" |
456 | + from lp.blueprints.model.sprintspecification import ( |
457 | + SprintSpecification) |
458 | for sprint_link in self.sprint_links: |
459 | # sprints have unique names |
460 | if sprint_link.sprint.name == sprint.name: |
461 | @@ -889,6 +891,7 @@ |
462 | @property |
463 | def coming_sprints(self): |
464 | """See ISpecificationSet.""" |
465 | + from lp.blueprints.model.sprint import Sprint |
466 | return Sprint.select("time_ends > 'NOW'", orderBy='time_starts', |
467 | limit=5) |
468 | |
469 | |
470 | === modified file 'lib/lp/blueprints/model/sprint.py' |
471 | --- lib/lp/blueprints/model/sprint.py 2010-11-24 11:12:51 +0000 |
472 | +++ lib/lp/blueprints/model/sprint.py 2010-11-26 18:08:22 +0000 |
473 | @@ -42,13 +42,14 @@ |
474 | ISprint, |
475 | ISprintSet, |
476 | ) |
477 | +from lp.blueprints.model.specification import HasSpecificationsMixin |
478 | from lp.blueprints.model.sprintattendance import SprintAttendance |
479 | from lp.blueprints.model.sprintspecification import SprintSpecification |
480 | from lp.registry.interfaces.person import validate_public_person |
481 | from lp.registry.model.hasdrivers import HasDriversMixin |
482 | |
483 | |
484 | -class Sprint(SQLBase, HasDriversMixin): |
485 | +class Sprint(SQLBase, HasDriversMixin, HasSpecificationsMixin): |
486 | """See `ISprint`.""" |
487 | |
488 | implements(ISprint, IHasLogo, IHasMugshot, IHasIcon) |
489 | @@ -93,7 +94,7 @@ |
490 | """See IHasDrivers.""" |
491 | if self.driver is not None: |
492 | return [self.driver, self.owner] |
493 | - return [self.owner,] |
494 | + return [self.owner] |
495 | |
496 | # useful joins |
497 | attendees = SQLRelatedJoin('Person', |
498 | @@ -141,7 +142,7 @@ |
499 | |
500 | # filter based on completion. see the implementation of |
501 | # Specification.is_complete() for more details |
502 | - completeness = Specification.completeness_clause |
503 | + completeness = Specification.completeness_clause |
504 | |
505 | if SpecificationFilter.COMPLETE in filter: |
506 | query += ' AND ( %s ) ' % completeness |
507 | |
508 | === modified file 'lib/lp/blueprints/stories/standalone/sprint-links.txt' |
509 | --- lib/lp/blueprints/stories/standalone/sprint-links.txt 2009-09-22 10:48:09 +0000 |
510 | +++ lib/lp/blueprints/stories/standalone/sprint-links.txt 2010-11-26 18:08:22 +0000 |
511 | @@ -13,10 +13,6 @@ |
512 | >>> browser.open('http://blueprints.launchpad.dev/firefox/+spec/canvas') |
513 | >>> browser.isHtml |
514 | True |
515 | - >>> 'Accepted' in browser.contents # make sure the page is not polluted |
516 | - False |
517 | - >>> 'Proposed' in browser.contents # make sure the page is not polluted |
518 | - False |
519 | |
520 | Then we are going to propose it for the meeting agenda: |
521 | |
522 | |
523 | === added file 'lib/lp/blueprints/tests/test_implements.py' |
524 | --- lib/lp/blueprints/tests/test_implements.py 1970-01-01 00:00:00 +0000 |
525 | +++ lib/lp/blueprints/tests/test_implements.py 2010-11-26 18:08:22 +0000 |
526 | @@ -0,0 +1,65 @@ |
527 | +# Copyright 2009 Canonical Ltd. This software is licensed under the |
528 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
529 | + |
530 | +"""Tests that various objects implement specification-related interfaces.""" |
531 | + |
532 | +__metaclass__ = type |
533 | + |
534 | +from canonical.testing import DatabaseFunctionalLayer |
535 | +from lp.blueprints.interfaces.specificationtarget import ( |
536 | + IHasSpecifications, ISpecificationTarget) |
537 | +from lp.testing import TestCaseWithFactory |
538 | + |
539 | + |
540 | +class ImplementsIHasSpecificationsTests(TestCaseWithFactory): |
541 | + """Test that various objects implement IHasSpecifications.""" |
542 | + layer = DatabaseFunctionalLayer |
543 | + |
544 | + def test_product_implements_IHasSpecifications(self): |
545 | + product = self.factory.makeProduct() |
546 | + self.assertProvides(product, IHasSpecifications) |
547 | + |
548 | + def test_distribution_implements_IHasSpecifications(self): |
549 | + product = self.factory.makeProduct() |
550 | + self.assertProvides(product, IHasSpecifications) |
551 | + |
552 | + def test_projectgroup_implements_IHasSpecifications(self): |
553 | + projectgroup = self.factory.makeProject() |
554 | + self.assertProvides(projectgroup, IHasSpecifications) |
555 | + |
556 | + def test_person_implements_IHasSpecifications(self): |
557 | + person = self.factory.makePerson() |
558 | + self.assertProvides(person, IHasSpecifications) |
559 | + |
560 | + def test_productseries_implements_IHasSpecifications(self): |
561 | + productseries = self.factory.makeProductSeries() |
562 | + self.assertProvides(productseries, IHasSpecifications) |
563 | + |
564 | + def test_distroseries_implements_IHasSpecifications(self): |
565 | + distroseries = self.factory.makeDistroSeries() |
566 | + self.assertProvides(distroseries, IHasSpecifications) |
567 | + |
568 | + def test_sprint_implements_IHasSpecifications(self): |
569 | + sprint = self.factory.makeSprint() |
570 | + self.assertProvides(sprint, IHasSpecifications) |
571 | + |
572 | + |
573 | +class ImplementsISpecificationTargetTests(TestCaseWithFactory): |
574 | + """Test that various objects implement ISpecificationTarget.""" |
575 | + layer = DatabaseFunctionalLayer |
576 | + |
577 | + def test_product_implements_ISpecificationTarget(self): |
578 | + product = self.factory.makeProduct() |
579 | + self.assertProvides(product, ISpecificationTarget) |
580 | + |
581 | + def test_distribution_implements_ISpecificationTarget(self): |
582 | + product = self.factory.makeProduct() |
583 | + self.assertProvides(product, ISpecificationTarget) |
584 | + |
585 | + def test_productseries_implements_ISpecificationTarget(self): |
586 | + productseries = self.factory.makeProductSeries() |
587 | + self.assertProvides(productseries, ISpecificationTarget) |
588 | + |
589 | + def test_distroseries_implements_ISpecificationTarget(self): |
590 | + distroseries = self.factory.makeDistroSeries() |
591 | + self.assertProvides(distroseries, ISpecificationTarget) |
592 | |
593 | === added file 'lib/lp/blueprints/tests/test_webservice.py' |
594 | --- lib/lp/blueprints/tests/test_webservice.py 1970-01-01 00:00:00 +0000 |
595 | +++ lib/lp/blueprints/tests/test_webservice.py 2010-11-26 18:08:22 +0000 |
596 | @@ -0,0 +1,395 @@ |
597 | +# Copyright 2009 Canonical Ltd. This software is licensed under the |
598 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
599 | + |
600 | +"""Webservice unit tests related to Launchpad blueprints.""" |
601 | + |
602 | +__metaclass__ = type |
603 | + |
604 | +from canonical.testing import DatabaseFunctionalLayer |
605 | +from canonical.launchpad.testing.pages import webservice_for_person |
606 | +from lp.blueprints.interfaces.specification import ( |
607 | + SpecificationDefinitionStatus, |
608 | + ) |
609 | +from lp.testing import ( |
610 | + launchpadlib_for, TestCaseWithFactory) |
611 | + |
612 | + |
613 | +class SpecificationWebserviceTestCase(TestCaseWithFactory): |
614 | + |
615 | + def getLaunchpadlib(self): |
616 | + user = self.factory.makePerson() |
617 | + return launchpadlib_for("testing", user, version='devel') |
618 | + |
619 | + def getSpecOnWebservice(self, spec_object): |
620 | + launchpadlib = self.getLaunchpadlib() |
621 | + return launchpadlib.load( |
622 | + '/%s/+spec/%s' % (spec_object.target.name, spec_object.name)) |
623 | + |
624 | + def getPillarOnWebservice(self, pillar_obj): |
625 | + # XXX: 2010-11-26, salgado, bug=681767: Can't use relative URLs here. |
626 | + launchpadlib = self.getLaunchpadlib() |
627 | + return launchpadlib.load( |
628 | + str(launchpadlib._root_uri) + '/' + pillar_obj.name) |
629 | + |
630 | + |
631 | +class SpecificationAttributeWebserviceTests(SpecificationWebserviceTestCase): |
632 | + """Test accessing specification attributes over the webservice.""" |
633 | + layer = DatabaseFunctionalLayer |
634 | + |
635 | + def test_representation_is_empty_on_1_dot_0(self): |
636 | + # ISpecification is exposed on the 1.0 version so that they can be |
637 | + # linked against branches, but none of its fields is exposed on that |
638 | + # version as we expect it to undergo significant refactorings before |
639 | + # it's ready for prime time. |
640 | + spec = self.factory.makeSpecification() |
641 | + user = self.factory.makePerson() |
642 | + webservice = webservice_for_person(user) |
643 | + response = webservice.get( |
644 | + '/%s/+spec/%s' % (spec.product.name, spec.name)) |
645 | + expected_keys = sorted( |
646 | + [u'self_link', u'http_etag', u'resource_type_link']) |
647 | + self.assertEqual(response.status, 200) |
648 | + self.assertEqual(sorted(response.jsonBody().keys()), expected_keys) |
649 | + |
650 | + def test_representation_contains_name(self): |
651 | + spec = self.factory.makeSpecification() |
652 | + spec_webservice = self.getSpecOnWebservice(spec) |
653 | + self.assertEqual(spec.name, spec_webservice.name) |
654 | + |
655 | + def test_representation_contains_target(self): |
656 | + spec = self.factory.makeSpecification( |
657 | + product=self.factory.makeProduct()) |
658 | + spec_webservice = self.getSpecOnWebservice(spec) |
659 | + self.assertEqual(spec.target.name, spec_webservice.target.name) |
660 | + |
661 | + def test_representation_contains_title(self): |
662 | + spec = self.factory.makeSpecification(title='Foo') |
663 | + spec_webservice = self.getSpecOnWebservice(spec) |
664 | + self.assertEqual(spec.title, spec_webservice.title) |
665 | + |
666 | + def test_representation_contains_specification_url(self): |
667 | + spec = self.factory.makeSpecification(specurl='http://example.com') |
668 | + spec_webservice = self.getSpecOnWebservice(spec) |
669 | + self.assertEqual(spec.specurl, spec_webservice.specification_url) |
670 | + |
671 | + def test_representation_contains_summary(self): |
672 | + spec = self.factory.makeSpecification(summary='Foo') |
673 | + spec_webservice = self.getSpecOnWebservice(spec) |
674 | + self.assertEqual(spec.summary, spec_webservice.summary) |
675 | + |
676 | + def test_representation_contains_implementation_status(self): |
677 | + spec = self.factory.makeSpecification() |
678 | + spec_webservice = self.getSpecOnWebservice(spec) |
679 | + self.assertEqual( |
680 | + spec.implementation_status.title, |
681 | + spec_webservice.implementation_status) |
682 | + |
683 | + def test_representation_contains_definition_status(self): |
684 | + spec = self.factory.makeSpecification() |
685 | + spec_webservice = self.getSpecOnWebservice(spec) |
686 | + self.assertEqual( |
687 | + spec.definition_status.title, spec_webservice.definition_status) |
688 | + |
689 | + def test_representation_contains_assignee(self): |
690 | + # Hard-code the person's name or else we'd need to set up a zope |
691 | + # interaction as IPerson.name is protected. |
692 | + spec = self.factory.makeSpecification( |
693 | + assignee=self.factory.makePerson(name='test-person')) |
694 | + spec_webservice = self.getSpecOnWebservice(spec) |
695 | + self.assertEqual('test-person', spec_webservice.assignee.name) |
696 | + |
697 | + def test_representation_contains_drafter(self): |
698 | + spec = self.factory.makeSpecification( |
699 | + drafter=self.factory.makePerson(name='test-person')) |
700 | + spec_webservice = self.getSpecOnWebservice(spec) |
701 | + self.assertEqual('test-person', spec_webservice.drafter.name) |
702 | + |
703 | + def test_representation_contains_approver(self): |
704 | + spec = self.factory.makeSpecification( |
705 | + approver=self.factory.makePerson(name='test-person')) |
706 | + spec_webservice = self.getSpecOnWebservice(spec) |
707 | + self.assertEqual('test-person', spec_webservice.approver.name) |
708 | + |
709 | + def test_representation_contains_owner(self): |
710 | + spec = self.factory.makeSpecification( |
711 | + owner=self.factory.makePerson(name='test-person')) |
712 | + spec_webservice = self.getSpecOnWebservice(spec) |
713 | + self.assertEqual('test-person', spec_webservice.owner.name) |
714 | + |
715 | + def test_representation_contains_priority(self): |
716 | + spec = self.factory.makeSpecification() |
717 | + spec_webservice = self.getSpecOnWebservice(spec) |
718 | + self.assertEqual(spec.priority.title, spec_webservice.priority) |
719 | + |
720 | + def test_representation_contains_date_created(self): |
721 | + spec = self.factory.makeSpecification() |
722 | + spec_webservice = self.getSpecOnWebservice(spec) |
723 | + self.assertEqual(spec.datecreated, spec_webservice.date_created) |
724 | + |
725 | + def test_representation_contains_whiteboard(self): |
726 | + spec = self.factory.makeSpecification(whiteboard='Test') |
727 | + spec_webservice = self.getSpecOnWebservice(spec) |
728 | + self.assertEqual(spec.whiteboard, spec_webservice.whiteboard) |
729 | + |
730 | + def test_representation_contains_milestone(self): |
731 | + product = self.factory.makeProduct() |
732 | + productseries = self.factory.makeProductSeries(product=product) |
733 | + milestone = self.factory.makeMilestone( |
734 | + name="1.0", product=product, productseries=productseries) |
735 | + spec_object = self.factory.makeSpecification( |
736 | + product=product, goal=productseries, milestone=milestone) |
737 | + spec = self.getSpecOnWebservice(spec_object) |
738 | + self.assertEqual("1.0", spec.milestone.name) |
739 | + |
740 | + |
741 | +class SpecificationTargetTests(SpecificationWebserviceTestCase): |
742 | + """Tests for accessing specifications via their targets.""" |
743 | + layer = DatabaseFunctionalLayer |
744 | + |
745 | + def test_get_specification_on_product(self): |
746 | + product = self.factory.makeProduct(name="fooix") |
747 | + spec_object = self.factory.makeSpecification( |
748 | + product=product, name="some-spec") |
749 | + product_on_webservice = self.getPillarOnWebservice(product) |
750 | + spec = product_on_webservice.getSpecification(name="some-spec") |
751 | + self.assertEqual("some-spec", spec.name) |
752 | + self.assertEqual("fooix", spec.target.name) |
753 | + |
754 | + def test_get_specification_on_distribution(self): |
755 | + distribution = self.factory.makeDistribution(name="foobuntu") |
756 | + spec_object = self.factory.makeSpecification( |
757 | + distribution=distribution, name="some-spec") |
758 | + distro_on_webservice = self.getPillarOnWebservice(distribution) |
759 | + spec = distro_on_webservice.getSpecification(name="some-spec") |
760 | + self.assertEqual("some-spec", spec.name) |
761 | + self.assertEqual("foobuntu", spec.target.name) |
762 | + |
763 | + def test_get_specification_on_productseries(self): |
764 | + product = self.factory.makeProduct(name="fooix") |
765 | + productseries = self.factory.makeProductSeries( |
766 | + product=product, name="fooix-dev") |
767 | + spec_object = self.factory.makeSpecification( |
768 | + product=product, name="some-spec", goal=productseries) |
769 | + product_on_webservice = self.getPillarOnWebservice(product) |
770 | + productseries_on_webservice = product_on_webservice.getSeries( |
771 | + name="fooix-dev") |
772 | + spec = productseries_on_webservice.getSpecification(name="some-spec") |
773 | + self.assertEqual("some-spec", spec.name) |
774 | + self.assertEqual("fooix", spec.target.name) |
775 | + |
776 | + def test_get_specification_on_distroseries(self): |
777 | + distribution = self.factory.makeDistribution(name="foobuntu") |
778 | + distroseries = self.factory.makeDistroSeries( |
779 | + distribution=distribution, name="maudlin") |
780 | + spec_object = self.factory.makeSpecification( |
781 | + distribution=distribution, name="some-spec", |
782 | + goal=distroseries) |
783 | + distro_on_webservice = self.getPillarOnWebservice(distribution) |
784 | + distroseries_on_webservice = distro_on_webservice.getSeries( |
785 | + name_or_version="maudlin") |
786 | + spec = distroseries_on_webservice.getSpecification(name="some-spec") |
787 | + self.assertEqual("some-spec", spec.name) |
788 | + self.assertEqual("foobuntu", spec.target.name) |
789 | + |
790 | + def test_get_specification_not_found(self): |
791 | + product = self.factory.makeProduct() |
792 | + product_on_webservice = self.getPillarOnWebservice(product) |
793 | + spec = product_on_webservice.getSpecification(name="nonexistant") |
794 | + self.assertEqual(None, spec) |
795 | + |
796 | + |
797 | +class IHasSpecificationsTests(SpecificationWebserviceTestCase): |
798 | + """Tests for accessing IHasSpecifications methods over the webservice.""" |
799 | + layer = DatabaseFunctionalLayer |
800 | + |
801 | + def assertNamesOfSpecificationsAre(self, expected_names, specifications): |
802 | + names = [s.name for s in specifications] |
803 | + self.assertEqual(sorted(expected_names), sorted(names)) |
804 | + |
805 | + def test_product_all_specifications(self): |
806 | + product = self.factory.makeProduct() |
807 | + self.factory.makeSpecification(product=product, name="spec1") |
808 | + self.factory.makeSpecification(product=product, name="spec2") |
809 | + product_on_webservice = self.getPillarOnWebservice(product) |
810 | + self.assertNamesOfSpecificationsAre( |
811 | + ["spec1", "spec2"], product_on_webservice.all_specifications) |
812 | + |
813 | + def test_product_valid_specifications(self): |
814 | + product = self.factory.makeProduct() |
815 | + self.factory.makeSpecification(product=product, name="spec1") |
816 | + self.factory.makeSpecification( |
817 | + product=product, name="spec2", |
818 | + status=SpecificationDefinitionStatus.OBSOLETE) |
819 | + product_on_webservice = self.getPillarOnWebservice(product) |
820 | + self.assertNamesOfSpecificationsAre( |
821 | + ["spec1"], product_on_webservice.valid_specifications) |
822 | + |
823 | + def test_distribution_all_specifications(self): |
824 | + distribution = self.factory.makeDistribution() |
825 | + self.factory.makeSpecification( |
826 | + distribution=distribution, name="spec1") |
827 | + self.factory.makeSpecification( |
828 | + distribution=distribution, name="spec2") |
829 | + distro_on_webservice = self.getPillarOnWebservice(distribution) |
830 | + self.assertNamesOfSpecificationsAre( |
831 | + ["spec1", "spec2"], distro_on_webservice.all_specifications) |
832 | + |
833 | + def test_distribution_valid_specifications(self): |
834 | + distribution = self.factory.makeDistribution() |
835 | + self.factory.makeSpecification( |
836 | + distribution=distribution, name="spec1") |
837 | + self.factory.makeSpecification( |
838 | + distribution=distribution, name="spec2", |
839 | + status=SpecificationDefinitionStatus.OBSOLETE) |
840 | + distro_on_webservice = self.getPillarOnWebservice(distribution) |
841 | + self.assertNamesOfSpecificationsAre( |
842 | + ["spec1"], distro_on_webservice.valid_specifications) |
843 | + |
844 | + def test_distroseries_all_specifications(self): |
845 | + distribution = self.factory.makeDistribution() |
846 | + distroseries = self.factory.makeDistroSeries( |
847 | + name='maudlin', distribution=distribution) |
848 | + self.factory.makeSpecification( |
849 | + distribution=distribution, name="spec1", |
850 | + goal=distroseries) |
851 | + self.factory.makeSpecification( |
852 | + distribution=distribution, name="spec2", |
853 | + goal=distroseries) |
854 | + self.factory.makeSpecification( |
855 | + distribution=distribution, name="spec3") |
856 | + distro_on_webservice = self.getPillarOnWebservice(distribution) |
857 | + distroseries_on_webservice = distro_on_webservice.getSeries( |
858 | + name_or_version="maudlin") |
859 | + self.assertNamesOfSpecificationsAre( |
860 | + ["spec1", "spec2"], |
861 | + distroseries_on_webservice.all_specifications) |
862 | + |
863 | + # XXX: salgado, 2010-11-25, bug=681432: Test disabled because |
864 | + # DistroSeries.valid_specifications is broken. |
865 | + def disabled_test_distroseries_valid_specifications(self): |
866 | + distribution = self.factory.makeDistribution() |
867 | + distroseries = self.factory.makeDistroSeries( |
868 | + name='maudlin', distribution=distribution) |
869 | + self.factory.makeSpecification( |
870 | + distribution=distribution, name="spec1", |
871 | + goal=distroseries) |
872 | + self.factory.makeSpecification( |
873 | + distribution=distribution, name="spec2", |
874 | + goal=distroseries) |
875 | + self.factory.makeSpecification( |
876 | + distribution=distribution, name="spec3", |
877 | + goal=distroseries, |
878 | + status=SpecificationDefinitionStatus.OBSOLETE) |
879 | + self.factory.makeSpecification( |
880 | + distribution=distribution, name="spec4") |
881 | + distro_on_webservice = self.getPillarOnWebservice(distribution) |
882 | + distroseries_on_webservice = distro_on_webservice.getSeries( |
883 | + name_or_version="maudlin") |
884 | + self.assertNamesOfSpecificationsAre( |
885 | + ["spec1", "spec2"], |
886 | + distroseries_on_webservice.valid_specifications) |
887 | + |
888 | + def test_productseries_all_specifications(self): |
889 | + product = self.factory.makeProduct() |
890 | + productseries = self.factory.makeProductSeries( |
891 | + product=product, name="fooix-dev") |
892 | + self.factory.makeSpecification( |
893 | + product=product, name="spec1", goal=productseries) |
894 | + self.factory.makeSpecification( |
895 | + product=product, name="spec2", goal=productseries) |
896 | + self.factory.makeSpecification(product=product, name="spec3") |
897 | + product_on_webservice = self.getPillarOnWebservice(product) |
898 | + series_on_webservice = product_on_webservice.getSeries( |
899 | + name="fooix-dev") |
900 | + self.assertNamesOfSpecificationsAre( |
901 | + ["spec1", "spec2"], series_on_webservice.all_specifications) |
902 | + |
903 | + def test_productseries_valid_specifications(self): |
904 | + product = self.factory.makeProduct() |
905 | + productseries = self.factory.makeProductSeries( |
906 | + product=product, name="fooix-dev") |
907 | + self.factory.makeSpecification( |
908 | + product=product, name="spec1", goal=productseries) |
909 | + self.factory.makeSpecification( |
910 | + product=product, name="spec2", goal=productseries) |
911 | + self.factory.makeSpecification( |
912 | + product=product, name="spec3", goal=productseries, |
913 | + status=SpecificationDefinitionStatus.OBSOLETE) |
914 | + self.factory.makeSpecification(product=product, name="spec4") |
915 | + product_on_webservice = self.getPillarOnWebservice(product) |
916 | + series_on_webservice = product_on_webservice.getSeries( |
917 | + name="fooix-dev") |
918 | + # Should this be different to the results for distroseries? |
919 | + self.assertNamesOfSpecificationsAre( |
920 | + ["spec1", "spec2"], |
921 | + series_on_webservice.valid_specifications) |
922 | + |
923 | + def test_projectgroup_all_specifications(self): |
924 | + projectgroup = self.factory.makeProject() |
925 | + other_projectgroup = self.factory.makeProject() |
926 | + product1 = self.factory.makeProduct(project=projectgroup) |
927 | + product2 = self.factory.makeProduct(project=projectgroup) |
928 | + product3 = self.factory.makeProduct(project=other_projectgroup) |
929 | + self.factory.makeSpecification( |
930 | + product=product1, name="spec1") |
931 | + self.factory.makeSpecification( |
932 | + product=product2, name="spec2", |
933 | + status=SpecificationDefinitionStatus.OBSOLETE) |
934 | + self.factory.makeSpecification( |
935 | + product=product3, name="spec3") |
936 | + projectgroup_on_webservice = self.getPillarOnWebservice(projectgroup) |
937 | + # Should this be different to the results for distroseries? |
938 | + self.assertNamesOfSpecificationsAre( |
939 | + ["spec1", "spec2"], |
940 | + projectgroup_on_webservice.all_specifications) |
941 | + |
942 | + def test_projectgroup_valid_specifications(self): |
943 | + projectgroup = self.factory.makeProject() |
944 | + other_projectgroup = self.factory.makeProject() |
945 | + product1 = self.factory.makeProduct(project=projectgroup) |
946 | + product2 = self.factory.makeProduct(project=projectgroup) |
947 | + product3 = self.factory.makeProduct(project=other_projectgroup) |
948 | + self.factory.makeSpecification( |
949 | + product=product1, name="spec1") |
950 | + self.factory.makeSpecification( |
951 | + product=product2, name="spec2", |
952 | + status=SpecificationDefinitionStatus.OBSOLETE) |
953 | + self.factory.makeSpecification( |
954 | + product=product3, name="spec3") |
955 | + projectgroup_on_webservice = self.getPillarOnWebservice(projectgroup) |
956 | + # Should this be different to the results for distroseries? |
957 | + self.assertNamesOfSpecificationsAre( |
958 | + ["spec1", "spec2"], |
959 | + projectgroup_on_webservice.valid_specifications) |
960 | + |
961 | + def test_person_all_specifications(self): |
962 | + person = self.factory.makePerson(name="james-w") |
963 | + product = self.factory.makeProduct() |
964 | + self.factory.makeSpecification( |
965 | + product=product, name="spec1", drafter=person) |
966 | + self.factory.makeSpecification( |
967 | + product=product, name="spec2", approver=person, |
968 | + status=SpecificationDefinitionStatus.OBSOLETE) |
969 | + self.factory.makeSpecification( |
970 | + product=product, name="spec3") |
971 | + launchpadlib = self.getLaunchpadlib() |
972 | + person_on_webservice = launchpadlib.load( |
973 | + str(launchpadlib._root_uri) + '/~james-w') |
974 | + self.assertNamesOfSpecificationsAre( |
975 | + ["spec1", "spec2"], person_on_webservice.all_specifications) |
976 | + |
977 | + def test_person_valid_specifications(self): |
978 | + person = self.factory.makePerson(name="james-w") |
979 | + product = self.factory.makeProduct() |
980 | + self.factory.makeSpecification( |
981 | + product=product, name="spec1", drafter=person) |
982 | + self.factory.makeSpecification( |
983 | + product=product, name="spec2", approver=person, |
984 | + status=SpecificationDefinitionStatus.OBSOLETE) |
985 | + self.factory.makeSpecification( |
986 | + product=product, name="spec3") |
987 | + launchpadlib = self.getLaunchpadlib() |
988 | + person_on_webservice = launchpadlib.load( |
989 | + str(launchpadlib._root_uri) + '/~james-w') |
990 | + self.assertNamesOfSpecificationsAre( |
991 | + ["spec1"], person_on_webservice.valid_specifications) |
992 | |
993 | === modified file 'lib/lp/testing/factory.py' |
994 | --- lib/lp/testing/factory.py 2010-11-18 12:05:34 +0000 |
995 | +++ lib/lp/testing/factory.py 2010-11-26 18:08:22 +0000 |
996 | @@ -102,7 +102,11 @@ |
997 | from lp.app.enums import ServiceUsage |
998 | from lp.archiveuploader.dscfile import DSCFile |
999 | from lp.archiveuploader.uploadpolicy import BuildDaemonUploadPolicy |
1000 | -from lp.blueprints.enums import SpecificationDefinitionStatus |
1001 | +from lp.blueprints.enums import ( |
1002 | + SpecificationDefinitionStatus, |
1003 | + SpecificationGoalStatus, |
1004 | + SpecificationPriority, |
1005 | + ) |
1006 | from lp.blueprints.interfaces.specification import ISpecificationSet |
1007 | from lp.blueprints.interfaces.sprint import ISprintSet |
1008 | from lp.bugs.interfaces.bug import ( |
1009 | @@ -1671,7 +1675,9 @@ |
1010 | def makeSpecification(self, product=None, title=None, distribution=None, |
1011 | name=None, summary=None, owner=None, |
1012 | status=SpecificationDefinitionStatus.NEW, |
1013 | - implementation_status=None): |
1014 | + implementation_status=None, goal=None, specurl=None, |
1015 | + assignee=None, drafter=None, approver=None, |
1016 | + priority=None, whiteboard=None, milestone=None): |
1017 | """Create and return a new, arbitrary Blueprint. |
1018 | |
1019 | :param product: The product to make the blueprint on. If one is |
1020 | @@ -1687,17 +1693,32 @@ |
1021 | title = self.getUniqueString('title') |
1022 | if owner is None: |
1023 | owner = self.makePerson() |
1024 | + if priority is None: |
1025 | + priority = SpecificationPriority.UNDEFINED |
1026 | spec = getUtility(ISpecificationSet).new( |
1027 | name=name, |
1028 | title=title, |
1029 | specurl=None, |
1030 | summary=summary, |
1031 | definition_status=status, |
1032 | + whiteboard=whiteboard, |
1033 | owner=owner, |
1034 | + assignee=assignee, |
1035 | + drafter=drafter, |
1036 | + approver=approver, |
1037 | product=product, |
1038 | - distribution=distribution) |
1039 | + distribution=distribution, |
1040 | + priority=priority) |
1041 | + naked_spec = removeSecurityProxy(spec) |
1042 | + if status == SpecificationDefinitionStatus.OBSOLETE: |
1043 | + # This is to satisfy a DB constraint of obsolete specs. |
1044 | + naked_spec.completer = owner |
1045 | + naked_spec.date_completed = datetime.now(pytz.UTC) |
1046 | + naked_spec.specurl = specurl |
1047 | + naked_spec.milestone = milestone |
1048 | + if goal is not None: |
1049 | + naked_spec.proposeGoal(goal, spec.target.owner) |
1050 | if implementation_status is not None: |
1051 | - naked_spec = removeSecurityProxy(spec) |
1052 | naked_spec.implementation_status = implementation_status |
1053 | naked_spec.updateLifecycleStatus(owner) |
1054 | return spec |
My last commit removes another controversial bit by exposing ISpecification. definition_ status as read only. That attribute will probably need a mutator (which in turn requires some refactoring) before it can be exported.