Skip to content

Commit

Permalink
Reduced the number of queries Haystack has to perform in many cases (…
Browse files Browse the repository at this point in the history
…pagination/facet_counts/spelling_suggestions). Thanks to acdha for the improvements!
  • Loading branch information
toastdriven committed Dec 15, 2011
1 parent baaa822 commit d0ab16f
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 51 deletions.
4 changes: 2 additions & 2 deletions haystack/backends/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,10 +410,10 @@ def get_count(self):
the results.
"""
if self._hit_count is None:
# Limit the slice to 10 so we get a count without consuming
# Limit the slice to 1 so we get a count without consuming
# everything.
if not self.end_offset:
self.end_offset = 10
self.end_offset = 1

if self._more_like_this:
# Special case for MLT.
Expand Down
14 changes: 10 additions & 4 deletions haystack/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -495,8 +495,11 @@ def facet_counts(self):
This will cause the query to execute and should generally be used when
presenting the data.
"""
clone = self._clone()
return clone.query.get_facet_counts()
if self.query.has_run():
return self.query.get_facet_counts()
else:
clone = self._clone()
return clone.query.get_facet_counts()

def spelling_suggestion(self, preferred_query=None):
"""
Expand All @@ -508,8 +511,11 @@ def spelling_suggestion(self, preferred_query=None):
This will cause the query to execute and should generally be used when
presenting the data.
"""
clone = self._clone()
return clone.query.get_spelling_suggestion(preferred_query)
if self.query.has_run():
return self.query.get_spelling_suggestion(preferred_query)
else:
clone = self._clone()
return clone.query.get_spelling_suggestion(preferred_query)


# Utility methods.
Expand Down
98 changes: 53 additions & 45 deletions haystack/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,36 +19,36 @@ class SearchView(object):
request = None
form = None
results_per_page = RESULTS_PER_PAGE

def __init__(self, template=None, load_all=True, form_class=None, searchqueryset=None, context_class=RequestContext, results_per_page=None):
self.load_all = load_all
self.form_class = form_class
self.context_class = context_class
self.searchqueryset = searchqueryset

if form_class is None:
self.form_class = ModelSearchForm

if not results_per_page is None:
self.results_per_page = results_per_page

if template:
self.template = template

def __call__(self, request):
"""
Generates the actual response to the search.
Relies on internal, overridable methods to construct the response.
"""
self.request = request

self.form = self.build_form()
self.query = self.get_query()
self.results = self.get_results()

return self.create_response()

def build_form(self, form_kwargs=None):
"""
Instantiates the form the class should use to process the search query.
Expand All @@ -59,76 +59,84 @@ def build_form(self, form_kwargs=None):
}
if form_kwargs:
kwargs.update(form_kwargs)

if len(self.request.GET):
data = self.request.GET

if self.searchqueryset is not None:
kwargs['searchqueryset'] = self.searchqueryset

return self.form_class(data, **kwargs)

def get_query(self):
"""
Returns the query provided by the user.
Returns an empty string if the query is invalid.
"""
if self.form.is_valid():
return self.form.cleaned_data['q']

return ''

def get_results(self):
"""
Fetches the results via the form.
Returns an empty list if there's no query to search with.
"""
return self.form.search()

def build_page(self):
"""
Paginates the results appropriately.
In case someone does not want to use Django's built-in pagination, it
should be a simple matter to override this method to do what they would
like.
"""
try:
page_no = int(self.request.GET.get('page', 1))
except (TypeError, ValueError):
raise Http404

start_offset = (page_no - 1) * self.results_per_page
self.results[start_offset:start_offset + self.results_per_page]

paginator = Paginator(self.results, self.results_per_page)

try:
page = paginator.page(self.request.GET.get('page', 1))
page = paginator.page(page_no)
except InvalidPage:
raise Http404

return (paginator, page)

def extra_context(self):
"""
Allows the addition of more context variables as needed.
Must return a dictionary.
"""
return {}

def create_response(self):
"""
Generates the actual HttpResponse to send back to the user.
"""
(paginator, page) = self.build_page()

context = {
'query': self.query,
'form': self.form,
'page': page,
'paginator': paginator,
'suggestion': None,
}

if self.results.query.backend.include_spelling:
context['suggestion'] = self.form.get_suggestion()

context.update(self.extra_context())
return render_to_response(self.template, context, context_instance=self.context_class(self.request))

Expand All @@ -141,24 +149,24 @@ def search_view(request):

class FacetedSearchView(SearchView):
__name__ = 'FacetedSearchView'

def __init__(self, *args, **kwargs):
# Needed to switch out the default form class.
if kwargs.get('form_class') is None:
kwargs['form_class'] = FacetedSearchForm

super(FacetedSearchView, self).__init__(*args, **kwargs)

def build_form(self, form_kwargs=None):
if form_kwargs is None:
form_kwargs = {}

# This way the form can always receive a list containing zero or more
# facet expressions:
form_kwargs['selected_facets'] = self.request.GET.getlist("selected_facets")

return super(FacetedSearchView, self).build_form(form_kwargs)

def extra_context(self):
extra = super(FacetedSearchView, self).extra_context()
extra['request'] = self.request
Expand All @@ -170,12 +178,12 @@ def basic_search(request, template='search/search.html', load_all=True, form_cla
"""
A more traditional view that also demonstrate an alternative
way to use Haystack.
Useful as an example of for basing heavily custom views off of.
Also has the benefit of thread-safety, which the ``SearchView`` class may
not be.
Template:: ``search/search.html``
Context::
* form
Expand All @@ -189,35 +197,35 @@ def basic_search(request, template='search/search.html', load_all=True, form_cla
"""
query = ''
results = EmptySearchQuerySet()

if request.GET.get('q'):
form = form_class(request.GET, searchqueryset=searchqueryset, load_all=load_all)

if form.is_valid():
query = form.cleaned_data['q']
results = form.search()
else:
form = form_class(searchqueryset=searchqueryset, load_all=load_all)

paginator = Paginator(results, results_per_page or RESULTS_PER_PAGE)

try:
page = paginator.page(int(request.GET.get('page', 1)))
except InvalidPage:
raise Http404("No such page of results!")

context = {
'form': form,
'page': page,
'paginator': paginator,
'query': query,
'suggestion': None,
}

if results.query.backend.include_spelling:
context['suggestion'] = form.get_suggestion()

if extra_context:
context.update(extra_context)

return render_to_response(template, context, context_instance=context_class(request))

0 comments on commit d0ab16f

Please sign in to comment.