Example #1
0
    def get(self, request, author_id):
        """landing page for an author"""
        author = get_object_or_404(models.Author, id=author_id)

        if is_api_request(request):
            return ActivitypubResponse(author.to_activity())

        default_editions = models.Edition.objects.filter(
            parent_work=OuterRef("parent_work")
        ).order_by("-edition_rank")

        books = (
            models.Edition.viewer_aware_objects(request.user)
            .filter(Q(authors=author) | Q(parent_work__authors=author))
            .annotate(default_id=Subquery(default_editions.values("id")[:1]))
            .filter(default_id=F("id"))
            .order_by("-first_published_date", "-published_date", "-created_date")
            .prefetch_related("authors")
        ).distinct()

        paginated = Paginator(books, PAGE_LENGTH)
        page = paginated.get_page(request.GET.get("page"))
        data = {
            "author": author,
            "books": page,
            "page_range": paginated.get_elided_page_range(
                page.number, on_each_side=2, on_ends=1
            ),
        }
        return TemplateResponse(request, "author/author.html", data)
Example #2
0
    def get(self, request, book_id):
        """list of editions of a book"""
        work = get_object_or_404(models.Work, id=book_id)

        if is_api_request(request):
            return ActivitypubResponse(work.to_edition_list(**request.GET))
        filters = {}

        if request.GET.get("language"):
            filters["languages__contains"] = [request.GET.get("language")]
        if request.GET.get("format"):
            filters["physical_format__iexact"] = request.GET.get("format")

        editions = work.editions.order_by("-edition_rank")
        languages = set(sum(editions.values_list("languages", flat=True), []))

        editions = editions.filter(**filters)

        query = request.GET.get("q")
        if query:
            searchable_array_fields = ["languages", "publishers"]
            searchable_fields = [
                "title",
                "physical_format",
                "isbn_10",
                "isbn_13",
                "oclc_number",
                "asin",
            ]
            search_filter_entries = [{
                f"{f}__icontains": query
            } for f in searchable_fields] + [{
                f"{f}__iexact": query
            } for f in searchable_array_fields]
            editions = editions.filter(
                reduce(operator.or_, (Q(**f) for f in search_filter_entries)))

        paginated = Paginator(editions, PAGE_LENGTH)
        page = paginated.get_page(request.GET.get("page"))
        data = {
            "editions":
            page,
            "page_range":
            paginated.get_elided_page_range(page.number,
                                            on_each_side=2,
                                            on_ends=1),
            "work":
            work,
            "work_form":
            forms.EditionFromWorkForm(instance=work),
            "languages":
            languages,
            "formats":
            set(e.physical_format.lower() for e in editions
                if e.physical_format),
        }
        return TemplateResponse(request, "book/editions/editions.html", data)
Example #3
0
    def get(self, request, author_id, slug=None):
        """landing page for an author"""
        author = get_object_or_404(models.Author, id=author_id)

        if is_api_request(request):
            return ActivitypubResponse(author.to_activity())

        if redirect_local_path := maybe_redirect_local_path(request, author):
            return redirect_local_path
Example #4
0
    def get(self, request, list_id, **kwargs):
        """display a book list"""
        add_failed = kwargs.get("add_failed", False)
        add_succeeded = kwargs.get("add_succeeded", False)

        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))

        if r := maybe_redirect_local_path(request, book_list):
            return r
Example #5
0
    def get(self, request, author_id):
        """landing page for an author"""
        author = get_object_or_404(models.Author, id=author_id)

        if is_api_request(request):
            return ActivitypubResponse(author.to_activity())

        books = models.Work.objects.filter(
            authors=author, editions__authors=author).distinct()

        paginated = Paginator(books, PAGE_LENGTH)
        page = paginated.get_page(request.GET.get("page"))
        data = {
            "author":
            author,
            "books":
            page,
            "page_range":
            paginated.get_elided_page_range(page.number,
                                            on_each_side=2,
                                            on_ends=1),
        }
        return TemplateResponse(request, "author/author.html", data)
Example #6
0
    def get(self, request, book_id, **kwargs):
        """info about a book"""
        if is_api_request(request):
            book = get_object_or_404(models.Book.objects.select_subclasses(),
                                     id=book_id)
            return ActivitypubResponse(book.to_activity())

        user_statuses = (kwargs.get("user_statuses", False)
                         if request.user.is_authenticated else False)

        # it's safe to use this OR because edition and work and subclasses of the same
        # table, so they never have clashing IDs
        book = (models.Edition.viewer_aware_objects(
            request.user).filter(Q(id=book_id) | Q(
                parent_work__id=book_id)).order_by("-edition_rank").
                select_related("parent_work").prefetch_related(
                    "authors", "file_links").first())

        if not book or not book.parent_work:
            raise Http404()

        if redirect_local_path := not user_statuses and maybe_redirect_local_path(
                request, book):
            return redirect_local_path
Example #7
0
    def get(self, request, username, shelf_identifier=None):
        """display a shelf"""
        user = get_user_from_username(request.user, username)

        is_self = user == request.user

        if is_self:
            shelves = user.shelf_set.all()
        else:
            shelves = models.Shelf.privacy_filter(
                request.user).filter(user=user).all()

        # get the shelf and make sure the logged in user should be able to see it
        if shelf_identifier:
            shelf = get_object_or_404(user.shelf_set,
                                      identifier=shelf_identifier)
            shelf.raise_visible_to_user(request.user)
            books = shelf.books
        else:
            # this is a constructed "all books" view, with a fake "shelf" obj
            FakeShelf = namedtuple(
                "Shelf", ("identifier", "name", "user", "books", "privacy"))
            books = (
                models.Edition.viewer_aware_objects(request.user).filter(
                    # privacy is ensured because the shelves are already filtered above
                    shelfbook__shelf__in=shelves).distinct())
            shelf = FakeShelf("all", _("All books"), user, books, "public")

        if is_api_request(request) and shelf_identifier:
            return ActivitypubResponse(shelf.to_activity(**request.GET))

        reviews = models.Review.objects
        if not is_self:
            reviews = models.Review.privacy_filter(request.user)

        reviews = reviews.filter(
            user=user,
            rating__isnull=False,
            book__id=OuterRef("id"),
            deleted=False,
        ).order_by("-published_date")

        reading = models.ReadThrough.objects

        reading = reading.filter(
            user=user, book__id=OuterRef("id")).order_by("start_date")

        books = books.annotate(shelved_date=Max("shelfbook__shelved_date"))
        books = books.annotate(
            rating=Subquery(reviews.values("rating")[:1]),
            start_date=Subquery(reading.values("start_date")[:1]),
            finish_date=Subquery(reading.values("finish_date")[:1]),
            author=Subquery(
                models.Book.objects.filter(
                    id=OuterRef("id")).values("authors__name")[:1]),
        ).prefetch_related("authors")

        books = sort_books(books, request.GET.get("sort"))

        paginated = Paginator(
            books,
            PAGE_LENGTH,
        )
        page = paginated.get_page(request.GET.get("page"))
        data = {
            "user":
            user,
            "is_self":
            is_self,
            "shelves":
            shelves,
            "shelf":
            shelf,
            "books":
            page,
            "edit_form":
            forms.ShelfForm(instance=shelf if shelf_identifier else None),
            "create_form":
            forms.ShelfForm(),
            "sort":
            request.GET.get("sort"),
            "page_range":
            paginated.get_elided_page_range(page.number,
                                            on_each_side=2,
                                            on_ends=1),
        }

        return TemplateResponse(request, "shelf/shelf.html", data)
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, book_id, user_statuses=False, update_error=False):
        """info about a book"""
        if is_api_request(request):
            book = get_object_or_404(models.Book.objects.select_subclasses(),
                                     id=book_id)
            return ActivitypubResponse(book.to_activity())

        user_statuses = user_statuses if request.user.is_authenticated else False

        # it's safe to use this OR because edition and work and subclasses of the same
        # table, so they never have clashing IDs
        book = (models.Edition.viewer_aware_objects(
            request.user).filter(Q(id=book_id) | Q(
                parent_work__id=book_id)).order_by("-edition_rank").
                select_related("parent_work").prefetch_related(
                    "authors", "file_links").first())

        if not book or not book.parent_work:
            raise Http404()

        # all reviews for all editions of the book
        reviews = models.Review.privacy_filter(
            request.user).filter(book__parent_work__editions=book)

        # the reviews to show
        if user_statuses:
            if user_statuses == "review":
                queryset = book.review_set.select_subclasses()
            elif user_statuses == "comment":
                queryset = book.comment_set
            else:
                queryset = book.quotation_set
            queryset = queryset.filter(user=request.user, deleted=False)
        else:
            queryset = reviews.exclude(Q(content__isnull=True) | Q(content=""))
        queryset = queryset.select_related("user").order_by("-published_date")
        paginated = Paginator(queryset, PAGE_LENGTH)

        lists = models.List.privacy_filter(request.user, ).filter(
            listitem__approved=True,
            listitem__book__in=book.parent_work.editions.all(),
        )
        data = {
            "book":
            book,
            "statuses":
            paginated.get_page(request.GET.get("page")),
            "review_count":
            reviews.count(),
            "ratings":
            reviews.filter(Q(content__isnull=True)
                           | Q(content="")).select_related("user")
            if not user_statuses else None,
            "rating":
            reviews.aggregate(Avg("rating"))["rating__avg"],
            "lists":
            lists,
            "update_error":
            update_error,
        }

        if request.user.is_authenticated:
            data["list_options"] = request.user.list_set.exclude(
                id__in=data["lists"])
            data["file_link_form"] = forms.FileLinkForm()
            readthroughs = models.ReadThrough.objects.filter(
                user=request.user,
                book=book,
            ).order_by("start_date")

            for readthrough in readthroughs:
                readthrough.progress_updates = (
                    readthrough.progressupdate_set.all().order_by(
                        "-updated_date"))
            data["readthroughs"] = readthroughs

            data["user_shelfbooks"] = models.ShelfBook.objects.filter(
                user=request.user, book=book).select_related("shelf")

            data["other_edition_shelves"] = models.ShelfBook.objects.filter(
                ~Q(book=book),
                user=request.user,
                book__parent_work=book.parent_work,
            ).select_related("shelf", "book")

            filters = {"user": request.user, "deleted": False}
            data["user_statuses"] = {
                "review_count": book.review_set.filter(**filters).count(),
                "comment_count": book.comment_set.filter(**filters).count(),
                "quotation_count":
                book.quotation_set.filter(**filters).count(),
            }

        return TemplateResponse(request, "book/book.html", data)