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