app_label = 'forum'
-from node import Node, NodeRevision
+from node import Node, NodeRevision, NodeManager
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
favorite_count = DenormalizedField("actions", action_type="favorite", canceled=False)
friendly_name = _("question")
+ objects = QuestionManager()
@property
def closed(self):
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"""
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
color: orange;
}
+#ask-related-questions {
+ max-height: 150px;
+ overflow-y: auto;
+}
\ No newline at end of file
<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>
{% 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
{% 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
--- /dev/null
+from django import template
+
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)
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
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
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
#(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',
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'),
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
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()
+
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)
-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],
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;
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
'forum',
]
-if DEBUG and False:
+if DEBUG:
try:
import debug_toolbar
MIDDLEWARE_CLASSES.append('debug_toolbar.middleware.DebugToolbarMiddleware')
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.