Permalink
Browse files

Faceted search UI for https://simonwillison.net/search/?q=london

  • Loading branch information...
simonw committed Oct 2, 2017
1 parent 776a562 commit b8484c50b6c36f3751b3139934898a752bea2cba
Showing with 105 additions and 9 deletions.
  1. +16 −0 blog/templatetags/blog_tags.py
  2. +44 −9 blog/views.py
  3. +11 −0 static/css/all.css
  4. +34 −0 templates/search.html
@@ -49,3 +49,19 @@ def page_href(context, page):
del query_dict['page']
query_dict['page'] = page
return '?' + query_dict.urlencode()
@register.simple_tag(takes_context=True)
def add_qsarg(context, name, value):
query_dict = context['request'].GET.copy()

This comment has been minimized.

Show comment
Hide comment
@r4vi

r4vi Oct 2, 2017

maybe you should prevent adding the same tag twice or more?
image

@r4vi

r4vi Oct 2, 2017

maybe you should prevent adding the same tag twice or more?
image

This comment has been minimized.

Show comment
Hide comment
@simonw

simonw Oct 3, 2017

Owner

Thanks! 3f5ca05

@simonw
query_dict.appendlist(name, value)
return '?' + query_dict.urlencode()
@register.simple_tag(takes_context=True)
def remove_qsarg(context, name, value):
query_dict = context['request'].GET.copy()
query_dict.setlist(name, [
v for v in query_dict.getlist(name) if v != value
])
return '?' + query_dict.urlencode()
View
@@ -394,13 +394,13 @@ def search(request):
def search_results(request, q):
start = time.time()
query = SearchQuery(q)
rank_annotation = SearchRank(models.F('search_document'), query)
filter_kwargs = {
'search_document': query
}
tags = request.GET.getlist('tag')
exclude_tags = request.GET.getlist('exclude.tag')
selected_tags = request.GET.getlist('tag')
excluded_tags = request.GET.getlist('exclude.tag')
selected_type = request.GET.get('type', '')
values = ('pk', 'type', 'created', 'rank')
@@ -411,26 +411,56 @@ def make_queryset(klass, type_name):
)
if q:
qs = qs.filter(search_document=query)
for tag in tags:
for tag in selected_tags:
qs = qs.filter(tags__tag=tag)
for exclude_tag in exclude_tags:
for exclude_tag in excluded_tags:
qs = qs.exclude(tags__tag=exclude_tag)
return qs.values(*values).order_by()
return qs.order_by()
# Start with a .none() queryset just so we can union stuff onto it
qs = Entry.objects.annotate(
rank=rank_annotation,
type=models.Value('empty', output_field=models.CharField())
).values(*values).none()
type_counts_raw = {}
tag_counts_raw = {}
for klass, type_name in (
(Entry, 'entry'),
(Blogmark, 'blogmark'),
(Quotation, 'quotation'),
):
qs = qs.union(make_queryset(klass, type_name))
if selected_type and selected_type != type_name:
continue
klass_qs = make_queryset(klass, type_name)
type_count = klass_qs.count()
if type_count:
type_counts_raw[type_name] = type_count
for tag, count in Tag.objects.filter(**{
'%s__in' % type_name: klass_qs
}).annotate(
n=models.Count('tag')
).values_list('tag', 'n'):
tag_counts_raw[tag] = tag_counts_raw.get(tag, 0) + count
qs = qs.union(klass_qs.values(*values))
qs = qs.order_by('-rank')
type_counts = sorted(
[
{'type': type_name, 'n': value}
for type_name, value in type_counts_raw.items()
],
key=lambda t: t['n'], reverse=True
)
tag_counts = sorted(
[
{'tag': tag, 'n': value}
for tag, value in tag_counts_raw.items()
],
key=lambda t: t['n'], reverse=True
)[:40]
paginator = Paginator(qs, 30)
page_number = request.GET.get('page') or '1'
try:
@@ -454,6 +484,11 @@ def make_queryset(klass, type_name):
'total': paginator.count,
'page': page,
'duration': end - start,
'type_counts': type_counts,
'tag_counts': tag_counts,
'selected_tags': selected_tags,
'excluded_tags': excluded_tags,
'selected_type': selected_type,
})
View
@@ -722,6 +722,17 @@ div.help p {
background-color: #733b96;
color: white;
}
div p.search-selections {
margin-top: 0.5em;
font-size: 0.8em;
}
a.selected-tag {
border: 1px solid #666;
text-decoration: none;
padding: 2px 5px;
background-color: rgba(115, 60, 150, 0.28);
color: black;
}
@media (max-width: 600px) {
div#secondary {
View
@@ -12,6 +12,19 @@ <h2>Search{% if q %} for “{{ q }}”{% endif %}</h2>
<input type="search" class="search-input" name="q" value="{{ q }}" style="width: 80%">
<input type="submit" class="search-submit" value="Search">
</form>
{% if selected_tags or selected_type %}
<p class="search-selections">
Filters:
{% if selected_type %}
<a class="selected-tag" href="{% remove_qsarg "type" selected_type %}">Type: {{ selected_type }} <strong>&#x00D7;</strong></a>
{% endif %}
{% for selected_tag in selected_tags %}
<a class="selected-tag" href="{% remove_qsarg "tag" selected_tag %}">{{ selected_tag }} <strong>&#x00D7;</strong></a>
{% endfor %}
</p>
{% endif %}
<br>
{% if total %}
@@ -21,3 +34,24 @@ <h2>Search{% if q %} for “{{ q }}”{% endif %}</h2>
{% endif %}
{% endblock %}
{% block secondary %}
<div class="metabox">
{% if type_counts %}
<h3>Types</h3>
<ul>
{% for t in type_counts %}
<li><a href="{% add_qsarg "type" t.type %}">{{ t.type }}</a> {{ t.n }}</a></li>
{% endfor %}
</ul>
{% endif %}
{% if tag_counts %}
<h3>Tags</h3>
<ul>
{% for t in tag_counts %}
<li><a href="{% add_qsarg "tag" t.tag %}">{{ t.tag }}</a> {{ t.n }}</a></li>
{% endfor %}
</ul>
{% endif %}
</div>
{% endblock %}

0 comments on commit b8484c5

Please sign in to comment.