Merge lp:~mwhudson/launchpad/move-SpecificationDepCandidatesVocabulary into lp:launchpad
- move-SpecificationDepCandidatesVocabulary
- Merge into devel
Proposed by
Michael Hudson-Doyle
Status: | Merged |
---|---|
Approved by: | Michael Hudson-Doyle |
Approved revision: | no longer in the source branch. |
Merged at revision: | 11446 |
Proposed branch: | lp:~mwhudson/launchpad/move-SpecificationDepCandidatesVocabulary |
Merge into: | lp:launchpad |
Diff against target: |
735 lines (+292/-223) 9 files modified
lib/canonical/launchpad/doc/vocabularies.txt (+38/-124) lib/canonical/launchpad/vocabularies/configure.zcml (+0/-14) lib/canonical/launchpad/vocabularies/dbobjects.py (+0/-80) lib/lp/blueprints/configure.zcml (+1/-0) lib/lp/blueprints/vocabularies/configure.zcml (+19/-0) lib/lp/blueprints/vocabularies/specificationdependency.py (+108/-0) lib/lp/blueprints/vocabularies/tests/specificationdepcandidates.txt (+99/-0) lib/lp/blueprints/vocabularies/tests/test_doc.py (+17/-0) lib/lp/testing/factory.py (+10/-5) |
To merge this branch: | bzr merge lp:~mwhudson/launchpad/move-SpecificationDepCandidatesVocabulary |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jeroen T. Vermeulen (community) | Approve | ||
Review via email: mp+33611@code.launchpad.net |
Commit message
Move the SpecificationDe
Description of the change
Hi there,
As a prep for some linaro work on blueprints (bug 3552), I moved the implementation and tests for SpecificationDe
I tidied up the test a very little bit and de-moined the doctest they came from. I didn't touch the implementation at all.
Cheers,
mwh
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'lib/canonical/launchpad/doc/vocabularies.txt' |
2 | --- lib/canonical/launchpad/doc/vocabularies.txt 2010-02-17 11:13:06 +0000 |
3 | +++ lib/canonical/launchpad/doc/vocabularies.txt 2010-08-25 21:37:45 +0000 |
4 | @@ -1,6 +1,8 @@ |
5 | -= Vocabularies = |
6 | +Vocabularies |
7 | +============ |
8 | |
9 | -== Introduction == |
10 | +Introduction |
11 | +------------ |
12 | |
13 | Vocabularies are lists of terms. In Launchpad's Component Architecture |
14 | (CA), a vocabulary is a list of terms that a widget (normally a selection |
15 | @@ -18,7 +20,8 @@ |
16 | >>> launchbag.clear() |
17 | |
18 | |
19 | -=== Values, Tokens, and Titles === |
20 | +Values, Tokens, and Titles |
21 | +.......................... |
22 | |
23 | In Launchpad, we generally use "tokenized vocabularies." Each term in |
24 | a vocabulary has a value, token and title. A term is rendered in a |
25 | @@ -31,7 +34,8 @@ |
26 | to the user. |
27 | |
28 | |
29 | -== Launchpad Vocabularies == |
30 | +Launchpad Vocabularies |
31 | +---------------------- |
32 | |
33 | There are two kinds of vocabularies in Launchpad: enumerable and |
34 | non-enumerable. Enumerable vocabularies are short enough to render in a |
35 | @@ -53,10 +57,12 @@ |
36 | 'Select a project' |
37 | |
38 | |
39 | -== Enumerable Vocabularies == |
40 | - |
41 | - |
42 | -=== DistributionUsingMaloneVocabulary === |
43 | +Enumerable Vocabularies |
44 | +----------------------- |
45 | + |
46 | + |
47 | +DistributionUsingMaloneVocabulary |
48 | +................................. |
49 | |
50 | All the distributions that use Malone as their main bug tracker. |
51 | |
52 | @@ -99,7 +105,8 @@ |
53 | LookupError:... |
54 | |
55 | |
56 | -=== BugNominatableSeriesVocabulary === |
57 | +BugNominatableSeriesVocabulary |
58 | +.............................. |
59 | |
60 | All the series that can be nominated for fixing. |
61 | |
62 | @@ -244,7 +251,8 @@ |
63 | NoSuchDistroSeries... |
64 | |
65 | |
66 | -=== ProjectProductsVocabularyUsingMalone === |
67 | +ProjectProductsVocabularyUsingMalone |
68 | +.................................... |
69 | |
70 | All the products in a project using Malone. |
71 | |
72 | @@ -262,14 +270,16 @@ |
73 | firefox: Mozilla Firefox |
74 | |
75 | |
76 | -== Non-Enumerable Vocabularies == |
77 | +Non-Enumerable Vocabularies |
78 | +--------------------------- |
79 | |
80 | Iterating over non-enumerable vocabularies, while possible, will |
81 | probably kill the database. Instead, these vocabularies are |
82 | search-driven. |
83 | |
84 | |
85 | -=== BinaryAndSourcePackageNameVocabulary === |
86 | +BinaryAndSourcePackageNameVocabulary |
87 | +.................................... |
88 | |
89 | The list of binary and source package names, ordered by name. |
90 | |
91 | @@ -315,7 +325,8 @@ |
92 | ('linux-source-2.6.15', u'Source of: linux-2.6.12')] |
93 | |
94 | |
95 | -=== BinaryPackageNameVocabulary === |
96 | +BinaryPackageNameVocabulary |
97 | +........................... |
98 | |
99 | All the binary packages in Launchpad. |
100 | |
101 | @@ -331,7 +342,8 @@ |
102 | ('mozilla-firefox-data', u'Mozilla Firefox Data is .....')] |
103 | |
104 | |
105 | -=== SourcePackageNameVocabulary === |
106 | +SourcePackageNameVocabulary |
107 | +........................... |
108 | |
109 | All the source packages in Launchpad. |
110 | |
111 | @@ -352,7 +364,8 @@ |
112 | [('pmount', u'pmount')] |
113 | |
114 | |
115 | -=== BranchVocabulary === |
116 | +BranchVocabulary |
117 | +................ |
118 | |
119 | The list of bzr branches registered in Launchpad. |
120 | |
121 | @@ -432,7 +445,8 @@ |
122 | >>> login('foo.bar@canonical.com') |
123 | |
124 | |
125 | -=== BranchRestrictedOnProduct === |
126 | +BranchRestrictedOnProduct |
127 | +......................... |
128 | |
129 | The BranchRestrictedOnProduct vocabulary restricts the result set to |
130 | those of the product of the context. Currently only two types of |
131 | @@ -461,7 +475,8 @@ |
132 | BranchVocabulary with respect to the tokens and privacy awareness. |
133 | |
134 | |
135 | -=== HostedBranchRestrictedOnOwner === |
136 | +HostedBranchRestrictedOnOwner |
137 | +............................. |
138 | |
139 | Here's a vocabulary for all hosted branches owned by the current user. |
140 | |
141 | @@ -486,7 +501,8 @@ |
142 | ~a-branching-user/product-two/hosted |
143 | |
144 | |
145 | -=== Processor === |
146 | +Processor |
147 | +......... |
148 | |
149 | All processors type available in Launchpad. |
150 | |
151 | @@ -498,7 +514,8 @@ |
152 | ['386'] |
153 | |
154 | |
155 | -=== BugWatchVocabulary === |
156 | +BugWatchVocabulary |
157 | +.................. |
158 | |
159 | All bug watches associated with a bugtask's bug. |
160 | |
161 | @@ -555,111 +572,8 @@ |
162 | Lionel Richtea (mailto:<email address hidden>) |
163 | |
164 | |
165 | -== SpecificationDepCandidatesVocabulary == |
166 | - |
167 | -All blueprints that can be added as a dependency of the |
168 | -context blueprint. |
169 | - |
170 | -First, we set up a product with three blueprints. |
171 | - |
172 | - >>> from canonical.launchpad.interfaces import ( |
173 | - ... ISpecificationSet, SpecificationDefinitionStatus) |
174 | - >>> evolution = product_set.getByName('evolution') |
175 | - >>> foobar_person = person_set.getByName('name16') |
176 | - >>> foobar_person.displayname |
177 | - u'Foo Bar' |
178 | - >>> specset = getUtility(ISpecificationSet) |
179 | - >>> spec_a = specset.new('spec-a', 'Spec A', |
180 | - ... 'http://www.example.org/SpecA', 'The first spec', |
181 | - ... SpecificationDefinitionStatus.APPROVED, foobar_person, |
182 | - ... product=evolution) |
183 | - >>> spec_b = specset.new('spec-b', 'Spec B', |
184 | - ... 'http://www.example.org/SpecB', 'The second spec', |
185 | - ... SpecificationDefinitionStatus.APPROVED, foobar_person, |
186 | - ... product=evolution) |
187 | - >>> spec_c = specset.new('spec-c', 'Spec C', |
188 | - ... 'http://www.example.org/SpecC', 'The third spec', |
189 | - ... SpecificationDefinitionStatus.APPROVED, foobar_person, |
190 | - ... product=evolution) |
191 | - >>> sorted([spec.name for spec in evolution.specifications()]) |
192 | - [u'spec-a', u'spec-b', u'spec-c'] |
193 | - |
194 | -The dependency candidates for spec_a are all blueprints for evolution |
195 | -except for spec_a itself. |
196 | - |
197 | - >>> vocab = vocabulary_registry.get( |
198 | - ... spec_a, "SpecificationDepCandidates") |
199 | - >>> sorted([term.value.name for term in vocab]) |
200 | - [u'spec-b', u'spec-c'] |
201 | - |
202 | -Dependency candidate come only from the same product of the blueprint |
203 | -they depend on. |
204 | - |
205 | - >>> unrelated_spec = specset.new('unrelated-spec', 'Unrelated Spec', |
206 | - ... 'http://example.com/SpecU', 'A spec unrelated to Evolution', |
207 | - ... SpecificationDefinitionStatus.APPROVED, foobar_person, |
208 | - ... product=firefox) |
209 | - >>> vocab = vocabulary_registry.get( |
210 | - ... spec_a, "SpecificationDepCandidates") |
211 | - >>> unrelated_spec in vocab |
212 | - False |
213 | - >>> [term.value.product for term in vocab |
214 | - ... if term.value.product != evolution] |
215 | - [] |
216 | - |
217 | -We mark spec_b as a dependency of spec_a and spec_c as a dependency |
218 | -of spec_b. |
219 | - |
220 | - >>> spec_a.createDependency(spec_b) |
221 | - <SpecificationDependency at ...> |
222 | - >>> [spec.name for spec in spec_a.dependencies] |
223 | - [u'spec-b'] |
224 | - |
225 | - >>> spec_b.createDependency(spec_c) |
226 | - <SpecificationDependency at ...> |
227 | - >>> [spec.name for spec in spec_b.dependencies] |
228 | - [u'spec-c'] |
229 | - |
230 | -No circular dependencies - the vocabulary excludes specifications that |
231 | -are a dependency of the context spec. |
232 | - |
233 | - >>> spec_a in spec_b.all_blocked |
234 | - True |
235 | - >>> spec_b in spec_c.all_blocked |
236 | - True |
237 | - >>> vocab = vocabulary_registry.get( |
238 | - ... spec_c, "SpecificationDepCandidates") |
239 | - >>> spec_a in [term.value for term in vocab] |
240 | - False |
241 | - |
242 | -This vocabulary provides the IHugeVocabulary interface. |
243 | - |
244 | - >>> from canonical.launchpad.webapp.testing import verifyObject |
245 | - >>> from canonical.launchpad.webapp.vocabulary import IHugeVocabulary |
246 | - >>> verifyObject(IHugeVocabulary, vocab) |
247 | - True |
248 | - |
249 | -The search() method returns specifications within the vocabulary |
250 | -that matches the search string. The string is matched against the name, |
251 | -or fallbacks to a full text search. |
252 | - |
253 | - >>> vocab = get_naked_vocab(spec_a, "SpecificationDepCandidates") |
254 | - >>> list(vocab.search('spec-b')) == [spec_b] |
255 | - True |
256 | - >>> list(vocab.search('third')) == [spec_c] |
257 | - True |
258 | - |
259 | -The search method uses the SQL `LIKE` operator, with the values quoted |
260 | -appropriately. Queries conataining regual expression operators, for |
261 | -example, will simply look for the respective characters within the |
262 | -vocabulary's item (this used to be the cause of an OOPS, see |
263 | -https://bugs.edge.launchpad.net/blueprint/+bug/139385 for more details). |
264 | - |
265 | - >>> list(vocab.search('*')) |
266 | - [] |
267 | - |
268 | - |
269 | -=== PPA === |
270 | +PPA |
271 | +... |
272 | |
273 | The PPA vocabulary contains all the PPAs available in a particular |
274 | collection. It provides the IHugeVocabulary interface. |
275 | |
276 | === modified file 'lib/canonical/launchpad/vocabularies/configure.zcml' |
277 | --- lib/canonical/launchpad/vocabularies/configure.zcml 2010-06-21 04:08:54 +0000 |
278 | +++ lib/canonical/launchpad/vocabularies/configure.zcml 2010-08-25 21:37:45 +0000 |
279 | @@ -309,20 +309,6 @@ |
280 | <allow interface="canonical.launchpad.webapp.vocabulary.IHugeVocabulary"/> |
281 | </class> |
282 | |
283 | - |
284 | - <securedutility |
285 | - name="SpecificationDepCandidates" |
286 | - component="canonical.launchpad.vocabularies.SpecificationDepCandidatesVocabulary" |
287 | - provides="zope.schema.interfaces.IVocabularyFactory" |
288 | - > |
289 | - <allow interface="zope.schema.interfaces.IVocabularyFactory"/> |
290 | - </securedutility> |
291 | - |
292 | - <class class="canonical.launchpad.vocabularies.SpecificationDepCandidatesVocabulary"> |
293 | - <allow interface="canonical.launchpad.webapp.vocabulary.IHugeVocabulary"/> |
294 | - </class> |
295 | - |
296 | - |
297 | <securedutility |
298 | name="Sprint" |
299 | component="canonical.launchpad.vocabularies.SprintVocabulary" |
300 | |
301 | === modified file 'lib/canonical/launchpad/vocabularies/dbobjects.py' |
302 | --- lib/canonical/launchpad/vocabularies/dbobjects.py 2010-08-20 20:31:18 +0000 |
303 | +++ lib/canonical/launchpad/vocabularies/dbobjects.py 2010-08-25 21:37:45 +0000 |
304 | @@ -33,7 +33,6 @@ |
305 | 'ProcessorFamilyVocabulary', |
306 | 'ProcessorVocabulary', |
307 | 'project_products_using_malone_vocabulary_factory', |
308 | - 'SpecificationDepCandidatesVocabulary', |
309 | 'SpecificationDependenciesVocabulary', |
310 | 'SpecificationVocabulary', |
311 | 'SprintVocabulary', |
312 | @@ -70,7 +69,6 @@ |
313 | |
314 | from canonical.database.sqlbase import ( |
315 | quote, |
316 | - quote_like, |
317 | sqlvalues, |
318 | ) |
319 | from canonical.launchpad.database import ( |
320 | @@ -87,7 +85,6 @@ |
321 | SQLObjectVocabularyBase, |
322 | ) |
323 | from lp.app.browser.stringformatter import FormattersAPI |
324 | -from lp.blueprints.interfaces.specification import SpecificationFilter |
325 | from lp.blueprints.model.specification import Specification |
326 | from lp.blueprints.model.sprint import Sprint |
327 | from lp.bugs.interfaces.bugtask import IBugTask |
328 | @@ -518,83 +515,6 @@ |
329 | yield SimpleTerm(spec, spec.name, spec.title) |
330 | |
331 | |
332 | -class SpecificationDepCandidatesVocabulary(SQLObjectVocabularyBase): |
333 | - """Specifications that could be dependencies of this spec. |
334 | - |
335 | - This includes only those specs that are not blocked by this spec |
336 | - (directly or indirectly), unless they are already dependencies. |
337 | - |
338 | - The current spec is not included. |
339 | - """ |
340 | - |
341 | - implements(IHugeVocabulary) |
342 | - |
343 | - _table = Specification |
344 | - _orderBy = 'name' |
345 | - displayname = 'Select a blueprint' |
346 | - |
347 | - def _filter_specs(self, specs): |
348 | - # XXX intellectronica 2007-07-05: is 100 a reasonable count before |
349 | - # starting to warn? |
350 | - speclist = shortlist(specs, 100) |
351 | - return [spec for spec in speclist |
352 | - if (spec != self.context and |
353 | - spec.target == self.context.target |
354 | - and spec not in self.context.all_blocked)] |
355 | - |
356 | - def _doSearch(self, query): |
357 | - """Return terms where query is in the text of name |
358 | - or title, or matches the full text index. |
359 | - """ |
360 | - |
361 | - if not query: |
362 | - return [] |
363 | - |
364 | - quoted_query = quote_like(query) |
365 | - sql_query = (""" |
366 | - (Specification.name LIKE %s OR |
367 | - Specification.title LIKE %s OR |
368 | - fti @@ ftq(%s)) |
369 | - """ |
370 | - % (quoted_query, quoted_query, quoted_query)) |
371 | - all_specs = Specification.select(sql_query, orderBy=self._orderBy) |
372 | - |
373 | - return self._filter_specs(all_specs) |
374 | - |
375 | - def toTerm(self, obj): |
376 | - return SimpleTerm(obj, obj.name, obj.title) |
377 | - |
378 | - def getTermByToken(self, token): |
379 | - search_results = self._doSearch(token) |
380 | - for search_result in search_results: |
381 | - if search_result.name == token: |
382 | - return self.toTerm(search_result) |
383 | - raise LookupError(token) |
384 | - |
385 | - def search(self, query): |
386 | - candidate_specs = self._doSearch(query) |
387 | - return CountableIterator(len(candidate_specs), |
388 | - candidate_specs) |
389 | - |
390 | - def _all_specs(self): |
391 | - all_specs = self.context.target.specifications( |
392 | - filter=[SpecificationFilter.ALL], |
393 | - prejoin_people=False) |
394 | - return self._filter_specs(all_specs) |
395 | - |
396 | - def __iter__(self): |
397 | - return (self.toTerm(spec) for spec in self._all_specs()) |
398 | - |
399 | - def __contains__(self, obj): |
400 | - # We don't use self._all_specs here, since it will call |
401 | - # self._filter_specs(all_specs) which will cause all the specs |
402 | - # to be loaded, whereas obj in all_specs will query a single object. |
403 | - all_specs = self.context.target.specifications( |
404 | - filter=[SpecificationFilter.ALL], |
405 | - prejoin_people=False) |
406 | - return obj in all_specs and len(self._filter_specs([obj])) > 0 |
407 | - |
408 | - |
409 | class SprintVocabulary(NamedSQLObjectVocabulary): |
410 | _table = Sprint |
411 | |
412 | |
413 | === modified file 'lib/lp/blueprints/configure.zcml' |
414 | --- lib/lp/blueprints/configure.zcml 2010-08-19 03:06:27 +0000 |
415 | +++ lib/lp/blueprints/configure.zcml 2010-08-25 21:37:45 +0000 |
416 | @@ -11,6 +11,7 @@ |
417 | i18n_domain="launchpad"> |
418 | |
419 | <include package=".browser"/> |
420 | + <include package=".vocabularies"/> |
421 | |
422 | <publisher |
423 | name="blueprints" |
424 | |
425 | === added directory 'lib/lp/blueprints/vocabularies' |
426 | === added file 'lib/lp/blueprints/vocabularies/__init__.py' |
427 | === added file 'lib/lp/blueprints/vocabularies/configure.zcml' |
428 | --- lib/lp/blueprints/vocabularies/configure.zcml 1970-01-01 00:00:00 +0000 |
429 | +++ lib/lp/blueprints/vocabularies/configure.zcml 2010-08-25 21:37:45 +0000 |
430 | @@ -0,0 +1,19 @@ |
431 | +<!-- Copyright 2010 Canonical Ltd. This software is licensed under the |
432 | + GNU Affero General Public License version 3 (see the file LICENSE). |
433 | +--> |
434 | + |
435 | +<configure xmlns="http://namespaces.zope.org/zope"> |
436 | + |
437 | + <securedutility |
438 | + name="SpecificationDepCandidates" |
439 | + component=".specificationdependency.SpecificationDepCandidatesVocabulary" |
440 | + provides="zope.schema.interfaces.IVocabularyFactory" |
441 | + > |
442 | + <allow interface="zope.schema.interfaces.IVocabularyFactory"/> |
443 | + </securedutility> |
444 | + |
445 | + <class class=".specificationdependency.SpecificationDepCandidatesVocabulary"> |
446 | + <allow interface="canonical.launchpad.webapp.vocabulary.IHugeVocabulary"/> |
447 | + </class> |
448 | + |
449 | +</configure> |
450 | |
451 | === added file 'lib/lp/blueprints/vocabularies/specificationdependency.py' |
452 | --- lib/lp/blueprints/vocabularies/specificationdependency.py 1970-01-01 00:00:00 +0000 |
453 | +++ lib/lp/blueprints/vocabularies/specificationdependency.py 2010-08-25 21:37:45 +0000 |
454 | @@ -0,0 +1,108 @@ |
455 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
456 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
457 | + |
458 | +"""The vocabularies relating to dependencies of specifications.""" |
459 | + |
460 | +__metaclass__ = type |
461 | +__all__ = ['SpecificationDepCandidatesVocabulary'] |
462 | + |
463 | +from zope.interface import implements |
464 | +from zope.schema.vocabulary import SimpleTerm |
465 | + |
466 | +from canonical.database.sqlbase import quote_like |
467 | +from canonical.launchpad.helpers import shortlist |
468 | +from canonical.launchpad.webapp.vocabulary import ( |
469 | + CountableIterator, |
470 | + IHugeVocabulary, |
471 | + SQLObjectVocabularyBase, |
472 | + ) |
473 | + |
474 | +from lp.blueprints.interfaces.specification import SpecificationFilter |
475 | +from lp.blueprints.model.specification import Specification |
476 | + |
477 | + |
478 | +class SpecificationDepCandidatesVocabulary(SQLObjectVocabularyBase): |
479 | + """Specifications that could be dependencies of this spec. |
480 | + |
481 | + This includes only those specs that are not blocked by this spec |
482 | + (directly or indirectly), unless they are already dependencies. |
483 | + |
484 | + The current spec is not included. |
485 | + """ |
486 | + |
487 | + implements(IHugeVocabulary) |
488 | + |
489 | + _table = Specification |
490 | + _orderBy = 'name' |
491 | + displayname = 'Select a blueprint' |
492 | + |
493 | + def _filter_specs(self, specs): |
494 | + """Filter `specs` to remove invalid candidates. |
495 | + |
496 | + Invalid candidates are: |
497 | + |
498 | + * The spec that we're adding a depdency to, |
499 | + * Specs for a different target, and |
500 | + * Specs that depend on this one. |
501 | + |
502 | + Preventing the last category prevents loops in the dependency graph. |
503 | + """ |
504 | + # XXX intellectronica 2007-07-05: is 100 a reasonable count before |
505 | + # starting to warn? |
506 | + speclist = shortlist(specs, 100) |
507 | + return [spec for spec in speclist |
508 | + if (spec != self.context and |
509 | + spec.target == self.context.target |
510 | + and spec not in self.context.all_blocked)] |
511 | + |
512 | + def _doSearch(self, query): |
513 | + """Return terms where query is in the text of name |
514 | + or title, or matches the full text index. |
515 | + """ |
516 | + |
517 | + if not query: |
518 | + return [] |
519 | + |
520 | + quoted_query = quote_like(query) |
521 | + sql_query = (""" |
522 | + (Specification.name LIKE %s OR |
523 | + Specification.title LIKE %s OR |
524 | + fti @@ ftq(%s)) |
525 | + """ |
526 | + % (quoted_query, quoted_query, quoted_query)) |
527 | + all_specs = Specification.select(sql_query, orderBy=self._orderBy) |
528 | + |
529 | + return self._filter_specs(all_specs) |
530 | + |
531 | + def toTerm(self, obj): |
532 | + return SimpleTerm(obj, obj.name, obj.title) |
533 | + |
534 | + def getTermByToken(self, token): |
535 | + search_results = self._doSearch(token) |
536 | + for search_result in search_results: |
537 | + if search_result.name == token: |
538 | + return self.toTerm(search_result) |
539 | + raise LookupError(token) |
540 | + |
541 | + def search(self, query): |
542 | + candidate_specs = self._doSearch(query) |
543 | + return CountableIterator(len(candidate_specs), |
544 | + candidate_specs) |
545 | + |
546 | + def _all_specs(self): |
547 | + all_specs = self.context.target.specifications( |
548 | + filter=[SpecificationFilter.ALL], |
549 | + prejoin_people=False) |
550 | + return self._filter_specs(all_specs) |
551 | + |
552 | + def __iter__(self): |
553 | + return (self.toTerm(spec) for spec in self._all_specs()) |
554 | + |
555 | + def __contains__(self, obj): |
556 | + # We don't use self._all_specs here, since it will call |
557 | + # self._filter_specs(all_specs) which will cause all the specs |
558 | + # to be loaded, whereas obj in all_specs will query a single object. |
559 | + all_specs = self.context.target.specifications( |
560 | + filter=[SpecificationFilter.ALL], |
561 | + prejoin_people=False) |
562 | + return obj in all_specs and len(self._filter_specs([obj])) > 0 |
563 | |
564 | === added directory 'lib/lp/blueprints/vocabularies/tests' |
565 | === added file 'lib/lp/blueprints/vocabularies/tests/__init__.py' |
566 | === added file 'lib/lp/blueprints/vocabularies/tests/specificationdepcandidates.txt' |
567 | --- lib/lp/blueprints/vocabularies/tests/specificationdepcandidates.txt 1970-01-01 00:00:00 +0000 |
568 | +++ lib/lp/blueprints/vocabularies/tests/specificationdepcandidates.txt 2010-08-25 21:37:45 +0000 |
569 | @@ -0,0 +1,99 @@ |
570 | +SpecificationDepCandidatesVocabulary |
571 | +==================================== |
572 | + |
573 | +All blueprints that can be added as a dependency of the context |
574 | +blueprint. |
575 | + |
576 | + >>> from zope.schema.vocabulary import getVocabularyRegistry |
577 | + >>> vocabulary_registry = getVocabularyRegistry() |
578 | + |
579 | +First, we set up a product with three blueprints. |
580 | + |
581 | + >>> specced_product = factory.makeProduct() |
582 | + >>> spec_a = factory.makeSpecification( |
583 | + ... name='spec-a', summary='The first spec', |
584 | + ... product=specced_product) |
585 | + >>> spec_b = factory.makeSpecification( |
586 | + ... name='spec-b', summary='The second spec', |
587 | + ... product=specced_product) |
588 | + >>> spec_c = factory.makeSpecification( |
589 | + ... name='spec-c', summary='The third spec', |
590 | + ... product=specced_product) |
591 | + >>> sorted([spec.name for spec in specced_product.specifications()]) |
592 | + [u'spec-a', u'spec-b', u'spec-c'] |
593 | + |
594 | +The dependency candidates for spec_a are all blueprints for |
595 | +specced_product except for spec_a itself. |
596 | + |
597 | + >>> vocab = vocabulary_registry.get( |
598 | + ... spec_a, "SpecificationDepCandidates") |
599 | + >>> sorted([term.value.name for term in vocab]) |
600 | + [u'spec-b', u'spec-c'] |
601 | + |
602 | +Dependency candidate come only from the same product of the blueprint |
603 | +they depend on. |
604 | + |
605 | + >>> unrelated_spec = factory.makeSpecification( |
606 | + ... product=factory.makeProduct()) |
607 | + >>> vocab = vocabulary_registry.get( |
608 | + ... spec_a, "SpecificationDepCandidates") |
609 | + >>> unrelated_spec in vocab |
610 | + False |
611 | + >>> [term.value.product for term in vocab |
612 | + ... if term.value.product != specced_product] |
613 | + [] |
614 | + |
615 | +We mark spec_b as a dependency of spec_a and spec_c as a dependency of |
616 | +spec_b. |
617 | + |
618 | + >>> spec_a.createDependency(spec_b) |
619 | + <SpecificationDependency at ...> |
620 | + >>> [spec.name for spec in spec_a.dependencies] |
621 | + [u'spec-b'] |
622 | + |
623 | + >>> spec_b.createDependency(spec_c) |
624 | + <SpecificationDependency at ...> |
625 | + >>> [spec.name for spec in spec_b.dependencies] |
626 | + [u'spec-c'] |
627 | + |
628 | +No circular dependencies - the vocabulary excludes specifications that |
629 | +are a dependency of the context spec. |
630 | + |
631 | + >>> spec_a in spec_b.all_blocked |
632 | + True |
633 | + >>> spec_b in spec_c.all_blocked |
634 | + True |
635 | + >>> vocab = vocabulary_registry.get( |
636 | + ... spec_c, "SpecificationDepCandidates") |
637 | + >>> spec_a in [term.value for term in vocab] |
638 | + False |
639 | + |
640 | +This vocabulary provides the IHugeVocabulary interface. |
641 | + |
642 | + >>> from canonical.launchpad.webapp.testing import verifyObject |
643 | + >>> from canonical.launchpad.webapp.vocabulary import IHugeVocabulary |
644 | + >>> verifyObject(IHugeVocabulary, vocab) |
645 | + True |
646 | + |
647 | +The search() method returns specifications within the vocabulary that |
648 | +matches the search string. The string is matched against the name, or |
649 | +fallbacks to a full text search. |
650 | + |
651 | + >>> from zope.security.proxy import removeSecurityProxy |
652 | + >>> naked_vocab = removeSecurityProxy( |
653 | + ... vocabulary_registry.get( |
654 | + ... spec_a, "SpecificationDepCandidates")) |
655 | + >>> list(naked_vocab.search('spec-b')) == [spec_b] |
656 | + True |
657 | + >>> list(naked_vocab.search('third')) == [spec_c] |
658 | + True |
659 | + |
660 | +The search method uses the SQL `LIKE` operator, with the values quoted |
661 | +appropriately. Queries conataining regual expression operators, for |
662 | +example, will simply look for the respective characters within the |
663 | +vocabulary's item (this used to be the cause of an OOPS, see |
664 | +https://bugs.edge.launchpad.net/blueprint/+bug/139385 for more |
665 | +details). |
666 | + |
667 | + >>> list(naked_vocab.search('*')) |
668 | + [] |
669 | |
670 | === added file 'lib/lp/blueprints/vocabularies/tests/test_doc.py' |
671 | --- lib/lp/blueprints/vocabularies/tests/test_doc.py 1970-01-01 00:00:00 +0000 |
672 | +++ lib/lp/blueprints/vocabularies/tests/test_doc.py 2010-08-25 21:37:45 +0000 |
673 | @@ -0,0 +1,17 @@ |
674 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
675 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
676 | + |
677 | +""" |
678 | +Run the doctests. |
679 | +""" |
680 | + |
681 | +import os |
682 | + |
683 | +from lp.services.testing import build_doctest_suite |
684 | + |
685 | + |
686 | +here = os.path.dirname(os.path.realpath(__file__)) |
687 | + |
688 | + |
689 | +def test_suite(): |
690 | + return build_doctest_suite(here, '') |
691 | |
692 | === modified file 'lib/lp/testing/factory.py' |
693 | --- lib/lp/testing/factory.py 2010-08-22 18:31:30 +0000 |
694 | +++ lib/lp/testing/factory.py 2010-08-25 21:37:45 +0000 |
695 | @@ -439,7 +439,6 @@ |
696 | registry_team.addMember(user, registry_team.teamowner) |
697 | return user |
698 | |
699 | - |
700 | def makeCopyArchiveLocation(self, distribution=None, owner=None, |
701 | name=None, enabled=True): |
702 | """Create and return a new arbitrary location for copy packages.""" |
703 | @@ -1495,7 +1494,9 @@ |
704 | mail.parsed_string = mail.as_string() |
705 | return mail |
706 | |
707 | - def makeSpecification(self, product=None, title=None, distribution=None): |
708 | + def makeSpecification(self, product=None, title=None, distribution=None, |
709 | + name=None, summary=None, |
710 | + status=SpecificationDefinitionStatus.NEW): |
711 | """Create and return a new, arbitrary Blueprint. |
712 | |
713 | :param product: The product to make the blueprint on. If one is |
714 | @@ -1503,14 +1504,18 @@ |
715 | """ |
716 | if distribution is None and product is None: |
717 | product = self.makeProduct() |
718 | + if name is None: |
719 | + name = self.getUniqueString('name') |
720 | + if summary is None: |
721 | + summary = self.getUniqueString('summary') |
722 | if title is None: |
723 | title = self.getUniqueString('title') |
724 | return getUtility(ISpecificationSet).new( |
725 | - name=self.getUniqueString('name'), |
726 | + name=name, |
727 | title=title, |
728 | specurl=None, |
729 | - summary=self.getUniqueString('summary'), |
730 | - definition_status=SpecificationDefinitionStatus.NEW, |
731 | + summary=summary, |
732 | + definition_status=status, |
733 | owner=self.makePerson(), |
734 | product=product, |
735 | distribution=distribution) |
Thanks for making the changes. (FTR: Discussed on IRC, see revision log). There were a few formalities missing with the code you moved, but no need to fix them all here.
One more nit: s/canidates/ candidates/ g
Jeroen