X-Git-Url: http://git.ithinksw.org/philo.git/blobdiff_plain/cc6dbd55a2af538657c288a709662b9c84949854..4c18b61fad6628589a6bd1a64895868ce3409c94:/philo/contrib/sobol/search.py diff --git a/philo/contrib/sobol/search.py b/philo/contrib/sobol/search.py index 2dbd4a7..2c31158 100644 --- a/philo/contrib/sobol/search.py +++ b/philo/contrib/sobol/search.py @@ -1,5 +1,6 @@ #encoding: utf-8 import datetime +from hashlib import sha1 from django.conf import settings from django.contrib.sites.models import Site @@ -24,16 +25,12 @@ else: __all__ = ( - 'Result', 'BaseSearch', 'DatabaseSearch', 'URLSearch', 'JSONSearch', 'GoogleSearch', 'SearchRegistry', 'registry' + 'Result', 'BaseSearch', 'DatabaseSearch', 'URLSearch', 'JSONSearch', 'GoogleSearch', 'SearchRegistry', 'registry', 'get_search_instance' ) -SEARCH_CACHE_KEY = 'philo_sobol_search_results' -DEFAULT_RESULT_TEMPLATE_STRING = "{% if url %}{% endif %}{{ title }}{% if url %}{% endif %}" -DEFAULT_RESULT_TEMPLATE = Template(DEFAULT_RESULT_TEMPLATE_STRING) - -# Determines the timeout on the entire result cache. -MAX_CACHE_TIMEOUT = 60*24*7 +SEARCH_CACHE_SEED = 'philo_sobol_search_results' +USE_CACHE = getattr(settings, 'SOBOL_USE_SEARCH', True) class RegistrationError(Exception): @@ -106,6 +103,25 @@ class SearchRegistry(object): registry = SearchRegistry() +def _make_cache_key(search, search_arg): + return sha1(SEARCH_CACHE_SEED + search.slug + search_arg).hexdigest() + + +def get_search_instance(slug, search_arg): + """Returns a search instance for the given slug, either from the cache or newly-instantiated.""" + search = registry[slug] + search_arg = search_arg.lower() + if USE_CACHE: + key = _make_cache_key(search, search_arg) + cached = cache.get(key) + if cached: + return cached + instance = search(search_arg) + instance.slug = slug + return instance + + + class Result(object): """ :class:`Result` is a helper class that, given a search and a result of that search, is able to correctly render itself with a template defined by the search. Every :class:`Result` will pass a ``title``, a ``url`` (if applicable), and the raw ``result`` returned by the search into the template context when rendering. @@ -173,7 +189,7 @@ class BaseSearchMetaclass(type): if 'verbose_name' not in attrs: attrs['verbose_name'] = capfirst(' '.join(convert_camelcase(name).rsplit(' ', 1)[:-1])) if 'slug' not in attrs: - attrs['slug'] = name.lower() + attrs['slug'] = name[:-6].lower() if name.endswith("Search") else name.lower() return super(BaseSearchMetaclass, cls).__new__(cls, name, bases, attrs) @@ -185,54 +201,38 @@ class BaseSearch(object): """ __metaclass__ = BaseSearchMetaclass - #: The number of results to return from the complete list. Default: 10 - result_limit = 10 + #: The number of results to return from the complete list. Default: 5 + result_limit = 5 #: How long the items for the search should be cached (in minutes). Default: 48 hours. _cache_timeout = 60*48 + #: The path to the template which will be used to render the :class:`Result`\ s for this search. + result_template = "sobol/search/basesearch.html" def __init__(self, search_arg): self.search_arg = search_arg - def _get_cached_results(self): - """Return the cached results if the results haven't timed out. Otherwise return None.""" - result_cache = cache.get(SEARCH_CACHE_KEY) - if result_cache and self.__class__ in result_cache and self.search_arg.lower() in result_cache[self.__class__]: - cached = result_cache[self.__class__][self.search_arg.lower()] - if cached['timeout'] >= datetime.datetime.now(): - return cached['results'] - return None - - def _set_cached_results(self, results, timeout): - """Sets the results to the cache for minutes.""" - result_cache = cache.get(SEARCH_CACHE_KEY) or {} - cached = result_cache.setdefault(self.__class__, {}).setdefault(self.search_arg.lower(), {}) - cached.update({ - 'results': results, - 'timeout': datetime.datetime.now() + datetime.timedelta(minutes=timeout) - }) - cache.set(SEARCH_CACHE_KEY, result_cache, MAX_CACHE_TIMEOUT) - @property def results(self): """Retrieves cached results or initiates a new search via :meth:`get_results` and caches the results.""" if not hasattr(self, '_results'): - results = self._get_cached_results() - if results is None: - try: - # Cache one extra result so we can see if there are - # more results to be had. - limit = self.result_limit - if limit is not None: - limit += 1 - results = self.get_results(limit) - except: - if settings.DEBUG: - raise - # On exceptions, don't set any cache; just return. - return [] + try: + # Cache one extra result so we can see if there are + # more results to be had. + limit = self.result_limit + if limit is not None: + limit += 1 + results = self.get_results(limit) + except: + if settings.DEBUG: + raise + # On exceptions, don't set any cache; just return. + return [] - self._set_cached_results(results, self._cache_timeout) self._results = results + + if USE_CACHE: + key = _make_cache_key(self, self.search_arg) + cache.set(key, self, self._cache_timeout) return self._results @@ -268,29 +268,29 @@ class BaseSearch(object): def get_result_template(self, result): """Returns the template to be used for rendering the ``result``.""" - if hasattr(self, 'result_template'): - return loader.get_template(self.result_template) - if not hasattr(self, '_result_template'): - self._result_template = DEFAULT_RESULT_TEMPLATE - return self._result_template + return loader.get_template(self.result_template) def get_result_extra_context(self, result): """Returns any extra context to be used when rendering the ``result``.""" return {} + @property def has_more_results(self): """Returns ``True`` if there are more results than :attr:`result_limit` and ``False`` otherwise.""" return len(self.results) > self.result_limit @property def more_results_url(self): - """Returns the actual url for more results. This should be accessed through :attr:`more_results_querydict` in the template so that the click can be tracked.""" - raise NotImplementedError + """Returns the actual url for more results. This should be accessed through :attr:`more_results_querydict` in the template so that the click can be tracked. By default, simply returns ``None``.""" + return None @property def more_results_querydict(self): """Returns a :class:`QueryDict` for tracking whether people click on a 'more results' link.""" - return make_tracking_querydict(self.search_arg, self.more_results_url) + url = self.more_results_url + if url: + return make_tracking_querydict(self.search_arg, url) + return None def __unicode__(self): return self.verbose_name @@ -347,9 +347,10 @@ class JSONSearch(URLSearch): class GoogleSearch(JSONSearch): """An example implementation of a :class:`JSONSearch`.""" search_url = "http://ajax.googleapis.com/ajax/services/search/web" - result_template = 'search/googlesearch.html' _cache_timeout = 60 verbose_name = "Google search (current site)" + result_template = "sobol/search/googlesearch.html" + _more_results_url = None @property def query_format_str(self):