Skip to content

Commit a442d8b

Browse files
committed
Add classifiers to search
1 parent b1e15f4 commit a442d8b

File tree

7 files changed

+212
-74
lines changed

7 files changed

+212
-74
lines changed

tests/common/db/classifiers.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License");
2+
# you may not use this file except in compliance with the License.
3+
# You may obtain a copy of the License at
4+
#
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS,
9+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
# See the License for the specific language governing permissions and
11+
# limitations under the License.
12+
13+
import factory
14+
import factory.fuzzy
15+
16+
from warehouse.classifiers.models import Classifier
17+
18+
from .base import WarehouseFactory
19+
20+
21+
class ClassifierFactory(WarehouseFactory):
22+
class Meta:
23+
model = Classifier
24+
25+
l2 = factory.fuzzy.FuzzyInteger(0)
26+
l3 = factory.fuzzy.FuzzyInteger(0)
27+
l4 = factory.fuzzy.FuzzyInteger(0)
28+
l5 = factory.fuzzy.FuzzyInteger(0)

tests/unit/packaging/test_search.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ def test_build_search():
3939
keywords="the, keywords, lol",
4040
platform="any platform",
4141
created=datetime.datetime(1956, 1, 31),
42+
_classifiers=[
43+
pretend.stub(classifier='Alpha'),
44+
pretend.stub(classifier='Beta'),
45+
],
4246
uploader=pretend.stub(
4347
username="some-username",
4448
name="the-users-name",
@@ -60,5 +64,6 @@ def test_build_search():
6064
assert obj["keywords"] == "the, keywords, lol"
6165
assert obj["platform"] == "any platform"
6266
assert obj["created"] == datetime.datetime(1956, 1, 31)
67+
assert obj["classifiers"] == ['Alpha', 'Beta']
6368
assert obj["uploader_name"] == "the-users-name"
6469
assert obj["uploader_username"] == "some-username"

tests/unit/test_views.py

Lines changed: 130 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import pretend
1616
import pytest
17+
from webob.multidict import MultiDict
1718

1819
from pyramid.httpexceptions import HTTPNotFound
1920

@@ -23,10 +24,11 @@
2324
search,
2425
)
2526

27+
from ..common.db.accounts import UserFactory
28+
from ..common.db.classifiers import ClassifierFactory
2629
from ..common.db.packaging import (
2730
ProjectFactory, ReleaseFactory, FileFactory,
2831
)
29-
from ..common.db.accounts import UserFactory
3032

3133

3234
def test_httpexception_view():
@@ -98,19 +100,21 @@ def test_esi_current_user_indicator():
98100
class TestSearch:
99101

100102
@pytest.mark.parametrize("page", [None, 1, 5])
101-
def test_with_a_query(self, monkeypatch, page):
102-
params = {"q": "foo bar"}
103+
def test_with_a_query(self, monkeypatch, db_request, page):
104+
params = MultiDict({"q": "foo bar"})
103105
if page is not None:
104106
params["page"] = page
105-
suggest = pretend.stub()
106-
query = pretend.stub(
107+
db_request.params = params
108+
109+
sort = pretend.stub()
110+
suggest = pretend.stub(
111+
sort=pretend.call_recorder(lambda *a, **kw: sort),
112+
)
113+
es_query = pretend.stub(
107114
suggest=pretend.call_recorder(lambda *a, **kw: suggest),
108115
)
109-
request = pretend.stub(
110-
es=pretend.stub(
111-
query=pretend.call_recorder(lambda *a, **kw: query),
112-
),
113-
params=params,
116+
db_request.es = pretend.stub(
117+
query=pretend.call_recorder(lambda *a, **kw: es_query)
114118
)
115119

116120
page_obj = pretend.stub(page_count=(page or 1) + 10)
@@ -121,16 +125,18 @@ def test_with_a_query(self, monkeypatch, page):
121125
url_maker_factory = pretend.call_recorder(lambda request: url_maker)
122126
monkeypatch.setattr(views, "paginate_url_factory", url_maker_factory)
123127

124-
assert search(request) == {
128+
assert search(db_request) == {
125129
"page": page_obj,
126-
"term": params.get("q"),
127-
"order": params.get("o"),
130+
"term": params.get("q", ''),
131+
"order": params.get("o", ''),
132+
"applied_filters": [],
133+
"available_filters": [],
128134
}
129135
assert page_cls.calls == [
130136
pretend.call(suggest, url_maker=url_maker, page=page or 1),
131137
]
132-
assert url_maker_factory.calls == [pretend.call(request)]
133-
assert request.es.query.calls == [
138+
assert url_maker_factory.calls == [pretend.call(db_request)]
139+
assert db_request.es.query.calls == [
134140
pretend.call(
135141
"multi_match",
136142
query="foo bar",
@@ -142,7 +148,7 @@ def test_with_a_query(self, monkeypatch, page):
142148
],
143149
),
144150
]
145-
assert query.suggest.calls == [
151+
assert es_query.suggest.calls == [
146152
pretend.call(
147153
name="name_suggestion",
148154
term={"field": "name"},
@@ -151,22 +157,21 @@ def test_with_a_query(self, monkeypatch, page):
151157
]
152158

153159
@pytest.mark.parametrize("page", [None, 1, 5])
154-
def test_with_an_ordering(self, monkeypatch, page):
155-
params = {"q": "foo bar", "o": "-created"}
160+
def test_with_an_ordering(self, monkeypatch, db_request, page):
161+
params = MultiDict({"q": "foo bar", "o": "-created"})
156162
if page is not None:
157163
params["page"] = page
164+
db_request.params = params
165+
158166
sort = pretend.stub()
159167
suggest = pretend.stub(
160168
sort=pretend.call_recorder(lambda *a, **kw: sort),
161169
)
162-
query = pretend.stub(
170+
es_query = pretend.stub(
163171
suggest=pretend.call_recorder(lambda *a, **kw: suggest),
164172
)
165-
request = pretend.stub(
166-
es=pretend.stub(
167-
query=pretend.call_recorder(lambda *a, **kw: query),
168-
),
169-
params=params,
173+
db_request.es = pretend.stub(
174+
query=pretend.call_recorder(lambda *a, **kw: es_query)
170175
)
171176

172177
page_obj = pretend.stub(page_count=(page or 1) + 10)
@@ -177,16 +182,18 @@ def test_with_an_ordering(self, monkeypatch, page):
177182
url_maker_factory = pretend.call_recorder(lambda request: url_maker)
178183
monkeypatch.setattr(views, "paginate_url_factory", url_maker_factory)
179184

180-
assert search(request) == {
185+
assert search(db_request) == {
181186
"page": page_obj,
182-
"term": params.get("q"),
183-
"order": params.get("o"),
187+
"term": params.get("q", ''),
188+
"order": params.get("o", ''),
189+
"applied_filters": [],
190+
"available_filters": [],
184191
}
185192
assert page_cls.calls == [
186193
pretend.call(sort, url_maker=url_maker, page=page or 1),
187194
]
188-
assert url_maker_factory.calls == [pretend.call(request)]
189-
assert request.es.query.calls == [
195+
assert url_maker_factory.calls == [pretend.call(db_request)]
196+
assert db_request.es.query.calls == [
190197
pretend.call(
191198
"multi_match",
192199
query="foo bar",
@@ -198,7 +205,7 @@ def test_with_an_ordering(self, monkeypatch, page):
198205
],
199206
),
200207
]
201-
assert query.suggest.calls == [
208+
assert es_query.suggest.calls == [
202209
pretend.call(
203210
name="name_suggestion",
204211
term={"field": "name"},
@@ -210,15 +217,28 @@ def test_with_an_ordering(self, monkeypatch, page):
210217
]
211218

212219
@pytest.mark.parametrize("page", [None, 1, 5])
213-
def test_without_a_query(self, monkeypatch, page):
214-
params = {}
220+
def test_with_classifiers(self, monkeypatch, db_request, page):
221+
params = MultiDict([
222+
("q", "foo bar"),
223+
("c", "foo :: bar"),
224+
("c", "fiz :: buz"),
225+
])
215226
if page is not None:
216227
params["page"] = page
217-
query = pretend.stub()
218-
request = pretend.stub(
219-
es=pretend.stub(query=lambda: query),
220-
params=params,
228+
db_request.params = params
229+
230+
es_query = pretend.stub(
231+
suggest=pretend.call_recorder(lambda *a, **kw: es_query),
232+
filter=pretend.call_recorder(lambda *a, **kw: es_query),
233+
sort=pretend.call_recorder(lambda *a, **kw: es_query),
221234
)
235+
db_request.es = pretend.stub(
236+
query=pretend.call_recorder(lambda *a, **kw: es_query)
237+
)
238+
239+
classifier1 = ClassifierFactory.create(classifier="foo :: bar")
240+
classifier2 = ClassifierFactory.create(classifier="foo :: baz")
241+
classifier3 = ClassifierFactory.create(classifier="fiz :: buz")
222242

223243
page_obj = pretend.stub(page_count=(page or 1) + 10)
224244
page_cls = pretend.call_recorder(lambda *a, **kw: page_obj)
@@ -228,23 +248,82 @@ def test_without_a_query(self, monkeypatch, page):
228248
url_maker_factory = pretend.call_recorder(lambda request: url_maker)
229249
monkeypatch.setattr(views, "paginate_url_factory", url_maker_factory)
230250

231-
assert search(request) == {
251+
assert search(db_request) == {
232252
"page": page_obj,
233-
"term": params.get("q"),
234-
"order": params.get("o"),
253+
"term": params.get("q", ''),
254+
"order": params.get("o", ''),
255+
"applied_filters": params.getall("c"),
256+
"available_filters": [
257+
('fiz', [classifier3.classifier]),
258+
('foo', [
259+
classifier1.classifier,
260+
classifier2.classifier,
261+
])
262+
],
235263
}
236264
assert page_cls.calls == [
237-
pretend.call(query, url_maker=url_maker, page=page or 1),
265+
pretend.call(es_query, url_maker=url_maker, page=page or 1),
266+
]
267+
assert url_maker_factory.calls == [pretend.call(db_request)]
268+
assert db_request.es.query.calls == [
269+
pretend.call(
270+
"multi_match",
271+
query="foo bar",
272+
fields=[
273+
"name^2", "version", "author", "author_email",
274+
"maintainer", "maintainer_email", "home_page", "license",
275+
"summary", "description", "keywords", "platform",
276+
"download_url",
277+
],
278+
),
279+
]
280+
assert es_query.suggest.calls == [
281+
pretend.call(
282+
name="name_suggestion",
283+
term={"field": "name"},
284+
text="foo bar",
285+
),
286+
]
287+
assert es_query.filter.calls == [
288+
pretend.call('terms', classifiers=['foo :: bar', 'fiz :: buz'])
238289
]
239-
assert url_maker_factory.calls == [pretend.call(request)]
240290

241-
def test_raises_404_with_pagenum_too_high(self, monkeypatch):
242-
params = {"page": 15}
243-
query = pretend.stub()
244-
request = pretend.stub(
245-
es=pretend.stub(query=lambda: query),
246-
params=params,
247-
)
291+
@pytest.mark.parametrize("page", [None, 1, 5])
292+
def test_without_a_query(self, monkeypatch, db_request, page):
293+
params = MultiDict()
294+
if page is not None:
295+
params["page"] = page
296+
db_request.params = params
297+
298+
es_query = pretend.stub()
299+
db_request.es = pretend.stub(query=lambda *a, **kw: es_query)
300+
301+
page_obj = pretend.stub(page_count=(page or 1) + 10)
302+
page_cls = pretend.call_recorder(lambda *a, **kw: page_obj)
303+
monkeypatch.setattr(views, "ElasticsearchPage", page_cls)
304+
305+
url_maker = pretend.stub()
306+
url_maker_factory = pretend.call_recorder(lambda request: url_maker)
307+
monkeypatch.setattr(views, "paginate_url_factory", url_maker_factory)
308+
309+
assert search(db_request) == {
310+
"page": page_obj,
311+
"term": params.get("q", ''),
312+
"order": params.get("o", ''),
313+
"applied_filters": [],
314+
"available_filters": [],
315+
}
316+
assert page_cls.calls == [
317+
pretend.call(es_query, url_maker=url_maker, page=page or 1),
318+
]
319+
assert url_maker_factory.calls == [pretend.call(db_request)]
320+
321+
def test_raises_404_with_pagenum_too_high(self, monkeypatch, db_request):
322+
params = MultiDict({"page": 15})
323+
db_request.params = params
324+
325+
es_query = pretend.stub()
326+
db_request.es = pretend.stub(query=lambda *a, **kw: es_query)
248327

249328
page_obj = pretend.stub(page_count=10)
250329
page_cls = pretend.call_recorder(lambda *a, **kw: page_obj)
@@ -255,9 +334,9 @@ def test_raises_404_with_pagenum_too_high(self, monkeypatch):
255334
monkeypatch.setattr(views, "paginate_url_factory", url_maker_factory)
256335

257336
with pytest.raises(HTTPNotFound):
258-
search(request)
337+
search(db_request)
259338

260339
assert page_cls.calls == [
261-
pretend.call(query, url_maker=url_maker, page=15 or 1),
340+
pretend.call(es_query, url_maker=url_maker, page=15 or 1),
262341
]
263-
assert url_maker_factory.calls == [pretend.call(request)]
342+
assert url_maker_factory.calls == [pretend.call(db_request)]

warehouse/packaging/search.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class Project(DocType):
4040
keywords = String(analyzer="snowball")
4141
platform = String(index="not_analyzed")
4242
created = Date()
43+
classifiers = String(index="not_analyzed", multi=True)
4344

4445
uploader_name = String()
4546
uploader_username = String()
@@ -65,6 +66,7 @@ def from_db(cls, release):
6566
obj["keywords"] = release.keywords
6667
obj["platform"] = release.platform
6768
obj["created"] = release.created
69+
obj["classifiers"] = [c.classifier for c in release._classifiers]
6870

6971
obj["uploader_name"] = release.uploader.name
7072
obj["uploader_username"] = release.uploader.username

warehouse/static/js/main.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ $(document).ready(function() {
7979
this.form.submit();
8080
});
8181

82+
// Trove classifiers
83+
$("#classifiers :checkbox").change(function () {
84+
this.form.submit();
85+
});
86+
8287
$.timeago.settings.cutoff = 7 * 24 * 60 * 60 * 1000; // One week
8388

8489
// document.l10n.ready.then(function() {

0 commit comments

Comments
 (0)