Merge lp:~stevenk/launchpad/new-branch-search into lp:launchpad
- new-branch-search
- Merge into devel
Status: | Merged |
---|---|
Approved by: | William Grant |
Approved revision: | no longer in the source branch. |
Merged at revision: | 16492 |
Proposed branch: | lp:~stevenk/launchpad/new-branch-search |
Merge into: | lp:launchpad |
Diff against target: |
901 lines (+133/-386) 11 files modified
lib/lp/code/feed/branch.py (+5/-8) lib/lp/code/interfaces/branchcollection.py (+6/-12) lib/lp/code/model/branch.py (+4/-0) lib/lp/code/model/branchcollection.py (+39/-87) lib/lp/code/model/tests/test_branchcollection.py (+26/-96) lib/lp/code/stories/feeds/xx-branch-atom.txt (+5/-4) lib/lp/code/vocabularies/branch.py (+10/-35) lib/lp/code/vocabularies/tests/branch.txt (+0/-13) lib/lp/code/vocabularies/tests/test_branch_vocabularies.py (+36/-127) lib/lp/registry/model/product.py (+0/-2) lib/lp/services/webapp/configure.zcml (+2/-2) |
To merge this branch: | bzr merge lp:~stevenk/launchpad/new-branch-search |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
William Grant | code | Approve | |
Review via email: mp+147840@code.launchpad.net |
Commit message
Rewrite branch search to not look through Product, Person and SourcePackageName, but only match against name or unique_name. Destroy (unused) IBranchCollecti
Description of the change
Destroy the current branch search paradigm. Unfortunately, now I can't finish this description since I've killed myself for using the word 'paradigm'.
IBranchCollecti
IBranchCollecti
I have refactored the branch vocabularies to no longer rely on a useless base
class, and cleaned up some whitespace and such.
Preview Diff
1 | === modified file 'lib/lp/code/feed/branch.py' | |||
2 | --- lib/lp/code/feed/branch.py 2011-12-30 07:32:58 +0000 | |||
3 | +++ lib/lp/code/feed/branch.py 2013-02-14 03:56:21 +0000 | |||
4 | @@ -31,6 +31,7 @@ | |||
5 | 31 | ) | 31 | ) |
6 | 32 | from lp.code.interfaces.branchcollection import IAllBranches | 32 | from lp.code.interfaces.branchcollection import IAllBranches |
7 | 33 | from lp.code.interfaces.revisioncache import IRevisionCache | 33 | from lp.code.interfaces.revisioncache import IRevisionCache |
8 | 34 | from lp.code.model.branch import Branch | ||
9 | 34 | from lp.registry.interfaces.person import IPerson | 35 | from lp.registry.interfaces.person import IPerson |
10 | 35 | from lp.registry.interfaces.product import IProduct | 36 | from lp.registry.interfaces.product import IProduct |
11 | 36 | from lp.registry.interfaces.projectgroup import IProjectGroup | 37 | from lp.registry.interfaces.projectgroup import IProjectGroup |
12 | @@ -162,17 +163,13 @@ | |||
13 | 162 | 163 | ||
14 | 163 | Only `self.quantity` revisions are returned. | 164 | Only `self.quantity` revisions are returned. |
15 | 164 | """ | 165 | """ |
16 | 165 | from lp.code.model.branch import Branch | ||
17 | 166 | collection = self._getCollection().visibleByUser( | 166 | collection = self._getCollection().visibleByUser( |
18 | 167 | None).withLifecycleStatus(*DEFAULT_BRANCH_STATUS_IN_LISTING) | 167 | None).withLifecycleStatus(*DEFAULT_BRANCH_STATUS_IN_LISTING) |
19 | 168 | branches = collection.getBranches(eager_load=False) | 168 | branches = collection.getBranches(eager_load=False) |
27 | 169 | branches.order_by( | 169 | return list(branches.order_by( |
28 | 170 | Desc(Branch.date_last_modified), | 170 | Desc(Branch.date_last_modified), Asc(Branch.target_suffix), |
29 | 171 | Asc(Branch.target_suffix), | 171 | Desc(Branch.lifecycle_status), Asc(Branch.name)).config( |
30 | 172 | Desc(Branch.lifecycle_status), | 172 | limit=self.quantity)) |
24 | 173 | Asc(Branch.name)) | ||
25 | 174 | branches.config(limit=self.quantity) | ||
26 | 175 | return list(branches) | ||
31 | 176 | 173 | ||
32 | 177 | 174 | ||
33 | 178 | class ProductBranchFeed(BranchListingFeed): | 175 | class ProductBranchFeed(BranchListingFeed): |
34 | 179 | 176 | ||
35 | === modified file 'lib/lp/code/interfaces/branchcollection.py' | |||
36 | --- lib/lp/code/interfaces/branchcollection.py 2013-01-07 02:40:55 +0000 | |||
37 | +++ lib/lp/code/interfaces/branchcollection.py 2013-02-14 03:56:21 +0000 | |||
38 | @@ -1,4 +1,4 @@ | |||
40 | 1 | # Copyright 2009-2011 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2013 Canonical Ltd. This software is licensed under the |
41 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
42 | 3 | 3 | ||
43 | 4 | """A collection of branches. | 4 | """A collection of branches. |
44 | @@ -178,17 +178,11 @@ | |||
45 | 178 | def registeredBy(person): | 178 | def registeredBy(person): |
46 | 179 | """Restrict the collection to branches registered by 'person'.""" | 179 | """Restrict the collection to branches registered by 'person'.""" |
47 | 180 | 180 | ||
59 | 181 | def relatedTo(person): | 181 | def search(term): |
60 | 182 | """Restrict the collection to branches related to 'person'. | 182 | """Search the collection for branches matching 'term'. |
61 | 183 | 183 | ||
62 | 184 | That is, branches that 'person' owns, registered or is subscribed to. | 184 | :param term: A string. |
63 | 185 | """ | 185 | :return: A `ResultSet` of branches that matched. |
53 | 186 | |||
54 | 187 | def search(search_term): | ||
55 | 188 | """Search the collection for branches matching 'search_term'. | ||
56 | 189 | |||
57 | 190 | :param search_term: A string. | ||
58 | 191 | :return: An `ICountableIterator`. | ||
64 | 192 | """ | 186 | """ |
65 | 193 | 187 | ||
66 | 194 | def scanned(): | 188 | def scanned(): |
67 | 195 | 189 | ||
68 | === modified file 'lib/lp/code/model/branch.py' | |||
69 | --- lib/lp/code/model/branch.py 2013-01-16 06:41:43 +0000 | |||
70 | +++ lib/lp/code/model/branch.py 2013-02-14 03:56:21 +0000 | |||
71 | @@ -147,6 +147,7 @@ | |||
72 | 147 | validate_person, | 147 | validate_person, |
73 | 148 | validate_public_person, | 148 | validate_public_person, |
74 | 149 | ) | 149 | ) |
75 | 150 | from lp.registry.interfaces.role import IPersonRoles | ||
76 | 150 | from lp.registry.interfaces.sharingjob import ( | 151 | from lp.registry.interfaces.sharingjob import ( |
77 | 151 | IRemoveArtifactSubscriptionsJobSource, | 152 | IRemoveArtifactSubscriptionsJobSource, |
78 | 152 | ) | 153 | ) |
79 | @@ -1630,6 +1631,9 @@ | |||
80 | 1630 | if user is None: | 1631 | if user is None: |
81 | 1631 | return [public_branch_filter] | 1632 | return [public_branch_filter] |
82 | 1632 | 1633 | ||
83 | 1634 | if IPersonRoles(user).in_admin: | ||
84 | 1635 | return [] | ||
85 | 1636 | |||
86 | 1633 | artifact_grant_query = Coalesce( | 1637 | artifact_grant_query = Coalesce( |
87 | 1634 | ArrayIntersects( | 1638 | ArrayIntersects( |
88 | 1635 | SQL('%s.access_grants' % branch_class.__storm_table__), | 1639 | SQL('%s.access_grants' % branch_class.__storm_table__), |
89 | 1636 | 1640 | ||
90 | === modified file 'lib/lp/code/model/branchcollection.py' | |||
91 | --- lib/lp/code/model/branchcollection.py 2012-10-18 19:06:48 +0000 | |||
92 | +++ lib/lp/code/model/branchcollection.py 2013-02-14 03:56:21 +0000 | |||
93 | @@ -1,4 +1,4 @@ | |||
95 | 1 | # Copyright 2009-2012 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2013 Canonical Ltd. This software is licensed under the |
96 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
97 | 3 | 3 | ||
98 | 4 | """Implementations of `IBranchCollection`.""" | 4 | """Implementations of `IBranchCollection`.""" |
99 | @@ -13,6 +13,10 @@ | |||
100 | 13 | from operator import attrgetter | 13 | from operator import attrgetter |
101 | 14 | 14 | ||
102 | 15 | from lazr.restful.utils import safe_hasattr | 15 | from lazr.restful.utils import safe_hasattr |
103 | 16 | from lazr.uri import ( | ||
104 | 17 | InvalidURIError, | ||
105 | 18 | URI, | ||
106 | 19 | ) | ||
107 | 16 | from storm.expr import ( | 20 | from storm.expr import ( |
108 | 17 | And, | 21 | And, |
109 | 18 | Count, | 22 | Count, |
110 | @@ -20,10 +24,8 @@ | |||
111 | 20 | In, | 24 | In, |
112 | 21 | Join, | 25 | Join, |
113 | 22 | LeftJoin, | 26 | LeftJoin, |
114 | 23 | Or, | ||
115 | 24 | Select, | 27 | Select, |
116 | 25 | SQL, | 28 | SQL, |
117 | 26 | Union, | ||
118 | 27 | With, | 29 | With, |
119 | 28 | ) | 30 | ) |
120 | 29 | from storm.info import ClassAlias | 31 | from storm.info import ClassAlias |
121 | @@ -31,10 +33,7 @@ | |||
122 | 31 | from zope.component import getUtility | 33 | from zope.component import getUtility |
123 | 32 | from zope.interface import implements | 34 | from zope.interface import implements |
124 | 33 | 35 | ||
129 | 34 | from lp.app.enums import ( | 36 | from lp.app.enums import PRIVATE_INFORMATION_TYPES |
126 | 35 | PRIVATE_INFORMATION_TYPES, | ||
127 | 36 | PUBLIC_INFORMATION_TYPES, | ||
128 | 37 | ) | ||
130 | 38 | from lp.bugs.interfaces.bugtask import IBugTaskSet | 37 | from lp.bugs.interfaces.bugtask import IBugTaskSet |
131 | 39 | from lp.bugs.interfaces.bugtaskfilter import filter_bugtasks_by_context | 38 | from lp.bugs.interfaces.bugtaskfilter import filter_bugtasks_by_context |
132 | 40 | from lp.bugs.interfaces.bugtasksearch import BugTaskSearchParams | 39 | from lp.bugs.interfaces.bugtasksearch import BugTaskSearchParams |
133 | @@ -66,12 +65,8 @@ | |||
134 | 66 | from lp.registry.enums import EXCLUSIVE_TEAM_POLICY | 65 | from lp.registry.enums import EXCLUSIVE_TEAM_POLICY |
135 | 67 | from lp.registry.model.distribution import Distribution | 66 | from lp.registry.model.distribution import Distribution |
136 | 68 | from lp.registry.model.distroseries import DistroSeries | 67 | from lp.registry.model.distroseries import DistroSeries |
141 | 69 | from lp.registry.model.person import ( | 68 | from lp.registry.model.person import Person |
138 | 70 | Owner, | ||
139 | 71 | Person, | ||
140 | 72 | ) | ||
142 | 73 | from lp.registry.model.product import Product | 69 | from lp.registry.model.product import Product |
143 | 74 | from lp.registry.model.sourcepackagename import SourcePackageName | ||
144 | 75 | from lp.registry.model.teammembership import TeamParticipation | 70 | from lp.registry.model.teammembership import TeamParticipation |
145 | 76 | from lp.services.database.bulk import ( | 71 | from lp.services.database.bulk import ( |
146 | 77 | load_referencing, | 72 | load_referencing, |
147 | @@ -87,7 +82,6 @@ | |||
148 | 87 | from lp.services.database.sqlbase import quote | 82 | from lp.services.database.sqlbase import quote |
149 | 88 | from lp.services.propertycache import get_property_cache | 83 | from lp.services.propertycache import get_property_cache |
150 | 89 | from lp.services.searchbuilder import any | 84 | from lp.services.searchbuilder import any |
151 | 90 | from lp.services.webapp.vocabulary import CountableIterator | ||
152 | 91 | 85 | ||
153 | 92 | 86 | ||
154 | 93 | class GenericBranchCollection: | 87 | class GenericBranchCollection: |
155 | @@ -647,71 +641,35 @@ | |||
156 | 647 | """See `IBranchCollection`.""" | 641 | """See `IBranchCollection`.""" |
157 | 648 | return self._filterBy([Branch.registrant == person], symmetric=False) | 642 | return self._filterBy([Branch.registrant == person], symmetric=False) |
158 | 649 | 643 | ||
222 | 650 | def relatedTo(self, person): | 644 | def _getExactMatch(self, term): |
223 | 651 | """See `IBranchCollection`.""" | 645 | # Try and look up the branch by its URL, which handles lp: shortfom. |
224 | 652 | return self._filterBy( | 646 | branch_url = getUtility(IBranchLookup).getByUrl(term) |
225 | 653 | [Branch.id.is_in( | 647 | if branch_url: |
226 | 654 | Union( | 648 | return branch_url |
227 | 655 | Select(Branch.id, Branch.owner == person), | 649 | # Fall back to searching by unique_name, stripping out the path if it's |
228 | 656 | Select(Branch.id, Branch.registrant == person), | 650 | # a URI. |
229 | 657 | Select(Branch.id, | 651 | try: |
230 | 658 | And(BranchSubscription.person == person, | 652 | path = URI(term).path.strip('/') |
231 | 659 | BranchSubscription.branch == Branch.id))))], | 653 | except InvalidURIError: |
232 | 660 | symmetric=False) | 654 | path = term |
233 | 661 | 655 | return getUtility(IBranchLookup).getByUniqueName(path) | |
234 | 662 | def _getExactMatch(self, search_term): | 656 | |
235 | 663 | """Return the exact branch that 'search_term' matches, or None.""" | 657 | def search(self, term): |
236 | 664 | search_term = search_term.rstrip('/') | 658 | """See `IBranchCollection`.""" |
237 | 665 | branch_set = getUtility(IBranchLookup) | 659 | branch = self._getExactMatch(term) |
238 | 666 | branch = branch_set.getByUniqueName(search_term) | 660 | if branch: |
239 | 667 | if branch is None: | 661 | collection = self._filterBy([Branch.id == branch.id]) |
240 | 668 | branch = branch_set.getByUrl(search_term) | 662 | else: |
241 | 669 | return branch | 663 | term = unicode(term) |
242 | 670 | 664 | # Filter by name. | |
243 | 671 | def search(self, search_term): | 665 | field = Branch.name |
244 | 672 | """See `IBranchCollection`.""" | 666 | # Except if the term contains /, when we use unique_name. |
245 | 673 | # XXX: JonathanLange 2009-02-23 bug 372591: This matches the old | 667 | if '/' in term: |
246 | 674 | # search algorithm that used to live in vocabularies/dbojects.py. It's | 668 | field = Branch.unique_name |
247 | 675 | # not actually very good -- really it should match based on substrings | 669 | collection = self._filterBy( |
248 | 676 | # of the unique name and sort based on relevance. | 670 | [field.lower().contains_string(term.lower())]) |
249 | 677 | branch = self._getExactMatch(search_term) | 671 | return collection.getBranches(eager_load=False).order_by( |
187 | 678 | if branch is not None: | ||
188 | 679 | if branch in self.getBranches(eager_load=False): | ||
189 | 680 | return CountableIterator(1, [branch]) | ||
190 | 681 | else: | ||
191 | 682 | return CountableIterator(0, []) | ||
192 | 683 | like_term = '%' + search_term + '%' | ||
193 | 684 | # Match the Branch name or the URL. | ||
194 | 685 | queries = [Select(Branch.id, | ||
195 | 686 | Or(Branch.name.like(like_term), | ||
196 | 687 | Branch.url == search_term))] | ||
197 | 688 | # Match the product name. | ||
198 | 689 | if 'product' not in self._exclude_from_search: | ||
199 | 690 | queries.append(Select( | ||
200 | 691 | Branch.id, | ||
201 | 692 | And(Branch.product == Product.id, | ||
202 | 693 | Product.name.like(like_term)))) | ||
203 | 694 | |||
204 | 695 | # Match the owner name. | ||
205 | 696 | queries.append(Select( | ||
206 | 697 | Branch.id, | ||
207 | 698 | And(Branch.owner == Owner.id, Owner.name.like(like_term)))) | ||
208 | 699 | |||
209 | 700 | # Match the package bits. | ||
210 | 701 | queries.append( | ||
211 | 702 | Select(Branch.id, | ||
212 | 703 | And(Branch.sourcepackagename == SourcePackageName.id, | ||
213 | 704 | Branch.distroseries == DistroSeries.id, | ||
214 | 705 | DistroSeries.distribution == Distribution.id, | ||
215 | 706 | Or(SourcePackageName.name.like(like_term), | ||
216 | 707 | DistroSeries.name.like(like_term), | ||
217 | 708 | Distribution.name.like(like_term))))) | ||
218 | 709 | |||
219 | 710 | # Get the results. | ||
220 | 711 | collection = self._filterBy([Branch.id.is_in(Union(*queries))]) | ||
221 | 712 | results = collection.getBranches(eager_load=False).order_by( | ||
250 | 713 | Branch.name, Branch.id) | 672 | Branch.name, Branch.id) |
251 | 714 | return CountableIterator(results.count(), results) | ||
252 | 715 | 673 | ||
253 | 716 | def scanned(self): | 674 | def scanned(self): |
254 | 717 | """See `IBranchCollection`.""" | 675 | """See `IBranchCollection`.""" |
255 | @@ -790,8 +748,7 @@ | |||
256 | 790 | 748 | ||
257 | 791 | def _getBranchVisibilityExpression(self, branch_class=Branch): | 749 | def _getBranchVisibilityExpression(self, branch_class=Branch): |
258 | 792 | """Return the where clauses for visibility.""" | 750 | """Return the where clauses for visibility.""" |
261 | 793 | return [ | 751 | return get_branch_privacy_filter(None, branch_class=branch_class) |
260 | 794 | branch_class.information_type.is_in(PUBLIC_INFORMATION_TYPES)] | ||
262 | 795 | 752 | ||
263 | 796 | 753 | ||
264 | 797 | class VisibleBranchCollection(GenericBranchCollection): | 754 | class VisibleBranchCollection(GenericBranchCollection): |
265 | @@ -839,13 +796,9 @@ | |||
266 | 839 | if exclude_from_search is None: | 796 | if exclude_from_search is None: |
267 | 840 | exclude_from_search = [] | 797 | exclude_from_search = [] |
268 | 841 | return self.__class__( | 798 | return self.__class__( |
273 | 842 | self._user, | 799 | self._user, self.store, symmetric_expr, tables, |
270 | 843 | self.store, | ||
271 | 844 | symmetric_expr, | ||
272 | 845 | tables, | ||
274 | 846 | self._exclude_from_search + exclude_from_search, | 800 | self._exclude_from_search + exclude_from_search, |
277 | 847 | asymmetric_expr, | 801 | asymmetric_expr, asymmetric_tables) |
276 | 848 | asymmetric_tables) | ||
278 | 849 | 802 | ||
279 | 850 | def _getBranchVisibilityExpression(self, branch_class=Branch): | 803 | def _getBranchVisibilityExpression(self, branch_class=Branch): |
280 | 851 | """Return the where clauses for visibility. | 804 | """Return the where clauses for visibility. |
281 | @@ -853,8 +806,7 @@ | |||
282 | 853 | :param branch_class: The Branch class to use - permits using | 806 | :param branch_class: The Branch class to use - permits using |
283 | 854 | ClassAliases. | 807 | ClassAliases. |
284 | 855 | """ | 808 | """ |
287 | 856 | return get_branch_privacy_filter( | 809 | return get_branch_privacy_filter(self._user, branch_class=branch_class) |
286 | 857 | self._user, branch_class=branch_class) | ||
288 | 858 | 810 | ||
289 | 859 | def visibleByUser(self, person): | 811 | def visibleByUser(self, person): |
290 | 860 | """See `IBranchCollection`.""" | 812 | """See `IBranchCollection`.""" |
291 | 861 | 813 | ||
292 | === modified file 'lib/lp/code/model/tests/test_branchcollection.py' | |||
293 | --- lib/lp/code/model/tests/test_branchcollection.py 2012-10-18 19:20:49 +0000 | |||
294 | +++ lib/lp/code/model/tests/test_branchcollection.py 2013-02-14 03:56:21 +0000 | |||
295 | @@ -1,4 +1,4 @@ | |||
297 | 1 | # Copyright 2009-2012 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2013 Canonical Ltd. This software is licensed under the |
298 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
299 | 3 | 3 | ||
300 | 4 | """Tests for branch collections.""" | 4 | """Tests for branch collections.""" |
301 | @@ -41,6 +41,7 @@ | |||
302 | 41 | IStoreSelector, | 41 | IStoreSelector, |
303 | 42 | MAIN_STORE, | 42 | MAIN_STORE, |
304 | 43 | ) | 43 | ) |
305 | 44 | from lp.services.webapp.publisher import canonical_url | ||
306 | 44 | from lp.testing import ( | 45 | from lp.testing import ( |
307 | 45 | person_logged_in, | 46 | person_logged_in, |
308 | 46 | run_with_login, | 47 | run_with_login, |
309 | @@ -560,34 +561,6 @@ | |||
310 | 560 | collection = self.all_branches.subscribedBy(subscriber) | 561 | collection = self.all_branches.subscribedBy(subscriber) |
311 | 561 | self.assertEqual([branch], list(collection.getBranches())) | 562 | self.assertEqual([branch], list(collection.getBranches())) |
312 | 562 | 563 | ||
313 | 563 | def test_relatedTo(self): | ||
314 | 564 | # 'relatedTo' returns a collection that has all branches that a user | ||
315 | 565 | # owns, is subscribed to or registered. No other branches are in this | ||
316 | 566 | # collection. | ||
317 | 567 | person = self.factory.makePerson() | ||
318 | 568 | team = self.factory.makeTeam(person) | ||
319 | 569 | owned_branch = self.factory.makeAnyBranch(owner=person) | ||
320 | 570 | # Unsubscribe the owner, to demonstrate that we show owned branches | ||
321 | 571 | # even if they aren't subscribed. | ||
322 | 572 | owned_branch.unsubscribe(person, person) | ||
323 | 573 | # Subscribe two other people to the owned branch to make sure | ||
324 | 574 | # that the BranchSubscription join is doing it right. | ||
325 | 575 | self.factory.makeBranchSubscription(branch=owned_branch) | ||
326 | 576 | self.factory.makeBranchSubscription(branch=owned_branch) | ||
327 | 577 | |||
328 | 578 | registered_branch = self.factory.makeAnyBranch( | ||
329 | 579 | owner=team, registrant=person) | ||
330 | 580 | subscribed_branch = self.factory.makeAnyBranch() | ||
331 | 581 | subscribed_branch.subscribe( | ||
332 | 582 | person, BranchSubscriptionNotificationLevel.NOEMAIL, | ||
333 | 583 | BranchSubscriptionDiffSize.NODIFF, | ||
334 | 584 | CodeReviewNotificationLevel.NOEMAIL, | ||
335 | 585 | person) | ||
336 | 586 | related_branches = self.all_branches.relatedTo(person) | ||
337 | 587 | self.assertEqual( | ||
338 | 588 | sorted([owned_branch, registered_branch, subscribed_branch]), | ||
339 | 589 | sorted(related_branches.getBranches())) | ||
340 | 590 | |||
341 | 591 | def test_withBranchType(self): | 564 | def test_withBranchType(self): |
342 | 592 | hosted_branch1 = self.factory.makeAnyBranch( | 565 | hosted_branch1 = self.factory.makeAnyBranch( |
343 | 593 | branch_type=BranchType.HOSTED) | 566 | branch_type=BranchType.HOSTED) |
344 | @@ -1161,6 +1134,21 @@ | |||
345 | 1161 | search_results = self.collection.search(branch.codebrowse_url()) | 1134 | search_results = self.collection.search(branch.codebrowse_url()) |
346 | 1162 | self.assertEqual([branch], list(search_results)) | 1135 | self.assertEqual([branch], list(search_results)) |
347 | 1163 | 1136 | ||
348 | 1137 | def test_exact_match_with_lp_colon_url(self): | ||
349 | 1138 | branch = self.factory.makeBranch() | ||
350 | 1139 | lp_name = 'lp://dev/' + branch.unique_name | ||
351 | 1140 | search_results = self.collection.search(lp_name) | ||
352 | 1141 | self.assertEqual([branch], list(search_results)) | ||
353 | 1142 | |||
354 | 1143 | def test_exact_match_full_url(self): | ||
355 | 1144 | branch = self.factory.makeBranch() | ||
356 | 1145 | url = canonical_url(branch) | ||
357 | 1146 | self.assertEqual([branch], list(self.collection.search(url))) | ||
358 | 1147 | |||
359 | 1148 | def test_exact_match_bad_url(self): | ||
360 | 1149 | search_results = self.collection.search('http:hahafail') | ||
361 | 1150 | self.assertEqual([], list(search_results)) | ||
362 | 1151 | |||
363 | 1164 | def test_exact_match_bzr_identity(self): | 1152 | def test_exact_match_bzr_identity(self): |
364 | 1165 | # If you search for the bzr identity of a branch, then you get a | 1153 | # If you search for the bzr identity of a branch, then you get a |
365 | 1166 | # single result with that branch. | 1154 | # single result with that branch. |
366 | @@ -1214,6 +1202,12 @@ | |||
367 | 1214 | search_results = self.collection.search('foo') | 1202 | search_results = self.collection.search('foo') |
368 | 1215 | self.assertEqual(sorted([branch1, branch2]), sorted(search_results)) | 1203 | self.assertEqual(sorted([branch1, branch2]), sorted(search_results)) |
369 | 1216 | 1204 | ||
370 | 1205 | def test_match_against_unique_name(self): | ||
371 | 1206 | branch = self.factory.makeAnyBranch(name='fooa') | ||
372 | 1207 | search_term = branch.product.name + '/foo' | ||
373 | 1208 | search_results = self.collection.search(search_term) | ||
374 | 1209 | self.assertEqual([branch], list(search_results)) | ||
375 | 1210 | |||
376 | 1217 | def test_match_sub_branch_name(self): | 1211 | def test_match_sub_branch_name(self): |
377 | 1218 | # search returns all branches which have a name of which the search | 1212 | # search returns all branches which have a name of which the search |
378 | 1219 | # term is a substring. | 1213 | # term is a substring. |
379 | @@ -1223,73 +1217,9 @@ | |||
380 | 1223 | search_results = self.collection.search('foo') | 1217 | search_results = self.collection.search('foo') |
381 | 1224 | self.assertEqual(sorted([branch1, branch2]), sorted(search_results)) | 1218 | self.assertEqual(sorted([branch1, branch2]), sorted(search_results)) |
382 | 1225 | 1219 | ||
450 | 1226 | def test_match_exact_owner_name(self): | 1220 | def test_match_ignores_case(self): |
451 | 1227 | # search returns all branches which have an owner with a name matching | 1221 | branch = self.factory.makeAnyBranch(name='foobar') |
452 | 1228 | # the server. | 1222 | search_results = self.collection.search('FOOBAR') |
386 | 1229 | person = self.factory.makePerson(name='foo') | ||
387 | 1230 | branch1 = self.factory.makeAnyBranch(owner=person) | ||
388 | 1231 | branch2 = self.factory.makeAnyBranch(owner=person) | ||
389 | 1232 | self.factory.makeAnyBranch() | ||
390 | 1233 | search_results = self.collection.search('foo') | ||
391 | 1234 | self.assertEqual(sorted([branch1, branch2]), sorted(search_results)) | ||
392 | 1235 | |||
393 | 1236 | def test_match_sub_owner_name(self): | ||
394 | 1237 | # search returns all branches that have an owner name where the search | ||
395 | 1238 | # term is a substring of the owner name. | ||
396 | 1239 | person1 = self.factory.makePerson(name='foom') | ||
397 | 1240 | branch1 = self.factory.makeAnyBranch(owner=person1) | ||
398 | 1241 | person2 = self.factory.makePerson(name='afoo') | ||
399 | 1242 | branch2 = self.factory.makeAnyBranch(owner=person2) | ||
400 | 1243 | self.factory.makeAnyBranch() | ||
401 | 1244 | search_results = self.collection.search('foo') | ||
402 | 1245 | self.assertEqual(sorted([branch1, branch2]), sorted(search_results)) | ||
403 | 1246 | |||
404 | 1247 | def test_match_exact_product_name(self): | ||
405 | 1248 | # search returns all branches that have a product name where the | ||
406 | 1249 | # product name is the same as the search term. | ||
407 | 1250 | product = self.factory.makeProduct(name='foo') | ||
408 | 1251 | branch1 = self.factory.makeAnyBranch(product=product) | ||
409 | 1252 | branch2 = self.factory.makeAnyBranch(product=product) | ||
410 | 1253 | self.factory.makeAnyBranch() | ||
411 | 1254 | search_results = self.collection.search('foo') | ||
412 | 1255 | self.assertEqual(sorted([branch1, branch2]), sorted(search_results)) | ||
413 | 1256 | |||
414 | 1257 | def test_match_sub_product_name(self): | ||
415 | 1258 | # search returns all branches that have a product name where the | ||
416 | 1259 | # search terms is a substring of the product name. | ||
417 | 1260 | product1 = self.factory.makeProduct(name='foom') | ||
418 | 1261 | branch1 = self.factory.makeProductBranch(product=product1) | ||
419 | 1262 | product2 = self.factory.makeProduct(name='afoo') | ||
420 | 1263 | branch2 = self.factory.makeProductBranch(product=product2) | ||
421 | 1264 | self.factory.makeAnyBranch() | ||
422 | 1265 | search_results = self.collection.search('foo') | ||
423 | 1266 | self.assertEqual(sorted([branch1, branch2]), sorted(search_results)) | ||
424 | 1267 | |||
425 | 1268 | def test_match_sub_distro_name(self): | ||
426 | 1269 | # search returns all branches that have a distro name where the search | ||
427 | 1270 | # term is a substring of the distro name. | ||
428 | 1271 | branch = self.factory.makePackageBranch() | ||
429 | 1272 | self.factory.makeAnyBranch() | ||
430 | 1273 | search_term = branch.distribution.name[1:] | ||
431 | 1274 | search_results = self.collection.search(search_term) | ||
432 | 1275 | self.assertEqual([branch], list(search_results)) | ||
433 | 1276 | |||
434 | 1277 | def test_match_sub_distroseries_name(self): | ||
435 | 1278 | # search returns all branches that have a distro series with a name | ||
436 | 1279 | # that the search term is a substring of. | ||
437 | 1280 | branch = self.factory.makePackageBranch() | ||
438 | 1281 | self.factory.makeAnyBranch() | ||
439 | 1282 | search_term = branch.distroseries.name[1:] | ||
440 | 1283 | search_results = self.collection.search(search_term) | ||
441 | 1284 | self.assertEqual([branch], list(search_results)) | ||
442 | 1285 | |||
443 | 1286 | def test_match_sub_sourcepackage_name(self): | ||
444 | 1287 | # search returns all branches that have a source package with a name | ||
445 | 1288 | # that contains the search term. | ||
446 | 1289 | branch = self.factory.makePackageBranch() | ||
447 | 1290 | self.factory.makeAnyBranch() | ||
448 | 1291 | search_term = branch.sourcepackagename.name[1:] | ||
449 | 1292 | search_results = self.collection.search(search_term) | ||
453 | 1293 | self.assertEqual([branch], list(search_results)) | 1223 | self.assertEqual([branch], list(search_results)) |
454 | 1294 | 1224 | ||
455 | 1295 | def test_dont_match_product_if_in_product(self): | 1225 | def test_dont_match_product_if_in_product(self): |
456 | 1296 | 1226 | ||
457 | === modified file 'lib/lp/code/stories/feeds/xx-branch-atom.txt' | |||
458 | --- lib/lp/code/stories/feeds/xx-branch-atom.txt 2012-07-05 04:59:52 +0000 | |||
459 | +++ lib/lp/code/stories/feeds/xx-branch-atom.txt 2013-02-14 03:56:21 +0000 | |||
460 | @@ -99,16 +99,17 @@ | |||
461 | 99 | 99 | ||
462 | 100 | If a branch is marked private it will not be displayed. The Landscape | 100 | If a branch is marked private it will not be displayed. The Landscape |
463 | 101 | developers team has two branches which are both private. | 101 | developers team has two branches which are both private. |
464 | 102 | |||
465 | 102 | >>> from zope.component import getUtility | 103 | >>> from zope.component import getUtility |
466 | 103 | >>> from zope.security.proxy import removeSecurityProxy | 104 | >>> from zope.security.proxy import removeSecurityProxy |
467 | 104 | >>> from lp.code.model.branch import Branch | 105 | >>> from lp.code.model.branch import Branch |
468 | 105 | >>> from lp.code.interfaces.branchcollection import IAllBranches | 106 | >>> from lp.code.interfaces.branchcollection import IAllBranches |
469 | 106 | >>> from lp.registry.interfaces.person import IPersonSet | 107 | >>> from lp.registry.interfaces.person import IPersonSet |
470 | 108 | >>> from lp.registry.interfaces.product import IProductSet | ||
471 | 107 | >>> login(ANONYMOUS) | 109 | >>> login(ANONYMOUS) |
476 | 108 | >>> person_set = getUtility(IPersonSet) | 110 | >>> test_user = getUtility(IPersonSet).getByEmail('test@canonical.com') |
477 | 109 | >>> landscape_team = person_set.getByName('landscape-developers') | 111 | >>> landscape = getUtility(IProductSet)['landscape'] |
478 | 110 | >>> test_user = person_set.getByEmail('test@canonical.com') | 112 | >>> branches = getUtility(IAllBranches).inProduct(landscape) |
475 | 111 | >>> branches = getUtility(IAllBranches).relatedTo(landscape_team) | ||
479 | 112 | >>> branches = branches.visibleByUser( | 113 | >>> branches = branches.visibleByUser( |
480 | 113 | ... test_user).getBranches().order_by(Branch.id) | 114 | ... test_user).getBranches().order_by(Branch.id) |
481 | 114 | >>> for branch in branches: | 115 | >>> for branch in branches: |
482 | 115 | 116 | ||
483 | === modified file 'lib/lp/code/vocabularies/branch.py' | |||
484 | --- lib/lp/code/vocabularies/branch.py 2012-10-18 17:49:39 +0000 | |||
485 | +++ lib/lp/code/vocabularies/branch.py 2013-02-14 03:56:21 +0000 | |||
486 | @@ -1,9 +1,8 @@ | |||
488 | 1 | # Copyright 2009 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2013 Canonical Ltd. This software is licensed under the |
489 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
490 | 3 | 3 | ||
491 | 4 | """Vocabularies that contain branches.""" | 4 | """Vocabularies that contain branches.""" |
492 | 5 | 5 | ||
493 | 6 | |||
494 | 7 | __metaclass__ = type | 6 | __metaclass__ = type |
495 | 8 | 7 | ||
496 | 9 | __all__ = [ | 8 | __all__ = [ |
497 | @@ -31,12 +30,8 @@ | |||
498 | 31 | ) | 30 | ) |
499 | 32 | 31 | ||
500 | 33 | 32 | ||
507 | 34 | class BranchVocabularyBase(SQLObjectVocabularyBase): | 33 | class BranchVocabulary(SQLObjectVocabularyBase): |
508 | 35 | """A base class for Branch vocabularies. | 34 | """A vocabulary for searching branches.""" |
503 | 36 | |||
504 | 37 | Override `BranchVocabularyBase._getCollection` to provide the collection | ||
505 | 38 | of branches which make up the vocabulary. | ||
506 | 39 | """ | ||
509 | 40 | 35 | ||
510 | 41 | implements(IHugeVocabulary) | 36 | implements(IHugeVocabulary) |
511 | 42 | 37 | ||
512 | @@ -56,17 +51,10 @@ | |||
513 | 56 | return iter(search_results).next() | 51 | return iter(search_results).next() |
514 | 57 | raise LookupError(token) | 52 | raise LookupError(token) |
515 | 58 | 53 | ||
516 | 59 | def _getCollection(self): | ||
517 | 60 | """Return the collection of branches the vocabulary searches. | ||
518 | 61 | |||
519 | 62 | Subclasses MUST override and implement this. | ||
520 | 63 | """ | ||
521 | 64 | raise NotImplementedError(self._getCollection) | ||
522 | 65 | |||
523 | 66 | def searchForTerms(self, query=None, vocab_filter=None): | 54 | def searchForTerms(self, query=None, vocab_filter=None): |
524 | 67 | """See `IHugeVocabulary`.""" | 55 | """See `IHugeVocabulary`.""" |
527 | 68 | logged_in_user = getUtility(ILaunchBag).user | 56 | user = getUtility(ILaunchBag).user |
528 | 69 | collection = self._getCollection().visibleByUser(logged_in_user) | 57 | collection = self._getCollection().visibleByUser(user) |
529 | 70 | if query is None: | 58 | if query is None: |
530 | 71 | branches = collection.getBranches(eager_load=False) | 59 | branches = collection.getBranches(eager_load=False) |
531 | 72 | else: | 60 | else: |
532 | @@ -77,28 +65,15 @@ | |||
533 | 77 | """See `IVocabulary`.""" | 65 | """See `IVocabulary`.""" |
534 | 78 | return self.search().count() | 66 | return self.search().count() |
535 | 79 | 67 | ||
536 | 80 | |||
537 | 81 | class BranchVocabulary(BranchVocabularyBase): | ||
538 | 82 | """A vocabulary for searching branches. | ||
539 | 83 | |||
540 | 84 | The name and URL of the branch, the name of the product, and the | ||
541 | 85 | name of the registrant of the branches is checked for the entered | ||
542 | 86 | value. | ||
543 | 87 | """ | ||
544 | 88 | |||
545 | 89 | def _getCollection(self): | 68 | def _getCollection(self): |
546 | 90 | return getUtility(IAllBranches) | 69 | return getUtility(IAllBranches) |
547 | 91 | 70 | ||
548 | 92 | 71 | ||
555 | 93 | class BranchRestrictedOnProductVocabulary(BranchVocabularyBase): | 72 | class BranchRestrictedOnProductVocabulary(BranchVocabulary): |
556 | 94 | """A vocabulary for searching branches restricted on product. | 73 | """A vocabulary for searching branches restricted on product.""" |
551 | 95 | |||
552 | 96 | The query entered checks the name or URL of the branch, or the | ||
553 | 97 | name of the registrant of the branch. | ||
554 | 98 | """ | ||
557 | 99 | 74 | ||
558 | 100 | def __init__(self, context=None): | 75 | def __init__(self, context=None): |
560 | 101 | BranchVocabularyBase.__init__(self, context) | 76 | super(BranchRestrictedOnProductVocabulary, self).__init__(context) |
561 | 102 | if IProduct.providedBy(self.context): | 77 | if IProduct.providedBy(self.context): |
562 | 103 | self.product = self.context | 78 | self.product = self.context |
563 | 104 | elif IProductSeries.providedBy(self.context): | 79 | elif IProductSeries.providedBy(self.context): |
564 | @@ -113,7 +88,7 @@ | |||
565 | 113 | return getUtility(IAllBranches).inProduct(self.product).isExclusive() | 88 | return getUtility(IAllBranches).inProduct(self.product).isExclusive() |
566 | 114 | 89 | ||
567 | 115 | 90 | ||
569 | 116 | class HostedBranchRestrictedOnOwnerVocabulary(BranchVocabularyBase): | 91 | class HostedBranchRestrictedOnOwnerVocabulary(BranchVocabulary): |
570 | 117 | """A vocabulary for hosted branches owned by the current user. | 92 | """A vocabulary for hosted branches owned by the current user. |
571 | 118 | 93 | ||
572 | 119 | These are branches that the user either owns themselves or which are | 94 | These are branches that the user either owns themselves or which are |
573 | @@ -124,7 +99,7 @@ | |||
574 | 124 | """Pass a Person as context, or anything else for the current user.""" | 99 | """Pass a Person as context, or anything else for the current user.""" |
575 | 125 | super(HostedBranchRestrictedOnOwnerVocabulary, self).__init__(context) | 100 | super(HostedBranchRestrictedOnOwnerVocabulary, self).__init__(context) |
576 | 126 | if IPerson.providedBy(self.context): | 101 | if IPerson.providedBy(self.context): |
578 | 127 | self.user = context | 102 | self.user = self.context |
579 | 128 | else: | 103 | else: |
580 | 129 | self.user = getUtility(ILaunchBag).user | 104 | self.user = getUtility(ILaunchBag).user |
581 | 130 | 105 | ||
582 | 131 | 106 | ||
583 | === modified file 'lib/lp/code/vocabularies/tests/branch.txt' | |||
584 | --- lib/lp/code/vocabularies/tests/branch.txt 2012-04-30 13:53:06 +0000 | |||
585 | +++ lib/lp/code/vocabularies/tests/branch.txt 2013-02-14 03:56:21 +0000 | |||
586 | @@ -32,16 +32,6 @@ | |||
587 | 32 | ~stevea/thunderbird/main | 32 | ~stevea/thunderbird/main |
588 | 33 | ~vcs-imports/evolution/main | 33 | ~vcs-imports/evolution/main |
589 | 34 | 34 | ||
590 | 35 | >>> print_vocab_branches(branch_vocabulary, 'vcs-imports') | ||
591 | 36 | ~vcs-imports/evolution/import | ||
592 | 37 | ~vcs-imports/evolution/main | ||
593 | 38 | ~vcs-imports/gnome-terminal/import | ||
594 | 39 | |||
595 | 40 | >>> print_vocab_branches(branch_vocabulary, 'evolution') | ||
596 | 41 | ~carlos/evolution/2.0 | ||
597 | 42 | ~vcs-imports/evolution/import | ||
598 | 43 | ~vcs-imports/evolution/main | ||
599 | 44 | |||
600 | 45 | A search with the full branch unique name should also find the branch. | 35 | A search with the full branch unique name should also find the branch. |
601 | 46 | 36 | ||
602 | 47 | >>> print_vocab_branches(branch_vocabulary, '~name12/firefox/main') | 37 | >>> print_vocab_branches(branch_vocabulary, '~name12/firefox/main') |
603 | @@ -107,9 +97,6 @@ | |||
604 | 107 | >>> print_vocab_branches(branch_vocabulary, 'main') | 97 | >>> print_vocab_branches(branch_vocabulary, 'main') |
605 | 108 | ~name12/gnome-terminal/main | 98 | ~name12/gnome-terminal/main |
606 | 109 | 99 | ||
607 | 110 | >>> print_vocab_branches(branch_vocabulary, 'vcs-imports') | ||
608 | 111 | ~vcs-imports/gnome-terminal/import | ||
609 | 112 | |||
610 | 113 | If a full unique name is entered that has a different product, the | 100 | If a full unique name is entered that has a different product, the |
611 | 114 | branch is not part of the vocabulary. | 101 | branch is not part of the vocabulary. |
612 | 115 | 102 | ||
613 | 116 | 103 | ||
614 | === modified file 'lib/lp/code/vocabularies/tests/test_branch_vocabularies.py' | |||
615 | --- lib/lp/code/vocabularies/tests/test_branch_vocabularies.py 2012-10-18 17:49:39 +0000 | |||
616 | +++ lib/lp/code/vocabularies/tests/test_branch_vocabularies.py 2013-02-14 03:56:21 +0000 | |||
617 | @@ -1,11 +1,10 @@ | |||
619 | 1 | # Copyright 2009 Canonical Ltd. This software is licensed under the | 1 | # Copyright 2009-2013 Canonical Ltd. This software is licensed under the |
620 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). |
621 | 3 | 3 | ||
622 | 4 | """Test the branch vocabularies.""" | 4 | """Test the branch vocabularies.""" |
623 | 5 | 5 | ||
624 | 6 | __metaclass__ = type | 6 | __metaclass__ = type |
625 | 7 | 7 | ||
626 | 8 | from unittest import TestCase | ||
627 | 9 | 8 | ||
628 | 10 | from zope.component import getUtility | 9 | from zope.component import getUtility |
629 | 11 | 10 | ||
630 | @@ -16,99 +15,36 @@ | |||
631 | 16 | ) | 15 | ) |
632 | 17 | from lp.registry.enums import TeamMembershipPolicy | 16 | from lp.registry.enums import TeamMembershipPolicy |
633 | 18 | from lp.registry.interfaces.product import IProductSet | 17 | from lp.registry.interfaces.product import IProductSet |
640 | 19 | from lp.testing import ( | 18 | from lp.testing import TestCaseWithFactory |
635 | 20 | ANONYMOUS, | ||
636 | 21 | login, | ||
637 | 22 | logout, | ||
638 | 23 | ) | ||
639 | 24 | from lp.testing.factory import LaunchpadObjectFactory | ||
641 | 25 | from lp.testing.layers import DatabaseFunctionalLayer | 19 | from lp.testing.layers import DatabaseFunctionalLayer |
642 | 26 | 20 | ||
643 | 27 | 21 | ||
657 | 28 | class BranchVocabTestCase(TestCase): | 22 | class TestBranchVocabulary(TestCaseWithFactory): |
645 | 29 | """A base class for the branch vocabulary test cases.""" | ||
646 | 30 | layer = DatabaseFunctionalLayer | ||
647 | 31 | |||
648 | 32 | def setUp(self): | ||
649 | 33 | # Set up the anonymous security interaction. | ||
650 | 34 | login(ANONYMOUS) | ||
651 | 35 | |||
652 | 36 | def tearDown(self): | ||
653 | 37 | logout() | ||
654 | 38 | |||
655 | 39 | |||
656 | 40 | class TestBranchVocabulary(BranchVocabTestCase): | ||
658 | 41 | """Test that the BranchVocabulary behaves as expected.""" | 23 | """Test that the BranchVocabulary behaves as expected.""" |
659 | 42 | 24 | ||
660 | 25 | layer = DatabaseFunctionalLayer | ||
661 | 26 | |||
662 | 43 | def setUp(self): | 27 | def setUp(self): |
664 | 44 | BranchVocabTestCase.setUp(self) | 28 | super(TestBranchVocabulary, self).setUp() |
665 | 45 | self._createBranches() | 29 | self._createBranches() |
666 | 46 | self.vocab = BranchVocabulary(context=None) | 30 | self.vocab = BranchVocabulary(context=None) |
667 | 47 | 31 | ||
668 | 48 | def _createBranches(self): | 32 | def _createBranches(self): |
675 | 49 | factory = LaunchpadObjectFactory() | 33 | widget = self.factory.makeProduct(name='widget') |
676 | 50 | widget = factory.makeProduct(name='widget') | 34 | sprocket = self.factory.makeProduct(name='sprocket') |
677 | 51 | sprocket = factory.makeProduct(name='sprocket') | 35 | scotty = self.factory.makePerson(name='scotty') |
678 | 52 | # Scotty's branches. | 36 | self.factory.makeProductBranch( |
673 | 53 | scotty = factory.makePerson(name='scotty') | ||
674 | 54 | factory.makeProductBranch( | ||
679 | 55 | owner=scotty, product=widget, name='fizzbuzz') | 37 | owner=scotty, product=widget, name='fizzbuzz') |
681 | 56 | factory.makeProductBranch( | 38 | self.factory.makeProductBranch( |
682 | 57 | owner=scotty, product=widget, name='mountain') | 39 | owner=scotty, product=widget, name='mountain') |
684 | 58 | factory.makeProductBranch( | 40 | self.factory.makeProductBranch( |
685 | 59 | owner=scotty, product=sprocket, name='fizzbuzz') | 41 | owner=scotty, product=sprocket, name='fizzbuzz') |
686 | 60 | # Spotty's branches. | ||
687 | 61 | spotty = factory.makePerson(name='spotty') | ||
688 | 62 | factory.makeProductBranch( | ||
689 | 63 | owner=spotty, product=widget, name='hill') | ||
690 | 64 | factory.makeProductBranch( | ||
691 | 65 | owner=spotty, product=widget, name='sprocket') | ||
692 | 66 | # Sprocket's branches. | ||
693 | 67 | sprocket_person = factory.makePerson(name='sprocket') | ||
694 | 68 | factory.makeProductBranch( | ||
695 | 69 | owner=sprocket_person, product=widget, name='foo') | ||
696 | 70 | 42 | ||
697 | 71 | def test_fizzbuzzBranches(self): | 43 | def test_fizzbuzzBranches(self): |
698 | 72 | """Return branches that match the string 'fizzbuzz'.""" | 44 | """Return branches that match the string 'fizzbuzz'.""" |
699 | 73 | results = self.vocab.searchForTerms('fizzbuzz') | 45 | results = self.vocab.searchForTerms('fizzbuzz') |
700 | 74 | expected = [ | 46 | expected = [ |
738 | 75 | u'~scotty/sprocket/fizzbuzz', | 47 | u'~scotty/sprocket/fizzbuzz', u'~scotty/widget/fizzbuzz'] |
702 | 76 | u'~scotty/widget/fizzbuzz', | ||
703 | 77 | ] | ||
704 | 78 | branch_names = sorted([branch.token for branch in results]) | ||
705 | 79 | self.assertEqual(expected, branch_names) | ||
706 | 80 | |||
707 | 81 | def test_widgetBranches(self): | ||
708 | 82 | """Searches match the product name too.""" | ||
709 | 83 | results = self.vocab.searchForTerms('widget') | ||
710 | 84 | expected = [ | ||
711 | 85 | u'~scotty/widget/fizzbuzz', | ||
712 | 86 | u'~scotty/widget/mountain', | ||
713 | 87 | u'~spotty/widget/hill', | ||
714 | 88 | u'~spotty/widget/sprocket', | ||
715 | 89 | u'~sprocket/widget/foo', | ||
716 | 90 | ] | ||
717 | 91 | branch_names = sorted([branch.token for branch in results]) | ||
718 | 92 | self.assertEqual(expected, branch_names) | ||
719 | 93 | |||
720 | 94 | def test_spottyBranches(self): | ||
721 | 95 | """Searches also match the registrant name.""" | ||
722 | 96 | results = self.vocab.searchForTerms('spotty') | ||
723 | 97 | expected = [ | ||
724 | 98 | u'~spotty/widget/hill', | ||
725 | 99 | u'~spotty/widget/sprocket', | ||
726 | 100 | ] | ||
727 | 101 | branch_names = sorted([branch.token for branch in results]) | ||
728 | 102 | self.assertEqual(expected, branch_names) | ||
729 | 103 | |||
730 | 104 | def test_crossAttributeBranches(self): | ||
731 | 105 | """The search checks name, product, and person.""" | ||
732 | 106 | results = self.vocab.searchForTerms('rocket') | ||
733 | 107 | expected = [ | ||
734 | 108 | u'~scotty/sprocket/fizzbuzz', | ||
735 | 109 | u'~spotty/widget/sprocket', | ||
736 | 110 | u'~sprocket/widget/foo', | ||
737 | 111 | ] | ||
739 | 112 | branch_names = sorted([branch.token for branch in results]) | 48 | branch_names = sorted([branch.token for branch in results]) |
740 | 113 | self.assertEqual(expected, branch_names) | 49 | self.assertEqual(expected, branch_names) |
741 | 114 | 50 | ||
742 | @@ -116,20 +52,15 @@ | |||
743 | 116 | # If there is a single search result that matches, use that | 52 | # If there is a single search result that matches, use that |
744 | 117 | # as the result. | 53 | # as the result. |
745 | 118 | term = self.vocab.getTermByToken('mountain') | 54 | term = self.vocab.getTermByToken('mountain') |
749 | 119 | self.assertEqual( | 55 | self.assertEqual('~scotty/widget/mountain', term.value.unique_name) |
747 | 120 | '~scotty/widget/mountain', | ||
748 | 121 | term.value.unique_name) | ||
750 | 122 | 56 | ||
751 | 123 | def test_multipleQueryResult(self): | 57 | def test_multipleQueryResult(self): |
752 | 124 | # If there are more than one search result, a LookupError is still | 58 | # If there are more than one search result, a LookupError is still |
753 | 125 | # raised. | 59 | # raised. |
761 | 126 | self.assertRaises( | 60 | self.assertRaises(LookupError, self.vocab.getTermByToken, 'fizzbuzz') |
762 | 127 | LookupError, | 61 | |
763 | 128 | self.vocab.getTermByToken, | 62 | |
764 | 129 | 'fizzbuzz') | 63 | class TestRestrictedBranchVocabularyOnProduct(TestCaseWithFactory): |
758 | 130 | |||
759 | 131 | |||
760 | 132 | class TestRestrictedBranchVocabularyOnProduct(BranchVocabTestCase): | ||
765 | 133 | """Test the BranchRestrictedOnProductVocabulary behaves as expected. | 64 | """Test the BranchRestrictedOnProductVocabulary behaves as expected. |
766 | 134 | 65 | ||
767 | 135 | When a BranchRestrictedOnProductVocabulary is used with a product the | 66 | When a BranchRestrictedOnProductVocabulary is used with a product the |
768 | @@ -137,8 +68,10 @@ | |||
769 | 137 | context. | 68 | context. |
770 | 138 | """ | 69 | """ |
771 | 139 | 70 | ||
772 | 71 | layer = DatabaseFunctionalLayer | ||
773 | 72 | |||
774 | 140 | def setUp(self): | 73 | def setUp(self): |
776 | 141 | BranchVocabTestCase.setUp(self) | 74 | super(TestRestrictedBranchVocabularyOnProduct, self).setUp() |
777 | 142 | self._createBranches() | 75 | self._createBranches() |
778 | 143 | self.vocab = BranchRestrictedOnProductVocabulary( | 76 | self.vocab = BranchRestrictedOnProductVocabulary( |
779 | 144 | context=self._getVocabRestriction()) | 77 | context=self._getVocabRestriction()) |
780 | @@ -148,18 +81,17 @@ | |||
781 | 148 | return getUtility(IProductSet).getByName('widget') | 81 | return getUtility(IProductSet).getByName('widget') |
782 | 149 | 82 | ||
783 | 150 | def _createBranches(self): | 83 | def _createBranches(self): |
789 | 151 | factory = LaunchpadObjectFactory() | 84 | test_product = self.factory.makeProduct(name='widget') |
790 | 152 | test_product = factory.makeProduct(name='widget') | 85 | other_product = self.factory.makeProduct(name='sprocket') |
791 | 153 | other_product = factory.makeProduct(name='sprocket') | 86 | person = self.factory.makePerson(name='scotty') |
792 | 154 | person = factory.makePerson(name='scotty') | 87 | self.factory.makeProductBranch( |
788 | 155 | factory.makeProductBranch( | ||
793 | 156 | owner=person, product=test_product, name='main') | 88 | owner=person, product=test_product, name='main') |
795 | 157 | factory.makeProductBranch( | 89 | self.factory.makeProductBranch( |
796 | 158 | owner=person, product=test_product, name='mountain') | 90 | owner=person, product=test_product, name='mountain') |
798 | 159 | factory.makeProductBranch( | 91 | self.factory.makeProductBranch( |
799 | 160 | owner=person, product=other_product, name='main') | 92 | owner=person, product=other_product, name='main') |
802 | 161 | person = factory.makePerson(name='spotty') | 93 | person = self.factory.makePerson(name='spotty') |
803 | 162 | factory.makeProductBranch( | 94 | self.factory.makeProductBranch( |
804 | 163 | owner=person, product=test_product, name='hill') | 95 | owner=person, product=test_product, name='hill') |
805 | 164 | self.product = test_product | 96 | self.product = test_product |
806 | 165 | 97 | ||
807 | @@ -169,23 +101,7 @@ | |||
808 | 169 | The result set should not show ~scotty/sprocket/main. | 101 | The result set should not show ~scotty/sprocket/main. |
809 | 170 | """ | 102 | """ |
810 | 171 | results = self.vocab.searchForTerms('main') | 103 | results = self.vocab.searchForTerms('main') |
828 | 172 | expected = [ | 104 | expected = [u'~scotty/widget/main'] |
812 | 173 | u'~scotty/widget/main', | ||
813 | 174 | ] | ||
814 | 175 | branch_names = sorted([branch.token for branch in results]) | ||
815 | 176 | self.assertEqual(expected, branch_names) | ||
816 | 177 | |||
817 | 178 | def test_ownersBranches(self): | ||
818 | 179 | """Look for branches owned by scotty. | ||
819 | 180 | |||
820 | 181 | The result set should not show ~scotty/sprocket/main. | ||
821 | 182 | """ | ||
822 | 183 | results = self.vocab.searchForTerms('scotty') | ||
823 | 184 | |||
824 | 185 | expected = [ | ||
825 | 186 | u'~scotty/widget/main', | ||
826 | 187 | u'~scotty/widget/mountain', | ||
827 | 188 | ] | ||
829 | 189 | branch_names = sorted([branch.token for branch in results]) | 105 | branch_names = sorted([branch.token for branch in results]) |
830 | 190 | self.assertEqual(expected, branch_names) | 106 | self.assertEqual(expected, branch_names) |
831 | 191 | 107 | ||
832 | @@ -193,26 +109,20 @@ | |||
833 | 193 | # If there is a single search result that matches, use that | 109 | # If there is a single search result that matches, use that |
834 | 194 | # as the result. | 110 | # as the result. |
835 | 195 | term = self.vocab.getTermByToken('mountain') | 111 | term = self.vocab.getTermByToken('mountain') |
839 | 196 | self.assertEqual( | 112 | self.assertEqual('~scotty/widget/mountain', term.value.unique_name) |
837 | 197 | '~scotty/widget/mountain', | ||
838 | 198 | term.value.unique_name) | ||
840 | 199 | 113 | ||
841 | 200 | def test_multipleQueryResult(self): | 114 | def test_multipleQueryResult(self): |
842 | 201 | # If there are more than one search result, a LookupError is still | 115 | # If there are more than one search result, a LookupError is still |
843 | 202 | # raised. | 116 | # raised. |
848 | 203 | self.assertRaises( | 117 | self.assertRaises(LookupError, self.vocab.getTermByToken, 'scotty') |
845 | 204 | LookupError, | ||
846 | 205 | self.vocab.getTermByToken, | ||
847 | 206 | 'scotty') | ||
849 | 207 | 118 | ||
850 | 208 | def test_does_not_contain_inclusive_teams(self): | 119 | def test_does_not_contain_inclusive_teams(self): |
853 | 209 | factory = LaunchpadObjectFactory() | 120 | open_team = self.factory.makeTeam(name='open-team', |
852 | 210 | open_team = factory.makeTeam(name='open-team', | ||
854 | 211 | membership_policy=TeamMembershipPolicy.OPEN) | 121 | membership_policy=TeamMembershipPolicy.OPEN) |
856 | 212 | delegated_team = factory.makeTeam(name='delegated-team', | 122 | delegated_team = self.factory.makeTeam(name='delegated-team', |
857 | 213 | membership_policy=TeamMembershipPolicy.DELEGATED) | 123 | membership_policy=TeamMembershipPolicy.DELEGATED) |
858 | 214 | for team in [open_team, delegated_team]: | 124 | for team in [open_team, delegated_team]: |
860 | 215 | factory.makeProductBranch( | 125 | self.factory.makeProductBranch( |
861 | 216 | owner=team, product=self.product, name='mountain') | 126 | owner=team, product=self.product, name='mountain') |
862 | 217 | results = self.vocab.searchForTerms('mountain') | 127 | results = self.vocab.searchForTerms('mountain') |
863 | 218 | branch_names = sorted([branch.token for branch in results]) | 128 | branch_names = sorted([branch.token for branch in results]) |
864 | @@ -230,5 +140,4 @@ | |||
865 | 230 | 140 | ||
866 | 231 | def _getVocabRestriction(self): | 141 | def _getVocabRestriction(self): |
867 | 232 | """Restrict using a branch on widget.""" | 142 | """Restrict using a branch on widget.""" |
870 | 233 | return getUtility(IBranchLookup).getByUniqueName( | 143 | return getUtility(IBranchLookup).getByUniqueName('~spotty/widget/hill') |
869 | 234 | '~spotty/widget/hill') | ||
871 | 235 | 144 | ||
872 | === modified file 'lib/lp/registry/model/product.py' | |||
873 | --- lib/lp/registry/model/product.py 2013-02-07 06:10:38 +0000 | |||
874 | +++ lib/lp/registry/model/product.py 2013-02-14 03:56:21 +0000 | |||
875 | @@ -585,8 +585,6 @@ | |||
876 | 585 | 585 | ||
877 | 586 | @property | 586 | @property |
878 | 587 | def official_codehosting(self): | 587 | def official_codehosting(self): |
879 | 588 | # XXX Need to remove official_codehosting column from Product | ||
880 | 589 | # table. | ||
881 | 590 | return self.development_focus.branch is not None | 588 | return self.development_focus.branch is not None |
882 | 591 | 589 | ||
883 | 592 | @property | 590 | @property |
884 | 593 | 591 | ||
885 | === modified file 'lib/lp/services/webapp/configure.zcml' | |||
886 | --- lib/lp/services/webapp/configure.zcml 2012-09-28 07:11:24 +0000 | |||
887 | +++ lib/lp/services/webapp/configure.zcml 2013-02-14 03:56:21 +0000 | |||
888 | @@ -418,11 +418,11 @@ | |||
889 | 418 | permission="zope.Public" | 418 | permission="zope.Public" |
890 | 419 | /> | 419 | /> |
891 | 420 | 420 | ||
893 | 421 | <!-- Define the widget used by fields that use BranchVocabularyBase. --> | 421 | <!-- Define the widget used by fields that use BranchVocabulary. --> |
894 | 422 | <view | 422 | <view |
895 | 423 | type="zope.publisher.interfaces.browser.IBrowserRequest" | 423 | type="zope.publisher.interfaces.browser.IBrowserRequest" |
896 | 424 | for="zope.schema.interfaces.IChoice | 424 | for="zope.schema.interfaces.IChoice |
898 | 425 | lp.code.vocabularies.branch.BranchVocabularyBase" | 425 | lp.code.vocabularies.branch.BranchVocabulary" |
899 | 426 | provides="zope.app.form.interfaces.IInputWidget" | 426 | provides="zope.app.form.interfaces.IInputWidget" |
900 | 427 | factory="lp.code.browser.widgets.branch.BranchPopupWidget" | 427 | factory="lp.code.browser.widgets.branch.BranchPopupWidget" |
901 | 428 | permission="zope.Public" | 428 | permission="zope.Public" |
221 + if term and term.startswith('lp:'):
term should never be None.
229 + path = urlparse( term)[2] [1:]
Perhaps use lazr.uri instead?
+ collection = self._filterBy( [field. contains_ string( term.lower( ))])
The field needs to be lowercased too.
What's the effect of dropping the CountableIterator wrapper?