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