From cfd7c6155ad83f9ead1f18ff6109cf1dba5835fe Mon Sep 17 00:00:00 2001 From: Stephen Burrows Date: Thu, 9 Jun 2011 17:20:50 -0400 Subject: [PATCH] Clarified sobol Search framework - now automatically finds result templates at "sobol/search//result.html" and supports the notion of link 'content'. Search.get_result_url and Search.more_results_url actually return urls. Improved grappelli admin integration for Search model instances. --- philo/contrib/sobol/models.py | 25 ++++-- philo/contrib/sobol/search.py | 83 ++++++++++++------- .../contrib/sobol/static/sobol/ajax_search.js | 2 +- .../admin/sobol/search/change_form.html | 44 ++++++++++ .../admin/sobol/search/change_list.html | 3 + .../admin/sobol/search/grappelli_results.html | 52 ------------ .../sobol/templates/sobol/search/_list.html | 12 ++- .../templates/sobol/search/basesearch.html | 1 - .../templates/sobol/search/googlesearch.html | 2 - .../sobol/templates/sobol/search/result.html | 2 + 10 files changed, 132 insertions(+), 94 deletions(-) create mode 100644 philo/contrib/sobol/templates/admin/sobol/search/change_form.html create mode 100644 philo/contrib/sobol/templates/admin/sobol/search/change_list.html delete mode 100644 philo/contrib/sobol/templates/admin/sobol/search/grappelli_results.html delete mode 100644 philo/contrib/sobol/templates/sobol/search/basesearch.html delete mode 100644 philo/contrib/sobol/templates/sobol/search/googlesearch.html create mode 100644 philo/contrib/sobol/templates/sobol/search/result.html diff --git a/philo/contrib/sobol/models.py b/philo/contrib/sobol/models.py index 1bef3cd..c437d17 100644 --- a/philo/contrib/sobol/models.py +++ b/philo/contrib/sobol/models.py @@ -79,6 +79,8 @@ class Search(models.Model): self._favored_results += subresults else: break + if len(self._favored_results) == len(results): + self._favored_results = [] return self._favored_results class Meta: @@ -168,7 +170,7 @@ try: except ImportError: pass else: - add_introspection_rules([], ["^philo\.contrib\.shipherd\.models\.RegistryChoiceField"]) + add_introspection_rules([], ["^philo\.contrib\.sobol\.models\.RegistryChoiceField"]) class SearchView(MultiView): @@ -255,8 +257,16 @@ class SearchView(MultiView): pool.waitall() context.update({ - 'searches': search_instances + 'searches': search_instances, + 'favored_results': [] }) + + try: + search = Search.objects.get(string=search_string) + except Search.DoesNotExist: + pass + else: + context['favored_results'] = [r.url for r in search.get_favored_results()] else: form = SearchForm() @@ -267,8 +277,10 @@ class SearchView(MultiView): def ajax_api_view(self, request, slug, extra_context=None): """ - Returns a JSON string containing two keyed lists. + Returns a JSON object containing the following variables: + search + Contains the slug for the search. results Contains the results of :meth:`.Result.get_context` for each result. rendered @@ -276,7 +288,7 @@ class SearchView(MultiView): hasMoreResults ``True`` or ``False`` whether the search has more results according to :meth:`BaseSearch.has_more_results` moreResultsURL - Contains None or a querystring which, once accessed, will note the :class:`Click` and redirect the user to a page containing more results. + Contains ``None`` or a querystring which, once accessed, will note the :class:`Click` and redirect the user to a page containing more results. """ search_string = request.GET.get(SEARCH_ARG_GET_KEY) @@ -288,7 +300,8 @@ class SearchView(MultiView): return HttpResponse(json.dumps({ 'search': search_instance.slug, - 'results': [result.render() for result in search_instance.results], + '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': (u"?%s" % search_instance.more_results_querydict.urlencode()) if search_instance.more_results_querydict else None, + '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 2c31158..693f879 100644 --- a/philo/contrib/sobol/search.py +++ b/philo/contrib/sobol/search.py @@ -139,20 +139,21 @@ 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_querydict` on the raw result and then encoding the querydict returned.""" - qd = self.search.get_result_querydict(self.result) - if qd is None: - return "" - return "?%s" % qd.urlencode() + """Returns the url of the result or an empty string by calling :meth:`BaseSearch.get_result_url` on the raw result.""" + return self.search.get_result_url(self.result) - def get_template(self): - """Returns the template for the result by calling :meth:`BaseSearch.get_result_template` on the raw result.""" - return self.search.get_result_template(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: @@ -161,15 +162,15 @@ class Result(object): The result of calling :meth:`get_title` url The result of calling :meth:`get_url` - result - The raw result which the :class:`Result` was instantiated with. + content + The result of calling :meth:`get_content` """ context = self.get_extra_context() context.update({ 'title': self.get_title(), 'url': self.get_url(), - 'result': self.result + 'content': self.get_content() }) return context @@ -205,8 +206,8 @@ 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. - result_template = "sobol/search/basesearch.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 def __init__(self, search_arg): self.search_arg = search_arg @@ -255,7 +256,7 @@ class BaseSearch(object): """Returns the title of the ``result``. Must be implemented by subclasses.""" raise NotImplementedError - def get_result_url(self, result): + 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 @@ -266,32 +267,54 @@ class BaseSearch(object): return None return make_tracking_querydict(self.search_arg, url) - def get_result_template(self, result): - """Returns the template to be used for rendering the ``result``.""" - return loader.get_template(self.result_template) + def get_result_url(self, result): + """Returns ``None`` or a url which, when accessed, will register a :class:`.Click` for that url.""" + qd = self.get_result_querydict(result) + if qd is None: + 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_extra_context(self, result): - """Returns any extra context to be used when rendering the ``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_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.""" + if self.result_template: + return loader.get_template(self.result_template) + return loader.select_template([ + 'sobol/search/%s/result.html' % self.slug, + 'sobol/search/result.html' + ]) + @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. By default, simply returns ``None``.""" + def get_actual_more_results_url(self): + """Returns the actual url for more results. By default, simply returns ``None``.""" return None - @property - def more_results_querydict(self): + def get_more_results_querydict(self): """Returns a :class:`QueryDict` for tracking whether people click on a 'more results' link.""" - url = self.more_results_url + url = self.get_actual_more_results_url() if url: return make_tracking_querydict(self.search_arg, url) return None + @property + def more_results_url(self): + """Returns a URL which consists of a querystring which, when accessed, will log a :class:`.Click` for the actual URL.""" + qd = self.get_more_results_querydict() + if qd is None: + return None + return "?%s" % qd.urlencode() + def __unicode__(self): return self.verbose_name @@ -325,9 +348,8 @@ class URLSearch(BaseSearch): def url(self): """The URL where the search gets its results. Composed from :attr:`search_url` and :attr:`query_format_str`.""" return self.search_url + self.query_format_str % urlquote_plus(self.search_arg) - - @property - def more_results_url(self): + + def get_actual_more_results_url(self): return self.url def parse_response(self, response, limit=None): @@ -349,7 +371,6 @@ class GoogleSearch(JSONSearch): search_url = "http://ajax.googleapis.com/ajax/services/search/web" _cache_timeout = 60 verbose_name = "Google search (current site)" - result_template = "sobol/search/googlesearch.html" _more_results_url = None @property @@ -389,8 +410,7 @@ class GoogleSearch(JSONSearch): return True return False - @property - def more_results_url(self): + def get_actual_more_results_url(self): return self._more_results_url def get_result_title(self, result): @@ -398,6 +418,9 @@ class GoogleSearch(JSONSearch): def get_result_url(self, result): return result['unescapedUrl'] + + def get_result_content(self, result): + return 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 33fdf4f..dc93da1 100644 --- a/philo/contrib/sobol/static/sobol/ajax_search.js +++ b/philo/contrib/sobol/static/sobol/ajax_search.js @@ -22,7 +22,7 @@ // hook for success! ele.removeClass('loading') if (data['results'].length) { - ele[0].innerHTML += "
" + data['results'].join("") + "
"; + ele[0].innerHTML += "
" + data['rendered'].join("") + "
"; if(data['hasMoreResults'] && data['moreResultsURL']) ele[0].innerHTML += ""; } else { ele.addClass('empty'); diff --git a/philo/contrib/sobol/templates/admin/sobol/search/change_form.html b/philo/contrib/sobol/templates/admin/sobol/search/change_form.html new file mode 100644 index 0000000..2761599 --- /dev/null +++ b/philo/contrib/sobol/templates/admin/sobol/search/change_form.html @@ -0,0 +1,44 @@ +{% extends 'admin/change_form.html' %} +{% load i18n %} + +{% block javascripts %}{% endblock %} +{% block object-tools %}{% endblock %} +{% block title %}Results for "{{ original.string }}" | {% trans 'Django site admin' %}{% endblock %} +{% block content_title %}

Results for "{{ original.string }}"

{% endblock %} +{% block extrastyle %} + +{% endblock %} + +{% block content %} +
+ + + + + + + + + {% for result in original.get_weighted_results %} + + + + + {% endfor %} + +
WeightURL
{{ result.weight }}{{ result.url }}
+
+ +{% endblock %} \ No newline at end of file diff --git a/philo/contrib/sobol/templates/admin/sobol/search/change_list.html b/philo/contrib/sobol/templates/admin/sobol/search/change_list.html new file mode 100644 index 0000000..9b01661 --- /dev/null +++ b/philo/contrib/sobol/templates/admin/sobol/search/change_list.html @@ -0,0 +1,3 @@ +{% extends 'admin/change_list.html' %} + +{% block object-tools %}{% endblock %} \ No newline at end of file diff --git a/philo/contrib/sobol/templates/admin/sobol/search/grappelli_results.html b/philo/contrib/sobol/templates/admin/sobol/search/grappelli_results.html deleted file mode 100644 index f01eb88..0000000 --- a/philo/contrib/sobol/templates/admin/sobol/search/grappelli_results.html +++ /dev/null @@ -1,52 +0,0 @@ -{% extends "admin/base_site.html" %} - - -{% load i18n %} - - -{% block extrastyle %}{% endblock %} - - -{% block breadcrumbs %} - -{% endblock %} - - -{% block content %} -
- {% for search in queryset %} -
-

{{ search_string }}

-
-
-
-
Weight
-
URL
-
-
-
- {% for result in search.get_weighted_results %} -
-
{{ result.weight }}
-
{{ result.url }}
-
- {% endfor %} -
-
-
- {% endfor %} -
-{% endblock %} \ No newline at end of file diff --git a/philo/contrib/sobol/templates/sobol/search/_list.html b/philo/contrib/sobol/templates/sobol/search/_list.html index 8fed939..a3d8108 100644 --- a/philo/contrib/sobol/templates/sobol/search/_list.html +++ b/philo/contrib/sobol/templates/sobol/search/_list.html @@ -1,5 +1,13 @@ {% with node.view.enable_ajax_api as ajax %} -{% if ajax and not suppress_scripts %}{% endif %} +{% if ajax %} + {% if not suppress_scripts %}{% endif %} + +{% endif %} {% for search in searches %}
@@ -10,7 +18,7 @@ {% if search.results %}
{% for result in search.results %} - {{ result }} + {{ result }} {% endfor %}
{% if search.has_more_results and search.more_results_url %} diff --git a/philo/contrib/sobol/templates/sobol/search/basesearch.html b/philo/contrib/sobol/templates/sobol/search/basesearch.html deleted file mode 100644 index e7661ac..0000000 --- a/philo/contrib/sobol/templates/sobol/search/basesearch.html +++ /dev/null @@ -1 +0,0 @@ -
{% if url %}{% endif %}{{ title }}{% if url %}{% endif %}
\ No newline at end of file diff --git a/philo/contrib/sobol/templates/sobol/search/googlesearch.html b/philo/contrib/sobol/templates/sobol/search/googlesearch.html deleted file mode 100644 index 8d23132..0000000 --- a/philo/contrib/sobol/templates/sobol/search/googlesearch.html +++ /dev/null @@ -1,2 +0,0 @@ -
{{ title|safe }}
-
{{ result.content|safe }}
\ 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 new file mode 100644 index 0000000..fbd89c7 --- /dev/null +++ b/philo/contrib/sobol/templates/sobol/search/result.html @@ -0,0 +1,2 @@ +
{% if url %}{% endif %}{{ title|safe }}{% if url %}{% endif %}
+{% if content %}
{{ content|safe|truncatewords_html:20 }}
{% endif %} \ No newline at end of file -- 2.20.1