Browse Source

Abstracted search query to extra class, exposed links to advanced search.

Rafał Pitoń 11 years ago
parent
commit
a6ea035b46

+ 3 - 3
misago/acl/permissions/privatethreads.py

@@ -39,13 +39,13 @@ def make_form(request, role, form):
 
 
 
 
 class PrivateThreadsACL(BaseACL):
 class PrivateThreadsACL(BaseACL):
+    def can_participate(self):
+        return self.acl['can_use_private_threads']
+
     def can_start(self):
     def can_start(self):
         return (self.acl['can_use_private_threads'] and
         return (self.acl['can_use_private_threads'] and
                 self.acl['can_start_private_threads'])
                 self.acl['can_start_private_threads'])
 
 
-    def can_participate(self):
-        return self.acl['can_use_private_threads']
-
     def can_invite_ignoring(self):
     def can_invite_ignoring(self):
         return self.acl['can_invite_ignoring']
         return self.acl['can_invite_ignoring']
 
 

+ 35 - 0
misago/apps/search/searchquery.py

@@ -0,0 +1,35 @@
+from haystack.inputs import AutoQuery
+from haystack.query import SearchQuerySet, RelatedSearchQuerySet
+from misago.acl.exceptions import ACLError403, ACLError404
+from misago.models import Forum, Thread, Post, User
+
+class MisagoSearchQuerySet(object):
+    def __init__(self, user, acl):
+        self.user = user
+        self.acl = acl
+
+    def search_in(self, target):
+        self.target = target
+
+        try:
+            self.allow_forum_search(target)
+        except AttributeError:
+            self.allow_thread_search(target)
+
+    def allow_forum_search(self, target):
+        raise Exception(dir(target))
+
+    def allow_thread_search(self, target):
+        pass
+
+    @property
+    def query(self):
+        try:
+            return self._searchquery
+        except AttributeError:
+            pass
+
+        sqs = SearchQuerySet()
+
+        self._searchquery = sqs
+        return self._searchquery

+ 31 - 51
misago/apps/search/views.py

@@ -10,7 +10,7 @@ from misago.acl.exceptions import ACLError403, ACLError404
 from misago.conf import settings
 from misago.conf import settings
 from misago.decorators import block_crawlers
 from misago.decorators import block_crawlers
 from misago.models import Forum, Thread, Post, User
 from misago.models import Forum, Thread, Post, User
-from misago.search import SearchException
+from misago.search import SearchException, MisagoSearchQuerySet
 from misago.shortcuts import render_to_response
 from misago.shortcuts import render_to_response
 from misago.utils.pagination import make_pagination
 from misago.utils.pagination import make_pagination
 from misago.apps.errors import error403, error404
 from misago.apps.errors import error403, error404
@@ -24,57 +24,29 @@ class ViewBase(object):
         pass
         pass
 
 
     def make_query(self, search_query):
     def make_query(self, search_query):
-        sqs = SearchQuerySet()
-        if self.request.POST.get('search_thread_titles'):
-            sqs = sqs.filter(thread_name=AutoQuery(search_query), start_post=1)
-        else:
-            sqs = sqs.auto_query(search_query)
-
-        if self.request.POST.get('search_in') == 'private_threads':
-            if not (self.request.acl.private_threads.can_participate()
-                    and settings.enable_private_threads):
-                raise ACLError404()
-            sqs = sqs.filter(thread__in=[t.pk for t in self.request.user.private_thread_set.all()])
-        elif self.request.POST.get('search_in') == 'reports':
-            if not self.request.acl.reports.can_handle():
-                raise ACLError404()
-            sqs = sqs.filter(forum=Forum.objects.special_pk('reports'))
-        elif self.request.POST.get('search_in') == 'thread':
-            try:
+        try:
+            sqs = MisagoSearchQuerySet(self.request.user, self.request.acl)
+
+            if self.request.POST.get('search_in') == 'thread':
                 thread_id = int(self.request.POST.get('search_thread'))
                 thread_id = int(self.request.POST.get('search_thread'))
-                thread_clean = Thread.objects.get(id=thread_id)
-
-                readable_forums = Forum.objects.readable_forums(self.request.acl, True)
-                starter_readable_forums = Forum.objects.starter_readable_forums(self.request.acl)
-                if not thread_clean.forum_id in readable_forums:
-                    if not (thread_clean.forum_id in starter_readable_forums
-                            and thread_clean.start_poster_id
-                            and thread_clean.start_poster_id == self.request.user.id):
-                        raise ACLError404()
-                self.thread_clean = thread_clean
-                sqs = sqs.filter(thread=thread_clean.pk)
-            except (TypeError, Thread.DoesNotExist):
-                raise ACLError404()
-        else:
-            readable_forums = Forum.objects.readable_forums(self.request.acl)
-            starter_readable_forums = Forum.objects.starter_readable_forums(self.request.acl)
-            if not readable_forums and not readable_forums:
-                return error403(request, _("You cannot search any forums."))
-
-            if readable_forums and starter_readable_forums:
-                sqs = sqs.filter(forum__in=starter_readable_forums, thread_starter=self.request.user.id)
-                sqs = sqs.filter_or(forum__in=readable_forums)
-            elif starter_readable_forums:
-                if not self.request.user.is_authenticated():
-                    return error403(request, _("You cannot search any forums."))
-                sqs = sqs.filter(forum__in=starter_readable_forums, thread_starter=self.request.user.id)
+                sqs.search_in(Thread.objects.get(id=thread_id))
+            elif self.request.POST.get('search_in') == 'private_threads':
+                sqs.search_in(Forum.objects.special_model('private_threads'))
+            elif self.request.POST.get('search_in') == 'reports':
+                sqs.search_in(Forum.objects.special_model('reports'))
             else:
             else:
-                sqs = sqs.filter(forum__in=readable_forums)
+                sqs.search_in(Forum.objects.special_model('root'))
 
 
-        if self.request.POST.get('search_author'):
-            sqs = sqs.filter(author__exact=self.request.POST.get('search_author'))
+            if self.request.POST.get('search_thread_titles'):
+                sqs.search_thread_name(search_query)
+            else:
+                sqs.search_content(search_query)
 
 
-        return sqs
+            if self.request.POST.get('search_author'):
+                sqs.search_user_name(search_query)
+            return sqs
+        except Thread.DoesNotExist:
+            raise ACLError404()
 
 
     def render_to_response(self, template, form, context):
     def render_to_response(self, template, form, context):
         for i in ('search_query', 'search_in', 'search_author', 'search_thread_titles'):
         for i in ('search_query', 'search_in', 'search_author', 'search_thread_titles'):
@@ -131,7 +103,15 @@ class QuickSearchView(ViewBase):
                     self.request.POST['username'] = form.target
                     self.request.POST['username'] = form.target
                     return users_list(self.request)
                     return users_list(self.request)
 
 
-                sqs = self.make_query(form.cleaned_data['search_query']).load_all()[:60]
+                sqs = self.make_query(form.cleaned_data['search_query']).query.load_all()[:120]
+                results = []
+                for p in sqs:
+                    post = p.object
+                    try:
+                        self.request.acl.threads.allow_post_view(self.request.user, post.thread, post)
+                        results.append(post)
+                    except ACLError404:
+                        pass
 
 
                 if self.request.user.is_authenticated():
                 if self.request.user.is_authenticated():
                     self.request.user.last_search = timezone.now()
                     self.request.user.last_search = timezone.now()
@@ -139,7 +119,7 @@ class QuickSearchView(ViewBase):
                 if self.request.user.is_anonymous():
                 if self.request.user.is_anonymous():
                     self.request.session['last_search'] = timezone.now()
                     self.request.session['last_search'] = timezone.now()
 
 
-                if not sqs:
+                if not results:
                     raise SearchException(_("Search returned no results. Change search query and try again."))
                     raise SearchException(_("Search returned no results. Change search query and try again."))
 
 
                 self.request.session['search_results'] = {
                 self.request.session['search_results'] = {
@@ -147,7 +127,7 @@ class QuickSearchView(ViewBase):
                                                           'search_in': self.request.POST.get('search_in'),
                                                           'search_in': self.request.POST.get('search_in'),
                                                           'search_author': self.request.POST.get('search_author'),
                                                           'search_author': self.request.POST.get('search_author'),
                                                           'search_thread_titles': self.request.POST.get('search_thread_titles'),
                                                           'search_thread_titles': self.request.POST.get('search_thread_titles'),
-                                                          'search_results': [p.object for p in sqs],
+                                                          'search_results': results,
                                                           }
                                                           }
                 try:
                 try:
                     self.request.session['search_results']['search_thread'] = self.thread_clean
                     self.request.session['search_results']['search_thread'] = self.thread_clean

+ 114 - 9
misago/search.py

@@ -1,4 +1,9 @@
+from django.db.models import Q
 from django.utils.translation import ugettext_lazy as _
 from django.utils.translation import ugettext_lazy as _
+from haystack.inputs import AutoQuery
+from haystack.query import SearchQuerySet, RelatedSearchQuerySet
+from misago.acl.exceptions import ACLError403, ACLError404
+from misago.models import Forum, Thread, Post, User
 
 
 class SearchException(Exception):
 class SearchException(Exception):
     def __init__(self, message=None, suggestion=None):
     def __init__(self, message=None, suggestion=None):
@@ -16,7 +21,7 @@ class SearchQuery(object):
         """
         """
         if raw_query:
         if raw_query:
             self.parse_query(raw_query)
             self.parse_query(raw_query)
-        
+
     def parse_query(self, raw_query):
     def parse_query(self, raw_query):
         """
         """
         Parse raw search query into dict of lists of words that should be found and cant be found in string
         Parse raw search query into dict of lists of words that should be found and cant be found in string
@@ -27,16 +32,16 @@ class SearchQuery(object):
             word = unicode(word).strip().lower()
             word = unicode(word).strip().lower()
             if len(word) == 0:
             if len(word) == 0:
                 pass
                 pass
-            
+
             # Find word mode
             # Find word mode
             mode = '+'
             mode = '+'
             if word[0] == '-':
             if word[0] == '-':
                 mode = '-'
                 mode = '-'
                 word = unicode(word[1:]).strip()
                 word = unicode(word[1:]).strip()
-                
+
             # Strip extra crap
             # Strip extra crap
             word = ''.join(e for e in word if e.isalnum())
             word = ''.join(e for e in word if e.isalnum())
-            
+
             # Slice word?
             # Slice word?
             if len(word) <= 3:
             if len(word) <= 3:
                 raise SearchException(_("One or more search phrases are shorter than four characters."))
                 raise SearchException(_("One or more search phrases are shorter than four characters."))
@@ -48,11 +53,11 @@ class SearchQuery(object):
                 if len(word) > 6:
                 if len(word) > 6:
                     word = word[0:-3]
                     word = word[0:-3]
             self.criteria[mode].append(word)
             self.criteria[mode].append(word)
-            
+
         # Complain that there are no positive matches
         # Complain that there are no positive matches
         if not self.criteria['+'] and not self.criteria['-']:
         if not self.criteria['+'] and not self.criteria['-']:
             raise SearchException(_("Search query is invalid."))
             raise SearchException(_("Search query is invalid."))
-    
+
     def search(self, value):
     def search(self, value):
         """
         """
         See if value meets search criteria, return True for success and False otherwhise
         See if value meets search criteria, return True for success and False otherwhise
@@ -69,7 +74,7 @@ class SearchQuery(object):
             return self.search_for(value) and not self.search_against(value)
             return self.search_for(value) and not self.search_against(value)
         except AttributeError:
         except AttributeError:
             raise SearchException(_("You have to define search query before you will be able to search."))
             raise SearchException(_("You have to define search query before you will be able to search."))
-        
+
     def search_for(self, value):
     def search_for(self, value):
         """
         """
         See if value is required
         See if value is required
@@ -78,7 +83,7 @@ class SearchQuery(object):
             if value.find(word) != -1:
             if value.find(word) != -1:
                 return True
                 return True
         return False
         return False
-        
+
     def search_against(self, value):
     def search_against(self, value):
         """
         """
         See if value is forbidden
         See if value is forbidden
@@ -86,4 +91,104 @@ class SearchQuery(object):
         for word in self.criteria['-']:
         for word in self.criteria['-']:
             if value.find(word) != -1:
             if value.find(word) != -1:
                 return True
                 return True
-        return False
+        return False
+
+
+class MisagoSearchQuerySet(object):
+    def __init__(self, user, acl):
+        self._content = None
+        self._thread_name = None
+        self._user_name = None
+        self._after = None
+        self._before = None
+        self._children = None
+        self._threads = None
+        self._forums = None
+
+        self.user = user
+        self.acl = acl
+
+    def search_in(self, target):
+        try:
+            self.allow_forum_search(target)
+        except AttributeError:
+            self.allow_thread_search(target)
+
+    def allow_forum_search(self, forum):
+        if forum.special == 'private_threads':
+            if not self.acl.private_threads.can_participate():
+                raise ACLError403()
+            if self.acl.private_threads.is_mod():
+                self._threads = [t.pk for t in forum.thread_set.filter(Q(participants__id=self.user.pk) | Q(replies_reported__gt=0)).iterator()]
+            else:
+                self._threads = [t.pk for t in forum.thread_set.filter(participants__id=self.user.pk).iterator()]
+        elif forum.special == 'reports':
+            if not self.acl.reports.can_handle():
+                raise ACLError403()
+            self._forums = [forum.pk]
+        else:
+            self._forums = Forum.objects.readable_forums(self.acl)
+
+    def allow_thread_search(self, thread):
+        self.allow_forum_search(thread.forum)
+        if thread.forum.special == 'private_threads':
+            if thread.pk in self._threads:
+                self._threads = [thread.pk]
+            else:
+                raise ACLError403()
+        self._threads = [thread.pk]
+
+    def search_content(self, query):
+        self._content = query
+
+    def restrict_threads(self, threads=None):
+        self._threads = threads
+
+    def search_thread_name(self, query):
+        self._thread_name = query
+
+    def search_user_name(self, query):
+        self._user_name = query
+
+    def after(self, datetime):
+        self._after = datetime
+
+    def before(self, datetime):
+        self._before = datetime
+
+    def in_forum(self, forum, children=False):
+        self._forum = forum
+        self._children = children
+
+    @property
+    def query(self):
+        try:
+            return self._searchquery
+        except AttributeError:
+            pass
+
+        sqs = SearchQuerySet()
+
+        if self._content:
+            sqs = sqs.auto_query(self._content)
+
+        if self._thread_name:
+            sqs = sqs.filter(thread_name=AutoQuery(self._thread_name), start_post=1)
+
+        if self._user_name:
+            sqs = sqs.filter(username=self._user_name)
+
+        if self._before:
+            sqs = sqs.filter(date__lte=self._before)
+
+        if self._after:
+            sqs = sqs.filter(date__gte=self._after)
+
+        if self._threads:
+            sqs = sqs.filter(thread__in=self._threads)
+
+        if self._forums:
+            sqs = sqs.filter(forum__in=self._forums)
+
+        self._searchquery = sqs
+        return self._searchquery

+ 1 - 0
static/cranefly/css/style.css

@@ -0,0 +1 @@
+lessc: ENOENT, open '/Users/rafapiton/Documents/misago/static/cranefly/css/style.less'

+ 8 - 8
templates/cranefly/layout.html

@@ -12,15 +12,15 @@
         <a href="{{ url('index') }}" class="brand">{% if settings.board_header %}{{ settings.board_header }}{% else %}{{ settings.board_name }}{% endif %}</a>
         <a href="{{ url('index') }}" class="brand">{% if settings.board_header %}{{ settings.board_header }}{% else %}{{ settings.board_name }}{% endif %}</a>
         {% if acl.search.can_search() and not user.is_crawler() %}
         {% if acl.search.can_search() and not user.is_crawler() %}
         <form action="{{ url('search') }}" method="post" class="navbar-form pull-left">
         <form action="{{ url('search') }}" method="post" class="navbar-form pull-left">
+          <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
+          {% if thread is defined and thread %}
+          <input type="hidden" name="search_thread" value="{{ thread.pk }}">
+          {% elif search_thread is defined and search_thread %}
+          <input type="hidden" name="search_thread" value="{{ search_thread.pk }}">
+          {% endif %}
           <div class="navbar-search-form">
           <div class="navbar-search-form">
             <div id="navbar-search" class="navbar-search-border">
             <div id="navbar-search" class="navbar-search-border">
               <div class="navbar-search-text">
               <div class="navbar-search-text">
-                <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
-                {% if thread is defined and thread %}
-                <input type="hidden" name="search_thread" value="{{ thread.pk }}">
-                {% elif search_thread is defined and search_thread %}
-                <input type="hidden" name="search_thread" value="{{ search_thread.pk }}">
-                {% endif %}
                 <i class="icon-search"></i>
                 <i class="icon-search"></i>
                 <input type="text" id="search-field" name="search_query" placeholder="{% trans %}Search...{% endtrans %}"{% if search_query is defined and search_query %} value="{{ search_query }}"{% endif %}>
                 <input type="text" id="search-field" name="search_query" placeholder="{% trans %}Search...{% endtrans %}"{% if search_query is defined and search_query %} value="{{ search_query }}"{% endif %}>
               </div>
               </div>
@@ -55,7 +55,7 @@
                 </div>
                 </div>
                 <div class="form-actions">
                 <div class="form-actions">
                   <button type="submit" class="btn btn-primary"><i class="icon-search"></i> {% trans %}Search{% endtrans%}</button>
                   <button type="submit" class="btn btn-primary"><i class="icon-search"></i> {% trans %}Search{% endtrans%}</button>
-                  {#<a href="{{ url('search') }}">{% trans %}Advanced Search{% endtrans %}</a>#}
+                  <a href="{{ url('search') }}">{% trans %}Advanced Search{% endtrans %}</a>
                 </div>
                 </div>
               </div>
               </div>
             </div>
             </div>
@@ -67,7 +67,7 @@
           {{ hook_primary_menu_prepend|safe }}
           {{ hook_primary_menu_prepend|safe }}
           <li><a href="{{ url('popular_threads') }}" title="{% trans %}Popular Threads{% endtrans %}" class="hot tooltip-bottom"><i class="icon-fire"></i></a></li>
           <li><a href="{{ url('popular_threads') }}" title="{% trans %}Popular Threads{% endtrans %}" class="hot tooltip-bottom"><i class="icon-fire"></i></a></li>
           <li><a href="{{ url('new_threads') }}" title="{% trans %}New Threads{% endtrans %}" class="fresh tooltip-bottom"><i class="icon-leaf"></i></a></li>{% if not user.crawler %}
           <li><a href="{{ url('new_threads') }}" title="{% trans %}New Threads{% endtrans %}" class="fresh tooltip-bottom"><i class="icon-leaf"></i></a></li>{% if not user.crawler %}
-          {% if 1==2 and acl.search.can_search() and not user.is_crawler() %}
+          {% if acl.search.can_search() and not user.is_crawler() %}
           <li><a href="{{ url('search') }}" title="{% trans %}Search Forums{% endtrans %}" class="tooltip-bottom"><i class="icon-search"></i></a></li>{% endif %}
           <li><a href="{{ url('search') }}" title="{% trans %}Search Forums{% endtrans %}" class="tooltip-bottom"><i class="icon-search"></i></a></li>{% endif %}
           {% endif %}
           {% endif %}
           <li><a href="{{ url('users') }}" title="{% trans %}Browse Users{% endtrans %}" class="tooltip-bottom"><i class="icon-group"></i></a></li>
           <li><a href="{{ url('users') }}" title="{% trans %}Browse Users{% endtrans %}" class="tooltip-bottom"><i class="icon-group"></i></a></li>