Merge lp:~abentley/launchpad/hide-sprint-blueprints into lp:launchpad

Proposed by Aaron Bentley
Status: Merged
Approved by: j.c.sackett
Approved revision: no longer in the source branch.
Merged at revision: 16057
Proposed branch: lp:~abentley/launchpad/hide-sprint-blueprints
Merge into: lp:launchpad
Prerequisite: lp:~abentley/launchpad/specification-cleanup
Diff against target: 1182 lines (+193/-119)
29 files modified
lib/lp/app/browser/root.py (+1/-1)
lib/lp/blueprints/browser/specification.py (+4/-4)
lib/lp/blueprints/browser/specificationtarget.py (+7/-6)
lib/lp/blueprints/browser/sprint.py (+2/-2)
lib/lp/blueprints/browser/tests/test_sprint.py (+13/-0)
lib/lp/blueprints/doc/specification.txt (+2/-2)
lib/lp/blueprints/doc/sprint.txt (+6/-6)
lib/lp/blueprints/interfaces/specification.py (+1/-1)
lib/lp/blueprints/interfaces/specificationtarget.py (+2/-1)
lib/lp/blueprints/model/specification.py (+10/-6)
lib/lp/blueprints/model/sprint.py (+14/-7)
lib/lp/blueprints/model/tests/test_sprint.py (+47/-14)
lib/lp/blueprints/templates/person-specworkload.pt (+1/-1)
lib/lp/blueprints/vocabularies/specification.py (+2/-1)
lib/lp/registry/browser/person.py (+5/-2)
lib/lp/registry/doc/distribution.txt (+8/-8)
lib/lp/registry/doc/distroseries.txt (+9/-9)
lib/lp/registry/doc/person-account.txt (+1/-1)
lib/lp/registry/doc/person.txt (+9/-9)
lib/lp/registry/doc/product.txt (+4/-4)
lib/lp/registry/doc/productseries.txt (+10/-10)
lib/lp/registry/doc/projectgroup.txt (+14/-12)
lib/lp/registry/model/distribution.py (+1/-1)
lib/lp/registry/model/distroseries.py (+1/-1)
lib/lp/registry/model/person.py (+1/-1)
lib/lp/registry/model/product.py (+1/-1)
lib/lp/registry/model/productseries.py (+1/-1)
lib/lp/registry/model/projectgroup.py (+4/-3)
lib/lp/testing/factory.py (+12/-4)
To merge this branch: bzr merge lp:~abentley/launchpad/hide-sprint-blueprints
Reviewer Review Type Date Requested Status
j.c.sackett (community) Approve
Review via email: mp+126792@code.launchpad.net

Commit message

Respect privacy listing specifications for sprints.

Description of the change

= Summary =
Fix bug #1051029: Fix meeting listings with PROPRIETARY specs

== Proposed fix ==
Sprint.specification filters un-viewable specs out of listings

== Pre-implementation notes ==
None

== LOC Rationale ==
Part of private projects

== Implementation details ==
IHasSpecifications.specifications() and all implementations accept a user as a mandatory parameter (but that user may be None if no Person is logged in).

Sprint.specifications uses the user to invoke get_specification_privacy_filter()

All callers are updated to supply a user. View methods supply self.user, and model methods accept an additional parameter. _all_specifications and _valid_specifications, as model properties, cannot accept parameters, so they use ILaunchBag.user.

== Tests ==
Everything

== Demo and Q/A ==
Create a PROPRIETARY blueprint. Link it to a sprint. Go to the sprint page. You should see the blueprint. Log out. It should stop being listed, but the page should not be broken.

= Launchpad lint =

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/blueprints/templates/specifications-portlet-latestregistered.pt
  lib/lp/blueprints/tests/test_hasspecifications.py
  lib/lp/services/database/stormexpr.py
  lib/lp/registry/doc/milestone.txt
  lib/lp/registry/model/projectgroup.py
  lib/lp/registry/doc/distribution.txt
  lib/lp/registry/model/productseries.py
  lib/lp/blueprints/interfaces/specificationtarget.py
  lib/lp/app/browser/root.py
  lib/lp/blueprints/templates/specificationtarget-assignments.pt
  lib/lp/blueprints/browser/specificationtarget.py
  lib/lp/blueprints/model/tests/test_sprint.py
  lib/lp/bugs/model/tests/test_bugtask.py
  lib/lp/blueprints/templates/person-specworkload.pt
  lib/lp/blueprints/browser/sprint.py
  lib/lp/blueprints/templates/specifications-portlet-stats.pt
  lib/lp/testing/factory.py
  lib/lp/registry/doc/projectgroup.txt
  lib/lp/blueprints/model/specification.py
  lib/lp/registry/browser/__init__.py
  lib/lp/blueprints/model/sprint.py
  lib/lp/blueprints/templates/specifications-portlet-latestcompleted.pt
  lib/lp/testing/_webservice.py
  lib/lp/registry/model/product.py
  lib/lp/blueprints/browser/configure.zcml
  lib/lp/blueprints/browser/tests/test_sprint.py
  lib/lp/blueprints/doc/specification.txt
  lib/lp/blueprints/browser/specification.py
  lib/lp/registry/browser/person.py
  lib/lp/registry/model/person.py
  lib/lp/registry/model/distroseries.py
  lib/lp/_schema_circular_imports.py
  lib/lp/registry/model/distribution.py
  lib/lp/blueprints/interfaces/specification.py

./lib/lp/registry/model/product.py
     408: redefinition of function 'date_next_suggest_packaging' from line 400

^^^ This is an acceptable case of re-definition. It's creating a setter.

To post a comment you must log in.
Revision history for this message
j.c.sackett (jcsackett) wrote :

This looks alright.

As one minor point, it might be cleaner to set the default argument for user in `specifications` to None, rather than passing in None everytime you need it. But I don't see that as a blocker to approval--it's just a thought.

review: Approve
Revision history for this message
Aaron Bentley (abentley) wrote :

> As one minor point, it might be cleaner to set the default argument for user
> in `specifications` to None, rather than passing in None everytime you need
> it. But I don't see that as a blocker to approval--it's just a thought.

I don't think it would be cleaner to set a default, because it would encourage mistakes and sloppy use. The fact is that you need to decide what user to use in all cases. What's worse, mistakes will cause only subtle differences and may go undetected. Deciding to use None is valid, but in most cases you'll be passing in a variable that may or may not have been set to None, like ILaunchBag.user or LaunchpadView.user.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/app/browser/root.py'
2--- lib/lp/app/browser/root.py 2012-09-28 14:42:25 +0000
3+++ lib/lp/app/browser/root.py 2012-09-28 14:42:25 +0000
4@@ -130,7 +130,7 @@
5 @property
6 def blueprint_count(self):
7 """The total blueprint count in all of Launchpad."""
8- return getUtility(ISpecificationSet).specificationCount()
9+ return getUtility(ISpecificationSet).specificationCount(self.user)
10
11 @property
12 def answer_count(self):
13
14=== modified file 'lib/lp/blueprints/browser/specification.py'
15--- lib/lp/blueprints/browser/specification.py 2012-09-28 14:42:25 +0000
16+++ lib/lp/blueprints/browser/specification.py 2012-09-28 14:42:25 +0000
17@@ -1036,7 +1036,7 @@
18 """Override the setup to define own fields."""
19 if self.context.target is None:
20 raise AssertionError("No target found for this spec.")
21- specs = sorted(self.context.target.specifications(),
22+ specs = sorted(self.context.target.specifications(self.user),
23 key=attrgetter('name'))
24 terms = [SimpleTerm(spec, spec.name, spec.title)
25 for spec in specs if spec != self.context]
26@@ -1530,17 +1530,17 @@
27 @property
28 def latest_specifications(self):
29 return self.context.specifications(
30- sort=SpecificationSort.DATE, quantity=5)
31+ self.user, sort=SpecificationSort.DATE, quantity=5)
32
33 @property
34 def latest_completed_specifications(self):
35 return self.context.specifications(
36- sort=SpecificationSort.DATE, quantity=5,
37+ self.user, sort=SpecificationSort.DATE, quantity=5,
38 filter=[SpecificationFilter.COMPLETE])
39
40 @property
41 def specification_count(self):
42- return self.context.specificationCount()
43+ return self.context.specificationCount(self.user)
44
45 @safe_action
46 @action('Find blueprints', name="search")
47
48=== modified file 'lib/lp/blueprints/browser/specificationtarget.py'
49--- lib/lp/blueprints/browser/specificationtarget.py 2012-09-28 14:42:25 +0000
50+++ lib/lp/blueprints/browser/specificationtarget.py 2012-09-28 14:42:25 +0000
51@@ -259,11 +259,11 @@
52
53 @cachedproperty
54 def has_any_specifications(self):
55- return self.context._all_specifications.count() != 0
56+ return not self.context._all_specifications.is_empty()
57
58 @cachedproperty
59 def all_specifications(self):
60- return shortlist(self.context.all_specifications)
61+ return shortlist(self.context.all_specifications(self.user))
62
63 @cachedproperty
64 def searchrequested(self):
65@@ -348,7 +348,7 @@
66 and not check_permission('launchpad.View', self.context)):
67 return []
68 filter = self.spec_filter
69- return self.context.specifications(filter=filter)
70+ return self.context.specifications(self.user, filter=filter)
71
72 @cachedproperty
73 def specs_batched(self):
74@@ -364,7 +364,7 @@
75 def documentation(self):
76 filter = [SpecificationFilter.COMPLETE,
77 SpecificationFilter.INFORMATIONAL]
78- return shortlist(self.context.specifications(filter=filter))
79+ return shortlist(self.context.specifications(self.user, filter=filter))
80
81 @cachedproperty
82 def categories(self):
83@@ -405,8 +405,9 @@
84 Only ACCEPTED specifications are returned. This list is used by the
85 +portlet-latestspecs view.
86 """
87- return self.context.specifications(sort=SpecificationSort.DATE,
88- quantity=quantity, prejoin_people=False)
89+ return self.context.specifications(self.user,
90+ sort=SpecificationSort.DATE, quantity=quantity,
91+ prejoin_people=False)
92
93
94 class SpecificationAssignmentsView(HasSpecificationsView):
95
96=== modified file 'lib/lp/blueprints/browser/sprint.py'
97--- lib/lp/blueprints/browser/sprint.py 2012-01-01 02:58:52 +0000
98+++ lib/lp/blueprints/browser/sprint.py 2012-09-28 14:42:25 +0000
99@@ -216,7 +216,7 @@
100 @cachedproperty
101 def latest_approved(self):
102 filter = [SpecificationFilter.ACCEPTED]
103- return self.context.specifications(filter=filter,
104+ return self.context.specifications(self.user, filter=filter,
105 quantity=self.latest_specs_limit,
106 sort=SpecificationSort.DATE)
107
108@@ -464,7 +464,7 @@
109
110 model_specs = []
111 for spec in self.context.specifications(
112- filter=[SpecificationFilter.ACCEPTED]):
113+ self.user, filter=[SpecificationFilter.ACCEPTED]):
114
115 # skip sprints with no priority or less than low:
116 if spec.priority < SpecificationPriority.UNDEFINED:
117
118=== modified file 'lib/lp/blueprints/browser/tests/test_sprint.py'
119--- lib/lp/blueprints/browser/tests/test_sprint.py 2012-09-28 14:42:25 +0000
120+++ lib/lp/blueprints/browser/tests/test_sprint.py 2012-09-28 14:42:25 +0000
121@@ -8,6 +8,7 @@
122 from storm.locals import Store
123 from testtools.matchers import Equals
124
125+from lp.app.enums import InformationType
126 from lp.testing import BrowserTestCase
127 from lp.testing.layers import DatabaseFunctionalLayer
128 from lp.testing.matchers import BrowsesWithQueryLimit, HasQueryCount
129@@ -40,3 +41,15 @@
130 with QueryCollector() as recorder:
131 self.getViewBrowser(sprint)
132 self.assertThat(recorder, HasQueryCount(Equals(30)))
133+
134+ def test_proprietary_blueprint_listing_query_count(self):
135+ """Set a maximum number of queries for sprint blueprint lists."""
136+ sprint = self.factory.makeSprint()
137+ for count in range(10):
138+ blueprint = self.factory.makeSpecification(
139+ information_type=InformationType.PROPRIETARY)
140+ link = blueprint.linkSprint(sprint, blueprint.owner)
141+ link.acceptBy(sprint.owner)
142+ with QueryCollector() as recorder:
143+ self.getViewBrowser(sprint)
144+ self.assertThat(recorder, HasQueryCount(Equals(22)))
145
146=== modified file 'lib/lp/blueprints/doc/specification.txt'
147--- lib/lp/blueprints/doc/specification.txt 2012-09-28 14:42:25 +0000
148+++ lib/lp/blueprints/doc/specification.txt 2012-09-28 14:42:25 +0000
149@@ -222,7 +222,7 @@
150 We can filter for specifications that contain specific text, across all
151 specifications:
152
153- >>> for spec in specset.specifications(filter=['install']):
154+ >>> for spec in specset.specifications(None, filter=['install']):
155 ... print spec.name, spec.target.name
156 cluster-installation kubuntu
157 extension-manager-upgrades firefox
158@@ -239,7 +239,7 @@
159 >>> unlink_source_packages(upstream_firefox)
160 >>> upstream_firefox.active = False
161 >>> flush_database_updates()
162- >>> for spec in specset.specifications(filter=['install']):
163+ >>> for spec in specset.specifications(None, filter=['install']):
164 ... print spec.name, spec.target.name
165 cluster-installation kubuntu
166 media-integrity-check ubuntu
167
168=== modified file 'lib/lp/blueprints/doc/sprint.txt'
169--- lib/lp/blueprints/doc/sprint.txt 2011-12-30 06:14:56 +0000
170+++ lib/lp/blueprints/doc/sprint.txt 2012-09-28 14:42:25 +0000
171@@ -118,19 +118,19 @@
172 First, there should be no informational specs for ubz:
173
174 >>> filter = [SpecificationFilter.INFORMATIONAL]
175- >>> ubz.specifications(filter=filter).count()
176+ >>> ubz.specifications(None, filter=filter).count()
177 1
178
179 There are 0 completed specs for UBZ:
180
181 >>> filter = [SpecificationFilter.COMPLETE]
182- >>> ubz.specifications(filter=filter).count()
183+ >>> ubz.specifications(None, filter=filter).count()
184 0
185
186 And there are three incomplete specs:
187
188 >>> filter = [SpecificationFilter.INCOMPLETE]
189- >>> for spec in ubz.specifications(filter=filter):
190+ >>> for spec in ubz.specifications(None, filter=filter):
191 ... print spec.name, spec.is_complete
192 svg-support False
193 extension-manager-upgrades False
194@@ -139,7 +139,7 @@
195 If we ask for all specs, we get them in the order of priority.
196
197 >>> filter = [SpecificationFilter.ALL]
198- >>> for spec in ubz.specifications(filter=filter):
199+ >>> for spec in ubz.specifications(None, filter=filter):
200 ... print spec.priority.title, spec.name
201 High svg-support
202 Medium extension-manager-upgrades
203@@ -147,7 +147,7 @@
204
205 And if we ask just for specs, we get them all
206
207- >>> for spec in ubz.specifications():
208+ >>> for spec in ubz.specifications(None):
209 ... print spec.name, spec.is_complete
210 svg-support False
211 extension-manager-upgrades False
212@@ -167,7 +167,7 @@
213 >>> unlink_source_packages(firefox)
214 >>> firefox.active = False
215 >>> flush_database_updates()
216- >>> ubz.specifications().count()
217+ >>> ubz.specifications(None).count()
218 0
219
220 Reset firefox so we don't mess up later tests.
221
222=== modified file 'lib/lp/blueprints/interfaces/specification.py'
223--- lib/lp/blueprints/interfaces/specification.py 2012-09-28 14:42:25 +0000
224+++ lib/lp/blueprints/interfaces/specification.py 2012-09-28 14:42:25 +0000
225@@ -674,7 +674,7 @@
226
227 coming_sprints = Attribute("The next 5 sprints in the system.")
228
229- def specificationCount():
230+ def specificationCount(user):
231 """The total number of blueprints in Launchpad"""
232
233 def getStatusCountsForProductSeries(product_series):
234
235=== modified file 'lib/lp/blueprints/interfaces/specificationtarget.py'
236--- lib/lp/blueprints/interfaces/specificationtarget.py 2012-09-28 14:42:25 +0000
237+++ lib/lp/blueprints/interfaces/specificationtarget.py 2012-09-28 14:42:25 +0000
238@@ -60,10 +60,11 @@
239 'have not been accepted for that goal'))),
240 exported_as="valid_specifications", as_of="devel")
241
242- def specifications(quantity=None, sort=None, filter=None,
243+ def specifications(user, quantity=None, sort=None, filter=None,
244 prejoin_people=True):
245 """Specifications for this target.
246
247+ The user specifies which user to use for calculation of visibility.
248 The sort is a dbschema which indicates the preferred sort order. The
249 filter is an indicator of the kinds of specs to be returned, and
250 appropriate filters depend on the kind of object this method is on.
251
252=== modified file 'lib/lp/blueprints/model/specification.py'
253--- lib/lp/blueprints/model/specification.py 2012-09-28 14:42:25 +0000
254+++ lib/lp/blueprints/model/specification.py 2012-09-28 14:42:25 +0000
255@@ -109,6 +109,7 @@
256 cachedproperty,
257 get_property_cache,
258 )
259+from lp.services.webapp.interfaces import ILaunchBag
260
261
262 def recursive_blocked_query(spec):
263@@ -957,7 +958,7 @@
264 for other classes that have specifications.
265 """
266
267- def specifications(self, sort=None, quantity=None, filter=None,
268+ def specifications(self, user, sort=None, quantity=None, filter=None,
269 prejoin_people=True):
270 """See IHasSpecifications."""
271 # this should be implemented by the actual context class
272@@ -1024,16 +1025,19 @@
273 @property
274 def _all_specifications(self):
275 """See IHasSpecifications."""
276- return self.specifications(filter=[SpecificationFilter.ALL])
277+ user = getUtility(ILaunchBag).user
278+ return self.specifications(user, filter=[SpecificationFilter.ALL])
279
280 @property
281 def _valid_specifications(self):
282 """See IHasSpecifications."""
283- return self.specifications(filter=[SpecificationFilter.VALID])
284+ user = getUtility(ILaunchBag).user
285+ return self.specifications(user, filter=[SpecificationFilter.VALID])
286
287- def specificationCount(self):
288+ def specificationCount(self, user):
289 """See IHasSpecifications."""
290- return self.specifications(filter=[SpecificationFilter.ALL]).count()
291+ return self.specifications(user,
292+ filter=[SpecificationFilter.ALL]).count()
293
294
295 class SpecificationSet(HasSpecificationsMixin):
296@@ -1072,7 +1076,7 @@
297 """See ISpecificationSet."""
298 return iter(self.all_specifications)
299
300- def specifications(self, sort=None, quantity=None, filter=None,
301+ def specifications(self, user, sort=None, quantity=None, filter=None,
302 prejoin_people=True):
303 """See IHasSpecifications."""
304
305
306=== modified file 'lib/lp/blueprints/model/sprint.py'
307--- lib/lp/blueprints/model/sprint.py 2012-09-28 14:42:25 +0000
308+++ lib/lp/blueprints/model/sprint.py 2012-09-28 14:42:25 +0000
309@@ -41,7 +41,10 @@
310 ISprint,
311 ISprintSet,
312 )
313-from lp.blueprints.model.specification import HasSpecificationsMixin
314+from lp.blueprints.model.specification import (
315+ get_specification_privacy_filter,
316+ HasSpecificationsMixin,
317+ )
318 from lp.blueprints.model.sprintattendance import SprintAttendance
319 from lp.blueprints.model.sprintspecification import SprintSpecification
320 from lp.registry.interfaces.person import (
321@@ -111,7 +114,7 @@
322 # Only really used in tests.
323 return [a.attendee for a in self.attendances]
324
325- def spec_filter_clause(self, filter=None):
326+ def spec_filter_clause(self, user, filter=None):
327 """Figure out the appropriate query for specifications on a sprint.
328
329 We separate out the query generation from the normal
330@@ -126,6 +129,7 @@
331 Or(Specification.product == None,
332 Not(Specification.productID.is_in(Select(Product.id,
333 Product.active == False))))]
334+ query.append(get_specification_privacy_filter(user))
335 if not filter:
336 # filter could be None or [] then we decide the default
337 # which for a sprint is to show everything approved
338@@ -169,7 +173,10 @@
339 query.append(fti_search(Specification, constraint))
340 return query
341
342- def specifications(self, sort=None, quantity=None, filter=None,
343+ def all_specifications(self, user):
344+ return self.specifications(user, filter=[SpecificationFilter.ALL])
345+
346+ def specifications(self, user, sort=None, quantity=None, filter=None,
347 prejoin_people=False):
348 """See IHasSpecifications."""
349 # prejoin_people is provided only for interface compatibility and
350@@ -177,7 +184,7 @@
351 assert not prejoin_people
352 if filter is None:
353 filter = set([SpecificationFilter.ACCEPTED])
354- query = self.spec_filter_clause(filter=filter)
355+ query = self.spec_filter_clause(user, filter=filter)
356 # import here to avoid circular deps
357 from lp.blueprints.model.specification import Specification
358 results = Store.of(self).find(Specification, *query)
359@@ -200,7 +207,7 @@
360
361 def specificationLinks(self, filter=None):
362 """See `ISprint`."""
363- query = self.spec_filter_clause(filter=filter)
364+ query = self.spec_filter_clause(None, filter=filter)
365 result = Store.of(self).find(SprintSpecification, *query)
366 return result
367
368@@ -227,7 +234,7 @@
369 # queue
370 flush_database_updates()
371
372- return self.specifications(
373+ return self.specifications(decider,
374 filter=[SpecificationFilter.PROPOSED]).count()
375
376 def declineSpecificationLinks(self, idlist, decider):
377@@ -241,7 +248,7 @@
378 # queue
379 flush_database_updates()
380
381- return self.specifications(
382+ return self.specifications(decider,
383 filter=[SpecificationFilter.PROPOSED]).count()
384
385 # attendance
386
387=== modified file 'lib/lp/blueprints/model/tests/test_sprint.py'
388--- lib/lp/blueprints/model/tests/test_sprint.py 2012-09-28 14:42:25 +0000
389+++ lib/lp/blueprints/model/tests/test_sprint.py 2012-09-28 14:42:25 +0000
390@@ -9,8 +9,10 @@
391 import datetime
392
393 from pytz import utc
394+from zope.component import getUtility
395 from zope.security.proxy import removeSecurityProxy
396
397+from lp.app.enums import InformationType
398 from lp.blueprints.enums import (
399 NewSpecificationDefinitionStatus,
400 SpecificationDefinitionStatus,
401@@ -18,12 +20,13 @@
402 SpecificationPriority,
403 SpecificationSort,
404 )
405+from lp.registry.interfaces.accesspolicy import IAccessPolicySource
406 from lp.testing import TestCaseWithFactory
407 from lp.testing.layers import DatabaseFunctionalLayer
408
409
410-def list_result(sprint, filter=None):
411- result = sprint.specifications(SpecificationSort.DATE, filter=filter)
412+def list_result(sprint, filter=None, user=None):
413+ result = sprint.specifications(user, SpecificationSort.DATE, filter=filter)
414 return list(result)
415
416
417@@ -38,11 +41,12 @@
418 def makeSpec(self, sprint=None, date_decided=0, date_created=0,
419 proposed=False, declined=False, title=None,
420 status=NewSpecificationDefinitionStatus.NEW,
421- name=None, priority=None):
422+ name=None, priority=None, information_type=None):
423 if sprint is None:
424 sprint = self.factory.makeSprint()
425 blueprint = self.factory.makeSpecification(
426- title=title, status=status, name=name, priority=priority)
427+ title=title, status=status, name=name, priority=priority,
428+ information_type=information_type)
429 link = blueprint.linkSprint(sprint, blueprint.owner)
430 naked_link = removeSecurityProxy(link)
431 if declined:
432@@ -61,10 +65,11 @@
433 sprint = self.factory.makeSprint()
434 for count in range(10):
435 self.makeSpec(sprint)
436- self.assertEqual(10, sprint.specifications().count())
437- self.assertEqual(10, sprint.specifications(quantity=None).count())
438- self.assertEqual(8, sprint.specifications(quantity=8).count())
439- self.assertEqual(10, sprint.specifications(quantity=11).count())
440+ self.assertEqual(10, sprint.specifications(None).count())
441+ result = sprint.specifications(None, quantity=None).count()
442+ self.assertEqual(10, result)
443+ self.assertEqual(8, sprint.specifications(None, quantity=8).count())
444+ self.assertEqual(10, sprint.specifications(None, quantity=11).count())
445
446 def test_specifications_date_sort_accepted_decided(self):
447 # If only accepted proposals are requested, date-sorting uses
448@@ -133,9 +138,9 @@
449 blueprint3 = self.makeSpec(
450 sprint, priority=SpecificationPriority.LOW,
451 status=SpecificationDefinitionStatus.OBSOLETE)
452- result = sprint.specifications()
453+ result = sprint.specifications(None)
454 self.assertEqual([blueprint3, blueprint1, blueprint2], list(result))
455- result = sprint.specifications(sort=SpecificationSort.PRIORITY)
456+ result = sprint.specifications(None, sort=SpecificationSort.PRIORITY)
457 self.assertEqual([blueprint3, blueprint1, blueprint2], list(result))
458
459 def test_priority_sort_fallback_status(self):
460@@ -148,9 +153,9 @@
461 sprint, status=SpecificationDefinitionStatus.APPROVED, name='c')
462 blueprint3 = self.makeSpec(
463 sprint, status=SpecificationDefinitionStatus.NEW, name='b')
464- result = sprint.specifications()
465+ result = sprint.specifications(None)
466 self.assertEqual([blueprint2, blueprint3, blueprint1], list(result))
467- result = sprint.specifications(sort=SpecificationSort.PRIORITY)
468+ result = sprint.specifications(None, sort=SpecificationSort.PRIORITY)
469 self.assertEqual([blueprint2, blueprint3, blueprint1], list(result))
470
471 def test_priority_sort_fallback_name(self):
472@@ -159,9 +164,9 @@
473 sprint = blueprint1.sprints[0]
474 blueprint2 = self.makeSpec(sprint, name='c')
475 blueprint3 = self.makeSpec(sprint, name='a')
476- result = sprint.specifications()
477+ result = sprint.specifications(None)
478 self.assertEqual([blueprint3, blueprint1, blueprint2], list(result))
479- result = sprint.specifications(sort=SpecificationSort.PRIORITY)
480+ result = sprint.specifications(None, sort=SpecificationSort.PRIORITY)
481 self.assertEqual([blueprint3, blueprint1, blueprint2], list(result))
482
483 def test_text_search(self):
484@@ -182,6 +187,34 @@
485 result = list_result(sprint, [SpecificationFilter.DECLINED])
486 self.assertEqual([blueprint2], result)
487
488+ def test_proprietary_not_listed(self):
489+ # Proprietary blueprints are not listed for random users
490+ blueprint1 = self.makeSpec(
491+ information_type=InformationType.PROPRIETARY)
492+ self.assertEqual([], list_result(blueprint1.sprints[0]))
493+
494+ def test_proprietary_listed_for_artifact_grant(self):
495+ # Proprietary blueprints are listed for users with an artifact grant.
496+ blueprint1 = self.makeSpec(
497+ information_type=InformationType.PROPRIETARY)
498+ grant = self.factory.makeAccessArtifactGrant(
499+ concrete_artifact=blueprint1)
500+ self.assertEqual(
501+ [blueprint1],
502+ list_result(blueprint1.sprints[0], user=grant.grantee))
503+
504+ def test_proprietary_listed_for_policy_grant(self):
505+ # Proprietary blueprints are listed for users with a policy grant.
506+ blueprint1 = self.makeSpec(
507+ information_type=InformationType.PROPRIETARY)
508+ policy_source = getUtility(IAccessPolicySource)
509+ (policy,) = policy_source.find(
510+ [(blueprint1.product, InformationType.PROPRIETARY)])
511+ grant = self.factory.makeAccessPolicyGrant(policy)
512+ self.assertEqual(
513+ [blueprint1],
514+ list_result(blueprint1.sprints[0], user=grant.grantee))
515+
516
517 class TestSprintAttendancesSort(TestCaseWithFactory):
518
519
520=== modified file 'lib/lp/blueprints/templates/person-specworkload.pt'
521--- lib/lp/blueprints/templates/person-specworkload.pt 2011-12-08 22:41:00 +0000
522+++ lib/lp/blueprints/templates/person-specworkload.pt 2012-09-28 14:42:25 +0000
523@@ -44,7 +44,7 @@
524
525 <tal:participants repeat="member members_batch">
526
527- <tal:specs define="specifications member/specifications">
528+ <tal:specs define="specifications member/@@+specworkload/specifications">
529
530 <div style="margin-bottom: 1em;" tal:condition="specifications">
531 <p>
532
533=== modified file 'lib/lp/blueprints/vocabularies/specification.py'
534--- lib/lp/blueprints/vocabularies/specification.py 2012-01-01 02:58:52 +0000
535+++ lib/lp/blueprints/vocabularies/specification.py 2012-09-28 14:42:25 +0000
536@@ -39,7 +39,8 @@
537
538 if target is not None:
539 for spec in sorted(
540- target.specifications(), key=attrgetter('title')):
541+ target.specifications(launchbag.user),
542+ key=attrgetter('title')):
543 # we will not show the current specification in the
544 # launchbag
545 if spec == launchbag.specification:
546
547=== modified file 'lib/lp/registry/browser/person.py'
548--- lib/lp/registry/browser/person.py 2012-09-11 13:25:48 +0000
549+++ lib/lp/registry/browser/person.py 2012-09-28 14:42:25 +0000
550@@ -1303,6 +1303,9 @@
551 batch_nav = BatchNavigator(members, self.request, size=20)
552 return batch_nav
553
554+ def specifications(self):
555+ return self.context.specifications(self.user)
556+
557
558 class PersonSpecWorkloadTableView(LaunchpadView):
559 """View to render the specification workload table for a person.
560@@ -1331,7 +1334,7 @@
561 approver, the assignee or the drafter.
562 """
563 return [PersonSpecWorkloadTableView.PersonSpec(spec, self.context)
564- for spec in self.context.specifications()]
565+ for spec in self.context.specifications(self.user)]
566
567
568 class PersonVouchersView(LaunchpadFormView):
569@@ -3573,7 +3576,7 @@
570 else:
571 project['bug_count'] = pillar.searchTasks(
572 BugTaskSet().open_bugtask_search).count()
573- project['spec_count'] = pillar.specifications().count()
574+ project['spec_count'] = pillar.specifications(user).count()
575 project['question_count'] = pillar.searchQuestions().count()
576 projects.append(project)
577 return projects
578
579=== modified file 'lib/lp/registry/doc/distribution.txt'
580--- lib/lp/registry/doc/distribution.txt 2012-09-28 14:42:25 +0000
581+++ lib/lp/registry/doc/distribution.txt 2012-09-28 14:42:25 +0000
582@@ -490,18 +490,18 @@
583 complete so it will not show up unless we explicitly ask for complete specs:
584
585 >>> filter = [SpecificationFilter.INFORMATIONAL]
586- >>> kubuntu.specifications(filter=filter).count()
587+ >>> kubuntu.specifications(None, filter=filter).count()
588 0
589 >>> filter = [SpecificationFilter.INFORMATIONAL,
590 ... SpecificationFilter.COMPLETE]
591- >>> kubuntu.specifications(filter=filter).count()
592+ >>> kubuntu.specifications(None, filter=filter).count()
593 1
594
595
596 There are 2 completed specs for Kubuntu:
597
598 >>> filter = [SpecificationFilter.COMPLETE]
599- >>> for spec in kubuntu.specifications(filter=filter):
600+ >>> for spec in kubuntu.specifications(None, filter=filter):
601 ... print spec.name, spec.is_complete
602 thinclient-local-devices True
603 usplash-on-hibernation True
604@@ -510,7 +510,7 @@
605 And there are four incomplete specs:
606
607 >>> filter = [SpecificationFilter.INCOMPLETE]
608- >>> for spec in kubuntu.specifications(filter=filter):
609+ >>> for spec in kubuntu.specifications(None, filter=filter):
610 ... print spec.name, spec.is_complete
611 cluster-installation False
612 revu False
613@@ -521,7 +521,7 @@
614 If we ask for all specs, we get them in the order of priority.
615
616 >>> filter = [SpecificationFilter.ALL]
617- >>> for spec in kubuntu.specifications(filter=filter):
618+ >>> for spec in kubuntu.specifications(None, filter=filter):
619 ... print spec.priority.title, spec.name
620 Essential cluster-installation
621 High revu
622@@ -533,7 +533,7 @@
623
624 And if we ask just for specs, we get the incomplete ones.
625
626- >>> for spec in kubuntu.specifications():
627+ >>> for spec in kubuntu.specifications(None):
628 ... print spec.name, spec.is_complete
629 cluster-installation False
630 revu False
631@@ -542,7 +542,7 @@
632
633 We can filter for specifications that contain specific text:
634
635- >>> for spec in kubuntu.specifications(filter=['package']):
636+ >>> for spec in kubuntu.specifications(None, filter=['package']):
637 ... print spec.name
638 revu
639
640@@ -550,7 +550,7 @@
641
642 >>> from lp.blueprints.enums import SpecificationDefinitionStatus
643 >>> login('mark@example.com')
644- >>> for spec in kubuntu.specifications():
645+ >>> for spec in kubuntu.specifications(None):
646 ... # Do this here, otherwise, the change will be flush before
647 ... # updateLifecycleStatus() acts and an IntegrityError will be
648 ... # raised.
649
650=== modified file 'lib/lp/registry/doc/distroseries.txt'
651--- lib/lp/registry/doc/distroseries.txt 2012-09-27 02:53:00 +0000
652+++ lib/lp/registry/doc/distroseries.txt 2012-09-28 14:42:25 +0000
653@@ -850,14 +850,14 @@
654 First, there should be one informational specs for krunch:
655
656 >>> filter = [SpecificationFilter.INFORMATIONAL]
657- >>> krunch.specifications(filter=filter).count()
658+ >>> krunch.specifications(None, filter=filter).count()
659 1
660
661
662 There are 2 completed specs for Krunch:
663
664 >>> filter = [SpecificationFilter.COMPLETE]
665- >>> for spec in kubuntu.specifications(filter=filter):
666+ >>> for spec in kubuntu.specifications(None, filter=filter):
667 ... print spec.name, spec.is_complete
668 thinclient-local-devices True
669 usplash-on-hibernation True
670@@ -866,7 +866,7 @@
671 And there are 2 incomplete specs:
672
673 >>> filter = [SpecificationFilter.INCOMPLETE]
674- >>> for spec in krunch.specifications(filter=filter):
675+ >>> for spec in krunch.specifications(None, filter=filter):
676 ... print spec.name, spec.is_complete
677 cluster-installation False
678 revu False
679@@ -875,7 +875,7 @@
680 If we ask for all specs, we get them in the order of priority.
681
682 >>> filter = [SpecificationFilter.ALL]
683- >>> for spec in krunch.specifications(filter=filter):
684+ >>> for spec in krunch.specifications(None, filter=filter):
685 ... print spec.priority.title, spec.name
686 Essential cluster-installation
687 High revu
688@@ -888,7 +888,7 @@
689 With a distroseries, we can ask for ACCEPTED, PROPOSED and DECLINED specs:
690
691 >>> filter=[SpecificationFilter.ACCEPTED]
692- >>> for spec in krunch.specifications(filter=filter):
693+ >>> for spec in krunch.specifications(None, filter=filter):
694 ... print spec.name, spec.goalstatus.title
695 cluster-installation Accepted
696 revu Accepted
697@@ -896,12 +896,12 @@
698 usplash-on-hibernation Accepted
699
700 >>> filter=[SpecificationFilter.PROPOSED]
701- >>> for spec in krunch.specifications(filter=filter):
702+ >>> for spec in krunch.specifications(None, filter=filter):
703 ... print spec.name, spec.goalstatus.title
704 kde-desktopfile-langpacks Proposed
705
706 >>> filter=[SpecificationFilter.DECLINED]
707- >>> for spec in krunch.specifications(filter=filter):
708+ >>> for spec in krunch.specifications(None, filter=filter):
709 ... print spec.name, spec.goalstatus.title
710 krunch-desktop-plan Declined
711
712@@ -909,7 +909,7 @@
713 And if we ask just for specs, we get BOTH the incomplete and the complete
714 ones that have been accepted.
715
716- >>> for spec in krunch.specifications():
717+ >>> for spec in krunch.specifications(None):
718 ... print spec.name, spec.is_complete, spec.goalstatus.title
719 cluster-installation False Accepted
720 revu False Accepted
721@@ -918,7 +918,7 @@
722
723 We can filter for specifications that contain specific text:
724
725- >>> for spec in krunch.specifications(filter=['usb']):
726+ >>> for spec in krunch.specifications(None, filter=['usb']):
727 ... print spec.name
728 thinclient-local-devices
729
730
731=== modified file 'lib/lp/registry/doc/person-account.txt'
732--- lib/lp/registry/doc/person-account.txt 2012-09-07 18:06:37 +0000
733+++ lib/lp/registry/doc/person-account.txt 2012-09-28 14:42:25 +0000
734@@ -70,7 +70,7 @@
735 >>> personset = getUtility(IPersonSet)
736 >>> foobar_preferredemail = emailset.getByEmail('foo.bar@canonical.com')
737 >>> foobar = personset.get(foobar_preferredemail.personID)
738- >>> foobar.specifications().count() > 0
739+ >>> foobar.specifications(None).count() > 0
740 True
741
742 >>> from lp.blueprints.model.specification import Specification
743
744=== modified file 'lib/lp/registry/doc/person.txt'
745--- lib/lp/registry/doc/person.txt 2012-09-26 07:45:31 +0000
746+++ lib/lp/registry/doc/person.txt 2012-09-28 14:42:25 +0000
747@@ -1146,7 +1146,7 @@
748 him:
749
750 >>> from lp.blueprints.enums import SpecificationFilter
751- >>> carlos.specifications(filter=[
752+ >>> carlos.specifications(None, filter=[
753 ... SpecificationFilter.ASSIGNEE,
754 ... SpecificationFilter.COMPLETE]).count()
755 0
756@@ -1154,7 +1154,7 @@
757 Next, Carlos has two incomplete specs *related* to him:
758
759 >>> filter = []
760- >>> for spec in carlos.specifications(filter=filter):
761+ >>> for spec in carlos.specifications(None, filter=filter):
762 ... print spec.name, spec.is_complete, spec.informational
763 svg-support False False
764 extension-manager-upgrades False True
765@@ -1177,39 +1177,39 @@
766
767 >>> mark = getUtility(IPersonSet).getByName('mark')
768 >>> filter = [SpecificationFilter.APPROVER]
769- >>> for spec in mark.specifications(filter=filter):
770+ >>> for spec in mark.specifications(None, filter=filter):
771 ... print spec.name
772 extension-manager-upgrades
773
774 But has registered 5 of them:
775
776 >>> filter = [SpecificationFilter.CREATOR]
777- >>> print foobar.specifications(filter=filter).count()
778+ >>> print foobar.specifications(None, filter=filter).count()
779 5
780
781 Now Celso, on the other hand, has 2 specs related to him:
782
783 >>> cprov = personset.getByName('cprov')
784- >>> cprov.specifications().count()
785+ >>> cprov.specifications(None).count()
786 2
787
788 On one of those, he is the approver:
789
790 >>> filter = [SpecificationFilter.APPROVER]
791- >>> for spec in cprov.specifications(filter=filter):
792+ >>> for spec in cprov.specifications(None, filter=filter):
793 ... print spec.name
794 svg-support
795
796 And on another one, he is the drafter
797
798 >>> filter = [SpecificationFilter.DRAFTER]
799- >>> for spec in cprov.specifications(filter=filter):
800+ >>> for spec in cprov.specifications(None, filter=filter):
801 ... print spec.name
802 e4x
803
804 We can filter for specifications that contain specific text:
805
806- >>> for spec in cprov.specifications(filter=['svg']):
807+ >>> for spec in cprov.specifications(None, filter=['svg']):
808 ... print spec.name
809 svg-support
810
811@@ -1225,7 +1225,7 @@
812 >>> unlink_source_packages(firefox)
813 >>> firefox.active = False
814 >>> flush_database_updates()
815- >>> cprov.specifications(filter=['svg']).count()
816+ >>> cprov.specifications(None, filter=['svg']).count()
817 0
818
819 Reset firefox so we don't mess up later tests.
820
821=== modified file 'lib/lp/registry/doc/product.txt'
822--- lib/lp/registry/doc/product.txt 2012-09-25 16:30:05 +0000
823+++ lib/lp/registry/doc/product.txt 2012-09-28 14:42:25 +0000
824@@ -384,7 +384,7 @@
825 First, there should be only one informational spec for firefox:
826
827 >>> filter = [SpecificationFilter.INFORMATIONAL]
828- >>> for spec in firefox.specifications(filter=filter):
829+ >>> for spec in firefox.specifications(None, filter=filter):
830 ... print spec.name
831 extension-manager-upgrades
832
833@@ -392,19 +392,19 @@
834 There are no completed specs for firefox:
835
836 >>> filter = [SpecificationFilter.COMPLETE]
837- >>> for spec in firefox.specifications(filter=filter):
838+ >>> for spec in firefox.specifications(None, filter=filter):
839 ... print spec.name
840
841
842 And there are five incomplete specs:
843
844 >>> filter = [SpecificationFilter.INCOMPLETE]
845- >>> firefox.specifications(filter=filter).count()
846+ >>> firefox.specifications(None, filter=filter).count()
847 5
848
849 We can filter for specifications that contain specific text:
850
851- >>> for spec in firefox.specifications(filter=['new']):
852+ >>> for spec in firefox.specifications(None, filter=['new']):
853 ... print spec.name
854 canvas
855 e4x
856
857=== modified file 'lib/lp/registry/doc/productseries.txt'
858--- lib/lp/registry/doc/productseries.txt 2012-04-10 14:01:17 +0000
859+++ lib/lp/registry/doc/productseries.txt 2012-09-28 14:42:25 +0000
860@@ -229,7 +229,7 @@
861 If we ask for ALL specs we should see them both.
862
863 >>> filter = [SpecificationFilter.ALL]
864- >>> for s in onezero.specifications(filter=filter):
865+ >>> for s in onezero.specifications(None, filter=filter):
866 ... print s.name
867 a
868 b
869@@ -238,23 +238,23 @@
870 specs:
871
872 >>> filter=[SpecificationFilter.ACCEPTED]
873- >>> for spec in onezero.specifications(filter=filter):
874+ >>> for spec in onezero.specifications(None, filter=filter):
875 ... print spec.name, spec.goalstatus.title
876 a Accepted
877
878 >>> filter=[SpecificationFilter.PROPOSED]
879- >>> onezero.specifications(filter=filter).count()
880+ >>> onezero.specifications(None, filter=filter).count()
881 0
882
883 >>> filter=[SpecificationFilter.DECLINED]
884- >>> onezero.specifications(filter=filter).count()
885+ >>> onezero.specifications(None, filter=filter).count()
886 1
887
888 We should see one informational spec if we ask just for that, the
889 accepted one.
890
891 >>> filter = [SpecificationFilter.INFORMATIONAL]
892- >>> for s in onezero.specifications(filter=filter):
893+ >>> for s in onezero.specifications(None, filter=filter):
894 ... print s.name
895 a
896
897@@ -262,21 +262,21 @@
898
899 >>> filter = [
900 ... SpecificationFilter.INFORMATIONAL, SpecificationFilter.DECLINED]
901- >>> for s in onezero.specifications(filter=filter):
902+ >>> for s in onezero.specifications(None, filter=filter):
903 ... print s.name
904 b
905
906 There are is one completed, accepted spec for 1.0:
907
908 >>> filter = [SpecificationFilter.COMPLETE]
909- >>> for spec in onezero.specifications(filter=filter):
910+ >>> for spec in onezero.specifications(None, filter=filter):
911 ... print spec.name, spec.is_complete, spec.goalstatus.title
912 a True Accepted
913
914 There is one completed, declined spec:
915
916 >>> filter = [SpecificationFilter.COMPLETE, SpecificationFilter.DECLINED]
917- >>> for spec in onezero.specifications(filter=filter):
918+ >>> for spec in onezero.specifications(None, filter=filter):
919 ... print spec.name, spec.is_complete, spec.goalstatus.title
920 b True Declined
921
922@@ -291,7 +291,7 @@
923 And if we ask just for specs, we get BOTH the incomplete and the
924 complete ones that have been accepted.
925
926- >>> for spec in onezero.specifications():
927+ >>> for spec in onezero.specifications(None):
928 ... print spec.name, spec.is_complete, spec.goalstatus.title
929 a True Accepted
930 b False Accepted
931@@ -299,7 +299,7 @@
932 We can search for text in specifications (in this case there are no
933 matches):
934
935- >>> print len(list(onezero.specifications(filter=['new'])))
936+ >>> print len(list(onezero.specifications(None, filter=['new'])))
937 0
938
939
940
941=== modified file 'lib/lp/registry/doc/projectgroup.txt'
942--- lib/lp/registry/doc/projectgroup.txt 2012-09-28 14:42:25 +0000
943+++ lib/lp/registry/doc/projectgroup.txt 2012-09-28 14:42:25 +0000
944@@ -150,7 +150,7 @@
945 First, there should be only one informational spec for mozilla:
946
947 >>> filter = [SpecificationFilter.INFORMATIONAL]
948- >>> for spec in mozilla.specifications(filter=filter):
949+ >>> for spec in mozilla.specifications(None, filter=filter):
950 ... print spec.name
951 extension-manager-upgrades
952
953@@ -158,19 +158,19 @@
954 There are no completed specs for mozilla:
955
956 >>> filter = [SpecificationFilter.COMPLETE]
957- >>> for spec in mozilla.specifications(filter=filter):
958+ >>> for spec in mozilla.specifications(None, filter=filter):
959 ... print spec.name
960
961
962 And there are five incomplete specs:
963
964 >>> filter = [SpecificationFilter.INCOMPLETE]
965- >>> mozilla.specifications(filter=filter).count()
966+ >>> mozilla.specifications(None, filter=filter).count()
967 5
968
969 We can filter for specifications that contain specific text:
970
971- >>> for spec in mozilla.specifications(filter=['install']):
972+ >>> for spec in mozilla.specifications(None, filter=['install']):
973 ... print spec.name
974 extension-manager-upgrades
975
976@@ -178,7 +178,7 @@
977 Inactive products are excluded from the listings.
978
979 >>> filter = [SpecificationFilter.INCOMPLETE]
980- >>> mozilla.specifications(filter=filter).count()
981+ >>> mozilla.specifications(None, filter=filter).count()
982 5
983
984 >>> from lp.registry.interfaces.product import IProductSet
985@@ -190,7 +190,7 @@
986 >>> firefox.active = False
987 >>> flush_database_updates()
988 >>> filter = [SpecificationFilter.INCOMPLETE]
989- >>> mozilla.specifications(filter=filter).count()
990+ >>> mozilla.specifications(None, filter=filter).count()
991 0
992
993 Reset firefox so we don't mess up later tests.
994@@ -248,7 +248,8 @@
995 mozilla_1_0_series._all_specifications.
996
997 >>> filter = [SpecificationFilter.INFORMATIONAL]
998- >>> extension_manager_upgrades = mozilla.specifications(filter=filter)[0]
999+ >>> extension_manager_upgrades = mozilla.specifications(
1000+ ... None, filter=filter)[0]
1001 >>> series_1_0 = firefox.getSeries('1.0')
1002 >>> extension_manager_upgrades.proposeGoal(series_1_0, no_priv)
1003 >>> for spec in mozilla_series_1_0._all_specifications:
1004@@ -264,7 +265,7 @@
1005 Filtered lists of project series related specifications are generated
1006 the same way as for project related specifications.
1007
1008- >>> for spec in mozilla_series_1_0.specifications(filter=filter):
1009+ >>> for spec in mozilla_series_1_0.specifications(None, filter=filter):
1010 ... print spec.name
1011 extension-manager-upgrades
1012
1013@@ -277,7 +278,7 @@
1014 project itself.
1015
1016 >>> filter = [SpecificationFilter.INCOMPLETE]
1017- >>> for spec in mozilla_series_1_0.specifications(filter=filter):
1018+ >>> for spec in mozilla_series_1_0.specifications(None, filter=filter):
1019 ... print spec.name
1020 svg-support
1021 canvas
1022@@ -287,21 +288,22 @@
1023
1024 Searching for text is also possible.
1025
1026- >>> for spec in mozilla_series_1_0.specifications(filter=['install']):
1027+ >>> for spec in mozilla_series_1_0.specifications(
1028+ ... None, filter=['install']):
1029 ... print spec.name
1030 extension-manager-upgrades
1031
1032 Inactive products are excluded from the series listings.
1033
1034 >>> filter = [SpecificationFilter.INCOMPLETE]
1035- >>> specs = mozilla_series_1_0.specifications(filter=filter)
1036+ >>> specs = mozilla_series_1_0.specifications(None, filter=filter)
1037 >>> print specs.count()
1038 5
1039
1040 >>> firefox = getUtility(IProductSet).getByName('firefox')
1041 >>> firefox.active = False
1042 >>> filter = [SpecificationFilter.INCOMPLETE]
1043- >>> mozilla_series_1_0.specifications(filter=filter).count()
1044+ >>> mozilla_series_1_0.specifications(None, filter=filter).count()
1045 0
1046
1047 Reset firefox so we don't mess up later tests.
1048
1049=== modified file 'lib/lp/registry/model/distribution.py'
1050--- lib/lp/registry/model/distribution.py 2012-09-28 14:42:25 +0000
1051+++ lib/lp/registry/model/distribution.py 2012-09-28 14:42:25 +0000
1052@@ -878,7 +878,7 @@
1053 return getUtility(IDistributionSet).getCurrentSourceReleases(
1054 {self: source_package_names})
1055
1056- def specifications(self, sort=None, quantity=None, filter=None,
1057+ def specifications(self, user, sort=None, quantity=None, filter=None,
1058 prejoin_people=True):
1059 """See `IHasSpecifications`.
1060
1061
1062=== modified file 'lib/lp/registry/model/distroseries.py'
1063--- lib/lp/registry/model/distroseries.py 2012-09-28 14:42:25 +0000
1064+++ lib/lp/registry/model/distroseries.py 2012-09-28 14:42:25 +0000
1065@@ -777,7 +777,7 @@
1066 """See `IHasBugs`."""
1067 return self.distribution.official_bug_tags
1068
1069- def specifications(self, sort=None, quantity=None, filter=None,
1070+ def specifications(self, user, sort=None, quantity=None, filter=None,
1071 prejoin_people=True):
1072 """See IHasSpecifications.
1073
1074
1075=== modified file 'lib/lp/registry/model/person.py'
1076--- lib/lp/registry/model/person.py 2012-09-28 14:42:25 +0000
1077+++ lib/lp/registry/model/person.py 2012-09-28 14:42:25 +0000
1078@@ -821,7 +821,7 @@
1079 """See `IPerson`."""
1080 return "%s (%s)" % (self.displayname, self.name)
1081
1082- def specifications(self, sort=None, quantity=None, filter=None,
1083+ def specifications(self, user, sort=None, quantity=None, filter=None,
1084 prejoin_people=True):
1085 """See `IHasSpecifications`."""
1086
1087
1088=== modified file 'lib/lp/registry/model/product.py'
1089--- lib/lp/registry/model/product.py 2012-09-28 14:42:25 +0000
1090+++ lib/lp/registry/model/product.py 2012-09-28 14:42:25 +0000
1091@@ -1303,7 +1303,7 @@
1092 # automatically shared.
1093 return True
1094
1095- def specifications(self, sort=None, quantity=None, filter=None,
1096+ def specifications(self, user, sort=None, quantity=None, filter=None,
1097 prejoin_people=True):
1098 """See `IHasSpecifications`."""
1099
1100
1101=== modified file 'lib/lp/registry/model/productseries.py'
1102--- lib/lp/registry/model/productseries.py 2012-09-28 14:42:25 +0000
1103+++ lib/lp/registry/model/productseries.py 2012-09-28 14:42:25 +0000
1104@@ -301,7 +301,7 @@
1105 """See `IProductSeries`."""
1106 return self == self.product.development_focus
1107
1108- def specifications(self, sort=None, quantity=None, filter=None,
1109+ def specifications(self, user, sort=None, quantity=None, filter=None,
1110 prejoin_people=True):
1111 """See IHasSpecifications.
1112
1113
1114=== modified file 'lib/lp/registry/model/projectgroup.py'
1115--- lib/lp/registry/model/projectgroup.py 2012-09-28 14:42:25 +0000
1116+++ lib/lp/registry/model/projectgroup.py 2012-09-28 14:42:25 +0000
1117@@ -231,7 +231,7 @@
1118 """ % sqlvalues(self, SprintSpecificationStatus.ACCEPTED)
1119 return query, ['Product', 'Specification', 'SprintSpecification']
1120
1121- def specifications(self, sort=None, quantity=None, filter=None,
1122+ def specifications(self, user, sort=None, quantity=None, filter=None,
1123 series=None, prejoin_people=True):
1124 """See `IHasSpecifications`."""
1125
1126@@ -631,10 +631,11 @@
1127 self.project = project
1128 self.name = name
1129
1130- def specifications(self, sort=None, quantity=None, filter=None,
1131+ def specifications(self, user, sort=None, quantity=None, filter=None,
1132 prejoin_people=True):
1133 return self.project.specifications(
1134- sort, quantity, filter, self.name, prejoin_people=prejoin_people)
1135+ user, sort, quantity, filter, self.name,
1136+ prejoin_people=prejoin_people)
1137
1138 @property
1139 def title(self):
1140
1141=== modified file 'lib/lp/testing/factory.py'
1142--- lib/lp/testing/factory.py 2012-09-28 06:25:44 +0000
1143+++ lib/lp/testing/factory.py 2012-09-28 14:42:25 +0000
1144@@ -2091,8 +2091,16 @@
1145 :param product: The product to make the blueprint on. If one is
1146 not specified, an arbitrary product is created.
1147 """
1148+ proprietary = (information_type not in PUBLIC_INFORMATION_TYPES and
1149+ information_type is not None)
1150 if distribution is None and product is None:
1151- product = self.makeProduct()
1152+ if proprietary:
1153+ specification_sharing_policy = (
1154+ SpecificationSharingPolicy.EMBARGOED_OR_PROPRIETARY)
1155+ else:
1156+ specification_sharing_policy = None
1157+ product = self.makeProduct(
1158+ specification_sharing_policy=specification_sharing_policy)
1159 if name is None:
1160 name = self.getUniqueString('name')
1161 if summary is None:
1162@@ -2125,7 +2133,7 @@
1163 priority=priority)
1164 naked_spec = removeSecurityProxy(spec)
1165 if information_type is not None:
1166- if information_type not in PUBLIC_INFORMATION_TYPES:
1167+ if proprietary:
1168 naked_spec.target._ensurePolicies([information_type])
1169 naked_spec.transitionToInformationType(
1170 information_type, spec.target.owner)
1171@@ -4340,9 +4348,9 @@
1172 return link
1173
1174 def makeAccessArtifactGrant(self, artifact=None, grantee=None,
1175- grantor=None):
1176+ grantor=None, concrete_artifact=None):
1177 if artifact is None:
1178- artifact = self.makeAccessArtifact()
1179+ artifact = self.makeAccessArtifact(concrete_artifact)
1180 if grantee is None:
1181 grantee = self.makePerson()
1182 if grantor is None: