+ return type
+
+
+def books_starting_with(prefix):
+ prefix = prefix.lower()
+ return models.Book.objects.filter(_word_starts_with('title', prefix))
+
+
+def find_best_matches(query, user=None):
+ """ Finds a Book, Tag, 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]