def get(self, request): """info about a book""" query = request.GET.get("query") book_results = popular_books = [] if query: book_results = connector_manager.local_search(query, raw=True)[:5] if len(book_results) < 5: popular_books = ( models.Edition.objects.exclude( # exclude already shelved Q(parent_work__in=[ b.book.parent_work for b in request.user.shelfbook_set.distinct().all() ]) | Q( # and exclude if it's already in search results parent_work__in=[b.parent_work for b in book_results])).annotate( Count("shelfbook")). order_by("-shelfbook__count")[:5 - len(book_results)]) data = { "book_results": book_results, "popular_books": popular_books, "next": self.next_view, } return TemplateResponse(request, "get_started/books.html", data)
def book_search(query, _, min_confidence, search_remote=False): """the real business is elsewhere""" if search_remote: return connector_manager.search(query, min_confidence=min_confidence) results = connector_manager.local_search(query, min_confidence=min_confidence) if not results: return None return [{"results": results}]
def post(self, request, book_id=None): """ edit a book cool """ # returns None if no match is found book = models.Edition.objects.filter(id=book_id).first() form = forms.EditionForm(request.POST, request.FILES, instance=book) data = {"book": book, "form": form} if not form.is_valid(): return TemplateResponse(request, "edit_book.html", data) add_author = request.POST.get("add_author") # we're adding an author through a free text field if add_author: data["add_author"] = add_author data["author_matches"] = [] for author in add_author.split(","): if not author: continue # check for existing authors vector = SearchVector("name", weight="A") + SearchVector( "aliases", weight="B") data["author_matches"].append({ "name": author.strip(), "matches": (models.Author.objects.annotate(search=vector).annotate( rank=SearchRank(vector, author)).filter( rank__gt=0.4).order_by("-rank")[:5]), }) print(data["author_matches"]) # we're creating a new book if not book: # check if this is an edition of an existing work author_text = book.author_text if book else add_author data["book_matches"] = connector_manager.local_search( "%s %s" % (form.cleaned_data.get("title"), author_text), min_confidence=0.5, raw=True, )[:5] # either of the above cases requires additional confirmation if add_author or not book: # creting a book or adding an author to a book needs another step data["confirm_mode"] = True # this isn't preserved because it isn't part of the form obj data["remove_authors"] = request.POST.getlist("remove_authors") return TemplateResponse(request, "edit_book.html", data) remove_authors = request.POST.getlist("remove_authors") for author_id in remove_authors: book.authors.remove(author_id) book = form.save() return redirect("/book/%s" % book.id)
def book_search(query, _, min_confidence, search_remote=False): """the real business is elsewhere""" # try a local-only search if not search_remote: results = connector_manager.local_search(query, min_confidence=min_confidence) if results: # gret, we found something return [{"results": results}], False # if there weere no local results, or the request was for remote, search all sources return connector_manager.search(query, min_confidence=min_confidence), True
def get(self, request): ''' that search bar up top ''' query = request.GET.get('q') min_confidence = request.GET.get('min_confidence', 0.1) if is_api_request(request): # only return local book results via json so we don't cascade book_results = connector_manager.local_search( query, min_confidence=min_confidence) return JsonResponse([r.json() for r in book_results], safe=False) # use webfinger for mastodon style [email protected] username if re.match(r'\B%s' % regex.full_username, query): handle_remote_webfinger(query) # do a user search user_results = models.User.objects.annotate( similarity=Greatest( TrigramSimilarity('username', query), TrigramSimilarity('localname', query), ) ).filter( similarity__gt=0.5, ).order_by('-similarity')[:10] # any relevent lists? list_results = privacy_filter( request.user, models.List.objects, ['public', 'followers'] ).annotate( similarity=Greatest( TrigramSimilarity('name', query), TrigramSimilarity('description', query), ) ).filter( similarity__gt=0.1, ).order_by('-similarity')[:10] book_results = connector_manager.search( query, min_confidence=min_confidence) data = { 'title': 'Search Results', 'book_results': book_results, 'user_results': user_results, 'list_results': list_results, 'query': query, } return TemplateResponse(request, 'search_results.html', data)
def get(self, request): """that search bar up top""" query = request.GET.get("q") min_confidence = request.GET.get("min_confidence", 0.1) search_type = request.GET.get("type") search_remote = ( request.GET.get("remote", False) and request.user.is_authenticated ) if is_api_request(request): # only return local book results via json so we don't cascade book_results = connector_manager.local_search( query, min_confidence=min_confidence ) return JsonResponse([r.json() for r in book_results], safe=False) if query and not search_type: search_type = "user" if "@" in query else "book" endpoints = { "book": book_search, "user": user_search, "list": list_search, } if not search_type in endpoints: search_type = "book" data = { "query": query or "", "type": search_type, "remote": search_remote, } if query: results = endpoints[search_type]( query, request.user, min_confidence, search_remote ) if results: paginated = Paginator(results, PAGE_LENGTH).get_page( request.GET.get("page") ) data["results"] = paginated return TemplateResponse(request, "search/{:s}.html".format(search_type), data)
def get(self, request): """ that search bar up top """ query = request.GET.get("q") min_confidence = request.GET.get("min_confidence", 0.1) if is_api_request(request): # only return local book results via json so we don't cascade book_results = connector_manager.local_search( query, min_confidence=min_confidence) return JsonResponse([r.json() for r in book_results], safe=False) # use webfinger for mastodon style [email protected] username if re.match(r"\B%s" % regex.full_username, query): handle_remote_webfinger(query) # do a user search user_results = (models.User.viewer_aware_objects( request.user).annotate(similarity=Greatest( TrigramSimilarity("username", query), TrigramSimilarity("localname", query), )).filter(similarity__gt=0.5, ).order_by("-similarity")[:10]) # any relevent lists? list_results = (privacy_filter( request.user, models.List.objects, privacy_levels=["public", "followers"], ).annotate(similarity=Greatest( TrigramSimilarity("name", query), TrigramSimilarity("description", query), )).filter(similarity__gt=0.1, ).order_by("-similarity")[:10]) book_results = connector_manager.search(query, min_confidence=min_confidence) data = { "book_results": book_results, "user_results": user_results, "list_results": list_results, "query": query, } return TemplateResponse(request, "search_results.html", data)
def get(self, request, list_id): ''' display a book list ''' book_list = get_object_or_404(models.List, id=list_id) if not object_visible_to_user(request.user, book_list): return HttpResponseNotFound() if is_api_request(request): return ActivitypubResponse(book_list.to_activity(**request.GET)) query = request.GET.get('q') suggestions = None if query and request.user.is_authenticated: # search for books suggestions = connector_manager.local_search(query, raw=True) elif request.user.is_authenticated: # just suggest whatever books are nearby suggestions = request.user.shelfbook_set.filter(~Q( book__in=book_list.books.all())) suggestions = [s.book for s in suggestions[:5]] if len(suggestions) < 5: suggestions += [ s.default_edition for s in \ models.Work.objects.filter( ~Q(editions__in=book_list.books.all()), ).order_by('-updated_date') ][:5 - len(suggestions)] data = { 'title': '%s | Lists' % book_list.name, 'list': book_list, 'items': book_list.listitem_set.filter(approved=True), 'pending_count': book_list.listitem_set.filter(approved=False).count(), 'suggested_books': suggestions, 'list_form': forms.ListForm(instance=book_list), 'query': query or '' } return TemplateResponse(request, 'lists/list.html', data)
def get(self, request, list_id): """ display a book list """ book_list = get_object_or_404(models.List, id=list_id) if not object_visible_to_user(request.user, book_list): return HttpResponseNotFound() if is_api_request(request): return ActivitypubResponse(book_list.to_activity(**request.GET)) query = request.GET.get("q") suggestions = None if query and request.user.is_authenticated: # search for books suggestions = connector_manager.local_search(query, raw=True) elif request.user.is_authenticated: # just suggest whatever books are nearby suggestions = request.user.shelfbook_set.filter(~Q( book__in=book_list.books.all())) suggestions = [s.book for s in suggestions[:5]] if len(suggestions) < 5: suggestions += [ s.default_edition for s in models.Work.objects.filter( ~Q(editions__in=book_list.books.all()), ).order_by( "-updated_date") ][:5 - len(suggestions)] data = { "list": book_list, "items": book_list.listitem_set.filter(approved=True), "pending_count": book_list.listitem_set.filter(approved=False).count(), "suggested_books": suggestions, "list_form": forms.ListForm(instance=book_list), "query": query or "", } return TemplateResponse(request, "lists/list.html", data)
def post(self, request, book_id=None): """ edit a book cool """ # returns None if no match is found book = models.Edition.objects.filter(id=book_id).first() form = forms.EditionForm(request.POST, request.FILES, instance=book) data = {"book": book, "form": form} if not form.is_valid(): return TemplateResponse(request, "book/edit_book.html", data) add_author = request.POST.get("add_author") # we're adding an author through a free text field if add_author: data["add_author"] = add_author data["author_matches"] = [] for author in add_author.split(","): if not author: continue # check for existing authors vector = SearchVector("name", weight="A") + SearchVector( "aliases", weight="B" ) data["author_matches"].append( { "name": author.strip(), "matches": ( models.Author.objects.annotate(search=vector) .annotate(rank=SearchRank(vector, author)) .filter(rank__gt=0.4) .order_by("-rank")[:5] ), } ) print(data["author_matches"]) # we're creating a new book if not book: # check if this is an edition of an existing work author_text = book.author_text if book else add_author data["book_matches"] = connector_manager.local_search( "%s %s" % (form.cleaned_data.get("title"), author_text), min_confidence=0.5, raw=True, )[:5] # either of the above cases requires additional confirmation if add_author or not book: # creting a book or adding an author to a book needs another step data["confirm_mode"] = True # this isn't preserved because it isn't part of the form obj data["remove_authors"] = request.POST.getlist("remove_authors") # make sure the dates are passed in as datetime, they're currently a string # QueryDicts are immutable, we need to copy formcopy = data["form"].data.copy() try: formcopy["first_published_date"] = dateparse( formcopy["first_published_date"] ) except (MultiValueDictKeyError, ValueError): pass try: formcopy["published_date"] = dateparse(formcopy["published_date"]) except (MultiValueDictKeyError, ValueError): pass data["form"].data = formcopy return TemplateResponse(request, "book/edit_book.html", data) remove_authors = request.POST.getlist("remove_authors") for author_id in remove_authors: book.authors.remove(author_id) book = form.save(commit=False) url = request.POST.get("cover-url") if url: image = set_cover_from_url(url) if image: book.cover.save(*image, save=False) book.save() return redirect("/book/%s" % book.id)
def test_local_search(self): """ search only the local database """ results = connector_manager.local_search("Example") self.assertEqual(len(results), 1) self.assertEqual(results[0].title, "Example Edition")
def test_local_search(self): ''' search only the local database ''' results = connector_manager.local_search('Example') self.assertEqual(len(results), 1) self.assertEqual(results[0].title, 'Example Edition')
def get(self, request, list_id): """display a book list""" book_list = get_object_or_404(models.List, id=list_id) if not book_list.visible_to_user(request.user): return HttpResponseNotFound() if is_api_request(request): return ActivitypubResponse(book_list.to_activity(**request.GET)) query = request.GET.get("q") suggestions = None # sort_by shall be "order" unless a valid alternative is given sort_by = request.GET.get("sort_by", "order") if sort_by not in ("order", "title", "rating"): sort_by = "order" # direction shall be "ascending" unless a valid alternative is given direction = request.GET.get("direction", "ascending") if direction not in ("ascending", "descending"): direction = "ascending" directional_sort_by = { "order": "order", "title": "book__title", "rating": "average_rating", }[sort_by] if direction == "descending": directional_sort_by = "-" + directional_sort_by items = book_list.listitem_set if sort_by == "rating": items = items.annotate(average_rating=Avg( Coalesce("book__review__rating", 0.0), output_field=DecimalField(), )) items = items.filter(approved=True).order_by(directional_sort_by) paginated = Paginator(items, PAGE_LENGTH) if query and request.user.is_authenticated: # search for books suggestions = connector_manager.local_search( query, raw=True, filters=[~Q(parent_work__editions__in=book_list.books.all())], ) elif request.user.is_authenticated: # just suggest whatever books are nearby suggestions = request.user.shelfbook_set.filter(~Q( book__in=book_list.books.all())) suggestions = [s.book for s in suggestions[:5]] if len(suggestions) < 5: suggestions += [ s.default_edition for s in models.Work.objects.filter( ~Q(editions__in=book_list.books.all()), ).order_by( "-updated_date") ][:5 - len(suggestions)] page = paginated.get_page(request.GET.get("page")) data = { "list": book_list, "items": page, "page_range": paginated.get_elided_page_range(page.number, on_each_side=2, on_ends=1), "pending_count": book_list.listitem_set.filter(approved=False).count(), "suggested_books": suggestions, "list_form": forms.ListForm(instance=book_list), "query": query or "", "sort_form": forms.SortListForm({ "direction": direction, "sort_by": sort_by }), } return TemplateResponse(request, "lists/list.html", data)