def test_validation(self): # no data - not valid form = AdvancedSearchForm(data={}) self.assertFalse(form.is_valid(), 'advanced search form is not valid when no fields are specified') self.assertTrue(form.non_field_errors(), 'a non-field error is displayed when no search terms are entered') # any one field - valid form = AdvancedSearchForm(data={'keywords': 'foo'}) self.assertTrue(form.is_valid(), 'advanced search form is valid when only keywords are specified') form = AdvancedSearchForm(data={'subject': 'bar'}) self.assertTrue(form.is_valid(), 'advanced search form is valid when only subject is specified') # FIXME: this could break due to caching when testing & running a dev site at the same time # adjust the test to clear the cache or use a separate cache for tests # grab a valid choice from the current options in the form: repo = form.fields['repository'].choices[0][0] # id value of first option tuple form = AdvancedSearchForm(data={'repository': repo}) self.assertTrue(form.is_valid(), 'advanced search form is valid when only repository is specified')
def search(request): "Simple keyword search - runs exist full-text terms query on all terms included." form = AdvancedSearchForm(request.GET) query_error = False if form.is_valid(): # form validation requires that at least one of subject & keyword is not empty subject = form.cleaned_data['subject'] keywords = form.cleaned_data['keywords'] repository = form.cleaned_data['repository'] dao = form.cleaned_data['dao'] page = request.GET.get('page', 1) # initialize findingaid queryset - filters will be added based on search terms findingaids = FindingAid.objects # local copy of return fields (fulltext-score may be added-- don't modify master copy!) return_fields = fa_listfields[:] try: if subject: # if a subject was specified, filter on subject findingaids = findingaids.filter(subject__fulltext_terms=subject).order_by('list_title') # order by list title when searching by subject only # (if keywords are specified, fulltext score ordering will override this) if repository: # if repository is set, filter finding aids by requested repository # expecting repository value to come in as exact phrase findingaids = findingaids.filter(repository__fulltext_terms=repository).order_by('list_title') if keywords: # if keywords were specified, do a fulltext search return_fields.append('fulltext_score') findingaids = findingaids.filter( # first do a full-text search to restrict to relevant documents fulltext_terms=keywords ).or_filter( # do an OR search on boosted fields, so that relevance score # will be calculated based on boosted field values fulltext_terms=keywords, boostfields__fulltext_terms=keywords, highlight=False, # disable highlighting in search results list ).order_by('-fulltext_score') # optional filter: restrict to items with digital archival objects if dao: findingaids = findingaids.filter(daos__exists=True) # if user does not have permission to view internal daos, # restrict to public daos only if not request.user.has_perm('fa_admin.can_view_internal_dao'): findingaids = findingaids.filter(public_dao_count__gte=1) # NOTE: using >= filter to force a where clause because this works # when what seems to be the same filter on the xpath does not # (possibly an indexing issue?) findingaids = findingaids.only(*return_fields) result_subset, paginator = paginate_queryset(request, findingaids, per_page=10, orphans=5) # when searching by subject only, use alpha pagination if subject and not keywords: page_labels = alpha_pagelabels(paginator, findingaids, label_attribute='list_title') else: page_labels = {} show_pages = pages_to_show(paginator, result_subset.number, page_labels) # query_times = findingaids.queryTime() # select non-empty form values for use in template search_params = dict((key, value) for key, value in form.cleaned_data.iteritems() if value) # set query and last page in session and set it to expire on browser close for key, val in search_params.iteritems(): if key == 'dao': search_params[key] = val else: search_params[key] = val.encode('utf-8') last_search = search_params.copy() # pagination url params should NOT include page if 'page' in last_search: del(last_search['page']) url_params = urlencode(last_search) # store the current page (even if not specified in URL) for saved search last_search['page'] = page last_search = "%s?%s" % (reverse("fa:search"), urlencode(last_search)) last_search = {"url": last_search, "txt": "Return to Search Results"} request.session["last_search"] = last_search request.session.set_expiry(0) # set to expire when browser closes # ONLY keywords - not page or subject - should be included in # document url for search term highlighting if 'keywords' in search_params: highlight_params = urlencode({'keywords': search_params['keywords']}) else: highlight_params = None response_context = { 'findingaids': result_subset, 'search_params': search_params, # actual search terms, for display 'url_params': url_params, # url opts for pagination 'highlight_params': highlight_params, # keyword highlighting # 'querytime': [query_times], 'show_pages': show_pages } if page_labels: # if there are page labels to show, add to context # other page labels handled by show_pages, but first & last are special response_context['first_page_label'] = page_labels[1] response_context['last_page_label'] = page_labels[paginator.num_pages] return render(request, 'fa/search_results.html', response_context) except ExistDBException as e: # for an invalid full-text query (e.g., missing close quote), eXist # error reports 'Cannot parse' and 'Lexical error' # FIXME: could/should this be a custom eXist exception class? query_error = True if 'Cannot parse' in e.message(): messages.error(request, 'Your search query could not be parsed. ' + 'Please revise your search and try again.') else: # generic error message for any other exception messages.error(request, 'There was an error processing your search.') elif 'keywords' not in request.GET and 'subject' not in request.GET: # if form was not valid and nothing was submitted, re-initialize # don't tell the user that fields are required if they haven't submitted anything! form = AdvancedSearchForm() # if form is invalid (no search terms) or there was an error, display search form response = render(request, 'fa/search_form.html', {'form': form, 'request': request}) # if query could not be parsed, set a 'Bad Request' status code on the response if query_error: response.status_code = 400 return response