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