From 20e280e67666052131a4d19bf89c9b246c74a574 Mon Sep 17 00:00:00 2001 From: Mouse Reeve <mousereeve@riseup.net> Date: Wed, 13 Jan 2021 10:24:24 -0800 Subject: [PATCH] Adds tag class views --- bookwyrm/tests/test_view_actions.py | 42 ------------ bookwyrm/tests/views/test_tag.py | 99 +++++++++++++++++++++++++++++ bookwyrm/urls.py | 9 +-- bookwyrm/view_actions.py | 42 ------------ bookwyrm/views/__init__.py | 1 + bookwyrm/views/tag.py | 78 +++++++++++++++++++++++ bookwyrm/vviews.py | 22 ------- 7 files changed, 183 insertions(+), 110 deletions(-) create mode 100644 bookwyrm/tests/views/test_tag.py create mode 100644 bookwyrm/views/tag.py diff --git a/bookwyrm/tests/test_view_actions.py b/bookwyrm/tests/test_view_actions.py index e7c5e0cb6..a554c645a 100644 --- a/bookwyrm/tests/test_view_actions.py +++ b/bookwyrm/tests/test_view_actions.py @@ -48,8 +48,6 @@ class ViewActions(TestCase): self.factory = RequestFactory() - - def test_edit_shelf_privacy(self): ''' set name or privacy on shelf ''' shelf = self.local_user.shelf_set.get(identifier='to-read') @@ -168,43 +166,3 @@ class ViewActions(TestCase): self.assertEqual(readthrough.finish_date.day, 7) self.assertEqual(readthrough.book, self.book) self.assertEqual(readthrough.user, self.local_user) - - - def test_tag(self): - ''' add a tag to a book ''' - request = self.factory.post( - '', { - 'name': 'A Tag!?', - 'book': self.book.id, - }) - request.user = self.local_user - - with patch('bookwyrm.broadcast.broadcast_task.delay'): - actions.tag(request) - - tag = models.Tag.objects.get() - user_tag = models.UserTag.objects.get() - self.assertEqual(tag.name, 'A Tag!?') - self.assertEqual(tag.identifier, 'A+Tag%21%3F') - self.assertEqual(user_tag.user, self.local_user) - self.assertEqual(user_tag.book, self.book) - - - def test_untag(self): - ''' remove a tag from a book ''' - tag = models.Tag.objects.create(name='A Tag!?') - models.UserTag.objects.create( - user=self.local_user, book=self.book, tag=tag) - request = self.factory.post( - '', { - 'user': self.local_user.id, - 'book': self.book.id, - 'name': tag.name, - }) - request.user = self.local_user - - with patch('bookwyrm.broadcast.broadcast_task.delay'): - actions.untag(request) - - self.assertTrue(models.Tag.objects.filter(name='A Tag!?').exists()) - self.assertFalse(models.UserTag.objects.exists()) diff --git a/bookwyrm/tests/views/test_tag.py b/bookwyrm/tests/views/test_tag.py new file mode 100644 index 000000000..1556139ca --- /dev/null +++ b/bookwyrm/tests/views/test_tag.py @@ -0,0 +1,99 @@ +''' test for app action functionality ''' +from unittest.mock import patch +from django.contrib.auth.models import Group, Permission +from django.contrib.contenttypes.models import ContentType +from django.template.response import TemplateResponse +from django.test import TestCase +from django.test.client import RequestFactory + +from bookwyrm import models, views +from bookwyrm.activitypub import ActivitypubResponse + + +class TagViews(TestCase): + ''' tag views''' + def setUp(self): + ''' we need basic test data and mocks ''' + self.factory = RequestFactory() + self.local_user = models.User.objects.create_user( + 'mouse@local.com', 'mouse@mouse.com', 'mouseword', + local=True, localname='mouse', + remote_id='https://example.com/users/mouse', + ) + self.group = Group.objects.create(name='editor') + self.group.permissions.add( + Permission.objects.create( + name='edit_book', + codename='edit_book', + content_type=ContentType.objects.get_for_model(models.User)).id + ) + self.work = models.Work.objects.create(title='Test Work') + self.book = models.Edition.objects.create( + title='Example Edition', + remote_id='https://example.com/book/1', + parent_work=self.work + ) + + + def test_tag_page(self): + ''' there are so many views, this just makes sure it LOADS ''' + view = views.Tag.as_view() + tag = models.Tag.objects.create(name='hi there') + models.UserTag.objects.create( + tag=tag, user=self.local_user, book=self.book) + request = self.factory.get('') + with patch('bookwyrm.views.tag.is_api_request') as is_api: + is_api.return_value = False + result = view(request, tag.identifier) + self.assertIsInstance(result, TemplateResponse) + self.assertEqual(result.template_name, 'tag.html') + self.assertEqual(result.status_code, 200) + + request = self.factory.get('') + with patch('bookwyrm.views.tag.is_api_request') as is_api: + is_api.return_value = True + result = view(request, tag.identifier) + self.assertIsInstance(result, ActivitypubResponse) + self.assertEqual(result.status_code, 200) + + + def test_tag(self): + ''' add a tag to a book ''' + view = views.AddTag.as_view() + request = self.factory.post( + '', { + 'name': 'A Tag!?', + 'book': self.book.id, + }) + request.user = self.local_user + + with patch('bookwyrm.broadcast.broadcast_task.delay'): + view(request) + + tag = models.Tag.objects.get() + user_tag = models.UserTag.objects.get() + self.assertEqual(tag.name, 'A Tag!?') + self.assertEqual(tag.identifier, 'A+Tag%21%3F') + self.assertEqual(user_tag.user, self.local_user) + self.assertEqual(user_tag.book, self.book) + + + def test_untag(self): + ''' remove a tag from a book ''' + view = views.RemoveTag.as_view() + tag = models.Tag.objects.create(name='A Tag!?') + models.UserTag.objects.create( + user=self.local_user, book=self.book, tag=tag) + request = self.factory.post( + '', { + 'user': self.local_user.id, + 'book': self.book.id, + 'name': tag.name, + }) + request.user = self.local_user + + with patch('bookwyrm.broadcast.broadcast_task.delay'): + view(request) + + self.assertTrue(models.Tag.objects.filter(name='A Tag!?').exists()) + self.assertFalse(models.UserTag.objects.exists()) diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index 260164b7b..02b3f6e92 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -100,11 +100,12 @@ urlpatterns = [ re_path(r'^author/(?P<author_id>\d+)(.json)?/?$', views.Author.as_view()), re_path(r'^author/(?P<author_id>\d+)/edit/?$', views.EditAuthor.as_view()), - re_path(r'^tag/?$', actions.tag), - re_path(r'^untag/?$', actions.untag), + # tags + re_path(r'^tag/(?P<tag_id>.+)\.json/?$', views.Tag.as_view()), + re_path(r'^tag/(?P<tag_id>.+)/?$', views.Tag.as_view()), + re_path(r'^tag/?$', views.AddTag.as_view()), + re_path(r'^untag/?$', views.RemoveTag.as_view()), - re_path(r'^tag/(?P<tag_id>.+)\.json/?$', vviews.tag_page), - re_path(r'^tag/(?P<tag_id>.+)/?$', vviews.tag_page), re_path(r'^%s/shelf/(?P<shelf_identifier>[\w-]+)(.json)?/?$' % \ user_path, vviews.shelf_page), re_path(r'^%s/shelf/(?P<shelf_identifier>[\w-]+)(.json)?/?$' % \ diff --git a/bookwyrm/view_actions.py b/bookwyrm/view_actions.py index c349f3f91..4279b281d 100644 --- a/bookwyrm/view_actions.py +++ b/bookwyrm/view_actions.py @@ -9,7 +9,6 @@ from django.utils import timezone from django.views.decorators.http import require_POST from bookwyrm import forms, models, outgoing -from bookwyrm.broadcast import broadcast from bookwyrm.vviews import get_user_from_username, get_edition @login_required @@ -215,47 +214,6 @@ def create_readthrough(request): return redirect(request.headers.get('Referer', '/')) -@login_required -@require_POST -def tag(request): - ''' tag a book ''' - # I'm not using a form here because sometimes "name" is sent as a hidden - # field which doesn't validate - name = request.POST.get('name') - book_id = request.POST.get('book') - book = get_object_or_404(models.Edition, id=book_id) - tag_obj, created = models.Tag.objects.get_or_create( - name=name, - ) - user_tag, _ = models.UserTag.objects.get_or_create( - user=request.user, - book=book, - tag=tag_obj, - ) - - if created: - broadcast(request.user, user_tag.to_add_activity(request.user)) - return redirect('/book/%s' % book_id) - - -@login_required -@require_POST -def untag(request): - ''' untag a book ''' - name = request.POST.get('name') - tag_obj = get_object_or_404(models.Tag, name=name) - book_id = request.POST.get('book') - book = get_object_or_404(models.Edition, id=book_id) - - user_tag = get_object_or_404( - models.UserTag, tag=tag_obj, book=book, user=request.user) - tag_activity = user_tag.to_remove_activity(request.user) - user_tag.delete() - - broadcast(request.user, tag_activity) - return redirect('/book/%s' % book_id) - - @login_required @require_POST def follow(request): diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py index 0d01fb566..5eed7f176 100644 --- a/bookwyrm/views/__init__.py +++ b/bookwyrm/views/__init__.py @@ -12,3 +12,4 @@ 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 +from .tag import Tag, AddTag, RemoveTag diff --git a/bookwyrm/views/tag.py b/bookwyrm/views/tag.py new file mode 100644 index 000000000..e95ffe817 --- /dev/null +++ b/bookwyrm/views/tag.py @@ -0,0 +1,78 @@ +''' tagging views''' +from django.contrib.auth.decorators import login_required +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 bookwyrm import models +from bookwyrm.activitypub import ActivitypubResponse +from bookwyrm.broadcast import broadcast +from .helpers import is_api_request + + +# pylint: disable= no-self-use +class Tag(View): + ''' tag page ''' + def get(self, request, tag_id): + ''' see books related to a tag ''' + tag_obj = models.Tag.objects.filter(identifier=tag_id).first() + if not tag_obj: + return HttpResponseNotFound() + + if is_api_request(request): + return ActivitypubResponse(tag_obj.to_activity(**request.GET)) + + books = models.Edition.objects.filter( + usertag__tag__identifier=tag_id + ).distinct() + data = { + 'title': tag_obj.name, + 'books': books, + 'tag': tag_obj, + } + return TemplateResponse(request, 'tag.html', data) + + +@method_decorator(login_required, name='dispatch') +class AddTag(View): + ''' add a tag to a book ''' + def post(self, request): + ''' tag a book ''' + # I'm not using a form here because sometimes "name" is sent as a hidden + # field which doesn't validate + name = request.POST.get('name') + book_id = request.POST.get('book') + book = get_object_or_404(models.Edition, id=book_id) + tag_obj, created = models.Tag.objects.get_or_create( + name=name, + ) + user_tag, _ = models.UserTag.objects.get_or_create( + user=request.user, + book=book, + tag=tag_obj, + ) + + if created: + broadcast(request.user, user_tag.to_add_activity(request.user)) + return redirect('/book/%s' % book_id) + + +@method_decorator(login_required, name='dispatch') +class RemoveTag(View): + ''' remove a user's tag from a book ''' + def post(self, request): + ''' untag a book ''' + name = request.POST.get('name') + tag_obj = get_object_or_404(models.Tag, name=name) + book_id = request.POST.get('book') + book = get_object_or_404(models.Edition, id=book_id) + + user_tag = get_object_or_404( + models.UserTag, tag=tag_obj, book=book, user=request.user) + tag_activity = user_tag.to_remove_activity(request.user) + user_tag.delete() + + broadcast(request.user, tag_activity) + return redirect('/book/%s' % book_id) diff --git a/bookwyrm/vviews.py b/bookwyrm/vviews.py index 3d78a2777..4110ebbbd 100644 --- a/bookwyrm/vviews.py +++ b/bookwyrm/vviews.py @@ -22,7 +22,6 @@ def get_edition(book_id): return book - def get_user_from_username(username): ''' helper function to resolve a localname or a username to a user ''' # raises DoesNotExist if user is now found @@ -87,27 +86,6 @@ def search(request): return TemplateResponse(request, 'search_results.html', data) -@require_GET -def tag_page(request, tag_id): - ''' books related to a tag ''' - tag_obj = models.Tag.objects.filter(identifier=tag_id).first() - if not tag_obj: - return HttpResponseNotFound() - - if is_api_request(request): - return ActivitypubResponse(tag_obj.to_activity(**request.GET)) - - books = models.Edition.objects.filter( - usertag__tag__identifier=tag_id - ).distinct() - data = { - 'title': tag_obj.name, - 'books': books, - 'tag': tag_obj, - } - return TemplateResponse(request, 'tag.html', data) - - @csrf_exempt @require_GET def user_shelves_page(request, username):