Skip to content

Commit f58c6a4

Browse files
committed
Add classifiers to search
1 parent 8ead54c commit f58c6a4

File tree

7 files changed

+203
-64
lines changed

7 files changed

+203
-64
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: 121 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,19 @@
1414

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

1819
from warehouse import views
1920
from warehouse.views import (
2021
forbidden, index, httpexception_view, robotstxt, current_user_indicator,
2122
search,
2223
)
2324

25+
from ..common.db.accounts import UserFactory
26+
from ..common.db.classifiers import ClassifierFactory
2427
from ..common.db.packaging import (
2528
ProjectFactory, ReleaseFactory, FileFactory,
2629
)
27-
from ..common.db.accounts import UserFactory
2830

2931

3032
def test_httpexception_view():
@@ -96,19 +98,21 @@ def test_esi_current_user_indicator():
9698
class TestSearch:
9799

98100
@pytest.mark.parametrize("page", [None, 1, 5])
99-
def test_with_a_query(self, monkeypatch, page):
100-
params = {"q": "foo bar"}
101+
def test_with_a_query(self, monkeypatch, db_request, page):
102+
params = MultiDict({"q": "foo bar"})
101103
if page is not None:
102104
params["page"] = page
103-
suggest = pretend.stub()
104-
query = pretend.stub(
105+
db_request.params = params
106+
107+
sort = pretend.stub()
108+
suggest = pretend.stub(
109+
sort=pretend.call_recorder(lambda *a, **kw: sort),
110+
)
111+
es_query = pretend.stub(
105112
suggest=pretend.call_recorder(lambda *a, **kw: suggest),
106113
)
107-
request = pretend.stub(
108-
es=pretend.stub(
109-
query=pretend.call_recorder(lambda *a, **kw: query),
110-
),
111-
params=params,
114+
db_request.es = pretend.stub(
115+
query=pretend.call_recorder(lambda *a, **kw: es_query)
112116
)
113117

114118
page_obj = pretend.stub()
@@ -119,16 +123,18 @@ def test_with_a_query(self, monkeypatch, page):
119123
url_maker_factory = pretend.call_recorder(lambda request: url_maker)
120124
monkeypatch.setattr(views, "paginate_url_factory", url_maker_factory)
121125

122-
assert search(request) == {
126+
assert search(db_request) == {
123127
"page": page_obj,
124-
"term": params.get("q"),
125-
"order": params.get("o"),
128+
"term": params.get("q", ''),
129+
"order": params.get("o", ''),
130+
"applied_filters": [],
131+
"available_filters": [],
126132
}
127133
assert page_cls.calls == [
128134
pretend.call(suggest, url_maker=url_maker, page=page or 1),
129135
]
130-
assert url_maker_factory.calls == [pretend.call(request)]
131-
assert request.es.query.calls == [
136+
assert url_maker_factory.calls == [pretend.call(db_request)]
137+
assert db_request.es.query.calls == [
132138
pretend.call(
133139
"multi_match",
134140
query="foo bar",
@@ -140,7 +146,7 @@ def test_with_a_query(self, monkeypatch, page):
140146
],
141147
),
142148
]
143-
assert query.suggest.calls == [
149+
assert es_query.suggest.calls == [
144150
pretend.call(
145151
name="name_suggestion",
146152
term={"field": "name"},
@@ -149,22 +155,21 @@ def test_with_a_query(self, monkeypatch, page):
149155
]
150156

151157
@pytest.mark.parametrize("page", [None, 1, 5])
152-
def test_with_an_ordering(self, monkeypatch, page):
153-
params = {"q": "foo bar", "o": "-created"}
158+
def test_with_an_ordering(self, monkeypatch, db_request, page):
159+
params = MultiDict({"q": "foo bar", "o": "-created"})
154160
if page is not None:
155161
params["page"] = page
162+
db_request.params = params
163+
156164
sort = pretend.stub()
157165
suggest = pretend.stub(
158166
sort=pretend.call_recorder(lambda *a, **kw: sort),
159167
)
160-
query = pretend.stub(
168+
es_query = pretend.stub(
161169
suggest=pretend.call_recorder(lambda *a, **kw: suggest),
162170
)
163-
request = pretend.stub(
164-
es=pretend.stub(
165-
query=pretend.call_recorder(lambda *a, **kw: query),
166-
),
167-
params=params,
171+
db_request.es = pretend.stub(
172+
query=pretend.call_recorder(lambda *a, **kw: es_query)
168173
)
169174

170175
page_obj = pretend.stub()
@@ -175,16 +180,18 @@ def test_with_an_ordering(self, monkeypatch, page):
175180
url_maker_factory = pretend.call_recorder(lambda request: url_maker)
176181
monkeypatch.setattr(views, "paginate_url_factory", url_maker_factory)
177182

178-
assert search(request) == {
183+
assert search(db_request) == {
179184
"page": page_obj,
180-
"term": params.get("q"),
181-
"order": params.get("o"),
185+
"term": params.get("q", ''),
186+
"order": params.get("o", ''),
187+
"applied_filters": [],
188+
"available_filters": [],
182189
}
183190
assert page_cls.calls == [
184191
pretend.call(sort, url_maker=url_maker, page=page or 1),
185192
]
186-
assert url_maker_factory.calls == [pretend.call(request)]
187-
assert request.es.query.calls == [
193+
assert url_maker_factory.calls == [pretend.call(db_request)]
194+
assert db_request.es.query.calls == [
188195
pretend.call(
189196
"multi_match",
190197
query="foo bar",
@@ -196,7 +203,7 @@ def test_with_an_ordering(self, monkeypatch, page):
196203
],
197204
),
198205
]
199-
assert query.suggest.calls == [
206+
assert es_query.suggest.calls == [
200207
pretend.call(
201208
name="name_suggestion",
202209
term={"field": "name"},
@@ -208,16 +215,87 @@ def test_with_an_ordering(self, monkeypatch, page):
208215
]
209216

210217
@pytest.mark.parametrize("page", [None, 1, 5])
211-
def test_without_a_query(self, monkeypatch, page):
212-
params = {}
218+
def test_with_classifiers(self, monkeypatch, db_request, page):
219+
params = MultiDict([
220+
("q", "foo bar"),
221+
("c", "foo :: bar"),
222+
("c", "fiz :: buz"),
223+
])
213224
if page is not None:
214225
params["page"] = page
215-
query = pretend.stub()
216-
request = pretend.stub(
217-
es=pretend.stub(query=lambda: query),
218-
params=params,
226+
db_request.params = params
227+
228+
es_query = pretend.stub(
229+
suggest=pretend.call_recorder(lambda *a, **kw: es_query),
230+
filter=pretend.call_recorder(lambda *a, **kw: es_query),
231+
sort=pretend.call_recorder(lambda *a, **kw: es_query),
232+
)
233+
db_request.es = pretend.stub(
234+
query=pretend.call_recorder(lambda *a, **kw: es_query)
219235
)
220236

237+
classifier1 = ClassifierFactory.create(classifier="foo :: bar")
238+
classifier2 = ClassifierFactory.create(classifier="foo :: baz")
239+
classifier3 = ClassifierFactory.create(classifier="fiz :: buz")
240+
241+
page_obj = pretend.stub()
242+
page_cls = pretend.call_recorder(lambda *a, **kw: page_obj)
243+
monkeypatch.setattr(views, "ElasticsearchPage", page_cls)
244+
245+
url_maker = pretend.stub()
246+
url_maker_factory = pretend.call_recorder(lambda request: url_maker)
247+
monkeypatch.setattr(views, "paginate_url_factory", url_maker_factory)
248+
249+
assert search(db_request) == {
250+
"page": page_obj,
251+
"term": params.get("q", ''),
252+
"order": params.get("o", ''),
253+
"applied_filters": params.getall("c"),
254+
"available_filters": [
255+
('fiz', [classifier3.classifier]),
256+
('foo', [
257+
classifier1.classifier,
258+
classifier2.classifier,
259+
])
260+
],
261+
}
262+
assert page_cls.calls == [
263+
pretend.call(es_query, url_maker=url_maker, page=page or 1),
264+
]
265+
assert url_maker_factory.calls == [pretend.call(db_request)]
266+
assert db_request.es.query.calls == [
267+
pretend.call(
268+
"multi_match",
269+
query="foo bar",
270+
fields=[
271+
"name^2", "version", "author", "author_email",
272+
"maintainer", "maintainer_email", "home_page", "license",
273+
"summary", "description", "keywords", "platform",
274+
"download_url",
275+
],
276+
),
277+
]
278+
assert es_query.suggest.calls == [
279+
pretend.call(
280+
name="name_suggestion",
281+
term={"field": "name"},
282+
text="foo bar",
283+
),
284+
]
285+
assert es_query.filter.calls == [
286+
pretend.call('terms', classifiers=['foo :: bar', 'fiz :: buz'])
287+
]
288+
289+
@pytest.mark.parametrize("page", [None, 1, 5])
290+
def test_without_a_query(self, monkeypatch, db_request, page):
291+
params = MultiDict()
292+
if page is not None:
293+
params["page"] = page
294+
db_request.params = params
295+
296+
es_query = pretend.stub()
297+
db_request.es = pretend.stub(query=lambda *a, **kw: es_query)
298+
221299
page_obj = pretend.stub()
222300
page_cls = pretend.call_recorder(lambda *a, **kw: page_obj)
223301
monkeypatch.setattr(views, "ElasticsearchPage", page_cls)
@@ -226,12 +304,14 @@ def test_without_a_query(self, monkeypatch, page):
226304
url_maker_factory = pretend.call_recorder(lambda request: url_maker)
227305
monkeypatch.setattr(views, "paginate_url_factory", url_maker_factory)
228306

229-
assert search(request) == {
307+
assert search(db_request) == {
230308
"page": page_obj,
231-
"term": params.get("q"),
232-
"order": params.get("o"),
309+
"term": params.get("q", ''),
310+
"order": params.get("o", ''),
311+
"applied_filters": [],
312+
"available_filters": [],
233313
}
234314
assert page_cls.calls == [
235-
pretend.call(query, url_maker=url_maker, page=page or 1),
315+
pretend.call(es_query, url_maker=url_maker, page=page or 1),
236316
]
237-
assert url_maker_factory.calls == [pretend.call(request)]
317+
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
@@ -81,6 +81,11 @@ $(document).ready(function() {
8181
this.form.submit();
8282
});
8383

84+
// Trove classifiers
85+
$('#classifiers :checkbox').change(function () {
86+
this.form.submit();
87+
});
88+
8489
$.timeago.settings.cutoff = 7 * 24 * 60 * 60 * 1000; // One week
8590

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

0 commit comments

Comments
 (0)