Merge branch 'master' into production
[wolnelektury.git] / apps / search / views.py
index e73b92a..ec8275b 100644 (file)
@@ -1,5 +1,6 @@
 # -*- coding: utf-8 -*-
 
 # -*- coding: utf-8 -*-
 
+from django.conf import settings
 from django.shortcuts import render_to_response, get_object_or_404
 from django.template import RequestContext
 from django.contrib.auth.decorators import login_required
 from django.shortcuts import render_to_response, get_object_or_404
 from django.template import RequestContext
 from django.contrib.auth.decorators import login_required
@@ -7,30 +8,43 @@ from django.views.decorators import cache
 from django.http import HttpResponse, HttpResponseRedirect, Http404, HttpResponsePermanentRedirect
 from django.utils.translation import ugettext as _
 
 from django.http import HttpResponse, HttpResponseRedirect, Http404, HttpResponsePermanentRedirect
 from django.utils.translation import ugettext as _
 
-from catalogue.utils import get_random_hash
-from catalogue.models import Book, Tag, Fragment, TAG_CATEGORIES
-from catalogue.fields import dumps
+from catalogue.utils import split_tags
+from catalogue.models import Book, Tag, Fragment
+from pdcounter.models import Author as PDCounterAuthor, BookStub as PDCounterBook
 from catalogue.views import JSONResponse
 from catalogue.views import JSONResponse
-from catalogue import forms
 from search import Search, JVM, SearchResult
 from lucene import StringReader
 from suggest.forms import PublishingSuggestForm
 from search import Search, JVM, SearchResult
 from lucene import StringReader
 from suggest.forms import PublishingSuggestForm
+from time import sleep
+import re
+#import enchant
+import json
 
 
-import enchant
+#dictionary = enchant.Dict('en_US')
 
 
-dictionary = enchant.Dict('pl_PL')
+
+def match_word_re(word):
+    if 'sqlite' in settings.DATABASES['default']['ENGINE']:
+        return r"\b%s\b" % word
+    elif 'mysql' in settings.DATABASES['default']['ENGINE']:
+        return "[[:<:]]%s[[:>:]]" % word
 
 
 def did_you_mean(query, tokens):
     change = {}
 
 
 def did_you_mean(query, tokens):
     change = {}
-    # sprawdzić, czy słowo nie jest aby autorem - proste szukanie termu w author!
     for t in tokens:
     for t in tokens:
-        print("%s ok? %s, sug: %s" %(t, dictionary.check(t), dictionary.suggest(t)))
-        if not dictionary.check(t):
-            try:
-                change[t] = dictionary.suggest(t)[0]
-            except IndexError:
-                pass
+        authors = Tag.objects.filter(category='author', name__iregex=match_word_re(t))
+        if len(authors) > 0:
+            continue
+
+        if False:
+            if not dictionary.check(t):
+                try:
+                    change_to = dictionary.suggest(t)[0].lower()
+                    if change_to != t.lower():
+                        change[t] = change_to
+                except IndexError:
+                    pass
 
     if change == {}:
         return None
 
     if change == {}:
         return None
@@ -41,14 +55,30 @@ def did_you_mean(query, tokens):
     return query
 
 
     return query
 
 
+JVM.attachCurrentThread()
+_search = None
+
+
+def get_search():
+    global _search
+
+    while _search is False:
+        sleep(1)
+
+    if _search is None:
+        _search = False
+        _search = Search()
+    return _search
+
+
 def hint(request):
     prefix = request.GET.get('term', '')
     if len(prefix) < 2:
         return JSONResponse([])
     JVM.attachCurrentThread()
 def hint(request):
     prefix = request.GET.get('term', '')
     if len(prefix) < 2:
         return JSONResponse([])
     JVM.attachCurrentThread()
-    s = Search()
 
 
-    hint = s.hint()
+    search = get_search()
+    hint = search.hint()
     try:
         tags = request.GET.get('tags', '')
         hint.tags(Tag.get_tag_list(tags))
     try:
         tags = request.GET.get('tags', '')
         hint.tags(Tag.get_tag_list(tags))
@@ -60,108 +90,165 @@ def hint(request):
     # jezeli tagi dot tylko ksiazki, to wazne zeby te nowe byly w tej samej ksiazce
     # jesli zas dotycza themes, to wazne, zeby byly w tym samym fragmencie.
 
     # jezeli tagi dot tylko ksiazki, to wazne zeby te nowe byly w tej samej ksiazce
     # jesli zas dotycza themes, to wazne, zeby byly w tym samym fragmencie.
 
-    
-    tags = s.hint_tags(prefix)
-    books = s.hint_books(prefix)
-
-    # TODO DODAC TU HINTY
-
-    return JSONResponse(
-        [{'label': t.name,
-          'category': _(t.category),
-          'id': t.id,
-          'url': t.get_absolute_url()}
-          for t in tags] + \
-          [{'label': b.title,
-            'category': _('book'),
-            'id': b.id,
-            'url': b.get_absolute_url()}
-            for b in books])
-
-
-def foo(s, q, tag_list=None):
-    hint = s.hint()
-    try:
-        tag_list = Tag.get_tag_list(tag_list)
-        hint.tags(tag_list)
-    except:
-        tag_list = None
+    tags = search.hint_tags(prefix, pdcounter=True)
+    books = search.hint_books(prefix)
 
 
-    q = StringReader(q)
-    return (q, hint)
+    
+    def is_dupe(tag):
+        if isinstance(tag, PDCounterAuthor):
+            if filter(lambda t: t.slug == tag.slug and t != tag, tags):
+                return True
+        elif isinstance(tag, PDCounterBook):
+            if filter(lambda b: b.slug == tag.slug, tags):
+                return True
+        return False
+
+    tags = filter(lambda t: not is_dupe(t), tags)
+
+    def category_name(c):
+        if c.startswith('pd_'):
+            c = c[len('pd_'):]
+        return _(c)
+
+    callback = request.GET.get('callback', None)
+    data = [{'label': t.name,
+              'category': category_name(t.category),
+              'id': t.id,
+              'url': t.get_absolute_url()}
+              for t in tags] + \
+              [{'label': b.title,
+                'category': _('book'),
+                'id': b.id,
+                'url': b.get_absolute_url()}
+                for b in books]
+    if callback:
+        return HttpResponse("%s(%s);" % (callback, json.dumps(data)),
+                            content_type="application/json; charset=utf-8")
+    else:
+        return JSONResponse(data)
+            
 
 
 def main(request):
     results = {}
     JVM.attachCurrentThread()  # where to put this?
 
 
 def main(request):
     results = {}
     JVM.attachCurrentThread()  # where to put this?
-    srch = Search()
 
     results = None
     query = None
 
     results = None
     query = None
-    fuzzy = False
+    fuzzy = False #0.8
+
+    query = request.GET.get('q','')
+    # book_id = request.GET.get('book', None)
+    # book = None
+    # if book_id is not None:
+    #     book = get_object_or_404(Book, id=book_id)
+
+    # hint = search.hint()
+    # try:
+    #     tag_list = Tag.get_tag_list(tags)
+    # except:
+    #     tag_list = []
+
+    if len(query) < 2:
+        return render_to_response('catalogue/search_too_short.html', {'prefix': query},
+                                  context_instance=RequestContext(request))
+
+    search = get_search()
+    # hint.tags(tag_list)
+    # if book:
+    #     hint.books(book)
+    tags = search.hint_tags(query, pdcounter=True, prefix=False, fuzzy=fuzzy)
+    tags = split_tags(tags)
+
+    toks = StringReader(query)
+    tokens_cache = {}
+
+    author_results = search.search_phrase(toks, 'authors', fuzzy=fuzzy, tokens_cache=tokens_cache)
+    title_results = search.search_phrase(toks, 'title', fuzzy=fuzzy, tokens_cache=tokens_cache)
+
+    # Boost main author/title results with mixed search, and save some of its results for end of list.
+    # boost author, title results
+    author_title_mixed = search.search_some(toks, ['authors', 'title', 'tags'], fuzzy=fuzzy, tokens_cache=tokens_cache)
+    author_title_rest = []
+    for b in author_title_mixed:
+        bks = filter(lambda ba: ba.book_id == b.book_id, author_results + title_results)
+        for b2 in bks:
+            b2.boost *= 1.1
+        if bks is []:
+            author_title_rest.append(b)
+
+    # Do a phrase search but a term search as well - this can give us better snippets then search_everywhere,
+    # Because the query is using only one field.
+    text_phrase = SearchResult.aggregate(
+        search.search_phrase(toks, 'content', fuzzy=fuzzy, tokens_cache=tokens_cache, snippets=True, book=False, slop=4),
+        search.search_some(toks, ['content'], tokens_cache=tokens_cache, snippets=True, book=False))
+
+    everywhere = search.search_everywhere(toks, fuzzy=fuzzy, tokens_cache=tokens_cache)
+
+    def already_found(results):
+        def f(e):
+            for r in results:
+                if e.book_id == r.book_id:
+                    e.boost = 0.9
+                    results.append(e)
+                    return True
+            return False
+        return f
+    f = already_found(author_results + title_results + text_phrase)
+    everywhere = filter(lambda x: not f(x), everywhere)
+
+    author_results = SearchResult.aggregate(author_results)
+    title_results = SearchResult.aggregate(title_results)
+
+    everywhere = SearchResult.aggregate(everywhere, author_title_rest)
+
+    for res in [author_results, title_results, text_phrase, everywhere]:
+        res.sort(reverse=True)
+        for r in res:
+            for h in r.hits:
+                h['snippets'] = map(lambda s:
+                                    re.subn(r"(^[ \t\n]+|[ \t\n]+$)", u"",
+                                            re.subn(r"[ \t\n]*\n[ \t\n]*", u"\n", s)[0])[0], h['snippets'])
 
 
-    if 'q' in request.GET:
-        tags = request.GET.get('tags', '')
-        query = request.GET['q']
-        book_id = request.GET.get('book', None)
-        book = None
-        if book_id is not None:
-            book = get_object_or_404(Book, id=book_id)
+    suggestion = did_you_mean(query, search.get_tokens(toks, field="SIMPLE"))
 
 
-        hint = srch.hint()
+    def ensure_exists(r):
         try:
         try:
-            tag_list = Tag.get_tag_list(tags)
-        except:
-            tag_list = []
-
-        if len(query) < 2:
-            return render_to_response('catalogue/search_too_short.html', {'tags': tag_list, 'prefix': query},
-                                      context_instance=RequestContext(request))
-
-        hint.tags(tag_list)
-        if book:
-            hint.books(book)
-
-        toks = StringReader(query)
-        fuzzy = 'fuzzy' in request.GET
-        if fuzzy:
-            fuzzy = 0.7
-
-        results = SearchResult.aggregate(srch.search_perfect_book(toks, fuzzy=fuzzy, hint=hint),
-                                         srch.search_book(toks, fuzzy=fuzzy, hint=hint),
-                                         srch.search_perfect_parts(toks, fuzzy=fuzzy, hint=hint),
-                                         srch.search_everywhere(toks, fuzzy=fuzzy, hint=hint))
-
-        for r in results:
-            r.process_hits()
-
-        results.sort(reverse=True)
-
-        for r in results:
-            print "-----"
-            for h in r.hits:
-                print "- %s" % h
-
-        if len(results) == 1:
-            if len(results[0].hits) == 0:
-                return HttpResponseRedirect(results[0].book.get_absolute_url())
-            elif len(results[0].hits) == 1 and results[0].hits[0] is not None:
-                frag = Fragment.objects.get(anchor=results[0].hits[0])
-                return HttpResponseRedirect(frag.get_absolute_url())
-        elif len(results) == 0:
-            form = PublishingSuggestForm(initial={"books": query + ", "})
-            return render_to_response('catalogue/search_no_hits.html',
-                                      {'tags': tag_list, 'prefix': query, "pubsuggest_form": form,
-                                       'form': forms.SearchForm()},
-                context_instance=RequestContext(request))
-
-        return render_to_response('catalogue/search_multiple_hits.html',
-                                  {'tags': tag_list, 'prefix': query,
-                                   'results': results, 'from': forms.SearchForm()},
+            return r.book
+        except Book.DoesNotExist:
+            return False
+
+    author_results = filter(ensure_exists, author_results)
+    title_results = filter(ensure_exists, title_results)
+    text_phrase = filter(ensure_exists, text_phrase)
+    everywhere = filter(ensure_exists, everywhere)
+
+    results = author_results + title_results + text_phrase + everywhere
+    # ensure books do exists & sort them
+    results.sort(reverse=True)
+
+    if len(results) == 1:
+        fragment_hits = filter(lambda h: 'fragment' in h, results[0].hits)
+        if len(fragment_hits) == 1:
+            #anchor = fragment_hits[0]['fragment']
+            #frag = Fragment.objects.get(anchor=anchor)
+            return HttpResponseRedirect(fragment_hits[0]['fragment'].get_absolute_url())
+        return HttpResponseRedirect(results[0].book.get_absolute_url())
+    elif len(results) == 0:
+        form = PublishingSuggestForm(initial={"books": query + ", "})
+        return render_to_response('catalogue/search_no_hits.html',
+                                  {'tags': tags,
+                                   'prefix': query,
+                                   "form": form,
+                                   'did_you_mean': suggestion},
             context_instance=RequestContext(request))
 
             context_instance=RequestContext(request))
 
-    # return render_to_response('newsearch/search.html', {'results': results,
-    #                                                     'did_you_mean': (query is not None) and
-    #                                                     did_you_mean(query, srch.get_tokens(query, field='SIMPLE')),
-    #                                                     'fuzzy': fuzzy},
-    #                           context_instance=RequestContext(request))
+    return render_to_response('catalogue/search_multiple_hits.html',
+                              {'tags': tags,
+                               'prefix': query,
+                               'results': { 'author': author_results,
+                                            'title': title_results,
+                                            'content': text_phrase,
+                                            'other': everywhere},
+                               'did_you_mean': suggestion},
+        context_instance=RequestContext(request))