diff options
author | Aurélien Bompard <aurelien@bompard.org> | 2013-06-06 16:53:53 +0200 |
---|---|---|
committer | Aurélien Bompard <aurelien@bompard.org> | 2013-06-06 16:53:53 +0200 |
commit | 421bf9d5b3087afb185d776a53bc90f973955df3 (patch) | |
tree | 9bf12eeb9e1c658b52c7656e7f32c1769b0ba234 | |
parent | 44a3da0ede4a028da4896c6e5f1ff136ac380f76 (diff) | |
download | hyperkitty-421bf9d5b3087afb185d776a53bc90f973955df3.tar.gz hyperkitty-421bf9d5b3087afb185d776a53bc90f973955df3.tar.xz hyperkitty-421bf9d5b3087afb185d776a53bc90f973955df3.zip |
Add search
-rw-r--r-- | hyperkitty/archiver.py | 4 | ||||
-rw-r--r-- | hyperkitty/lib/store.py | 2 | ||||
-rw-r--r-- | hyperkitty/static/css/hyperkitty-base.css | 23 | ||||
-rw-r--r-- | hyperkitty/static/css/hyperkitty-common.css | 6 | ||||
-rw-r--r-- | hyperkitty/templates/base.html | 13 | ||||
-rw-r--r-- | hyperkitty/templates/messages/like_form.html | 2 | ||||
-rw-r--r-- | hyperkitty/templates/messages/summary_message.html | 37 | ||||
-rw-r--r-- | hyperkitty/templates/search_results.html | 103 | ||||
-rw-r--r-- | hyperkitty/templatetags/hk_generic.py | 9 | ||||
-rw-r--r-- | hyperkitty/urls.py | 5 | ||||
-rw-r--r-- | hyperkitty/views/forms.py | 18 | ||||
-rw-r--r-- | hyperkitty/views/list.py | 31 | ||||
-rw-r--r-- | hyperkitty/views/pages.py | 2 | ||||
-rw-r--r-- | hyperkitty/views/search.py | 114 | ||||
-rw-r--r-- | hyperkitty/views/thread.py | 4 |
15 files changed, 298 insertions, 75 deletions
diff --git a/hyperkitty/archiver.py b/hyperkitty/archiver.py index fa294de..a14e2ad 100644 --- a/hyperkitty/archiver.py +++ b/hyperkitty/archiver.py @@ -46,6 +46,7 @@ class Archiver(object): self.store = None self.base_url = None self.store_url = None + self.store_search_index = None self._load_conf() def _load_conf(self): @@ -72,6 +73,7 @@ class Archiver(object): raise ImportError("Could not import Django's settings from %s" % settings_path) self.store_url = settings.KITTYSTORE_URL + self.store_search_index = settings.KITTYSTORE_SEARCH_INDEX #if path_added: # sys.path.remove(settings_path) @@ -110,7 +112,7 @@ class Archiver(object): be calculated. """ if self.store is None: - self.store = get_store(self.store_url) + self.store = get_store(self.store_url, search=self.store_search_index) msg.message_id_hash = self.store.add_to_list(mlist, msg) self.store.commit() # TODO: Update karma diff --git a/hyperkitty/lib/store.py b/hyperkitty/lib/store.py index 0e09fe4..6af7344 100644 --- a/hyperkitty/lib/store.py +++ b/hyperkitty/lib/store.py @@ -52,6 +52,7 @@ class KittyStoreWSGIMiddleware(object): environ['kittystore.store'] = \ self._local.__dict__.setdefault('store', kittystore.get_store(settings.KITTYSTORE_URL, + settings.KITTYSTORE_SEARCH_INDEX, settings.KITTYSTORE_DEBUG)) try: return self._app(environ, start_response) @@ -77,6 +78,7 @@ class KittyStoreDjangoMiddleware(object): request.environ['kittystore.store'] = \ self._local.__dict__.setdefault('store', kittystore.get_store(settings.KITTYSTORE_URL, + settings.KITTYSTORE_SEARCH_INDEX, settings.KITTYSTORE_DEBUG)) def process_response(self, request, response): diff --git a/hyperkitty/static/css/hyperkitty-base.css b/hyperkitty/static/css/hyperkitty-base.css index 9d17e55..4455312 100644 --- a/hyperkitty/static/css/hyperkitty-base.css +++ b/hyperkitty/static/css/hyperkitty-base.css @@ -9,22 +9,11 @@ ul.nav.auth { margin: 0; } - -/* Search box */ - -#searchbox { - text-align:right; - padding-right: 20px; -} - -#searchbox input { - width: 250px -} - -#searchbox input::-webkit-input-placeholder { - font-style: italic; +#search-form { + float: right; + margin-right: 4em; + padding: 5px 0; } - -#searchbox input:-moz-placeholder { - font-style: italic; +#search-form form { + margin-bottom: 0; } diff --git a/hyperkitty/static/css/hyperkitty-common.css b/hyperkitty/static/css/hyperkitty-common.css index 2fe6669..daa925e 100644 --- a/hyperkitty/static/css/hyperkitty-common.css +++ b/hyperkitty/static/css/hyperkitty-common.css @@ -16,6 +16,12 @@ list-style-type: none; } + +.pagination { + margin-top: 3em; +} + + /* from Bootstrap's alert class */ .errorlist { list-style-type: none; diff --git a/hyperkitty/templates/base.html b/hyperkitty/templates/base.html index 76d7d66..8bdb1c5 100644 --- a/hyperkitty/templates/base.html +++ b/hyperkitty/templates/base.html @@ -36,6 +36,19 @@ {% endif %} </ul> + <div id="search-form" class="nav"> + <form name="search" method="get" action="{% url 'search' %}" class="form-search"> + {% if mlist %}<input type="hidden" name="list" value="{{ mlist.name }}" />{% endif %} + <div class="input-append"> + <input name="query" type="text" class="search-query" + placeholder="Search {% if mlist %}this list{% else %}all lists{% endif %}" + {% if query %}value="{{ query }}"{% endif %} + /> + <button type="submit" class="btn">Search</button> + </div> + </form> + </div> + <a class="brand" href="{% url 'root' %}">{{ app_name|title }}</a> diff --git a/hyperkitty/templates/messages/like_form.html b/hyperkitty/templates/messages/like_form.html index bf7a5ba..c499f4f 100644 --- a/hyperkitty/templates/messages/like_form.html +++ b/hyperkitty/templates/messages/like_form.html @@ -1,7 +1,7 @@ {% load url from future %} <form method="post" class="likeform" - action="{% url 'message_vote' mlist_fqdn=mlist.name message_id_hash=message_id_hash %}"> + action="{% url 'message_vote' mlist_fqdn=object.list_name message_id_hash=message_id_hash %}"> {% csrf_token %} <span class="likestatus {{ object.likestatus }}">+{{ object.likes }}/-{{ object.dislikes }}</span> {% if object.myvote == 1 %} diff --git a/hyperkitty/templates/messages/summary_message.html b/hyperkitty/templates/messages/summary_message.html new file mode 100644 index 0000000..6e14777 --- /dev/null +++ b/hyperkitty/templates/messages/summary_message.html @@ -0,0 +1,37 @@ +{% load url from future %} +{% load gravatar %} +{% load hk_generic %} +{% load storm %} + + <div class="thread"> + <div> + <span class="thread-title"> + <a name="{{message.message_id_hash}}" + href="{% url 'message_index' message_id_hash=message.message_id_hash mlist_fqdn=message.list_name %}" + >{{ message.subject|strip_subject:mlist }}</a></span> + <span class="thread-date">{{ message|get_date|timesince }}</span> + </div> + <div class="thread-content"> + <div class="gravatar"> + {% if message.sender_email %} + {% gravatar message.sender_email 40 %} + <br /> + {% endif %} + {{ message.sender_name|escapeemail }} + </div> + <div class="thread-email"> + <span class="expander collapsed"> + {{ message.content|urlizetrunc:76|escapeemail }} + </span> + </div> + </div> + <div class="thread-info"> + <div class="tags"> + </div> + {% include "messages/like_form.html" with message_id_hash=message.message_id_hash object=message %} + <a href="{% url 'message_index' message_id_hash=message.message_id_hash mlist_fqdn=message.list_name %}" + class="btn thread-show">Show message</a> + </div> + </div> + +{# vim: set noet: #} diff --git a/hyperkitty/templates/search_results.html b/hyperkitty/templates/search_results.html new file mode 100644 index 0000000..41b97ba --- /dev/null +++ b/hyperkitty/templates/search_results.html @@ -0,0 +1,103 @@ +{% extends "base.html" %} +{% load url from future %} +{% load gravatar %} +{% load hk_generic %} + + +{% block title %} +Search results for "{{ query }}"{% if mlist %} - {{ mlist.display_name|default:mlist.name|escapeemail }} {% endif %} - {{ app_name|title }} +{% endblock %} + +{% block content %} + +<div class="row-fluid"> + +{% include 'threads/month_list.html' %} + + <div id="thread-list" class="span8"> + + <div class="thread-list-header page-header"> + {% if mlist %} + <h1>{{ mlist.display_name|default:mlist.name|escapeemail }} + <small>Search results for query "{{ query }}"</small> + </h1> + {% else %} + <h1>Search results + <small>for query "{{ query }}"</small> + </h1> + {% endif %} + <ul class="thread-list-info"> + {% if mlist.display_name %} + <li class="list-address"> + {{ mlist.name|escapeemail }} + </li> + {% endif %} + <li class="discussion"> + {{ total }} messages + </li> + </ul> + </div> + + {% for message in messages %} + {% include "messages/summary_message.html" %} + {% empty %} + <p>Sorry no email could be found for this query.</p> + {% endfor %} + + <div class="pagination pagination-centered"> + <ul> + {% if messages.has_previous %} + <li> + <a href="?{% add_to_query_string page=messages.previous_page_number %}"> + {% else %} + <li class="disabled"> + <a href="#"> + {% endif %} + ← Previous</a> + </li> + + {% for pagenum in page_range %} + <li{% if pagenum == messages.number %} class="active"{% endif %}> + <a href="?{% add_to_query_string page=pagenum %}">{{ pagenum }}</a> + </li> + {% endfor %} + + {% if messages.has_next %} + <li> + <a href="?{% add_to_query_string page=messages.next_page_number %}"> + {% else %} + <li class="disabled"> + <a href="#"> + {% endif %} + Next →</a> + </li> + </ul> + </div> + + </div> + +</div> + +{% endblock %} + +{% block additionaljs %} +<script> +$(document).ready(function() { + $('span.expander').expander({ + slicePoint: 500, + userCollapseText : '\n[View Less]', + expandText : '\n[View More]', + beforeExpand: function() { + $(this).removeClass("collapsed"); + $(this).addClass("expanded"); + }, + onCollapse: function() { + $(this).removeClass("expanded"); + $(this).addClass("collapsed"); + } + }); +}); +</script> +{% endblock %} + +{# vim: set noet: #} diff --git a/hyperkitty/templatetags/hk_generic.py b/hyperkitty/templatetags/hk_generic.py index 2796528..d233309 100644 --- a/hyperkitty/templatetags/hk_generic.py +++ b/hyperkitty/templatetags/hk_generic.py @@ -183,6 +183,7 @@ def multiply(num1, num2): return num1 * num2 +@register.assignment_tag(takes_context=True) def is_message_new(context, refdate): user = context["user"] last_view = context.get("last_view") @@ -190,4 +191,10 @@ def is_message_new(context, refdate): return (user.is_authenticated() and (not last_view or refdate > last_view) ) -register.assignment_tag(takes_context=True)(is_message_new) + + +@register.simple_tag(takes_context=True) +def add_to_query_string(context, **kwargs): + qs = context["request"].GET.copy() + qs.update(kwargs) + return qs.urlencode() diff --git a/hyperkitty/urls.py b/hyperkitty/urls.py index a640ae2..b75632b 100644 --- a/hyperkitty/urls.py +++ b/hyperkitty/urls.py @@ -82,9 +82,10 @@ urlpatterns = patterns('hyperkitty.views', 'thread.favorite', name='favorite'), - # Search Tag + # Search + url(r'^search$', 'search.search', name='search'), url(r'^list/(?P<mlist_fqdn>[^/@]+@[^/@]+)/tag/(?P<tag>.*)/$', - 'list.search_tag', name='search_tag'), + 'search.search_tag', name='search_tag'), # REST API diff --git a/hyperkitty/views/forms.py b/hyperkitty/views/forms.py index 6f6c294..938deba 100644 --- a/hyperkitty/views/forms.py +++ b/hyperkitty/views/forms.py @@ -98,24 +98,6 @@ class AddTagForm(forms.Form): -class SearchForm(forms.Form): - target = forms.CharField(label='', help_text=None, - widget=forms.Select( - choices=(('Subject', 'Subject'), - ('Content', 'Content'), - ('SubjectContent', 'Subject & Content'), - ('From', 'From')) - ) - ) - - keyword = forms.CharField(max_length=100,label='', help_text=None, - widget=forms.TextInput( - attrs={'placeholder': 'Search this list.'} - ) - ) - - - class ReplyForm(forms.Form): newthread = forms.BooleanField(label="", required=False) subject = forms.CharField(label="", required=False, diff --git a/hyperkitty/views/list.py b/hyperkitty/views/list.py index a58bdbb..523aff4 100644 --- a/hyperkitty/views/list.py +++ b/hyperkitty/views/list.py @@ -35,8 +35,7 @@ from django.http import Http404 from hyperkitty.models import Tag, Favorite, LastView from hyperkitty.lib import get_months, get_store, get_display_dates, daterange from hyperkitty.lib import FLASH_MESSAGES -from hyperkitty.lib.voting import get_votes -from forms import SearchForm +from hyperkitty.lib.voting import get_votes, set_message_votes if settings.USE_MOCKUPS: @@ -76,7 +75,6 @@ def _thread_list(request, mlist, threads, template_name='thread_list.html', extr if mlist is None: raise Http404("No archived mailing-list by that name.") store = get_store(request) - search_form = SearchForm(auto_id=False) participants = set() for thread in threads: @@ -165,7 +163,6 @@ def _thread_list(request, mlist, threads, template_name='thread_list.html', extr context = { 'mlist' : mlist, 'current_page': page_num, - 'search_form': search_form, 'threads': threads, 'participants': len(participants), 'months_list': get_months(store, mlist.name), @@ -178,7 +175,6 @@ def _thread_list(request, mlist, threads, template_name='thread_list.html', extr def overview(request, mlist_fqdn=None): if not mlist_fqdn: return redirect('/') - search_form = SearchForm(auto_id=False) # Get stats for last 30 days today = datetime.datetime.utcnow() @@ -250,7 +246,6 @@ def overview(request, mlist_fqdn=None): context = { 'mlist' : mlist, - 'search_form': search_form, 'top_threads': top_threads[:5], 'most_active_threads': active_threads[:5], 'top_author': authors, @@ -261,27 +256,3 @@ def overview(request, mlist_fqdn=None): 'archives_baseurl': archives_baseurl, } return render(request, "recent_activities.html", context) - - -def search_tag(request, mlist_fqdn, tag): - '''Returns threads having a particular tag''' - store = get_store(request) - mlist = store.get_list(mlist_fqdn) - - try: - tags = Tag.objects.filter(tag=tag) - except Tag.DoesNotExist: - tags = {} - - threads = [] - for t in tags: - thread = store.get_thread(mlist_fqdn, t.threadid) - if thread is not None: - threads.append(thread) - - extra_context = { - "tag": tag, - "list_title": "Search results for tag \"%s\"" % tag, - "no_results_text": "for this tag", - } - return _thread_list(request, mlist, threads, extra_context=extra_context) diff --git a/hyperkitty/views/pages.py b/hyperkitty/views/pages.py index 7148322..fa529c0 100644 --- a/hyperkitty/views/pages.py +++ b/hyperkitty/views/pages.py @@ -31,7 +31,6 @@ from django.http import HttpResponse from mailmanclient import Client from hyperkitty.lib import get_store -from forms import SearchForm def index(request): @@ -40,7 +39,6 @@ def index(request): context = { 'all_lists': lists, - 'search_form': SearchForm(auto_id=False), } return render(request, "index.html", context) diff --git a/hyperkitty/views/search.py b/hyperkitty/views/search.py new file mode 100644 index 0000000..1d2f3bf --- /dev/null +++ b/hyperkitty/views/search.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- +# Copyright (C) 1998-2012 by the Free Software Foundation, Inc. +# +# This file is part of HyperKitty. +# +# HyperKitty is free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License as published by the Free +# Software Foundation, either version 3 of the License, or (at your option) +# any later version. +# +# HyperKitty is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# HyperKitty. If not, see <http://www.gnu.org/licenses/>. +# +# Author: Aurelien Bompard <abompard@fedoraproject.org> +# + + +from django.shortcuts import render +from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger, Page + +from hyperkitty.models import Tag +from hyperkitty.lib import get_store +from hyperkitty.lib.voting import get_votes, set_message_votes + +from .list import _thread_list + + +class SearchPaginator(Paginator): + """ + A paginator which does not split the object_list into pages, because Whoosh + already handles that + """ + def __init__(self, object_list, per_page, total): + super(SearchPaginator, self).__init__(object_list, per_page) + self._count = total + + def page(self, number): + "Returns the object list without paginating""" + return Page(self.object_list, number, self) + + +def search_tag(request, mlist_fqdn, tag): + '''Returns threads having a particular tag''' + store = get_store(request) + mlist = store.get_list(mlist_fqdn) + + try: + tags = Tag.objects.filter(tag=tag) + except Tag.DoesNotExist: + tags = {} + + threads = [] + for t in tags: + thread = store.get_thread(mlist_fqdn, t.threadid) + if thread is not None: + threads.append(thread) + + extra_context = { + "tag": tag, + "list_title": "Search results for tag \"%s\"" % tag, + "no_results_text": "for this tag", + } + return _thread_list(request, mlist, threads, extra_context=extra_context) + + +def search(request, page=1): + """ Returns messages corresponding to a query """ + store = get_store(request) + query = request.GET.get("query") + mlist_fqdn = request.GET.get("list") + try: + page_num = int(request.GET.get('page')) + except ValueError: + page_num = 1 + results_per_page = 10 + query_result = store.search(query, mlist_fqdn, page_num, results_per_page) + total = query_result["total"] + messages = query_result["results"] + for message in messages: + set_message_votes(message, request.user) + if mlist_fqdn is None: + mlist = None + else: + mlist = store.get_list(mlist_fqdn) + if mlist is None: + raise Http404("No archived mailing-list by that name.") + + paginator = SearchPaginator(messages, 10, total) + try: + messages = paginator.page(page_num) + except PageNotAnInteger: + # If page is not an integer, deliver first page. + messages = paginator.page(1) + except EmptyPage: + # If page is out of range (e.g. 9999), deliver last page of results. + messages = paginator.page(paginator.num_pages) + + context = { + 'mlist' : mlist, + "query": query, + 'current_page': page_num, + 'messages': messages, + 'total': total, + 'page_range': [ p+1 for p in range(paginator.num_pages) ], + } + return render(request, "search_results.html", context) + + + diff --git a/hyperkitty/views/thread.py b/hyperkitty/views/thread.py index 1e44714..fd52831 100644 --- a/hyperkitty/views/thread.py +++ b/hyperkitty/views/thread.py @@ -34,7 +34,7 @@ from django.utils.timezone import utc import robot_detection from hyperkitty.models import Tag, Favorite, LastView -from forms import SearchForm, AddTagForm, ReplyForm +from forms import AddTagForm, ReplyForm from hyperkitty.lib import get_months, get_store, stripped_subject from hyperkitty.lib.voting import set_message_votes @@ -72,7 +72,6 @@ def _get_thread_replies(request, thread, offset=1, limit=None): def thread_index(request, mlist_fqdn, threadid, month=None, year=None): ''' Displays all the email for a given thread identifier ''' - search_form = SearchForm(auto_id=False) store = get_store(request) thread = store.get_thread(mlist_fqdn, threadid) if not thread: @@ -140,7 +139,6 @@ def thread_index(request, mlist_fqdn, threadid, month=None, year=None): 'threadid': threadid, 'subject': subject, 'tags': tags, - 'search_form': search_form, 'addtag_form': tag_form, 'month': thread.date_active, 'first_mail': thread.starting_email, |