0002-add-internal-page-contents-search-service-6793.patch
combo/apps/search/models.py | ||
---|---|---|
20 | 20 |
from django import template |
21 | 21 |
from django.http import HttpResponse |
22 | 22 |
from django.core.exceptions import PermissionDenied |
23 |
from django.core.urlresolvers import reverse |
|
23 | 24 |
from django.forms import models as model_forms, Select |
24 | 25 |
from django.utils.http import quote |
25 | 26 | |
... | ... | |
37 | 38 |
class Meta: |
38 | 39 |
verbose_name = _('Search') |
39 | 40 | |
40 |
@classmethod |
|
41 |
def is_enabled(cls): |
|
42 |
return bool(getattr(settings, 'COMBO_SEARCH_SERVICES', {})) |
|
43 | ||
44 | 41 |
def is_visible(self, user=None): |
42 |
if not self.search_service: |
|
43 |
return False |
|
45 | 44 |
return super(SearchCell, self).is_visible(user=user) |
46 | 45 | |
47 | 46 |
def get_default_form_class(self): |
48 | 47 |
search_services = [(None, _('Not configured'))] |
48 |
search_services.append(('_text', _('Page Contents'))) |
|
49 | 49 |
search_services.extend([(code, service['label']) |
50 | 50 |
for code, service in getattr(settings, 'COMBO_SEARCH_SERVICES', {}).items()]) |
51 | 51 |
widgets = {'_search_service': Select(choices=search_services)} |
... | ... | |
62 | 62 | |
63 | 63 |
@property |
64 | 64 |
def search_service(self): |
65 |
if self._search_service == '_text': |
|
66 |
return {'url': reverse('api-search') + '?q=%(q)s', 'label': _('Page Contents')} |
|
65 | 67 |
return settings.COMBO_SEARCH_SERVICES.get(self._search_service) or {} |
66 | 68 | |
67 | 69 |
def modify_global_context(self, context, request): |
... | ... | |
100 | 102 |
query = request.GET.get('q') |
101 | 103 |
if query and cell.search_service.get('url'): |
102 | 104 |
url = cell.search_service.get('url') % {'q': quote(query.encode('utf-8'))} |
105 |
if url.startswith('/'): |
|
106 |
url = request.build_absolute_uri(url) |
|
103 | 107 |
results = requests.get(url, cache_duration=0).json() |
104 | 108 |
else: |
105 | 109 |
results = {'err': 0, 'data': []} |
combo/apps/search/templates/combo/search-cell-results.html | ||
---|---|---|
1 | 1 |
<div class="links-list"> |
2 | 2 |
<ul> |
3 | 3 |
{% for item in results.data %} |
4 |
<li><a href="{{ item.url }}">{{ item.text }}</a>{% if item.description %}
|
|
5 |
{{ item.description|safe }}
|
|
6 |
{% endif %}</li>
|
|
4 |
<li><a href="{{ item.url }}">{{ item.text }}</a> |
|
5 |
{% if item.description %}<div>{{ item.description|safe }}</div>{% endif %}
|
|
6 |
</li> |
|
7 | 7 |
{% endfor %} |
8 | 8 |
</ul> |
9 | 9 |
</div> |
combo/public/urls.py | ||
---|---|---|
20 | 20 | |
21 | 21 |
urlpatterns = patterns('combo.publicviews', |
22 | 22 |
url(r'^api/menu-badges/', views.menu_badges), |
23 |
url(r'^api/search/', views.api_search, name='api-search'), |
|
23 | 24 |
url(r'^ajax/cell/(?P<page_pk>\w+)/(?P<cell_reference>[\w_-]+)/$', |
24 | 25 |
views.ajax_page_cell, name='combo-public-ajax-page-cell'), |
25 | 26 |
(r'__style__/$', views.style), |
combo/public/views.py | ||
---|---|---|
38 | 38 |
from django.utils.translation import ugettext as _ |
39 | 39 |
from django.forms.widgets import Media |
40 | 40 | |
41 |
from haystack.query import SearchQuerySet |
|
42 | ||
41 | 43 |
if 'mellon' in settings.INSTALLED_APPS: |
42 | 44 |
from mellon.utils import get_idps |
43 | 45 |
else: |
44 | 46 |
get_idps = lambda: [] |
45 | 47 | |
46 | 48 |
from combo.data.models import CellBase, Page, ParentContentCell, TextCell |
49 |
from combo.apps.search.models import SearchCell |
|
47 | 50 |
from combo import utils |
48 | 51 | |
49 | 52 | |
... | ... | |
342 | 345 |
if badge: |
343 | 346 |
badges[cell.page_id] = badge |
344 | 347 |
return HttpResponse(json.dumps(badges)) |
348 | ||
349 | ||
350 |
def api_search(request): |
|
351 |
for cell in SearchCell.objects.filter(_search_service='_text'): |
|
352 |
if not cell.is_visible(request.user): |
|
353 |
continue |
|
354 |
break |
|
355 |
else: |
|
356 |
raise Http404() |
|
357 |
query = request.GET.get('q') or '' |
|
358 |
searchqueryset = SearchQuerySet() |
|
359 |
sqs = searchqueryset.auto_query(query).highlight() |
|
360 |
sqs.load_all() |
|
361 |
hits = [] |
|
362 |
for page in sqs: |
|
363 |
if page.highlighted['text']: |
|
364 |
description = '<p>%s</p>' % page.highlighted['text'][0] |
|
365 |
hits.append({ |
|
366 |
'text': page.title, |
|
367 |
'url': page.url, |
|
368 |
'description': description, |
|
369 |
}) |
|
370 | ||
371 |
return HttpResponse(json.dumps({'data': hits}), content_type='application/json') |
tests/settings.py | ||
---|---|---|
24 | 24 |
import tempfile |
25 | 25 |
MEDIA_ROOT = tempfile.mkdtemp('combo-test') |
26 | 26 | |
27 |
HAYSTACK_CONNECTIONS['default']['PATH'] = os.path.join( |
|
28 |
tempfile.mkdtemp('combo-test-whoosh')) |
|
29 | ||
27 | 30 |
if 'DISABLE_MIGRATIONS' in os.environ: |
28 | 31 |
class DisableMigrations(object): |
29 | 32 |
def __contains__(self, item): |
tests/test_search.py | ||
---|---|---|
36 | 36 |
def __exit__(self, *args, **kwargs): |
37 | 37 |
delattr(settings, 'COMBO_SEARCH_SERVICES') |
38 | 38 | |
39 |
def test_enabled(app): |
|
40 |
assert SearchCell.is_enabled() == False |
|
41 |
with SearchServices(SEARCH_SERVICES): |
|
42 |
assert SearchCell.is_enabled() == True |
|
43 |
with SearchServices({}): |
|
44 |
assert SearchCell.is_enabled() == False |
|
45 | ||
46 | 39 |
def test_search_cell(app): |
47 | 40 |
with SearchServices(SEARCH_SERVICES): |
48 | 41 |
page = Page(title='Search', slug='search_page', template_name='standard') |
... | ... | |
169 | 162 |
prepared_data = page_index.prepare(page) |
170 | 163 |
assert page.title in prepared_data['text'] |
171 | 164 |
assert 'foobar' in prepared_data['text'] |
165 | ||
166 |
def test_search_api(app): |
|
167 |
page = Page(title='example page', slug='example-page') |
|
168 |
page.save() |
|
169 | ||
170 |
cell = TextCell(page=page, text='<p>foobar baz</p>', order=0) |
|
171 |
cell.save() |
|
172 | ||
173 |
second_page = Page(title='second page', slug='second-page') |
|
174 |
second_page.save() |
|
175 | ||
176 |
cell = TextCell(page=second_page, text='<p>other baz</p>', order=0) |
|
177 |
cell.save() |
|
178 | ||
179 |
page_index = PageIndex() |
|
180 |
page_index.reindex() |
|
181 | ||
182 |
resp = app.get('/api/search/?q=foobar', status=404) |
|
183 | ||
184 |
cell = SearchCell(page=page, _search_service='_text', order=0) |
|
185 |
cell.save() |
|
186 | ||
187 |
resp = app.get('/api/search/?q=foobar', status=200) |
|
188 |
assert len(resp.json['data']) == 1 |
|
189 |
assert resp.json['data'][0]['text'] == 'example page' |
|
190 | ||
191 |
resp = app.get('/api/search/?q=other', status=200) |
|
192 |
assert len(resp.json['data']) == 1 |
|
193 |
assert resp.json['data'][0]['text'] == 'second page' |
|
194 | ||
195 |
resp = app.get('/api/search/?q=baz', status=200) |
|
196 |
assert len(resp.json['data']) == 2 |
|
197 | ||
198 |
resp = app.get('/api/search/?q=quux', status=200) |
|
199 |
assert len(resp.json['data']) == 0 |
|
172 |
- |