Merge lp:~stevenk/launchpad/distroseries-spec-preload into lp:launchpad
- distroseries-spec-preload
- Merge into devel
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Approved by: | Steve Kowalik | ||||||||
Approved revision: | no longer in the source branch. | ||||||||
Merged at revision: | 16732 | ||||||||
Proposed branch: | lp:~stevenk/launchpad/distroseries-spec-preload | ||||||||
Merge into: | lp:launchpad | ||||||||
Diff against target: |
1014 lines (+203/-159) 22 files modified
lib/lp/_schema_circular_imports.py (+1/-1) lib/lp/blueprints/browser/specificationtarget.py (+1/-1) lib/lp/blueprints/interfaces/specificationtarget.py (+12/-5) lib/lp/blueprints/mail/notifications.py (+0/-1) lib/lp/blueprints/model/specification.py (+28/-19) lib/lp/blueprints/model/specificationsearch.py (+36/-20) lib/lp/blueprints/model/sprint.py (+5/-6) lib/lp/blueprints/model/tests/test_specification.py (+21/-20) lib/lp/blueprints/tests/test_hasspecifications.py (+25/-45) lib/lp/registry/configure.zcml (+2/-1) lib/lp/registry/doc/distribution.txt (+1/-1) lib/lp/registry/doc/projectgroup.txt (+4/-4) lib/lp/registry/model/distribution.py (+5/-2) lib/lp/registry/model/distroseries.py (+5/-3) lib/lp/registry/model/person.py (+5/-2) lib/lp/registry/model/product.py (+5/-3) lib/lp/registry/model/productseries.py (+5/-3) lib/lp/registry/model/projectgroup.py (+9/-6) lib/lp/registry/tests/test_distroseries.py (+22/-1) lib/lp/registry/tests/test_product.py (+2/-2) lib/lp/registry/tests/test_productseries.py (+7/-7) lib/lp/soyuz/browser/tests/test_archive_webservice.py (+2/-6) |
||||||||
To merge this branch: | bzr merge lp:~stevenk/launchpad/distroseries-spec-preload | ||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
William Grant | code | Approve | |
Review via email: mp+180046@code.launchpad.net |
Commit message
Rewrite preloading for searching specifications, allowing callsites to specify which of people, branches and workitems they need preloaded; rename IHasSpecificati
Description of the change
Rewrite preloading for searching specifications, allowing callsites to specify which of people, branches and workitems they need preloaded, modeled off IPersonSet.
IHasSpecificati
I've also re-enabled a disabled test that now works due to the refactoring work that went into search_
I have more than enough LoC credit to take the +50 hit for this branch.
Preview Diff
1 | === modified file 'lib/lp/_schema_circular_imports.py' |
2 | --- lib/lp/_schema_circular_imports.py 2013-05-01 18:14:22 +0000 |
3 | +++ lib/lp/_schema_circular_imports.py 2013-08-20 07:26:08 +0000 |
4 | @@ -737,7 +737,7 @@ |
5 | patch_collection_property( |
6 | IHasSpecifications, 'visible_specifications', ISpecification) |
7 | patch_collection_property( |
8 | - IHasSpecifications, '_valid_specifications', ISpecification) |
9 | + IHasSpecifications, 'api_valid_specifications', ISpecification) |
10 | |
11 | |
12 | ### |
13 | |
14 | === modified file 'lib/lp/blueprints/browser/specificationtarget.py' |
15 | --- lib/lp/blueprints/browser/specificationtarget.py 2013-04-10 09:36:25 +0000 |
16 | +++ lib/lp/blueprints/browser/specificationtarget.py 2013-08-20 07:26:08 +0000 |
17 | @@ -406,7 +406,7 @@ |
18 | """ |
19 | return self.context.specifications(self.user, |
20 | sort=SpecificationSort.DATE, quantity=quantity, |
21 | - prejoin_people=False) |
22 | + need_people=False, need_branches=False, need_workitems=False) |
23 | |
24 | |
25 | class SpecificationAssignmentsView(HasSpecificationsView): |
26 | |
27 | === modified file 'lib/lp/blueprints/interfaces/specificationtarget.py' |
28 | --- lib/lp/blueprints/interfaces/specificationtarget.py 2013-04-03 03:09:04 +0000 |
29 | +++ lib/lp/blueprints/interfaces/specificationtarget.py 2013-08-20 07:26:08 +0000 |
30 | @@ -47,7 +47,7 @@ |
31 | 'approval or completion, for this object.'))), |
32 | exported_as="all_specifications", as_of="devel") |
33 | |
34 | - _valid_specifications = exported(doNotSnapshot( |
35 | + api_valid_specifications = exported(doNotSnapshot( |
36 | CollectionField( |
37 | title=_("Valid specifications"), |
38 | value_type=Reference(schema=Interface), # ISpecification, really. |
39 | @@ -59,7 +59,8 @@ |
40 | exported_as="valid_specifications", as_of="devel") |
41 | |
42 | def specifications(user, quantity=None, sort=None, filter=None, |
43 | - prejoin_people=True): |
44 | + need_people=True, need_branches=True, |
45 | + need_workitems=False): |
46 | """Specifications for this target. |
47 | |
48 | The user specifies which user to use for calculation of visibility. |
49 | @@ -73,9 +74,15 @@ |
50 | it will show all specs, in others, all approved specs, and in |
51 | others, all incomplete specs. |
52 | |
53 | - If prejoin_people=False is specified, then the assignee, drafter |
54 | - and approver will not be prejoined. This can be used in |
55 | - situations in which these are not rendered. |
56 | + If need_people is True, then the assignee, drafter and approver will |
57 | + be preloaded, if need_branches is True, linked_branches will be |
58 | + preloaded, and if need_workitems is True, work_items will be preloaded. |
59 | + """ |
60 | + |
61 | + def valid_specifications(**kwargs): |
62 | + """Valid specifications for this target. |
63 | + |
64 | + Any kwargs are passed to specifications. |
65 | """ |
66 | |
67 | |
68 | |
69 | === modified file 'lib/lp/blueprints/mail/notifications.py' |
70 | --- lib/lp/blueprints/mail/notifications.py 2013-03-05 08:24:25 +0000 |
71 | +++ lib/lp/blueprints/mail/notifications.py 2013-08-20 07:26:08 +0000 |
72 | @@ -92,7 +92,6 @@ |
73 | workitems_delta['old'], workitems_delta['new'], 72) |
74 | info_lines.append('Work items changed:') |
75 | info_lines.append(workitems_diff) |
76 | - |
77 | if not info_lines: |
78 | # The specification was modified, but we don't yet support |
79 | # sending notification for the change. |
80 | |
81 | === modified file 'lib/lp/blueprints/model/specification.py' |
82 | --- lib/lp/blueprints/model/specification.py 2013-06-27 15:11:38 +0000 |
83 | +++ lib/lp/blueprints/model/specification.py 2013-08-20 07:26:08 +0000 |
84 | @@ -335,7 +335,7 @@ |
85 | else: |
86 | return "Work items for %s:" % milestone.name |
87 | |
88 | - if self.work_items.count() == 0: |
89 | + if len(self.work_items) == 0: |
90 | return '' |
91 | milestone = self.work_items[0].milestone |
92 | # Start by appending a header for the milestone of the first work |
93 | @@ -353,9 +353,9 @@ |
94 | else: |
95 | assignee_part = "" |
96 | # work_items are ordered by sequence |
97 | - workitems_lines.append("%s%s: %s" % (assignee_part, |
98 | - work_item.title, |
99 | - work_item.status.name)) |
100 | + workitems_lines.append( |
101 | + "%s%s: %s" % ( |
102 | + assignee_part, work_item.title, work_item.status.name)) |
103 | return "\n".join(workitems_lines) |
104 | |
105 | @property |
106 | @@ -377,9 +377,13 @@ |
107 | title=title, status=status, specification=self, assignee=assignee, |
108 | milestone=milestone, sequence=sequence) |
109 | |
110 | - @property |
111 | + @cachedproperty |
112 | def work_items(self): |
113 | """See ISpecification.""" |
114 | + return list(self._work_items) |
115 | + |
116 | + @property |
117 | + def _work_items(self): |
118 | return Store.of(self).find( |
119 | SpecificationWorkItem, specification=self, |
120 | deleted=False).order_by("sequence") |
121 | @@ -395,7 +399,7 @@ |
122 | """ |
123 | title_counts = self._list_to_dict_of_frequency(titles) |
124 | |
125 | - for work_item in self.work_items: |
126 | + for work_item in self._work_items: |
127 | if (work_item.title not in title_counts or |
128 | title_counts[work_item.title] == 0): |
129 | work_item.deleted = True |
130 | @@ -418,9 +422,7 @@ |
131 | # deleted. |
132 | self._deleteWorkItemsNotMatching( |
133 | [wi['title'] for wi in new_work_items]) |
134 | - work_items = Store.of(self).find( |
135 | - SpecificationWorkItem, specification=self, deleted=False) |
136 | - work_items = list(work_items.order_by("sequence")) |
137 | + work_items = self._work_items |
138 | # At this point the list of new_work_items is necessarily the same |
139 | # size (or longer) than the list of existing ones, so we can just |
140 | # iterate over it updating the existing items and creating any new |
141 | @@ -456,6 +458,7 @@ |
142 | for sequence, item in to_insert: |
143 | self.newWorkItem(item['title'], sequence, item['status'], |
144 | item['assignee'], item['milestone']) |
145 | + del get_property_cache(self).work_items |
146 | |
147 | def setTarget(self, target): |
148 | """See ISpecification.""" |
149 | @@ -683,9 +686,7 @@ |
150 | delta.recordNewAndOld(("name", "priority", "definition_status", |
151 | "target", "approver", "assignee", "drafter", |
152 | "whiteboard", "workitems_text")) |
153 | - delta.recordListAddedAndRemoved("bugs", |
154 | - "bugs_linked", |
155 | - "bugs_unlinked") |
156 | + delta.recordListAddedAndRemoved("bugs", "bugs_linked", "bugs_unlinked") |
157 | |
158 | if delta.changes: |
159 | changes = delta.changes |
160 | @@ -952,9 +953,10 @@ |
161 | """ |
162 | |
163 | def specifications(self, user, sort=None, quantity=None, filter=None, |
164 | - prejoin_people=True): |
165 | + need_people=True, need_branches=True, |
166 | + need_workitems=False): |
167 | """See IHasSpecifications.""" |
168 | - # this should be implemented by the actual context class |
169 | + # This should be implemented by the actual context class. |
170 | raise NotImplementedError |
171 | |
172 | def _specification_sort(self, sort): |
173 | @@ -976,11 +978,16 @@ |
174 | user = getUtility(ILaunchBag).user |
175 | return self.specifications(user, filter=[SpecificationFilter.ALL]) |
176 | |
177 | - @property |
178 | - def _valid_specifications(self): |
179 | + def valid_specifications(self, **kwargs): |
180 | """See IHasSpecifications.""" |
181 | user = getUtility(ILaunchBag).user |
182 | - return self.specifications(user, filter=[SpecificationFilter.VALID]) |
183 | + return self.specifications( |
184 | + user, filter=[SpecificationFilter.VALID], **kwargs) |
185 | + |
186 | + @property |
187 | + def api_valid_specifications(self): |
188 | + return self.valid_specifications( |
189 | + need_people=True, need_branches=True, need_workitems=True) |
190 | |
191 | def specificationCount(self, user): |
192 | """See IHasSpecifications.""" |
193 | @@ -1017,11 +1024,13 @@ |
194 | return cur.fetchall() |
195 | |
196 | def specifications(self, user, sort=None, quantity=None, filter=None, |
197 | - prejoin_people=True): |
198 | + need_people=True, need_branches=True, |
199 | + need_workitems=False): |
200 | from lp.blueprints.model.specificationsearch import ( |
201 | search_specifications) |
202 | return search_specifications( |
203 | - self, [], user, sort, quantity, filter, prejoin_people) |
204 | + self, [], user, sort, quantity, filter, need_people=need_people, |
205 | + need_branches=need_branches, need_workitems=need_workitems) |
206 | |
207 | def getByURL(self, url): |
208 | """See ISpecificationSet.""" |
209 | |
210 | === modified file 'lib/lp/blueprints/model/specificationsearch.py' |
211 | --- lib/lp/blueprints/model/specificationsearch.py 2013-06-20 05:50:00 +0000 |
212 | +++ lib/lp/blueprints/model/specificationsearch.py 2013-08-20 07:26:08 +0000 |
213 | @@ -11,6 +11,8 @@ |
214 | 'search_specifications', |
215 | ] |
216 | |
217 | +from collections import defaultdict |
218 | + |
219 | from storm.expr import ( |
220 | And, |
221 | Coalesce, |
222 | @@ -36,6 +38,7 @@ |
223 | ) |
224 | from lp.blueprints.model.specification import Specification |
225 | from lp.blueprints.model.specificationbranch import SpecificationBranch |
226 | +from lp.blueprints.model.specificationworkitem import SpecificationWorkItem |
227 | from lp.registry.interfaces.distribution import IDistribution |
228 | from lp.registry.interfaces.distroseries import IDistroSeries |
229 | from lp.registry.interfaces.person import IPersonSet |
230 | @@ -56,8 +59,9 @@ |
231 | |
232 | |
233 | def search_specifications(context, base_clauses, user, sort=None, |
234 | - quantity=None, spec_filter=None, prejoin_people=True, |
235 | - tables=[], default_acceptance=False): |
236 | + quantity=None, spec_filter=None, tables=[], |
237 | + default_acceptance=False, need_people=True, |
238 | + need_branches=True, need_workitems=False): |
239 | store = IStore(Specification) |
240 | if not default_acceptance: |
241 | default = SpecificationFilter.INCOMPLETE |
242 | @@ -106,12 +110,39 @@ |
243 | order = [sort] |
244 | # Set the _known_viewers property for each specification, as well as |
245 | # preloading the objects involved, if asked. |
246 | + def preload_hook(rows): |
247 | + person_ids = set() |
248 | + work_items_by_spec = defaultdict(list) |
249 | + for spec in rows: |
250 | + if need_people: |
251 | + person_ids |= set( |
252 | + [spec._assigneeID, spec._approverID, spec._drafterID]) |
253 | + if need_branches: |
254 | + get_property_cache(spec).linked_branches = [] |
255 | + if need_workitems: |
256 | + work_items = load_referencing( |
257 | + SpecificationWorkItem, rows, ['specification_id']) |
258 | + for workitem in work_items: |
259 | + person_ids.add(workitem.assignee_id) |
260 | + work_items_by_spec[workitem.specification_id].append(workitem) |
261 | + person_ids -= set([None]) |
262 | + if need_people: |
263 | + list(getUtility(IPersonSet).getPrecachedPersonsFromIDs( |
264 | + person_ids, need_validity=True)) |
265 | + if need_workitems: |
266 | + for spec in rows: |
267 | + get_property_cache(spec).work_items = sorted( |
268 | + work_items_by_spec[spec.id], key=lambda wi: wi.sequence) |
269 | + if need_branches: |
270 | + spec_branches = load_referencing( |
271 | + SpecificationBranch, rows, ['specificationID']) |
272 | + for sbranch in spec_branches: |
273 | + spec_cache = get_property_cache(sbranch.specification) |
274 | + spec_cache.linked_branches.append(sbranch) |
275 | + |
276 | decorators = [] |
277 | - preload_hook = None |
278 | if user is not None and not IPersonRoles(user).in_admin: |
279 | decorators.append(_make_cache_user_can_view_spec(user)) |
280 | - if prejoin_people: |
281 | - preload_hook = _preload_specifications_related_objects |
282 | results = store.using(*tables).find( |
283 | Specification, *clauses).order_by(*order).config(limit=quantity) |
284 | return DecoratedResultSet( |
285 | @@ -227,21 +258,6 @@ |
286 | return cache_user_can_view_spec |
287 | |
288 | |
289 | -def _preload_specifications_related_objects(rows): |
290 | - person_ids = set() |
291 | - for spec in rows: |
292 | - person_ids |= set( |
293 | - [spec._assigneeID, spec._approverID, spec._drafterID]) |
294 | - get_property_cache(spec).linked_branches = [] |
295 | - person_ids -= set([None]) |
296 | - list(getUtility(IPersonSet).getPrecachedPersonsFromIDs( |
297 | - person_ids, need_validity=True)) |
298 | - spec_branches = load_referencing( |
299 | - SpecificationBranch, rows, ['specificationID']) |
300 | - for sbranch in spec_branches: |
301 | - get_property_cache(sbranch.specification).linked_branches.append( |
302 | - sbranch) |
303 | - |
304 | |
305 | def get_specification_started_clause(): |
306 | return Or(Not(Specification.implementation_status.is_in([ |
307 | |
308 | === modified file 'lib/lp/blueprints/model/sprint.py' |
309 | --- lib/lp/blueprints/model/sprint.py 2013-01-22 05:07:31 +0000 |
310 | +++ lib/lp/blueprints/model/sprint.py 2013-08-20 07:26:08 +0000 |
311 | @@ -163,18 +163,17 @@ |
312 | return self.specifications(user, filter=[SpecificationFilter.ALL]) |
313 | |
314 | def specifications(self, user, sort=None, quantity=None, filter=None, |
315 | - prejoin_people=False): |
316 | + need_people=False, need_branches=False, |
317 | + need_workitems=False): |
318 | """See IHasSpecifications.""" |
319 | - # prejoin_people is provided only for interface compatibility and |
320 | - # prejoin_people=True is not implemented. |
321 | - assert not prejoin_people |
322 | + # need_* is provided only for interface compatibility and |
323 | + # need_*=True is not implemented. |
324 | if filter is None: |
325 | filter = set([SpecificationFilter.ACCEPTED]) |
326 | tables, query = self.spec_filter_clause(user, filter) |
327 | # import here to avoid circular deps |
328 | from lp.blueprints.model.specification import Specification |
329 | - store = Store.of(self) |
330 | - results = store.using(*tables).find(Specification, *query) |
331 | + results = Store.of(self).using(*tables).find(Specification, *query) |
332 | if sort == SpecificationSort.DATE: |
333 | order = (Desc(SprintSpecification.date_created), Specification.id) |
334 | distinct = [SprintSpecification.date_created, Specification.id] |
335 | |
336 | === modified file 'lib/lp/blueprints/model/tests/test_specification.py' |
337 | --- lib/lp/blueprints/model/tests/test_specification.py 2013-01-25 03:30:08 +0000 |
338 | +++ lib/lp/blueprints/model/tests/test_specification.py 2013-08-20 07:26:08 +0000 |
339 | @@ -33,6 +33,7 @@ |
340 | ) |
341 | from lp.registry.errors import CannotChangeInformationType |
342 | from lp.registry.model.milestone import Milestone |
343 | +from lp.services.propertycache import get_property_cache |
344 | from lp.services.mail import stub |
345 | from lp.services.webapp import canonical_url |
346 | from lp.testing import ( |
347 | @@ -222,10 +223,9 @@ |
348 | stub.test_emails = [] |
349 | spec = self.factory.makeSpecification() |
350 | old_spec = Snapshot(spec, providing=providedBy(spec)) |
351 | - status = SpecificationWorkItemStatus.TODO |
352 | new_work_item = { |
353 | 'title': u'A work item', |
354 | - 'status': status, |
355 | + 'status': SpecificationWorkItemStatus.TODO, |
356 | 'assignee': None, |
357 | 'milestone': None, |
358 | 'sequence': 0 |
359 | @@ -233,10 +233,9 @@ |
360 | |
361 | login_person(spec.owner) |
362 | spec.updateWorkItems([new_work_item]) |
363 | - # In production this notification is fired by lazr.restful for changes |
364 | - # in the specification form and notify(ObjectModifiedEvent(...)) for |
365 | - # changes in the +workitems form. We need to do it ourselves in this |
366 | - # test. |
367 | + # For API requests, lazr.restful does the notify() call, for this test |
368 | + # we need to call ourselves. |
369 | + transaction.commit() |
370 | notify(ObjectModifiedEvent( |
371 | spec, old_spec, edited_fields=['workitems_text'])) |
372 | transaction.commit() |
373 | @@ -500,7 +499,7 @@ |
374 | spec.updateWorkItems([work_item1_data, work_item2_data]) |
375 | |
376 | # And after calling updateWorkItems() we have 2 work items. |
377 | - self.assertEqual(2, spec.work_items.count()) |
378 | + self.assertEqual(2, len(spec.work_items)) |
379 | |
380 | # The data dicts we pass to updateWorkItems() have no sequence because |
381 | # that's taken from their position on the list, so we update our data |
382 | @@ -523,7 +522,7 @@ |
383 | # Create two work-items in our database. |
384 | wi1_data = self._createWorkItemAndReturnDataDict(spec) |
385 | wi2_data = self._createWorkItemAndReturnDataDict(spec) |
386 | - self.assertEqual(2, spec.work_items.count()) |
387 | + self.assertEqual(2, len(spec.work_items)) |
388 | |
389 | # These are the work items we'll be inserting. |
390 | new_wi1_data = dict( |
391 | @@ -546,7 +545,7 @@ |
392 | new_wi2_data['sequence'] = 2 |
393 | wi2_data['sequence'] = 3 |
394 | |
395 | - self.assertEqual(4, spec.work_items.count()) |
396 | + self.assertEqual(4, len(spec.work_items)) |
397 | for data, obj in zip(work_items, list(spec.work_items)): |
398 | self.assertThat(obj, MatchesStructure.byEquality(**data)) |
399 | |
400 | @@ -571,8 +570,8 @@ |
401 | new_wi2_data['sequence'] = 2 |
402 | wi2_data['sequence'] = 3 |
403 | |
404 | - self.assertEqual(4, spec.work_items.count()) |
405 | - for data, obj in zip(work_items, list(spec.work_items)): |
406 | + self.assertEqual(4, len(spec.work_items)) |
407 | + for data, obj in zip(work_items, spec.work_items): |
408 | self.assertThat(obj, MatchesStructure.byEquality(**data)) |
409 | |
410 | return spec, work_items |
411 | @@ -586,7 +585,7 @@ |
412 | work_items.append(new_wi3_data) |
413 | spec.updateWorkItems(work_items) |
414 | |
415 | - self.assertEqual(5, spec.work_items.count()) |
416 | + self.assertEqual(5, len(spec.work_items)) |
417 | for data, obj in zip(work_items, list(spec.work_items)): |
418 | self.assertThat(obj, MatchesStructure.byEquality(**data)) |
419 | |
420 | @@ -597,7 +596,7 @@ |
421 | work_items.pop() |
422 | spec.updateWorkItems(work_items) |
423 | |
424 | - self.assertEqual(3, spec.work_items.count()) |
425 | + self.assertEqual(3, len(spec.work_items)) |
426 | for data, obj in zip(work_items, list(spec.work_items)): |
427 | self.assertThat(obj, MatchesStructure.byEquality(**data)) |
428 | |
429 | @@ -606,7 +605,7 @@ |
430 | login_person(spec.owner) |
431 | # Create a work-item in our database. |
432 | wi_data = self._createWorkItemAndReturnDataDict(spec) |
433 | - self.assertEqual(1, spec.work_items.count()) |
434 | + self.assertEqual(1, len(spec.work_items)) |
435 | |
436 | # This time we're only changing the existing work item; we'll change |
437 | # its assignee and status. |
438 | @@ -614,29 +613,29 @@ |
439 | assignee=spec.owner)) |
440 | spec.updateWorkItems([wi_data]) |
441 | |
442 | - self.assertEqual(1, spec.work_items.count()) |
443 | + self.assertEqual(1, len(spec.work_items)) |
444 | self.assertThat( |
445 | spec.work_items[0], MatchesStructure.byEquality(**wi_data)) |
446 | |
447 | def test_updateWorkItems_deletes_all_if_given_empty_list(self): |
448 | work_item = self.factory.makeSpecificationWorkItem() |
449 | spec = work_item.specification |
450 | - self.assertEqual(1, spec.work_items.count()) |
451 | + self.assertEqual(1, len(spec.work_items)) |
452 | spec.updateWorkItems([]) |
453 | - self.assertEqual(0, spec.work_items.count()) |
454 | + self.assertEqual(0, len(spec.work_items)) |
455 | |
456 | def test_updateWorkItems_marks_removed_ones_as_deleted(self): |
457 | spec = self.factory.makeSpecification() |
458 | self._createWorkItemAndReturnDataDict(spec) |
459 | wi2_data = self._createWorkItemAndReturnDataDict(spec) |
460 | - self.assertEqual(2, spec.work_items.count()) |
461 | + self.assertEqual(2, len(spec.work_items)) |
462 | login_person(spec.owner) |
463 | |
464 | # We have two work items in the DB but now we want to update them to |
465 | # keep just the second one. The first will be deleted and the sequence |
466 | # of the second will be changed. |
467 | spec.updateWorkItems([wi2_data]) |
468 | - self.assertEqual(1, spec.work_items.count()) |
469 | + self.assertEqual(1, len(spec.work_items)) |
470 | wi2_data['sequence'] = 0 |
471 | self.assertThat( |
472 | spec.work_items[0], MatchesStructure.byEquality(**wi2_data)) |
473 | @@ -648,12 +647,14 @@ |
474 | Return a dict with the title, status, assignee, milestone and sequence |
475 | attributes of the spec. |
476 | """ |
477 | - if spec.work_items.count() == 0: |
478 | + del get_property_cache(spec).work_items |
479 | + if len(spec.work_items) == 0: |
480 | sequence = 0 |
481 | else: |
482 | sequence = max(wi.sequence for wi in spec.work_items) + 1 |
483 | wi = self.factory.makeSpecificationWorkItem( |
484 | specification=spec, sequence=sequence) |
485 | + del get_property_cache(spec).work_items |
486 | return dict( |
487 | title=wi.title, status=wi.status, assignee=wi.assignee, |
488 | milestone=wi.milestone, sequence=sequence) |
489 | |
490 | === modified file 'lib/lp/blueprints/tests/test_hasspecifications.py' |
491 | --- lib/lp/blueprints/tests/test_hasspecifications.py 2013-04-03 03:09:04 +0000 |
492 | +++ lib/lp/blueprints/tests/test_hasspecifications.py 2013-08-20 07:26:08 +0000 |
493 | @@ -34,61 +34,48 @@ |
494 | product=product, name="spec2", |
495 | status=SpecificationDefinitionStatus.OBSOLETE) |
496 | self.assertNamesOfSpecificationsAre( |
497 | - ["spec1"], product._valid_specifications) |
498 | + ["spec1"], product.valid_specifications()) |
499 | |
500 | def test_distribution_all_specifications(self): |
501 | distribution = self.factory.makeDistribution() |
502 | - self.factory.makeSpecification( |
503 | - distribution=distribution, name="spec1") |
504 | - self.factory.makeSpecification( |
505 | - distribution=distribution, name="spec2") |
506 | + self.factory.makeSpecification(distribution=distribution, name="spec1") |
507 | + self.factory.makeSpecification(distribution=distribution, name="spec2") |
508 | self.assertNamesOfSpecificationsAre( |
509 | ["spec1", "spec2"], distribution.visible_specifications) |
510 | |
511 | def test_distribution_valid_specifications(self): |
512 | distribution = self.factory.makeDistribution() |
513 | - self.factory.makeSpecification( |
514 | - distribution=distribution, name="spec1") |
515 | + self.factory.makeSpecification(distribution=distribution, name="spec1") |
516 | self.factory.makeSpecification( |
517 | distribution=distribution, name="spec2", |
518 | status=SpecificationDefinitionStatus.OBSOLETE) |
519 | self.assertNamesOfSpecificationsAre( |
520 | - ["spec1"], distribution._valid_specifications) |
521 | + ["spec1"], distribution.valid_specifications()) |
522 | |
523 | def test_distroseries_all_specifications(self): |
524 | distroseries = self.factory.makeDistroSeries(name='maudlin') |
525 | distribution = distroseries.distribution |
526 | self.factory.makeSpecification( |
527 | - distribution=distribution, name="spec1", |
528 | - goal=distroseries) |
529 | - self.factory.makeSpecification( |
530 | - distribution=distribution, name="spec2", |
531 | - goal=distroseries) |
532 | - self.factory.makeSpecification( |
533 | - distribution=distribution, name="spec3") |
534 | + distribution=distribution, name="spec1", goal=distroseries) |
535 | + self.factory.makeSpecification( |
536 | + distribution=distribution, name="spec2", goal=distroseries) |
537 | + self.factory.makeSpecification(distribution=distribution, name="spec3") |
538 | self.assertNamesOfSpecificationsAre( |
539 | ["spec1", "spec2"], distroseries.visible_specifications) |
540 | |
541 | - # XXX: salgado, 2010-11-25, bug=681432: Test disabled because |
542 | - # DistroSeries._valid_specifications is broken. |
543 | - def disabled_test_distroseries_valid_specifications(self): |
544 | + def test_distroseries_valid_specifications(self): |
545 | distroseries = self.factory.makeDistroSeries(name='maudlin') |
546 | distribution = distroseries.distribution |
547 | self.factory.makeSpecification( |
548 | - distribution=distribution, name="spec1", |
549 | - goal=distroseries) |
550 | - self.factory.makeSpecification( |
551 | - distribution=distribution, name="spec2", |
552 | - goal=distroseries) |
553 | - self.factory.makeSpecification( |
554 | - distribution=distribution, name="spec3", |
555 | - goal=distroseries, |
556 | + distribution=distribution, name="spec1", goal=distroseries) |
557 | + self.factory.makeSpecification( |
558 | + distribution=distribution, name="spec2", goal=distroseries) |
559 | + self.factory.makeSpecification( |
560 | + distribution=distribution, name="spec3", goal=distroseries, |
561 | status=SpecificationDefinitionStatus.OBSOLETE) |
562 | - self.factory.makeSpecification( |
563 | - distribution=distribution, name="spec4") |
564 | + self.factory.makeSpecification(distribution=distribution, name="spec4") |
565 | self.assertNamesOfSpecificationsAre( |
566 | - ["spec1", "spec2"], |
567 | - distroseries._valid_specifications) |
568 | + ["spec1", "spec2"], distroseries.valid_specifications()) |
569 | |
570 | def test_productseries_all_specifications(self): |
571 | product = self.factory.makeProduct() |
572 | @@ -115,7 +102,7 @@ |
573 | status=SpecificationDefinitionStatus.OBSOLETE) |
574 | self.factory.makeSpecification(product=product, name="spec4") |
575 | self.assertNamesOfSpecificationsAre( |
576 | - ["spec1", "spec2"], productseries._valid_specifications) |
577 | + ["spec1", "spec2"], productseries.valid_specifications()) |
578 | |
579 | def test_projectgroup_all_specifications(self): |
580 | projectgroup = self.factory.makeProject() |
581 | @@ -123,13 +110,11 @@ |
582 | product1 = self.factory.makeProduct(project=projectgroup) |
583 | product2 = self.factory.makeProduct(project=projectgroup) |
584 | product3 = self.factory.makeProduct(project=other_projectgroup) |
585 | - self.factory.makeSpecification( |
586 | - product=product1, name="spec1") |
587 | + self.factory.makeSpecification(product=product1, name="spec1") |
588 | self.factory.makeSpecification( |
589 | product=product2, name="spec2", |
590 | status=SpecificationDefinitionStatus.OBSOLETE) |
591 | - self.factory.makeSpecification( |
592 | - product=product3, name="spec3") |
593 | + self.factory.makeSpecification(product=product3, name="spec3") |
594 | self.assertNamesOfSpecificationsAre( |
595 | ["spec1", "spec2"], projectgroup.visible_specifications) |
596 | |
597 | @@ -145,7 +130,7 @@ |
598 | status=SpecificationDefinitionStatus.OBSOLETE) |
599 | self.factory.makeSpecification(product=product3, name="spec3") |
600 | self.assertNamesOfSpecificationsAre( |
601 | - ["spec1"], projectgroup._valid_specifications) |
602 | + ["spec1"], projectgroup.valid_specifications()) |
603 | |
604 | def test_person_all_specifications(self): |
605 | person = self.factory.makePerson(name="james-w") |
606 | @@ -155,8 +140,7 @@ |
607 | self.factory.makeSpecification( |
608 | product=product, name="spec2", approver=person, |
609 | status=SpecificationDefinitionStatus.OBSOLETE) |
610 | - self.factory.makeSpecification( |
611 | - product=product, name="spec3") |
612 | + self.factory.makeSpecification(product=product, name="spec3") |
613 | self.assertNamesOfSpecificationsAre( |
614 | ["spec1", "spec2"], person.visible_specifications) |
615 | |
616 | @@ -168,10 +152,9 @@ |
617 | self.factory.makeSpecification( |
618 | product=product, name="spec2", approver=person, |
619 | status=SpecificationDefinitionStatus.OBSOLETE) |
620 | - self.factory.makeSpecification( |
621 | - product=product, name="spec3") |
622 | + self.factory.makeSpecification(product=product, name="spec3") |
623 | self.assertNamesOfSpecificationsAre( |
624 | - ["spec1"], person._valid_specifications) |
625 | + ["spec1"], person.valid_specifications()) |
626 | |
627 | |
628 | class HasSpecificationsSnapshotTestCase(TestCaseWithFactory): |
629 | @@ -181,10 +164,7 @@ |
630 | |
631 | def check_skipped(self, target): |
632 | """Asserts that fields marked doNotSnapshot are skipped.""" |
633 | - skipped = [ |
634 | - 'all_specifications', |
635 | - 'valid_specifications', |
636 | - ] |
637 | + skipped = ['all_specifications', 'valid_specifications'] |
638 | self.assertThat(target, DoesNotSnapshot(skipped, IHasSpecifications)) |
639 | |
640 | def test_product(self): |
641 | |
642 | === modified file 'lib/lp/registry/configure.zcml' |
643 | --- lib/lp/registry/configure.zcml 2013-04-03 03:09:04 +0000 |
644 | +++ lib/lp/registry/configure.zcml 2013-08-20 07:26:08 +0000 |
645 | @@ -1460,7 +1460,8 @@ |
646 | permission="launchpad.View" |
647 | attributes=" |
648 | visible_specifications |
649 | - _valid_specifications |
650 | + valid_specifications |
651 | + api_valid_specifications |
652 | getAllowedSpecificationInformationTypes |
653 | getDefaultSpecificationInformationType |
654 | specifications" /> |
655 | |
656 | === modified file 'lib/lp/registry/doc/distribution.txt' |
657 | --- lib/lp/registry/doc/distribution.txt 2013-05-01 21:23:16 +0000 |
658 | +++ lib/lp/registry/doc/distribution.txt 2013-08-20 07:26:08 +0000 |
659 | @@ -562,7 +562,7 @@ |
660 | ... spec.definition_status = ( |
661 | ... SpecificationDefinitionStatus.SUPERSEDED) |
662 | ... shim = spec.updateLifecycleStatus(owner) |
663 | - >>> for spec in kubuntu._valid_specifications: |
664 | + >>> for spec in kubuntu.valid_specifications(): |
665 | ... print spec.name |
666 | kde-desktopfile-langpacks |
667 | |
668 | |
669 | === modified file 'lib/lp/registry/doc/projectgroup.txt' |
670 | --- lib/lp/registry/doc/projectgroup.txt 2013-05-01 21:23:16 +0000 |
671 | +++ lib/lp/registry/doc/projectgroup.txt 2013-08-20 07:26:08 +0000 |
672 | @@ -201,7 +201,7 @@ |
673 | >>> flush_database_updates() |
674 | |
675 | We can get all the specifications via the visible_specifications property, |
676 | -and all valid specifications via the _valid_specifications property: |
677 | +and all valid specifications via the valid_specifications method: |
678 | |
679 | >>> for spec in mozilla.visible_specifications: |
680 | ... print spec.name |
681 | @@ -211,7 +211,7 @@ |
682 | mergewin |
683 | e4x |
684 | |
685 | - >>> for spec in mozilla._valid_specifications: |
686 | + >>> for spec in mozilla.valid_specifications(): |
687 | ... print spec.name |
688 | svg-support |
689 | canvas |
690 | @@ -313,7 +313,7 @@ |
691 | >>> firefox.active = True |
692 | |
693 | We can get all the specifications via the visible_specifications property, |
694 | -and all valid specifications via the _valid_specifications property: |
695 | +and all valid specifications via the valid_specifications method: |
696 | |
697 | >>> for spec in mozilla_series_1_0.visible_specifications: |
698 | ... print spec.name |
699 | @@ -323,7 +323,7 @@ |
700 | mergewin |
701 | e4x |
702 | |
703 | - >>> for spec in mozilla_series_1_0._valid_specifications: |
704 | + >>> for spec in mozilla_series_1_0.valid_specifications(): |
705 | ... print spec.name |
706 | svg-support |
707 | canvas |
708 | |
709 | === modified file 'lib/lp/registry/model/distribution.py' |
710 | --- lib/lp/registry/model/distribution.py 2013-07-11 06:12:20 +0000 |
711 | +++ lib/lp/registry/model/distribution.py 2013-08-20 07:26:08 +0000 |
712 | @@ -868,7 +868,8 @@ |
713 | {self: source_package_names}) |
714 | |
715 | def specifications(self, user, sort=None, quantity=None, filter=None, |
716 | - prejoin_people=True): |
717 | + need_people=True, need_branches=True, |
718 | + need_workitems=False): |
719 | """See `IHasSpecifications`. |
720 | |
721 | In the case of distributions, there are two kinds of filtering, |
722 | @@ -880,7 +881,9 @@ |
723 | """ |
724 | base_clauses = [Specification.distributionID == self.id] |
725 | return search_specifications( |
726 | - self, base_clauses, user, sort, quantity, filter, prejoin_people) |
727 | + self, base_clauses, user, sort, quantity, filter, |
728 | + need_people=need_people, need_branches=need_branches, |
729 | + need_workitems=need_workitems) |
730 | |
731 | def getSpecification(self, name): |
732 | """See `ISpecificationTarget`.""" |
733 | |
734 | === modified file 'lib/lp/registry/model/distroseries.py' |
735 | --- lib/lp/registry/model/distroseries.py 2013-07-11 06:12:20 +0000 |
736 | +++ lib/lp/registry/model/distroseries.py 2013-08-20 07:26:08 +0000 |
737 | @@ -766,7 +766,8 @@ |
738 | return self.distribution.official_bug_tags |
739 | |
740 | def specifications(self, user, sort=None, quantity=None, filter=None, |
741 | - prejoin_people=True): |
742 | + need_people=True, need_branches=True, |
743 | + need_workitems=False): |
744 | """See IHasSpecifications. |
745 | |
746 | In this case the rules for the default behaviour cover three things: |
747 | @@ -778,8 +779,9 @@ |
748 | """ |
749 | base_clauses = [Specification.distroseriesID == self.id] |
750 | return search_specifications( |
751 | - self, base_clauses, user, sort, quantity, filter, prejoin_people, |
752 | - default_acceptance=True) |
753 | + self, base_clauses, user, sort, quantity, filter, |
754 | + default_acceptance=True, need_people=need_people, |
755 | + need_branches=need_branches, need_workitems=need_workitems) |
756 | |
757 | def getDistroSeriesLanguage(self, language): |
758 | """See `IDistroSeries`.""" |
759 | |
760 | === modified file 'lib/lp/registry/model/person.py' |
761 | --- lib/lp/registry/model/person.py 2013-08-08 04:11:59 +0000 |
762 | +++ lib/lp/registry/model/person.py 2013-08-20 07:26:08 +0000 |
763 | @@ -823,7 +823,8 @@ |
764 | return "%s (%s)" % (self.displayname, self.name) |
765 | |
766 | def specifications(self, user, sort=None, quantity=None, filter=None, |
767 | - prejoin_people=True, in_progress=False): |
768 | + in_progress=False, need_people=True, need_branches=True, |
769 | + need_workitems=False): |
770 | """See `IHasSpecifications`.""" |
771 | from lp.blueprints.model.specificationsubscription import ( |
772 | SpecificationSubscription, |
773 | @@ -874,7 +875,9 @@ |
774 | SpecificationFilter.STARTED]) |
775 | |
776 | return search_specifications( |
777 | - self, clauses, user, sort, quantity, list(filter), prejoin_people) |
778 | + self, clauses, user, sort, quantity, list(filter), |
779 | + need_people=need_people, need_branches=need_branches, |
780 | + need_workitems=need_workitems) |
781 | |
782 | # XXX: Tom Berger 2008-04-14 bug=191799: |
783 | # The implementation of these functions |
784 | |
785 | === modified file 'lib/lp/registry/model/product.py' |
786 | --- lib/lp/registry/model/product.py 2013-07-11 06:12:20 +0000 |
787 | +++ lib/lp/registry/model/product.py 2013-08-20 07:26:08 +0000 |
788 | @@ -1416,12 +1416,14 @@ |
789 | return True |
790 | |
791 | def specifications(self, user, sort=None, quantity=None, filter=None, |
792 | - prejoin_people=True): |
793 | + need_people=True, need_branches=True, |
794 | + need_workitems=False): |
795 | """See `IHasSpecifications`.""" |
796 | - |
797 | base_clauses = [Specification.productID == self.id] |
798 | return search_specifications( |
799 | - self, base_clauses, user, sort, quantity, filter, prejoin_people) |
800 | + self, base_clauses, user, sort, quantity, filter, |
801 | + need_people=need_people, need_branches=need_branches, |
802 | + need_workitems=need_workitems) |
803 | |
804 | def getSpecification(self, name): |
805 | """See `ISpecificationTarget`.""" |
806 | |
807 | === modified file 'lib/lp/registry/model/productseries.py' |
808 | --- lib/lp/registry/model/productseries.py 2013-04-03 03:09:04 +0000 |
809 | +++ lib/lp/registry/model/productseries.py 2013-08-20 07:26:08 +0000 |
810 | @@ -316,7 +316,8 @@ |
811 | return self == self.product.development_focus |
812 | |
813 | def specifications(self, user, sort=None, quantity=None, filter=None, |
814 | - prejoin_people=True): |
815 | + need_people=True, need_branches=True, |
816 | + need_workitems=False): |
817 | """See IHasSpecifications. |
818 | |
819 | The rules for filtering are that there are three areas where you can |
820 | @@ -329,8 +330,9 @@ |
821 | """ |
822 | base_clauses = [Specification.productseriesID == self.id] |
823 | return search_specifications( |
824 | - self, base_clauses, user, sort, quantity, filter, prejoin_people, |
825 | - default_acceptance=True) |
826 | + self, base_clauses, user, sort, quantity, filter, |
827 | + default_acceptance=True, need_people=need_people, |
828 | + need_branches=need_branches, need_workitems=need_workitems) |
829 | |
830 | @property |
831 | def all_specifications(self): |
832 | |
833 | === modified file 'lib/lp/registry/model/projectgroup.py' |
834 | --- lib/lp/registry/model/projectgroup.py 2013-07-12 06:14:22 +0000 |
835 | +++ lib/lp/registry/model/projectgroup.py 2013-08-20 07:26:08 +0000 |
836 | @@ -244,7 +244,8 @@ |
837 | return query, ['Product', 'Specification', 'SprintSpecification'] |
838 | |
839 | def specifications(self, user, sort=None, quantity=None, filter=None, |
840 | - series=None, prejoin_people=True): |
841 | + series=None, need_people=True, need_branches=True, |
842 | + need_workitems=False): |
843 | """See `IHasSpecifications`.""" |
844 | base_clauses = [ |
845 | Specification.productID == Product.id, |
846 | @@ -256,8 +257,9 @@ |
847 | Join(ProductSeries, |
848 | Specification.productseriesID == ProductSeries.id)) |
849 | return search_specifications( |
850 | - self, base_clauses, user, sort, quantity, filter, prejoin_people, |
851 | - tables=tables) |
852 | + self, base_clauses, user, sort, quantity, filter, tables=tables, |
853 | + need_people=need_people, need_branches=need_branches, |
854 | + need_workitems=need_workitems) |
855 | |
856 | def _customizeSearchParams(self, search_params): |
857 | """Customize `search_params` for this milestone.""" |
858 | @@ -617,10 +619,11 @@ |
859 | self.name = name |
860 | |
861 | def specifications(self, user, sort=None, quantity=None, filter=None, |
862 | - prejoin_people=True): |
863 | + need_people=True, need_branches=True, |
864 | + need_workitems=False): |
865 | return self.project.specifications( |
866 | - user, sort, quantity, filter, self.name, |
867 | - prejoin_people=prejoin_people) |
868 | + user, sort, quantity, filter, self.name, need_people=need_people, |
869 | + need_branches=need_branches, need_workitems=need_workitems) |
870 | |
871 | @property |
872 | def title(self): |
873 | |
874 | === modified file 'lib/lp/registry/tests/test_distroseries.py' |
875 | --- lib/lp/registry/tests/test_distroseries.py 2012-09-26 04:14:01 +0000 |
876 | +++ lib/lp/registry/tests/test_distroseries.py 2013-08-20 07:26:08 +0000 |
877 | @@ -1,4 +1,4 @@ |
878 | -# Copyright 2009-2012 Canonical Ltd. This software is licensed under the |
879 | +# Copyright 2009-2013 Canonical Ltd. This software is licensed under the |
880 | # GNU Affero General Public License version 3 (see the file LICENSE). |
881 | |
882 | """Tests for distroseries.""" |
883 | @@ -11,6 +11,7 @@ |
884 | |
885 | from logging import getLogger |
886 | |
887 | +from testtools.matchers import Equals |
888 | import transaction |
889 | from zope.component import getUtility |
890 | from zope.security.proxy import removeSecurityProxy |
891 | @@ -19,6 +20,7 @@ |
892 | from lp.registry.interfaces.distroseries import IDistroSeriesSet |
893 | from lp.registry.interfaces.pocket import PackagePublishingPocket |
894 | from lp.registry.interfaces.series import SeriesStatus |
895 | +from lp.services.database.interfaces import IStore |
896 | from lp.soyuz.enums import ( |
897 | ArchivePurpose, |
898 | PackagePublishingStatus, |
899 | @@ -38,6 +40,7 @@ |
900 | ANONYMOUS, |
901 | login, |
902 | person_logged_in, |
903 | + StormStatementRecorder, |
904 | TestCase, |
905 | TestCaseWithFactory, |
906 | ) |
907 | @@ -45,6 +48,7 @@ |
908 | DatabaseFunctionalLayer, |
909 | LaunchpadFunctionalLayer, |
910 | ) |
911 | +from lp.testing.matchers import HasQueryCount |
912 | from lp.translations.interfaces.translations import ( |
913 | TranslationsBranchImportMode, |
914 | ) |
915 | @@ -324,6 +328,23 @@ |
916 | self.assertTrue(self.checkLegalPocket( |
917 | SeriesStatus.CURRENT, PackagePublishingPocket.UPDATES)) |
918 | |
919 | + def test_valid_specifications_query_count(self): |
920 | + distroseries = self.factory.makeDistroSeries() |
921 | + distribution = distroseries.distribution |
922 | + spec1 = self.factory.makeSpecification( |
923 | + distribution=distribution, goal=distroseries) |
924 | + spec2 = self.factory.makeSpecification( |
925 | + distribution=distribution, goal=distroseries) |
926 | + for i in range(5): |
927 | + self.factory.makeSpecificationWorkItem(specification=spec1) |
928 | + self.factory.makeSpecificationWorkItem(specification=spec2) |
929 | + IStore(spec1.__class__).flush() |
930 | + IStore(spec1.__class__).invalidate() |
931 | + with StormStatementRecorder() as recorder: |
932 | + for spec in distroseries.api_valid_specifications: |
933 | + spec.workitems_text |
934 | + self.assertThat(recorder, HasQueryCount(Equals(4))) |
935 | + |
936 | |
937 | class TestDistroSeriesPackaging(TestCaseWithFactory): |
938 | |
939 | |
940 | === modified file 'lib/lp/registry/tests/test_product.py' |
941 | --- lib/lp/registry/tests/test_product.py 2013-06-20 05:50:00 +0000 |
942 | +++ lib/lp/registry/tests/test_product.py 2013-08-20 07:26:08 +0000 |
943 | @@ -813,8 +813,8 @@ |
944 | 'owner', 'parent_subscription_target', 'project', 'title', )), |
945 | 'launchpad.View': set(( |
946 | '_getOfficialTagClause', 'visible_specifications', |
947 | - '_valid_specifications', 'active_or_packaged_series', |
948 | - 'aliases', 'all_milestones', |
949 | + 'valid_specifications', 'api_valid_specifications', |
950 | + 'active_or_packaged_series', 'aliases', 'all_milestones', |
951 | 'allowsTranslationEdits', 'allowsTranslationSuggestions', |
952 | 'announce', 'answer_contacts', 'answers_usage', 'autoupdate', |
953 | 'blueprints_usage', 'branch_sharing_policy', |
954 | |
955 | === modified file 'lib/lp/registry/tests/test_productseries.py' |
956 | --- lib/lp/registry/tests/test_productseries.py 2013-06-20 05:50:00 +0000 |
957 | +++ lib/lp/registry/tests/test_productseries.py 2013-08-20 07:26:08 +0000 |
958 | @@ -627,13 +627,13 @@ |
959 | 'parent_subscription_target', 'product', 'productID', 'series')), |
960 | 'launchpad.View': set(( |
961 | 'visible_specifications', '_getOfficialTagClause', |
962 | - '_valid_specifications', 'active', 'all_milestones', |
963 | - 'answers_usage', 'blueprints_usage', 'branch', |
964 | - 'bug_reported_acknowledgement', 'bug_reporting_guidelines', |
965 | - 'bug_subscriptions', 'bug_supervisor', 'bug_tracking_usage', |
966 | - 'bugtargetname', |
967 | - 'codehosting_usage', 'createBug', 'datecreated', 'displayname', |
968 | - 'driver', 'drivers', 'enable_bugfiling_duplicate_search', |
969 | + 'valid_specifications', 'api_valid_specifications', |
970 | + 'active', 'all_milestones', 'answers_usage', 'blueprints_usage', |
971 | + 'branch', 'bug_reported_acknowledgement', |
972 | + 'bug_reporting_guidelines', 'bug_subscriptions', 'bug_supervisor', |
973 | + 'bug_tracking_usage', 'bugtargetname', 'codehosting_usage', |
974 | + 'createBug', 'datecreated', 'displayname', 'driver', 'drivers', |
975 | + 'enable_bugfiling_duplicate_search', |
976 | 'getAllowedSpecificationInformationTypes', |
977 | 'getBugSummaryContextWhereClause', |
978 | 'getBugTaskWeightFunction', 'getCachedReleases', |
979 | |
980 | === modified file 'lib/lp/soyuz/browser/tests/test_archive_webservice.py' |
981 | --- lib/lp/soyuz/browser/tests/test_archive_webservice.py 2013-05-10 05:30:11 +0000 |
982 | +++ lib/lp/soyuz/browser/tests/test_archive_webservice.py 2013-08-20 07:26:08 +0000 |
983 | @@ -1,4 +1,4 @@ |
984 | -# Copyright 2010-2012 Canonical Ltd. This software is licensed under the |
985 | +# Copyright 2010-2013 Canonical Ltd. This software is licensed under the |
986 | # GNU Affero General Public License version 3 (see the file LICENSE). |
987 | |
988 | __metaclass__ = type |
989 | @@ -37,14 +37,13 @@ |
990 | ) |
991 | from lp.testing.layers import DatabaseFunctionalLayer |
992 | from lp.testing.matchers import HasQueryCount |
993 | -from lp.testing.pages import LaunchpadWebServiceCaller |
994 | |
995 | |
996 | class TestArchiveWebservice(TestCaseWithFactory): |
997 | layer = DatabaseFunctionalLayer |
998 | |
999 | def setUp(self): |
1000 | - TestCaseWithFactory.setUp(self) |
1001 | + super(TestArchiveWebservice, self).setUp() |
1002 | with celebrity_logged_in('admin'): |
1003 | self.archive = self.factory.makeArchive( |
1004 | purpose=ArchivePurpose.PRIMARY) |
1005 | @@ -55,9 +54,6 @@ |
1006 | distroseries_name = distroseries.name |
1007 | person_name = person.name |
1008 | |
1009 | - self.webservice = LaunchpadWebServiceCaller( |
1010 | - 'launchpad-library', 'salgado-change-anything') |
1011 | - |
1012 | self.launchpad = launchpadlib_for( |
1013 | "archive test", "salgado", "WRITE_PUBLIC") |
1014 | self.distribution = self.launchpad.distributions[distro_name] |
125 - for work_item in self.work_items:
126 + for work_item in self._work_items:
127 if (work_item.title not in title_counts or
Can't this use the cached version?
267 + for specid in work_items_ by_spec. keys(): by_spec[ specid] .sort(key= lambda wi: wi.sequence) by_spec[ specid] : cache(workitem. specification) .work_items. append(
268 + work_items_
269 + for workitem in work_items_
270 + get_property_
271 + workitem)
This seems needlessly circuitous. get_property_ cache(spec) .work_items = list(sorted( work_items_ by_spec[ specid] , key=lambda wi: wi.sequence))?