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
=== modified file 'lib/canonical/launchpad/doc/vocabularies.txt'
--- lib/canonical/launchpad/doc/vocabularies.txt 2010-02-17 11:13:06 +0000
+++ lib/canonical/launchpad/doc/vocabularies.txt 2010-08-25 21:37:45 +0000
@@ -1,6 +1,8 @@
1= Vocabularies =1Vocabularies
2============
23
3== Introduction ==4Introduction
5------------
46
5Vocabularies are lists of terms. In Launchpad's Component Architecture7Vocabularies are lists of terms. In Launchpad's Component Architecture
6(CA), a vocabulary is a list of terms that a widget (normally a selection8(CA), a vocabulary is a list of terms that a widget (normally a selection
@@ -18,7 +20,8 @@
18 >>> launchbag.clear()20 >>> launchbag.clear()
1921
2022
21=== Values, Tokens, and Titles ===23Values, Tokens, and Titles
24..........................
2225
23In Launchpad, we generally use "tokenized vocabularies." Each term in26In Launchpad, we generally use "tokenized vocabularies." Each term in
24a vocabulary has a value, token and title. A term is rendered in a27a vocabulary has a value, token and title. A term is rendered in a
@@ -31,7 +34,8 @@
31to the user.34to the user.
3235
3336
34== Launchpad Vocabularies ==37Launchpad Vocabularies
38----------------------
3539
36There are two kinds of vocabularies in Launchpad: enumerable and40There are two kinds of vocabularies in Launchpad: enumerable and
37non-enumerable. Enumerable vocabularies are short enough to render in a41non-enumerable. Enumerable vocabularies are short enough to render in a
@@ -53,10 +57,12 @@
53 'Select a project'57 'Select a project'
5458
5559
56== Enumerable Vocabularies ==60Enumerable Vocabularies
5761-----------------------
5862
59=== DistributionUsingMaloneVocabulary ===63
64DistributionUsingMaloneVocabulary
65.................................
6066
61All the distributions that use Malone as their main bug tracker.67All the distributions that use Malone as their main bug tracker.
6268
@@ -99,7 +105,8 @@
99 LookupError:...105 LookupError:...
100106
101107
102=== BugNominatableSeriesVocabulary ===108BugNominatableSeriesVocabulary
109..............................
103110
104All the series that can be nominated for fixing.111All the series that can be nominated for fixing.
105112
@@ -244,7 +251,8 @@
244 NoSuchDistroSeries...251 NoSuchDistroSeries...
245252
246253
247=== ProjectProductsVocabularyUsingMalone ===254ProjectProductsVocabularyUsingMalone
255....................................
248256
249All the products in a project using Malone.257All the products in a project using Malone.
250258
@@ -262,14 +270,16 @@
262 firefox: Mozilla Firefox270 firefox: Mozilla Firefox
263271
264272
265== Non-Enumerable Vocabularies ==273Non-Enumerable Vocabularies
274---------------------------
266275
267Iterating over non-enumerable vocabularies, while possible, will276Iterating over non-enumerable vocabularies, while possible, will
268probably kill the database. Instead, these vocabularies are277probably kill the database. Instead, these vocabularies are
269search-driven.278search-driven.
270279
271280
272=== BinaryAndSourcePackageNameVocabulary ===281BinaryAndSourcePackageNameVocabulary
282....................................
273283
274The list of binary and source package names, ordered by name.284The list of binary and source package names, ordered by name.
275285
@@ -315,7 +325,8 @@
315 ('linux-source-2.6.15', u'Source of: linux-2.6.12')]325 ('linux-source-2.6.15', u'Source of: linux-2.6.12')]
316326
317327
318=== BinaryPackageNameVocabulary ===328BinaryPackageNameVocabulary
329...........................
319330
320All the binary packages in Launchpad.331All the binary packages in Launchpad.
321332
@@ -331,7 +342,8 @@
331 ('mozilla-firefox-data', u'Mozilla Firefox Data is .....')]342 ('mozilla-firefox-data', u'Mozilla Firefox Data is .....')]
332343
333344
334=== SourcePackageNameVocabulary ===345SourcePackageNameVocabulary
346...........................
335347
336All the source packages in Launchpad.348All the source packages in Launchpad.
337349
@@ -352,7 +364,8 @@
352 [('pmount', u'pmount')]364 [('pmount', u'pmount')]
353365
354366
355=== BranchVocabulary ===367BranchVocabulary
368................
356369
357The list of bzr branches registered in Launchpad.370The list of bzr branches registered in Launchpad.
358371
@@ -432,7 +445,8 @@
432 >>> login('foo.bar@canonical.com')445 >>> login('foo.bar@canonical.com')
433446
434447
435=== BranchRestrictedOnProduct ===448BranchRestrictedOnProduct
449.........................
436450
437The BranchRestrictedOnProduct vocabulary restricts the result set to451The BranchRestrictedOnProduct vocabulary restricts the result set to
438those of the product of the context. Currently only two types of452those of the product of the context. Currently only two types of
@@ -461,7 +475,8 @@
461BranchVocabulary with respect to the tokens and privacy awareness.475BranchVocabulary with respect to the tokens and privacy awareness.
462476
463477
464=== HostedBranchRestrictedOnOwner ===478HostedBranchRestrictedOnOwner
479.............................
465480
466Here's a vocabulary for all hosted branches owned by the current user.481Here's a vocabulary for all hosted branches owned by the current user.
467482
@@ -486,7 +501,8 @@
486 ~a-branching-user/product-two/hosted501 ~a-branching-user/product-two/hosted
487502
488503
489=== Processor ===504Processor
505.........
490506
491All processors type available in Launchpad.507All processors type available in Launchpad.
492508
@@ -498,7 +514,8 @@
498 ['386']514 ['386']
499515
500516
501=== BugWatchVocabulary ===517BugWatchVocabulary
518..................
502519
503All bug watches associated with a bugtask's bug.520All bug watches associated with a bugtask's bug.
504521
@@ -555,111 +572,8 @@
555 Lionel Richtea (mailto:<email address hidden>)572 Lionel Richtea (mailto:<email address hidden>)
556573
557574
558== SpecificationDepCandidatesVocabulary ==575PPA
559576...
560All blueprints that can be added as a dependency of the
561context blueprint.
562
563First, we set up a product with three blueprints.
564
565 >>> from canonical.launchpad.interfaces import (
566 ... ISpecificationSet, SpecificationDefinitionStatus)
567 >>> evolution = product_set.getByName('evolution')
568 >>> foobar_person = person_set.getByName('name16')
569 >>> foobar_person.displayname
570 u'Foo Bar'
571 >>> specset = getUtility(ISpecificationSet)
572 >>> spec_a = specset.new('spec-a', 'Spec A',
573 ... 'http://www.example.org/SpecA', 'The first spec',
574 ... SpecificationDefinitionStatus.APPROVED, foobar_person,
575 ... product=evolution)
576 >>> spec_b = specset.new('spec-b', 'Spec B',
577 ... 'http://www.example.org/SpecB', 'The second spec',
578 ... SpecificationDefinitionStatus.APPROVED, foobar_person,
579 ... product=evolution)
580 >>> spec_c = specset.new('spec-c', 'Spec C',
581 ... 'http://www.example.org/SpecC', 'The third spec',
582 ... SpecificationDefinitionStatus.APPROVED, foobar_person,
583 ... product=evolution)
584 >>> sorted([spec.name for spec in evolution.specifications()])
585 [u'spec-a', u'spec-b', u'spec-c']
586
587The dependency candidates for spec_a are all blueprints for evolution
588except for spec_a itself.
589
590 >>> vocab = vocabulary_registry.get(
591 ... spec_a, "SpecificationDepCandidates")
592 >>> sorted([term.value.name for term in vocab])
593 [u'spec-b', u'spec-c']
594
595Dependency candidate come only from the same product of the blueprint
596they depend on.
597
598 >>> unrelated_spec = specset.new('unrelated-spec', 'Unrelated Spec',
599 ... 'http://example.com/SpecU', 'A spec unrelated to Evolution',
600 ... SpecificationDefinitionStatus.APPROVED, foobar_person,
601 ... product=firefox)
602 >>> vocab = vocabulary_registry.get(
603 ... spec_a, "SpecificationDepCandidates")
604 >>> unrelated_spec in vocab
605 False
606 >>> [term.value.product for term in vocab
607 ... if term.value.product != evolution]
608 []
609
610We mark spec_b as a dependency of spec_a and spec_c as a dependency
611of spec_b.
612
613 >>> spec_a.createDependency(spec_b)
614 <SpecificationDependency at ...>
615 >>> [spec.name for spec in spec_a.dependencies]
616 [u'spec-b']
617
618 >>> spec_b.createDependency(spec_c)
619 <SpecificationDependency at ...>
620 >>> [spec.name for spec in spec_b.dependencies]
621 [u'spec-c']
622
623No circular dependencies - the vocabulary excludes specifications that
624are a dependency of the context spec.
625
626 >>> spec_a in spec_b.all_blocked
627 True
628 >>> spec_b in spec_c.all_blocked
629 True
630 >>> vocab = vocabulary_registry.get(
631 ... spec_c, "SpecificationDepCandidates")
632 >>> spec_a in [term.value for term in vocab]
633 False
634
635This vocabulary provides the IHugeVocabulary interface.
636
637 >>> from canonical.launchpad.webapp.testing import verifyObject
638 >>> from canonical.launchpad.webapp.vocabulary import IHugeVocabulary
639 >>> verifyObject(IHugeVocabulary, vocab)
640 True
641
642The search() method returns specifications within the vocabulary
643that matches the search string. The string is matched against the name,
644or fallbacks to a full text search.
645
646 >>> vocab = get_naked_vocab(spec_a, "SpecificationDepCandidates")
647 >>> list(vocab.search('spec-b')) == [spec_b]
648 True
649 >>> list(vocab.search('third')) == [spec_c]
650 True
651
652The search method uses the SQL `LIKE` operator, with the values quoted
653appropriately. Queries conataining regual expression operators, for
654example, will simply look for the respective characters within the
655vocabulary's item (this used to be the cause of an OOPS, see
656https://bugs.edge.launchpad.net/blueprint/+bug/139385 for more details).
657
658 >>> list(vocab.search('*'))
659 []
660
661
662=== PPA ===
663577
664The PPA vocabulary contains all the PPAs available in a particular578The PPA vocabulary contains all the PPAs available in a particular
665collection. It provides the IHugeVocabulary interface.579collection. It provides the IHugeVocabulary interface.
666580
=== modified file 'lib/canonical/launchpad/vocabularies/configure.zcml'
--- lib/canonical/launchpad/vocabularies/configure.zcml 2010-06-21 04:08:54 +0000
+++ lib/canonical/launchpad/vocabularies/configure.zcml 2010-08-25 21:37:45 +0000
@@ -309,20 +309,6 @@
309 <allow interface="canonical.launchpad.webapp.vocabulary.IHugeVocabulary"/>309 <allow interface="canonical.launchpad.webapp.vocabulary.IHugeVocabulary"/>
310 </class>310 </class>
311311
312
313 <securedutility
314 name="SpecificationDepCandidates"
315 component="canonical.launchpad.vocabularies.SpecificationDepCandidatesVocabulary"
316 provides="zope.schema.interfaces.IVocabularyFactory"
317 >
318 <allow interface="zope.schema.interfaces.IVocabularyFactory"/>
319 </securedutility>
320
321 <class class="canonical.launchpad.vocabularies.SpecificationDepCandidatesVocabulary">
322 <allow interface="canonical.launchpad.webapp.vocabulary.IHugeVocabulary"/>
323 </class>
324
325
326 <securedutility312 <securedutility
327 name="Sprint"313 name="Sprint"
328 component="canonical.launchpad.vocabularies.SprintVocabulary"314 component="canonical.launchpad.vocabularies.SprintVocabulary"
329315
=== modified file 'lib/canonical/launchpad/vocabularies/dbobjects.py'
--- lib/canonical/launchpad/vocabularies/dbobjects.py 2010-08-20 20:31:18 +0000
+++ lib/canonical/launchpad/vocabularies/dbobjects.py 2010-08-25 21:37:45 +0000
@@ -33,7 +33,6 @@
33 'ProcessorFamilyVocabulary',33 'ProcessorFamilyVocabulary',
34 'ProcessorVocabulary',34 'ProcessorVocabulary',
35 'project_products_using_malone_vocabulary_factory',35 'project_products_using_malone_vocabulary_factory',
36 'SpecificationDepCandidatesVocabulary',
37 'SpecificationDependenciesVocabulary',36 'SpecificationDependenciesVocabulary',
38 'SpecificationVocabulary',37 'SpecificationVocabulary',
39 'SprintVocabulary',38 'SprintVocabulary',
@@ -70,7 +69,6 @@
7069
71from canonical.database.sqlbase import (70from canonical.database.sqlbase import (
72 quote,71 quote,
73 quote_like,
74 sqlvalues,72 sqlvalues,
75 )73 )
76from canonical.launchpad.database import (74from canonical.launchpad.database import (
@@ -87,7 +85,6 @@
87 SQLObjectVocabularyBase,85 SQLObjectVocabularyBase,
88 )86 )
89from lp.app.browser.stringformatter import FormattersAPI87from lp.app.browser.stringformatter import FormattersAPI
90from lp.blueprints.interfaces.specification import SpecificationFilter
91from lp.blueprints.model.specification import Specification88from lp.blueprints.model.specification import Specification
92from lp.blueprints.model.sprint import Sprint89from lp.blueprints.model.sprint import Sprint
93from lp.bugs.interfaces.bugtask import IBugTask90from lp.bugs.interfaces.bugtask import IBugTask
@@ -518,83 +515,6 @@
518 yield SimpleTerm(spec, spec.name, spec.title)515 yield SimpleTerm(spec, spec.name, spec.title)
519516
520517
521class SpecificationDepCandidatesVocabulary(SQLObjectVocabularyBase):
522 """Specifications that could be dependencies of this spec.
523
524 This includes only those specs that are not blocked by this spec
525 (directly or indirectly), unless they are already dependencies.
526
527 The current spec is not included.
528 """
529
530 implements(IHugeVocabulary)
531
532 _table = Specification
533 _orderBy = 'name'
534 displayname = 'Select a blueprint'
535
536 def _filter_specs(self, specs):
537 # XXX intellectronica 2007-07-05: is 100 a reasonable count before
538 # starting to warn?
539 speclist = shortlist(specs, 100)
540 return [spec for spec in speclist
541 if (spec != self.context and
542 spec.target == self.context.target
543 and spec not in self.context.all_blocked)]
544
545 def _doSearch(self, query):
546 """Return terms where query is in the text of name
547 or title, or matches the full text index.
548 """
549
550 if not query:
551 return []
552
553 quoted_query = quote_like(query)
554 sql_query = ("""
555 (Specification.name LIKE %s OR
556 Specification.title LIKE %s OR
557 fti @@ ftq(%s))
558 """
559 % (quoted_query, quoted_query, quoted_query))
560 all_specs = Specification.select(sql_query, orderBy=self._orderBy)
561
562 return self._filter_specs(all_specs)
563
564 def toTerm(self, obj):
565 return SimpleTerm(obj, obj.name, obj.title)
566
567 def getTermByToken(self, token):
568 search_results = self._doSearch(token)
569 for search_result in search_results:
570 if search_result.name == token:
571 return self.toTerm(search_result)
572 raise LookupError(token)
573
574 def search(self, query):
575 candidate_specs = self._doSearch(query)
576 return CountableIterator(len(candidate_specs),
577 candidate_specs)
578
579 def _all_specs(self):
580 all_specs = self.context.target.specifications(
581 filter=[SpecificationFilter.ALL],
582 prejoin_people=False)
583 return self._filter_specs(all_specs)
584
585 def __iter__(self):
586 return (self.toTerm(spec) for spec in self._all_specs())
587
588 def __contains__(self, obj):
589 # We don't use self._all_specs here, since it will call
590 # self._filter_specs(all_specs) which will cause all the specs
591 # to be loaded, whereas obj in all_specs will query a single object.
592 all_specs = self.context.target.specifications(
593 filter=[SpecificationFilter.ALL],
594 prejoin_people=False)
595 return obj in all_specs and len(self._filter_specs([obj])) > 0
596
597
598class SprintVocabulary(NamedSQLObjectVocabulary):518class SprintVocabulary(NamedSQLObjectVocabulary):
599 _table = Sprint519 _table = Sprint
600520
601521
=== modified file 'lib/lp/blueprints/configure.zcml'
--- lib/lp/blueprints/configure.zcml 2010-08-19 03:06:27 +0000
+++ lib/lp/blueprints/configure.zcml 2010-08-25 21:37:45 +0000
@@ -11,6 +11,7 @@
11 i18n_domain="launchpad">11 i18n_domain="launchpad">
1212
13 <include package=".browser"/>13 <include package=".browser"/>
14 <include package=".vocabularies"/>
1415
15 <publisher16 <publisher
16 name="blueprints"17 name="blueprints"
1718
=== added directory 'lib/lp/blueprints/vocabularies'
=== added file 'lib/lp/blueprints/vocabularies/__init__.py'
=== added file 'lib/lp/blueprints/vocabularies/configure.zcml'
--- lib/lp/blueprints/vocabularies/configure.zcml 1970-01-01 00:00:00 +0000
+++ lib/lp/blueprints/vocabularies/configure.zcml 2010-08-25 21:37:45 +0000
@@ -0,0 +1,19 @@
1<!-- Copyright 2010 Canonical Ltd. This software is licensed under the
2 GNU Affero General Public License version 3 (see the file LICENSE).
3-->
4
5<configure xmlns="http://namespaces.zope.org/zope">
6
7 <securedutility
8 name="SpecificationDepCandidates"
9 component=".specificationdependency.SpecificationDepCandidatesVocabulary"
10 provides="zope.schema.interfaces.IVocabularyFactory"
11 >
12 <allow interface="zope.schema.interfaces.IVocabularyFactory"/>
13 </securedutility>
14
15 <class class=".specificationdependency.SpecificationDepCandidatesVocabulary">
16 <allow interface="canonical.launchpad.webapp.vocabulary.IHugeVocabulary"/>
17 </class>
18
19</configure>
020
=== added file 'lib/lp/blueprints/vocabularies/specificationdependency.py'
--- lib/lp/blueprints/vocabularies/specificationdependency.py 1970-01-01 00:00:00 +0000
+++ lib/lp/blueprints/vocabularies/specificationdependency.py 2010-08-25 21:37:45 +0000
@@ -0,0 +1,108 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""The vocabularies relating to dependencies of specifications."""
5
6__metaclass__ = type
7__all__ = ['SpecificationDepCandidatesVocabulary']
8
9from zope.interface import implements
10from zope.schema.vocabulary import SimpleTerm
11
12from canonical.database.sqlbase import quote_like
13from canonical.launchpad.helpers import shortlist
14from canonical.launchpad.webapp.vocabulary import (
15 CountableIterator,
16 IHugeVocabulary,
17 SQLObjectVocabularyBase,
18 )
19
20from lp.blueprints.interfaces.specification import SpecificationFilter
21from lp.blueprints.model.specification import Specification
22
23
24class SpecificationDepCandidatesVocabulary(SQLObjectVocabularyBase):
25 """Specifications that could be dependencies of this spec.
26
27 This includes only those specs that are not blocked by this spec
28 (directly or indirectly), unless they are already dependencies.
29
30 The current spec is not included.
31 """
32
33 implements(IHugeVocabulary)
34
35 _table = Specification
36 _orderBy = 'name'
37 displayname = 'Select a blueprint'
38
39 def _filter_specs(self, specs):
40 """Filter `specs` to remove invalid candidates.
41
42 Invalid candidates are:
43
44 * The spec that we're adding a depdency to,
45 * Specs for a different target, and
46 * Specs that depend on this one.
47
48 Preventing the last category prevents loops in the dependency graph.
49 """
50 # XXX intellectronica 2007-07-05: is 100 a reasonable count before
51 # starting to warn?
52 speclist = shortlist(specs, 100)
53 return [spec for spec in speclist
54 if (spec != self.context and
55 spec.target == self.context.target
56 and spec not in self.context.all_blocked)]
57
58 def _doSearch(self, query):
59 """Return terms where query is in the text of name
60 or title, or matches the full text index.
61 """
62
63 if not query:
64 return []
65
66 quoted_query = quote_like(query)
67 sql_query = ("""
68 (Specification.name LIKE %s OR
69 Specification.title LIKE %s OR
70 fti @@ ftq(%s))
71 """
72 % (quoted_query, quoted_query, quoted_query))
73 all_specs = Specification.select(sql_query, orderBy=self._orderBy)
74
75 return self._filter_specs(all_specs)
76
77 def toTerm(self, obj):
78 return SimpleTerm(obj, obj.name, obj.title)
79
80 def getTermByToken(self, token):
81 search_results = self._doSearch(token)
82 for search_result in search_results:
83 if search_result.name == token:
84 return self.toTerm(search_result)
85 raise LookupError(token)
86
87 def search(self, query):
88 candidate_specs = self._doSearch(query)
89 return CountableIterator(len(candidate_specs),
90 candidate_specs)
91
92 def _all_specs(self):
93 all_specs = self.context.target.specifications(
94 filter=[SpecificationFilter.ALL],
95 prejoin_people=False)
96 return self._filter_specs(all_specs)
97
98 def __iter__(self):
99 return (self.toTerm(spec) for spec in self._all_specs())
100
101 def __contains__(self, obj):
102 # We don't use self._all_specs here, since it will call
103 # self._filter_specs(all_specs) which will cause all the specs
104 # to be loaded, whereas obj in all_specs will query a single object.
105 all_specs = self.context.target.specifications(
106 filter=[SpecificationFilter.ALL],
107 prejoin_people=False)
108 return obj in all_specs and len(self._filter_specs([obj])) > 0
0109
=== added directory 'lib/lp/blueprints/vocabularies/tests'
=== added file 'lib/lp/blueprints/vocabularies/tests/__init__.py'
=== added file 'lib/lp/blueprints/vocabularies/tests/specificationdepcandidates.txt'
--- lib/lp/blueprints/vocabularies/tests/specificationdepcandidates.txt 1970-01-01 00:00:00 +0000
+++ lib/lp/blueprints/vocabularies/tests/specificationdepcandidates.txt 2010-08-25 21:37:45 +0000
@@ -0,0 +1,99 @@
1SpecificationDepCandidatesVocabulary
2====================================
3
4All blueprints that can be added as a dependency of the context
5blueprint.
6
7 >>> from zope.schema.vocabulary import getVocabularyRegistry
8 >>> vocabulary_registry = getVocabularyRegistry()
9
10First, we set up a product with three blueprints.
11
12 >>> specced_product = factory.makeProduct()
13 >>> spec_a = factory.makeSpecification(
14 ... name='spec-a', summary='The first spec',
15 ... product=specced_product)
16 >>> spec_b = factory.makeSpecification(
17 ... name='spec-b', summary='The second spec',
18 ... product=specced_product)
19 >>> spec_c = factory.makeSpecification(
20 ... name='spec-c', summary='The third spec',
21 ... product=specced_product)
22 >>> sorted([spec.name for spec in specced_product.specifications()])
23 [u'spec-a', u'spec-b', u'spec-c']
24
25The dependency candidates for spec_a are all blueprints for
26specced_product except for spec_a itself.
27
28 >>> vocab = vocabulary_registry.get(
29 ... spec_a, "SpecificationDepCandidates")
30 >>> sorted([term.value.name for term in vocab])
31 [u'spec-b', u'spec-c']
32
33Dependency candidate come only from the same product of the blueprint
34they depend on.
35
36 >>> unrelated_spec = factory.makeSpecification(
37 ... product=factory.makeProduct())
38 >>> vocab = vocabulary_registry.get(
39 ... spec_a, "SpecificationDepCandidates")
40 >>> unrelated_spec in vocab
41 False
42 >>> [term.value.product for term in vocab
43 ... if term.value.product != specced_product]
44 []
45
46We mark spec_b as a dependency of spec_a and spec_c as a dependency of
47spec_b.
48
49 >>> spec_a.createDependency(spec_b)
50 <SpecificationDependency at ...>
51 >>> [spec.name for spec in spec_a.dependencies]
52 [u'spec-b']
53
54 >>> spec_b.createDependency(spec_c)
55 <SpecificationDependency at ...>
56 >>> [spec.name for spec in spec_b.dependencies]
57 [u'spec-c']
58
59No circular dependencies - the vocabulary excludes specifications that
60are a dependency of the context spec.
61
62 >>> spec_a in spec_b.all_blocked
63 True
64 >>> spec_b in spec_c.all_blocked
65 True
66 >>> vocab = vocabulary_registry.get(
67 ... spec_c, "SpecificationDepCandidates")
68 >>> spec_a in [term.value for term in vocab]
69 False
70
71This vocabulary provides the IHugeVocabulary interface.
72
73 >>> from canonical.launchpad.webapp.testing import verifyObject
74 >>> from canonical.launchpad.webapp.vocabulary import IHugeVocabulary
75 >>> verifyObject(IHugeVocabulary, vocab)
76 True
77
78The search() method returns specifications within the vocabulary that
79matches the search string. The string is matched against the name, or
80fallbacks to a full text search.
81
82 >>> from zope.security.proxy import removeSecurityProxy
83 >>> naked_vocab = removeSecurityProxy(
84 ... vocabulary_registry.get(
85 ... spec_a, "SpecificationDepCandidates"))
86 >>> list(naked_vocab.search('spec-b')) == [spec_b]
87 True
88 >>> list(naked_vocab.search('third')) == [spec_c]
89 True
90
91The search method uses the SQL `LIKE` operator, with the values quoted
92appropriately. Queries conataining regual expression operators, for
93example, will simply look for the respective characters within the
94vocabulary's item (this used to be the cause of an OOPS, see
95https://bugs.edge.launchpad.net/blueprint/+bug/139385 for more
96details).
97
98 >>> list(naked_vocab.search('*'))
99 []
0100
=== added file 'lib/lp/blueprints/vocabularies/tests/test_doc.py'
--- lib/lp/blueprints/vocabularies/tests/test_doc.py 1970-01-01 00:00:00 +0000
+++ lib/lp/blueprints/vocabularies/tests/test_doc.py 2010-08-25 21:37:45 +0000
@@ -0,0 +1,17 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""
5Run the doctests.
6"""
7
8import os
9
10from lp.services.testing import build_doctest_suite
11
12
13here = os.path.dirname(os.path.realpath(__file__))
14
15
16def test_suite():
17 return build_doctest_suite(here, '')
018
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2010-08-22 18:31:30 +0000
+++ lib/lp/testing/factory.py 2010-08-25 21:37:45 +0000
@@ -439,7 +439,6 @@
439 registry_team.addMember(user, registry_team.teamowner)439 registry_team.addMember(user, registry_team.teamowner)
440 return user440 return user
441441
442
443 def makeCopyArchiveLocation(self, distribution=None, owner=None,442 def makeCopyArchiveLocation(self, distribution=None, owner=None,
444 name=None, enabled=True):443 name=None, enabled=True):
445 """Create and return a new arbitrary location for copy packages."""444 """Create and return a new arbitrary location for copy packages."""
@@ -1495,7 +1494,9 @@
1495 mail.parsed_string = mail.as_string()1494 mail.parsed_string = mail.as_string()
1496 return mail1495 return mail
14971496
1498 def makeSpecification(self, product=None, title=None, distribution=None):1497 def makeSpecification(self, product=None, title=None, distribution=None,
1498 name=None, summary=None,
1499 status=SpecificationDefinitionStatus.NEW):
1499 """Create and return a new, arbitrary Blueprint.1500 """Create and return a new, arbitrary Blueprint.
15001501
1501 :param product: The product to make the blueprint on. If one is1502 :param product: The product to make the blueprint on. If one is
@@ -1503,14 +1504,18 @@
1503 """1504 """
1504 if distribution is None and product is None:1505 if distribution is None and product is None:
1505 product = self.makeProduct()1506 product = self.makeProduct()
1507 if name is None:
1508 name = self.getUniqueString('name')
1509 if summary is None:
1510 summary = self.getUniqueString('summary')
1506 if title is None:1511 if title is None:
1507 title = self.getUniqueString('title')1512 title = self.getUniqueString('title')
1508 return getUtility(ISpecificationSet).new(1513 return getUtility(ISpecificationSet).new(
1509 name=self.getUniqueString('name'),1514 name=name,
1510 title=title,1515 title=title,
1511 specurl=None,1516 specurl=None,
1512 summary=self.getUniqueString('summary'),1517 summary=summary,
1513 definition_status=SpecificationDefinitionStatus.NEW,1518 definition_status=status,
1514 owner=self.makePerson(),1519 owner=self.makePerson(),
1515 product=product,1520 product=product,
1516 distribution=distribution)1521 distribution=distribution)