bgneal@469
|
1 """
|
bgneal@469
|
2 This module contains custom forms to tailor the Haystack search application to
|
bgneal@469
|
3 our needs.
|
bgneal@469
|
4
|
bgneal@469
|
5 """
|
bgneal@753
|
6 import logging
|
bgneal@753
|
7
|
bgneal@469
|
8 from django import forms
|
bgneal@753
|
9 from django.conf import settings
|
bgneal@469
|
10 from haystack.forms import ModelSearchForm
|
bgneal@469
|
11
|
bgneal@469
|
12
|
bgneal@469
|
13 MODEL_CHOICES = (
|
bgneal@469
|
14 ('forums.topic', 'Forum Topics'),
|
bgneal@469
|
15 ('forums.post', 'Forum Posts'),
|
bgneal@469
|
16 ('news.story', 'News Stories'),
|
bgneal@469
|
17 ('bio.userprofile', 'User Profiles'),
|
bgneal@469
|
18 ('weblinks.link', 'Links'),
|
bgneal@469
|
19 ('downloads.download', 'Downloads'),
|
bgneal@469
|
20 ('podcast.item', 'Podcasts'),
|
bgneal@469
|
21 ('ygroup.post', 'Yahoo Group Archives'),
|
bgneal@469
|
22 )
|
bgneal@469
|
23
|
bgneal@753
|
24 logger = logging.getLogger(__name__)
|
bgneal@753
|
25
|
bgneal@469
|
26
|
bgneal@469
|
27 class CustomModelSearchForm(ModelSearchForm):
|
bgneal@469
|
28 """
|
bgneal@469
|
29 This customized ModelSearchForm allows us to explictly label and order
|
bgneal@469
|
30 the model choices.
|
bgneal@469
|
31
|
bgneal@753
|
32 We also provide "all words", "exact phrase", and "exclude" text input boxes.
|
bgneal@753
|
33 Haystack 2.1.0's auto_query() function did not seem to work right so we just
|
bgneal@753
|
34 rolled our own.
|
bgneal@753
|
35
|
bgneal@763
|
36 This form can optionally receive the user making the search as a keyword
|
bgneal@763
|
37 argument ('user') to __init__. This will be used for logging search queries.
|
bgneal@763
|
38
|
bgneal@469
|
39 """
|
bgneal@753
|
40 q = forms.CharField(required=False, label='All these words',
|
bgneal@753
|
41 widget=forms.TextInput(attrs={'type': 'search', 'class': 'search',
|
bgneal@753
|
42 'size': 48}))
|
bgneal@753
|
43 exact = forms.CharField(required=False, label='This exact word or phrase',
|
bgneal@753
|
44 widget=forms.TextInput(attrs={'type': 'search', 'class': 'search',
|
bgneal@753
|
45 'size': 48}))
|
bgneal@753
|
46 exclude = forms.CharField(required=False, label='None of these words',
|
bgneal@753
|
47 widget=forms.TextInput(attrs={'type': 'search', 'class': 'search',
|
bgneal@753
|
48 'size': 48}))
|
bgneal@469
|
49
|
bgneal@469
|
50 def __init__(self, *args, **kwargs):
|
bgneal@763
|
51 self.user = kwargs.pop('user', None)
|
bgneal@469
|
52 super(CustomModelSearchForm, self).__init__(*args, **kwargs)
|
bgneal@469
|
53 self.fields['models'] = forms.MultipleChoiceField(choices=MODEL_CHOICES,
|
bgneal@753
|
54 label='Search in', widget=forms.CheckboxSelectMultiple)
|
bgneal@753
|
55
|
bgneal@753
|
56 def clean(self):
|
bgneal@956
|
57 super(CustomModelSearchForm, self).clean()
|
bgneal@753
|
58 if not settings.SEARCH_QUEUE_ENABLED:
|
bgneal@753
|
59 raise forms.ValidationError("Our search function is offline for "
|
bgneal@753
|
60 "maintenance. Please try again later. "
|
bgneal@753
|
61 "We apologize for any inconvenience.")
|
bgneal@753
|
62
|
bgneal@956
|
63 q = self.cleaned_data.get('q')
|
bgneal@956
|
64 exact = self.cleaned_data.get('exact')
|
bgneal@956
|
65 exclude = self.cleaned_data.get('exclude')
|
bgneal@956
|
66
|
bgneal@956
|
67 if not (q or exact or exclude):
|
bgneal@753
|
68 raise forms.ValidationError('Please supply some search terms')
|
bgneal@753
|
69
|
bgneal@753
|
70 return self.cleaned_data
|
bgneal@753
|
71
|
bgneal@943
|
72 def clean_exact(self):
|
bgneal@943
|
73 exact_field = self.cleaned_data['exact']
|
bgneal@943
|
74 if "'" in exact_field or '"' in exact_field:
|
bgneal@943
|
75 raise forms.ValidationError("Quotes are not needed in this field")
|
bgneal@943
|
76 return exact_field
|
bgneal@943
|
77
|
bgneal@753
|
78 def search(self):
|
bgneal@753
|
79 if not self.is_valid():
|
bgneal@753
|
80 return self.no_query_found()
|
bgneal@753
|
81
|
bgneal@763
|
82 if self.user is None:
|
bgneal@763
|
83 username = 'UNKNOWN'
|
bgneal@763
|
84 elif self.user.is_authenticated():
|
bgneal@763
|
85 username = self.user.username
|
bgneal@763
|
86 else:
|
bgneal@763
|
87 username = 'ANONYMOUS'
|
bgneal@763
|
88
|
bgneal@763
|
89 logger.info('Search executed: /%s/%s/%s/ in %s by %s',
|
bgneal@753
|
90 self.cleaned_data['q'],
|
bgneal@753
|
91 self.cleaned_data['exact'],
|
bgneal@753
|
92 self.cleaned_data['exclude'],
|
bgneal@763
|
93 self.cleaned_data['models'],
|
bgneal@763
|
94 username)
|
bgneal@753
|
95
|
bgneal@753
|
96 # Note that in Haystack 2.x content is untrusted and is automatically
|
bgneal@753
|
97 # auto-escaped for us.
|
bgneal@753
|
98 #
|
bgneal@943
|
99 # Gather regular search terms
|
bgneal@1034
|
100 terms = u' '.join(self.cleaned_data['q'].split())
|
bgneal@753
|
101
|
bgneal@753
|
102 # Exact words or phrases:
|
bgneal@943
|
103 exact = self.cleaned_data['exact'].strip()
|
bgneal@943
|
104 if exact:
|
bgneal@1034
|
105 exact = u'"{}"'.format(exact)
|
bgneal@753
|
106
|
bgneal@753
|
107 # Exclude terms:
|
bgneal@1034
|
108 exclude = [u"-{}".format(term) for term in self.cleaned_data['exclude'].split()]
|
bgneal@1034
|
109 exclude = u' '.join(exclude)
|
bgneal@943
|
110
|
bgneal@1034
|
111 query = u' '.join([terms, exact, exclude]).strip()
|
bgneal@943
|
112 logger.debug("auto_query: %s", query)
|
bgneal@943
|
113
|
bgneal@943
|
114 sqs = self.searchqueryset.auto_query(query)
|
bgneal@753
|
115
|
bgneal@753
|
116 if self.load_all:
|
bgneal@753
|
117 sqs = sqs.load_all()
|
bgneal@753
|
118
|
bgneal@753
|
119 # Apply model filtering
|
bgneal@753
|
120 sqs = sqs.models(*self.get_models())
|
bgneal@753
|
121
|
bgneal@753
|
122 return sqs
|