Example #1
0
    def get(self, request):
        """info about a book"""
        query = request.GET.get("query")
        book_results = popular_books = []
        if query:
            book_results = book_search.search(query)[: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)
Example #2
0
    def test_search(self):
        """search for a book in the db"""
        # title/author
        results = book_search.search("Example")
        self.assertEqual(len(results), 1)
        self.assertEqual(results[0], self.first_edition)

        # isbn
        results = book_search.search("0000000000")
        self.assertEqual(len(results), 1)
        self.assertEqual(results[0], self.first_edition)

        # identifier
        results = book_search.search("hello")
        self.assertEqual(len(results), 1)
        self.assertEqual(results[0], self.second_edition)
Example #3
0
def first_search_result(query, min_confidence=0.1):
    """search until you find a result that fits"""
    # try local search first
    result = book_search.search(query, min_confidence=min_confidence, return_first=True)
    if result:
        return result
    # otherwise, try remote endpoints
    return search(query, min_confidence=min_confidence, return_first=True) or None
Example #4
0
def book_search(query, user, min_confidence, search_remote=False):
    """the real business is elsewhere"""
    # try a local-only search
    results = [{"results": search(query, min_confidence=min_confidence)}]
    if not user.is_authenticated or (results[0]["results"] and not search_remote):
        return results, False

    # if there were no local results, or the request was for remote, search all sources
    results += connector_manager.search(query, min_confidence=min_confidence)
    return results, True
Example #5
0
    def post(self, request):
        """create a new book"""
        # returns None if no match is found
        form = forms.EditionForm(request.POST, request.FILES)
        data = {"form": form}

        # collect data provided by the work or import item
        parent_work_id = request.POST.get("parent_work")
        authors = None
        if request.POST.get("authors"):
            author_ids = findall(r"\d+", request.POST["authors"])
            authors = models.Author.objects.filter(id__in=author_ids)

        # fake book in case we need to keep editing
        if parent_work_id:
            data["book"] = {
                "parent_work": {
                    "id": parent_work_id
                },
                "authors": authors,
            }

        if not form.is_valid():
            return TemplateResponse(request, "book/edit/edit_book.html", data)

        data = add_authors(request, data)

        # check if this is an edition of an existing work
        author_text = ", ".join(data.get("add_author", []))
        data["book_matches"] = book_search.search(
            f'{form.cleaned_data.get("title")} {author_text}',
            min_confidence=0.1,
        )[:5]

        # go to confirm mode
        if not parent_work_id or data.get("add_author"):
            return TemplateResponse(request, "book/edit/edit_book.html", data)

        with transaction.atomic():
            book = form.save()
            parent_work = get_object_or_404(models.Work, id=parent_work_id)
            book.parent_work = parent_work

            if authors:
                book.authors.add(*authors)

            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(f"/book/{book.id}")
Example #6
0
    def get(self, request):
        """that search bar up top"""
        query = request.GET.get("q")
        # check if query is isbn
        query = isbn_check(query)
        min_confidence = request.GET.get("min_confidence", 0)
        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 = search(query, min_confidence=min_confidence)
            return JsonResponse(
                [format_search_result(r) 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, search_remote = 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
                data["remote"] = search_remote

        return TemplateResponse(request, f"search/{search_type}.html", data)
Example #7
0
    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/edit_book.html", data)

        # filter out empty author fields
        add_author = [
            author for author in request.POST.getlist("add_author") if author
        ]
        if add_author:
            data["add_author"] = add_author
            data["author_matches"] = []
            data["isni_matches"] = []

            for author in add_author:
                if not author:
                    continue
                # check for existing authors
                vector = SearchVector("name", weight="A") + SearchVector(
                    "aliases", weight="B")

                author_matches = (models.Author.objects.annotate(
                    search=vector).annotate(
                        rank=SearchRank(vector, author)).filter(
                            rank__gt=0.4).order_by("-rank")[:5])

                isni_authors = find_authors_by_name(
                    author, description=True)  # find matches from ISNI API

                # dedupe isni authors we already have in the DB
                exists = [
                    i for i in isni_authors for a in author_matches if sub(
                        r"\D", "", str(i.isni)) == sub(r"\D", "", str(a.isni))
                ]

                # pylint: disable=cell-var-from-loop
                matches = list(filter(lambda x: x not in exists, isni_authors))
                # combine existing and isni authors
                matches.extend(author_matches)

                data["author_matches"].append({
                    "name": author.strip(),
                    "matches": matches,
                    "existing_isnis": exists,
                })

        # 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"] = book_search.search(
                f'{form.cleaned_data.get("title")} {author_text}',
                min_confidence=0.5,
            )[: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")
            data["cover_url"] = request.POST.get("cover-url")

            # 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/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(f"/book/{book.id}")
Example #8
0
    def get(self, request, list_id, add_failed=False, add_succeeded=False):
        """display a book list"""
        book_list = get_object_or_404(models.List, id=list_id)
        book_list.raise_visible_to_user(request.user)

        if is_api_request(request):
            return ActivitypubResponse(book_list.to_activity(**request.GET))

        query = request.GET.get("q")
        suggestions = None

        items = book_list.listitem_set.filter(approved=True).prefetch_related(
            "user", "book", "book__authors")
        items = sort_list(request, items)

        paginated = Paginator(items, PAGE_LENGTH)

        if query and request.user.is_authenticated:
            # search for books
            suggestions = book_search.search(
                query,
                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"))

        embed_key = str(book_list.embed_key.hex)
        embed_url = reverse("embed-list", args=[book_list.id, embed_key])
        embed_url = request.build_absolute_uri(embed_url)

        if request.GET:
            embed_url = f"{embed_url}?{request.GET.urlencode()}"

        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(request.GET),
            "embed_url":
            embed_url,
            "add_failed":
            add_failed,
            "add_succeeded":
            add_succeeded,
        }
        return TemplateResponse(request, "lists/list.html", data)
Example #9
0
    def get(self, request, list_id):
        """display a book list"""
        book_list = get_object_or_404(models.List, id=list_id)
        book_list.raise_visible_to_user(request.user)

        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.prefetch_related("user", "book", "book__authors")
        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 = book_search.search(
                query,
                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"))

        embed_key = str(book_list.embed_key.hex)
        embed_url = reverse("embed-list", args=[book_list.id, embed_key])
        embed_url = request.build_absolute_uri(embed_url)

        if request.GET:
            embed_url = f"{embed_url}?{request.GET.urlencode()}"

        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}
            ),
            "embed_url": embed_url,
        }
        return TemplateResponse(request, "lists/list.html", data)