From 4dea22bef658d222e5ebd3bb6cfa45a298f8a47a Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 13 Jan 2021 09:54:35 -0800 Subject: [PATCH] Author class view --- bookwyrm/templates/edit_author.html | 2 +- bookwyrm/urls.py | 7 +- bookwyrm/view_actions.py | 24 +-- bookwyrm/views/__init__.py | 1 + bookwyrm/views/author.py | 66 ++++++++ bookwyrm/views/books.py | 228 ++++++++++++++++++++++++++++ bookwyrm/vviews.py | 37 +---- 7 files changed, 301 insertions(+), 64 deletions(-) create mode 100644 bookwyrm/views/author.py create mode 100644 bookwyrm/views/books.py diff --git a/bookwyrm/templates/edit_author.html b/bookwyrm/templates/edit_author.html index b08aa983..de028d85 100644 --- a/bookwyrm/templates/edit_author.html +++ b/bookwyrm/templates/edit_author.html @@ -27,7 +27,7 @@ {% endif %} -
+ {% csrf_token %} diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index 186a0d94..260164b7 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -96,12 +96,13 @@ urlpatterns = [ re_path(r'^resolve-book/?$', views.resolve_book), re_path(r'^switch-edition/?$', views.switch_edition), - re_path(r'^author/(?P[\w\-]+)/edit/?$', vviews.edit_author_page), + # author + re_path(r'^author/(?P\d+)(.json)?/?$', views.Author.as_view()), + re_path(r'^author/(?P\d+)/edit/?$', views.EditAuthor.as_view()), re_path(r'^tag/?$', actions.tag), re_path(r'^untag/?$', actions.untag), - re_path(r'^author/(?P[\w\-]+)(.json)?/?$', vviews.author_page), re_path(r'^tag/(?P.+)\.json/?$', vviews.tag_page), re_path(r'^tag/(?P.+)/?$', vviews.tag_page), re_path(r'^%s/shelf/(?P[\w-]+)(.json)?/?$' % \ @@ -111,8 +112,6 @@ urlpatterns = [ re_path(r'^search/?$', vviews.search), - re_path(r'^edit-author/(?P\d+)/?$', actions.edit_author), - re_path(r'^edit-readthrough/?$', actions.edit_readthrough), re_path(r'^delete-readthrough/?$', actions.delete_readthrough), re_path(r'^create-readthrough/?$', actions.create_readthrough), diff --git a/bookwyrm/view_actions.py b/bookwyrm/view_actions.py index f2456491..c349f3f9 100644 --- a/bookwyrm/view_actions.py +++ b/bookwyrm/view_actions.py @@ -2,10 +2,9 @@ import dateutil.parser from dateutil.parser import ParserError -from django.contrib.auth.decorators import login_required, permission_required +from django.contrib.auth.decorators import login_required from django.http import HttpResponseBadRequest, HttpResponseNotFound from django.shortcuts import get_object_or_404, redirect -from django.template.response import TemplateResponse from django.utils import timezone from django.views.decorators.http import require_POST @@ -13,27 +12,6 @@ from bookwyrm import forms, models, outgoing from bookwyrm.broadcast import broadcast from bookwyrm.vviews import get_user_from_username, get_edition -@login_required -@permission_required('bookwyrm.edit_book', raise_exception=True) -@require_POST -def edit_author(request, author_id): - ''' edit a author cool ''' - author = get_object_or_404(models.Author, id=author_id) - - form = forms.AuthorForm(request.POST, request.FILES, instance=author) - if not form.is_valid(): - data = { - 'title': 'Edit Author', - 'author': author, - 'form': form - } - return TemplateResponse(request, 'edit_author.html', data) - author = form.save() - - broadcast(request.user, author.to_update_activity(request.user)) - return redirect('/author/%s' % author.id) - - @login_required @require_POST def create_shelf(request): diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py index 52f4f36f..0d01fb56 100644 --- a/bookwyrm/views/__init__.py +++ b/bookwyrm/views/__init__.py @@ -11,3 +11,4 @@ from .status import Status, Replies, CreateStatus, DeleteStatus from .interaction import Favorite, Unfavorite, Boost, Unboost from .books import Book, EditBook, Editions from .books import upload_cover, add_description, switch_edition, resolve_book +from .author import Author, EditAuthor diff --git a/bookwyrm/views/author.py b/bookwyrm/views/author.py new file mode 100644 index 00000000..ad9d1873 --- /dev/null +++ b/bookwyrm/views/author.py @@ -0,0 +1,66 @@ +''' the good people stuff! the authors! ''' +from django.contrib.auth.decorators import login_required, permission_required +from django.db.models import Q +from django.shortcuts import get_object_or_404, redirect +from django.template.response import TemplateResponse +from django.utils.decorators import method_decorator +from django.views import View + +from bookwyrm import forms, models +from bookwyrm.activitypub import ActivitypubResponse +from bookwyrm.broadcast import broadcast +from .helpers import is_api_request + + +# pylint: disable= no-self-use +class Author(View): + ''' this person wrote a book ''' + 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( + Q(authors=author) | Q(editions__authors=author)).distinct() + data = { + 'title': author.name, + 'author': author, + 'books': [b.get_default_edition() for b in books], + } + return TemplateResponse(request, 'author.html', data) + + +@method_decorator(login_required, name='dispatch') +@method_decorator( + permission_required('bookwyrm.edit_book', raise_exception=True), + name='dispatch') +class EditAuthor(View): + ''' edit author info ''' + def get(self, request, author_id): + ''' info about a book ''' + author = get_object_or_404(models.Author, id=author_id) + data = { + 'title': 'Edit Author', + 'author': author, + 'form': forms.AuthorForm(instance=author) + } + return TemplateResponse(request, 'edit_author.html', data) + + def post(self, request, author_id): + ''' edit a author cool ''' + author = get_object_or_404(models.Author, id=author_id) + + form = forms.AuthorForm(request.POST, request.FILES, instance=author) + if not form.is_valid(): + data = { + 'title': 'Edit Author', + 'author': author, + 'form': form + } + return TemplateResponse(request, 'edit_author.html', data) + author = form.save() + + broadcast(request.user, author.to_update_activity(request.user)) + return redirect('/author/%s' % author.id) diff --git a/bookwyrm/views/books.py b/bookwyrm/views/books.py new file mode 100644 index 00000000..553c7478 --- /dev/null +++ b/bookwyrm/views/books.py @@ -0,0 +1,228 @@ +''' the good stuff! the books! ''' +from django.core.paginator import Paginator +from django.contrib.auth.decorators import login_required, permission_required +from django.db import transaction +from django.db.models import Avg, Q +from django.http import HttpResponseNotFound +from django.shortcuts import get_object_or_404, redirect +from django.template.response import TemplateResponse +from django.utils.decorators import method_decorator +from django.views import View +from django.views.decorators.http import require_POST + +from bookwyrm import forms, models +from bookwyrm.activitypub import ActivitypubResponse +from bookwyrm.broadcast import broadcast +from bookwyrm.connectors import connector_manager +from bookwyrm.settings import PAGE_LENGTH +from .helpers import is_api_request, get_activity_feed, get_edition + + +# pylint: disable= no-self-use +class Book(View): + ''' a book! this is the stuff ''' + def get(self, request, book_id): + ''' info about a book ''' + try: + page = int(request.GET.get('page', 1)) + except ValueError: + page = 1 + + try: + book = models.Book.objects.select_subclasses().get(id=book_id) + except models.Book.DoesNotExist: + return HttpResponseNotFound() + + if is_api_request(request): + return ActivitypubResponse(book.to_activity()) + + if isinstance(book, models.Work): + book = book.get_default_edition() + if not book: + return HttpResponseNotFound() + + work = book.parent_work + if not work: + return HttpResponseNotFound() + + reviews = models.Review.objects.filter( + book__in=work.editions.all(), + ) + # all reviews for the book + reviews = get_activity_feed( + request.user, + ['public', 'unlisted', 'followers', 'direct'], + queryset=reviews + ) + + # the reviews to show + paginated = Paginator(reviews.exclude( + Q(content__isnull=True) | Q(content='') + ), PAGE_LENGTH) + reviews_page = paginated.page(page) + + user_tags = readthroughs = user_shelves = other_edition_shelves = [] + if request.user.is_authenticated: + user_tags = models.UserTag.objects.filter( + book=book, user=request.user + ).values_list('tag__identifier', flat=True) + + readthroughs = models.ReadThrough.objects.filter( + user=request.user, + book=book, + ).order_by('start_date') + + user_shelves = models.ShelfBook.objects.filter( + added_by=request.user, book=book + ) + + other_edition_shelves = models.ShelfBook.objects.filter( + ~Q(book=book), + added_by=request.user, + book__parent_work=book.parent_work, + ) + + data = { + 'title': book.title, + 'book': book, + 'reviews': reviews_page, + 'review_count': reviews.count(), + 'ratings': reviews.filter(Q(content__isnull=True) | Q(content='')), + 'rating': reviews.aggregate(Avg('rating'))['rating__avg'], + 'tags': models.UserTag.objects.filter(book=book), + 'user_tags': user_tags, + 'user_shelves': user_shelves, + 'other_edition_shelves': other_edition_shelves, + 'readthroughs': readthroughs, + 'path': '/book/%s' % book_id, + } + return TemplateResponse(request, 'book.html', data) + + +@method_decorator(login_required, name='dispatch') +@method_decorator( + permission_required('bookwyrm.edit_book', raise_exception=True), + name='dispatch') +class EditBook(View): + ''' edit a book ''' + def get(self, request, book_id): + ''' info about a book ''' + book = get_edition(book_id) + if not book.description: + book.description = book.parent_work.description + data = { + 'title': 'Edit Book', + 'book': book, + 'form': forms.EditionForm(instance=book) + } + return TemplateResponse(request, 'edit_book.html', data) + + def post(self, request, book_id): + ''' edit a book cool ''' + book = get_object_or_404(models.Edition, id=book_id) + + form = forms.EditionForm(request.POST, request.FILES, instance=book) + if not form.is_valid(): + data = { + 'title': 'Edit Book', + 'book': book, + 'form': form + } + return TemplateResponse(request, 'edit_book.html', data) + book = form.save() + + broadcast(request.user, book.to_update_activity(request.user)) + return redirect('/book/%s' % book.id) + + +class Editions(View): + ''' list of editions ''' + 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)) + + data = { + 'title': 'Editions of %s' % work.title, + 'editions': work.editions.order_by('-edition_rank').all(), + 'work': work, + } + return TemplateResponse(request, 'editions.html', data) + + +@login_required +@require_POST +def upload_cover(request, book_id): + ''' upload a new cover ''' + book = get_object_or_404(models.Edition, id=book_id) + + form = forms.CoverForm(request.POST, request.FILES, instance=book) + if not form.is_valid(): + return redirect('/book/%d' % book.id) + + book.cover = form.files['cover'] + book.save() + + broadcast(request.user, book.to_update_activity(request.user)) + return redirect('/book/%s' % book.id) + + +@login_required +@require_POST +@permission_required('bookwyrm.edit_book', raise_exception=True) +def add_description(request, book_id): + ''' upload a new cover ''' + if not request.method == 'POST': + return redirect('/') + + book = get_object_or_404(models.Edition, id=book_id) + + description = request.POST.get('description') + + book.description = description + book.save() + + broadcast(request.user, book.to_update_activity(request.user)) + return redirect('/book/%s' % book.id) + + +@require_POST +def resolve_book(request): + ''' figure out the local path to a book from a remote_id ''' + remote_id = request.POST.get('remote_id') + connector = connector_manager.get_or_create_connector(remote_id) + book = connector.get_or_create_book(remote_id) + + return redirect('/book/%d' % book.id) + + +@login_required +@require_POST +@transaction.atomic +def switch_edition(request): + ''' switch your copy of a book to a different edition ''' + edition_id = request.POST.get('edition') + new_edition = get_object_or_404(models.Edition, id=edition_id) + shelfbooks = models.ShelfBook.objects.filter( + book__parent_work=new_edition.parent_work, + shelf__user=request.user + ) + for shelfbook in shelfbooks.all(): + broadcast(request.user, shelfbook.to_remove_activity(request.user)) + + shelfbook.book = new_edition + shelfbook.save() + + broadcast(request.user, shelfbook.to_add_activity(request.user)) + + readthroughs = models.ReadThrough.objects.filter( + book__parent_work=new_edition.parent_work, + user=request.user + ) + for readthrough in readthroughs.all(): + readthrough.book = new_edition + readthrough.save() + + return redirect('/book/%d' % new_edition.id) diff --git a/bookwyrm/vviews.py b/bookwyrm/vviews.py index befdca2d..3d78a277 100644 --- a/bookwyrm/vviews.py +++ b/bookwyrm/vviews.py @@ -1,18 +1,15 @@ ''' views for pages you can go to in the application ''' import re -from django.contrib.auth.decorators import login_required, permission_required from django.contrib.postgres.search import TrigramSimilarity -from django.db.models import Q from django.db.models.functions import Greatest from django.http import HttpResponseNotFound, JsonResponse -from django.shortcuts import get_object_or_404 from django.template.response import TemplateResponse from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_GET from bookwyrm import outgoing -from bookwyrm import forms, models +from bookwyrm import models from bookwyrm.activitypub import ActivitypubResponse from bookwyrm.connectors import connector_manager from bookwyrm.utils import regex @@ -90,38 +87,6 @@ def search(request): return TemplateResponse(request, 'search_results.html', data) -@login_required -@permission_required('bookwyrm.edit_book', raise_exception=True) -@require_GET -def edit_author_page(request, author_id): - ''' info about a book ''' - author = get_object_or_404(models.Author, id=author_id) - data = { - 'title': 'Edit Author', - 'author': author, - 'form': forms.AuthorForm(instance=author) - } - return TemplateResponse(request, 'edit_author.html', data) - - -@require_GET -def author_page(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( - Q(authors=author) | Q(editions__authors=author)).distinct() - data = { - 'title': author.name, - 'author': author, - 'books': [b.get_default_edition() for b in books], - } - return TemplateResponse(request, 'author.html', data) - - @require_GET def tag_page(request, tag_id): ''' books related to a tag '''