From 327a00ede6928f80a8b9bd2c5df313b069f80a62 Mon Sep 17 00:00:00 2001 From: Roberto Rosario Date: Thu, 3 Feb 2011 22:52:25 -0400 Subject: [PATCH] Added search app --- apps/documents/models.py | 6 +- apps/dynamic_search/__init__.py | 6 ++ apps/dynamic_search/api.py | 7 ++ apps/dynamic_search/forms.py | 7 ++ .../locale/es/LC_MESSAGES/django.mo | Bin 0 -> 726 bytes .../locale/es/LC_MESSAGES/django.po | 45 ++++++++++ .../locale/ru/LC_MESSAGES/django.mo | Bin 0 -> 838 bytes .../locale/ru/LC_MESSAGES/django.po | 45 ++++++++++ apps/dynamic_search/models.py | 3 + .../templates/search_results.html | 18 ++++ apps/dynamic_search/tests.py | 23 +++++ apps/dynamic_search/urls.py | 8 ++ apps/dynamic_search/views.py | 81 ++++++++++++++++++ settings.py | 1 + urls.py | 1 + 15 files changed, 250 insertions(+), 1 deletion(-) create mode 100644 apps/dynamic_search/__init__.py create mode 100644 apps/dynamic_search/api.py create mode 100644 apps/dynamic_search/forms.py create mode 100644 apps/dynamic_search/locale/es/LC_MESSAGES/django.mo create mode 100644 apps/dynamic_search/locale/es/LC_MESSAGES/django.po create mode 100644 apps/dynamic_search/locale/ru/LC_MESSAGES/django.mo create mode 100644 apps/dynamic_search/locale/ru/LC_MESSAGES/django.po create mode 100644 apps/dynamic_search/models.py create mode 100755 apps/dynamic_search/templates/search_results.html create mode 100644 apps/dynamic_search/tests.py create mode 100644 apps/dynamic_search/urls.py create mode 100644 apps/dynamic_search/views.py 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 0000000000000000000000000000000000000000..78cef4617452b939c33c5c224e151f8b155f59d0 GIT binary patch literal 726 zcmZvZzi-n(6vq!Jpqd3Lu`xV`N-&(UBYvb#Q#B1~6_KP->Qt;(`|4a}-Vckj#VX7a=@8RCUXJHbwU0bUI;shVBHWi^Vrjplc828 zJh8N!(nGF|P%?sHtNo<4*MY;%=%Bkl@J75;+S0zsL`rw6%+Odx;0^a<+SZ&|JVYNe z%Oj`-VMv3J*6I*!M72jWxE};)q$B=ZxZ?l!)x91wmd2V%lQXMy1e+`^gxsD!{X`X8 zXpvdPGEQU8iU@kG!QR+)x!LP&=fYPy+w%I|erI`)&=0(}l9o%XjhBt^f4sE!%A5(= zfMmk7;kG%BcWHg;b3Gh$O*=ACsgPL&^&?>yU&nhtT{Oy5IyO^JdV<|4sufnW3, 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 0000000000000000000000000000000000000000..94a65a81e2976d2bb592f37b0160cfbcc5049c94 GIT binary patch literal 838 zcmZ`%yKWOf6dfQuv^0ncB#Hqk5fo!)9YE2>8xn#OML3C-I1%mOoj4oWJJ#%&hm-3;@4?ug3`a1E#>^fEKy$GhSqTm~uZDhJS-|2-$*s7`x&B(Rme-8dlRHNt?nb9ng}9 zT5&R-pM|<=jX{z$I-((2O^Q}PD-(0mOK!_jr83@(3raV}yxE!A^3gr~0{13WVq}8Z zxNLy`@z`8%H$|L`()y~D&%0QnPYWS5cfa0y^>_Hiuuwz3(3`DkHX( z=9*{?+bO0MCK^F`;b!5MS8aB*XtF7#TWLU(b{?kT@F?xOVKV6RF!brr5OprU9Qr=J zO*w@c@mN?qD3!t+&b^w;N-`pJ_%-zS=qZkR#J;h;?1k;xcXpTB9ou!smhIX1 z$nDr&V&B?bNS@j4e_U$6IQe$=6r~N@&H0u6OyG3whwQoilx?E0m2G4%^Ec!g_QxRK IL%2o$0vKfia{vGU literal 0 HcmV?d00001 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)), )