Permalink
Browse files

Merge pull request #85 from willkg/opensearch

Opensearch
  • Loading branch information...
2 parents 8b790f1 + a8f803e commit ee5b1f13eba74c6425e555692bd19819a8719e98 @willkg committed Apr 22, 2012
@@ -27,6 +27,9 @@ class VideoIndex(indexes.SearchIndex, indexes.Indexable):
tags = indexes.MultiValueField()
speakers = indexes.MultiValueField()
+ # Used for suggestions in opensearch
+ title_auto = indexes.EdgeNgramField(model_attr='title')
+
def prepare(self, obj):
self.prepared_data = super(VideoIndex, self).prepare(obj)
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
+ <ShortName>{{ settings.SITE_TITLE }}</ShortName>
+ <Description>Find videos on {{ settings.SITE_TITLE }}.</Description>
+ <Url type="text/html" template="http://{{ site }}{{ url('haystack-search') }}?q={searchTerms}"/>
+ {% if settings.OPENSEARCH_ENABLE_SUGGESTIONS %}
+ <Url type="application/x-suggestions+json"
+ template="http://{{ site }}{{ url('videos-opensearch-suggestions') }}?q={searchTerms}"/>
+ {% endif %}
+</OpenSearchDescription>
@@ -14,8 +14,15 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import os
+import json
+import shutil
+
+from django.conf import settings
+from django.core import management
from django.core.urlresolvers import reverse
from django.test import TestCase
+from django.test.utils import override_settings
from nose.tools import eq_
@@ -26,6 +33,14 @@
class TestVideos(TestCase):
"""Tests for the ``videos`` app."""
+ def tearDown(self):
+ """Remove the search index after each test run.
+
+ The path is set in richard/settings_test.py."""
+ path = settings.HAYSTACK_CONNECTIONS['default']['PATH']
+ if os.path.exists(path):
+ shutil.rmtree(path)
+
# category
def test_category_list_empty(self):
@@ -241,3 +256,43 @@ def test_search(self):
resp = self.client.get(url)
eq_(resp.status_code, 200)
+
+ def test_opensearch_description(self):
+ """Test the opensearch description view."""
+ url = reverse('videos-opensearch')
+
+ resp = self.client.get(url)
+ eq_(resp.status_code, 200)
+
+ @override_settings(OPENSEARCH_ENABLE_SUGGESTIONS=True)
+ def test_opensearch_description_with_suggestions(self):
+ """Test the opensearch description view."""
+ url = reverse('videos-opensearch')
+
+ resp = self.client.get(url)
+ eq_(resp.status_code, 200)
+
+ @override_settings(OPENSEARCH_ENABLE_SUGGESTIONS=True)
+ def test_opensearch_suggestions(self):
+ """Test the opensearch suggestions view."""
+ video(title='introduction to pypy', save=True)
+ video(title='django testing', save=True)
+ video(title='pycon 2012 keynote', save=True)
+ video(title='Speedily Practical Large-Scale Tests', save=True)
+ management.call_command('rebuild_index', interactive=False)
+
+ url = reverse('videos-opensearch-suggestions')
+
+ response = self.client.get(url, {'q': 'test'})
+ eq_(response.status_code, 200)
+ data = json.loads(response.content)
+ eq_(data[0], 'test')
+ eq_(set(data[1]),
+ set(['django testing', 'Speedily Practical Large-Scale Tests']))
+
+ def test_opensearch_suggestions_disabled(self):
+ """Test that when suggestions are disabled, the view does nothing."""
+ url = reverse('videos-opensearch-suggestions')
+
+ response = self.client.get(url, {'q': 'test'})
+ eq_(response.status_code, 404)
View
@@ -14,6 +14,7 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+from django.conf import settings
from django.conf.urls.defaults import patterns, url, include
from haystack.views import SearchView, search_view_factory
from haystack.forms import ModelSearchForm
@@ -61,6 +62,10 @@
template='videos/search.html',
form_class=ModelSearchForm),
name='haystack-search'),
+ url(r'^search/xml/?$',
+ 'opensearch', name='videos-opensearch'),
+ url(r'^search/suggestions/$',
+ 'opensearch_suggestions', name='videos-opensearch-suggestions'),
# faux api for carl
url(r'^api/1.0/videos/urlforsource$',
View
@@ -15,10 +15,13 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import bleach
+import json
-
+from django.conf import settings
+from django.contrib.sites.models import Site
from django.http import Http404, HttpResponse
from django.shortcuts import get_object_or_404, render
+from haystack.query import SearchQuerySet
from videos import models
@@ -91,6 +94,31 @@ def video(request, video_id, slug):
return ret
+def opensearch(request):
+ """Return opensearch description document."""
+ ret = render(
+ request, 'videos/opensearch.xml',
+ {'site': Site.objects.get_current()},
+ content_type='application/opensearchdescription+xml')
+ return ret
+
+
+def opensearch_suggestions(request):
+ """Return suggestions for a search query.
+
+ Implements the OpenSearch suggestions extension.
+ """
+ if not settings.OPENSEARCH_ENABLE_SUGGESTIONS:
+ raise Http404
+
+ query = request.GET.get('q', '')
+ sqs = (SearchQuerySet().filter(title_auto=query)
+ .values_list('title_auto', flat=True))
+ result = [query, list(sqs)]
+
+ return JSONResponse(json.dumps(result))
+
+
# TODO: Move this elsewhere
class JSONResponse(HttpResponse):
View
@@ -224,6 +224,8 @@
MEDIA_PREFERENCE = ('ogv', 'webm', 'mp4', 'flv',)
+OPENSEARCH_ENABLE_SUGGESTIONS = False
+
try:
from richard.settings_local import *
except ImportError:
View
@@ -1 +1,13 @@
# Override settings for test environment here
+
+import os
+
+# site_root is the parent directory
+SITE_ROOT = os.path.dirname(os.path.dirname(__file__))
+
+HAYSTACK_CONNECTIONS = {
+ 'default': {
+ 'ENGINE': 'haystack.backends.whoosh_backend.WhooshEngine',
+ 'PATH': os.path.join(SITE_ROOT, '_test_whoosh_index'),
+ },
+}
@@ -21,6 +21,7 @@
<link rel="stylesheet" href="{{ settings.STATIC_URL }}bootstrap/css/bootstrap.min.css">
<link rel="stylesheet" href="{{ settings.STATIC_URL }}bootstrap/css/bootstrap-responsive.min.css">
<link rel="stylesheet" href="{{ settings.STATIC_URL }}css/richard.css">
+ <link rel="search" type="application/opensearchdescription+xml" title="{{ settings.SITE_TITLE }} search" href="{{ url('videos-opensearch') }}">
<title>{% block title %}{{ page_title() }}{% endblock %}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{% if meta %}

0 comments on commit ee5b1f1

Please sign in to comment.