]> git.openstreetmap.org Git - osqa.git/commitdiff
Improvements on full text search.
authorhernani <hernani@0cfe37f9-358a-4d5e-be75-b63607b5c754>
Wed, 19 May 2010 23:06:16 +0000 (23:06 +0000)
committerhernani <hernani@0cfe37f9-358a-4d5e-be75-b63607b5c754>
Wed, 19 May 2010 23:06:16 +0000 (23:06 +0000)
Related question suggestions when asking (thanks Justin).
Option to make sidebar custom blocks not wraped by the default container.

git-svn-id: http://svn.osqa.net/svnroot/osqa/trunk@306 0cfe37f9-358a-4d5e-be75-b63607b5c754

18 files changed:
forum/models/base.py
forum/models/question.py
forum/settings/sidebar.py
forum/skins/default/media/style/style.css
forum/skins/default/templates/ask.html
forum/skins/default/templates/notifications/answeraccepted.html
forum/skins/default/templates/sidebar/user_blocks.html
forum/templatetags/email_tags.py [new file with mode: 0644]
forum/templatetags/extra_tags.py
forum/templatetags/general_sidebar_tags.py
forum/urls.py
forum/views/commands.py
forum/views/readers.py
forum_modules/pgfulltext/handlers.py
forum_modules/pgfulltext/pg_fts_install.sql
forum_modules/pgfulltext/startup.py
settings.py
settings_local.py.dist

index 1f15bde0bdc1dd304bd6f518ace1209d70e94bc4..627888f47f080db4c1a186e35fddf11ee3cd8312 100644 (file)
@@ -273,7 +273,7 @@ class CancelableContent(models.Model):
         app_label = 'forum'
 
 
-from node import Node, NodeRevision
+from node import Node, NodeRevision, NodeManager
 
 
 
index 54840166d1d9c51534de77884296f670622bb0cf..9e8dcf643cd986c3ebc7b1b2550346aff2868b59 100644 (file)
@@ -1,9 +1,15 @@
 from base import *
 from tag import Tag
 from django.utils.translation import ugettext as _
+from forum.modules.decorators import decoratable
 
 question_view = django.dispatch.Signal(providing_args=['instance', 'user'])
 
+class QuestionManager(NodeManager):
+    @decoratable.method
+    def search(self, keywords):
+        return self.filter(models.Q(title__icontains=keywords) | models.Q(body__icontains=keywords))
+
 class Question(Node):
     class Meta(Node.Meta):
         proxy = True
@@ -12,6 +18,7 @@ class Question(Node):
     favorite_count = DenormalizedField("actions", action_type="favorite", canceled=False)
 
     friendly_name = _("question")
+    objects = QuestionManager()
 
     @property   
     def closed(self):
index 2ff349e91c0dcd6da24babbf2291d86df657be4a..a2b3f231a7df14e9b97ebcd4328289054fd5104b 100644 (file)
@@ -20,6 +20,10 @@ label = "Show Upper Block",
 help_text = "Check if your pages should display the upper sidebar block.",
 required=False))
 
+SIDEBAR_UPPER_DONT_WRAP = Setting('SIDEBAR_UPPER_DONT_WRAP', False, SIDEBAR_SET, dict(
+label = "Don't Wrap Upper Block",
+help_text = "Don't wrap upper block with the standard style.",
+required=False))
 
 SIDEBAR_UPPER_TEXT = Setting('SIDEBAR_UPPER_TEXT',
 u"""
@@ -42,6 +46,11 @@ label = "Show Lower Block",
 help_text = "Check if your pages should display the lower sidebar block.",
 required=False))
 
+SIDEBAR_LOWER_DONT_WRAP = Setting('SIDEBAR_LOWER_DONT_WRAP', False, SIDEBAR_SET, dict(
+label = "Don't Wrap Lower Block",
+help_text = "Don't wrap lower block with the standard style.",
+required=False))
+
 SIDEBAR_LOWER_TEXT = Setting('SIDEBAR_LOWER_TEXT',
 u"""
 ## Learn more about OSQA
index a8a43ae2a2c9b80e2562e91d74b60aa2f07ca109..373b1d5875d6cd7ee0986c512bf09c679ce96eb9 100644 (file)
@@ -1368,3 +1368,7 @@ div.comment-tools a:hover {
     color: orange;    
 }
 
+#ask-related-questions {
+    max-height: 150px;
+    overflow-y: auto;
+}
\ No newline at end of file
index 35a6c2c50acd383fb971572897dbd8498fa66559..ac393cde3355fabec8aa0aa1af44fd53c83396e5 100644 (file)
@@ -7,6 +7,14 @@
         <script type='text/javascript' src='{% media  "/media/js/wmd/showdown.js" %}'></script>
         <script type='text/javascript' src='{% media  "/media/js/wmd/wmd.js" %}'></script>
         <link rel="stylesheet" type="text/css" href="{% media  "/media/js/wmd/wmd.css" %}" />
+        <script type="text/html" id="question-summary-template">
+            <div class="answer-summary">
+                <a href="%URL%"><div class="answer-votes">%SCORE%</div></a>
+                <div class="answer-link">
+                    <a class="question-hyperlink" href="%URL%" title="%SUMMARY%">%TITLE%</a>
+                </div>
+            </div>
+        </script>
         <script type="text/javascript">
         $().ready(function(){
             //set current module button style
                 }
 
             });
+
+            var $input = $('#id_title');
+            var $box = $('#ask-related-questions');
+            var template = $('#question-summary-template').html()
+
+            function reload_suggestion_box(q) {
+                $.post('{% url related_questions %}', {title: q}, function(data) {
+                    if (data) {
+                        var html = '';
+                        for (var i = 0; i < data.length; i++) {
+                            var item = template.replace(new RegExp('%URL%', 'g'), data[i].url)
+                                               .replace(new RegExp('%SCORE%', 'g'), data[i].score)
+                                               .replace(new RegExp('%TITLE%', 'g'), data[i].title)
+                                               .replace(new RegExp('%SUMMARY%', 'g'), data[i].summary);
+
+                            html += item;
+
+                        }
+                        $('#ask-related-questions').html(html);
+                    }
+                }, 'json');
+            }
+
+            $input.keyup(function(e) {
+                var q = $input.val().trim();
+
+                if (e.which == 32 && q.length > 8 && q.split(' ').length > 2) {
+                    reload_suggestion_box(q);
+                }
+
+                return true;
+            });
+
+            $input.change(function() {
+                var q = $input.val().trim();
+
+                if (q.length > 8) {
+                    reload_suggestion_box(q);
+                }
+            });
         });
         </script>
 {% endblock %}
                     {{ form.title.help_text }}
                 </div>
             </div>
-
+            <div id="ask-related-questions"></div>
             <div class="form-item">
                 <div id="wmd-button-bar" class="wmd-panel"></div>
                 {{ form.text }} {{ form.text.errors }}
-
                 <div class="preview-toggle">
                     <table width="100%">
                         <tr>
index 504970bece0379120704f8a19db7c0a787e40e83..bdbf79a7012ea46d9712fa563e99df2b6dfe2893 100644 (file)
@@ -3,11 +3,17 @@
 {% load extra_tags %}\r
 \r
 {% block content %}\r
+    {% var accepted_by = answer.accepted.by.username %}\r
+    {% var answer_author = answer.author.username %}\r
+    {% var app_url = settings.APP_URL %}\r
+    {% var question_url = question.get_absolute_url %}\r
+    {% var question_title = question.title %}\r
+\r
     <p>{% trans "Hello" %} {% user_var username %},</p>\r
 \r
     <p>\r
-        {% blocktrans with answer.accepted.by.username as accepter and answer.author.username as answer_author and settings.APP_SHORT_NAME as app_title and settings.APP_URL as app_url and question.get_absolute_url as question_url and question.title as question_title %}\r
-        Just to let you know that {{ accepter }} has just accepted {{ answer_author }}'s answer on his question\r
+        {% blocktrans %}\r
+        Just to let you know that {{ accepted_by }} has just accepted {{ answer_author }}'s answer on his question\r
         <a href="{{ app_url }}{{ question_url }}">{{ question_title }}</a>:\r
         {% endblocktrans %}\r
     </p>\r
index 4303e3195c27372c596b3fe24e45d84f964fa03e..b7fd4b2be096c5201f9fdb21084f314a877bfe0a 100644 (file)
@@ -1,9 +1,9 @@
 {% load markup %}
 
 {% if show %}
-<div id="{{ blockid }}" class="boxC">
-    <div class="body">
+{% if wrap %}<div id="{{ blockid }}" class="boxC">
+    <div class="body">{% endif %}
         {{ content|markdown }}
-     </div>
-</div>
+     {% if wrap %}</div>
+</div>{% endif %}
 {% endif %}
\ No newline at end of file
diff --git a/forum/templatetags/email_tags.py b/forum/templatetags/email_tags.py
new file mode 100644 (file)
index 0000000..0f62bad
--- /dev/null
@@ -0,0 +1,2 @@
+from django import template
+
index 9f595c8418bf182c776fd1b88b0fae8be6092d48..9f215b50816f0d78035c6d1c87fc6e7ffd031c89 100644 (file)
@@ -372,30 +372,61 @@ def user_var(parser, token):
     return UserVarNode(tokens)
 
 
-class VariablesNode(template.Node):
-    def __init__(self, nodelist, var_name):
-        self.nodelist = nodelist
-        self.var_name = var_name
+class SimpleVarNode(template.Node):
+    def __init__(self, name, value):
+        self.name = name
+        self.value = template.Variable(value)
 
     def render(self, context):
-        source = self.nodelist.render(context)
-        context[self.var_name] = simplejson.loads(source)
+        context[self.name] = self.value.resolve(context)
         return ''
 
+class BlockVarNode(template.Node):
+    def __init__(self, name, block):
+        self.name = name
+        self.block = block
+
+    def render(self, context):
+        source = self.block.render(context)
+        context[self.name] = source.strip()
+        return ''
+
+
 @register.tag(name='var')
-def do_variables(parser, token):
-    try:
-        tag_name, arg = token.contents.split(None, 1)
-    except ValueError:
-        msg = '"%s" tag requires arguments' % token.contents.split()[0]
-        raise template.TemplateSyntaxError(msg)
-    m = re.search(r'as (\w+)', arg)
-    if m:
-        var_name, = m.groups()
-    else:
-        msg = '"%s" tag had invalid arguments' % tag_name
-        raise template.TemplateSyntaxError(msg)
+def do_var(parser, token):
+    tokens = token.split_contents()[1:]
+
+    if not len(tokens) or not re.match('^\w+$', tokens[0]):
+        raise template.TemplateSyntaxError("Expected variable name")
+
+    if len(tokens) == 1:
+        nodelist = parser.parse(('endvar',))
+        parser.delete_first_token()
+        return BlockVarNode(tokens[0], nodelist)
+    elif len(tokens) == 3:
+        return SimpleVarNode(tokens[0], tokens[2])
+
+    raise template.TemplateSyntaxError("Invalid number of arguments")
+
+class DeclareNode(template.Node):
+    dec_re = re.compile('^\s*(\w+)\s*(:?=)\s*(.*)$')
+
+    def __init__(self, block):
+        self.block = block
+
+    def render(self, context):
+        source = self.block.render(context)
+
+        for line in source.splitlines():
+            m = self.dec_re.search(line)
+            if m and m.group(2) == '=':
+                context[m.group(1).strip()] = m.group(3).strip()
+            elif m and m.group(2) == ':=':
+                context[m.group(1).strip()] = template.Variable(m.group(3).strip()).resolve(context)
+        return ''
 
-    nodelist = parser.parse(('endvar',))
+@register.tag(name='declare')
+def do_declare(parser, token):
+    nodelist = parser.parse(('enddeclare',))
     parser.delete_first_token()
-    return VariablesNode(nodelist, var_name)
+    return DeclareNode(nodelist)
index 5741d1baa5a6e3c3dbd13819aca7f40bd2b772ad..09f30cccf215d8923c741f42c25c3ee23f9bf768 100644 (file)
@@ -21,6 +21,7 @@ def sidebar_upper():
     return {\r
         'show': settings.SIDEBAR_UPPER_SHOW,\r
         'content': settings.SIDEBAR_UPPER_TEXT,\r
+        'wrap': not settings.SIDEBAR_UPPER_DONT_WRAP,\r
         'blockid': 'sidebar-upper'\r
     }\r
 \r
@@ -29,5 +30,6 @@ def sidebar_lower():
     return {\r
         'show': settings.SIDEBAR_LOWER_SHOW,\r
         'content': settings.SIDEBAR_LOWER_TEXT,\r
+        'wrap': not settings.SIDEBAR_LOWER_DONT_WRAP,\r
         'blockid': 'sidebar-lower'\r
     }\r
index 58ccc2ce3d7117bc1a6c6c3b317f63e0b10e6d29..54624fba7661660d3a7f5418549dbf8fc0eb5ee8 100644 (file)
@@ -3,6 +3,7 @@ import startup
 import os.path
 from forum import settings
 from django.conf.urls.defaults import *
+from django.conf import settings as djsettings
 from django.contrib import admin
 from forum import views as app
 from forum.feed import RssLastestQuestionsFeed
@@ -37,6 +38,7 @@ urlpatterns += patterns('',
     #(r'^favicon\.ico$', 'django.views.generic.simple.redirect_to', {'url': '/media/images/favicon.ico'}),
     #(r'^favicon\.gif$', 'django.views.generic.simple.redirect_to', {'url': '/media/images/favicon.gif'}),
     (r'^favicon\.ico$', app.meta.favicon),
+
     url(r'^m/(?P<path>.*)$', 'django.views.static.serve',
         {'document_root': os.path.join(APP_PATH,'skins').replace('\\','/')},
         name='osqa_media',
@@ -56,6 +58,8 @@ urlpatterns += patterns('',
     url(r'^%s(?P<id>\d+)/$' % _('revisions/'), app.readers.revisions, name='revisions'),
     url(r'^%s$' % _('questions/'), app.readers.questions, name='questions'),
     url(r'^%s%s$' % (_('questions/'), _('ask/')), app.writers.ask, name='ask'),
+    url(r'^%s%s$' % (_('questions/'), _('related_questions/')), app.commands.related_questions, name='related_questions'),
+
     url(r'^%s%s$' % (_('questions/'), _('unanswered/')), app.readers.unanswered, name='unanswered'),
     url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('edit/')), app.writers.edit_question, name='edit_question'),
     url(r'^%s(?P<id>\d+)/%s$' % (_('questions/'), _('close/')), app.commands.close, kwargs=dict(close=True), name='close'),
index d8295547972692211f371d177e7abb6e721edbf1..e09924b064a38409874e253a66495263454d4fc9 100644 (file)
@@ -2,7 +2,7 @@ import datetime
 from forum import settings
 from django.core.exceptions import ObjectDoesNotExist
 from django.utils import simplejson
-from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden
+from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, Http404
 from django.shortcuts import get_object_or_404, render_to_response
 from django.utils.translation import ungettext, ugettext as _
 from django.template import RequestContext
@@ -410,6 +410,14 @@ def matching_tags(request):
         
     return HttpResponse(tag_output, mimetype="text/plain")
 
+def related_questions(request):
+    if request.POST and request.POST.get('title', None):
+        return HttpResponse(simplejson.dumps(
+                [dict(title=q.title, url=q.get_absolute_url(), score=q.score, summary=q.summary)
+                 for q in Question.objects.search(request.POST['title'])[0:10]]), mimetype="application/json")
+    else:
+        raise Http404()
+
 
 
 
index b865fbb0dc2b91f9ddef69b6928b66e698c5ac8f..a48c35b19041b94e7ecf2932dc1fae3ddbae18c7 100644 (file)
@@ -128,13 +128,9 @@ def search(request):
     else:
         return render_to_response("search.html", context_instance=RequestContext(request))
 
-@decoratable
-def do_question_search(keywords):
-    return Question.objects.filter(Q(title__icontains=keywords) | Q(body__icontains=keywords))
-
 @decorators.render('questions.html')
 def question_search(request, keywords):
-    initial = do_question_search(keywords)
+    initial = Question.objects.search(keywords)
 
     return question_list(request, initial, _("questions matching '%(keywords)s'") % {'keywords': keywords},
             base_path="%s?t=question&q=%s" % (reverse('search'), django_urlquote(keywords)), sort=False)
index 2b57e96efe699bca3b6f45a81dfb5b44c1ab7541..e1d98f20877875e6ac958a0b0862c41ee031f38d 100644 (file)
@@ -1,13 +1,12 @@
-from forum.models import Question
+from forum.models.question import Question, QuestionManager
 from forum.modules.decorators import decorate
-from forum.views.readers import do_question_search
 
-@decorate(do_question_search, needs_origin=False)
-def question_search(keywords):
-    return Question.objects.all().extra(
+@decorate(QuestionManager.search, needs_origin=False)
+def question_search(self, keywords):
+    return self.extra(
                     tables=['forum_rootnode_doc'],
                     select={
-                        'ranking': 'ts_rank_cd("forum_rootnode_doc"."document", plainto_tsquery(\'english\', %s), 32)',
+                        'ranking': 'ts_rank_cd(\'{0.1, 0.2, 0.8, 1.0}\'::float4[], "forum_rootnode_doc"."document", plainto_tsquery(\'english\', %s), 32)',
                     },
                     where=['"forum_rootnode_doc"."node_id" = "forum_node"."id"', '"forum_rootnode_doc"."document" @@ plainto_tsquery(\'english\', %s)'],
                     params=[keywords],
index bba7eefcc1f406d8609b57d555b5f37113fb9499..ddc48a1b8f63e52f22b18c050e71c02746c384b5 100644 (file)
@@ -53,10 +53,11 @@ begin
         return new;
     END IF;
 
-    doc :=
-      setweight(to_tsvector('english', coalesce(new.tagnames,'')), 'A') ||
-      setweight(to_tsvector('english', coalesce(new.title,'')), 'B') ||
-      setweight(to_tsvector('english', coalesce(new.body,'')), 'C');
+    SELECT
+      setweight(to_tsvector('english', coalesce(tagnames,'')), 'A') ||
+      setweight(to_tsvector('english', coalesce(title,'')), 'B') ||
+      setweight(to_tsvector('english', coalesce(body,'')), 'C') INTO doc
+      FROM forum_node WHERE id = root_id;
 
     SELECT count(*)::int INTO rcount FROM forum_node WHERE abs_parent_id = root_id AND deleted_id IS NULL;
 
index 0eb3f3901e1d11a95a37ff8f38305e260523ea67..c7cd5eab8a62b26092ac4febf37ea613a6a32e2a 100644 (file)
@@ -3,7 +3,7 @@ from forum.models import KeyValue
 from django.db import connection, transaction\r
 import settings\r
 \r
-VERSION = 6\r
+VERSION = 7\r
 \r
 if int(settings.PG_FTSTRIGGERS_VERSION) < VERSION:\r
     f = open(os.path.join(os.path.dirname(__file__), 'pg_fts_install.sql'), 'r')\r
index 7b9e1f0f432ed6821051afade6fbbad30d7bdbbd..a7c589300508523648a62fe07d68b59b85765799 100644 (file)
@@ -71,7 +71,7 @@ INSTALLED_APPS = [
     'forum',
 ]
 
-if DEBUG and False:
+if DEBUG:
     try:
         import debug_toolbar
         MIDDLEWARE_CLASSES.append('debug_toolbar.middleware.DebugToolbarMiddleware')
index 3b1cd458a2c1a85dd96baf2040de95c29a056b5f..053032b10422558ebaf205139d4055ea893de77c 100644 (file)
@@ -23,6 +23,7 @@ DEBUG_TOOLBAR_CONFIG = {
 TEMPLATE_DEBUG = DEBUG
 INTERNAL_IPS = ('127.0.0.1',)
 
+
 DATABASE_NAME = ''             # Or path to database file if using sqlite3.
 DATABASE_USER = ''               # Not used with sqlite3.
 DATABASE_PASSWORD = ''               # Not used with sqlite3.