Navigation Menu

Skip to content

Commit

Permalink
Merge branch '555251-json_empty' into development
Browse files Browse the repository at this point in the history
  • Loading branch information
Paul Craciunoiu committed Apr 20, 2010
2 parents 5c27b21 + 6394f63 commit 6452ead
Show file tree
Hide file tree
Showing 5 changed files with 682 additions and 529 deletions.
34 changes: 34 additions & 0 deletions apps/search/tests/test_json.py
Expand Up @@ -2,6 +2,8 @@

from django.test.client import Client

import test_utils

from sumo.urlresolvers import reverse

from .test_search import SphinxTestCase
Expand Down Expand Up @@ -51,3 +53,35 @@ def test_json_callback_validation(self):
})
eq_(response['Content-Type'], 'application/x-javascript')
eq_(response.status_code, status)

def test_json_empty_query(self):
"""Empty query returns JSON format"""
c = Client()

# Test with flags for advanced search or not
a_types = (0, 1, 2)
for a in a_types:
response = c.get(reverse('search'), {
'format': 'json', 'a': a,
})
eq_(response['Content-Type'], 'application/json')


def test_json_down():
"""When the Sphinx is down, return JSON and 503 status"""
c = Client()

# Test with flags for advanced search or not
callbacks = (
('', 503, 'application/json'),
('validCallback', 503, 'application/x-javascript'),
# Invalid callback does not search
('eval("xss");a', 400, 'application/x-javascript'),
)
for callback, status, mimetype in callbacks:
response = c.get(reverse('search'), {
'q': 'json down', 'format': 'json',
'callback': callback,
})
eq_(response['Content-Type'], mimetype);
eq_(response.status_code, status)
119 changes: 109 additions & 10 deletions apps/search/tests/test_search.py
Expand Up @@ -102,6 +102,21 @@ def create_extra_tables():
(62, 14), (62, 19), (62, 25);
""")

cursor.execute("""
INSERT IGNORE INTO tiki_freetags (tagId, tag, raw_tag, lang) VALUES
(1, 'installation', 'installation', 'en'),
(26, 'video', 'video', 'en'),
(28, 'realplayer', 'realplayer', 'en');
""")

cursor.execute("""
INSERT IGNORE INTO tiki_freetagged_objects (tagId, objectId, user,
created) VALUES
(1, 5, 'admin', 1185895872),
(26, 62, 'np', 188586976),
(28, 62, 'Vectorspace', 186155207);
""")

cursor.execute('SET SQL_NOTES=@OLD_SQL_NOTES;')


Expand Down Expand Up @@ -163,27 +178,41 @@ def tearDownClass(cls):

class SearchTest(SphinxTestCase):

def setUp(self):
SphinxTestCase.setUp(self)
self.client = client.Client()

def test_indexer(self):
wc = WikiClient()
results = wc.query('practice')
self.assertNotEquals(0, len(results))

def test_content(self):
"""Ensure template is rendered with no errors for a common search"""
response = self.client.get(reverse('search'), {'q': 'audio', 'w': 3})
self.assertEquals(response['Content-Type'],
'text/html; charset=utf-8')
self.assertEquals(response.status_code, 200)

def test_category_filter(self):
wc = WikiClient()
results = wc.query('', ({'filter': 'category', 'value': [13]},))
self.assertNotEquals(0, len(results))

def test_category_exclude(self):
c = client.Client()
response = c.get(reverse('search'),
{'q': 'audio', 'format': 'json', 'w': 3})
response = self.client.get(reverse('search'),
{'q': 'audio', 'format': 'json', 'w': 3})
self.assertNotEquals(0, json.loads(response.content)['total'])

response = c.get(reverse('search'),
{'q': 'audio', 'category': [-13] +
list(settings.SEARCH_DEFAULT_CATEGORIES),
'format': 'json', 'w': 1})
self.assertEquals(0, json.loads(response.content)['total'])
response = self.client.get(reverse('search'),
{'q': 'audio', 'category': -13,
'format': 'json', 'w': 1})
self.assertEquals(1, json.loads(response.content)['total'])

def test_category_invalid(self):
qs = {'a': 1, 'w': 3, 'format': 'json', 'category': 'invalid'}
response = self.client.get(reverse('search'), qs)
self.assertNotEquals(0, json.loads(response.content)['total'])

def test_no_filter(self):
"""Test searching with no filters."""
Expand All @@ -204,9 +233,8 @@ def test_range_filter(self):

def test_search_en_locale(self):
"""Searches from the en-US locale should return documents from en."""
c = client.Client()
qs = {'q': 'contribute', 'w': 1, 'format': 'json', 'category': 23}
response = c.get(reverse('search'), qs)
response = self.client.get(reverse('search'), qs)
self.assertNotEquals(0, json.loads(response.content)['total'])

def test_sort_mode(self):
Expand All @@ -226,6 +254,77 @@ def test_sort_mode(self):
results[-1]['attrs'][test_for[i]])
i += 1

def test_lastmodif(self):
qs = {'a': 1, 'w': 3, 'format': 'json', 'lastmodif': 1}
response = self.client.get(reverse('search'), qs)
self.assertNotEquals(0, json.loads(response.content)['total'])

def test_created(self):
qs = {'a': 1, 'w': 2, 'format': 'json',
'sortby': 2, 'created_date': '10/13/2008'}
created_vals = (
(1, '/8288'),
(2, '/185508'),
)

for created, url_id in created_vals:
qs.update({'created': created})
response = self.client.get(reverse('search'), qs)
self.assertEquals(url_id, json.loads(response.content)['results']
[-1]['url'][-len(url_id):])

def test_created_invalid(self):
"""Invalid created_date is ignored."""
qs = {'a': 1, 'w': 2, 'format': 'json',
'created': 1, 'created_date': 'invalid'}
response = self.client.get(reverse('search'), qs)
self.assertEquals(9, json.loads(response.content)['total'])

def test_author(self):
"""Check several author values, including test for (anon)"""
qs = {'a': 1, 'w': 2, 'format': 'json'}
author_vals = (
('DoesNotExist', 0),
('Andreas Gustafsson', 1),
('Bob', 2),
)

for author, total in author_vals:
qs.update({'author': author})
response = self.client.get(reverse('search'), qs)
self.assertEquals(total, json.loads(response.content)['total'])

def test_status(self):
qs = {'a': 1, 'w': 2, 'format': 'json'}
status_vals = (
(91, 8),
(92, 2),
(93, 1),
(94, 2),
(95, 1),
(96, 3),
)

for status, total in status_vals:
qs.update({'status': status})
response = self.client.get(reverse('search'), qs)
self.assertEquals(total, json.loads(response.content)['total'])

def test_tags(self):
"""Search for tags, includes multiple"""
qs = {'a': 1, 'w': 1, 'format': 'json'}
tags_vals = (
('doesnotexist', 0),
('video', 1),
('realplayer video', 1),
('realplayer installation', 0),
)

for tag_string, total in tags_vals:
qs.update({'tags': tag_string})
response = self.client.get(reverse('search'), qs)
self.assertEquals(total, json.loads(response.content)['total'])

def test_unicode_excerpt(self):
"""Unicode characters in the excerpt should not be a problem."""
wc = WikiClient()
Expand Down
102 changes: 61 additions & 41 deletions apps/search/views.py
Expand Up @@ -43,11 +43,12 @@ def clean(self):
# Validate created date
if cleaned_data['created_date'] != '':
try:
cleaned_data['created_date'] = int(time.mktime(
created_timestamp = time.mktime(
time.strptime(cleaned_data['created_date'],
'%m/%d/%Y'), ))
'%m/%d/%Y'))
cleaned_data['created_date'] = int(created_timestamp)
except ValueError:
raise ValidationError('Invalid created date.')
cleaned_data['created'] = None

# Set defaults for MultipleChoiceFields and convert to ints.
# Ticket #12398 adds TypedMultipleChoiceField which would replace
Expand All @@ -66,49 +67,68 @@ def clean(self):

return cleaned_data

class NoValidateMultipleChoiceField(forms.MultipleChoiceField):
def valid_value(self, value):
return True

# Common fields
q = forms.CharField(required=False)

w = forms.TypedChoiceField(widget=forms.HiddenInput,
required=False,
coerce=int,
empty_value=constants.WHERE_ALL,
choices=((constants.WHERE_FORUM, None),
(constants.WHERE_WIKI, None),
(constants.WHERE_ALL, None)))
required=False, coerce=int,
empty_value=constants.WHERE_ALL,
choices=((constants.WHERE_FORUM, None),
(constants.WHERE_WIKI, None),
(constants.WHERE_ALL, None)))

a = forms.IntegerField(widget=forms.HiddenInput, required=False)

# KB fields
tags = forms.CharField(label=_('Tags'), required=False)

language = forms.ChoiceField(label=_('Language'), required=False,
language = forms.ChoiceField(
label=_('Language'), required=False,
choices=[(LOCALES[k].external, LOCALES[k].native) for
k in settings.SUMO_LANGUAGES])

categories = [(cat.categId, cat.name) for
cat in Category.objects.all()]
category = forms.MultipleChoiceField(
category = NoValidateMultipleChoiceField(
widget=forms.CheckboxSelectMultiple,
label=_('Category'), choices=categories, required=False)

# Forum fields
status = forms.TypedChoiceField(label=_('Post status'), coerce=int,
choices=constants.STATUS_LIST, empty_value=0, required=False)
status = forms.TypedChoiceField(
label=_('Post status'), coerce=int, empty_value=0,
choices=constants.STATUS_LIST, required=False)
author = forms.CharField(required=False)

created = forms.TypedChoiceField(label=_('Created'), coerce=int,
choices=constants.CREATED_LIST, empty_value=0, required=False)
created = forms.TypedChoiceField(
label=_('Created'), coerce=int, empty_value=0,
choices=constants.CREATED_LIST, required=False)
created_date = forms.CharField(required=False)

lastmodif = forms.TypedChoiceField(label=_('Last updated'), coerce=int,
choices=constants.LUP_LIST, empty_value=0, required=False)
sortby = forms.TypedChoiceField(label=_('Sort results by'), coerce=int,
choices=constants.SORTBY_LIST, empty_value=0, required=False)
lastmodif = forms.TypedChoiceField(
label=_('Last updated'), coerce=int, empty_value=0,
choices=constants.LUP_LIST, required=False)
sortby = forms.TypedChoiceField(
label=_('Sort results by'), coerce=int, empty_value=0,
choices=constants.SORTBY_LIST, required=False)

forums = [(f.forumId, f.name) for f in Forum.objects.all()]
forum = forms.MultipleChoiceField(label=_('Search in forum'),
choices=forums, required=False)
forum = NoValidateMultipleChoiceField(label=_('Search in forum'),
choices=forums, required=False)

# JSON-specific variables
is_json = (request.GET.get('format') == 'json')
callback = request.GET.get('callback', '').strip()
mimetype = 'application/x-javascript' if callback else 'application/json'

# Check callback is valid
if is_json and callback and not jsonp_is_valid(callback):
return HttpResponse(
json.dumps({'error': _('Invalid callback function.')}),
mimetype=mimetype, status=400)

language = request.GET.get('language', request.locale)
if not language in LOCALES:
Expand All @@ -119,7 +139,7 @@ def clean(self):
# Search default values
try:
category = map(int, r.getlist('category')) or \
settings.SEARCH_DEFAULT_CATEGORIES
settings.SEARCH_DEFAULT_CATEGORIES
except ValueError:
category = settings.SEARCH_DEFAULT_CATEGORIES
r.setlist('category', [x for x in category if x > 0])
Expand All @@ -137,9 +157,15 @@ def clean(self):
search_form = SearchForm(r)

if not search_form.is_valid() or a == '2':
return jingo.render(request, 'form.html',
{'advanced': a, 'request': request,
'search_form': search_form})
if is_json:
return HttpResponse(
json.dumps({'error': _('Invalid search data.')}),
mimetype=mimetype,
status=400)
else:
return jingo.render(request, 'form.html',
{'advanced': a, 'request': request,
'search_form': search_form})

cleaned = search_form.cleaned_data
search_locale = (crc32(LOCALES[language].internal),)
Expand Down Expand Up @@ -268,14 +294,19 @@ def clean(self):

documents += fc.query(cleaned['q'], filters_f)
except SearchError:
return jingo.render(request, 'down.html', {}, status=503)
if is_json:
return HttpResponse(json.dumps({'error':
_('Search Unavailable')}),
mimetype=mimetype, status=503)
else:
return jingo.render(request, 'down.html', {}, status=503)

pages = paginate(request, documents, settings.SEARCH_RESULTS_PER_PAGE)

results = []
for i in range(offset, offset + settings.SEARCH_RESULTS_PER_PAGE):
try:
if documents[i]['attrs'].get('category', False):
if documents[i]['attrs'].get('category', False) != False:
wiki_page = WikiPage.objects.get(pk=documents[i]['id'])

excerpt = wc.excerpt(wiki_page.data, cleaned['q'])
Expand Down Expand Up @@ -307,29 +338,18 @@ def clean(self):

refine_query = u'?%s' % urlencode(items)

if request.GET.get('format') == 'json':
callback = request.GET.get('callback', '').strip()
# Check callback is valid
if callback and not jsonp_is_valid(callback):
return HttpResponse('', mimetype='application/x-javascript',
status=400)

if is_json:
data = {}
data['results'] = results
data['total'] = len(documents)
data['total'] = len(results)
data['query'] = cleaned['q']
if not results:
data['message'] = _('No pages matched the search criteria')
json_data = json.dumps(data)
if callback:
json_data = callback + '(' + json_data + ');'
response = HttpResponse(json_data,
mimetype='application/x-javascript')
else:
response = HttpResponse(json_data,
mimetype='application/json')

return response
return HttpResponse(json_data, mimetype=mimetype)

return jingo.render(request, 'results.html',
{'num_results': len(documents), 'results': results, 'q': cleaned['q'],
Expand Down

0 comments on commit 6452ead

Please sign in to comment.