Merge lp:~mwhudson/launchpad/move-SpecificationDepCandidatesVocabulary into lp:launchpad

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
Reviewer Review Type Date Requested Status
Jeroen T. Vermeulen (community) Approve
Review via email: mp+33611@code.launchpad.net

Commit message

Move the SpecificationDepCandidates vocab into the lp.blueprints tree

Description of the change

Hi there,

As a prep for some linaro work on blueprints (bug 3552), I moved the implementation and tests for SpecificationDepCandidates vocabulary into the lp.blueprints tree.

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.
Revision history for this message
Jeroen T. Vermeulen (jtv) wrote :

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

review: Approve

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)