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

Thanks for making the changes. (FTR: Discussed on IRC, see revision log). There were a few formalities missing with the code you moved, but no need to fix them all here.
One more nit: s/canidates/ candidates/ g
Jeroen