1.. _ref-views-and_forms:
2
3=============
4Views & Forms
5=============
6
7.. note::
8
9    As of version 2.4 the views in ``haystack.views.SearchView`` are deprecated in
10    favor of the new generic views in ``haystack.generic_views.SearchView``
11    which use the standard Django `class-based views`_ which are available in
12    every version of Django which is supported by Haystack.
13
14.. _class-based views: https://docs.djangoproject.com/en/1.7/topics/class-based-views/
15
16Haystack comes with some default, simple views & forms as well as some
17django-style views to help you get started and to cover the common cases.
18Included is a way to provide:
19
20  * Basic, query-only search.
21  * Search by models.
22  * Search with basic highlighted results.
23  * Faceted search.
24  * Search by models with basic highlighted results.
25
26Most processing is done by the forms provided by Haystack via the ``search``
27method. As a result, all but the faceted types (see :doc:`faceting`) use the
28standard ``SearchView``.
29
30There is very little coupling between the forms & the views (other than relying
31on the existence of a ``search`` method on the form), so you may interchangeably
32use forms and/or views anywhere within your own code.
33
34Forms
35=====
36
37.. currentmodule:: haystack.forms
38
39``SearchForm``
40--------------
41
42The most basic of the form types, this form consists of a single field, the
43``q`` field (for query). Upon searching, the form will take the cleaned contents
44of the ``q`` field and perform an ``auto_query`` on either the custom
45``SearchQuerySet`` you provide or off a default ``SearchQuerySet``.
46
47To customize the ``SearchQuerySet`` the form will use, pass it a
48``searchqueryset`` parameter to the constructor with the ``SearchQuerySet``
49you'd like to use. If using this form in conjunction with a ``SearchView``,
50the form will receive whatever ``SearchQuerySet`` you provide to the view with
51no additional work needed.
52
53The ``SearchForm`` also accepts a ``load_all`` parameter (``True`` or
54``False``), which determines how the database is queried when iterating through
55the results. This also is received automatically from the ``SearchView``.
56
57All other forms in Haystack inherit (either directly or indirectly) from this
58form.
59
60``HighlightedSearchForm``
61-------------------------
62
63Identical to the ``SearchForm`` except that it tags the ``highlight`` method on
64to the end of the ``SearchQuerySet`` to enable highlighted results.
65
66``ModelSearchForm``
67-------------------
68
69This form adds new fields to form. It iterates through all registered models for
70the current ``SearchSite`` and provides a checkbox for each one. If no models
71are selected, all types will show up in the results.
72
73``HighlightedModelSearchForm``
74------------------------------
75
76Identical to the ``ModelSearchForm`` except that it tags the ``highlight``
77method on to the end of the ``SearchQuerySet`` to enable highlighted results on
78the selected models.
79
80``FacetedSearchForm``
81---------------------
82
83Identical to the ``SearchForm`` except that it adds a hidden ``selected_facets``
84field onto the form, allowing the form to narrow the results based on the facets
85chosen by the user.
86
87Creating Your Own Form
88----------------------
89
90The simplest way to go about creating your own form is to inherit from
91``SearchForm`` (or the desired parent) and extend the ``search`` method. By
92doing this, you save yourself most of the work of handling data correctly and
93stay API compatible with the ``SearchView``.
94
95For example, let's say you're providing search with a user-selectable date range
96associated with it. You might create a form that looked as follows::
97
98    from django import forms
99    from haystack.forms import SearchForm
100
101
102    class DateRangeSearchForm(SearchForm):
103        start_date = forms.DateField(required=False)
104        end_date = forms.DateField(required=False)
105
106        def search(self):
107            # First, store the SearchQuerySet received from other processing.
108            sqs = super(DateRangeSearchForm, self).search()
109
110            if not self.is_valid():
111                return self.no_query_found()
112
113            # Check to see if a start_date was chosen.
114            if self.cleaned_data['start_date']:
115                sqs = sqs.filter(pub_date__gte=self.cleaned_data['start_date'])
116
117            # Check to see if an end_date was chosen.
118            if self.cleaned_data['end_date']:
119                sqs = sqs.filter(pub_date__lte=self.cleaned_data['end_date'])
120
121            return sqs
122
123This form adds two new fields for (optionally) choosing the start and end dates.
124Within the ``search`` method, we grab the results from the parent form's
125processing. Then, if a user has selected a start and/or end date, we apply that
126filtering. Finally, we simply return the ``SearchQuerySet``.
127
128Views
129=====
130
131.. currentmodule:: haystack.views
132
133.. note::
134
135    As of version 2.4 the views in ``haystack.views.SearchView`` are deprecated in
136    favor of the new generic views in ``haystack.generic_views.SearchView``
137    which use the standard Django `class-based views`_ which are available in
138    every version of Django which is supported by Haystack.
139
140.. _class-based views: https://docs.djangoproject.com/en/1.7/topics/class-based-views/
141
142New Django Class Based Views
143----------------------------
144
145 .. versionadded:: 2.4.0
146
147The views in ``haystack.generic_views.SearchView`` inherit from Django’s standard
148`FormView <https://docs.djangoproject.com/en/1.7/ref/class-based-views/generic-editing/#formview>`_.
149The example views can be customized like any other Django class-based view as
150demonstrated in this example which filters the search results in ``get_queryset``::
151
152    # views.py
153    from datetime import date
154
155    from haystack.generic_views import SearchView
156
157    class MySearchView(SearchView):
158        """My custom search view."""
159
160        def get_queryset(self):
161            queryset = super(MySearchView, self).get_queryset()
162            # further filter queryset based on some set of criteria
163            return queryset.filter(pub_date__gte=date(2015, 1, 1))
164
165        def get_context_data(self, *args, **kwargs):
166            context = super(MySearchView, self).get_context_data(*args, **kwargs)
167            # do something
168            return context
169
170    # urls.py
171
172    urlpatterns = [
173        url(r'^/search/?$', MySearchView.as_view(), name='search_view'),
174    ]
175
176
177Upgrading
178~~~~~~~~~
179
180Upgrading from basic usage of the old-style views to new-style views is usually as simple as:
181
182#. Create new views under ``views.py`` subclassing ``haystack.generic_views.SearchView``
183   or ``haystack.generic_views.FacetedSearchView``
184#. Move all parameters of your old-style views from your ``urls.py`` to attributes on
185   your new views. This will require renaming ``searchqueryset`` to ``queryset`` and
186   ``template`` to ``template_name``
187#. Review your templates and replace the ``page`` variable with ``page_obj``
188
189Here's an example::
190
191    ### old-style views...
192    # urls.py
193
194    sqs = SearchQuerySet().filter(author='john')
195
196    urlpatterns = [
197        url(r'^$', SearchView(
198            template='my/special/path/john_search.html',
199            searchqueryset=sqs,
200            form_class=SearchForm
201        ), name='haystack_search'),
202    ]
203
204    ### new-style views...
205    # views.py
206
207    class JohnSearchView(SearchView):
208        template_name = 'my/special/path/john_search.html'
209        queryset = SearchQuerySet().filter(author='john')
210        form_class = SearchForm
211
212    # urls.py
213    from myapp.views import JohnSearchView
214
215    urlpatterns = [
216        url(r'^$', JohnSearchView.as_view(), name='haystack_search'),
217    ]
218
219
220If your views overrode methods on the old-style SearchView, you will need to
221refactor those methods to the equivalents on Django's generic views. For example,
222if you previously used ``extra_context()`` to add additional template variables or
223preprocess the values returned by Haystack, that code would move to ``get_context_data``
224
225+-----------------------+-------------------------------------------+
226| Old Method            | New Method                                |
227+=======================+===========================================+
228| ``extra_context()``   | `get_context_data()`_                     |
229+-----------------------+-------------------------------------------+
230| ``create_response()`` | `dispatch()`_ or ``get()`` and ``post()`` |
231+-----------------------+-------------------------------------------+
232| ``get_query()``       | `get_queryset()`_                         |
233+-----------------------+-------------------------------------------+
234
235.. _get_context_data(): https://docs.djangoproject.com/en/1.7/ref/class-based-views/mixins-simple/#django.views.generic.base.ContextMixin.get_context_data
236.. _dispatch(): https://docs.djangoproject.com/en/1.7/ref/class-based-views/base/#django.views.generic.base.View.dispatch
237.. _get_queryset(): https://docs.djangoproject.com/en/1.7/ref/class-based-views/mixins-multiple-object/#django.views.generic.list.MultipleObjectMixin.get_queryset
238
239
240Old-Style Views
241---------------
242
243 .. deprecated:: 2.4.0
244
245Haystack comes bundled with three views, the class-based views (``SearchView`` &
246``FacetedSearchView``) and a traditional functional view (``basic_search``).
247
248The class-based views provide for easy extension should you need to alter the
249way a view works. Except in the case of faceting (again, see :doc:`faceting`),
250the ``SearchView`` works interchangeably with all other forms provided by
251Haystack.
252
253The functional view provides an example of how Haystack can be used in more
254traditional settings or as an example of how to write a more complex custom
255view. It is also thread-safe.
256
257``SearchView(template=None, load_all=True, form_class=None, searchqueryset=None, results_per_page=None)``
258---------------------------------------------------------------------------------------------------------------------------------------
259
260The ``SearchView`` is designed to be easy/flexible enough to override common
261changes as well as being internally abstracted so that only altering a specific
262portion of the code should be easy to do.
263
264Without touching any of the internals of the ``SearchView``, you can modify
265which template is used, which form class should be instantiated to search with,
266what ``SearchQuerySet`` to use in the event you wish to pre-filter the results.
267what ``Context``-style object to use in the response and the ``load_all``
268performance optimization to reduce hits on the database. These options can (and
269generally should) be overridden at the URLconf level. For example, to have a
270custom search limited to the 'John' author, displaying all models to search by
271and specifying a custom template (``my/special/path/john_search.html``), your
272URLconf should look something like::
273
274    from django.conf.urls import url
275    from haystack.forms import ModelSearchForm
276    from haystack.query import SearchQuerySet
277    from haystack.views import SearchView
278
279    sqs = SearchQuerySet().filter(author='john')
280
281    # Without threading...
282    urlpatterns = [
283        url(r'^$', SearchView(
284            template='my/special/path/john_search.html',
285            searchqueryset=sqs,
286            form_class=SearchForm
287        ), name='haystack_search'),
288    ]
289
290    # With threading...
291    from haystack.views import SearchView, search_view_factory
292
293    urlpatterns = [
294        url(r'^$', search_view_factory(
295            view_class=SearchView,
296            template='my/special/path/john_search.html',
297            searchqueryset=sqs,
298            form_class=ModelSearchForm
299        ), name='haystack_search'),
300    ]
301
302.. warning::
303
304    The standard ``SearchView`` is not thread-safe. Use the
305    ``search_view_factory`` function, which returns thread-safe instances of
306    ``SearchView``.
307
308By default, if you don't specify a ``form_class``, the view will use the
309``haystack.forms.ModelSearchForm`` form.
310
311Beyond this customizations, you can create your own ``SearchView`` and
312extend/override the following methods to change the functionality.
313
314``__call__(self, request)``
315~~~~~~~~~~~~~~~~~~~~~~~~~~~
316
317Generates the actual response to the search.
318
319Relies on internal, overridable methods to construct the response. You generally
320should avoid altering this method unless you need to change the flow of the
321methods or to add a new method into the processing.
322
323``build_form(self, form_kwargs=None)``
324~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
325
326Instantiates the form the class should use to process the search query.
327
328Optionally accepts a dictionary of parameters that are passed on to the
329form's ``__init__``. You can use this to lightly customize the form.
330
331You should override this if you write a custom form that needs special
332parameters for instantiation.
333
334``get_query(self)``
335~~~~~~~~~~~~~~~~~~~
336
337Returns the query provided by the user.
338
339Returns an empty string if the query is invalid. This pulls the cleaned query
340from the form, via the ``q`` field, for use elsewhere within the ``SearchView``.
341This is used to populate the ``query`` context variable.
342
343``get_results(self)``
344~~~~~~~~~~~~~~~~~~~~~
345
346Fetches the results via the form.
347
348Returns an empty list if there's no query to search with. This method relies on
349the form to do the heavy lifting as much as possible.
350
351``build_page(self)``
352~~~~~~~~~~~~~~~~~~~~
353
354Paginates the results appropriately.
355
356In case someone does not want to use Django's built-in pagination, it
357should be a simple matter to override this method to do what they would
358like.
359
360``extra_context(self)``
361~~~~~~~~~~~~~~~~~~~~~~~
362
363Allows the addition of more context variables as needed. Must return a
364dictionary whose contents will add to or overwrite the other variables in the
365context.
366
367``create_response(self)``
368~~~~~~~~~~~~~~~~~~~~~~~~~
369
370Generates the actual HttpResponse to send back to the user. It builds the page,
371creates the context and renders the response for all the aforementioned
372processing.
373
374
375``basic_search(request, template='search/search.html', load_all=True, form_class=ModelSearchForm, searchqueryset=None, extra_context=None, results_per_page=None)``
376-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
377
378The ``basic_search`` tries to provide most of the same functionality as the
379class-based views but resembles a more traditional generic view. It's both a
380working view if you prefer not to use the class-based views as well as a good
381starting point for writing highly custom views.
382
383Since it is all one function, the only means of extension are passing in
384kwargs, similar to the way generic views work.
385
386
387Creating Your Own View
388----------------------
389
390As with the forms, inheritance is likely your best bet. In this case, the
391``FacetedSearchView`` is a perfect example of how to extend the existing
392``SearchView``. The complete code for the ``FacetedSearchView`` looks like::
393
394    class FacetedSearchView(SearchView):
395        def extra_context(self):
396            extra = super(FacetedSearchView, self).extra_context()
397
398            if self.results == []:
399                extra['facets'] = self.form.search().facet_counts()
400            else:
401                extra['facets'] = self.results.facet_counts()
402
403            return extra
404
405It updates the name of the class (generally for documentation purposes) and
406adds the facets from the ``SearchQuerySet`` to the context as the ``facets``
407variable. As with the custom form example above, it relies on the parent class
408to handle most of the processing and extends that only where needed.
409