Commits

Anonymous committed fecaaa4

Added a search button (quicksearch will come once the search is working)
and basic search functionality based on xapian.

Comments (0)

Files changed (9)

 from solace.i18n import lazy_gettext, _
 from solace.auth import get_auth_system
 from solace.models import Topic, Post, Comment, User
+from solace.search import get_engine as get_search_engine
 
 
 def is_valid_email(form, value):
         return Comment(self.post, user, self['text'])
 
 
+class SearchForm(forms.Form):
+    """A form for searches."""
+    q = forms.TextField(lazy_gettext(u'Search for'), required=True)
+    order_by = forms.ChoiceField(lazy_gettext(u'Order by'), choices=[
+        ('relevance', lazy_gettext(u'Relevance')),
+        ('date', lazy_gettext(u'Date')),
+        ('votes', lazy_gettext(u'Votes')),
+        ('replies', lazy_gettext(u'Replies'))
+    ], widget=forms.RadioButtonGroup)
+    page = forms.IntegerField(min_value=1, required=False)
+    default_method = 'GET'
+    csrf_protected = False
+
+    def __init__(self, language=None, initial=None, action=None):
+        initial = forms.fill_dict(initial, order_by='relevance')
+        forms.Form.__init__(self, initial, action)
+        if language is None and self.request is not None:
+            language = self.request.view_lang
+        self.language = language
+
+    def get_results(self):
+        engine = get_search_engine()
+        return engine.query(self.data['q'], self.language,
+                            self.data['page'] or 1,
+                            order_by=self.data['order_by'])
+
+
 class BanUserForm(forms.Form):
     """Used to ban new users."""
     username = forms.TextField(lazy_gettext(u'Username'), required=True)
             doc.add_value(3, self._xap.sortable_serialise(post.votes))
             doc.add_value(4, self._xap.sortable_serialise(post.topic.reply_count))
             for tag in post.topic.tags:
-                doc.add_term(stemmer(
+                doc.add_term(stemmer(tag.name).lower())
         indexer.index_text(post.text)
         doc.add_term('CP%d' % post.id)
         doc.add_term('L%s' % post.topic.locale)

solace/static/layout.css

     position: relative;
 }
 
+ul.choicegroup {
+    list-style: none;
+    margin: 0;
+    padding: 0;
+}
+
 /* defintion datas and divs inside forms may contain ul.errors that are
    styled to float over the control that preceed it. */
 form dd, form div {

solace/static/solace.js

 
   /* make selects with the correct class submit forms on select */
   submitOnSelect : function(element) {
-    $('select', element).bind('change', function() {
+    $('select.submit_on_select', element).bind('change', function() {
       this.form.submit();
     });
   },

solace/templates/kb/search.html

+{% extends 'layout.html' %}
+{% from 'kb/_boxes.html' import render_topics %}
+{% set page_title = _('Search') %}
+{% block html_head %}
+{{- super() }}
+<link rel="alternate" href="{{ url_for('kb.overview_feed', order_by=order_by)
+  }}" type="application/atom+xml">
+{%- endblock %}
+{% block body %}
+  <h1>{{ page_title }}</h1>
+  {% call form() %}
+    <dl class="search">
+      {{ form.q.as_dd() }}
+      {{ form.order_by.as_dd() }}
+    </dl>
+    <div class="submit">
+      <input type="submit" value="{{ _('Search') }}">
+    </div>
+  {% endcall %}
+  {% if results %}
+  {{ render_topics(results) }}
+  {% endif %}
+{% endblock %}

solace/templates/layout.html

     ('kb.overview', true, _('Overview')),
     ('kb.unanswered', true, _('Unanswered')),
     ('kb.tags', true, _('Tags')),
+    ('kb.search', true, _('Search')),
     (('kb.userlist', true, _('Users')) if request.view_lang and settings.LANGUAGE_SECTIONS|length > 1
      else ('users.userlist', false, _('Users'))),
     ('badges.show_list', false, _('Badges'))]  %}
         Rule('/post/<int:id>/delete') > 'kb.delete_post',
         Rule('/post/<int:id>/restore') > 'kb.restore_post',
         Rule('/post/<int:id>/revisions') > 'kb.post_revisions',
-        Rule('/users/') > 'kb.userlist'
+        Rule('/users/') > 'kb.userlist',
+        Rule('/search') > 'kb.search'
     ]),
 
     # kb sections not depending on the lang code

solace/utils/forms.py

         attrs.setdefault('class', 'actions')
         return html.div(html.input(type='submit', value=label), **attrs)
 
-    def render(self, method='post', **attrs):
+    def render(self, method=None, **attrs):
         self._attr_setdefault(attrs)
         with_errors = attrs.pop('with_errors', False)
+        if method is None:
+            method = self._field.form.default_method
 
         # support jinja's caller
         caller = attrs.pop('caller', None)
     """
     __metaclass__ = FormMeta
 
-    csrf_protected = False
+    csrf_protected = None
     redirect_tracking = True
     captcha_protected = False
+    default_method = 'POST'
 
     def __init__(self, initial=None, action=None, request=None):
         if request is None:
         self.invalid_redirect_targets = set()
 
         if self.request is not None:
-            self.csrf_protected = True
+            if self.csrf_protected is None:
+                self.csrf_protected = True
             if self.action in (None, '', '.'):
                 self.action = request.url
             else:
                 self.action = urljoin(request.url, self.action)
+        elif self.csrf_protected is None:
+            self.csrf_protected = False
 
         self._root_field = _bind(self.__class__._root_field, self, {})
         self.reset()
         the form data of the current request is taken.
         """
         if data is None:
-            data = getattr(self.request, 'form', None)
-            if data is None:
+            if self.request is None:
                 raise RuntimeError('cannot validate implicitly without '
                                    'form being bound to request')
+            if self.default_method == 'GET':
+                data = self.request.args
+            elif self.default_method == 'POST':
+                data = self.request.form
+            else:
+                raise RuntimeError('for unknown methods you have to '
+                                   'explicitly provide a data dict')
         self.raw_data = _decode(data)
 
         # for each field in the root that requires validation on value

solace/views/kb.py

 from solace.utils.pagination import Pagination
 from solace.templating import render_template, get_macro
 from solace.i18n import _, format_datetime, list_sections
-from solace.forms import QuestionForm, ReplyForm, CommentForm
+from solace.forms import QuestionForm, ReplyForm, CommentForm, SearchForm
 from solace.utils.forms import Form as EmptyForm
 from solace.utils.formatting import format_creole_diff, format_creole
 from solace.utils.csrf import exchange_token_protected
     return common_userlist(request, locale=request.view_lang)
 
 
+def search(request):
+    """Allows searching for topics"""
+    form = SearchForm()
+    results = None
+    if 'q' in request.args and form.validate():
+        results = form.get_results()
+    return render_template('kb/search.html', results=results,
+                           form=form.as_widget())
+
+
 @no_cache
 @require_login
 @exchange_token_protected