From 8b55084e724338896ce94dd91d67fd4d0c0187e2 Mon Sep 17 00:00:00 2001 From: Stephen Burrows Date: Fri, 10 Jun 2011 13:24:42 -0400 Subject: [PATCH] Refactored BaseSearch/Result to use templates to render the title and content entries in the Result's context. Removed rendered results from the ajax API. Added favored result support to _list for ajax and non-ajax versions. --- philo/contrib/sobol/models.py | 1 - philo/contrib/sobol/search.py | 74 +++++++++++-------- .../contrib/sobol/static/sobol/ajax_search.js | 47 +++++++++++- .../sobol/templates/sobol/search/_list.html | 23 +++++- .../sobol/templates/sobol/search/content.html | 1 + .../sobol/templates/sobol/search/result.html | 4 +- 6 files changed, 112 insertions(+), 38 deletions(-) create mode 100644 philo/contrib/sobol/templates/sobol/search/content.html diff --git a/philo/contrib/sobol/models.py b/philo/contrib/sobol/models.py index c437d17..b35133e 100644 --- a/philo/contrib/sobol/models.py +++ b/philo/contrib/sobol/models.py @@ -301,7 +301,6 @@ class SearchView(MultiView): return HttpResponse(json.dumps({ 'search': search_instance.slug, 'results': [result.get_context() for result in search_instance.results], - 'rendered': [result.render() for result in search_instance.results], 'hasMoreResults': search_instance.has_more_results, 'moreResultsURL': search_instance.more_results_url, }), mimetype="application/json") \ No newline at end of file diff --git a/philo/contrib/sobol/search.py b/philo/contrib/sobol/search.py index 693f879..5f5fddc 100644 --- a/philo/contrib/sobol/search.py +++ b/philo/contrib/sobol/search.py @@ -10,7 +10,7 @@ from django.utils import simplejson as json from django.utils.http import urlquote_plus from django.utils.safestring import mark_safe from django.utils.text import capfirst -from django.template import loader, Context, Template +from django.template import loader, Context, Template, TemplateDoesNotExist from philo.contrib.sobol.utils import make_tracking_querydict, RegistryIterator @@ -139,24 +139,24 @@ class Result(object): return self.search.get_result_title(self.result) def get_url(self): - """Returns the url of the result or an empty string by calling :meth:`BaseSearch.get_result_url` on the raw result.""" + """Returns the url of the result or ``None`` by calling :meth:`BaseSearch.get_result_url` on the raw result. This url will contain a querystring which, if used, will track a :class:`.Click` for the actual url.""" return self.search.get_result_url(self.result) + def get_actual_url(self): + """Returns the actual url of the result by calling :meth:`BaseSearch.get_actual_result_url` on the raw result.""" + return self.search.get_actual_result_url(self.result) + def get_content(self): """Returns the content of the result by calling :meth:`BaseSearch.get_result_content` on the raw result.""" return self.search.get_result_content(self.result) - def get_extra_context(self): - """Returns any extra context for the result by calling :meth:`BaseSearch.get_result_extra_context` on the raw result.""" - return self.search.get_result_extra_context(self.result) - def get_template(self): """Returns the template which will be used to render the :class:`Result` by calling :meth:`BaseSearch.get_result_template` on the raw result.""" return self.search.get_result_template(self.result) def get_context(self): """ - Returns the context dictionary for the result. This is used both in rendering the result and in the AJAX return value for :meth:`.SearchView.ajax_api_view`. The context will contain everything from :meth:`get_extra_context` as well as the following keys: + Returns the context dictionary for the result. This is used both in rendering the result and in the AJAX return value for :meth:`.SearchView.ajax_api_view`. The context will contain the following keys: title The result of calling :meth:`get_title` @@ -166,13 +166,14 @@ class Result(object): The result of calling :meth:`get_content` """ - context = self.get_extra_context() - context.update({ - 'title': self.get_title(), - 'url': self.get_url(), - 'content': self.get_content() - }) - return context + if not hasattr(self, '_context'): + self._context = { + 'title': self.get_title(), + 'url': self.get_url(), + 'actual_url': self.get_actual_url(), + 'content': self.get_content() + } + return self._context def render(self): """Returns the template from :meth:`get_template` rendered with the context from :meth:`get_context`.""" @@ -206,8 +207,12 @@ class BaseSearch(object): 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. If this is ``None``, then the framework will try "sobol/search//result.html" and "sobol/search/result.html". + #: The path to the template which will be used to render the :class:`Result`\ s for this search. If this is ``None``, then the framework will try ``sobol/search//result.html`` and ``sobol/search/result.html``. result_template = None + #: The path to the template which will be used to generate the title of the :class:`Result`\ s for this search. If this is ``None``, then the framework will try ``sobol/search//title.html`` and ``sobol/search/title.html``. + title_template = None + #: The path to the template which will be used to generate the content of the :class:`Result`\ s for this search. If this is ``None``, then the framework will try ``sobol/search//content.html`` and ``sobol/search/content.html``. + content_template = None def __init__(self, search_arg): self.search_arg = search_arg @@ -232,6 +237,8 @@ class BaseSearch(object): self._results = results if USE_CACHE: + for result in results: + result.get_context() key = _make_cache_key(self, self.search_arg) cache.set(key, self, self._cache_timeout) @@ -252,17 +259,13 @@ class BaseSearch(object): """Returns an iterable of up to ``limit`` results. The :meth:`get_result_title`, :meth:`get_result_url`, :meth:`get_result_template`, and :meth:`get_result_extra_context` methods will be used to interpret the individual items that this function returns, so the result can be an object with attributes as easily as a dictionary with keys. However, keep in mind that the raw results will be stored with django's caching mechanisms and will be converted to JSON.""" raise NotImplementedError - def get_result_title(self, result): - """Returns the title of the ``result``. Must be implemented by subclasses.""" - raise NotImplementedError - def get_actual_result_url(self, result): """Returns the actual URL for the ``result`` or ``None`` if there is no URL. Must be implemented by subclasses.""" raise NotImplementedError def get_result_querydict(self, result): """Returns a querydict for tracking selection of the result, or ``None`` if there is no URL for the result.""" - url = self.get_result_url(result) + url = self.get_actual_result_url(result) if url is None: return None return make_tracking_querydict(self.search_arg, url) @@ -274,13 +277,22 @@ class BaseSearch(object): return None return "?%s" % qd.urlencode() - def get_result_content(self, result): - """Returns the content for the ``result`` or ``None`` if there is no content. Must be implemented by subclasses.""" - raise NotImplementedError + def get_result_title(self, result): + """Returns the title of the ``result``. By default, renders ``sobol/search//title.html`` or ``sobol/search/title.html`` with the result in the context. This can be overridden by setting :attr:`title_template` or simply overriding :meth:`get_result_title`. If no template can be found, this will raise :exc:`TemplateDoesNotExist`.""" + return loader.render_to_string(self.title_template or [ + 'sobol/search/%s/title.html' % self.slug, + 'sobol/search/title.html' + ], {'result': result}) - def get_result_extra_context(self, result): - """Returns any extra context to be used when rendering the ``result``. Make sure that any extra context can be serialized as JSON.""" - return {} + def get_result_content(self, result): + """Returns the content for the ``result``. By default, renders ``sobol/search//content.html`` or ``sobol/search/content.html`` with the result in the context. This can be overridden by setting :attr:`content_template` or simply overriding :meth:`get_result_content`. If no template is found, this will return an empty string.""" + try: + return loader.render_to_string(self.content_template or [ + 'sobol/search/%s/content.html' % self.slug, + 'sobol/search/content.html' + ], {'result': result}) + except TemplateDoesNotExist: + return "" def get_result_template(self, result): """Returns the template to be used for rendering the ``result``. For a search with slug ``google``, this would first try ``sobol/search/google/result.html``, then fall back on ``sobol/search/result.html``. Subclasses can override this by setting :attr:`result_template` to the path of another template.""" @@ -413,14 +425,14 @@ class GoogleSearch(JSONSearch): def get_actual_more_results_url(self): return self._more_results_url - def get_result_title(self, result): - return result['titleNoFormatting'] - - def get_result_url(self, result): + def get_actual_result_url(self, result): return result['unescapedUrl'] + def get_result_title(self, result): + return mark_safe(result['titleNoFormatting']) + def get_result_content(self, result): - return result['content'] + return mark_safe(result['content']) registry.register(GoogleSearch) diff --git a/philo/contrib/sobol/static/sobol/ajax_search.js b/philo/contrib/sobol/static/sobol/ajax_search.js index dc93da1..b2ef413 100644 --- a/philo/contrib/sobol/static/sobol/ajax_search.js +++ b/philo/contrib/sobol/static/sobol/ajax_search.js @@ -1,8 +1,11 @@ (function($){ var sobol = window.sobol = {}; + sobol.favoredResults = [] + sobol.favoredResultSearch = null; sobol.search = function(){ var searches = sobol.searches = $('article.search'); - for (var i=0;i" + title + ""; + } else { + rendered += "
" + title + "
"; + } + if(content && content != ''){ + rendered += "
" + content + "
" + } + return rendered + } + sobol.addFavoredResult = function(result) { + var dl = sobol.favoredResultSearch.find('dl'); + if(!dl.length){ + dl = $('
'); + dl.appendTo(sobol.favoredResultSearch); + sobol.favoredResultSearch.removeClass('loading'); + } + dl[0].innerHTML += sobol.renderResult(result) + } sobol.onSuccess = function(ele, data){ // hook for success! - ele.removeClass('loading') + ele.removeClass('loading'); if (data['results'].length) { - ele[0].innerHTML += "
" + data['rendered'].join("") + "
"; + ele[0].innerHTML += "
"; + $.each(data['results'], function(i, v){ + ele[0].innerHTML += sobol.renderResult(v); + }) + ele[0].innerHTML += "
"; if(data['hasMoreResults'] && data['moreResultsURL']) ele[0].innerHTML += ""; } else { ele.addClass('empty'); ele[0].innerHTML += "

No results found.

"; ele.slideUp(); } + if (sobol.favoredResultSearch){ + for (var i=0;i {% endif %} +{% if favored_results %} + +{% endif %} {% for search in searches %}
@@ -23,7 +44,7 @@
{% if search.has_more_results and search.more_results_url %} {% endif %} {% else %} diff --git a/philo/contrib/sobol/templates/sobol/search/content.html b/philo/contrib/sobol/templates/sobol/search/content.html new file mode 100644 index 0000000..82088ec --- /dev/null +++ b/philo/contrib/sobol/templates/sobol/search/content.html @@ -0,0 +1 @@ +{{ result.content|truncatewords_html:20 }} \ No newline at end of file diff --git a/philo/contrib/sobol/templates/sobol/search/result.html b/philo/contrib/sobol/templates/sobol/search/result.html index fbd89c7..c5a906a 100644 --- a/philo/contrib/sobol/templates/sobol/search/result.html +++ b/philo/contrib/sobol/templates/sobol/search/result.html @@ -1,2 +1,2 @@ -
{% if url %}{% endif %}{{ title|safe }}{% if url %}{% endif %}
-{% if content %}
{{ content|safe|truncatewords_html:20 }}
{% endif %} \ No newline at end of file +
{% if url %}{% endif %}{{ title }}{% if url %}{% endif %}
+{% if content %}
{{ content }}
{% endif %} \ No newline at end of file -- 2.20.1