bgneal@469: """ bgneal@469: This module contains custom forms to tailor the Haystack search application to bgneal@469: our needs. bgneal@469: bgneal@469: """ bgneal@753: import logging bgneal@753: bgneal@469: from django import forms bgneal@753: from django.conf import settings bgneal@469: from haystack.forms import ModelSearchForm bgneal@469: bgneal@469: bgneal@469: MODEL_CHOICES = ( bgneal@469: ('forums.topic', 'Forum Topics'), bgneal@469: ('forums.post', 'Forum Posts'), bgneal@469: ('news.story', 'News Stories'), bgneal@469: ('bio.userprofile', 'User Profiles'), bgneal@469: ('weblinks.link', 'Links'), bgneal@469: ('downloads.download', 'Downloads'), bgneal@469: ('podcast.item', 'Podcasts'), bgneal@469: ('ygroup.post', 'Yahoo Group Archives'), bgneal@469: ) bgneal@469: bgneal@753: logger = logging.getLogger(__name__) bgneal@753: bgneal@469: bgneal@469: class CustomModelSearchForm(ModelSearchForm): bgneal@469: """ bgneal@469: This customized ModelSearchForm allows us to explictly label and order bgneal@469: the model choices. bgneal@469: bgneal@753: We also provide "all words", "exact phrase", and "exclude" text input boxes. bgneal@753: Haystack 2.1.0's auto_query() function did not seem to work right so we just bgneal@753: rolled our own. bgneal@753: bgneal@763: This form can optionally receive the user making the search as a keyword bgneal@763: argument ('user') to __init__. This will be used for logging search queries. bgneal@763: bgneal@469: """ bgneal@753: q = forms.CharField(required=False, label='All these words', bgneal@753: widget=forms.TextInput(attrs={'type': 'search', 'class': 'search', bgneal@753: 'size': 48})) bgneal@753: exact = forms.CharField(required=False, label='This exact word or phrase', bgneal@753: widget=forms.TextInput(attrs={'type': 'search', 'class': 'search', bgneal@753: 'size': 48})) bgneal@753: exclude = forms.CharField(required=False, label='None of these words', bgneal@753: widget=forms.TextInput(attrs={'type': 'search', 'class': 'search', bgneal@753: 'size': 48})) bgneal@469: bgneal@469: def __init__(self, *args, **kwargs): bgneal@763: self.user = kwargs.pop('user', None) bgneal@469: super(CustomModelSearchForm, self).__init__(*args, **kwargs) bgneal@469: self.fields['models'] = forms.MultipleChoiceField(choices=MODEL_CHOICES, bgneal@753: label='Search in', widget=forms.CheckboxSelectMultiple) bgneal@753: bgneal@753: def clean(self): bgneal@753: if not settings.SEARCH_QUEUE_ENABLED: bgneal@753: raise forms.ValidationError("Our search function is offline for " bgneal@753: "maintenance. Please try again later. " bgneal@753: "We apologize for any inconvenience.") bgneal@753: bgneal@753: if not (self.cleaned_data['q'] or self.cleaned_data['exact'] or bgneal@753: self.cleaned_data['exclude']): bgneal@753: raise forms.ValidationError('Please supply some search terms') bgneal@753: bgneal@753: return self.cleaned_data bgneal@753: bgneal@943: def clean_exact(self): bgneal@943: exact_field = self.cleaned_data['exact'] bgneal@943: if "'" in exact_field or '"' in exact_field: bgneal@943: raise forms.ValidationError("Quotes are not needed in this field") bgneal@943: return exact_field bgneal@943: bgneal@753: def search(self): bgneal@753: if not self.is_valid(): bgneal@753: return self.no_query_found() bgneal@753: bgneal@763: if self.user is None: bgneal@763: username = 'UNKNOWN' bgneal@763: elif self.user.is_authenticated(): bgneal@763: username = self.user.username bgneal@763: else: bgneal@763: username = 'ANONYMOUS' bgneal@763: bgneal@763: logger.info('Search executed: /%s/%s/%s/ in %s by %s', bgneal@753: self.cleaned_data['q'], bgneal@753: self.cleaned_data['exact'], bgneal@753: self.cleaned_data['exclude'], bgneal@763: self.cleaned_data['models'], bgneal@763: username) bgneal@753: bgneal@753: # Note that in Haystack 2.x content is untrusted and is automatically bgneal@753: # auto-escaped for us. bgneal@753: # bgneal@943: # Gather regular search terms bgneal@943: terms = ' '.join(self.cleaned_data['q'].split()) bgneal@753: bgneal@753: # Exact words or phrases: bgneal@943: exact = self.cleaned_data['exact'].strip() bgneal@943: if exact: bgneal@943: exact = '"{}"'.format(exact) bgneal@753: bgneal@753: # Exclude terms: bgneal@943: exclude = ["-{}".format(term) for term in self.cleaned_data['exclude'].split()] bgneal@943: exclude = ' '.join(exclude) bgneal@943: bgneal@943: query = ' '.join([terms, exact, exclude]).strip() bgneal@943: logger.debug("auto_query: %s", query) bgneal@943: bgneal@943: sqs = self.searchqueryset.auto_query(query) bgneal@753: bgneal@753: if self.load_all: bgneal@753: sqs = sqs.load_all() bgneal@753: bgneal@753: # Apply model filtering bgneal@753: sqs = sqs.models(*self.get_models()) bgneal@753: bgneal@753: return sqs