diff --git a/apps/documents/models.py b/apps/documents/models.py index fcce10b6c2..e182662072 100644 --- a/apps/documents/models.py +++ b/apps/documents/models.py @@ -6,6 +6,8 @@ from datetime import datetime from django.db import models from django.utils.translation import ugettext_lazy as _ +from dynamic_search.api import register + from documents.conf.settings import AVAILABLE_FUNCTIONS @@ -50,7 +52,6 @@ class Document(models.Model): @models.permalink def get_absolute_url(self): return ('document_view', [self.id]) - available_functions_string = (_(u' Available functions: %s') % ','.join(['%s()' % name for name, function in AVAILABLE_FUNCTIONS.items()])) if AVAILABLE_FUNCTIONS else '' @@ -96,3 +97,6 @@ class DocumentMetadata(models.Model): class Meta: verbose_name = _(u'document metadata') verbose_name_plural = _(u'document metadata') + + +register(Document, _(u'document'), ['document_type__name', 'file_mimetype', 'file_filename', 'file_extension']) diff --git a/apps/dynamic_search/__init__.py b/apps/dynamic_search/__init__.py new file mode 100644 index 0000000000..82bdbfac9a --- /dev/null +++ b/apps/dynamic_search/__init__.py @@ -0,0 +1,6 @@ +from django.utils.translation import ugettext_lazy as _ +from common.api import register_menu + +register_menu([ + {'text':_(u'search'), 'view':'search', 'famfam':'zoom', 'position':5}, +]) diff --git a/apps/dynamic_search/api.py b/apps/dynamic_search/api.py new file mode 100644 index 0000000000..0cf21c411e --- /dev/null +++ b/apps/dynamic_search/api.py @@ -0,0 +1,7 @@ +search_list = {} + +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} diff --git a/apps/dynamic_search/forms.py b/apps/dynamic_search/forms.py new file mode 100644 index 0000000000..f5fafba6f7 --- /dev/null +++ b/apps/dynamic_search/forms.py @@ -0,0 +1,7 @@ +from django import forms +from django.utils.translation import ugettext_lazy as _ + + +class SearchForm(forms.Form): + q = forms.CharField(max_length=128, label=_(u'Search term')) + diff --git a/apps/dynamic_search/locale/es/LC_MESSAGES/django.mo b/apps/dynamic_search/locale/es/LC_MESSAGES/django.mo new file mode 100644 index 0000000000..78cef46174 Binary files /dev/null and b/apps/dynamic_search/locale/es/LC_MESSAGES/django.mo differ diff --git a/apps/dynamic_search/locale/es/LC_MESSAGES/django.po b/apps/dynamic_search/locale/es/LC_MESSAGES/django.po new file mode 100644 index 0000000000..cf1df3b955 --- /dev/null +++ b/apps/dynamic_search/locale/es/LC_MESSAGES/django.po @@ -0,0 +1,45 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2011-02-03 01:11-0400\n" +"PO-Revision-Date: 2011-01-28 09:28\n" +"Last-Translator: \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Translated-Using: django-rosetta 0.5.6\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: __init__.py:5 +msgid "search" +msgstr "búsqueda" + +#: forms.py:6 +msgid "Search term" +msgstr "Término de búsqueda" + +#: views.py:77 +msgid "Search" +msgstr "Búsqueda" + +#: views.py:78 +msgid "type" +msgstr "tipo" + +#: views.py:79 +#, python-format +msgid "results with: %s" +msgstr "resultados con: %s" + +#: templates/search_results.html:14 +msgid "No results found" +msgstr "No hay resultados" diff --git a/apps/dynamic_search/locale/ru/LC_MESSAGES/django.mo b/apps/dynamic_search/locale/ru/LC_MESSAGES/django.mo new file mode 100644 index 0000000000..94a65a81e2 Binary files /dev/null and b/apps/dynamic_search/locale/ru/LC_MESSAGES/django.mo differ diff --git a/apps/dynamic_search/locale/ru/LC_MESSAGES/django.po b/apps/dynamic_search/locale/ru/LC_MESSAGES/django.po new file mode 100644 index 0000000000..328b49e35c --- /dev/null +++ b/apps/dynamic_search/locale/ru/LC_MESSAGES/django.po @@ -0,0 +1,45 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2011-02-03 01:31-0400\n" +"PO-Revision-Date: 2011-02-03 01:31\n" +"Last-Translator: \n" +"Language-Team: LANGUAGE \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Language: \n" +"X-Translated-Using: django-rosetta 0.5.6\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" + +#: __init__.py:5 +msgid "search" +msgstr "поиск" + +#: forms.py:6 +msgid "Search term" +msgstr "Поиск по критериям" + +#: views.py:77 +msgid "Search" +msgstr "Поиск" + +#: views.py:78 +msgid "type" +msgstr "типа" + +#: views.py:79 +#, python-format +msgid "results with: %s" +msgstr "результаты: %s" + +#: templates/search_results.html:14 +msgid "No results found" +msgstr "Ничего не наидено" diff --git a/apps/dynamic_search/models.py b/apps/dynamic_search/models.py new file mode 100644 index 0000000000..71a8362390 --- /dev/null +++ b/apps/dynamic_search/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/apps/dynamic_search/templates/search_results.html b/apps/dynamic_search/templates/search_results.html new file mode 100755 index 0000000000..b999529f1c --- /dev/null +++ b/apps/dynamic_search/templates/search_results.html @@ -0,0 +1,18 @@ +{% extends "base.html" %} +{% load i18n %} +{% block title %} :: {% trans 'Search results' %}{% endblock %} +{% block content %} + + {% with 'get' as submit_method %} + {% with form_title as title %} + {% include 'generic_form_subtemplate.html' %} + {% endwith %} + {% endwith %} + + {% if query_string %} + {% include 'generic_list_subtemplate.html' %} + {% endif %} + +{% endblock %} + + diff --git a/apps/dynamic_search/tests.py b/apps/dynamic_search/tests.py new file mode 100644 index 0000000000..2247054b35 --- /dev/null +++ b/apps/dynamic_search/tests.py @@ -0,0 +1,23 @@ +""" +This file demonstrates two different styles of tests (one doctest and one +unittest). These will both pass when you run "manage.py test". + +Replace these with more appropriate tests for your application. +""" + +from django.test import TestCase + +class SimpleTest(TestCase): + def test_basic_addition(self): + """ + Tests that 1 + 1 always equals 2. + """ + self.failUnlessEqual(1 + 1, 2) + +__test__ = {"doctest": """ +Another way to test that 1 + 1 is equal to 2. + +>>> 1 + 1 == 2 +True +"""} + diff --git a/apps/dynamic_search/urls.py b/apps/dynamic_search/urls.py new file mode 100644 index 0000000000..bb7363e7c2 --- /dev/null +++ b/apps/dynamic_search/urls.py @@ -0,0 +1,8 @@ +from django.conf.urls.defaults import * + + +urlpatterns = patterns('dynamic_search.views', + url(r'^search/$', 'search', (), 'search'), +) + + diff --git a/apps/dynamic_search/views.py b/apps/dynamic_search/views.py new file mode 100644 index 0000000000..1c45857e74 --- /dev/null +++ b/apps/dynamic_search/views.py @@ -0,0 +1,81 @@ +import re + +from django.shortcuts import render_to_response +from django.template import RequestContext +from django.utils.translation import ugettext as _ +from django.db.models import Q + +from api import search_list +from forms import SearchForm + +#original code from: +#http://www.julienphalip.com/blog/2008/08/16/adding-search-django-site-snap/ + +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 + 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)] + + +def get_query(terms, search_fields): + ''' Returns a query, that is a combination of Q objects. That combination + aims to search keywords within a model by testing the given search fields. + + ''' + query = None # Query to search for every search term + #terms = normalize_query(query_string) + for term in terms: + or_query = None # Query to search for a given term in each field + for field_name in search_fields: + q = Q(**{"%s__icontains" % field_name: term}) + if or_query is None: + or_query = q + else: + or_query = or_query | q + if query is None: + query = or_query + else: + query = query & or_query + return query + + +def search(request): + query_string = '' + found_entries = {} + object_list = [] + + if ('q' in request.GET) and request.GET['q'].strip(): + query_string = request.GET['q'] + form = SearchForm(initial={'q':query_string}) + + terms = normalize_query(query_string) + + for model, data in search_list.items(): + query = get_query(terms, data['fields']) + + results = model.objects.filter(query) + if results: + found_entries[data['text']] = results + for result in results: + object_list.append(result) + else: + form = SearchForm() + + return render_to_response('search_results.html', { + 'query_string':query_string, + 'found_entries':found_entries, + 'form':form, + 'object_list':object_list, + 'form_title':_(u'Search'), + 'extra_columns':[{'name':_(u'type'), 'attribute':lambda x:x._meta.verbose_name[0].upper() + x._meta.verbose_name[1:]}], + 'title':_(u'results with: %s') % query_string + }, + context_instance=RequestContext(request)) diff --git a/settings.py b/settings.py index d224947e4f..05f93a0953 100644 --- a/settings.py +++ b/settings.py @@ -121,6 +121,7 @@ INSTALLED_APPS = ( 'common', 'documents', 'pagination', + 'dynamic_search', ) TEMPLATE_CONTEXT_PROCESSORS = ( diff --git a/urls.py b/urls.py index 62c5aa5aad..e3a9d4662a 100644 --- a/urls.py +++ b/urls.py @@ -8,6 +8,7 @@ urlpatterns = patterns('', (r'^', include('common.urls')), (r'^', include('main.urls')), (r'^documents/', include('documents.urls')), + (r'^search/', include('dynamic_search.urls')), (r'^admin/doc/', include('django.contrib.admindocs.urls')), (r'^admin/', include(admin.site.urls)), )