Merge lp:~mwhudson/launchpad/move-some-code-vocabularies into lp:launchpad

Proposed by Michael Hudson-Doyle
Status: Merged
Approved by: Robert Collins
Approved revision: no longer in the source branch.
Merged at revision: 11583
Proposed branch: lp:~mwhudson/launchpad/move-some-code-vocabularies
Merge into: lp:launchpad
Diff against target: 796 lines (+358/-305)
12 files modified
lib/canonical/launchpad/browser/tests/test_widgets.py (+1/-1)
lib/canonical/launchpad/doc/vocabularies.txt (+0/-137)
lib/canonical/launchpad/vocabularies/configure.zcml (+0/-39)
lib/canonical/launchpad/vocabularies/dbobjects.py (+2/-112)
lib/canonical/launchpad/webapp/configure.zcml (+1/-1)
lib/lp/code/browser/configure.zcml (+0/-14)
lib/lp/code/configure.zcml (+1/-0)
lib/lp/code/vocabularies/branch.py (+133/-0)
lib/lp/code/vocabularies/configure.zcml (+54/-0)
lib/lp/code/vocabularies/tests/branch.txt (+148/-0)
lib/lp/code/vocabularies/tests/test_branch_vocabularies.py (+1/-1)
lib/lp/code/vocabularies/tests/test_doc.py (+17/-0)
To merge this branch: bzr merge lp:~mwhudson/launchpad/move-some-code-vocabularies
Reviewer Review Type Date Requested Status
Robert Collins (community) Approve
Review via email: mp+35974@code.launchpad.net

Commit message

Move the remaining code-related vocabularies to the lp.code tree

Description of the change

Hi,

This lunch hour branch moves the remaining code-related vocabularies to the lp.code tree.

Cheers,
mwh

To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote :

+ """Override this to return the collection to which the search is
477 + restricted.
478

-> """Return the collection of branches the vocabulary searches.

Subclasses MUST override and implement this.
"""

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/browser/tests/test_widgets.py'
2--- lib/canonical/launchpad/browser/tests/test_widgets.py 2010-08-20 20:31:18 +0000
3+++ lib/canonical/launchpad/browser/tests/test_widgets.py 2010-09-20 22:41:19 +0000
4@@ -24,7 +24,7 @@
5 login,
6 logout,
7 )
8-from canonical.launchpad.vocabularies import (
9+from lp.code.vocabularies.branch import (
10 BranchRestrictedOnProductVocabulary,
11 BranchVocabulary,
12 )
13
14=== modified file 'lib/canonical/launchpad/doc/vocabularies.txt'
15--- lib/canonical/launchpad/doc/vocabularies.txt 2010-08-27 14:27:22 +0000
16+++ lib/canonical/launchpad/doc/vocabularies.txt 2010-09-20 22:41:19 +0000
17@@ -364,143 +364,6 @@
18 [('pmount', u'pmount')]
19
20
21-BranchVocabulary
22-................
23-
24-The list of bzr branches registered in Launchpad.
25-
26-Searchable by branch name or URL, registrant name, and project name.
27-Results are not restricted in any way by the context, but the results
28-are restricted based on who is asking (as far as private branches is
29-concerned).
30-
31- # Just use None as the context.
32- >>> branch_vocabulary = get_naked_vocab(None, "Branch")
33- >>> def print_vocab_branches(vocab, search):
34- ... for term in vocab.searchForTerms(search):
35- ... print term.value.unique_name
36-
37- >>> print_vocab_branches(branch_vocabulary, 'main')
38- ~name12/firefox/main
39- ~stevea/thunderbird/main
40- ~justdave/+junk/main
41- ~kiko/+junk/main
42- ~vcs-imports/evolution/main
43- ~name12/gnome-terminal/main
44-
45- >>> print_vocab_branches(branch_vocabulary, 'vcs-imports')
46- ~vcs-imports/gnome-terminal/import
47- ~vcs-imports/evolution/import
48- ~vcs-imports/evolution/main
49-
50- >>> print_vocab_branches(branch_vocabulary, 'evolution')
51- ~carlos/evolution/2.0
52- ~vcs-imports/evolution/import
53- ~vcs-imports/evolution/main
54-
55-A search with the full branch unique name should also find the branch.
56-
57- >>> print_vocab_branches(branch_vocabulary, '~name12/firefox/main')
58- ~name12/firefox/main
59-
60-The tokens used by terms retrieved from BranchVocabulary use the
61-branch unique name as an ID:
62-
63- >>> from lp.code.interfaces.branchlookup import IBranchLookup
64- >>> branch = getUtility(IBranchLookup).get(15)
65- >>> print branch.unique_name
66- ~name12/gnome-terminal/main
67- >>> term = branch_vocabulary.toTerm(branch)
68- >>> print term.token
69- ~name12/gnome-terminal/main
70-
71-The BranchVocabulary recognises both unique names and URLs as tokens:
72-
73- >>> term = branch_vocabulary.getTermByToken('~name12/gnome-terminal/main')
74- >>> term.value == branch
75- True
76- >>> term = branch_vocabulary.getTermByToken(
77- ... 'http://bazaar.launchpad.dev/~name12/gnome-terminal/main/')
78- >>> term.value == branch
79- True
80- >>> term = branch_vocabulary.getTermByToken(
81- ... 'http://example.com/gnome-terminal/main')
82- >>> term.value == branch
83- True
84-
85-The searches that the BranchVocabulary does are private branch aware.
86-The results are effectively filtered on what the logged in user is
87-able to see.
88-
89- >>> print_vocab_branches(branch_vocabulary, 'trunk')
90- ~spiv/+junk/trunk
91- ~limi/+junk/trunk
92- ~landscape-developers/landscape/trunk
93-
94- >>> login('no-priv@canonical.com')
95- >>> print_vocab_branches(branch_vocabulary, 'trunk')
96- ~spiv/+junk/trunk
97- ~limi/+junk/trunk
98-
99- >>> login('foo.bar@canonical.com')
100-
101-
102-BranchRestrictedOnProduct
103-.........................
104-
105-The BranchRestrictedOnProduct vocabulary restricts the result set to
106-those of the product of the context. Currently only two types of
107-context are supported: Product; and Branch. If a branch is the context,
108-then the product of the branch is used to restrict the query.
109-
110- >>> gnome_terminal = getUtility(IProductSet)["gnome-terminal"]
111- >>> branch_vocabulary = vocabulary_registry.get(
112- ... gnome_terminal, "BranchRestrictedOnProduct")
113- >>> print_vocab_branches(branch_vocabulary, 'main')
114- ~name12/gnome-terminal/main
115-
116- >>> print_vocab_branches(branch_vocabulary, 'vcs-imports')
117- ~vcs-imports/gnome-terminal/import
118-
119-If a full unique name is entered that has a different product, the
120-branch is not part of the vocabulary.
121-
122- >>> print_vocab_branches(branch_vocabulary, '~name12/gnome-terminal/main')
123- ~name12/gnome-terminal/main
124-
125- >>> print_vocab_branches(branch_vocabulary, '~name12/firefox/main')
126-
127-
128-The BranchRestrictedOnProduct behaves the same way as the more generic
129-BranchVocabulary with respect to the tokens and privacy awareness.
130-
131-
132-HostedBranchRestrictedOnOwner
133-.............................
134-
135-Here's a vocabulary for all hosted branches owned by the current user.
136-
137- >>> from lp.code.enums import BranchType
138-
139- >>> a_user = factory.makePerson(name='a-branching-user')
140- >>> product1 = factory.makeProduct(name='product-one')
141- >>> mirrored_branch = factory.makeBranch(
142- ... owner=a_user, product=product1, name='mirrored',
143- ... branch_type=BranchType.MIRRORED)
144- >>> product2 = factory.makeProduct(name='product-two')
145- >>> hosted_branch = factory.makeBranch(
146- ... owner=a_user, product=product2, name='hosted')
147- >>> foreign_branch = factory.makeBranch()
148-
149-It returns branches owned by the user, but not ones owned by others, nor
150-ones that aren't hosted on Launchpad.
151-
152- >>> branch_vocabulary = vocabulary_registry.get(
153- ... a_user, "HostedBranchRestrictedOnOwner")
154- >>> print_vocab_branches(branch_vocabulary, None)
155- ~a-branching-user/product-two/hosted
156-
157-
158 Processor
159 .........
160
161
162=== modified file 'lib/canonical/launchpad/vocabularies/configure.zcml'
163--- lib/canonical/launchpad/vocabularies/configure.zcml 2010-08-25 04:12:59 +0000
164+++ lib/canonical/launchpad/vocabularies/configure.zcml 2010-09-20 22:41:19 +0000
165@@ -13,45 +13,6 @@
166 </class>
167
168 <securedutility
169- name="Branch"
170- component="canonical.launchpad.vocabularies.BranchVocabulary"
171- provides="zope.schema.interfaces.IVocabularyFactory"
172- >
173- <allow interface="zope.schema.interfaces.IVocabularyFactory"/>
174- </securedutility>
175-
176- <class class="canonical.launchpad.vocabularies.BranchVocabulary">
177- <allow interface="canonical.launchpad.webapp.vocabulary.IHugeVocabulary"/>
178- </class>
179-
180-
181- <securedutility
182- name="HostedBranchRestrictedOnOwner"
183- component="canonical.launchpad.vocabularies.HostedBranchRestrictedOnOwnerVocabulary"
184- provides="zope.schema.interfaces.IVocabularyFactory"
185- >
186- <allow interface="zope.schema.interfaces.IVocabularyFactory"/>
187- </securedutility>
188-
189- <class class="canonical.launchpad.vocabularies.HostedBranchRestrictedOnOwnerVocabulary">
190- <allow interface="canonical.launchpad.webapp.vocabulary.IHugeVocabulary"/>
191- </class>
192-
193-
194- <securedutility
195- name="BranchRestrictedOnProduct"
196- component="canonical.launchpad.vocabularies.BranchRestrictedOnProductVocabulary"
197- provides="zope.schema.interfaces.IVocabularyFactory"
198- >
199- <allow interface="zope.schema.interfaces.IVocabularyFactory"/>
200- </securedutility>
201-
202- <class class="canonical.launchpad.vocabularies.BranchRestrictedOnProductVocabulary">
203- <allow interface="canonical.launchpad.webapp.vocabulary.IHugeVocabulary"/>
204- </class>
205-
206-
207- <securedutility
208 name="Bug"
209 component="canonical.launchpad.vocabularies.BugVocabulary"
210 provides="zope.schema.interfaces.IVocabularyFactory"
211
212=== modified file 'lib/canonical/launchpad/vocabularies/dbobjects.py'
213--- lib/canonical/launchpad/vocabularies/dbobjects.py 2010-08-27 14:27:22 +0000
214+++ lib/canonical/launchpad/vocabularies/dbobjects.py 2010-09-20 22:41:19 +0000
215@@ -1,5 +1,5 @@
216-# Copyright 2009 Canonical Ltd. This software is licensed under the
217-# GNU Affero General Public License version 3 (see the file LICENSE).
218+# Copyright 2009, 2010 Canonical Ltd. This software is licensed under the GNU
219+# Affero General Public License version 3 (see the file LICENSE).
220
221 """Vocabularies pulling stuff from the database.
222
223@@ -10,8 +10,6 @@
224 __metaclass__ = type
225
226 __all__ = [
227- 'BranchRestrictedOnProductVocabulary',
228- 'BranchVocabulary',
229 'BugNominatableDistroSeriesVocabulary',
230 'BugNominatableProductSeriesVocabulary',
231 'BugNominatableSeriesVocabulary',
232@@ -26,7 +24,6 @@
233 'FilteredFullLanguagePackVocabulary',
234 'FilteredLanguagePackVocabulary',
235 'FutureSprintVocabulary',
236- 'HostedBranchRestrictedOnOwnerVocabulary',
237 'LanguageVocabulary',
238 'PackageReleaseVocabulary',
239 'PPAVocabulary',
240@@ -92,15 +89,8 @@
241 from lp.bugs.interfaces.bugtracker import BugTrackerType
242 from lp.bugs.model.bug import Bug
243 from lp.bugs.model.bugtracker import BugTracker
244-from lp.code.enums import BranchType
245-from lp.code.interfaces.branch import IBranch
246-from lp.code.interfaces.branchcollection import IAllBranches
247-from lp.code.model.branch import Branch
248 from lp.registry.interfaces.distribution import IDistribution
249 from lp.registry.interfaces.distroseries import IDistroSeries
250-from lp.registry.interfaces.person import IPerson
251-from lp.registry.interfaces.product import IProduct
252-from lp.registry.interfaces.productseries import IProductSeries
253 from lp.registry.interfaces.projectgroup import IProjectGroup
254 from lp.registry.interfaces.series import SeriesStatus
255 from lp.registry.model.distribution import Distribution
256@@ -146,106 +136,6 @@
257 return SimpleTerm(obj, obj.id, obj.name)
258
259
260-class BranchVocabularyBase(SQLObjectVocabularyBase):
261- """A base class for Branch vocabularies.
262-
263- Override `BranchVocabularyBase._getCollection` to provide the collection
264- of branches which make up the vocabulary.
265- """
266-
267- implements(IHugeVocabulary)
268-
269- _table = Branch
270- _orderBy = ['name', 'id']
271- displayname = 'Select a branch'
272-
273- def toTerm(self, branch):
274- """The display should include the URL if there is one."""
275- return SimpleTerm(branch, branch.unique_name, branch.unique_name)
276-
277- def getTermByToken(self, token):
278- """See `IVocabularyTokenized`."""
279- search_results = self.searchForTerms(token)
280- if search_results.count() == 1:
281- return iter(search_results).next()
282- raise LookupError(token)
283-
284- def _getCollection(self):
285- """Override this to return the collection to which the search is
286- restricted.
287- """
288- raise NotImplementedError(self._getCollection)
289-
290- def searchForTerms(self, query=None):
291- """See `IHugeVocabulary`."""
292- logged_in_user = getUtility(ILaunchBag).user
293- collection = self._getCollection().visibleByUser(logged_in_user)
294- if query is None:
295- branches = collection.getBranches()
296- else:
297- branches = collection.search(query)
298- return CountableIterator(branches.count(), branches, self.toTerm)
299-
300- def __len__(self):
301- """See `IVocabulary`."""
302- return self.search().count()
303-
304-
305-class BranchVocabulary(BranchVocabularyBase):
306- """A vocabulary for searching branches.
307-
308- The name and URL of the branch, the name of the product, and the
309- name of the registrant of the branches is checked for the entered
310- value.
311- """
312-
313- def _getCollection(self):
314- return getUtility(IAllBranches)
315-
316-
317-class BranchRestrictedOnProductVocabulary(BranchVocabularyBase):
318- """A vocabulary for searching branches restricted on product.
319-
320- The query entered checks the name or URL of the branch, or the
321- name of the registrant of the branch.
322- """
323-
324- def __init__(self, context=None):
325- BranchVocabularyBase.__init__(self, context)
326- if IProduct.providedBy(self.context):
327- self.product = self.context
328- elif IProductSeries.providedBy(self.context):
329- self.product = self.context.product
330- elif IBranch.providedBy(self.context):
331- self.product = self.context.product
332- else:
333- # An unexpected type.
334- raise AssertionError('Unexpected context type')
335-
336- def _getCollection(self):
337- return getUtility(IAllBranches).inProduct(self.product)
338-
339-
340-class HostedBranchRestrictedOnOwnerVocabulary(BranchVocabularyBase):
341- """A vocabulary for hosted branches owned by the current user.
342-
343- These are branches that the user is guaranteed to be able to push
344- to.
345- """
346-
347- def __init__(self, context=None):
348- """Pass a Person as context, or anything else for the current user."""
349- super(HostedBranchRestrictedOnOwnerVocabulary, self).__init__(context)
350- if IPerson.providedBy(self.context):
351- self.user = context
352- else:
353- self.user = getUtility(ILaunchBag).user
354-
355- def _getCollection(self):
356- return getUtility(IAllBranches).ownedBy(self.user).withBranchType(
357- BranchType.HOSTED)
358-
359-
360 class BugVocabulary(SQLObjectVocabularyBase):
361
362 _table = Bug
363
364=== modified file 'lib/canonical/launchpad/webapp/configure.zcml'
365--- lib/canonical/launchpad/webapp/configure.zcml 2010-09-10 06:38:15 +0000
366+++ lib/canonical/launchpad/webapp/configure.zcml 2010-09-20 22:41:19 +0000
367@@ -828,7 +828,7 @@
368 <view
369 type="zope.publisher.interfaces.browser.IBrowserRequest"
370 for="zope.schema.interfaces.IChoice
371- canonical.launchpad.vocabularies.dbobjects.BranchVocabularyBase"
372+ lp.code.vocabularies.branch.BranchVocabularyBase"
373 provides="zope.app.form.interfaces.IInputWidget"
374 factory="canonical.launchpad.browser.widgets.BranchPopupWidget"
375 permission="zope.Public"
376
377=== modified file 'lib/lp/code/browser/configure.zcml'
378--- lib/lp/code/browser/configure.zcml 2010-08-24 02:17:19 +0000
379+++ lib/lp/code/browser/configure.zcml 2010-09-20 22:41:19 +0000
380@@ -1306,19 +1306,5 @@
381 factory="canonical.launchpad.webapp.breadcrumb.NameBreadcrumb"
382 permission="zope.Public"/>
383 </facet>
384- <securedutility
385- name="BuildableDistroSeries"
386- component="lp.code.vocabularies.sourcepackagerecipe.buildable_distroseries_vocabulary"
387- provides="zope.schema.interfaces.IVocabularyFactory"
388- >
389- <allow interface="zope.schema.interfaces.IVocabularyFactory"/>
390- </securedutility>
391- <securedutility
392- name="TargetPPAs"
393- component="lp.code.vocabularies.sourcepackagerecipe.target_ppas_vocabulary"
394- provides="zope.schema.interfaces.IVocabularyFactory"
395- >
396- <allow interface="zope.schema.interfaces.IVocabularyFactory"/>
397- </securedutility>
398
399 </configure>
400
401=== modified file 'lib/lp/code/configure.zcml'
402--- lib/lp/code/configure.zcml 2010-09-15 16:03:39 +0000
403+++ lib/lp/code/configure.zcml 2010-09-20 22:41:19 +0000
404@@ -11,6 +11,7 @@
405 xmlns:webservice="http://namespaces.canonical.com/webservice"
406 i18n_domain="launchpad">
407 <include package=".browser"/>
408+ <include package=".vocabularies"/>
409 <authorizations module="lp.code.security" />
410
411 <publisher
412
413=== added file 'lib/lp/code/vocabularies/branch.py'
414--- lib/lp/code/vocabularies/branch.py 1970-01-01 00:00:00 +0000
415+++ lib/lp/code/vocabularies/branch.py 2010-09-20 22:41:19 +0000
416@@ -0,0 +1,133 @@
417+# Copyright 2009 Canonical Ltd. This software is licensed under the
418+# GNU Affero General Public License version 3 (see the file LICENSE).
419+
420+"""Vocabularies that contain branches."""
421+
422+
423+__metaclass__ = type
424+
425+__all__ = [
426+ 'BranchRestrictedOnProductVocabulary',
427+ 'BranchVocabulary',
428+ 'HostedBranchRestrictedOnOwnerVocabulary',
429+ ]
430+
431+from zope.component import getUtility
432+from zope.interface import implements
433+from zope.schema.vocabulary import SimpleTerm
434+
435+from canonical.launchpad.webapp.interfaces import ILaunchBag
436+from canonical.launchpad.webapp.vocabulary import (
437+ CountableIterator,
438+ IHugeVocabulary,
439+ SQLObjectVocabularyBase,
440+ )
441+
442+from lp.code.enums import BranchType
443+from lp.code.interfaces.branch import IBranch
444+from lp.code.interfaces.branchcollection import IAllBranches
445+from lp.code.model.branch import Branch
446+from lp.registry.interfaces.person import IPerson
447+from lp.registry.interfaces.product import IProduct
448+from lp.registry.interfaces.productseries import IProductSeries
449+
450+
451+class BranchVocabularyBase(SQLObjectVocabularyBase):
452+ """A base class for Branch vocabularies.
453+
454+ Override `BranchVocabularyBase._getCollection` to provide the collection
455+ of branches which make up the vocabulary.
456+ """
457+
458+ implements(IHugeVocabulary)
459+
460+ _table = Branch
461+ _orderBy = ['name', 'id']
462+ displayname = 'Select a branch'
463+
464+ def toTerm(self, branch):
465+ """The display should include the URL if there is one."""
466+ return SimpleTerm(branch, branch.unique_name, branch.unique_name)
467+
468+ def getTermByToken(self, token):
469+ """See `IVocabularyTokenized`."""
470+ search_results = self.searchForTerms(token)
471+ if search_results.count() == 1:
472+ return iter(search_results).next()
473+ raise LookupError(token)
474+
475+ def _getCollection(self):
476+ """Return the collection of branches the vocabulary searches.
477+
478+ Subclasses MUST override and implement this.
479+ """
480+ raise NotImplementedError(self._getCollection)
481+
482+ def searchForTerms(self, query=None):
483+ """See `IHugeVocabulary`."""
484+ logged_in_user = getUtility(ILaunchBag).user
485+ collection = self._getCollection().visibleByUser(logged_in_user)
486+ if query is None:
487+ branches = collection.getBranches()
488+ else:
489+ branches = collection.search(query)
490+ return CountableIterator(branches.count(), branches, self.toTerm)
491+
492+ def __len__(self):
493+ """See `IVocabulary`."""
494+ return self.search().count()
495+
496+
497+class BranchVocabulary(BranchVocabularyBase):
498+ """A vocabulary for searching branches.
499+
500+ The name and URL of the branch, the name of the product, and the
501+ name of the registrant of the branches is checked for the entered
502+ value.
503+ """
504+
505+ def _getCollection(self):
506+ return getUtility(IAllBranches)
507+
508+
509+class BranchRestrictedOnProductVocabulary(BranchVocabularyBase):
510+ """A vocabulary for searching branches restricted on product.
511+
512+ The query entered checks the name or URL of the branch, or the
513+ name of the registrant of the branch.
514+ """
515+
516+ def __init__(self, context=None):
517+ BranchVocabularyBase.__init__(self, context)
518+ if IProduct.providedBy(self.context):
519+ self.product = self.context
520+ elif IProductSeries.providedBy(self.context):
521+ self.product = self.context.product
522+ elif IBranch.providedBy(self.context):
523+ self.product = self.context.product
524+ else:
525+ # An unexpected type.
526+ raise AssertionError('Unexpected context type')
527+
528+ def _getCollection(self):
529+ return getUtility(IAllBranches).inProduct(self.product)
530+
531+
532+class HostedBranchRestrictedOnOwnerVocabulary(BranchVocabularyBase):
533+ """A vocabulary for hosted branches owned by the current user.
534+
535+ These are branches that the user is guaranteed to be able to push
536+ to.
537+ """
538+
539+ def __init__(self, context=None):
540+ """Pass a Person as context, or anything else for the current user."""
541+ super(HostedBranchRestrictedOnOwnerVocabulary, self).__init__(context)
542+ if IPerson.providedBy(self.context):
543+ self.user = context
544+ else:
545+ self.user = getUtility(ILaunchBag).user
546+
547+ def _getCollection(self):
548+ return getUtility(IAllBranches).ownedBy(self.user).withBranchType(
549+ BranchType.HOSTED)
550
551=== added file 'lib/lp/code/vocabularies/configure.zcml'
552--- lib/lp/code/vocabularies/configure.zcml 1970-01-01 00:00:00 +0000
553+++ lib/lp/code/vocabularies/configure.zcml 2010-09-20 22:41:19 +0000
554@@ -0,0 +1,54 @@
555+<!-- Copyright 2010 Canonical Ltd. This software is licensed under the
556+ GNU Affero General Public License version 3 (see the file LICENSE).
557+-->
558+
559+<configure xmlns="http://namespaces.zope.org/zope">
560+
561+ <securedutility
562+ name="BuildableDistroSeries"
563+ component=".sourcepackagerecipe.buildable_distroseries_vocabulary"
564+ provides="zope.schema.interfaces.IVocabularyFactory">
565+ <allow interface="zope.schema.interfaces.IVocabularyFactory"/>
566+ </securedutility>
567+
568+ <securedutility
569+ name="TargetPPAs"
570+ component=".sourcepackagerecipe.target_ppas_vocabulary"
571+ provides="zope.schema.interfaces.IVocabularyFactory">
572+ <allow interface="zope.schema.interfaces.IVocabularyFactory"/>
573+ </securedutility>
574+
575+ <securedutility
576+ name="Branch"
577+ component=".branch.BranchVocabulary"
578+ provides="zope.schema.interfaces.IVocabularyFactory">
579+ <allow interface="zope.schema.interfaces.IVocabularyFactory"/>
580+ </securedutility>
581+
582+ <class class=".branch.BranchVocabulary">
583+ <allow interface="canonical.launchpad.webapp.vocabulary.IHugeVocabulary"/>
584+ </class>
585+
586+ <securedutility
587+ name="HostedBranchRestrictedOnOwner"
588+ component=".branch.HostedBranchRestrictedOnOwnerVocabulary"
589+ provides="zope.schema.interfaces.IVocabularyFactory">
590+ <allow interface="zope.schema.interfaces.IVocabularyFactory"/>
591+ </securedutility>
592+
593+ <class class=".branch.HostedBranchRestrictedOnOwnerVocabulary">
594+ <allow interface="canonical.launchpad.webapp.vocabulary.IHugeVocabulary"/>
595+ </class>
596+
597+ <securedutility
598+ name="BranchRestrictedOnProduct"
599+ component=".branch.BranchRestrictedOnProductVocabulary"
600+ provides="zope.schema.interfaces.IVocabularyFactory">
601+ <allow interface="zope.schema.interfaces.IVocabularyFactory"/>
602+ </securedutility>
603+
604+ <class class=".branch.BranchRestrictedOnProductVocabulary">
605+ <allow interface="canonical.launchpad.webapp.vocabulary.IHugeVocabulary"/>
606+ </class>
607+
608+</configure>
609
610=== added file 'lib/lp/code/vocabularies/tests/branch.txt'
611--- lib/lp/code/vocabularies/tests/branch.txt 1970-01-01 00:00:00 +0000
612+++ lib/lp/code/vocabularies/tests/branch.txt 2010-09-20 22:41:19 +0000
613@@ -0,0 +1,148 @@
614+Branch Vocabularies
615+===================
616+
617+Launchpad has a few vocabularies that contain branches filtered in
618+various ways.
619+
620+ >>> from zope.schema.vocabulary import getVocabularyRegistry
621+ >>> vocabulary_registry = getVocabularyRegistry()
622+
623+BranchVocabulary
624+----------------
625+
626+The list of bzr branches registered in Launchpad.
627+
628+Searchable by branch name or URL, registrant name, and project name.
629+Results are not restricted in any way by the context, but the results
630+are restricted based on who is asking (as far as private branches is
631+concerned).
632+
633+ # Just use None as the context.
634+ >>> branch_vocabulary = vocabulary_registry.get(None, "Branch")
635+ >>> def print_vocab_branches(vocab, search):
636+ ... for term in vocab.searchForTerms(search):
637+ ... print term.value.unique_name
638+
639+ >>> print_vocab_branches(branch_vocabulary, 'main')
640+ ~name12/firefox/main
641+ ~stevea/thunderbird/main
642+ ~justdave/+junk/main
643+ ~kiko/+junk/main
644+ ~vcs-imports/evolution/main
645+ ~name12/gnome-terminal/main
646+
647+ >>> print_vocab_branches(branch_vocabulary, 'vcs-imports')
648+ ~vcs-imports/gnome-terminal/import
649+ ~vcs-imports/evolution/import
650+ ~vcs-imports/evolution/main
651+
652+ >>> print_vocab_branches(branch_vocabulary, 'evolution')
653+ ~carlos/evolution/2.0
654+ ~vcs-imports/evolution/import
655+ ~vcs-imports/evolution/main
656+
657+A search with the full branch unique name should also find the branch.
658+
659+ >>> print_vocab_branches(branch_vocabulary, '~name12/firefox/main')
660+ ~name12/firefox/main
661+
662+The tokens used by terms retrieved from BranchVocabulary use the
663+branch unique name as an ID:
664+
665+ >>> from lp.code.interfaces.branchlookup import IBranchLookup
666+ >>> branch = getUtility(IBranchLookup).get(15)
667+ >>> print branch.unique_name
668+ ~name12/gnome-terminal/main
669+ >>> from zope.security.proxy import removeSecurityProxy
670+ >>> term = removeSecurityProxy(branch_vocabulary).toTerm(branch)
671+ >>> print term.token
672+ ~name12/gnome-terminal/main
673+
674+The BranchVocabulary recognises both unique names and URLs as tokens:
675+
676+ >>> term = branch_vocabulary.getTermByToken('~name12/gnome-terminal/main')
677+ >>> term.value == branch
678+ True
679+ >>> term = branch_vocabulary.getTermByToken(
680+ ... 'http://bazaar.launchpad.dev/~name12/gnome-terminal/main/')
681+ >>> term.value == branch
682+ True
683+ >>> term = branch_vocabulary.getTermByToken(
684+ ... 'http://example.com/gnome-terminal/main')
685+ >>> term.value == branch
686+ True
687+
688+The searches that the BranchVocabulary does are private branch aware.
689+The results are effectively filtered on what the logged in user is
690+able to see.
691+
692+ >>> from lp.testing import login, ANONYMOUS
693+ >>> from lp.testing.sampledata import ADMIN_EMAIL
694+
695+ >>> login(ADMIN_EMAIL)
696+ >>> print_vocab_branches(branch_vocabulary, 'trunk')
697+ ~spiv/+junk/trunk
698+ ~limi/+junk/trunk
699+ ~landscape-developers/landscape/trunk
700+
701+ >>> login(ANONYMOUS)
702+ >>> print_vocab_branches(branch_vocabulary, 'trunk')
703+ ~spiv/+junk/trunk
704+ ~limi/+junk/trunk
705+
706+
707+BranchRestrictedOnProduct
708+-------------------------
709+
710+The BranchRestrictedOnProduct vocabulary restricts the result set to
711+those of the product of the context. Currently only two types of
712+context are supported: Product; and Branch. If a branch is the context,
713+then the product of the branch is used to restrict the query.
714+
715+ >>> from lp.registry.interfaces.product import IProductSet
716+ >>> gnome_terminal = getUtility(IProductSet)["gnome-terminal"]
717+ >>> branch_vocabulary = vocabulary_registry.get(
718+ ... gnome_terminal, "BranchRestrictedOnProduct")
719+ >>> print_vocab_branches(branch_vocabulary, 'main')
720+ ~name12/gnome-terminal/main
721+
722+ >>> print_vocab_branches(branch_vocabulary, 'vcs-imports')
723+ ~vcs-imports/gnome-terminal/import
724+
725+If a full unique name is entered that has a different product, the
726+branch is not part of the vocabulary.
727+
728+ >>> print_vocab_branches(branch_vocabulary, '~name12/gnome-terminal/main')
729+ ~name12/gnome-terminal/main
730+
731+ >>> print_vocab_branches(branch_vocabulary, '~name12/firefox/main')
732+
733+
734+The BranchRestrictedOnProduct behaves the same way as the more generic
735+BranchVocabulary with respect to the tokens and privacy awareness.
736+
737+
738+HostedBranchRestrictedOnOwner
739+-----------------------------
740+
741+Here's a vocabulary for all hosted branches owned by the current user.
742+
743+ >>> from lp.code.enums import BranchType
744+
745+ >>> a_user = factory.makePerson(name='a-branching-user')
746+ >>> product1 = factory.makeProduct(name='product-one')
747+ >>> mirrored_branch = factory.makeBranch(
748+ ... owner=a_user, product=product1, name='mirrored',
749+ ... branch_type=BranchType.MIRRORED)
750+ >>> product2 = factory.makeProduct(name='product-two')
751+ >>> hosted_branch = factory.makeBranch(
752+ ... owner=a_user, product=product2, name='hosted')
753+ >>> foreign_branch = factory.makeBranch()
754+
755+It returns branches owned by the user, but not ones owned by others, nor
756+ones that aren't hosted on Launchpad.
757+
758+ >>> branch_vocabulary = vocabulary_registry.get(
759+ ... a_user, "HostedBranchRestrictedOnOwner")
760+ >>> print_vocab_branches(branch_vocabulary, None)
761+ ~a-branching-user/product-two/hosted
762
763=== modified file 'lib/lp/code/vocabularies/tests/test_branch_vocabularies.py'
764--- lib/lp/code/vocabularies/tests/test_branch_vocabularies.py 2010-08-20 20:31:18 +0000
765+++ lib/lp/code/vocabularies/tests/test_branch_vocabularies.py 2010-09-20 22:41:19 +0000
766@@ -17,7 +17,7 @@
767 login,
768 logout,
769 )
770-from canonical.launchpad.vocabularies.dbobjects import (
771+from lp.code.vocabularies.branch import (
772 BranchRestrictedOnProductVocabulary,
773 BranchVocabulary,
774 )
775
776=== added file 'lib/lp/code/vocabularies/tests/test_doc.py'
777--- lib/lp/code/vocabularies/tests/test_doc.py 1970-01-01 00:00:00 +0000
778+++ lib/lp/code/vocabularies/tests/test_doc.py 2010-09-20 22:41:19 +0000
779@@ -0,0 +1,17 @@
780+# Copyright 2010 Canonical Ltd. This software is licensed under the
781+# GNU Affero General Public License version 3 (see the file LICENSE).
782+
783+"""
784+Run the doctests.
785+"""
786+
787+import os
788+
789+from lp.services.testing import build_doctest_suite
790+
791+
792+here = os.path.dirname(os.path.realpath(__file__))
793+
794+
795+def test_suite():
796+ return build_doctest_suite(here, '')