public
Description: Modular search for Django. (rc1)
Homepage: http://haystacksearch.org/
Clone URL: git://github.com/toastdriven/django-haystack.git
django-haystack / haystack / views.py
100644 185 lines (139 sloc) 5.462 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
from django.conf import settings
from django.core.paginator import Paginator, InvalidPage
from django.http import Http404
from django.shortcuts import render_to_response
from django.template import RequestContext
from haystack.forms import ModelSearchForm
 
 
RESULTS_PER_PAGE = getattr(settings, 'HAYSTACK_SEARCH_RESULTS_PER_PAGE', 20)
 
 
class SearchView(object):
    template = 'search/search.html'
    extra_context = {}
    query = ''
    results = []
    request = None
    form = None
    
    def __init__(self, template=None, load_all=True, form_class=ModelSearchForm, searchqueryset=None, context_class=RequestContext):
        self.load_all = load_all
        self.form_class = form_class
        self.context_class = context_class
        self.searchqueryset = searchqueryset
        
        if template:
            self.template = template
 
    def __name__(self):
        return "SearchView"
 
    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):
        """
Instantiates the form the class should use to process the search query.
"""
        if self.searchqueryset is None:
            return self.form_class(self.request.GET, load_all=self.load_all)
        
        return self.form_class(self.request.GET, searchqueryset=self.searchqueryset, load_all=self.load_all)
    
    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.
"""
        if self.query:
            return self.form.search()
        
        return []
    
    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.
"""
        paginator = Paginator(self.results, RESULTS_PER_PAGE)
        
        try:
            page = paginator.page(self.request.GET.get('page', 1))
        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,
        }
        context.update(self.extra_context())
        
        return render_to_response(self.template, context, context_instance=self.context_class(self.request))
 
 
class FacetedSearchView(SearchView):
    def __name__(self):
        return "FacetedSearchView"
    
    def extra_context(self):
        extra = super(FacetedSearchView, self).extra_context()
        
        if self.results == []:
            extra['facets'] = self.form.search().facet_counts()
        else:
            extra['facets'] = self.results.facet_counts()
        
        return extra
 
 
def basic_search(request, template='search/search.html', load_all=True, form_class=ModelSearchForm, searchqueryset=None, context_class=RequestContext, extra_context=None):
    """
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
An instance of the ``form_class``. (default: ``ModelSearchForm``)
* page
The current page of search results.
* paginator
A paginator instance for the results.
* query
The query received by the form.
"""
    query = ''
    results = []
    
    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)
    
    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,
    }
    
    if extra_context:
        context.update(extra_context)
    
    return render_to_response(template, context, context_instance=context_class(request))