summaryrefslogtreecommitdiffstats
path: root/hyperkitty
diff options
context:
space:
mode:
authorAurélien Bompard <aurelien@bompard.org>2013-06-06 16:53:53 +0200
committerAurélien Bompard <aurelien@bompard.org>2013-06-06 16:53:53 +0200
commit421bf9d5b3087afb185d776a53bc90f973955df3 (patch)
tree9bf12eeb9e1c658b52c7656e7f32c1769b0ba234 /hyperkitty
parent44a3da0ede4a028da4896c6e5f1ff136ac380f76 (diff)
downloadhyperkitty-421bf9d5b3087afb185d776a53bc90f973955df3.tar.gz
hyperkitty-421bf9d5b3087afb185d776a53bc90f973955df3.tar.xz
hyperkitty-421bf9d5b3087afb185d776a53bc90f973955df3.zip
Add search
Diffstat (limited to 'hyperkitty')
-rw-r--r--hyperkitty/archiver.py4
-rw-r--r--hyperkitty/lib/store.py2
-rw-r--r--hyperkitty/static/css/hyperkitty-base.css23
-rw-r--r--hyperkitty/static/css/hyperkitty-common.css6
-rw-r--r--hyperkitty/templates/base.html13
-rw-r--r--hyperkitty/templates/messages/like_form.html2
-rw-r--r--hyperkitty/templates/messages/summary_message.html37
-rw-r--r--hyperkitty/templates/search_results.html103
-rw-r--r--hyperkitty/templatetags/hk_generic.py9
-rw-r--r--hyperkitty/urls.py5
-rw-r--r--hyperkitty/views/forms.py18
-rw-r--r--hyperkitty/views/list.py31
-rw-r--r--hyperkitty/views/pages.py2
-rw-r--r--hyperkitty/views/search.py114
-rw-r--r--hyperkitty/views/thread.py4
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 %}
+ &larr; 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 &rarr;</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,