Implementes an advanced search feature, which allows for individual field terms

This commit is contained in:
Roberto Rosario
2011-06-22 00:47:37 -04:00
parent ec31f0ecce
commit 604ef90f79
6 changed files with 160 additions and 66 deletions

View File

@@ -323,5 +323,16 @@ class RecentDocument(models.Model):
# Register the fields that will be searchable
register(Document, _(u'document'), [u'document_type__name', u'file_mimetype', u'file_filename', u'file_extension', u'documentmetadata__value', u'documentpage__content', u'description', u'tags__name', u'comments__comment'])
register('document', Document, _(u'document'), [
{'name': u'document_type__name', 'title': _(u'Document type')},
{'name': u'file_mimetype', 'title': _(u'MIME type')},
{'name': u'file_filename', 'title': _(u'Filename')},
{'name': u'file_extension', 'title': _(u'Filename extension')},
{'name': u'documentmetadata__value', 'title': _(u'Metadata value')},
{'name': u'documentpage__content', 'title': _(u'Content')},
{'name': u'description', 'title': _(u'Description')},
{'name': u'tags__name', 'title': _(u'Tags')},
{'name': u'comments__comment', 'title': _(u'Comments')},
]
)
#register(Document, _(u'document'), ['document_type__name', 'file_mimetype', 'file_extension', 'documentmetadata__value', 'documentpage__content', 'description', {'field_name':'file_filename', 'comparison':'iexact'}])

View File

@@ -1,3 +1,10 @@
from navigation.api import register_sidebar_template
from django.utils.translation import ugettext_lazy as _
register_sidebar_template(['search'], 'search_help.html')
from navigation.api import register_sidebar_template, register_links
search = {'text': _(u'search'), 'view': 'search', 'famfam': 'zoom'}
search_advanced = {'text': _(u'advanced search'), 'view': 'search_advanced', 'famfam': 'zoom_in'}
register_sidebar_template(['search', 'search_advanced'], 'search_help.html')
register_links(['search', 'search_advanced'], [search, search_advanced], menu_name='form_header')

View File

@@ -9,26 +9,23 @@ from django.db.models import Q
from dynamic_search.conf.settings import LIMIT
search_list = {}
registered_search_dict = {}
def register(model, text, field_list):
if model in search_list:
search_list[model]['fields'].append(field_list)
else:
search_list[model] = {'fields': field_list, 'text': text}
def register(model_name, model, title, fields):
registered_search_dict.setdefault(model_name, {'model': model, 'fields': [], 'title': title})
registered_search_dict[model_name]['fields'].extend(fields)
def normalize_query(query_string,
findterms=re.compile(r'"([^"]+)"|(\S+)').findall,
normspace=re.compile(r'\s{2,}').sub):
""" Splits the query string in invidual keywords, getting rid of unecessary spaces
"""
Splits the query string in invidual keywords, getting rid of unecessary spaces
and grouping quoted words together.
Example:
>>> normalize_query(' some random words "with quotes " and spaces')
['some', 'random', 'words', 'with quotes', 'and', 'spaces']
"""
return [normspace(' ', (t[0] or t[1]).strip()) for t in findterms(query_string)]
@@ -60,19 +57,53 @@ def get_query(terms, search_fields):
return queries
def perform_search(query_string):
def perform_search(query_string, field_list=None):
model_list = {}
flat_list = []
result_count = 0
shown_result_count = 0
elapsed_time = 0
start_time = datetime.datetime.now()
search_dict = {}
if query_string:
start_time = datetime.datetime.now()
terms = normalize_query(query_string)
simple_query_string = query_string.get('q', u'').strip()
if simple_query_string:
for model, values in registered_search_dict.items():
search_dict.setdefault(values['model'], {'query_entries': [], 'title': values['title']})
field_names = [field['name'] for field in values['fields']]
# One entry, single set of terms for all fields names
search_dict[values['model']]['query_entries'].append(
{
'field_name': field_names,
'terms': normalize_query(simple_query_string)
}
)
else:
for key, value in query_string.items():
try:
model, field_name = key.split('__', 1)
model_entry = registered_search_dict.get(model, {})
if model_entry:
for model_field in model_entry.get('fields', [{}]):
if model_field.get('name') == field_name:
search_dict.setdefault(model_entry['model'], {'query_entries': [], 'title': model_entry['title']})
search_dict[model_entry['model']]['query_entries'].append(
{
'field_name': [field_name],
'terms': normalize_query(value.strip())
}
)
except ValueError:
pass
for model, data in search_list.items():
queries = get_query(terms, data['fields'])
for model, data in search_dict.items():
title = data['title']
queries = []
for query_entry in data['query_entries']:
queries.extend(get_query(query_entry['terms'], query_entry['field_name']))
model_result_ids = None
for query in queries:
@@ -84,14 +115,24 @@ def perform_search(query_string):
else:
model_result_ids &= single_result_ids
if model_result_ids == None:
model_result_ids = []
result_count += len(model_result_ids)
results = model.objects.in_bulk(list(model_result_ids)[: LIMIT]).values()
shown_result_count += len(results)
if results:
model_list[data['text']] = results
model_list[title] = results
for result in results:
if result not in flat_list:
flat_list.append(result)
elapsed_time = unicode(datetime.datetime.now() - start_time).split(':')[2]
return model_list, flat_list, shown_result_count, result_count, elapsed_time
return {
'model_list': model_list,
'flat_list': flat_list,
'shown_result_count': shown_result_count,
'result_count': result_count,
'elapsed_time': elapsed_time
}

View File

@@ -3,4 +3,17 @@ from django.utils.translation import ugettext_lazy as _
class SearchForm(forms.Form):
q = forms.CharField(max_length=128, label=_(u'Search term'))
q = forms.CharField(max_length=128, label=_(u'Search terms'))
class AdvancedSearchForm(forms.Form):
def __init__(self, *args, **kwargs):
search_fields = kwargs.pop('search_fields')
super(AdvancedSearchForm, self).__init__(*args, **kwargs)
#Set form fields initial values
for search_field in search_fields:
self.fields[search_field['name']] = forms.CharField(
label=search_field['title'],
required=False
)

View File

@@ -2,5 +2,6 @@ from django.conf.urls.defaults import patterns, url
urlpatterns = patterns('dynamic_search.views',
url(r'^search/$', 'search', (), 'search'),
url(r'^search/advanced/$', 'search', { 'advanced': True }, 'search_advanced'),
url(r'^results/$', 'results', (), 'results'),
)

View File

@@ -4,48 +4,18 @@ from django.utils.translation import ugettext_lazy as _
from django.contrib import messages
from django.conf import settings
from dynamic_search.api import perform_search
from dynamic_search.forms import SearchForm
from dynamic_search.api import perform_search, registered_search_dict
from dynamic_search.forms import SearchForm, AdvancedSearchForm
from dynamic_search.conf.settings import SHOW_OBJECT_TYPE
from dynamic_search.conf.settings import LIMIT
def results(request, form=None):
def results(request, extra_context=None):
query_string = ''
context = {}
if ('q' in request.GET) and request.GET['q'].strip():
query_string = request.GET['q']
try:
model_list, flat_list, shown_result_count, total_result_count, elapsed_time = perform_search(query_string)
if shown_result_count != total_result_count:
title = _(u'results with: %(query_string)s (showing only %(shown_result_count)s out of %(total_result_count)s)') % {
'query_string': query_string, 'shown_result_count': shown_result_count,
'total_result_count': total_result_count}
else:
title = _(u'results with: %s') % query_string
context.update({
'found_entries': model_list,
'object_list': flat_list,
'title': title,
'time_delta': elapsed_time,
})
except Exception, e:
if settings.DEBUG:
raise
elif request.user.is_staff or request.user.is_superuser:
messages.error(request, _(u'Search error: %s') % e)
else:
context.update({
'found_entries': [],
'object_list': [],
'title': _(u'results'),
})
context.update({
'query_string': query_string,
'form': form,
'query_string': request.GET,
'form_title': _(u'Search'),
#'hide_header': True,
'form_hide_required_text': True,
@@ -56,6 +26,30 @@ def results(request, form=None):
'search_results_limit': LIMIT,
})
if extra_context:
context.update(extra_context)
try:
response = perform_search(request.GET)
if response['shown_result_count'] != response['result_count']:
title = _(u'results, (showing only %(shown_result_count)s out of %(result_count)s)') % {
'shown_result_count': response['shown_result_count'],
'result_count': response['result_count']}
else:
title = _(u'results')
context.update({
'found_entries': response['model_list'],
'object_list': response['flat_list'],
'title': title,
'time_delta': response['elapsed_time'],
})
except Exception, e:
if settings.DEBUG:
raise
elif request.user.is_staff or request.user.is_superuser:
messages.error(request, _(u'Search error: %s') % e)
if SHOW_OBJECT_TYPE:
context.update({'extra_columns':
[{'name': _(u'type'), 'attribute': lambda x: x._meta.verbose_name[0].upper() + x._meta.verbose_name[1:]}]})
@@ -64,11 +58,38 @@ def results(request, form=None):
context_instance=RequestContext(request))
def search(request):
def search(request, advanced=False):
if advanced:
search_fields = []
for model_name, values in registered_search_dict.items():
for field in values['fields']:
search_fields.append(
{
'title': field['title'],
'name': '%s__%s' % (model_name, field['name'])
}
)
form = AdvancedSearchForm(
search_fields=search_fields,
data=request.GET
)
return results(request, extra_context={
'form': form,
'form_title': _(u'advanced search')
}
)
else:
if ('q' in request.GET) and request.GET['q'].strip():
query_string = request.GET['q']
form = SearchForm(initial={'q': query_string})
return results(request, form=form)
return results(request, extra_context={
'form': form
}
)
else:
form = SearchForm()
return results(request, form=form)
return results(request, extra_context={
'form': form
}
)