django-watson/src/watson/backends.py

105 lines
3.8 KiB
Python
Raw Normal View History

2011-08-20 17:08:00 +00:00
"""Search backends used by django-watson."""
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.contrib.contenttypes.models import ContentType
from django.db import models, connection
from django.db.models import Q
from watson.models import SearchEntry, has_int_pk
2011-08-20 17:08:00 +00:00
class SearchBackend(object):
"""Base class for all search backends."""
def do_install(self):
2011-08-20 17:08:00 +00:00
"""Generates the SQL needed to install django-watson."""
2011-08-23 16:12:35 +00:00
pass
2011-08-20 17:08:00 +00:00
def do_search(self, queryset, search_text):
"""Filters the given queryset according the the search logic for this backend."""
2011-08-23 16:12:35 +00:00
words = search_text.split()
regex = u"|".join(
u"(\s{word}\s)|(^{word}\s)|(\s{word}$)|(^{word}$)".format(
word = word,
)
for word in words
)
return queryset.filter(
Q(title__iregex=regex) | Q(content__iregex=regex) | Q(content__iregex=regex),
)
2011-08-23 16:12:35 +00:00
def save_search_entry(self, search_entry, obj, adapter):
"""Saves the given search entry in the database."""
2011-08-23 16:12:35 +00:00
search_entry.save()
2011-08-20 17:08:00 +00:00
class PostgresSearchBackend(SearchBackend):
"""A search backend that uses native PostgreSQL full text indices."""
def do_install(self):
2011-08-20 17:08:00 +00:00
"""Generates the PostgreSQL specific SQL code to install django-watson."""
connection.cursor().execute("""
-- Ensure that plpgsql is installed.
CREATE OR REPLACE FUNCTION make_plpgsql() RETURNS VOID LANGUAGE SQL AS
$$
CREATE LANGUAGE plpgsql;
$$;
SELECT
CASE
WHEN EXISTS(
SELECT 1
FROM pg_catalog.pg_language
WHERE lanname='plpgsql'
)
THEN NULL
ELSE make_plpgsql() END;
DROP FUNCTION make_plpgsql();
-- Create the search index.
ALTER TABLE watson_searchentry ADD COLUMN search_tsv tsvector NOT NULL;
CREATE INDEX watson_searchentry_search_tsv ON watson_searchentry USING gin(search_tsv);
-- Create the trigger function.
CREATE FUNCTION watson_searchentry_trigger_handler() RETURNS trigger AS $$
begin
new.search_tsv :=
setweight(to_tsvector('pg_catalog.english', coalesce(new.title, '')), 'A') ||
setweight(to_tsvector('pg_catalog.english', coalesce(new.description, '')), 'C') ||
setweight(to_tsvector('pg_catalog.english', coalesce(new.content, '')), 'D');
return new;
end
$$ LANGUAGE plpgsql;
CREATE TRIGGER watson_searchitem_trigger BEFORE INSERT OR UPDATE
ON watson_searchentry FOR EACH ROW EXECUTE PROCEDURE watson_searchentry_trigger_handler();
""")
def do_search(self, queryset, search_text):
"""Performs the full text search."""
return queryset.extra(
select = {
"rank": 'ts_rank_cd("search_tsv", plainto_tsquery(%s))',
},
select_params = (search_text,),
where = ('"search_tsv" @@ plainto_tsquery(%s)',),
params = (search_text,),
order_by = ("-rank",),
)
2011-08-20 17:08:00 +00:00
class AdaptiveSearchBackend(SearchBackend):
"""
A search backend that guesses the correct search backend based on the
DATABASES["default"] settings.
"""
def __new__(cls):
"""Guess the correct search backend and initialize it."""
database_engine = settings.DATABASES["default"]["ENGINE"]
if database_engine.endswith("postgresql_psycopg2") or database_engine.endswith("postgresql"):
return PostgresSearchBackend()
else:
2011-08-23 16:12:35 +00:00
return SearchBackend()