self._favored_results += subresults
else:
break
+ if len(self._favored_results) == len(results):
+ self._favored_results = []
return self._favored_results
class Meta:
except ImportError:
pass
else:
- add_introspection_rules([], ["^philo\.contrib\.shipherd\.models\.RegistryChoiceField"])
+ add_introspection_rules([], ["^philo\.contrib\.sobol\.models\.RegistryChoiceField"])
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()
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
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)
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
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:
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
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/<slug>/result.html" and "sobol/search/result.html".
+ result_template = None
def __init__(self, search_arg):
self.search_arg = search_arg
"""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
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
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):
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
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):
def get_result_url(self, result):
return result['unescapedUrl']
+
+ def get_result_content(self, result):
+ return result['content']
registry.register(GoogleSearch)
+++ /dev/null
-{% extends "admin/base_site.html" %}
-
-<!-- LOADING -->
-{% load i18n %}
-
-<!-- EXTRASTYLES -->
-{% block extrastyle %}<style type="text/css">.favored{font-weight:bold;}</style>{% endblock %}
-
-<!-- BREADCRUMBS -->
-{% block breadcrumbs %}
- <div id="breadcrumbs">
- {% if queryset|length > 1 %}
- <a href="../../">{% trans "Home" %}</a> ›
- <a href="../">{{ app_label|capfirst }}</a> ›
- <a href="./">{{ opts.verbose_name_plural|capfirst }}</a> ›
- {% trans 'Search results for multiple objects' %}
- {% else %}
- <a href="../../../../">{% trans "Home" %}</a> ›
- <a href="../../../">{{ app_label|capfirst }}</a> ›
- <a href="../../">{{ opts.verbose_name_plural|capfirst }}</a> ›
- <a href="../">{{ queryset|first|truncatewords:"18" }}</a> ›
- {% trans 'Results' %}
- {% endif %}
- </div>
-{% endblock %}
-
-<!-- CONTENT -->
-{% block content %}
- <div class="container-grid delete-confirmation">
- {% for search in queryset %}
- <div class="group tabular">
- <h2>{{ search_string }}</h2>
- <div class="module table">
- <div class="module thead">
- <div class="tr">
- <div class="th">Weight</div>
- <div class="th">URL</div>
- </div>
- </div>
- <div class="module tbody">
- {% for result in search.get_weighted_results %}
- <div class="tr{% if result in search.get_favored_results %} favored{% endif %}">
- <div class="td">{{ result.weight }}</div>
- <div class="td">{{ result.url }}</div>
- </div>
- {% endfor %}
- </div>
- </div>
- </div>
- {% endfor %}
- </div>
-{% endblock %}
\ No newline at end of file