annotate custom_search/forms.py @ 943:cf9918328c64

Haystack tweaks for Django 1.7.7. I had to upgrade to Haystack 2.3.1 to get it to work with Django 1.7.7. I also had to update the Xapian backend. But I ran into problems. On my laptop anyway (Ubuntu 14.0.4), xapian gets mad when search terms are greater than 245 chars (or something) when indexing. So I created a custom field that would simply omit terms greater than 64 chars and used this field everywhere I previously used a CharField. Secondly, the custom search form was broken now. Something changed in the Xapian backend and exact searches stopped working. Fortunately the auto_query (which I was using originally and broke during an upgrade) started working again. So I cut the search form back over to doing an auto_query. I kept the form the same (3 fields) because I didn't want to change the form and I think it's better that way.
author Brian Neal <bgneal@gmail.com>
date Wed, 13 May 2015 20:25:07 -0500
parents 20a3bf7a6370
children 6cc9221d04a7
rev   line source
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@753 57 if not settings.SEARCH_QUEUE_ENABLED:
bgneal@753 58 raise forms.ValidationError("Our search function is offline for "
bgneal@753 59 "maintenance. Please try again later. "
bgneal@753 60 "We apologize for any inconvenience.")
bgneal@753 61
bgneal@753 62 if not (self.cleaned_data['q'] or self.cleaned_data['exact'] or
bgneal@753 63 self.cleaned_data['exclude']):
bgneal@753 64 raise forms.ValidationError('Please supply some search terms')
bgneal@753 65
bgneal@753 66 return self.cleaned_data
bgneal@753 67
bgneal@943 68 def clean_exact(self):
bgneal@943 69 exact_field = self.cleaned_data['exact']
bgneal@943 70 if "'" in exact_field or '"' in exact_field:
bgneal@943 71 raise forms.ValidationError("Quotes are not needed in this field")
bgneal@943 72 return exact_field
bgneal@943 73
bgneal@753 74 def search(self):
bgneal@753 75 if not self.is_valid():
bgneal@753 76 return self.no_query_found()
bgneal@753 77
bgneal@763 78 if self.user is None:
bgneal@763 79 username = 'UNKNOWN'
bgneal@763 80 elif self.user.is_authenticated():
bgneal@763 81 username = self.user.username
bgneal@763 82 else:
bgneal@763 83 username = 'ANONYMOUS'
bgneal@763 84
bgneal@763 85 logger.info('Search executed: /%s/%s/%s/ in %s by %s',
bgneal@753 86 self.cleaned_data['q'],
bgneal@753 87 self.cleaned_data['exact'],
bgneal@753 88 self.cleaned_data['exclude'],
bgneal@763 89 self.cleaned_data['models'],
bgneal@763 90 username)
bgneal@753 91
bgneal@753 92 # Note that in Haystack 2.x content is untrusted and is automatically
bgneal@753 93 # auto-escaped for us.
bgneal@753 94 #
bgneal@943 95 # Gather regular search terms
bgneal@943 96 terms = ' '.join(self.cleaned_data['q'].split())
bgneal@753 97
bgneal@753 98 # Exact words or phrases:
bgneal@943 99 exact = self.cleaned_data['exact'].strip()
bgneal@943 100 if exact:
bgneal@943 101 exact = '"{}"'.format(exact)
bgneal@753 102
bgneal@753 103 # Exclude terms:
bgneal@943 104 exclude = ["-{}".format(term) for term in self.cleaned_data['exclude'].split()]
bgneal@943 105 exclude = ' '.join(exclude)
bgneal@943 106
bgneal@943 107 query = ' '.join([terms, exact, exclude]).strip()
bgneal@943 108 logger.debug("auto_query: %s", query)
bgneal@943 109
bgneal@943 110 sqs = self.searchqueryset.auto_query(query)
bgneal@753 111
bgneal@753 112 if self.load_all:
bgneal@753 113 sqs = sqs.load_all()
bgneal@753 114
bgneal@753 115 # Apply model filtering
bgneal@753 116 sqs = sqs.models(*self.get_models())
bgneal@753 117
bgneal@753 118 return sqs