+def find_best_matches(query, user=None):
+ """ Finds a models.Book, Tag, models.BookStub or Author best matching a query.
+
+ Returns a with:
+ - zero elements when nothing is found,
+ - one element when a best result is found,
+ - more then one element on multiple exact matches
+
+ Raises a ValueError on too short a query.
+ """
+
+ query = query.lower()
+ if len(query) < 2:
+ raise ValueError("query must have at least two characters")
+
+ result = tuple(_tags_starting_with(query, user))
+ # remove pdcounter stuff
+ book_titles = set(match.pretty_title().lower() for match in result
+ if isinstance(match, models.Book))
+ authors = set(match.name.lower() for match in result
+ if isinstance(match, models.Tag) and match.category=='author')
+ result = tuple(res for res in result if not (
+ (isinstance(res, pdcounter_models.BookStub) and res.pretty_title().lower() in book_titles)
+ or (isinstance(res, pdcounter_models.Author) and res.name.lower() in authors)
+ ))
+
+ exact_matches = tuple(res for res in result if res.name.lower() == query)
+ if exact_matches:
+ return exact_matches
+ else:
+ return tuple(result)[:1]
+
+
+def search(request):
+ tags = request.GET.get('tags', '')
+ prefix = request.GET.get('q', '')
+
+ try:
+ tag_list = models.Tag.get_tag_list(tags)
+ except:
+ tag_list = []
+
+ try:
+ result = find_best_matches(prefix, request.user)
+ except ValueError:
+ return render_to_response('catalogue/search_too_short.html', {'tags':tag_list, 'prefix':prefix},
+ context_instance=RequestContext(request))
+
+ if len(result) == 1:
+ return HttpResponseRedirect(_get_result_link(result[0], tag_list))
+ elif len(result) > 1:
+ return render_to_response('catalogue/search_multiple_hits.html',
+ {'tags':tag_list, 'prefix':prefix, 'results':((x, _get_result_link(x, tag_list), _get_result_type(x)) for x in result)},
+ context_instance=RequestContext(request))
+ else:
+ form = PublishingSuggestForm(initial={"books": prefix + ", "})
+ return render_to_response('catalogue/search_no_hits.html',
+ {'tags':tag_list, 'prefix':prefix, "pubsuggest_form": form},
+ context_instance=RequestContext(request))
+
+
+def tags_starting_with(request):
+ prefix = request.GET.get('q', '')
+ # Prefix must have at least 2 characters
+ if len(prefix) < 2:
+ return HttpResponse('')
+ tags_list = []
+ result = ""
+ for tag in _tags_starting_with(prefix, request.user):
+ if not tag.name in tags_list:
+ result += "\n" + tag.name
+ tags_list.append(tag.name)
+ return HttpResponse(result)
+
+def json_tags_starting_with(request, callback=None):
+ # Callback for JSONP
+ prefix = request.GET.get('q', '')
+ callback = request.GET.get('callback', '')
+ # Prefix must have at least 2 characters
+ if len(prefix) < 2:
+ return HttpResponse('')
+ tags_list = []
+ for tag in _tags_starting_with(prefix, request.user):
+ if not tag.name in tags_list:
+ tags_list.append(tag.name)
+ if request.GET.get('mozhint', ''):
+ result = [prefix, tags_list]
+ else:
+ result = {"matches": tags_list}
+ return JSONResponse(result, callback)
+
+# ====================
+# = Shelf management =
+# ====================
+@login_required
+@cache.never_cache
+def user_shelves(request):
+ shelves = models.Tag.objects.filter(category='set', user=request.user)
+ new_set_form = forms.NewSetForm()
+ return render_to_response('catalogue/user_shelves.html', locals(),
+ context_instance=RequestContext(request))
+
+@cache.never_cache
+def book_sets(request, book):
+ if not request.user.is_authenticated():
+ return HttpResponse(_('<p>To maintain your shelves you need to be logged in.</p>'))
+
+ kwargs = models.Book.split_urlid(book)
+ if kwargs is None:
+ raise Http404
+ book = get_object_or_404(models.Book, **kwargs)