Skip to content

Commit cfdbd77

Browse files
author
Jens Kadenbach
committed
Added facets for tags in the search
Changed the haystack requirement to a fork of haystack with a fix for faceting with Whoosh until it is implemented in the main line
1 parent 8ee16ba commit cfdbd77

File tree

5 files changed

+110
-58
lines changed

5 files changed

+110
-58
lines changed

functional_tests/tests.py

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,18 @@ def create_pre_authenticated_session(self):
9393
path='/',
9494
))
9595

96-
def _add_example_item(self):
97-
return Item.objects.create(url=EXAMPLE_COM, domain='nothing', owner=self.user)
96+
def _add_example_item(self, tags=None):
97+
item = Item.objects.create(url=EXAMPLE_COM, domain='nothing', owner=self.user)
98+
if tags is not None:
99+
item.tags.add(*tags)
100+
item.save()
101+
return item
102+
103+
def _add_tagged_items(self):
104+
self._add_example_item(('fish', 'boxing'))
105+
self._add_example_item(('fish', 'queen'))
106+
self._add_example_item(('queen', 'bartender'))
107+
self._add_example_item(('queen', 'pypo'))
98108

99109
def _find_tags_from_detail(self):
100110
self.b.find_element_by_css_selector('a.tags-dropdown').click()
@@ -230,9 +240,6 @@ def test_item_tags_are_shown_in_the_list(self):
230240
item.save()
231241

232242
self.b.get(self.live_server_url)
233-
# Uther activates the tags-list dropdown
234-
self.b.find_element_by_css_selector('a.tags-dropdown').click()
235-
# Uther sees the two tags for his example entry in a list
236243
tag_string = ''.join(self._find_tags_from_detail())
237244
# Uther sees the two tags for his example entry in a list
238245
self.assertIn('example', tag_string)
@@ -268,4 +275,34 @@ def test_can_update_tags_from_the_list(self):
268275
self.assertEqual(1, len(items), 'Item not found in results')
269276
self.assertEqual(EXAMPLE_COM, items[0].get_attribute('href'))
270277

278+
def _add_item_for_new_user(self):
279+
another_user = User.objects.create(username='someone')
280+
another_item = Item.objects.create(url=EXAMPLE_COM, domain='nothing', owner=another_user)
281+
another_item.tags.add('queen')
282+
another_item.save()
283+
284+
def test_facets_are_shown_in_a_search(self):
285+
self.create_pre_authenticated_session()
286+
# Uther added some of his tagged items
287+
self._add_tagged_items()
288+
289+
# Another user also adds an item with the same tag
290+
self._add_item_for_new_user()
271291

292+
# Uther starts a search for his queen-tagged items
293+
self.b.get(self.live_server_url+'/search')
294+
search_input = self.b.find_element_by_name('q')
295+
search_input.send_keys('queen')
296+
search_input.send_keys(Keys.ENTER)
297+
# He sees that his queen-tagged items also have other tags
298+
tags = {}
299+
for tag in self.b.find_elements_by_css_selector('li.tag'):
300+
tags[tag.find_element_by_css_selector('a.taglink').text] = int(
301+
tag.find_element_by_css_selector('span.count').text)
302+
# And only those items that are shown are counted in the list of tags
303+
self.assertEqual({
304+
'queen': 3,
305+
'fish': 1,
306+
'bartender': 1,
307+
'pypo': 1
308+
}, tags)

readme/search_indexes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ class ItemIndex(indexes.SearchIndex, indexes.Indexable):
77
title = indexes.CharField(model_attr='title')
88
created = indexes.DateTimeField(model_attr='created')
99
owner_id = indexes.IntegerField(model_attr='owner_id')
10-
tags = indexes.MultiValueField()
10+
tags = indexes.MultiValueField(faceted=True)
1111

1212
def prepare_tags(self, obj):
1313
if not type(obj.tags) is list:

readme/templates/search/search.html

Lines changed: 46 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,47 @@
1-
{% extends 'base.html' %}
2-
{% load crispy_forms_tags %}
3-
{% block content %}
4-
<h2>Search</h2>
5-
6-
<form method="get" action=".">
7-
{{ form|crispy }}
8-
<input type="submit" value="Search">
9-
10-
</form>
11-
{% if query %}
12-
<h3>Results</h3>
13-
14-
<div class="row">
15-
{% for result in page.object_list %}
16-
{% with result.object as item %}
17-
{% include "readme/item_single.html" %}
18-
{% endwith %}
19-
{% empty %}
20-
<p>No results found.</p>
21-
{% endfor %}
22-
</div>
23-
24-
{% if page.has_previous or page.has_next %}
25-
<div>
26-
{% if page.has_previous %}<a href="?q={{ query }}&amp;page={{ page.previous_page_number }}">{% endif %}&laquo; Previous{% if page.has_previous %}</a>{% endif %}
27-
|
28-
{% if page.has_next %}<a href="?q={{ query }}&amp;page={{ page.next_page_number }}">{% endif %}Next &raquo;{% if page.has_next %}</a>{% endif %}
29-
</div>
30-
{% endif %}
31-
{% endif %}
1+
{% extends 'base.html' %}
2+
{% load crispy_forms_tags %}
3+
{% block content %}
4+
<h2>Search</h2>
5+
6+
<form method="get" action=".">
7+
{{ form|crispy }}
8+
<input type="submit" value="Search">
9+
10+
</form>
11+
{% if query %}
12+
<h3>Results</h3>
13+
14+
<div class="row">
15+
{% for result in page.object_list %}
16+
{% with result.object as item %}
17+
{% include "readme/item_single.html" %}
18+
{% endwith %}
19+
{% empty %}
20+
<p>No results found.</p>
21+
{% endfor %}
22+
</div>
23+
24+
{% if facets.fields.tags %}
25+
<h3>Tags</h3>
26+
27+
<div class="row">
28+
<ul id="taglist">
29+
{% for tag in facets.fields.tags %}
30+
<li class="tag">
31+
<a class="taglink" href="{{ request.get_full_path }}&amp;selected_facets=tags_exact:{{ tag.0|urlencode }}">{{ tag.0 }}</a>
32+
(<span class="count">{{ tag.1 }}</span>)
33+
</li>
34+
{% endfor %}
35+
</ul>
36+
37+
</div>
38+
{% endif %}
39+
{% if page.has_previous or page.has_next %}
40+
<div>
41+
{% if page.has_previous %}<a href="?q={{ query }}&amp;page={{ page.previous_page_number }}">{% endif %}&laquo; Previous{% if page.has_previous %}</a>{% endif %}
42+
|
43+
{% if page.has_next %}<a href="?q={{ query }}&amp;page={{ page.next_page_number }}">{% endif %}Next &raquo;{% if page.has_next %}</a>{% endif %}
44+
</div>
45+
{% endif %}
46+
{% endif %}
3247
{% endblock %}

readme/views.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from django.views import generic
2-
from haystack.forms import SearchForm
2+
from haystack.forms import FacetedSearchForm
33
from haystack.query import SearchQuerySet
4-
from haystack.views import SearchView, search_view_factory
4+
from haystack.views import FacetedSearchView, search_view_factory
55
from .models import Item
66
from .forms import CreateItemForm, UpdateItemForm
77
from django.http import HttpResponseRedirect
@@ -94,7 +94,7 @@ class ItemView(RestrictItemAccessMixin, generic.DetailView):
9494
context_object_name = 'item'
9595

9696

97-
class ItemSearchView(SearchView):
97+
class ItemSearchView(FacetedSearchView):
9898
"""
9999
SearchView that passes a dynamic SearchQuerySet to the
100100
form that restricts the result to those owned by
@@ -103,12 +103,12 @@ class ItemSearchView(SearchView):
103103

104104
def build_form(self, form_kwargs=None):
105105
user_id = self.request.user.id
106-
self.searchqueryset = SearchQuerySet().filter(owner_id=user_id)
106+
self.searchqueryset = SearchQuerySet().filter(owner_id=user_id).facet('tags')
107107
return super(ItemSearchView, self).build_form(form_kwargs)
108108

109109
search = login_required(search_view_factory(
110110
view_class=ItemSearchView,
111-
form_class=SearchForm,
111+
form_class=FacetedSearchForm,
112112
))
113113

114114
# Class based views as normal view function

requirements.txt

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
Django==1.6.0
2-
South==0.8.2
3-
Whoosh==2.5.4
4-
django-crispy-forms==1.4.0
5-
django-filter==0.7
6-
django-haystack==2.1.0
7-
django-taggit==0.10
8-
djangorestframework==2.3.8
9-
psycopg2==2.5.1
10-
pycrypto==2.6
11-
lxml==3.2.4
12-
requests==2.0.0
13-
tld==0.6.1
14-
selenium==2.37.2
15-
cssselect==0.9.1
16-
https://github.com/audax/python-readability/archive/master.zip
1+
Django==1.6.0
2+
South==0.8.2
3+
Whoosh==2.5.4
4+
django-crispy-forms==1.4.0
5+
django-filter==0.7
6+
django-taggit==0.10
7+
djangorestframework==2.3.8
8+
psycopg2==2.5.1
9+
pycrypto==2.6
10+
lxml==3.2.4
11+
requests==2.0.0
12+
tld==0.6.1
13+
selenium==2.37.2
14+
cssselect==0.9.1
15+
https://github.com/audax/python-readability/archive/master.zip
16+
https://github.com/audax/django-haystack/archive/whoosh-basic-facets.zip

0 commit comments

Comments
 (0)