From d26f827463f2d76423dc12288f2c3d6740f2b607 Mon Sep 17 00:00:00 2001 From: Adolfo Fitoria Date: Tue, 19 Jun 2012 14:22:47 -0600 Subject: temporal commit, search is still broken --- askbot/models/question.py | 167 +++++++++++++++++++++++++++++++++++++ askbot/models/tag.py | 3 +- askbot/search/haystack/__init__.py | 7 ++ 3 files changed, 176 insertions(+), 1 deletion(-) diff --git a/askbot/models/question.py b/askbot/models/question.py index d0e5c5b2..371063eb 100644 --- a/askbot/models/question.py +++ b/askbot/models/question.py @@ -154,6 +154,13 @@ class ThreadManager(models.Manager): def run_advanced_search(self, request_user, search_state): # TODO: !! review, fix, and write tests for this + if settings.ENABLE_HAYSTACK_SEARCH: + return self._run_advanced_haystack_search(request_user, search_state) + else: + return self._run_advanced_full_text_search(request_user, search_state) + + + def _run_advanced_full_text_search(self, request_user, search_state): """ all parameters are guaranteed to be clean however may not relate to database - in that case @@ -309,6 +316,166 @@ class ThreadManager(models.Manager): return qs.distinct(), meta_data + def _run_advanced_haystack_search(self, request_user, search_state): + from askbot.conf import settings as askbot_settings # Avoid circular import + try: + from askbot.search.haystack import AskbotSearchQuerySet + qs = AskbotSearchQuerySet() + except ImportError, e: + print e.message + qs = self + + # TODO: add a possibility to see deleted questions + qs = qs.filter( + posts__post_type='question' + # posts__deleted=False, + ) # (***) brings `askbot_post` into the SQL query, see the ordering section below + + #if askbot_settings.ENABLE_CONTENT_MODERATION: + # qs = qs.filter(approved = True) + + meta_data = {} + + if search_state.stripped_query: + qs = self.get_for_query(search_query=search_state.stripped_query, qs=qs) + if search_state.query_title: + qs = qs.filter(title__icontains = search_state.query_title) + if search_state.query_users: + query_users = User.objects.filter(username__in=search_state.query_users) + if query_users: + qs = qs.filter(posts__post_type='question', posts__author__in=query_users) # TODO: unify with search_state.author ? + + tags = search_state.unified_tags() + if len(tags) > 0: + + if askbot_settings.TAG_SEARCH_INPUT_ENABLED: + #todo: this may be gone or disabled per option + #"tag_search_box_enabled" + existing_tags = set( + Tag.objects.filter( + name__in = tags + ).values_list( + 'name', + flat = True + ) + ) + + non_existing_tags = set(tags) - existing_tags + meta_data['non_existing_tags'] = list(non_existing_tags) + tags = existing_tags + else: + meta_data['non_existing_tags'] = list() + + #construct filter for the tag search + for tag in tags: + qs = qs.filter(tags__name=tag) # Tags or AND-ed here, not OR-ed (i.e. we fetch only threads with all tags) + else: + meta_data['non_existing_tags'] = list() + + if search_state.scope == 'unanswered': + print 'search state unanswered' + qs = qs.filter(closed = False) # Do not show closed questions in unanswered section + if askbot_settings.UNANSWERED_QUESTION_MEANING == 'NO_ANSWERS': + qs = qs.filter(answer_count=0) # TODO: expand for different meanings of this + elif askbot_settings.UNANSWERED_QUESTION_MEANING == 'NO_ACCEPTED_ANSWERS': + qs = qs.filter(accepted_answer__isnull=True) + elif askbot_settings.UNANSWERED_QUESTION_MEANING == 'NO_UPVOTED_ANSWERS': + raise NotImplementedError() + else: + raise Exception('UNANSWERED_QUESTION_MEANING setting is wrong') + + elif search_state.scope == 'favorite': + print 'favorite_filter' + favorite_filter = models.Q(favorited_by=request_user) + if 'followit' in settings.INSTALLED_APPS: + followed_users = request_user.get_followed_users() + favorite_filter |= models.Q(posts__post_type__in=('question', 'answer'), posts__author__in=followed_users) + qs = qs.filter(favorite_filter) + + #user contributed questions & answers + if search_state.author: + try: + # TODO: maybe support selection by multiple authors + u = User.objects.get(id=int(search_state.author)) + except User.DoesNotExist: + meta_data['author_name'] = None + else: + qs = qs.filter(posts__post_type__in=('question', 'answer'), posts__author=u, posts__deleted=False) + meta_data['author_name'] = u.username + + #get users tag filters + if request_user and request_user.is_authenticated(): + #mark questions tagged with interesting tags + #a kind of fancy annotation, would be nice to avoid it + interesting_tags = Tag.objects.filter( + user_selections__user = request_user, + user_selections__reason = 'good' + ) + ignored_tags = Tag.objects.filter( + user_selections__user = request_user, + user_selections__reason = 'bad' + ) + if askbot_settings.SUBSCRIBED_TAG_SELECTOR_ENABLED: + meta_data['subscribed_tag_names'] = Tag.objects.filter( + user_selections__user = request_user, + user_selections__reason = 'subscribed' + ).values_list('name', flat = True) + + meta_data['interesting_tag_names'] = [tag.name for tag in interesting_tags] + meta_data['ignored_tag_names'] = [tag.name for tag in ignored_tags] + + if request_user.display_tag_filter_strategy == const.INCLUDE_INTERESTING and (interesting_tags or request_user.has_interesting_wildcard_tags()): + #filter by interesting tags only + interesting_tag_filter = models.Q(tags__in=interesting_tags) + if request_user.has_interesting_wildcard_tags(): + interesting_wildcards = request_user.interesting_tags.split() + extra_interesting_tags = Tag.objects.get_by_wildcards(interesting_wildcards) + interesting_tag_filter |= models.Q(tags__in=extra_interesting_tags) + qs = qs.filter(interesting_tag_filter) + + # get the list of interesting and ignored tags (interesting_tag_names, ignored_tag_names) = (None, None) + if request_user.display_tag_filter_strategy == const.EXCLUDE_IGNORED and (ignored_tags or request_user.has_ignored_wildcard_tags()): + #exclude ignored tags if the user wants to + qs = qs.exclude(tags__in=ignored_tags) + if request_user.has_ignored_wildcard_tags(): + ignored_wildcards = request_user.ignored_tags.split() + extra_ignored_tags = Tag.objects.get_by_wildcards(ignored_wildcards) + qs = qs.exclude(tags__in = extra_ignored_tags) + + if askbot_settings.USE_WILDCARD_TAGS: + meta_data['interesting_tag_names'].extend(request_user.interesting_tags.split()) + meta_data['ignored_tag_names'].extend(request_user.ignored_tags.split()) + + QUESTION_ORDER_BY_MAP = { + 'age-desc': '-added_at', + 'age-asc': 'added_at', + 'activity-desc': '-last_activity_at', + 'activity-asc': 'last_activity_at', + 'answers-desc': '-answer_count', + 'answers-asc': 'answer_count', + 'votes-desc': '-points', + 'votes-asc': 'points', + + 'relevance-desc': '-relevance', # special Postgresql-specific ordering, 'relevance' quaso-column is added by get_for_query() + } + orderby = QUESTION_ORDER_BY_MAP[search_state.sort] + #FIXTHIS + #qs = qs.extra(order_by=[orderby]) + + # HACK: We add 'ordering_key' column as an alias and order by it, because when distict() is used, + # qs.extra(order_by=[orderby,]) is lost if only `orderby` column is from askbot_post! + # Removing distinct() from the queryset fixes the problem, but we have to use it here. + # UPDATE: Apparently we don't need distinct, the query don't duplicate Thread rows! + # qs = qs.extra(select={'ordering_key': orderby.lstrip('-')}, order_by=['-ordering_key' if orderby.startswith('-') else 'ordering_key']) + # qs = qs.distinct() + + #FIXTHIS + #qs = qs.only('id', 'title', 'view_count', 'answer_count', 'last_activity_at', 'last_activity_by', 'closed', 'tagnames', 'accepted_answer') + + #print qs.query + + return qs.get_django_queryset(Thread), meta_data + def precache_view_data_hack(self, threads): # TODO: Re-enable this when we have a good test cases to verify that it works properly. # diff --git a/askbot/models/tag.py b/askbot/models/tag.py index 858db2e6..cb9070a4 100644 --- a/askbot/models/tag.py +++ b/askbot/models/tag.py @@ -2,11 +2,12 @@ import re from django.db import models from django.contrib.auth.models import User from django.utils.translation import ugettext as _ +from django.conf import settings from askbot.models.base import BaseQuerySetManager from askbot import const def tags_match_some_wildcard(tag_names, wildcard_tags): - """Same as + """Same as :meth:`~askbot.models.tag.TagQuerySet.tags_match_some_wildcard` except it works on tag name strings """ diff --git a/askbot/search/haystack/__init__.py b/askbot/search/haystack/__init__.py index 8f008a93..0bd4476f 100644 --- a/askbot/search/haystack/__init__.py +++ b/askbot/search/haystack/__init__.py @@ -1,5 +1,6 @@ try: from haystack import indexes, site + from haystack.query import SearchQuerySet except ImportError: pass @@ -27,3 +28,9 @@ class PostIndex(indexes.SearchIndex): site.register(Post, PostIndex) site.register(Thread, ThreadIndex) + +class AskbotSearchQuerySet(SearchQuerySet): + + def get_django_queryset(self, model_klass): + id_list = [r.pk for r in self.models(model_klass)] + return model_klass.objects.filter(id__in=id_list) -- cgit v1.2.3-1-g7c22