Merge lp:~wgrant/launchpad/blueprintsproduct into lp:launchpad

Proposed by William Grant on 2014-03-24
Status: Work in progress
Proposed branch: lp:~wgrant/launchpad/blueprintsproduct
Merge into: lp:launchpad
Diff against target: 2393 lines (+696/-409)
44 files modified
lib/lp/_schema_circular_imports.py (+5/-5)
lib/lp/blueprints/browser/person.py (+7/-2)
lib/lp/blueprints/browser/specification.py (+12/-5)
lib/lp/blueprints/browser/specificationgoal.py (+6/-2)
lib/lp/blueprints/browser/specificationtarget.py (+15/-6)
lib/lp/blueprints/browser/tests/test_specification.py (+8/-3)
lib/lp/blueprints/configure.zcml (+56/-0)
lib/lp/blueprints/doc/specgraph.txt (+5/-1)
lib/lp/blueprints/doc/specification-branch.txt (+5/-1)
lib/lp/blueprints/doc/specification-notifications.txt (+5/-1)
lib/lp/blueprints/doc/specification.txt (+8/-6)
lib/lp/blueprints/doc/sprint-agenda.txt (+5/-1)
lib/lp/blueprints/doc/sprint-meeting-export.txt (+7/-3)
lib/lp/blueprints/interfaces/specification.py (+7/-3)
lib/lp/blueprints/interfaces/specificationtarget.py (+20/-12)
lib/lp/blueprints/interfaces/sprint.py (+6/-2)
lib/lp/blueprints/interfaces/targetadapters.py (+36/-0)
lib/lp/blueprints/interfaces/webservice.py (+7/-0)
lib/lp/blueprints/model/specification.py (+8/-2)
lib/lp/blueprints/model/targetadapters.py (+268/-0)
lib/lp/blueprints/tests/test_hasspecifications.py (+28/-13)
lib/lp/blueprints/tests/test_specification.py (+4/-1)
lib/lp/blueprints/vocabularies/specification.py (+6/-2)
lib/lp/blueprints/vocabularies/specificationdependency.py (+7/-2)
lib/lp/registry/browser/distribution.py (+5/-1)
lib/lp/registry/browser/product.py (+5/-1)
lib/lp/registry/configure.zcml (+0/-15)
lib/lp/registry/interfaces/distribution.py (+1/-3)
lib/lp/registry/interfaces/distroseries.py (+1/-2)
lib/lp/registry/interfaces/person.py (+1/-6)
lib/lp/registry/interfaces/product.py (+1/-3)
lib/lp/registry/interfaces/productseries.py (+3/-4)
lib/lp/registry/interfaces/projectgroup.py (+3/-6)
lib/lp/registry/model/distribution.py (+1/-36)
lib/lp/registry/model/distroseries.py (+1/-28)
lib/lp/registry/model/person.py (+9/-66)
lib/lp/registry/model/product.py (+5/-30)
lib/lp/registry/model/productseries.py (+2/-29)
lib/lp/registry/model/projectgroup.py (+3/-32)
lib/lp/registry/tests/test_distribution.py (+7/-3)
lib/lp/registry/tests/test_distroseries.py (+5/-1)
lib/lp/registry/tests/test_person.py (+48/-45)
lib/lp/registry/tests/test_product.py (+29/-25)
lib/lp/security.py (+25/-0)
To merge this branch: bzr merge lp:~wgrant/launchpad/blueprintsproduct
Reviewer Review Type Date Requested Status
Launchpad code reviewers 2014-03-24 Pending
Review via email: mp+212382@code.launchpad.net
To post a comment you must log in.
16971. By William Grant on 2014-03-24

Add missed file.

16972. By William Grant on 2014-03-28

Apply appropriate security adapters to the IHasSpecificationsOperations adapters.

16973. By William Grant on 2014-03-28

Fix BlueprintsProjectGroup(Series).

16974. By William Grant on 2014-03-28

Fix findVisibleAssignedInProgressSpecs.

16975. By William Grant on 2014-03-28

Fix empty filter to be the right type.

16976. By William Grant on 2014-03-28

Port test_person.TestSpecifications to the new adapter.

16977. By William Grant on 2014-03-28

Fix test_product.TestSpecifications.

16978. By William Grant on 2014-03-28

Fix get(Allowed|Default)InformationTypes? tests to adapt.

Unmerged revisions

16978. By William Grant on 2014-03-28

Fix get(Allowed|Default)InformationTypes? tests to adapt.

16977. By William Grant on 2014-03-28

Fix test_product.TestSpecifications.

16976. By William Grant on 2014-03-28

Port test_person.TestSpecifications to the new adapter.

16975. By William Grant on 2014-03-28

Fix empty filter to be the right type.

16974. By William Grant on 2014-03-28

Fix findVisibleAssignedInProgressSpecs.

16973. By William Grant on 2014-03-28

Fix BlueprintsProjectGroup(Series).

16972. By William Grant on 2014-03-28

Apply appropriate security adapters to the IHasSpecificationsOperations adapters.

16971. By William Grant on 2014-03-24

Add missed file.

16970. By William Grant on 2014-03-24

Drop temporary IBlueprints* webservice interfaces.

16969. By William Grant on 2014-03-24

Factor out common bits of the blueprint target adapters.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/_schema_circular_imports.py'
2--- lib/lp/_schema_circular_imports.py 2014-03-11 10:29:43 +0000
3+++ lib/lp/_schema_circular_imports.py 2014-03-28 05:15:47 +0000
4@@ -21,8 +21,8 @@
5 from lp.blueprints.interfaces.specification import ISpecification
6 from lp.blueprints.interfaces.specificationbranch import ISpecificationBranch
7 from lp.blueprints.interfaces.specificationtarget import (
8- IHasSpecifications,
9- ISpecificationTarget,
10+ IHasSpecificationsOperations,
11+ ISpecificationTargetOperations,
12 )
13 from lp.bugs.enums import BugNotificationLevel
14 from lp.bugs.interfaces.bug import (
15@@ -732,13 +732,13 @@
16
17 # ISpecificationTarget
18 patch_entry_return_type(
19- ISpecificationTarget, 'getSpecification', ISpecification)
20+ ISpecificationTargetOperations, 'getSpecification', ISpecification)
21
22 # IHasSpecifications
23 patch_collection_property(
24- IHasSpecifications, 'visible_specifications', ISpecification)
25+ IHasSpecificationsOperations, 'visible_specifications', ISpecification)
26 patch_collection_property(
27- IHasSpecifications, 'api_valid_specifications', ISpecification)
28+ IHasSpecificationsOperations, 'api_valid_specifications', ISpecification)
29
30
31 ###
32
33=== modified file 'lib/lp/blueprints/browser/person.py'
34--- lib/lp/blueprints/browser/person.py 2013-01-07 02:40:55 +0000
35+++ lib/lp/blueprints/browser/person.py 2014-03-28 05:15:47 +0000
36@@ -8,6 +8,9 @@
37 'PersonSpecWorkloadView',
38 ]
39
40+from lp.blueprints.interfaces.specificationtarget import (
41+ IHasSpecificationsOperations,
42+ )
43 from lp.registry.interfaces.person import IPerson
44 from lp.services.propertycache import cachedproperty
45 from lp.services.webapp import (
46@@ -79,7 +82,8 @@
47 return batch_nav
48
49 def specifications(self):
50- return self.context.specifications(self.user)
51+ return IHasSpecificationsOperations(self.context).specifications(
52+ self.user)
53
54
55 class PersonSpecWorkloadTableView(LaunchpadView):
56@@ -109,4 +113,5 @@
57 approver, the assignee or the drafter.
58 """
59 return [PersonSpecWorkloadTableView.PersonSpec(spec, self.context)
60- for spec in self.context.specifications(self.user)]
61+ for spec in IHasSpecificationsOperations(
62+ self.context).specifications(self.user)]
63
64=== modified file 'lib/lp/blueprints/browser/specification.py'
65--- lib/lp/blueprints/browser/specification.py 2014-01-04 18:45:58 +0000
66+++ lib/lp/blueprints/browser/specification.py 2014-03-28 05:15:47 +0000
67@@ -119,6 +119,9 @@
68 ISpecificationSet,
69 )
70 from lp.blueprints.interfaces.specificationbranch import ISpecificationBranch
71+from lp.blueprints.interfaces.specificationtarget import (
72+ ISpecificationTargetOperations,
73+ )
74 from lp.blueprints.interfaces.sprintspecification import ISprintSpecification
75 from lp.code.interfaces.branchnamespace import IBranchNamespaceSet
76 from lp.registry.interfaces.distribution import IDistribution
77@@ -236,7 +239,8 @@
78 IProduct.providedBy(self.context) or
79 IProductSeries.providedBy(self.context)):
80 information_type = (
81- self.context.getDefaultSpecificationInformationType())
82+ ISpecificationTargetOperations(
83+ self.context).getDefaultSpecificationInformationType())
84 spec = getUtility(ISpecificationSet).new(
85 owner=self.user,
86 name=data.get('name'),
87@@ -299,7 +303,8 @@
88 if (IProduct.providedBy(self.context) or
89 IProductSeries.providedBy(self.context)):
90 information_type = (
91- self.context.getDefaultSpecificationInformationType())
92+ ISpecificationTargetOperations(
93+ self.context).getDefaultSpecificationInformationType())
94 values = {'information_type': information_type}
95 return values
96
97@@ -320,7 +325,8 @@
98 product = self.context
99
100 if product:
101- allowed = product.getAllowedSpecificationInformationTypes()
102+ allowed = ISpecificationTargetOperations(
103+ product).getAllowedSpecificationInformationTypes()
104 # Check that the information type is a valid one for this
105 # Product.
106 if information_type not in allowed:
107@@ -342,7 +348,8 @@
108
109 @property
110 def info_types(self):
111- return self.context.getAllowedSpecificationInformationTypes()
112+ target = ISpecificationTargetOperations(self.context)
113+ return target.getAllowedSpecificationInformationTypes()
114
115 @property
116 def schema(self):
117@@ -416,7 +423,7 @@
118 name = data.get('name')
119 target = data.get('target')
120 if name is not None and target is not None:
121- if target.getSpecification(name):
122+ if ISpecificationTargetOperations(target).getSpecification(name):
123 errormessage = INewSpecification['name'].errormessage
124 self.setFieldError('name', errormessage % name)
125
126
127=== modified file 'lib/lp/blueprints/browser/specificationgoal.py'
128--- lib/lp/blueprints/browser/specificationgoal.py 2012-01-01 02:58:52 +0000
129+++ lib/lp/blueprints/browser/specificationgoal.py 2014-03-28 05:15:47 +0000
130@@ -14,6 +14,9 @@
131
132 from lp.blueprints.browser.specificationtarget import HasSpecificationsView
133 from lp.blueprints.enums import SpecificationFilter
134+from lp.blueprints.interfaces.specificationtarget import (
135+ ISpecificationTargetOperations,
136+ )
137 from lp.services.propertycache import cachedproperty
138 from lp.services.webapp import (
139 canonical_url,
140@@ -84,8 +87,9 @@
141 # list for the general case, so convert it to a list
142 selected_specs = [selected_specs]
143
144- specs = [self.context.getSpecification(name)
145- for name in selected_specs]
146+ specs = [
147+ ISpecificationTargetOperations(self.context).getSpecification(name)
148+ for name in selected_specs]
149 for spec in specs:
150 if action == 'Accepted':
151 spec.acceptBy(user)
152
153=== modified file 'lib/lp/blueprints/browser/specificationtarget.py'
154--- lib/lp/blueprints/browser/specificationtarget.py 2014-02-19 00:35:25 +0000
155+++ lib/lp/blueprints/browser/specificationtarget.py 2014-03-28 05:15:47 +0000
156@@ -35,7 +35,10 @@
157 SpecificationFilter,
158 SpecificationSort,
159 )
160-from lp.blueprints.interfaces.specificationtarget import ISpecificationTarget
161+from lp.blueprints.interfaces.specificationtarget import (
162+ IHasSpecificationsOperations,
163+ ISpecificationTarget,
164+ )
165 from lp.blueprints.interfaces.sprint import ISprint
166 from lp.registry.interfaces.distribution import IDistribution
167 from lp.registry.interfaces.distroseries import IDistroSeries
168@@ -238,6 +241,10 @@
169 size=config.launchpad.default_batch_size)
170
171 @property
172+ def spec_context(self):
173+ return IHasSpecificationsOperations(self.context)
174+
175+ @property
176 def can_configure_blueprints(self):
177 """Can the user configure blueprints for the `ISpecificationTarget`.
178 """
179@@ -259,11 +266,11 @@
180
181 @cachedproperty
182 def has_any_specifications(self):
183- return not self.context.visible_specifications.is_empty()
184+ return not self.spec_context.visible_specifications.is_empty()
185
186 @cachedproperty
187 def all_specifications(self):
188- return shortlist(self.context.all_specifications(self.user))
189+ return shortlist(self.spec_context.all_specifications(self.user))
190
191 @cachedproperty
192 def searchrequested(self):
193@@ -347,7 +354,8 @@
194 and self.context.private
195 and not check_permission('launchpad.View', self.context)):
196 return []
197- return self.context.specifications(self.user, filter=self.spec_filter)
198+ return self.spec_context.specifications(
199+ self.user, filter=self.spec_filter)
200
201 @cachedproperty
202 def specs_batched(self):
203@@ -363,7 +371,8 @@
204 def documentation(self):
205 filter = [SpecificationFilter.COMPLETE,
206 SpecificationFilter.INFORMATIONAL]
207- return shortlist(self.context.specifications(self.user, filter=filter))
208+ return shortlist(
209+ self.spec_context.specifications(self.user, filter=filter))
210
211 @cachedproperty
212 def categories(self):
213@@ -404,7 +413,7 @@
214 Only ACCEPTED specifications are returned. This list is used by the
215 +portlet-latestspecs view.
216 """
217- return self.context.specifications(self.user,
218+ return self.spec_context.specifications(self.user,
219 sort=SpecificationSort.DATE, quantity=quantity,
220 need_people=False, need_branches=False, need_workitems=False)
221
222
223=== modified file 'lib/lp/blueprints/browser/tests/test_specification.py'
224--- lib/lp/blueprints/browser/tests/test_specification.py 2014-01-04 18:45:58 +0000
225+++ lib/lp/blueprints/browser/tests/test_specification.py 2014-03-28 05:15:47 +0000
226@@ -32,6 +32,9 @@
227 ISpecification,
228 ISpecificationSet,
229 )
230+from lp.blueprints.interfaces.specificationtarget import (
231+ ISpecificationTargetOperations,
232+ )
233 from lp.registry.enums import SpecificationSharingPolicy
234 from lp.registry.interfaces.person import PersonVisibility
235 from lp.registry.interfaces.product import IProductSeries
236@@ -547,7 +550,8 @@
237 # Using the browser terminated the interaction, but we need
238 # an interaction in order to access a product.
239 with person_logged_in(ANONYMOUS):
240- return product.getSpecification(specification_name)
241+ return ISpecificationTargetOperations(
242+ product).getSpecification(specification_name)
243
244 def test_supplied_information_types(self):
245 """Creating honours information types."""
246@@ -617,8 +621,9 @@
247 # We need an interaction in order to access a product.
248 with person_logged_in(ANONYMOUS):
249 if IProductSeries.providedBy(target):
250- return target.product.getSpecification(name)
251- return target.getSpecification(name)
252+ target = target.product
253+ return ISpecificationTargetOperations(
254+ target).getSpecification(name)
255
256 def submitSpec(self, browser):
257 """Submit a Specification via a browser."""
258
259=== modified file 'lib/lp/blueprints/configure.zcml'
260--- lib/lp/blueprints/configure.zcml 2013-06-28 00:49:47 +0000
261+++ lib/lp/blueprints/configure.zcml 2014-03-28 05:15:47 +0000
262@@ -245,6 +245,62 @@
263 <allow interface="lp.blueprints.interfaces.specificationmessage.ISpecificationMessageSet"/>
264 </securedutility>
265
266+ <adapter factory="lp.blueprints.model.targetadapters.BlueprintsProduct" trusted="yes" />
267+ <adapter factory="lp.blueprints.model.targetadapters.BlueprintsProductSeries" trusted="yes" />
268+ <adapter factory="lp.blueprints.model.targetadapters.BlueprintsProjectGroup" trusted="yes" />
269+ <adapter factory="lp.blueprints.model.targetadapters.BlueprintsProjectGroupSeries" trusted="yes" />
270+ <adapter factory="lp.blueprints.model.targetadapters.BlueprintsDistribution" trusted="yes" />
271+ <adapter factory="lp.blueprints.model.targetadapters.BlueprintsDistroSeries" trusted="yes" />
272+ <adapter factory="lp.blueprints.model.targetadapters.BlueprintsPerson" trusted="yes" />
273+
274+ <class class="lp.blueprints.model.targetadapters.BlueprintsProduct">
275+ <require
276+ permission="launchpad.LimitedView"
277+ attributes="
278+ getSpecification" />
279+ <require
280+ permission="launchpad.View"
281+ attributes="
282+ visible_specifications
283+ valid_specifications
284+ api_valid_specifications
285+ getAllowedSpecificationInformationTypes
286+ getDefaultSpecificationInformationType
287+ specifications" />
288+ </class>
289+
290+ <class class="lp.blueprints.model.targetadapters.BlueprintsProductSeries">
291+ <require
292+ permission="launchpad.View"
293+ interface="lp.blueprints.interfaces.specificationtarget.ISpecificationTargetOperations" />
294+ </class>
295+
296+ <class class="lp.blueprints.model.targetadapters.BlueprintsProjectGroup">
297+ <allow
298+ interface="lp.blueprints.interfaces.specificationtarget.IHasSpecificationsOperations" />
299+ </class>
300+
301+ <class class="lp.blueprints.model.targetadapters.BlueprintsProjectGroupSeries">
302+ <allow
303+ interface="lp.blueprints.interfaces.specificationtarget.IHasSpecificationsOperations" />
304+ </class>
305+
306+ <class class="lp.blueprints.model.targetadapters.BlueprintsDistribution">
307+ <allow
308+ interface="lp.blueprints.interfaces.specificationtarget.ISpecificationTargetOperations" />
309+ </class>
310+
311+ <class class="lp.blueprints.model.targetadapters.BlueprintsDistroSeries">
312+ <allow
313+ interface="lp.blueprints.interfaces.specificationtarget.ISpecificationTargetOperations" />
314+ </class>
315+
316+ <class class="lp.blueprints.model.targetadapters.BlueprintsPerson">
317+ <require
318+ permission="launchpad.View"
319+ interface="lp.blueprints.interfaces.specificationtarget.IHasSpecificationsOperations" />
320+ </class>
321+
322 <webservice:register module="lp.blueprints.interfaces.webservice" />
323
324 </configure>
325
326=== modified file 'lib/lp/blueprints/doc/specgraph.txt'
327--- lib/lp/blueprints/doc/specgraph.txt 2013-01-25 03:30:08 +0000
328+++ lib/lp/blueprints/doc/specgraph.txt 2014-03-28 05:15:47 +0000
329@@ -249,11 +249,15 @@
330 >>> from zope.component import getMultiAdapter
331 >>> from lp.blueprints.browser.specification import (
332 ... SpecificationTreeImageTag)
333+ >>> from lp.blueprints.interfaces.specificationtarget import (
334+ ... ISpecificationTargetOperations,
335+ ... )
336 >>> from lp.services.webapp.servers import LaunchpadTestRequest
337 >>> from lp.registry.interfaces.product import IProductSet
338
339 >>> firefox = getUtility(IProductSet).getByName('firefox')
340- >>> svg_support = firefox.getSpecification('svg-support')
341+ >>> svg_support = ISpecificationTargetOperations(
342+ ... firefox).getSpecification('svg-support')
343 >>> request = LaunchpadTestRequest(form={})
344 >>> graph_view = getMultiAdapter(
345 ... (svg_support, request), name="+deptreeimgtag")
346
347=== modified file 'lib/lp/blueprints/doc/specification-branch.txt'
348--- lib/lp/blueprints/doc/specification-branch.txt 2013-01-30 05:31:20 +0000
349+++ lib/lp/blueprints/doc/specification-branch.txt 2014-03-28 05:15:47 +0000
350@@ -13,11 +13,15 @@
351
352 >>> from zope.component import getUtility
353 >>> from lp.app.interfaces.launchpad import ILaunchpadCelebrities
354+ >>> from lp.blueprints.interfaces.specificationtarget import (
355+ ... ISpecificationTargetOperations,
356+ ... )
357 >>> from lp.code.interfaces.branchlookup import IBranchLookup
358 >>> from lp.registry.interfaces.person import IPersonSet
359
360 >>> ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
361- >>> spec = ubuntu.getSpecification('media-integrity-check')
362+ >>> spec = ISpecificationTargetOperations(
363+ ... ubuntu).getSpecification('media-integrity-check')
364
365 >>> for branchlink in spec.linked_branches:
366 ... print branchlink.branch.unique_name
367
368=== modified file 'lib/lp/blueprints/doc/specification-notifications.txt'
369--- lib/lp/blueprints/doc/specification-notifications.txt 2011-12-24 17:49:30 +0000
370+++ lib/lp/blueprints/doc/specification-notifications.txt 2014-03-28 05:15:47 +0000
371@@ -8,12 +8,16 @@
372 Changing the status:
373
374 >>> from zope.component import getMultiAdapter
375+ >>> from lp.blueprints.interfaces.specificationtarget import (
376+ ... ISpecificationTargetOperations,
377+ ... )
378 >>> from lp.services.webapp.servers import LaunchpadTestRequest
379 >>> from lp.registry.interfaces.product import IProductSet
380
381 >>> login('foo.bar@canonical.com')
382 >>> firefox = getUtility(IProductSet).getByName('firefox')
383- >>> svg_support = firefox.getSpecification('svg-support')
384+ >>> svg_support = ISpecificationTargetOperations(
385+ ... firefox).getSpecification('svg-support')
386 >>> form = {
387 ... 'field.actions.change': 'Change',
388 ... 'field.definition_status': 'Drafting',
389
390=== modified file 'lib/lp/blueprints/doc/specification.txt'
391--- lib/lp/blueprints/doc/specification.txt 2013-05-01 21:23:16 +0000
392+++ lib/lp/blueprints/doc/specification.txt 2014-03-28 05:15:47 +0000
393@@ -14,6 +14,9 @@
394 ... SpecificationDefinitionStatus,
395 ... SpecificationImplementationStatus, SpecificationPriority)
396 >>> from lp.blueprints.interfaces.specification import ISpecificationSet
397+ >>> from lp.blueprints.interfaces.specificationtarget import (
398+ ... ISpecificationTargetOperations,
399+ ... )
400 >>> specset = getUtility(ISpecificationSet)
401
402 To create a new Specification, use ISpecificationSet.new:
403@@ -38,13 +41,14 @@
404
405 It should be possible to retrieve a specification by its name
406
407- >>> upstream_firefox.getSpecification('mng').name
408+ >>> spec_firefox = ISpecificationTargetOperations(upstream_firefox)
409+ >>> spec_firefox.getSpecification('mng').name
410 u'mng'
411
412 And if we try to retrieve a non-existent specification we should get
413 None
414
415- >>> print upstream_firefox.getSpecification('nonexistentspec')
416+ >>> print spec_firefox.getSpecification('nonexistentspec')
417 None
418
419 It's also possible to retrieve a specification by its URL
420@@ -256,9 +260,7 @@
421 as the full set of specs-which-block-this-one-and-all-the-specs-that-
422 block-them-too.
423
424- >>> from lp.registry.interfaces.product import IProductSet
425- >>> efourx = getUtility(IProductSet).getByName(
426- ... 'firefox').getSpecification('e4x')
427+ >>> efourx = spec_firefox.getSpecification('e4x')
428 >>> for spec in efourx.getDependencies(): print spec.name
429 svg-support
430
431@@ -377,7 +379,7 @@
432 First, we will show how to propose a goal, and what metadata is recorded
433 when we do.
434
435- >>> e4x = upstream_firefox.getSpecification('e4x')
436+ >>> e4x = spec_firefox.getSpecification('e4x')
437 >>> onezero = upstream_firefox.getSeries('1.0')
438 >>> e4x.goal is not None
439 False
440
441=== modified file 'lib/lp/blueprints/doc/sprint-agenda.txt'
442--- lib/lp/blueprints/doc/sprint-agenda.txt 2011-12-30 06:14:56 +0000
443+++ lib/lp/blueprints/doc/sprint-agenda.txt 2014-03-28 05:15:47 +0000
444@@ -8,11 +8,15 @@
445
446 First, lets get hold of some people, a product, a sprint and a spec.
447
448+ >>> from lp.blueprints.interfaces.specificationtarget import (
449+ ... ISpecificationTargetOperations,
450+ ... )
451 >>> from lp.blueprints.model.sprint import SprintSet
452 >>> from lp.registry.model.person import PersonSet
453 >>> from lp.registry.model.product import ProductSet
454 >>> upstream_firefox = ProductSet()['firefox']
455- >>> canvas = upstream_firefox.getSpecification('canvas')
456+ >>> canvas = ISpecificationTargetOperations(
457+ ... upstream_firefox).getSpecification('canvas')
458 >>> guacamole = SprintSet()['uds-guacamole']
459 >>> danner = PersonSet().getByName('danner')
460 >>> jblack = PersonSet().getByName('jblack')
461
462=== modified file 'lib/lp/blueprints/doc/sprint-meeting-export.txt'
463--- lib/lp/blueprints/doc/sprint-meeting-export.txt 2013-04-16 01:18:10 +0000
464+++ lib/lp/blueprints/doc/sprint-meeting-export.txt 2014-03-28 05:15:47 +0000
465@@ -15,6 +15,9 @@
466 >>> from lp.services.webapp.servers import LaunchpadTestRequest
467 >>> from lp.blueprints.browser.sprint import SprintMeetingExportView
468 >>> from lp.blueprints.interfaces.sprint import ISprintSet
469+ >>> from lp.blueprints.interfaces.specificationtarget import (
470+ ... ISpecificationTargetOperations,
471+ ... )
472 >>> from lp.registry.interfaces.person import IPersonSet
473 >>> from lp.registry.interfaces.product import IProductSet
474
475@@ -25,9 +28,10 @@
476 >>> mark = getUtility(IPersonSet).getByName('mark')
477 >>> sampleperson = getUtility(IPersonSet).getByName('name12')
478 >>> firefox = getUtility(IProductSet).getByName('firefox')
479- >>> svg_support = firefox.getSpecification('svg-support')
480- >>> ext_spec = firefox.getSpecification('extension-manager-upgrades')
481- >>> js_spec = firefox.getSpecification('e4x')
482+ >>> spec_firefox = ISpecificationTargetOperations(firefox)
483+ >>> svg_support = spec_firefox.getSpecification('svg-support')
484+ >>> ext_spec = spec_firefox.getSpecification('extension-manager-upgrades')
485+ >>> js_spec = spec_firefox.getSpecification('e4x')
486
487 Create a view for the UBZ sprint meeting export:
488
489
490=== modified file 'lib/lp/blueprints/interfaces/specification.py'
491--- lib/lp/blueprints/interfaces/specification.py 2013-06-28 00:49:47 +0000
492+++ lib/lp/blueprints/interfaces/specification.py 2014-03-28 05:15:47 +0000
493@@ -67,7 +67,9 @@
494 )
495 from lp.blueprints.interfaces.specificationtarget import (
496 IHasSpecifications,
497+ IHasSpecificationsOperations,
498 ISpecificationTarget,
499+ ISpecificationTargetOperations,
500 )
501 from lp.blueprints.interfaces.specificationworkitem import (
502 ISpecificationWorkItem,
503@@ -126,7 +128,8 @@
504 # The context is a specification. Since a specification's
505 # target defines a single specification namespace, we ask
506 # the target to perform the lookup.
507- return self.context.target.getSpecification(name)
508+ return ISpecificationTargetOperations(
509+ self.context.target).getSpecification(name)
510 elif ISprint.providedBy(self.context):
511 # The context is a sprint. Since a sprint corresponds
512 # to multiple specification namespaces, we return None.
513@@ -136,7 +139,8 @@
514 # Since this type of context is associated with exactly one
515 # specification namespace, we ask the context to perform the
516 # lookup.
517- return self.context.getSpecification(name)
518+ return ISpecificationTargetOperations(
519+ self.context).getSpecification(name)
520
521
522 class SpecURLField(TextLine):
523@@ -710,7 +714,7 @@
524 """
525
526
527-class ISpecificationSet(IHasSpecifications):
528+class ISpecificationSet(IHasSpecifications, IHasSpecificationsOperations):
529 """A container for specifications."""
530
531 displayname = Attribute('Displayname')
532
533=== modified file 'lib/lp/blueprints/interfaces/specificationtarget.py'
534--- lib/lp/blueprints/interfaces/specificationtarget.py 2013-08-19 06:43:04 +0000
535+++ lib/lp/blueprints/interfaces/specificationtarget.py 2014-03-28 05:15:47 +0000
536@@ -7,7 +7,9 @@
537
538 __all__ = [
539 'IHasSpecifications',
540+ 'IHasSpecificationsOperations',
541 'ISpecificationTarget',
542+ 'ISpecificationTargetOperations',
543 'ISpecificationGoal',
544 ]
545
546@@ -37,6 +39,23 @@
547 associated with them, and you can use this interface to query those.
548 """
549
550+
551+class ISpecificationTarget(IHasSpecifications):
552+ """An interface for the objects which actually have unique
553+ specifications directly attached to them.
554+ """
555+
556+ export_as_webservice_entry(as_of="devel")
557+
558+
559+class ISpecificationGoal(ISpecificationTarget):
560+ """An interface for those things which can have specifications proposed
561+ as goals for them.
562+ """
563+
564+
565+class IHasSpecificationsOperations(Interface):
566+
567 visible_specifications = exported(doNotSnapshot(
568 CollectionField(
569 title=_("All specifications"),
570@@ -86,12 +105,7 @@
571 """
572
573
574-class ISpecificationTarget(IHasSpecifications):
575- """An interface for the objects which actually have unique
576- specifications directly attached to them.
577- """
578-
579- export_as_webservice_entry(as_of="devel")
580+class ISpecificationTargetOperations(IHasSpecificationsOperations):
581
582 @operation_parameters(
583 name=TextLine(title=_('The name of the specification')))
584@@ -108,9 +122,3 @@
585
586 def getDefaultSpecificationInformationType():
587 """Get the default InformationType for the target's specifications."""
588-
589-
590-class ISpecificationGoal(ISpecificationTarget):
591- """An interface for those things which can have specifications proposed
592- as goals for them.
593- """
594
595=== modified file 'lib/lp/blueprints/interfaces/sprint.py'
596--- lib/lp/blueprints/interfaces/sprint.py 2013-01-07 02:40:55 +0000
597+++ lib/lp/blueprints/interfaces/sprint.py 2014-03-28 05:15:47 +0000
598@@ -31,7 +31,10 @@
599 from lp import _
600 from lp.app.interfaces.headings import IRootContext
601 from lp.app.validators.name import name_validator
602-from lp.blueprints.interfaces.specificationtarget import IHasSpecifications
603+from lp.blueprints.interfaces.specificationtarget import (
604+ IHasSpecifications,
605+ IHasSpecificationsOperations,
606+ )
607 from lp.registry.interfaces.role import (
608 IHasDrivers,
609 IHasOwner,
610@@ -57,7 +60,8 @@
611 return getUtility(ISprintSet)[name]
612
613
614-class ISprint(IHasOwner, IHasDrivers, IHasSpecifications, IRootContext):
615+class ISprint(IHasOwner, IHasDrivers, IHasSpecifications,
616+ IHasSpecificationsOperations, IRootContext):
617 """A sprint, or conference, or meeting."""
618
619 id = Int(title=_('The Sprint ID'))
620
621=== added file 'lib/lp/blueprints/interfaces/targetadapters.py'
622--- lib/lp/blueprints/interfaces/targetadapters.py 1970-01-01 00:00:00 +0000
623+++ lib/lp/blueprints/interfaces/targetadapters.py 2014-03-28 05:15:47 +0000
624@@ -0,0 +1,36 @@
625+# Copyright 2014 Canonical Ltd. This software is licensed under the
626+# GNU Affero General Public License version 3 (see the file LICENSE).
627+
628+"""Webservice extension interfaces for Registry target objects."""
629+
630+__metaclass__ = type
631+
632+__all__ = [
633+ 'IHasSpecificationsOperationsForWebservice',
634+ 'ISpecificationTargetOperationsForWebservice',
635+ ]
636+
637+from lazr.restful.declarations import export_as_webservice_entry
638+
639+from lp.blueprints.interfaces.specificationtarget import (
640+ IHasSpecificationsOperations,
641+ ISpecificationTargetOperations,
642+ )
643+from lp.registry.interfaces.distribution import IDistribution
644+from lp.registry.interfaces.distroseries import IDistroSeries
645+from lp.registry.interfaces.person import IPerson
646+from lp.registry.interfaces.product import IProduct
647+from lp.registry.interfaces.productseries import IProductSeries
648+from lp.registry.interfaces.projectgroup import IProjectGroup
649+
650+
651+class IHasSpecificationsOperationsForWebservice(IHasSpecificationsOperations):
652+
653+ export_as_webservice_entry(contributes_to=[IPerson, IProjectGroup])
654+
655+
656+class ISpecificationTargetOperationsForWebservice(
657+ ISpecificationTargetOperations):
658+
659+ export_as_webservice_entry(contributes_to=[
660+ IProduct, IProductSeries, IDistribution, IDistroSeries])
661
662=== modified file 'lib/lp/blueprints/interfaces/webservice.py'
663--- lib/lp/blueprints/interfaces/webservice.py 2013-06-27 15:11:38 +0000
664+++ lib/lp/blueprints/interfaces/webservice.py 2014-03-28 05:15:47 +0000
665@@ -11,9 +11,12 @@
666
667 __all__ = [
668 'GoalProposeError',
669+ 'IHasSpecificationsOperationsForWebservice',
670 'ISpecification',
671 'ISpecificationBranch',
672 'ISpecificationSubscription',
673+ 'ISpecificationTarget',
674+ 'ISpecificationTargetOperationsForWebservice',
675 ]
676
677 # XXX: JonathanLange 2010-11-09 bug=673083: Legacy work-around for circular
678@@ -28,6 +31,10 @@
679 ISpecificationSubscription,
680 )
681 from lp.blueprints.interfaces.specificationtarget import ISpecificationTarget
682+from lp.blueprints.interfaces.targetadapters import (
683+ IHasSpecificationsOperationsForWebservice,
684+ ISpecificationTargetOperationsForWebservice,
685+ )
686
687
688 _schema_circular_imports
689
690=== modified file 'lib/lp/blueprints/model/specification.py'
691--- lib/lp/blueprints/model/specification.py 2013-12-13 12:51:41 +0000
692+++ lib/lp/blueprints/model/specification.py 2014-03-28 05:15:47 +0000
693@@ -61,6 +61,9 @@
694 ISpecification,
695 ISpecificationSet,
696 )
697+from lp.blueprints.interfaces.specificationtarget import (
698+ ISpecificationTargetOperations,
699+ )
700 from lp.blueprints.model.specificationbranch import SpecificationBranch
701 from lp.blueprints.model.specificationbug import SpecificationBug
702 from lp.blueprints.model.specificationdependency import (
703@@ -495,7 +498,9 @@
704
705 def validateMove(self, target):
706 """See ISpecification."""
707- if target.getSpecification(self.name) is not None:
708+ existing = ISpecificationTargetOperations(target).getSpecification(
709+ self.name)
710+ if existing is not None:
711 raise TargetAlreadyHasSpecification(target, self.name)
712
713 @property
714@@ -902,7 +907,8 @@
715
716 def getAllowedInformationTypes(self, who):
717 """See `ISpecification`."""
718- return self.target.getAllowedSpecificationInformationTypes()
719+ return ISpecificationTargetOperations(
720+ self.target).getAllowedSpecificationInformationTypes()
721
722 def transitionToInformationType(self, information_type, who):
723 """See ISpecification."""
724
725=== added file 'lib/lp/blueprints/model/targetadapters.py'
726--- lib/lp/blueprints/model/targetadapters.py 1970-01-01 00:00:00 +0000
727+++ lib/lp/blueprints/model/targetadapters.py 2014-03-28 05:15:47 +0000
728@@ -0,0 +1,268 @@
729+# Copyright 2009-2014 Canonical Ltd. This software is licensed under the
730+# GNU Affero General Public License version 3 (see the file LICENSE).
731+
732+__metaclass__ = type
733+
734+__all__ = [
735+ 'BlueprintsDistribution',
736+ 'BlueprintsDistroSeries',
737+ 'BlueprintsPerson',
738+ 'BlueprintsProduct',
739+ 'BlueprintsProductSeries',
740+ 'BlueprintsProjectGroup',
741+ 'BlueprintsProjectGroupSeries',
742+ ]
743+
744+from lazr.delegates import delegates
745+from storm.expr import (
746+ Join,
747+ Or,
748+ Select,
749+ )
750+from zope.component import adapter
751+from zope.interface import (
752+ classImplements,
753+ implementer,
754+ )
755+
756+from lp.app.enums import InformationType
757+from lp.blueprints.enums import SpecificationFilter
758+from lp.blueprints.interfaces.specificationtarget import (
759+ IHasSpecifications,
760+ ISpecificationGoal,
761+ ISpecificationTarget,
762+ )
763+from lp.blueprints.interfaces.targetadapters import (
764+ IHasSpecificationsOperationsForWebservice,
765+ ISpecificationTargetOperationsForWebservice,
766+ )
767+from lp.blueprints.model.specification import (
768+ HasSpecificationsMixin,
769+ Specification,
770+ SPECIFICATION_POLICY_ALLOWED_TYPES,
771+ SPECIFICATION_POLICY_DEFAULT_TYPES,
772+ )
773+from lp.blueprints.model.specificationsearch import search_specifications
774+from lp.registry.interfaces.distribution import IDistribution
775+from lp.registry.interfaces.distroseries import IDistroSeries
776+from lp.registry.interfaces.person import IPerson
777+from lp.registry.interfaces.product import IProduct
778+from lp.registry.interfaces.productseries import IProductSeries
779+from lp.registry.interfaces.projectgroup import (
780+ IProjectGroup,
781+ IProjectGroupSeries,
782+ )
783+from lp.registry.model.distribution import Distribution
784+from lp.registry.model.distroseries import DistroSeries
785+from lp.registry.model.person import Person
786+from lp.registry.model.product import Product
787+from lp.registry.model.productseries import ProductSeries
788+from lp.registry.model.projectgroup import (
789+ ProjectGroup,
790+ ProjectGroupSeries,
791+ )
792+
793+
794+class BlueprintsTargetBase(HasSpecificationsMixin):
795+
796+ default_acceptance = False
797+ search_tables = (Specification,)
798+
799+ def __init__(self, context):
800+ self.context = context
801+
802+ def _makeClausesAndFilter(self, filter):
803+ raise NotImplementedError()
804+
805+ def specifications(self, user, sort=None, quantity=None, filter=None,
806+ in_progress=False, need_people=True,
807+ need_branches=True, need_workitems=False):
808+ """See `IHasSpecifications`.
809+
810+ For all targets there are two kinds of filtering, based on:
811+
812+ - completeness: we want to show INCOMPLETE if nothing is said
813+ - informationalness: we will show ANY if nothing is said
814+
815+ Series targets have an additional kind:
816+
817+ - acceptance: if nothing is said, ACCEPTED only
818+ """
819+ # Make a new copy of the filter, so that we do not mutate what we
820+ # were passed as a filter.
821+ filter = set(filter) if filter is not None else set()
822+ clauses, filter = self._makeClausesAndFilter(filter)
823+
824+ if SpecificationFilter.COMPLETE not in filter:
825+ if (in_progress and SpecificationFilter.INCOMPLETE not in filter
826+ and SpecificationFilter.ALL not in filter):
827+ filter.update(
828+ [SpecificationFilter.INCOMPLETE,
829+ SpecificationFilter.STARTED])
830+
831+ return search_specifications(
832+ self.context, clauses, user, sort, quantity, list(filter),
833+ tables=list(self.search_tables),
834+ default_acceptance=self.default_acceptance,
835+ need_people=need_people, need_branches=need_branches,
836+ need_workitems=need_workitems)
837+
838+
839+@adapter(IDistribution)
840+@implementer(ISpecificationTargetOperationsForWebservice)
841+class BlueprintsDistribution(BlueprintsTargetBase):
842+
843+ def _makeClausesAndFilter(self, filter):
844+ return [Specification.distributionID == self.context.id], filter
845+
846+ def getSpecification(self, name):
847+ """See `ISpecificationTarget`."""
848+ return Specification.selectOneBy(distribution=self.context, name=name)
849+
850+ def getAllowedSpecificationInformationTypes(self):
851+ """See `ISpecificationTarget`."""
852+ return (InformationType.PUBLIC,)
853+
854+ def getDefaultSpecificationInformationType(self):
855+ """See `ISpecificationTarget`."""
856+ return InformationType.PUBLIC
857+
858+
859+@adapter(IDistroSeries)
860+@implementer(ISpecificationTargetOperationsForWebservice)
861+class BlueprintsDistroSeries(BlueprintsTargetBase):
862+
863+ delegates(ISpecificationTargetOperationsForWebservice, context='pillar')
864+
865+ default_acceptance = True
866+
867+ @property
868+ def pillar(self):
869+ return ISpecificationTargetOperationsForWebservice(
870+ self.context.distribution)
871+
872+ def _makeClausesAndFilter(self, filter):
873+ return [Specification.distroseriesID == self.context.id], filter
874+
875+
876+@adapter(IPerson)
877+@implementer(IHasSpecificationsOperationsForWebservice)
878+class BlueprintsPerson(BlueprintsTargetBase):
879+
880+ def _makeClausesAndFilter(self, filter):
881+ from lp.blueprints.model.specificationsubscription import (
882+ SpecificationSubscription,
883+ )
884+
885+ # Now look at the filter and fill in the unsaid bits.
886+
887+ # Defaults for acceptance: in this case we have nothing to do
888+ # because specs are not accepted/declined against a person.
889+
890+ # Defaults for informationalness: we don't have to do anything
891+ # because the default if nothing is said is ANY.
892+
893+ roles = set([
894+ SpecificationFilter.CREATOR, SpecificationFilter.ASSIGNEE,
895+ SpecificationFilter.DRAFTER, SpecificationFilter.APPROVER,
896+ SpecificationFilter.SUBSCRIBER])
897+ # If no roles are given, then we want everything.
898+ if not filter & roles:
899+ filter.update(roles)
900+ role_clauses = []
901+ if SpecificationFilter.CREATOR in filter:
902+ role_clauses.append(Specification.owner == self.context)
903+ if SpecificationFilter.ASSIGNEE in filter:
904+ role_clauses.append(Specification._assignee == self.context)
905+ if SpecificationFilter.DRAFTER in filter:
906+ role_clauses.append(Specification._drafter == self.context)
907+ if SpecificationFilter.APPROVER in filter:
908+ role_clauses.append(Specification._approver == self.context)
909+ if SpecificationFilter.SUBSCRIBER in filter:
910+ role_clauses.append(
911+ Specification.id.is_in(
912+ Select(SpecificationSubscription.specificationID,
913+ [SpecificationSubscription.person == self.context])))
914+ return [Or(*role_clauses)], filter
915+
916+
917+@adapter(IProduct)
918+@implementer(ISpecificationTargetOperationsForWebservice)
919+class BlueprintsProduct(BlueprintsTargetBase):
920+
921+ def _makeClausesAndFilter(self, filter):
922+ return [Specification.productID == self.context.id], filter
923+
924+ def getSpecification(self, name):
925+ """See `ISpecificationTarget`."""
926+ return Specification.selectOneBy(product=self.context, name=name)
927+
928+ def getAllowedSpecificationInformationTypes(self):
929+ """See `ISpecificationTarget`."""
930+ return SPECIFICATION_POLICY_ALLOWED_TYPES[
931+ self.context.specification_sharing_policy]
932+
933+ def getDefaultSpecificationInformationType(self):
934+ """See `ISpecificationTarget`."""
935+ return SPECIFICATION_POLICY_DEFAULT_TYPES[
936+ self.context.specification_sharing_policy]
937+
938+
939+@adapter(IProductSeries)
940+@implementer(ISpecificationTargetOperationsForWebservice)
941+class BlueprintsProductSeries(BlueprintsTargetBase):
942+
943+ delegates(ISpecificationTargetOperationsForWebservice, context='pillar')
944+
945+ default_acceptance = True
946+
947+ @property
948+ def pillar(self):
949+ return ISpecificationTargetOperationsForWebservice(
950+ self.context.product)
951+
952+ def _makeClausesAndFilter(self, filter):
953+ return [Specification.productseriesID == self.context.id], filter
954+
955+
956+@adapter(IProjectGroup)
957+@implementer(IHasSpecificationsOperationsForWebservice)
958+class BlueprintsProjectGroup(BlueprintsTargetBase):
959+
960+ def __init__(self, context):
961+ self.context = context
962+
963+ def _makeClausesAndFilter(self, filter):
964+ return [
965+ Specification.productID == Product.id,
966+ Product.projectID == self.context.id], filter
967+
968+
969+@adapter(IProjectGroupSeries)
970+@implementer(IHasSpecificationsOperationsForWebservice)
971+class BlueprintsProjectGroupSeries(BlueprintsTargetBase):
972+
973+ delegates(IHasSpecificationsOperationsForWebservice, context='pillar')
974+
975+ search_tables = (
976+ Specification,
977+ Join(ProductSeries, Specification.productseriesID == ProductSeries.id))
978+
979+ @property
980+ def pillar(self):
981+ return IHasSpecificationsOperationsForWebservice(self.context.project)
982+
983+ def _makeClausesAndFilter(self, filter):
984+ return [
985+ Specification.productID == Product.id,
986+ Product.projectID == self.context.project.id,
987+ ProductSeries.name == self.context.name], filter
988+
989+
990+classImplements(Distribution, ISpecificationTarget)
991+classImplements(DistroSeries, ISpecificationGoal)
992+classImplements(Person, IHasSpecifications)
993+classImplements(Product, ISpecificationTarget)
994+classImplements(ProductSeries, ISpecificationGoal)
995+classImplements(ProjectGroup, IHasSpecifications)
996+classImplements(ProjectGroupSeries, IHasSpecifications)
997
998=== modified file 'lib/lp/blueprints/tests/test_hasspecifications.py'
999--- lib/lp/blueprints/tests/test_hasspecifications.py 2013-08-19 06:43:04 +0000
1000+++ lib/lp/blueprints/tests/test_hasspecifications.py 2014-03-28 05:15:47 +0000
1001@@ -6,7 +6,10 @@
1002 __metaclass__ = type
1003
1004 from lp.blueprints.enums import SpecificationDefinitionStatus
1005-from lp.blueprints.interfaces.specificationtarget import IHasSpecifications
1006+from lp.blueprints.interfaces.specificationtarget import (
1007+ IHasSpecifications,
1008+ IHasSpecificationsOperations,
1009+ )
1010 from lp.testing import TestCaseWithFactory
1011 from lp.testing.layers import DatabaseFunctionalLayer
1012 from lp.testing.matchers import DoesNotSnapshot
1013@@ -25,7 +28,8 @@
1014 self.factory.makeSpecification(product=product, name="spec1")
1015 self.factory.makeSpecification(product=product, name="spec2")
1016 self.assertNamesOfSpecificationsAre(
1017- ["spec1", "spec2"], product.visible_specifications)
1018+ ["spec1", "spec2"],
1019+ IHasSpecificationsOperations(product).visible_specifications)
1020
1021 def test_product_valid_specifications(self):
1022 product = self.factory.makeProduct()
1023@@ -34,14 +38,16 @@
1024 product=product, name="spec2",
1025 status=SpecificationDefinitionStatus.OBSOLETE)
1026 self.assertNamesOfSpecificationsAre(
1027- ["spec1"], product.valid_specifications())
1028+ ["spec1"],
1029+ IHasSpecificationsOperations(product).valid_specifications())
1030
1031 def test_distribution_all_specifications(self):
1032 distribution = self.factory.makeDistribution()
1033 self.factory.makeSpecification(distribution=distribution, name="spec1")
1034 self.factory.makeSpecification(distribution=distribution, name="spec2")
1035 self.assertNamesOfSpecificationsAre(
1036- ["spec1", "spec2"], distribution.visible_specifications)
1037+ ["spec1", "spec2"],
1038+ IHasSpecificationsOperations(distribution).visible_specifications)
1039
1040 def test_distribution_valid_specifications(self):
1041 distribution = self.factory.makeDistribution()
1042@@ -50,7 +56,8 @@
1043 distribution=distribution, name="spec2",
1044 status=SpecificationDefinitionStatus.OBSOLETE)
1045 self.assertNamesOfSpecificationsAre(
1046- ["spec1"], distribution.valid_specifications())
1047+ ["spec1"],
1048+ IHasSpecificationsOperations(distribution).valid_specifications())
1049
1050 def test_distroseries_all_specifications(self):
1051 distroseries = self.factory.makeDistroSeries(name='maudlin')
1052@@ -61,7 +68,8 @@
1053 distribution=distribution, name="spec2", goal=distroseries)
1054 self.factory.makeSpecification(distribution=distribution, name="spec3")
1055 self.assertNamesOfSpecificationsAre(
1056- ["spec1", "spec2"], distroseries.visible_specifications)
1057+ ["spec1", "spec2"],
1058+ IHasSpecificationsOperations(distroseries).visible_specifications)
1059
1060 def test_distroseries_valid_specifications(self):
1061 distroseries = self.factory.makeDistroSeries(name='maudlin')
1062@@ -75,7 +83,8 @@
1063 status=SpecificationDefinitionStatus.OBSOLETE)
1064 self.factory.makeSpecification(distribution=distribution, name="spec4")
1065 self.assertNamesOfSpecificationsAre(
1066- ["spec1", "spec2"], distroseries.valid_specifications())
1067+ ["spec1", "spec2"],
1068+ IHasSpecificationsOperations(distroseries).valid_specifications())
1069
1070 def test_productseries_all_specifications(self):
1071 product = self.factory.makeProduct()
1072@@ -87,7 +96,8 @@
1073 product=product, name="spec2", goal=productseries)
1074 self.factory.makeSpecification(product=product, name="spec3")
1075 self.assertNamesOfSpecificationsAre(
1076- ["spec1", "spec2"], productseries.visible_specifications)
1077+ ["spec1", "spec2"],
1078+ IHasSpecificationsOperations(productseries).visible_specifications)
1079
1080 def test_productseries_valid_specifications(self):
1081 product = self.factory.makeProduct()
1082@@ -102,7 +112,8 @@
1083 status=SpecificationDefinitionStatus.OBSOLETE)
1084 self.factory.makeSpecification(product=product, name="spec4")
1085 self.assertNamesOfSpecificationsAre(
1086- ["spec1", "spec2"], productseries.valid_specifications())
1087+ ["spec1", "spec2"],
1088+ IHasSpecificationsOperations(productseries).valid_specifications())
1089
1090 def test_projectgroup_all_specifications(self):
1091 projectgroup = self.factory.makeProject()
1092@@ -116,7 +127,8 @@
1093 status=SpecificationDefinitionStatus.OBSOLETE)
1094 self.factory.makeSpecification(product=product3, name="spec3")
1095 self.assertNamesOfSpecificationsAre(
1096- ["spec1", "spec2"], projectgroup.visible_specifications)
1097+ ["spec1", "spec2"],
1098+ IHasSpecificationsOperations(projectgroup).visible_specifications)
1099
1100 def test_projectgroup_valid_specifications(self):
1101 projectgroup = self.factory.makeProject()
1102@@ -130,7 +142,8 @@
1103 status=SpecificationDefinitionStatus.OBSOLETE)
1104 self.factory.makeSpecification(product=product3, name="spec3")
1105 self.assertNamesOfSpecificationsAre(
1106- ["spec1"], projectgroup.valid_specifications())
1107+ ["spec1"],
1108+ IHasSpecificationsOperations(projectgroup).valid_specifications())
1109
1110 def test_person_all_specifications(self):
1111 person = self.factory.makePerson(name="james-w")
1112@@ -142,7 +155,8 @@
1113 status=SpecificationDefinitionStatus.OBSOLETE)
1114 self.factory.makeSpecification(product=product, name="spec3")
1115 self.assertNamesOfSpecificationsAre(
1116- ["spec1", "spec2"], person.visible_specifications)
1117+ ["spec1", "spec2"],
1118+ IHasSpecificationsOperations(person).visible_specifications)
1119
1120 def test_person_valid_specifications(self):
1121 person = self.factory.makePerson(name="james-w")
1122@@ -154,7 +168,8 @@
1123 status=SpecificationDefinitionStatus.OBSOLETE)
1124 self.factory.makeSpecification(product=product, name="spec3")
1125 self.assertNamesOfSpecificationsAre(
1126- ["spec1"], person.valid_specifications())
1127+ ["spec1"],
1128+ IHasSpecificationsOperations(person).valid_specifications())
1129
1130
1131 class HasSpecificationsSnapshotTestCase(TestCaseWithFactory):
1132
1133=== modified file 'lib/lp/blueprints/tests/test_specification.py'
1134--- lib/lp/blueprints/tests/test_specification.py 2013-10-03 05:33:54 +0000
1135+++ lib/lp/blueprints/tests/test_specification.py 2014-03-28 05:15:47 +0000
1136@@ -41,6 +41,9 @@
1137 )
1138 from lp.blueprints.errors import TargetAlreadyHasSpecification
1139 from lp.blueprints.interfaces.specification import ISpecificationSet
1140+from lp.blueprints.interfaces.specificationtarget import (
1141+ IHasSpecificationsOperations,
1142+ )
1143 from lp.blueprints.model.specification import Specification
1144 from lp.blueprints.model.specificationsearch import (
1145 get_specification_privacy_filter,
1146@@ -568,7 +571,7 @@
1147
1148
1149 def list_result(context, filter=None, user=None):
1150- result = context.specifications(
1151+ result = IHasSpecificationsOperations(context).specifications(
1152 user, SpecificationSort.DATE, filter=filter)
1153 return list(result)
1154
1155
1156=== modified file 'lib/lp/blueprints/vocabularies/specification.py'
1157--- lib/lp/blueprints/vocabularies/specification.py 2013-10-14 01:40:18 +0000
1158+++ lib/lp/blueprints/vocabularies/specification.py 2014-03-28 05:15:47 +0000
1159@@ -13,6 +13,9 @@
1160 from zope.component import getUtility
1161 from zope.schema.vocabulary import SimpleTerm
1162
1163+from lp.blueprints.interfaces.specificationtarget import (
1164+ IHasSpecificationsOperations,
1165+ )
1166 from lp.blueprints.model.specification import Specification
1167 from lp.services.webapp.interfaces import ILaunchBag
1168 from lp.services.webapp.vocabulary import SQLObjectVocabularyBase
1169@@ -38,9 +41,10 @@
1170 target = distribution
1171
1172 if target is not None:
1173+ spec_target = IHasSpecificationsOperations(target)
1174 for spec in sorted(
1175- target.specifications(launchbag.user),
1176- key=attrgetter('title')):
1177+ spec_target.specifications(launchbag.user),
1178+ key=attrgetter('title')):
1179 # we will not show the current specification in the
1180 # launchbag
1181 if spec == launchbag.specification:
1182
1183=== modified file 'lib/lp/blueprints/vocabularies/specificationdependency.py'
1184--- lib/lp/blueprints/vocabularies/specificationdependency.py 2013-05-24 07:25:24 +0000
1185+++ lib/lp/blueprints/vocabularies/specificationdependency.py 2014-03-28 05:15:47 +0000
1186@@ -18,6 +18,9 @@
1187 from zope.interface import implements
1188 from zope.schema.vocabulary import SimpleTerm
1189
1190+from lp.blueprints.interfaces.specificationtarget import (
1191+ ISpecificationTargetOperations,
1192+ )
1193 from lp.blueprints.model.specification import (
1194 recursive_blocked_query,
1195 Specification,
1196@@ -158,7 +161,8 @@
1197 pillar_name, ignore_inactive=True)
1198 if pillar is None:
1199 return None
1200- return pillar.getSpecification(spec_name)
1201+ return ISpecificationTargetOperations(
1202+ pillar).getSpecification(spec_name)
1203
1204 def getTermByToken(self, token):
1205 """See `zope.schema.interfaces.IVocabularyTokenized`.
1206@@ -168,7 +172,8 @@
1207 """
1208 spec = self._spec_from_url(token)
1209 if spec is None:
1210- spec = self.context.target.getSpecification(token)
1211+ spec = ISpecificationTargetOperations(
1212+ self.context.target).getSpecification(token)
1213 if self._is_valid_candidate(spec):
1214 return self.toTerm(spec)
1215 raise LookupError(token)
1216
1217=== modified file 'lib/lp/registry/browser/distribution.py'
1218--- lib/lp/registry/browser/distribution.py 2014-02-26 04:47:33 +0000
1219+++ lib/lp/registry/browser/distribution.py 2014-03-28 05:15:47 +0000
1220@@ -70,6 +70,9 @@
1221 from lp.blueprints.browser.specificationtarget import (
1222 HasSpecificationsMenuMixin,
1223 )
1224+from lp.blueprints.interfaces.specificationtarget import (
1225+ ISpecificationTargetOperations,
1226+ )
1227 from lp.bugs.browser.bugtask import BugTargetTraversalMixin
1228 from lp.bugs.browser.structuralsubscription import (
1229 expose_structural_subscription_data_to_js,
1230@@ -164,7 +167,8 @@
1231
1232 @stepthrough('+spec')
1233 def traverse_spec(self, name):
1234- return self.context.getSpecification(name)
1235+ return ISpecificationTargetOperations(self.context).getSpecification(
1236+ name)
1237
1238 @stepthrough('+archive')
1239 def traverse_archive(self, name):
1240
1241=== modified file 'lib/lp/registry/browser/product.py'
1242--- lib/lp/registry/browser/product.py 2014-02-26 05:35:04 +0000
1243+++ lib/lp/registry/browser/product.py 2014-03-28 05:15:47 +0000
1244@@ -124,6 +124,9 @@
1245 from lp.blueprints.browser.specificationtarget import (
1246 HasSpecificationsMenuMixin,
1247 )
1248+from lp.blueprints.interfaces.specificationtarget import (
1249+ ISpecificationTargetOperations,
1250+ )
1251 from lp.bugs.browser.bugtask import (
1252 BugTargetTraversalMixin,
1253 get_buglisting_search_filter_url,
1254@@ -222,7 +225,8 @@
1255
1256 @stepthrough('+spec')
1257 def traverse_spec(self, name):
1258- spec = self.context.getSpecification(name)
1259+ spec = ISpecificationTargetOperations(self.context).getSpecification(
1260+ name)
1261 if not check_permission('launchpad.LimitedView', spec):
1262 return None
1263 return spec
1264
1265=== modified file 'lib/lp/registry/configure.zcml'
1266--- lib/lp/registry/configure.zcml 2014-02-25 06:24:21 +0000
1267+++ lib/lp/registry/configure.zcml 2014-03-28 05:15:47 +0000
1268@@ -1451,21 +1451,6 @@
1269 permission="launchpad.Edit"
1270 set_attributes="bug_supervisor"/>
1271
1272- <!-- ISpecificationTarget -->
1273- <require
1274- permission="launchpad.LimitedView"
1275- attributes="
1276- getSpecification" />
1277- <require
1278- permission="launchpad.View"
1279- attributes="
1280- visible_specifications
1281- valid_specifications
1282- api_valid_specifications
1283- getAllowedSpecificationInformationTypes
1284- getDefaultSpecificationInformationType
1285- specifications" />
1286-
1287 <!-- IHasDrivers -->
1288 <require
1289 permission="launchpad.LimitedView"
1290
1291=== modified file 'lib/lp/registry/interfaces/distribution.py'
1292--- lib/lp/registry/interfaces/distribution.py 2013-08-01 14:43:03 +0000
1293+++ lib/lp/registry/interfaces/distribution.py 2014-03-28 05:15:47 +0000
1294@@ -62,7 +62,6 @@
1295 IServiceUsage,
1296 )
1297 from lp.app.validators.name import name_validator
1298-from lp.blueprints.interfaces.specificationtarget import ISpecificationTarget
1299 from lp.blueprints.interfaces.sprint import IHasSprints
1300 from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor
1301 from lp.bugs.interfaces.bugtarget import (
1302@@ -139,8 +138,7 @@
1303 IHasBuildRecords, IHasDrivers, IHasMilestones, IHasSharingPolicies,
1304 IHasOOPSReferences, IHasOwner, IHasSprints, IHasTranslationImports,
1305 ITranslationPolicy, IKarmaContext, ILaunchpadUsage, IMakesAnnouncements,
1306- IOfficialBugTagTargetPublic, IPillar, IServiceUsage,
1307- ISpecificationTarget, IHasExpirableBugs):
1308+ IOfficialBugTagTargetPublic, IPillar, IServiceUsage, IHasExpirableBugs):
1309 """Public IDistribution properties."""
1310
1311 id = Attribute("The distro's unique number.")
1312
1313=== modified file 'lib/lp/registry/interfaces/distroseries.py'
1314--- lib/lp/registry/interfaces/distroseries.py 2013-09-10 06:28:26 +0000
1315+++ lib/lp/registry/interfaces/distroseries.py 2014-03-28 05:15:47 +0000
1316@@ -59,7 +59,6 @@
1317 from lp.app.validators.email import email_validator
1318 from lp.app.validators.name import name_validator
1319 from lp.app.validators.version import sane_version
1320-from lp.blueprints.interfaces.specificationtarget import ISpecificationGoal
1321 from lp.bugs.interfaces.bugtarget import (
1322 IBugTarget,
1323 IHasBugs,
1324@@ -174,7 +173,7 @@
1325
1326 class IDistroSeriesPublic(
1327 ISeriesMixin, IHasAppointedDriver, IHasOwner, IBugTarget,
1328- ISpecificationGoal, IHasMilestones, IHasOfficialBugTags,
1329+ IHasMilestones, IHasOfficialBugTags,
1330 IHasBuildRecords, IHasTranslationImports, IHasTranslationTemplates,
1331 IServiceUsage, IHasExpirableBugs):
1332 """Public IDistroSeries properties."""
1333
1334=== modified file 'lib/lp/registry/interfaces/person.py'
1335--- lib/lp/registry/interfaces/person.py 2014-03-11 10:37:23 +0000
1336+++ lib/lp/registry/interfaces/person.py 2014-03-28 05:15:47 +0000
1337@@ -104,7 +104,6 @@
1338 from lp.app.validators import LaunchpadValidationError
1339 from lp.app.validators.email import email_validator
1340 from lp.app.validators.name import name_validator
1341-from lp.blueprints.interfaces.specificationtarget import IHasSpecifications
1342 from lp.bugs.interfaces.bugtarget import IHasBugs
1343 from lp.code.interfaces.hasbranches import (
1344 IHasBranches,
1345@@ -678,7 +677,7 @@
1346 """
1347
1348
1349-class IPersonViewRestricted(IHasBranches, IHasSpecifications,
1350+class IPersonViewRestricted(IHasBranches,
1351 IHasMergeProposals, IHasMugshot,
1352 IHasLocation, IHasRequestedReviews, IObjectWithLocation,
1353 IHasBugs, IHasRecipes, IHasTranslationImports,
1354@@ -843,10 +842,6 @@
1355 exported_as='confirmed_email_addresses')
1356 unvalidatedemails = Attribute(
1357 "Emails this person added in Launchpad but are not yet validated.")
1358- specifications = Attribute(
1359- "Any specifications related to this person, either because the are "
1360- "a subscriber, or an assignee, or a drafter, or the creator. "
1361- "Sorted newest-first.")
1362
1363 def findVisibleAssignedInProgressSpecs(user):
1364 """List specifications in progress assigned to this person.
1365
1366=== modified file 'lib/lp/registry/interfaces/product.py'
1367--- lib/lp/registry/interfaces/product.py 2013-02-13 14:17:19 +0000
1368+++ lib/lp/registry/interfaces/product.py 2014-03-28 05:15:47 +0000
1369@@ -85,7 +85,6 @@
1370 )
1371 from lp.app.validators import LaunchpadValidationError
1372 from lp.app.validators.name import name_validator
1373-from lp.blueprints.interfaces.specificationtarget import ISpecificationTarget
1374 from lp.blueprints.interfaces.sprint import IHasSprints
1375 from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor
1376 from lp.bugs.interfaces.bugtarget import (
1377@@ -885,8 +884,7 @@
1378 IBugTarget, IHasBugSupervisor, IHasDrivers, IProductEditRestricted,
1379 IProductModerateRestricted, IProductDriverRestricted, IProductView,
1380 IProductLimitedView, IProductPublic, IQuestionTarget, IRootContext,
1381- ISpecificationTarget, IStructuralSubscriptionTarget, IInformationType,
1382- IPillar):
1383+ IStructuralSubscriptionTarget, IInformationType, IPillar):
1384 """A Product.
1385
1386 The Launchpad Registry describes the open source world as ProjectGroups
1387
1388=== modified file 'lib/lp/registry/interfaces/productseries.py'
1389--- lib/lp/registry/interfaces/productseries.py 2013-04-03 03:59:28 +0000
1390+++ lib/lp/registry/interfaces/productseries.py 2014-03-28 05:15:47 +0000
1391@@ -50,7 +50,6 @@
1392 from lp.app.validators import LaunchpadValidationError
1393 from lp.app.validators.name import name_validator
1394 from lp.app.validators.url import validate_url
1395-from lp.blueprints.interfaces.specificationtarget import ISpecificationGoal
1396 from lp.bugs.interfaces.bugtarget import (
1397 IBugTarget,
1398 IHasExpirableBugs,
1399@@ -157,9 +156,9 @@
1400
1401
1402 class IProductSeriesView(
1403- ISeriesMixin, IHasAppointedDriver, IHasOwner,
1404- ISpecificationGoal, IHasMilestones, IHasOfficialBugTags, IHasExpirableBugs,
1405- IHasTranslationImports, IHasTranslationTemplates, IServiceUsage):
1406+ ISeriesMixin, IHasAppointedDriver, IHasOwner, IHasMilestones,
1407+ IHasOfficialBugTags, IHasExpirableBugs, IHasTranslationImports,
1408+ IHasTranslationTemplates, IServiceUsage):
1409 status = exported(
1410 Choice(
1411 title=_('Status'), required=True, vocabulary=SeriesStatus,
1412
1413=== modified file 'lib/lp/registry/interfaces/projectgroup.py'
1414--- lib/lp/registry/interfaces/projectgroup.py 2013-01-07 02:40:55 +0000
1415+++ lib/lp/registry/interfaces/projectgroup.py 2014-03-28 05:15:47 +0000
1416@@ -49,7 +49,6 @@
1417 IServiceUsage,
1418 )
1419 from lp.app.validators.name import name_validator
1420-from lp.blueprints.interfaces.specificationtarget import IHasSpecifications
1421 from lp.blueprints.interfaces.sprint import IHasSprints
1422 from lp.bugs.interfaces.bugtarget import (
1423 IHasBugs,
1424@@ -117,9 +116,8 @@
1425 class IProjectGroupPublic(
1426 ICanGetMilestonesDirectly, IHasAppointedDriver, IHasBranches, IHasBugs,
1427 IHasDrivers, IHasIcon, IHasLogo, IHasMergeProposals, IHasMilestones,
1428- IHasMugshot, IHasOwner, IHasSpecifications, IHasSprints,
1429- IMakesAnnouncements, IKarmaContext, IRootContext, IHasOfficialBugTags,
1430- IServiceUsage):
1431+ IHasMugshot, IHasOwner, IHasSprints, IMakesAnnouncements, IKarmaContext,
1432+ IRootContext, IHasOfficialBugTags, IServiceUsage):
1433 """Public IProjectGroup properties."""
1434
1435 id = Int(title=_('ID'), readonly=True)
1436@@ -415,8 +413,7 @@
1437 products that needs review."""
1438
1439
1440-class IProjectGroupSeries(IHasSpecifications, IHasAppointedDriver, IHasIcon,
1441- IHasOwner):
1442+class IProjectGroupSeries(IHasAppointedDriver, IHasIcon, IHasOwner):
1443 """Interface for ProjectGroupSeries.
1444
1445 This class provides the specifications related to a "virtual project
1446
1447=== modified file 'lib/lp/registry/model/distribution.py'
1448--- lib/lp/registry/model/distribution.py 2014-01-07 01:45:40 +0000
1449+++ lib/lp/registry/model/distribution.py 2014-03-28 05:15:47 +0000
1450@@ -67,11 +67,6 @@
1451 valid_name,
1452 )
1453 from lp.archivepublisher.debversion import Version
1454-from lp.blueprints.model.specification import (
1455- HasSpecificationsMixin,
1456- Specification,
1457- )
1458-from lp.blueprints.model.specificationsearch import search_specifications
1459 from lp.blueprints.model.sprint import HasSprintsMixin
1460 from lp.bugs.interfaces.bugsummary import IBugSummaryDimension
1461 from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor
1462@@ -188,7 +183,7 @@
1463
1464
1465 class Distribution(SQLBase, BugTargetBase, MakesAnnouncements,
1466- HasSpecificationsMixin, HasSprintsMixin, HasAliasMixin,
1467+ HasSprintsMixin, HasAliasMixin,
1468 HasTranslationImportsMixin, KarmaContextMixin,
1469 OfficialBugTagTargetMixin, QuestionTargetMixin,
1470 StructuralSubscriptionTargetMixin, HasMilestonesMixin,
1471@@ -822,36 +817,6 @@
1472 return getUtility(IDistributionSet).getCurrentSourceReleases(
1473 {self: source_package_names})
1474
1475- def specifications(self, user, sort=None, quantity=None, filter=None,
1476- need_people=True, need_branches=True,
1477- need_workitems=False):
1478- """See `IHasSpecifications`.
1479-
1480- In the case of distributions, there are two kinds of filtering,
1481- based on:
1482-
1483- - completeness: we want to show INCOMPLETE if nothing is said
1484- - informationalness: we will show ANY if nothing is said
1485-
1486- """
1487- base_clauses = [Specification.distributionID == self.id]
1488- return search_specifications(
1489- self, base_clauses, user, sort, quantity, filter,
1490- need_people=need_people, need_branches=need_branches,
1491- need_workitems=need_workitems)
1492-
1493- def getSpecification(self, name):
1494- """See `ISpecificationTarget`."""
1495- return Specification.selectOneBy(distribution=self, name=name)
1496-
1497- def getAllowedSpecificationInformationTypes(self):
1498- """See `ISpecificationTarget`."""
1499- return (InformationType.PUBLIC,)
1500-
1501- def getDefaultSpecificationInformationType(self):
1502- """See `ISpecificationTarget`."""
1503- return InformationType.PUBLIC
1504-
1505 def searchQuestions(self, search_text=None,
1506 status=QUESTION_STATUS_DEFAULT_SEARCH,
1507 language=None, sort=None, owner=None,
1508
1509=== modified file 'lib/lp/registry/model/distroseries.py'
1510--- lib/lp/registry/model/distroseries.py 2014-01-06 14:24:05 +0000
1511+++ lib/lp/registry/model/distroseries.py 2014-03-28 05:15:47 +0000
1512@@ -15,7 +15,6 @@
1513 import logging
1514
1515 import apt_pkg
1516-from lazr.delegates import delegates
1517 from sqlobject import (
1518 BoolCol,
1519 ForeignKey,
1520@@ -42,12 +41,6 @@
1521 from lp.app.enums import service_uses_launchpad
1522 from lp.app.errors import NotFoundError
1523 from lp.app.interfaces.launchpad import IServiceUsage
1524-from lp.blueprints.interfaces.specificationtarget import ISpecificationTarget
1525-from lp.blueprints.model.specification import (
1526- HasSpecificationsMixin,
1527- Specification,
1528- )
1529-from lp.blueprints.model.specificationsearch import search_specifications
1530 from lp.bugs.interfaces.bugsummary import IBugSummaryDimension
1531 from lp.bugs.interfaces.bugtarget import ISeriesBugTarget
1532 from lp.bugs.interfaces.bugtaskfilter import OrderedBugTask
1533@@ -180,7 +173,7 @@
1534 )
1535
1536
1537-class DistroSeries(SQLBase, BugTargetBase, HasSpecificationsMixin,
1538+class DistroSeries(SQLBase, BugTargetBase,
1539 HasTranslationImportsMixin, HasTranslationTemplatesMixin,
1540 HasMilestonesMixin, SeriesMixin,
1541 StructuralSubscriptionTargetMixin):
1542@@ -190,8 +183,6 @@
1543 IHasBuildRecords, IHasQueueItems, IServiceUsage,
1544 ISeriesBugTarget)
1545
1546- delegates(ISpecificationTarget, 'distribution')
1547-
1548 _table = 'DistroSeries'
1549 _defaultOrder = ['distribution', 'version']
1550
1551@@ -760,24 +751,6 @@
1552 """See `IHasBugs`."""
1553 return self.distribution.official_bug_tags
1554
1555- def specifications(self, user, sort=None, quantity=None, filter=None,
1556- need_people=True, need_branches=True,
1557- need_workitems=False):
1558- """See IHasSpecifications.
1559-
1560- In this case the rules for the default behaviour cover three things:
1561-
1562- - acceptance: if nothing is said, ACCEPTED only
1563- - completeness: if nothing is said, ANY
1564- - informationalness: if nothing is said, ANY
1565-
1566- """
1567- base_clauses = [Specification.distroseriesID == self.id]
1568- return search_specifications(
1569- self, base_clauses, user, sort, quantity, filter,
1570- default_acceptance=True, need_people=need_people,
1571- need_branches=need_branches, need_workitems=need_workitems)
1572-
1573 def getDistroSeriesLanguage(self, language):
1574 """See `IDistroSeries`."""
1575 return DistroSeriesLanguage.selectOneBy(
1576
1577=== modified file 'lib/lp/registry/model/person.py'
1578--- lib/lp/registry/model/person.py 2014-03-11 11:34:08 +0000
1579+++ lib/lp/registry/model/person.py 2014-03-28 05:15:47 +0000
1580@@ -122,14 +122,10 @@
1581 valid_name,
1582 )
1583 from lp.blueprints.enums import SpecificationFilter
1584-from lp.blueprints.model.specification import (
1585- HasSpecificationsMixin,
1586- Specification,
1587- )
1588+from lp.blueprints.model.specification import Specification
1589 from lp.blueprints.model.specificationsearch import (
1590 get_specification_active_product_filter,
1591 get_specification_privacy_filter,
1592- search_specifications,
1593 )
1594 from lp.blueprints.model.specificationworkitem import SpecificationWorkItem
1595 from lp.bugs.interfaces.bugtarget import IBugTarget
1596@@ -470,7 +466,7 @@
1597
1598
1599 class Person(
1600- SQLBase, HasBugsBase, HasSpecificationsMixin, HasTranslationImportsMixin,
1601+ SQLBase, HasBugsBase, HasTranslationImportsMixin,
1602 HasBranchesMixin, HasMergeProposalsMixin, HasRequestedReviewsMixin,
1603 QuestionsPersonMixin):
1604 """A Person."""
1605@@ -801,72 +797,19 @@
1606
1607 def findVisibleAssignedInProgressSpecs(self, user):
1608 """See `IPerson`."""
1609- return self.specifications(user, in_progress=True, quantity=5,
1610- sort=Desc(Specification.date_started),
1611- filter=[SpecificationFilter.ASSIGNEE])
1612+ from lp.blueprints.interfaces.specificationtarget import (
1613+ IHasSpecificationsOperations,
1614+ )
1615+ return IHasSpecificationsOperations(self).specifications(
1616+ user, in_progress=True, quantity=5,
1617+ sort=Desc(Specification.date_started),
1618+ filter=[SpecificationFilter.ASSIGNEE])
1619
1620 @property
1621 def unique_displayname(self):
1622 """See `IPerson`."""
1623 return "%s (%s)" % (self.displayname, self.name)
1624
1625- def specifications(self, user, sort=None, quantity=None, filter=None,
1626- in_progress=False, need_people=True, need_branches=True,
1627- need_workitems=False):
1628- """See `IHasSpecifications`."""
1629- from lp.blueprints.model.specificationsubscription import (
1630- SpecificationSubscription,
1631- )
1632- # Make a new copy of the filter, so that we do not mutate what we
1633- # were passed as a filter.
1634- if filter is None:
1635- filter = set()
1636- else:
1637- filter = set(filter)
1638-
1639- # Now look at the filter and fill in the unsaid bits.
1640-
1641- # Defaults for acceptance: in this case we have nothing to do
1642- # because specs are not accepted/declined against a person.
1643-
1644- # Defaults for informationalness: we don't have to do anything
1645- # because the default if nothing is said is ANY.
1646-
1647- roles = set([
1648- SpecificationFilter.CREATOR, SpecificationFilter.ASSIGNEE,
1649- SpecificationFilter.DRAFTER, SpecificationFilter.APPROVER,
1650- SpecificationFilter.SUBSCRIBER])
1651- # If no roles are given, then we want everything.
1652- if filter.intersection(roles) == set():
1653- filter.update(roles)
1654- role_clauses = []
1655- if SpecificationFilter.CREATOR in filter:
1656- role_clauses.append(Specification.owner == self)
1657- if SpecificationFilter.ASSIGNEE in filter:
1658- role_clauses.append(Specification._assignee == self)
1659- if SpecificationFilter.DRAFTER in filter:
1660- role_clauses.append(Specification._drafter == self)
1661- if SpecificationFilter.APPROVER in filter:
1662- role_clauses.append(Specification._approver == self)
1663- if SpecificationFilter.SUBSCRIBER in filter:
1664- role_clauses.append(
1665- Specification.id.is_in(
1666- Select(SpecificationSubscription.specificationID,
1667- [SpecificationSubscription.person == self])))
1668-
1669- clauses = [Or(*role_clauses)]
1670- if SpecificationFilter.COMPLETE not in filter:
1671- if (in_progress and SpecificationFilter.INCOMPLETE not in filter
1672- and SpecificationFilter.ALL not in filter):
1673- filter.update(
1674- [SpecificationFilter.INCOMPLETE,
1675- SpecificationFilter.STARTED])
1676-
1677- return search_specifications(
1678- self, clauses, user, sort, quantity, list(filter),
1679- need_people=need_people, need_branches=need_branches,
1680- need_workitems=need_workitems)
1681-
1682 # XXX: Tom Berger 2008-04-14 bug=191799:
1683 # The implementation of these functions
1684 # is no longer appropriate, since it now relies on subscriptions,
1685
1686=== modified file 'lib/lp/registry/model/product.py'
1687--- lib/lp/registry/model/product.py 2013-10-24 01:32:09 +0000
1688+++ lib/lp/registry/model/product.py 2014-03-28 05:15:47 +0000
1689@@ -91,12 +91,8 @@
1690 from lp.app.model.launchpad import InformationTypeMixin
1691 from lp.blueprints.enums import SpecificationFilter
1692 from lp.blueprints.model.specification import (
1693- HasSpecificationsMixin,
1694- Specification,
1695 SPECIFICATION_POLICY_ALLOWED_TYPES,
1696- SPECIFICATION_POLICY_DEFAULT_TYPES,
1697 )
1698-from lp.blueprints.model.specificationsearch import search_specifications
1699 from lp.blueprints.model.sprint import HasSprintsMixin
1700 from lp.bugs.interfaces.bugsummary import IBugSummaryDimension
1701 from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor
1702@@ -354,7 +350,7 @@
1703
1704
1705 class Product(SQLBase, BugTargetBase, MakesAnnouncements,
1706- HasDriversMixin, HasSpecificationsMixin, HasSprintsMixin,
1707+ HasDriversMixin, HasSprintsMixin,
1708 KarmaContextMixin, QuestionTargetMixin,
1709 HasTranslationImportsMixin, HasAliasMixin,
1710 StructuralSubscriptionTargetMixin, HasMilestonesMixin,
1711@@ -463,6 +459,9 @@
1712 changed. Has the side-effect of creating a commercial subscription if
1713 permitted.
1714 """
1715+ from lp.blueprints.interfaces.specificationtarget import (
1716+ IHasSpecificationsOperations,
1717+ )
1718 if value not in PUBLIC_PROPRIETARY_INFORMATION_TYPES:
1719 yield CannotChangeInformationType('Not supported for Projects.')
1720 if value in PROPRIETARY_INFORMATION_TYPES:
1721@@ -473,7 +472,7 @@
1722 # Additional checks when transitioning an existing product to a
1723 # proprietary type
1724 # All specs located by an ALL search are public.
1725- public_specs = self.specifications(
1726+ public_specs = IHasSpecificationsOperations(self).specifications(
1727 None, filter=[SpecificationFilter.ALL])
1728 if not public_specs.is_empty():
1729 # Unlike bugs and branches, specifications cannot be USERDATA or a
1730@@ -757,16 +756,6 @@
1731 """See `IProduct.`"""
1732 return BUG_POLICY_DEFAULT_TYPES[self.bug_sharing_policy]
1733
1734- def getAllowedSpecificationInformationTypes(self):
1735- """See `ISpecificationTarget`."""
1736- return SPECIFICATION_POLICY_ALLOWED_TYPES[
1737- self.specification_sharing_policy]
1738-
1739- def getDefaultSpecificationInformationType(self):
1740- """See `ISpecificationTarget`."""
1741- return SPECIFICATION_POLICY_DEFAULT_TYPES[
1742- self.specification_sharing_policy]
1743-
1744 def _ensurePolicies(self, information_types):
1745 # Ensure that the product has access policies for the specified
1746 # information types.
1747@@ -1417,20 +1406,6 @@
1748 # automatically shared.
1749 return True
1750
1751- def specifications(self, user, sort=None, quantity=None, filter=None,
1752- need_people=True, need_branches=True,
1753- need_workitems=False):
1754- """See `IHasSpecifications`."""
1755- base_clauses = [Specification.productID == self.id]
1756- return search_specifications(
1757- self, base_clauses, user, sort, quantity, filter,
1758- need_people=need_people, need_branches=need_branches,
1759- need_workitems=need_workitems)
1760-
1761- def getSpecification(self, name):
1762- """See `ISpecificationTarget`."""
1763- return Specification.selectOneBy(product=self, name=name)
1764-
1765 def getSeries(self, name):
1766 """See `IProduct`."""
1767 return ProductSeries.selectOneBy(product=self, name=name)
1768
1769=== modified file 'lib/lp/registry/model/productseries.py'
1770--- lib/lp/registry/model/productseries.py 2013-08-19 06:43:04 +0000
1771+++ lib/lp/registry/model/productseries.py 2014-03-28 05:15:47 +0000
1772@@ -13,7 +13,6 @@
1773
1774 import datetime
1775
1776-from lazr.delegates import delegates
1777 from sqlobject import (
1778 ForeignKey,
1779 SQLMultipleJoin,
1780@@ -38,12 +37,7 @@
1781 ILaunchpadCelebrities,
1782 IServiceUsage,
1783 )
1784-from lp.blueprints.interfaces.specificationtarget import ISpecificationTarget
1785-from lp.blueprints.model.specification import (
1786- HasSpecificationsMixin,
1787- Specification,
1788- )
1789-from lp.blueprints.model.specificationsearch import search_specifications
1790+from lp.blueprints.model.specification import Specification
1791 from lp.bugs.interfaces.bugsummary import IBugSummaryDimension
1792 from lp.bugs.interfaces.bugtarget import ISeriesBugTarget
1793 from lp.bugs.interfaces.bugtaskfilter import OrderedBugTask
1794@@ -111,7 +105,7 @@
1795
1796
1797 class ProductSeries(SQLBase, BugTargetBase, HasMilestonesMixin,
1798- HasSpecificationsMixin, HasTranslationImportsMixin,
1799+ HasTranslationImportsMixin,
1800 HasTranslationTemplatesMixin,
1801 StructuralSubscriptionTargetMixin, SeriesMixin):
1802 """A series of product releases."""
1803@@ -119,8 +113,6 @@
1804 IBugSummaryDimension, IProductSeries, IServiceUsage,
1805 ISeriesBugTarget)
1806
1807- delegates(ISpecificationTarget, 'product')
1808-
1809 _table = 'ProductSeries'
1810
1811 product = ForeignKey(dbName='product', foreignKey='Product', notNull=True)
1812@@ -315,25 +307,6 @@
1813 """See `IProductSeries`."""
1814 return self == self.product.development_focus
1815
1816- def specifications(self, user, sort=None, quantity=None, filter=None,
1817- need_people=True, need_branches=True,
1818- need_workitems=False):
1819- """See IHasSpecifications.
1820-
1821- The rules for filtering are that there are three areas where you can
1822- apply a filter:
1823-
1824- - acceptance, which defaults to ACCEPTED if nothing is said,
1825- - completeness, which defaults to showing BOTH if nothing is said
1826- - informational, which defaults to showing BOTH if nothing is said
1827-
1828- """
1829- base_clauses = [Specification.productseriesID == self.id]
1830- return search_specifications(
1831- self, base_clauses, user, sort, quantity, filter,
1832- default_acceptance=True, need_people=need_people,
1833- need_branches=need_branches, need_workitems=need_workitems)
1834-
1835 @property
1836 def all_specifications(self):
1837 return Store.of(self).find(
1838
1839=== modified file 'lib/lp/registry/model/projectgroup.py'
1840--- lib/lp/registry/model/projectgroup.py 2013-08-19 06:43:04 +0000
1841+++ lib/lp/registry/model/projectgroup.py 2014-03-28 05:15:47 +0000
1842@@ -45,11 +45,7 @@
1843 IHasMugshot,
1844 )
1845 from lp.blueprints.enums import SprintSpecificationStatus
1846-from lp.blueprints.model.specification import (
1847- HasSpecificationsMixin,
1848- Specification,
1849- )
1850-from lp.blueprints.model.specificationsearch import search_specifications
1851+from lp.blueprints.model.specification import Specification
1852 from lp.blueprints.model.sprint import HasSprintsMixin
1853 from lp.bugs.interfaces.bugsummary import IBugSummaryDimension
1854 from lp.bugs.model.bugtarget import (
1855@@ -105,7 +101,7 @@
1856 from lp.translations.model.translationpolicy import TranslationPolicyMixin
1857
1858
1859-class ProjectGroup(SQLBase, BugTargetBase, HasSpecificationsMixin,
1860+class ProjectGroup(SQLBase, BugTargetBase,
1861 MakesAnnouncements, HasSprintsMixin, HasAliasMixin,
1862 KarmaContextMixin, StructuralSubscriptionTargetMixin,
1863 HasBranchesMixin, HasMergeProposalsMixin,
1864@@ -243,24 +239,6 @@
1865 """ % sqlvalues(self, SprintSpecificationStatus.ACCEPTED)
1866 return query, ['Product', 'Specification', 'SprintSpecification']
1867
1868- def specifications(self, user, sort=None, quantity=None, filter=None,
1869- series=None, need_people=True, need_branches=True,
1870- need_workitems=False):
1871- """See `IHasSpecifications`."""
1872- base_clauses = [
1873- Specification.productID == Product.id,
1874- Product.projectID == self.id]
1875- tables = [Specification]
1876- if series:
1877- base_clauses.append(ProductSeries.name == series)
1878- tables.append(
1879- Join(ProductSeries,
1880- Specification.productseriesID == ProductSeries.id))
1881- return search_specifications(
1882- self, base_clauses, user, sort, quantity, filter, tables=tables,
1883- need_people=need_people, need_branches=need_branches,
1884- need_workitems=need_workitems)
1885-
1886 def _customizeSearchParams(self, search_params):
1887 """Customize `search_params` for this milestone."""
1888 search_params.setProject(self)
1889@@ -609,7 +587,7 @@
1890 query, distinct=True, clauseTables=clauseTables)
1891
1892
1893-class ProjectGroupSeries(HasSpecificationsMixin):
1894+class ProjectGroupSeries:
1895 """See `IProjectGroupSeries`."""
1896
1897 implements(IProjectGroupSeries)
1898@@ -618,13 +596,6 @@
1899 self.project = project
1900 self.name = name
1901
1902- def specifications(self, user, sort=None, quantity=None, filter=None,
1903- need_people=True, need_branches=True,
1904- need_workitems=False):
1905- return self.project.specifications(
1906- user, sort, quantity, filter, self.name, need_people=need_people,
1907- need_branches=need_branches, need_workitems=need_workitems)
1908-
1909 @property
1910 def title(self):
1911 return "%s Series %s" % (self.project.title, self.name)
1912
1913=== modified file 'lib/lp/registry/tests/test_distribution.py'
1914--- lib/lp/registry/tests/test_distribution.py 2013-08-01 14:09:45 +0000
1915+++ lib/lp/registry/tests/test_distribution.py 2014-03-28 05:15:47 +0000
1916@@ -29,6 +29,9 @@
1917 )
1918 from lp.app.errors import NotFoundError
1919 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
1920+from lp.blueprints.interfaces.specificationtarget import (
1921+ ISpecificationTargetOperations,
1922+ )
1923 from lp.registry.enums import (
1924 BranchSharingPolicy,
1925 BugSharingPolicy,
1926@@ -293,8 +296,8 @@
1927 distro = self.factory.makeDistribution()
1928 self.assertContentEqual(
1929 [InformationType.PUBLIC],
1930- distro.getAllowedSpecificationInformationTypes()
1931- )
1932+ ISpecificationTargetOperations(
1933+ distro).getAllowedSpecificationInformationTypes())
1934
1935 def test_getDefaultSpecificationInformtationType(self):
1936 # All distros currently support only Public by default
1937@@ -302,7 +305,8 @@
1938 distro = self.factory.makeDistribution()
1939 self.assertEqual(
1940 InformationType.PUBLIC,
1941- distro.getDefaultSpecificationInformationType())
1942+ ISpecificationTargetOperations(
1943+ distro).getDefaultSpecificationInformationType())
1944
1945
1946 class TestDistributionCurrentSourceReleases(
1947
1948=== modified file 'lib/lp/registry/tests/test_distroseries.py'
1949--- lib/lp/registry/tests/test_distroseries.py 2013-09-11 06:05:44 +0000
1950+++ lib/lp/registry/tests/test_distroseries.py 2014-03-28 05:15:47 +0000
1951@@ -16,6 +16,9 @@
1952 from zope.component import getUtility
1953 from zope.security.proxy import removeSecurityProxy
1954
1955+from lp.blueprints.interfaces.specificationtarget import (
1956+ IHasSpecificationsOperations,
1957+ )
1958 from lp.registry.errors import NoSuchDistroSeries
1959 from lp.registry.interfaces.distroseries import IDistroSeriesSet
1960 from lp.registry.interfaces.pocket import PackagePublishingPocket
1961@@ -352,7 +355,8 @@
1962 self.factory.makeSpecificationWorkItem(specification=spec)
1963 workitems = [
1964 s.workitems_text
1965- for s in distroseries.api_valid_specifications]
1966+ for s in IHasSpecificationsOperations(
1967+ distroseries).api_valid_specifications]
1968 self.assertContentEqual([spec.workitems_text], workitems)
1969
1970
1971
1972=== modified file 'lib/lp/registry/tests/test_person.py'
1973--- lib/lp/registry/tests/test_person.py 2014-01-08 07:18:59 +0000
1974+++ lib/lp/registry/tests/test_person.py 2014-03-28 05:15:47 +0000
1975@@ -33,6 +33,9 @@
1976 SpecificationPriority,
1977 SpecificationSort,
1978 )
1979+from lp.blueprints.interfaces.specificationtarget import (
1980+ IHasSpecificationsOperations,
1981+ )
1982 from lp.blueprints.model.specification import Specification
1983 from lp.bugs.interfaces.bugtasksearch import (
1984 get_person_bugtasks_search_params,
1985@@ -1759,10 +1762,16 @@
1986
1987
1988 def list_result(sprint, filter=None, user=None):
1989- result = sprint.specifications(user, SpecificationSort.DATE, filter=filter)
1990+ result = IHasSpecificationsOperations(sprint).specifications(
1991+ user, SpecificationSort.DATE, filter=filter)
1992 return list(result)
1993
1994
1995+def get_specs(context, user=None, **kwargs):
1996+ return IHasSpecificationsOperations(context).specifications(
1997+ user, **kwargs)
1998+
1999+
2000 class TestSpecifications(TestCaseWithFactory):
2001
2002 layer = DatabaseFunctionalLayer
2003@@ -1787,11 +1796,10 @@
2004 owner = self.factory.makePerson()
2005 for count in range(10):
2006 self.factory.makeSpecification(owner=owner)
2007- self.assertEqual(10, owner.specifications(None).count())
2008- result = owner.specifications(None, quantity=None).count()
2009- self.assertEqual(10, result)
2010- self.assertEqual(8, owner.specifications(None, quantity=8).count())
2011- self.assertEqual(10, owner.specifications(None, quantity=11).count())
2012+ self.assertEqual(10, get_specs(owner).count())
2013+ self.assertEqual(10, get_specs(owner, quantity=None).count())
2014+ self.assertEqual(8, get_specs(owner, quantity=8).count())
2015+ self.assertEqual(10, get_specs(owner, quantity=11).count())
2016
2017 def test_date_sort(self):
2018 # Sort on date_created.
2019@@ -1823,9 +1831,9 @@
2020 blueprint3 = self.makeSpec(
2021 owner, priority=SpecificationPriority.LOW,
2022 status=SpecificationDefinitionStatus.NEW)
2023- result = owner.specifications(None)
2024+ result = get_specs(owner)
2025 self.assertEqual([blueprint3, blueprint1, blueprint2], list(result))
2026- result = owner.specifications(None, sort=SpecificationSort.PRIORITY)
2027+ result = get_specs(owner, sort=SpecificationSort.PRIORITY)
2028 self.assertEqual([blueprint3, blueprint1, blueprint2], list(result))
2029
2030 def test_priority_sort_fallback_status(self):
2031@@ -1838,9 +1846,9 @@
2032 owner, status=SpecificationDefinitionStatus.APPROVED, name='c')
2033 blueprint3 = self.makeSpec(
2034 owner, status=SpecificationDefinitionStatus.DISCUSSION, name='b')
2035- result = owner.specifications(None)
2036+ result = get_specs(owner)
2037 self.assertEqual([blueprint2, blueprint3, blueprint1], list(result))
2038- result = owner.specifications(None, sort=SpecificationSort.PRIORITY)
2039+ result = get_specs(owner, sort=SpecificationSort.PRIORITY)
2040 self.assertEqual([blueprint2, blueprint3, blueprint1], list(result))
2041
2042 def test_priority_sort_fallback_name(self):
2043@@ -1849,9 +1857,9 @@
2044 owner = blueprint1.owner
2045 blueprint2 = self.makeSpec(owner, name='c')
2046 blueprint3 = self.makeSpec(owner, name='a')
2047- result = owner.specifications(None)
2048+ result = get_specs(owner)
2049 self.assertEqual([blueprint3, blueprint1, blueprint2], list(result))
2050- result = owner.specifications(None, sort=SpecificationSort.PRIORITY)
2051+ result = get_specs(owner, sort=SpecificationSort.PRIORITY)
2052 self.assertEqual([blueprint3, blueprint1, blueprint2], list(result))
2053
2054 def test_ignore_inactive(self):
2055@@ -1860,13 +1868,13 @@
2056 with celebrity_logged_in('admin'):
2057 product.active = False
2058 spec = self.factory.makeSpecification(product=product)
2059- self.assertNotIn(spec, spec.owner.specifications(None))
2060+ self.assertNotIn(spec, get_specs(spec.owner))
2061
2062 def test_include_distro(self):
2063 # Specs for distributions are included.
2064 distribution = self.factory.makeDistribution()
2065 spec = self.factory.makeSpecification(distribution=distribution)
2066- self.assertIn(spec, spec.owner.specifications(None))
2067+ self.assertIn(spec, get_specs(spec.owner))
2068
2069 def test_informational(self):
2070 # INFORMATIONAL causes only informational specs to be shown.
2071@@ -1875,11 +1883,10 @@
2072 implementation_status=enum.INFORMATIONAL)
2073 owner = informational.owner
2074 plain = self.factory.makeSpecification(owner=owner)
2075- result = owner.specifications(None)
2076+ result = get_specs(owner)
2077 self.assertIn(informational, result)
2078 self.assertIn(plain, result)
2079- result = owner.specifications(
2080- None, filter=[SpecificationFilter.INFORMATIONAL])
2081+ result = get_specs(owner, filter=[SpecificationFilter.INFORMATIONAL])
2082 self.assertIn(informational, result)
2083 self.assertNotIn(plain, result)
2084
2085@@ -1892,17 +1899,13 @@
2086 implementation_status=enum.IMPLEMENTED)
2087 owner = implemented.owner
2088 non_implemented = self.factory.makeSpecification(owner=owner)
2089- result = owner.specifications(
2090- None, filter=[SpecificationFilter.COMPLETE])
2091+ result = get_specs(owner, filter=[SpecificationFilter.COMPLETE])
2092 self.assertIn(implemented, result)
2093 self.assertNotIn(non_implemented, result)
2094-
2095- result = owner.specifications(
2096- None, filter=[SpecificationFilter.INCOMPLETE])
2097+ result = get_specs(owner, filter=[SpecificationFilter.INCOMPLETE])
2098 self.assertNotIn(implemented, result)
2099 self.assertIn(non_implemented, result)
2100- result = owner.specifications(
2101- None)
2102+ result = get_specs(owner)
2103 self.assertNotIn(implemented, result)
2104 self.assertIn(non_implemented, result)
2105
2106@@ -1913,7 +1916,7 @@
2107 implementation_status=enum.IMPLEMENTED)
2108 owner = implemented.owner
2109 non_implemented = self.factory.makeSpecification(owner=owner)
2110- result = owner.specifications(None, filter=[SpecificationFilter.ALL])
2111+ result = get_specs(owner, filter=[SpecificationFilter.ALL])
2112 self.assertContentEqual([implemented, non_implemented], result)
2113
2114 def test_valid(self):
2115@@ -1926,8 +1929,9 @@
2116 owner = implemented.owner
2117 self.factory.makeSpecification(owner=owner, status=d_enum.SUPERSEDED)
2118 self.factory.makeSpecification(owner=owner, status=d_enum.OBSOLETE)
2119- filter = [SpecificationFilter.VALID, SpecificationFilter.COMPLETE]
2120- results = owner.specifications(None, filter=filter)
2121+ results = get_specs(
2122+ owner,
2123+ filter=[SpecificationFilter.VALID, SpecificationFilter.COMPLETE])
2124 self.assertContentEqual([implemented], results)
2125
2126 def test_roles(self):
2127@@ -1937,7 +1941,7 @@
2128 person = created.owner
2129
2130 def rlist(filter=None):
2131- return list(person.specifications(None, filter=filter))
2132+ return list(get_specs(person, filter=filter))
2133 assigned = self.factory.makeSpecification(assignee=person)
2134 drafting = self.factory.makeSpecification(drafter=person)
2135 approving = self.factory.makeSpecification(approver=person)
2136@@ -1996,9 +2000,9 @@
2137 spec = self.factory.makeSpecification(owner=owner, name='a')
2138 spec2 = self.factory.makeSpecification(owner=owner, name='z')
2139 spec3 = self.factory.makeSpecification(owner=owner, name='b')
2140- self.assertEqual([spec2, spec3, spec],
2141- list(owner.specifications(owner,
2142- sort=Desc(Specification.name))))
2143+ self.assertContentEqual(
2144+ [spec2, spec3, spec],
2145+ get_specs(owner, sort=Desc(Specification.name)))
2146
2147 def test_in_progress(self):
2148 # In-progress filters to exclude not-started and completed.
2149@@ -2010,18 +2014,18 @@
2150 owner=owner, implementation_status=enum.STARTED)
2151 self.factory.makeSpecification(
2152 owner=owner, implementation_status=enum.IMPLEMENTED)
2153- specs = list(owner.specifications(owner, in_progress=True))
2154- self.assertEqual([started], specs)
2155+ self.assertContentEqual([started], get_specs(owner, in_progress=True))
2156
2157 def test_in_progress_all(self):
2158 # SpecificationFilter.ALL overrides in_progress.
2159 enum = SpecificationImplementationStatus
2160 notstarted = self.factory.makeSpecification(
2161 implementation_status=enum.NOTSTARTED)
2162- owner = notstarted.owner
2163- specs = list(owner.specifications(
2164- owner, filter=[SpecificationFilter.ALL], in_progress=True))
2165- self.assertEqual([notstarted], specs)
2166+ self.assertContentEqual(
2167+ [notstarted],
2168+ get_specs(
2169+ notstarted.owner, filter=[SpecificationFilter.ALL],
2170+ in_progress=True))
2171
2172 def test_complete_overrides_in_progress(self):
2173 # SpecificationFilter.COMPLETE overrides in_progress.
2174@@ -2031,17 +2035,16 @@
2175 owner = started.owner
2176 implemented = self.factory.makeSpecification(
2177 implementation_status=enum.IMPLEMENTED, owner=owner)
2178- specs = list(owner.specifications(
2179- owner, filter=[SpecificationFilter.COMPLETE], in_progress=True))
2180- self.assertEqual([implemented], specs)
2181+ specs = get_specs(
2182+ owner, filter=[SpecificationFilter.COMPLETE], in_progress=True)
2183+ self.assertContentEqual([implemented], specs)
2184
2185 def test_incomplete_overrides_in_progress(self):
2186 # SpecificationFilter.INCOMPLETE overrides in_progress.
2187 enum = SpecificationImplementationStatus
2188 notstarted = self.factory.makeSpecification(
2189 implementation_status=enum.NOTSTARTED)
2190- owner = notstarted.owner
2191- specs = list(owner.specifications(
2192- owner, filter=[SpecificationFilter.INCOMPLETE],
2193- in_progress=True))
2194- self.assertEqual([notstarted], specs)
2195+ specs = get_specs(
2196+ notstarted.owner, filter=[SpecificationFilter.INCOMPLETE],
2197+ in_progress=True)
2198+ self.assertContentEqual([notstarted], specs)
2199
2200=== modified file 'lib/lp/registry/tests/test_product.py'
2201--- lib/lp/registry/tests/test_product.py 2014-02-25 08:13:02 +0000
2202+++ lib/lp/registry/tests/test_product.py 2014-03-28 05:15:47 +0000
2203@@ -50,6 +50,10 @@
2204 SpecificationPriority,
2205 SpecificationSort,
2206 )
2207+from lp.blueprints.interfaces.specificationtarget import (
2208+ IHasSpecificationsOperations,
2209+ ISpecificationTargetOperations,
2210+ )
2211 from lp.blueprints.model.specification import (
2212 SPECIFICATION_POLICY_ALLOWED_TYPES,
2213 )
2214@@ -1291,12 +1295,12 @@
2215 with person_logged_in(product.owner):
2216 product.setSpecificationSharingPolicy(
2217 specification_sharing_policy)
2218- return product
2219+ return ISpecificationTargetOperations(product)
2220
2221 def test_no_policy(self):
2222 # Projects that have not specified a policy can use the PUBLIC
2223 # information type.
2224- product = self.factory.makeProduct()
2225+ product = ISpecificationTargetOperations(self.factory.makeProduct())
2226 self.assertContentEqual(
2227 [InformationType.PUBLIC],
2228 product.getAllowedSpecificationInformationTypes())
2229@@ -1940,11 +1944,16 @@
2230
2231
2232 def list_result(product, filter=None, user=None):
2233- result = product.specifications(
2234+ result = IHasSpecificationsOperations(product).specifications(
2235 user, SpecificationSort.DATE, filter=filter)
2236 return list(result)
2237
2238
2239+def get_specs(product, user=None, **kwargs):
2240+ return IHasSpecificationsOperations(product).specifications(
2241+ user, **kwargs)
2242+
2243+
2244 class TestSpecifications(TestCaseWithFactory):
2245
2246 layer = DatabaseFunctionalLayer
2247@@ -1969,11 +1978,10 @@
2248 product = self.factory.makeProduct()
2249 for count in range(10):
2250 self.factory.makeSpecification(product=product)
2251- self.assertEqual(10, product.specifications(None).count())
2252- result = product.specifications(None, quantity=None).count()
2253- self.assertEqual(10, result)
2254- self.assertEqual(8, product.specifications(None, quantity=8).count())
2255- self.assertEqual(10, product.specifications(None, quantity=11).count())
2256+ self.assertEqual(10, get_specs(product).count())
2257+ self.assertEqual(10, get_specs(product, quantity=None).count())
2258+ self.assertEqual(8, get_specs(product, quantity=8).count())
2259+ self.assertEqual(10, get_specs(product, quantity=11).count())
2260
2261 def test_date_sort(self):
2262 # Sort on date_created.
2263@@ -2005,9 +2013,9 @@
2264 blueprint3 = self.makeSpec(
2265 product, priority=SpecificationPriority.LOW,
2266 status=SpecificationDefinitionStatus.NEW)
2267- result = product.specifications(None)
2268+ result = get_specs(product)
2269 self.assertEqual([blueprint3, blueprint1, blueprint2], list(result))
2270- result = product.specifications(None, sort=SpecificationSort.PRIORITY)
2271+ result = get_specs(product, sort=SpecificationSort.PRIORITY)
2272 self.assertEqual([blueprint3, blueprint1, blueprint2], list(result))
2273
2274 def test_priority_sort_fallback_status(self):
2275@@ -2020,9 +2028,9 @@
2276 product, status=SpecificationDefinitionStatus.APPROVED, name='c')
2277 blueprint3 = self.makeSpec(
2278 product, status=SpecificationDefinitionStatus.DISCUSSION, name='b')
2279- result = product.specifications(None)
2280+ result = get_specs(product)
2281 self.assertEqual([blueprint2, blueprint3, blueprint1], list(result))
2282- result = product.specifications(None, sort=SpecificationSort.PRIORITY)
2283+ result = get_specs(product, sort=SpecificationSort.PRIORITY)
2284 self.assertEqual([blueprint2, blueprint3, blueprint1], list(result))
2285
2286 def test_priority_sort_fallback_name(self):
2287@@ -2031,9 +2039,9 @@
2288 product = blueprint1.product
2289 blueprint2 = self.makeSpec(product, name='c')
2290 blueprint3 = self.makeSpec(product, name='a')
2291- result = product.specifications(None)
2292+ result = get_specs(product)
2293 self.assertEqual([blueprint3, blueprint1, blueprint2], list(result))
2294- result = product.specifications(None, sort=SpecificationSort.PRIORITY)
2295+ result = get_specs(product, sort=SpecificationSort.PRIORITY)
2296 self.assertEqual([blueprint3, blueprint1, blueprint2], list(result))
2297
2298 def test_informational(self):
2299@@ -2043,11 +2051,10 @@
2300 implementation_status=enum.INFORMATIONAL)
2301 product = informational.product
2302 plain = self.factory.makeSpecification(product=product)
2303- result = product.specifications(None)
2304+ result = get_specs(product)
2305 self.assertIn(informational, result)
2306 self.assertIn(plain, result)
2307- result = product.specifications(
2308- None, filter=[SpecificationFilter.INFORMATIONAL])
2309+ result = get_specs(product, filter=[SpecificationFilter.INFORMATIONAL])
2310 self.assertIn(informational, result)
2311 self.assertNotIn(plain, result)
2312
2313@@ -2060,17 +2067,14 @@
2314 implementation_status=enum.IMPLEMENTED)
2315 product = implemented.product
2316 non_implemented = self.factory.makeSpecification(product=product)
2317- result = product.specifications(
2318- None, filter=[SpecificationFilter.COMPLETE])
2319+ result = get_specs(product, filter=[SpecificationFilter.COMPLETE])
2320 self.assertIn(implemented, result)
2321 self.assertNotIn(non_implemented, result)
2322
2323- result = product.specifications(
2324- None, filter=[SpecificationFilter.INCOMPLETE])
2325+ result = get_specs(product, filter=[SpecificationFilter.INCOMPLETE])
2326 self.assertNotIn(implemented, result)
2327 self.assertIn(non_implemented, result)
2328- result = product.specifications(
2329- None)
2330+ result = get_specs(product)
2331 self.assertNotIn(implemented, result)
2332 self.assertIn(non_implemented, result)
2333
2334@@ -2081,7 +2085,7 @@
2335 implementation_status=enum.IMPLEMENTED)
2336 product = implemented.product
2337 non_implemented = self.factory.makeSpecification(product=product)
2338- result = product.specifications(None, filter=[SpecificationFilter.ALL])
2339+ result = get_specs(product, filter=[SpecificationFilter.ALL])
2340 self.assertContentEqual([implemented, non_implemented], result)
2341
2342 def test_valid(self):
2343@@ -2096,7 +2100,7 @@
2344 status=d_enum.SUPERSEDED)
2345 self.factory.makeSpecification(product=product, status=d_enum.OBSOLETE)
2346 filter = [SpecificationFilter.VALID, SpecificationFilter.COMPLETE]
2347- results = product.specifications(None, filter=filter)
2348+ results = get_specs(product, filter=filter)
2349 self.assertContentEqual([implemented], results)
2350
2351 def test_text_search(self):
2352
2353=== modified file 'lib/lp/security.py'
2354--- lib/lp/security.py 2014-03-17 21:50:33 +0000
2355+++ lib/lp/security.py 2014-03-28 05:15:47 +0000
2356@@ -45,6 +45,9 @@
2357 from lp.blueprints.interfaces.specificationsubscription import (
2358 ISpecificationSubscription,
2359 )
2360+from lp.blueprints.interfaces.specificationtarget import (
2361+ IHasSpecificationsOperations,
2362+ )
2363 from lp.blueprints.interfaces.sprint import ISprint
2364 from lp.blueprints.interfaces.sprintspecification import ISprintSpecification
2365 from lp.blueprints.model.specificationsubscription import (
2366@@ -552,6 +555,28 @@
2367 return True
2368
2369
2370+class ViewHasSpecificationsOperations(DelegatedAuthorization):
2371+ """Delegator for IHasSpecificationsOperations adapters."""
2372+
2373+ permission = 'launchpad.View'
2374+ usedfor = IHasSpecificationsOperations
2375+
2376+ def __init__(self, obj):
2377+ super(ViewHasSpecificationsOperations, self).__init__(
2378+ obj, obj.context)
2379+
2380+
2381+class LimitedViewHasSpecificationsOperations(DelegatedAuthorization):
2382+ """Delegator for IHasSpecificationsOperations adapters."""
2383+
2384+ permission = 'launchpad.LimitedView'
2385+ usedfor = IHasSpecificationsOperations
2386+
2387+ def __init__(self, obj):
2388+ super(LimitedViewHasSpecificationsOperations, self).__init__(
2389+ obj, obj.context)
2390+
2391+
2392 class AnonymousAccessToISpecificationPublic(AnonymousAuthorization):
2393 """Anonymous users have launchpad.View on ISpecificationPublic.
2394