From cfa1a1b42c175d1fe15b2ada4267ab34c5920dcd Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 21 Dec 2020 12:17:18 -0800 Subject: [PATCH 01/22] Remove sync fields and share fields between book and author --- bookwyrm/forms.py | 1 - .../migrations/0029_auto_20201221_2014.py | 61 +++++++++++++++++++ bookwyrm/models/author.py | 19 +----- bookwyrm/models/book.py | 37 ++++++----- bookwyrm/templates/edit_book.html | 14 ----- bookwyrm/view_actions.py | 1 - 6 files changed, 84 insertions(+), 49 deletions(-) create mode 100644 bookwyrm/migrations/0029_auto_20201221_2014.py diff --git a/bookwyrm/forms.py b/bookwyrm/forms.py index 1422b4b9..c4a602ca 100644 --- a/bookwyrm/forms.py +++ b/bookwyrm/forms.py @@ -123,7 +123,6 @@ class EditionForm(CustomForm): 'remote_id', 'created_date', 'updated_date', - 'last_sync_date', 'authors',# TODO 'parent_work', diff --git a/bookwyrm/migrations/0029_auto_20201221_2014.py b/bookwyrm/migrations/0029_auto_20201221_2014.py new file mode 100644 index 00000000..ebf27a74 --- /dev/null +++ b/bookwyrm/migrations/0029_auto_20201221_2014.py @@ -0,0 +1,61 @@ +# Generated by Django 3.0.7 on 2020-12-21 20:14 + +import bookwyrm.models.fields +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0028_remove_book_author_text'), + ] + + operations = [ + migrations.RemoveField( + model_name='author', + name='last_sync_date', + ), + migrations.RemoveField( + model_name='author', + name='sync', + ), + migrations.RemoveField( + model_name='book', + name='last_sync_date', + ), + migrations.RemoveField( + model_name='book', + name='sync', + ), + migrations.RemoveField( + model_name='book', + name='sync_cover', + ), + migrations.AddField( + model_name='author', + name='goodreads_key', + field=bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='author', + name='last_edited_by', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='author', + name='librarything_key', + field=bookwyrm.models.fields.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='book', + name='last_edited_by', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='author', + name='origin_id', + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/bookwyrm/models/author.py b/bookwyrm/models/author.py index a2eac507..d0cb8d19 100644 --- a/bookwyrm/models/author.py +++ b/bookwyrm/models/author.py @@ -1,21 +1,15 @@ ''' database schema for info about authors ''' from django.db import models -from django.utils import timezone from bookwyrm import activitypub from bookwyrm.settings import DOMAIN -from .base_model import ActivitypubMixin, BookWyrmModel +from .book import BookDataModel from . import fields -class Author(ActivitypubMixin, BookWyrmModel): +class Author(BookDataModel): ''' basic biographic info ''' - origin_id = models.CharField(max_length=255, null=True) - openlibrary_key = fields.CharField( - max_length=255, blank=True, null=True, deduplication_field=True) - sync = models.BooleanField(default=True) - last_sync_date = models.DateTimeField(default=timezone.now) wikipedia_link = fields.CharField( max_length=255, blank=True, null=True, deduplication_field=True) # idk probably other keys would be useful here? @@ -27,15 +21,6 @@ class Author(ActivitypubMixin, BookWyrmModel): ) bio = fields.HtmlField(null=True, blank=True) - def save(self, *args, **kwargs): - ''' handle remote vs origin ids ''' - if self.id: - self.remote_id = self.get_remote_id() - else: - self.origin_id = self.remote_id - self.remote_id = None - return super().save(*args, **kwargs) - def get_remote_id(self): ''' editions and works both use "book" instead of model_name ''' return 'https://%s/author/%s' % (DOMAIN, self.id) diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index 21311d6c..7e45eacd 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -2,7 +2,6 @@ import re from django.db import models -from django.utils import timezone from model_utils.managers import InheritanceManager from bookwyrm import activitypub @@ -12,10 +11,9 @@ from .base_model import BookWyrmModel from .base_model import ActivitypubMixin, OrderedCollectionPageMixin from . import fields -class Book(ActivitypubMixin, BookWyrmModel): - ''' a generic book, which can mean either an edition or a work ''' +class BookDataModel(ActivitypubMixin, BookWyrmModel): + ''' fields shared between editable book data (books, works, authors) ''' origin_id = models.CharField(max_length=255, null=True, blank=True) - # these identifiers apply to both works and editions openlibrary_key = fields.CharField( max_length=255, blank=True, null=True, deduplication_field=True) librarything_key = fields.CharField( @@ -23,15 +21,28 @@ class Book(ActivitypubMixin, BookWyrmModel): goodreads_key = fields.CharField( max_length=255, blank=True, null=True, deduplication_field=True) - # info about where the data comes from and where/if to sync - sync = models.BooleanField(default=True) - sync_cover = models.BooleanField(default=True) - last_sync_date = models.DateTimeField(default=timezone.now) + last_edited_by = models.ForeignKey( + 'User', on_delete=models.PROTECT, null=True) + + class Meta: + ''' can't initialize this model, that wouldn't make sense ''' + abstract = True + + def save(self, *args, **kwargs): + ''' ensure that the remote_id is within this instance ''' + if self.id: + self.remote_id = self.get_remote_id() + else: + self.origin_id = self.remote_id + self.remote_id = None + return super().save(*args, **kwargs) + + +class Book(BookDataModel): + ''' a generic book, which can mean either an edition or a work ''' connector = models.ForeignKey( 'Connector', on_delete=models.PROTECT, null=True) - # TODO: edit history - # book/work metadata title = fields.CharField(max_length=255) sort_title = fields.CharField(max_length=255, blank=True, null=True) @@ -86,12 +97,6 @@ class Book(ActivitypubMixin, BookWyrmModel): ''' can't be abstract for query reasons, but you shouldn't USE it ''' if not isinstance(self, Edition) and not isinstance(self, Work): raise ValueError('Books should be added as Editions or Works') - - if self.id: - self.remote_id = self.get_remote_id() - else: - self.origin_id = self.remote_id - self.remote_id = None return super().save(*args, **kwargs) def get_remote_id(self): diff --git a/bookwyrm/templates/edit_book.html b/bookwyrm/templates/edit_book.html index 54cefb0a..3d63ae6d 100644 --- a/bookwyrm/templates/edit_book.html +++ b/bookwyrm/templates/edit_book.html @@ -28,20 +28,6 @@
{% csrf_token %} -
-

Data sync -

If sync is enabled, any changes will be over-written

-

-
-
- -
-
- -
-
-
-

Metadata

diff --git a/bookwyrm/view_actions.py b/bookwyrm/view_actions.py index c3e4d2be..7939cab1 100644 --- a/bookwyrm/view_actions.py +++ b/bookwyrm/view_actions.py @@ -289,7 +289,6 @@ def upload_cover(request, book_id): return redirect('/book/%d' % book.id) book.cover = form.files['cover'] - book.sync_cover = False book.save() outgoing.handle_update_book(request.user, book) From 830aaf9d1c77f04953d0905739665bec768fc169 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 21 Dec 2020 13:21:17 -0800 Subject: [PATCH 02/22] Add identifier fields to author activity --- bookwyrm/activitypub/book.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bookwyrm/activitypub/book.py b/bookwyrm/activitypub/book.py index ee4b8851..6fa80b32 100644 --- a/bookwyrm/activitypub/book.py +++ b/bookwyrm/activitypub/book.py @@ -63,5 +63,7 @@ class Author(ActivityObject): aliases: List[str] = field(default_factory=lambda: []) bio: str = '' openlibraryKey: str = '' + librarythingKey: str = '' + goodreadsKey: str = '' wikipediaLink: str = '' type: str = 'Person' From b4c60c059197acc08b6a179d795c5be173f8da46 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 21 Dec 2020 14:25:10 -0800 Subject: [PATCH 03/22] Catches exception thrown when boosting unknown statuses --- bookwyrm/activitypub/base_activity.py | 7 +++++-- bookwyrm/tests/test_incoming.py | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index c344c120..b9facf2f 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -3,7 +3,7 @@ from dataclasses import dataclass, fields, MISSING from json import JSONEncoder from django.apps import apps -from django.db import transaction +from django.db import IntegrityError, transaction from bookwyrm.connectors import ConnectorException, get_data from bookwyrm.tasks import app @@ -92,7 +92,10 @@ class ActivityObject: with transaction.atomic(): # we can't set many to many and reverse fields on an unsaved object - instance.save() + try: + instance.save() + except IntegrityError as e: + raise ActivitySerializerError(e) # add many to many fields, which have to be set post-save for field in instance.many_to_many_fields: diff --git a/bookwyrm/tests/test_incoming.py b/bookwyrm/tests/test_incoming.py index 7e58da86..0269c64c 100644 --- a/bookwyrm/tests/test_incoming.py +++ b/bookwyrm/tests/test_incoming.py @@ -8,6 +8,7 @@ from django.http import HttpResponseBadRequest, HttpResponseNotAllowed, \ HttpResponseNotFound from django.test import TestCase from django.test.client import RequestFactory +import responses from bookwyrm import models, incoming @@ -421,6 +422,25 @@ class Incoming(TestCase): self.assertEqual(notification.related_status, self.status) + @responses.activate + def test_handle_discarded_boost(self): + ''' test a boost of a mastodon status that will be discarded ''' + activity = { + 'type': 'Announce', + 'id': 'http://www.faraway.com/boost/12', + 'actor': self.remote_user.remote_id, + 'object': self.status.to_activity(), + } + responses.add( + responses.GET, + 'http://www.faraway.com/boost/12', + json={'id': 'http://www.faraway.com/boost/12'}, + status=200) + incoming.handle_boost(activity) + self.assertEqual(models.Boost.objects.count(), 0) + + + def test_handle_unboost(self): ''' undo a boost ''' activity = { From fa1ddf359a26df3cffc5b91d8f21d26abf77e449 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 15 Dec 2020 13:59:37 -0800 Subject: [PATCH 04/22] create single outgoing tests file --- .../tests/data/ap_generated_shelve_note.json | 52 +++++ bookwyrm/tests/outgoing/__init__.py | 1 - bookwyrm/tests/outgoing/test_follow.py | 80 -------- .../tests/outgoing/test_remote_webfinger.py | 61 ------ bookwyrm/tests/outgoing/test_shelving.py | 69 ------- bookwyrm/tests/test_outgoing.py | 194 ++++++++++++++++++ 6 files changed, 246 insertions(+), 211 deletions(-) create mode 100644 bookwyrm/tests/data/ap_generated_shelve_note.json delete mode 100644 bookwyrm/tests/outgoing/__init__.py delete mode 100644 bookwyrm/tests/outgoing/test_follow.py delete mode 100644 bookwyrm/tests/outgoing/test_remote_webfinger.py delete mode 100644 bookwyrm/tests/outgoing/test_shelving.py create mode 100644 bookwyrm/tests/test_outgoing.py diff --git a/bookwyrm/tests/data/ap_generated_shelve_note.json b/bookwyrm/tests/data/ap_generated_shelve_note.json new file mode 100644 index 00000000..cc2fc7b7 --- /dev/null +++ b/bookwyrm/tests/data/ap_generated_shelve_note.json @@ -0,0 +1,52 @@ +{ + "id": "https://example.com/users/rat/generatednote/2567/activity", + "type": "Create", + "actor": "https://example.com/users/rat", + "object": { + "id": "https://example.com/users/rat/generatednote/2567", + "type": "GeneratedNote", + "url": null, + "inReplyTo": null, + "published": "2020-12-16T01:45:19.662734+00:00", + "attributedTo": "https://example.com/users/rat", + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "cc": [ + "https://example.com/users/rat/followers" + ], + "content": "wants to read", + "replies": { + "id": "https://example.com/users/rat/generatednote/2567/replies", + "type": "OrderedCollection", + "totalItems": 0, + "first": "https://example.com/users/rat/generatednote/2567/replies?page=true", + "last": "https://example.com/users/rat/generatednote/2567/replies?page=true", + "name": "", + "@context": "https://www.w3.org/ns/activitystreams" + }, + "tag": [ + { + "href": "https://bookwyrm.social/book/37292", + "name": "Female Husbands", + "type": "Book" + } + ], + "attachment": [], + "sensitive": false, + "@context": "https://www.w3.org/ns/activitystreams" + }, + "to": [ + "https://example.com/users/rat/followers" + ], + "cc": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "signature": { + "creator": "https://example.com/users/rat#main-key", + "created": "2020-12-16T01:45:19.662734+00:00", + "signatureValue": "R+W8nN1CQAlREjSUeaQwJXZrXTOOLvpHQi9n/3vd8QKq+l6HJEpu7eAht9fjpk8YOKEgV3OUQ7w3E42wM4t+sFiaPoQjY6Xy9IOvx/2LcOZjSOtTkiZ1XnnVb3DSbl8BOBH02+cPvoR6k4LIPHm2IHYZ1UL02WdDWaicHEwl7bw=", + "type": "RsaSignature2017" + }, + "@context": "https://www.w3.org/ns/activitystreams" +} diff --git a/bookwyrm/tests/outgoing/__init__.py b/bookwyrm/tests/outgoing/__init__.py deleted file mode 100644 index b6e690fd..00000000 --- a/bookwyrm/tests/outgoing/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import * diff --git a/bookwyrm/tests/outgoing/test_follow.py b/bookwyrm/tests/outgoing/test_follow.py deleted file mode 100644 index d27db876..00000000 --- a/bookwyrm/tests/outgoing/test_follow.py +++ /dev/null @@ -1,80 +0,0 @@ -from unittest.mock import patch -from django.test import TestCase - -from bookwyrm import models, outgoing -from bookwyrm.settings import DOMAIN - - -class Following(TestCase): - def setUp(self): - with patch('bookwyrm.models.user.set_remote_server'): - self.remote_user = models.User.objects.create_user( - 'rat', 'rat@rat.com', 'ratword', - local=False, - remote_id='https://example.com/users/rat', - inbox='https://example.com/users/rat/inbox', - outbox='https://example.com/users/rat/outbox', - ) - self.local_user = models.User.objects.create_user( - 'mouse', 'mouse@mouse.com', 'mouseword', - local=True, - remote_id='http://local.com/users/mouse', - ) - - - def test_handle_follow(self): - self.assertEqual(models.UserFollowRequest.objects.count(), 0) - - with patch('bookwyrm.broadcast.broadcast_task.delay'): - outgoing.handle_follow(self.local_user, self.remote_user) - - rel = models.UserFollowRequest.objects.get() - - self.assertEqual(rel.user_subject, self.local_user) - self.assertEqual(rel.user_object, self.remote_user) - self.assertEqual(rel.status, 'follow_request') - - - def test_handle_unfollow(self): - self.remote_user.followers.add(self.local_user) - self.assertEqual(self.remote_user.followers.count(), 1) - with patch('bookwyrm.broadcast.broadcast_task.delay'): - outgoing.handle_unfollow(self.local_user, self.remote_user) - - self.assertEqual(self.remote_user.followers.count(), 0) - - - def test_handle_accept(self): - rel = models.UserFollowRequest.objects.create( - user_subject=self.local_user, - user_object=self.remote_user - ) - rel_id = rel.id - - with patch('bookwyrm.broadcast.broadcast_task.delay'): - outgoing.handle_accept(rel) - # request should be deleted - self.assertEqual( - models.UserFollowRequest.objects.filter(id=rel_id).count(), 0 - ) - # follow relationship should exist - self.assertEqual(self.remote_user.followers.first(), self.local_user) - - - def test_handle_reject(self): - rel = models.UserFollowRequest.objects.create( - user_subject=self.local_user, - user_object=self.remote_user - ) - rel_id = rel.id - - with patch('bookwyrm.broadcast.broadcast_task.delay'): - outgoing.handle_reject(rel) - # request should be deleted - self.assertEqual( - models.UserFollowRequest.objects.filter(id=rel_id).count(), 0 - ) - # follow relationship should not exist - self.assertEqual( - models.UserFollows.objects.filter(id=rel_id).count(), 0 - ) diff --git a/bookwyrm/tests/outgoing/test_remote_webfinger.py b/bookwyrm/tests/outgoing/test_remote_webfinger.py deleted file mode 100644 index 1bf884a6..00000000 --- a/bookwyrm/tests/outgoing/test_remote_webfinger.py +++ /dev/null @@ -1,61 +0,0 @@ -''' testing user lookup ''' -import json -import pathlib -from unittest.mock import patch - -from django.test import TestCase -import responses - -from bookwyrm import models, outgoing -from bookwyrm.settings import DOMAIN - -class TestOutgoingRemoteWebfinger(TestCase): - ''' overwrites standard model feilds to work with activitypub ''' - def setUp(self): - ''' get user data ready ''' - datafile = pathlib.Path(__file__).parent.joinpath( - '../data/ap_user.json' - ) - self.userdata = json.loads(datafile.read_bytes()) - del self.userdata['icon'] - - def test_existing_user(self): - ''' simple database lookup by username ''' - user = models.User.objects.create_user( - 'mouse', 'mouse@mouse.mouse', 'mouseword', local=True) - - result = outgoing.handle_remote_webfinger('@mouse@%s' % DOMAIN) - self.assertEqual(result, user) - - result = outgoing.handle_remote_webfinger('mouse@%s' % DOMAIN) - self.assertEqual(result, user) - - - @responses.activate - def test_load_user(self): - username = 'mouse@example.com' - wellknown = { - "subject": "acct:mouse@example.com", - "links": [ - { - "rel": "self", - "type": "application/activity+json", - "href": "https://example.com/user/mouse" - } - ] - } - responses.add( - responses.GET, - 'https://example.com/.well-known/webfinger?resource=acct:%s' \ - % username, - json=wellknown, - status=200) - responses.add( - responses.GET, - 'https://example.com/user/mouse', - json=self.userdata, - status=200) - with patch('bookwyrm.models.user.set_remote_server.delay'): - result = outgoing.handle_remote_webfinger('@mouse@example.com') - self.assertIsInstance(result, models.User) - self.assertEqual(result.username, 'mouse@example.com') diff --git a/bookwyrm/tests/outgoing/test_shelving.py b/bookwyrm/tests/outgoing/test_shelving.py deleted file mode 100644 index 5567784e..00000000 --- a/bookwyrm/tests/outgoing/test_shelving.py +++ /dev/null @@ -1,69 +0,0 @@ -from unittest.mock import patch -from django.test import TestCase - -from bookwyrm import models, outgoing - - -class Shelving(TestCase): - def setUp(self): - self.user = models.User.objects.create_user( - 'mouse', 'mouse@mouse.com', 'mouseword', - local=True, - remote_id='http://local.com/users/mouse', - ) - work = models.Work.objects.create( - title='Example work', - ) - self.book = models.Edition.objects.create( - title='Example Edition', - remote_id='https://example.com/book/1', - parent_work=work, - ) - self.shelf = models.Shelf.objects.create( - name='Test Shelf', - identifier='test-shelf', - user=self.user - ) - - - def test_handle_shelve(self): - with patch('bookwyrm.broadcast.broadcast_task.delay') as _: - outgoing.handle_shelve(self.user, self.book, self.shelf) - # make sure the book is on the shelf - self.assertEqual(self.shelf.books.get(), self.book) - - - def test_handle_shelve_to_read(self): - shelf = models.Shelf.objects.get(identifier='to-read') - - with patch('bookwyrm.broadcast.broadcast_task.delay') as _: - outgoing.handle_shelve(self.user, self.book, shelf) - # make sure the book is on the shelf - self.assertEqual(shelf.books.get(), self.book) - - - def test_handle_shelve_reading(self): - shelf = models.Shelf.objects.get(identifier='reading') - - with patch('bookwyrm.broadcast.broadcast_task.delay') as _: - outgoing.handle_shelve(self.user, self.book, shelf) - # make sure the book is on the shelf - self.assertEqual(shelf.books.get(), self.book) - - - def test_handle_shelve_read(self): - shelf = models.Shelf.objects.get(identifier='read') - - with patch('bookwyrm.broadcast.broadcast_task.delay') as _: - outgoing.handle_shelve(self.user, self.book, shelf) - # make sure the book is on the shelf - self.assertEqual(shelf.books.get(), self.book) - - - def test_handle_unshelve(self): - self.shelf.books.add(self.book) - self.shelf.save() - self.assertEqual(self.shelf.books.count(), 1) - with patch('bookwyrm.broadcast.broadcast_task.delay') as _: - outgoing.handle_unshelve(self.user, self.book, self.shelf) - self.assertEqual(self.shelf.books.count(), 0) diff --git a/bookwyrm/tests/test_outgoing.py b/bookwyrm/tests/test_outgoing.py new file mode 100644 index 00000000..2bdcb225 --- /dev/null +++ b/bookwyrm/tests/test_outgoing.py @@ -0,0 +1,194 @@ +''' sending out activities ''' +import json +import pathlib +from unittest.mock import patch + +from django.test import TestCase +import responses + +from bookwyrm import models, outgoing +from bookwyrm.settings import DOMAIN + + +class Outgoing(TestCase): + ''' sends out activities ''' + def setUp(self): + ''' we'll need some data ''' + with patch('bookwyrm.models.user.set_remote_server'): + self.remote_user = models.User.objects.create_user( + 'rat', 'rat@rat.com', 'ratword', + local=False, + remote_id='https://example.com/users/rat', + inbox='https://example.com/users/rat/inbox', + outbox='https://example.com/users/rat/outbox', + ) + self.local_user = models.User.objects.create_user( + 'mouse', 'mouse@mouse.com', 'mouseword', local=True, + remote_id='https://example.com/users/mouse', + ) + + datafile = pathlib.Path(__file__).parent.joinpath( + 'data/ap_user.json' + ) + self.userdata = json.loads(datafile.read_bytes()) + del self.userdata['icon'] + + self.book = models.Edition.objects.create( + title='Example Edition', + remote_id='https://example.com/book/1', + ) + self.shelf = models.Shelf.objects.create( + name='Test Shelf', + identifier='test-shelf', + user=self.user + ) + + + def test_handle_follow(self): + ''' send a follow request ''' + self.assertEqual(models.UserFollowRequest.objects.count(), 0) + + with patch('bookwyrm.broadcast.broadcast_task.delay'): + outgoing.handle_follow(self.local_user, self.remote_user) + + rel = models.UserFollowRequest.objects.get() + + self.assertEqual(rel.user_subject, self.local_user) + self.assertEqual(rel.user_object, self.remote_user) + self.assertEqual(rel.status, 'follow_request') + + + def test_handle_unfollow(self): + ''' send an unfollow ''' + self.remote_user.followers.add(self.local_user) + self.assertEqual(self.remote_user.followers.count(), 1) + with patch('bookwyrm.broadcast.broadcast_task.delay'): + outgoing.handle_unfollow(self.local_user, self.remote_user) + + self.assertEqual(self.remote_user.followers.count(), 0) + + + def test_handle_accept(self): + ''' accept a follow request ''' + rel = models.UserFollowRequest.objects.create( + user_subject=self.local_user, + user_object=self.remote_user + ) + rel_id = rel.id + + with patch('bookwyrm.broadcast.broadcast_task.delay'): + outgoing.handle_accept(rel) + # request should be deleted + self.assertEqual( + models.UserFollowRequest.objects.filter(id=rel_id).count(), 0 + ) + # follow relationship should exist + self.assertEqual(self.remote_user.followers.first(), self.local_user) + + + def test_handle_reject(self): + ''' reject a follow request ''' + rel = models.UserFollowRequest.objects.create( + user_subject=self.local_user, + user_object=self.remote_user + ) + rel_id = rel.id + + with patch('bookwyrm.broadcast.broadcast_task.delay'): + outgoing.handle_reject(rel) + # request should be deleted + self.assertEqual( + models.UserFollowRequest.objects.filter(id=rel_id).count(), 0 + ) + # follow relationship should not exist + self.assertEqual( + models.UserFollows.objects.filter(id=rel_id).count(), 0 + ) + + def test_existing_user(self): + ''' simple database lookup by username ''' + user = models.User.objects.create_user( + 'mouse', 'mouse@mouse.mouse', 'mouseword', local=True) + + result = outgoing.handle_remote_webfinger('@mouse@%s' % DOMAIN) + self.assertEqual(result, user) + + result = outgoing.handle_remote_webfinger('mouse@%s' % DOMAIN) + self.assertEqual(result, user) + + + @responses.activate + def test_load_user(self): + ''' find a remote user using webfinger ''' + username = 'mouse@example.com' + wellknown = { + "subject": "acct:mouse@example.com", + "links": [{ + "rel": "self", + "type": "application/activity+json", + "href": "https://example.com/user/mouse" + }] + } + responses.add( + responses.GET, + 'https://example.com/.well-known/webfinger?resource=acct:%s' \ + % username, + json=wellknown, + status=200) + responses.add( + responses.GET, + 'https://example.com/user/mouse', + json=self.userdata, + status=200) + with patch('bookwyrm.models.user.set_remote_server.delay'): + result = outgoing.handle_remote_webfinger('@mouse@example.com') + self.assertIsInstance(result, models.User) + self.assertEqual(result.username, 'mouse@example.com') + + + def test_handle_shelve(self): + ''' shelve a book ''' + with patch('bookwyrm.broadcast.broadcast_task.delay'): + outgoing.handle_shelve(self.user, self.book, self.shelf) + # make sure the book is on the shelf + self.assertEqual(self.shelf.books.get(), self.book) + + + def test_handle_shelve_to_read(self): + ''' special behavior for the to-read shelf ''' + shelf = models.Shelf.objects.get(identifier='to-read') + + with patch('bookwyrm.broadcast.broadcast_task.delay'): + outgoing.handle_shelve(self.user, self.book, shelf) + # make sure the book is on the shelf + self.assertEqual(shelf.books.get(), self.book) + + + def test_handle_shelve_reading(self): + ''' special behavior for the reading shelf ''' + shelf = models.Shelf.objects.get(identifier='reading') + + with patch('bookwyrm.broadcast.broadcast_task.delay'): + outgoing.handle_shelve(self.user, self.book, shelf) + # make sure the book is on the shelf + self.assertEqual(shelf.books.get(), self.book) + + + def test_handle_shelve_read(self): + ''' special behavior for the read shelf ''' + shelf = models.Shelf.objects.get(identifier='read') + + with patch('bookwyrm.broadcast.broadcast_task.delay'): + outgoing.handle_shelve(self.user, self.book, shelf) + # make sure the book is on the shelf + self.assertEqual(shelf.books.get(), self.book) + + + def test_handle_unshelve(self): + ''' remove a book from a shelf ''' + self.shelf.books.add(self.book) + self.shelf.save() + self.assertEqual(self.shelf.books.count(), 1) + with patch('bookwyrm.broadcast.broadcast_task.delay'): + outgoing.handle_unshelve(self.user, self.book, self.shelf) + self.assertEqual(self.shelf.books.count(), 0) From 65e9afd2712737991fe9e55dd27d1b6fd23d68d4 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 21 Dec 2020 14:54:27 -0800 Subject: [PATCH 05/22] Fixes user in outgoing tests --- bookwyrm/tests/test_outgoing.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bookwyrm/tests/test_outgoing.py b/bookwyrm/tests/test_outgoing.py index 2bdcb225..2a83d484 100644 --- a/bookwyrm/tests/test_outgoing.py +++ b/bookwyrm/tests/test_outgoing.py @@ -40,7 +40,7 @@ class Outgoing(TestCase): self.shelf = models.Shelf.objects.create( name='Test Shelf', identifier='test-shelf', - user=self.user + user=self.local_user ) @@ -149,7 +149,7 @@ class Outgoing(TestCase): def test_handle_shelve(self): ''' shelve a book ''' with patch('bookwyrm.broadcast.broadcast_task.delay'): - outgoing.handle_shelve(self.user, self.book, self.shelf) + outgoing.handle_shelve(self.local_user, self.book, self.shelf) # make sure the book is on the shelf self.assertEqual(self.shelf.books.get(), self.book) @@ -159,7 +159,7 @@ class Outgoing(TestCase): shelf = models.Shelf.objects.get(identifier='to-read') with patch('bookwyrm.broadcast.broadcast_task.delay'): - outgoing.handle_shelve(self.user, self.book, shelf) + outgoing.handle_shelve(self.local_user, self.book, shelf) # make sure the book is on the shelf self.assertEqual(shelf.books.get(), self.book) @@ -169,7 +169,7 @@ class Outgoing(TestCase): shelf = models.Shelf.objects.get(identifier='reading') with patch('bookwyrm.broadcast.broadcast_task.delay'): - outgoing.handle_shelve(self.user, self.book, shelf) + outgoing.handle_shelve(self.local_user, self.book, shelf) # make sure the book is on the shelf self.assertEqual(shelf.books.get(), self.book) @@ -179,7 +179,7 @@ class Outgoing(TestCase): shelf = models.Shelf.objects.get(identifier='read') with patch('bookwyrm.broadcast.broadcast_task.delay'): - outgoing.handle_shelve(self.user, self.book, shelf) + outgoing.handle_shelve(self.local_user, self.book, shelf) # make sure the book is on the shelf self.assertEqual(shelf.books.get(), self.book) @@ -190,5 +190,5 @@ class Outgoing(TestCase): self.shelf.save() self.assertEqual(self.shelf.books.count(), 1) with patch('bookwyrm.broadcast.broadcast_task.delay'): - outgoing.handle_unshelve(self.user, self.book, self.shelf) + outgoing.handle_unshelve(self.local_user, self.book, self.shelf) self.assertEqual(self.shelf.books.count(), 0) From e6105c6cb0000a49c8aeb76411f57124a80947c1 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 21 Dec 2020 14:54:45 -0800 Subject: [PATCH 06/22] Simplify edit user function --- bookwyrm/tests/test_view_actions.py | 30 +++++++++++++++++++++++++++++ bookwyrm/view_actions.py | 22 ++++++--------------- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/bookwyrm/tests/test_view_actions.py b/bookwyrm/tests/test_view_actions.py index 77584c90..a23b602c 100644 --- a/bookwyrm/tests/test_view_actions.py +++ b/bookwyrm/tests/test_view_actions.py @@ -238,6 +238,36 @@ class ViewActions(TestCase): self.assertEqual(resp.template_name, 'password_reset.html') self.assertTrue(models.PasswordReset.objects.exists()) + + def test_password_change(self): + ''' change password ''' + password_hash = self.local_user.password + request = self.factory.post('', { + 'password': 'hi', + 'confirm-password': 'hi' + }) + request.user = self.local_user + resp = actions.password_change(request) + self.assertEqual(resp.template_name, 'user.html') + self.assertNotEqual(self.user.password, password_hash) + + def test_password_change_mismatch(self): + ''' change password ''' + password_hash = self.local_user.password + request = self.factory.post('', { + 'password': 'hi', + 'confirm-password': 'hihi' + }) + request.user = self.local_user + resp = actions.password_change(request) + self.assertEqual(resp.template_name, 'edit_user.html') + self.assertEqual(self.user.password, password_hash) + + + def test_edit_user(self): + ''' use a form to update a user ''' + + def test_switch_edition(self): ''' updates user's relationships to a book ''' work = models.Work.objects.create(title='test work') diff --git a/bookwyrm/view_actions.py b/bookwyrm/view_actions.py index c3e4d2be..dcbdede4 100644 --- a/bookwyrm/view_actions.py +++ b/bookwyrm/view_actions.py @@ -159,7 +159,7 @@ def password_change(request): request.user.set_password(new_password) request.user.save() login(request, request.user) - return redirect('/user-edit') + return redirect('/user/%s' % request.user.localname) @login_required @@ -168,14 +168,11 @@ def edit_profile(request): ''' les get fancy with images ''' form = forms.EditUserForm(request.POST, request.FILES) if not form.is_valid(): - data = { - 'form': form, - 'user': request.user, - } + data = {'form': form, 'user': request.user} return TemplateResponse(request, 'edit_user.html', data) - request.user.name = form.data['name'] - request.user.email = form.data['email'] + user = form.save(commit=False) + if 'avatar' in form.files: # crop and resize avatar upload image = Image.open(form.files['avatar']) @@ -201,15 +198,8 @@ def edit_profile(request): # set the name to a hash extension = form.files['avatar'].name.split('.')[-1] filename = '%s.%s' % (uuid4(), extension) - request.user.avatar.save( - filename, - ContentFile(output.getvalue()) - ) - - request.user.summary = form.data['summary'] - request.user.manually_approves_followers = \ - form.cleaned_data['manually_approves_followers'] - request.user.save() + user.avatar.save(filename, ContentFile(output.getvalue())) + user.save() outgoing.handle_update_user(request.user) return redirect('/user/%s' % request.user.localname) From fcce1a5a3604ed935d0f4b9b56efd4e96e535c0e Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 21 Dec 2020 15:44:11 -0800 Subject: [PATCH 07/22] Fixes outgoing tests --- bookwyrm/tests/test_outgoing.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/bookwyrm/tests/test_outgoing.py b/bookwyrm/tests/test_outgoing.py index 2a83d484..2c1d119c 100644 --- a/bookwyrm/tests/test_outgoing.py +++ b/bookwyrm/tests/test_outgoing.py @@ -33,9 +33,11 @@ class Outgoing(TestCase): self.userdata = json.loads(datafile.read_bytes()) del self.userdata['icon'] + 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=work ) self.shelf = models.Shelf.objects.create( name='Test Shelf', @@ -107,14 +109,11 @@ class Outgoing(TestCase): def test_existing_user(self): ''' simple database lookup by username ''' - user = models.User.objects.create_user( - 'mouse', 'mouse@mouse.mouse', 'mouseword', local=True) - result = outgoing.handle_remote_webfinger('@mouse@%s' % DOMAIN) - self.assertEqual(result, user) + self.assertEqual(result, self.local_user) result = outgoing.handle_remote_webfinger('mouse@%s' % DOMAIN) - self.assertEqual(result, user) + self.assertEqual(result, self.local_user) @responses.activate From db281d5154c69dac19315228407e6dda0617742e Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 21 Dec 2020 16:19:36 -0800 Subject: [PATCH 08/22] Simplifies update user view --- bookwyrm/tests/test_view_actions.py | 19 +++++++++++++------ bookwyrm/view_actions.py | 5 +++-- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/bookwyrm/tests/test_view_actions.py b/bookwyrm/tests/test_view_actions.py index a23b602c..c1e6f860 100644 --- a/bookwyrm/tests/test_view_actions.py +++ b/bookwyrm/tests/test_view_actions.py @@ -6,7 +6,7 @@ from django.http.response import Http404 from django.test import TestCase from django.test.client import RequestFactory -from bookwyrm import view_actions as actions, models +from bookwyrm import forms, models, view_actions as actions from bookwyrm.settings import DOMAIN @@ -247,9 +247,9 @@ class ViewActions(TestCase): 'confirm-password': 'hi' }) request.user = self.local_user - resp = actions.password_change(request) - self.assertEqual(resp.template_name, 'user.html') - self.assertNotEqual(self.user.password, password_hash) + with patch('bookwyrm.view_actions.login'): + resp = actions.password_change(request) + self.assertNotEqual(self.local_user.password, password_hash) def test_password_change_mismatch(self): ''' change password ''' @@ -260,12 +260,19 @@ class ViewActions(TestCase): }) request.user = self.local_user resp = actions.password_change(request) - self.assertEqual(resp.template_name, 'edit_user.html') - self.assertEqual(self.user.password, password_hash) + self.assertEqual(self.local_user.password, password_hash) def test_edit_user(self): ''' use a form to update a user ''' + form = forms.EditUserForm(instance=self.local_user) + form.data['name'] = 'New Name' + request = self.factory.post('', form.data) + request.user = self.local_user + + with patch('bookwyrm.broadcast.broadcast_task.delay'): + actions.edit_profile(request) + self.assertEqual(self.local_user.name, 'New Name') def test_switch_edition(self): diff --git a/bookwyrm/view_actions.py b/bookwyrm/view_actions.py index dcbdede4..5a7a059d 100644 --- a/bookwyrm/view_actions.py +++ b/bookwyrm/view_actions.py @@ -166,7 +166,8 @@ def password_change(request): @require_POST def edit_profile(request): ''' les get fancy with images ''' - form = forms.EditUserForm(request.POST, request.FILES) + form = forms.EditUserForm( + request.POST, request.FILES, instance=request.user) if not form.is_valid(): data = {'form': form, 'user': request.user} return TemplateResponse(request, 'edit_user.html', data) @@ -201,7 +202,7 @@ def edit_profile(request): user.avatar.save(filename, ContentFile(output.getvalue())) user.save() - outgoing.handle_update_user(request.user) + outgoing.handle_update_user(user) return redirect('/user/%s' % request.user.localname) From fdbce5d05e2ab198773e014b88eab63d6c32bcb4 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 22 Dec 2020 08:21:43 -0800 Subject: [PATCH 09/22] Adds close button to suggested post window on mobile --- bookwyrm/templates/feed.html | 18 +++++++++++++----- bookwyrm/templates/snippets/book_titleby.html | 8 ++------ 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/bookwyrm/templates/feed.html b/bookwyrm/templates/feed.html index aec76acc..40e6dad1 100644 --- a/bookwyrm/templates/feed.html +++ b/bookwyrm/templates/feed.html @@ -44,18 +44,26 @@
diff --git a/bookwyrm/templates/snippets/book_titleby.html b/bookwyrm/templates/snippets/book_titleby.html index d8b84646..b01ede4c 100644 --- a/bookwyrm/templates/snippets/book_titleby.html +++ b/bookwyrm/templates/snippets/book_titleby.html @@ -1,9 +1,5 @@ - - {{ book.title }} - +{{ book.title }} {% if book.authors %} - - by {% include 'snippets/authors.html' with book=book %} - +by {% include 'snippets/authors.html' with book=book %} {% endif %} From fd2f452b466971599beb93024b4ceb2acece9235 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 22 Dec 2020 08:26:02 -0800 Subject: [PATCH 10/22] Adds title along with alt attributes on images --- bookwyrm/templates/snippets/book_cover.html | 2 +- bookwyrm/templates/snippets/status_content.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bookwyrm/templates/snippets/book_cover.html b/bookwyrm/templates/snippets/book_cover.html index 6d15b37f..0df2d6d1 100644 --- a/bookwyrm/templates/snippets/book_cover.html +++ b/bookwyrm/templates/snippets/book_cover.html @@ -1,7 +1,7 @@ {% load bookwyrm_tags %}
{% if book.cover %} -{{ book.alt_text }} +{{ book.alt_text }} {% else %}
No cover diff --git a/bookwyrm/templates/snippets/status_content.html b/bookwyrm/templates/snippets/status_content.html index 95d6f8ee..d2663653 100644 --- a/bookwyrm/templates/snippets/status_content.html +++ b/bookwyrm/templates/snippets/status_content.html @@ -43,7 +43,7 @@ From 25dee8362d013eb4398ee22730d9a64f4b8a769a Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 22 Dec 2020 09:26:40 -0800 Subject: [PATCH 11/22] Adds edit author form and stores last edited by --- bookwyrm/forms.py | 13 ++++- bookwyrm/outgoing.py | 4 +- bookwyrm/templates/author.html | 22 ++++++- bookwyrm/templates/edit_author.html | 89 +++++++++++++++++++++++++++++ bookwyrm/templates/edit_book.html | 36 ++++++------ bookwyrm/urls.py | 2 + bookwyrm/view_actions.py | 27 ++++++++- bookwyrm/views.py | 14 +++++ 8 files changed, 182 insertions(+), 25 deletions(-) create mode 100644 bookwyrm/templates/edit_author.html diff --git a/bookwyrm/forms.py b/bookwyrm/forms.py index c4a602ca..686ac8b1 100644 --- a/bookwyrm/forms.py +++ b/bookwyrm/forms.py @@ -30,6 +30,7 @@ class CustomForm(ModelForm): visible.field.widget.attrs['rows'] = None visible.field.widget.attrs['class'] = css_classes[input_type] + # pylint: disable=missing-class-docstring class LoginForm(CustomForm): class Meta: @@ -121,13 +122,13 @@ class EditionForm(CustomForm): model = models.Edition exclude = [ 'remote_id', + 'origin_id', 'created_date', 'updated_date', 'authors',# TODO 'parent_work', 'shelves', - 'misc_identifiers', 'subjects',# TODO 'subject_places',# TODO @@ -135,6 +136,16 @@ class EditionForm(CustomForm): 'connector', ] +class AuthorForm(CustomForm): + class Meta: + model = models.Author + exclude = [ + 'remote_id', + 'origin_id', + 'created_date', + 'updated_date', + ] + class ImportForm(forms.Form): csv_file = forms.FileField() diff --git a/bookwyrm/outgoing.py b/bookwyrm/outgoing.py index 13df9026..00154cf4 100644 --- a/bookwyrm/outgoing.py +++ b/bookwyrm/outgoing.py @@ -375,9 +375,9 @@ def handle_unboost(user, status): broadcast(user, activity) -def handle_update_book(user, book): +def handle_update_book_data(user, item): ''' broadcast the news about our book ''' - broadcast(user, book.to_update_activity(user)) + broadcast(user, item.to_update_activity(user)) def handle_update_user(user): diff --git a/bookwyrm/templates/author.html b/bookwyrm/templates/author.html index 9a7a20ab..5e8aab71 100644 --- a/bookwyrm/templates/author.html +++ b/bookwyrm/templates/author.html @@ -2,13 +2,31 @@ {% load bookwyrm_tags %} {% block content %}
-

{{ author.name }}

+
+
+

{{ author.name }}

+
+ {% if request.user.is_authenticated and perms.bookwyrm.edit_book %} + + {% endif %} +
+
+
{% if author.bio %}

- {{ author.bio }} + {{ author.bio | to_markdown | safe }}

{% endif %} + {% if author.wikipedia_link %} +

Wikipedia

+ {% endif %}
diff --git a/bookwyrm/templates/edit_author.html b/bookwyrm/templates/edit_author.html new file mode 100644 index 00000000..b08aa983 --- /dev/null +++ b/bookwyrm/templates/edit_author.html @@ -0,0 +1,89 @@ +{% extends 'layout.html' %} +{% load humanize %} +{% block content %} +
+
+

+ Edit "{{ author.name }}" +

+ +
+
+

Added: {{ author.created_date | naturaltime }}

+

Updated: {{ author.updated_date | naturaltime }}

+

Last edited by: {{ author.last_edited_by.display_name }}

+
+
+ +{% if form.non_field_errors %} +
+

{{ form.non_field_errors }}

+
+{% endif %} + + + {% csrf_token %} + + +
+
+

Metadata

+

{{ form.name }}

+ {% for error in form.name.errors %} +

{{ error | escape }}

+ {% endfor %} + +

{{ form.bio }}

+ {% for error in form.bio.errors %} +

{{ error | escape }}

+ {% endfor %} + +

{{ form.wikipedia_link }}

+ {% for error in form.wikipedia_link.errors %} +

{{ error | escape }}

+ {% endfor %} + +

{{ form.born }}

+ {% for error in form.born.errors %} +

{{ error | escape }}

+ {% endfor %} + +

{{ form.died }}

+ {% for error in form.died.errors %} +

{{ error | escape }}

+ {% endfor %} +
+
+

Author Identifiers

+

{{ form.openlibrary_key }}

+ {% for error in form.openlibrary_key.errors %} +

{{ error | escape }}

+ {% endfor %} + +

{{ form.librarything_key }}

+ {% for error in form.librarything_key.errors %} +

{{ error | escape }}

+ {% endfor %} + +

{{ form.goodreads_key }}

+ {% for error in form.goodreads_key.errors %} +

{{ error | escape }}

+ {% endfor %} + +
+
+ +
+ + Cancel +
+ + +{% endblock %} + diff --git a/bookwyrm/templates/edit_book.html b/bookwyrm/templates/edit_book.html index 3d63ae6d..b730f3e4 100644 --- a/bookwyrm/templates/edit_book.html +++ b/bookwyrm/templates/edit_book.html @@ -17,49 +17,51 @@

Added: {{ book.created_date | naturaltime }}

Updated: {{ book.updated_date | naturaltime }}

+

Last edited by: {{ book.last_edited_by.display_name }}

-{% if login_form.non_field_errors %} +{% if form.non_field_errors %}
-

{{ login_form.non_field_errors }}

+

{{ form.non_field_errors }}

{% endif %}
{% csrf_token %} +

Metadata

-

{{ form.title }}

+

{{ form.title }}

{% for error in form.title.errors %}

{{ error | escape }}

{% endfor %} -

{{ form.sort_title }}

+

{{ form.sort_title }}

{% for error in form.sort_title.errors %}

{{ error | escape }}

{% endfor %} -

{{ form.subtitle }}

+

{{ form.subtitle }}

{% for error in form.subtitle.errors %}

{{ error | escape }}

{% endfor %} -

{{ form.description }}

+

{{ form.description }}

{% for error in form.description.errors %}

{{ error | escape }}

{% endfor %} -

{{ form.series }}

+

{{ form.series }}

{% for error in form.series.errors %}

{{ error | escape }}

{% endfor %} -

{{ form.series_number }}

+

{{ form.series_number }}

{% for error in form.series_number.errors %}

{{ error | escape }}

{% endfor %} -

{{ form.first_published_date }}

+

{{ form.first_published_date }}

{% for error in form.first_published_date.errors %}

{{ error | escape }}

{% endfor %} -

{{ form.published_date }}

+

{{ form.published_date }}

{% for error in form.published_date.errors %}

{{ error | escape }}

{% endfor %} @@ -83,7 +85,7 @@

Physical Properties

-

{{ form.physical_format }}

+

{{ form.physical_format }}

{% for error in form.physical_format.errors %}

{{ error | escape }}

{% endfor %} @@ -91,7 +93,7 @@

{{ error | escape }}

{% endfor %} -

{{ form.pages }}

+

{{ form.pages }}

{% for error in form.pages.errors %}

{{ error | escape }}

{% endfor %} @@ -99,23 +101,23 @@

Book Identifiers

-

{{ form.isbn_13 }}

+

{{ form.isbn_13 }}

{% for error in form.isbn_13.errors %}

{{ error | escape }}

{% endfor %} -

{{ form.isbn_10 }}

+

{{ form.isbn_10 }}

{% for error in form.isbn_10.errors %}

{{ error | escape }}

{% endfor %} -

{{ form.openlibrary_key }}

+

{{ form.openlibrary_key }}

{% for error in form.openlibrary_key.errors %}

{{ error | escape }}

{% endfor %} -

{{ form.librarything_key }}

+

{{ form.librarything_key }}

{% for error in form.librarything_key.errors %}

{{ error | escape }}

{% endfor %} -

{{ form.goodreads_key }}

+

{{ form.goodreads_key }}

{% for error in form.goodreads_key.errors %}

{{ error | escape }}

{% endfor %} diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index e6c3f79f..22edd38a 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -76,6 +76,7 @@ urlpatterns = [ # books re_path(r'%s(.json)?/?$' % book_path, views.book_page), re_path(r'%s/edit/?$' % book_path, views.edit_book_page), + re_path(r'^author/(?P[\w\-]+)/edit/?$', views.edit_author_page), re_path(r'%s/editions(.json)?/?$' % book_path, views.editions_page), re_path(r'^author/(?P[\w\-]+)(.json)?/?$', views.author_page), @@ -104,6 +105,7 @@ urlpatterns = [ re_path(r'^edit-book/(?P\d+)/?$', actions.edit_book), re_path(r'^upload-cover/(?P\d+)/?$', actions.upload_cover), re_path(r'^add-description/(?P\d+)/?$', actions.add_description), + re_path(r'^edit-author/(?P\d+)/?$', actions.edit_author), re_path(r'^switch-edition/?$', actions.switch_edition), re_path(r'^edit-readthrough/?$', actions.edit_readthrough), diff --git a/bookwyrm/view_actions.py b/bookwyrm/view_actions.py index 7939cab1..9f834eb1 100644 --- a/bookwyrm/view_actions.py +++ b/bookwyrm/view_actions.py @@ -244,7 +244,7 @@ def edit_book(request, book_id): return TemplateResponse(request, 'edit_book.html', data) book = form.save() - outgoing.handle_update_book(request.user, book) + outgoing.handle_update_book_data(request.user, book) return redirect('/book/%s' % book.id) @@ -291,7 +291,7 @@ def upload_cover(request, book_id): book.cover = form.files['cover'] book.save() - outgoing.handle_update_book(request.user, book) + outgoing.handle_update_book_data(request.user, book) return redirect('/book/%s' % book.id) @@ -310,10 +310,31 @@ def add_description(request, book_id): book.description = description book.save() - outgoing.handle_update_book(request.user, book) + outgoing.handle_update_book_data(request.user, book) return redirect('/book/%s' % book.id) +@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() + + outgoing.handle_update_book_data(request.user, author) + return redirect('/author/%s' % author.id) + + @login_required @require_POST def create_shelf(request): diff --git a/bookwyrm/views.py b/bookwyrm/views.py index 8e40f362..1afb8266 100644 --- a/bookwyrm/views.py +++ b/bookwyrm/views.py @@ -657,6 +657,20 @@ def edit_book_page(request, book_id): return TemplateResponse(request, 'edit_book.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 editions_page(request, book_id): ''' list of editions of a book ''' From 7d1cbb7be185c1832cf142c94da27696e840361c Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 22 Dec 2020 10:10:36 -0800 Subject: [PATCH 12/22] Adds tests for edit author view --- bookwyrm/tests/test_view_actions.py | 56 +++++++++++++++++++++++++++-- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/bookwyrm/tests/test_view_actions.py b/bookwyrm/tests/test_view_actions.py index c1e6f860..c846c248 100644 --- a/bookwyrm/tests/test_view_actions.py +++ b/bookwyrm/tests/test_view_actions.py @@ -2,6 +2,8 @@ from unittest.mock import patch from django.core.exceptions import PermissionDenied +from django.contrib.auth.models import Group, Permission +from django.contrib.contenttypes.models import ContentType from django.http.response import Http404 from django.test import TestCase from django.test.client import RequestFactory @@ -19,6 +21,13 @@ class ViewActions(TestCase): 'mouse', 'mouse@mouse.com', 'mouseword', local=True) self.local_user.remote_id = 'https://example.com/user/mouse' self.local_user.save() + 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 + ) with patch('bookwyrm.models.user.set_remote_server.delay'): self.remote_user = models.User.objects.create_user( 'rat', 'rat@rat.com', 'ratword', @@ -248,7 +257,7 @@ class ViewActions(TestCase): }) request.user = self.local_user with patch('bookwyrm.view_actions.login'): - resp = actions.password_change(request) + actions.password_change(request) self.assertNotEqual(self.local_user.password, password_hash) def test_password_change_mismatch(self): @@ -259,7 +268,7 @@ class ViewActions(TestCase): 'confirm-password': 'hihi' }) request.user = self.local_user - resp = actions.password_change(request) + actions.password_change(request) self.assertEqual(self.local_user.password, password_hash) @@ -299,3 +308,46 @@ class ViewActions(TestCase): self.assertEqual(models.ShelfBook.objects.get().book, edition2) self.assertEqual(models.ReadThrough.objects.get().book, edition2) + + + def test_edit_author(self): + ''' edit an author ''' + author = models.Author.objects.create(name='Test Author') + self.local_user.groups.add(self.group) + form = forms.AuthorForm(instance=author) + form.data['name'] = 'New Name' + form.data['last_edited_by'] = self.local_user.id + request = self.factory.post('', form.data) + request.user = self.local_user + with patch('bookwyrm.broadcast.broadcast_task.delay'): + actions.edit_author(request, author.id) + author.refresh_from_db() + self.assertEqual(author.name, 'New Name') + self.assertEqual(author.last_edited_by, self.local_user) + + def test_edit_author_non_editor(self): + ''' edit an author with invalid post data''' + author = models.Author.objects.create(name='Test Author') + form = forms.AuthorForm(instance=author) + form.data['name'] = 'New Name' + form.data['last_edited_by'] = self.local_user.id + request = self.factory.post('', form.data) + request.user = self.local_user + with self.assertRaises(PermissionDenied): + actions.edit_author(request, author.id) + author.refresh_from_db() + self.assertEqual(author.name, 'Test Author') + + def test_edit_author_invalid_form(self): + ''' edit an author with invalid post data''' + author = models.Author.objects.create(name='Test Author') + self.local_user.groups.add(self.group) + form = forms.AuthorForm(instance=author) + form.data['name'] = '' + form.data['last_edited_by'] = self.local_user.id + request = self.factory.post('', form.data) + request.user = self.local_user + resp = actions.edit_author(request, author.id) + author.refresh_from_db() + self.assertEqual(author.name, 'Test Author') + self.assertEqual(resp.template_name, 'edit_author.html') From f2f2f3dd3ee72854cc3ce889cc5456eee4b040f6 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 22 Dec 2020 10:12:41 -0800 Subject: [PATCH 13/22] Remove useless if/else on null state radio button --- bookwyrm/templates/feed.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bookwyrm/templates/feed.html b/bookwyrm/templates/feed.html index 40e6dad1..f02219bc 100644 --- a/bookwyrm/templates/feed.html +++ b/bookwyrm/templates/feed.html @@ -63,7 +63,9 @@ {% endfor %} {% endwith %} {% endfor %} - +
+ +
{% endif %}
From 7c3f2373c7373b0a9931a92c6352b77267c481dd Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 22 Dec 2020 10:19:01 -0800 Subject: [PATCH 14/22] Adds noopener to link --- bookwyrm/templates/author.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/templates/author.html b/bookwyrm/templates/author.html index 5e8aab71..e51ef302 100644 --- a/bookwyrm/templates/author.html +++ b/bookwyrm/templates/author.html @@ -25,7 +25,7 @@

{% endif %} {% if author.wikipedia_link %} -

Wikipedia

+

Wikipedia

{% endif %}
From e7e90360b3218eadc42035652e220d261465772f Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 22 Dec 2020 10:28:04 -0800 Subject: [PATCH 15/22] Adds model import --- bookwyrm/models/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/models/__init__.py b/bookwyrm/models/__init__.py index 86bdf219..0c3bf33e 100644 --- a/bookwyrm/models/__init__.py +++ b/bookwyrm/models/__init__.py @@ -2,7 +2,7 @@ import inspect import sys -from .book import Book, Work, Edition +from .book import Book, Work, Edition, BookDataModel from .author import Author from .connector import Connector From a3df0847e1e4a7ce929e4abef8be49354da67965 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 23 Dec 2020 12:45:40 -0800 Subject: [PATCH 16/22] Fixes celery tasks expanding data not setting many to many fields --- bookwyrm/activitypub/base_activity.py | 36 ++++++++++++--------------- bookwyrm/models/book.py | 2 -- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index b9facf2f..ff057a32 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -111,15 +111,10 @@ class ActivityObject: continue model_field = getattr(model, model_field_name) - try: - # this is for one to many - related_model = model_field.field.model - related_field_name = model_field.field.name - except AttributeError: - # it's a one to one or foreign key - related_model = model_field.related.related_model - related_field_name = model_field.related.related_name - values = [values] + # creating a Work, model_field is 'editions' + # creating a User, model field is 'key_pair' + related_model = model_field.field.model + related_field_name = model_field.field.activitypub_field for item in values: set_related_field.delay( @@ -153,23 +148,24 @@ def set_related_field( with transaction.atomic(): if isinstance(data, str): - item = resolve_remote_id(model, data, save=False) - else: - # look for a match based on all the available data - item = model.find_existing(data) - if not item: - # create a new model instance - item = model.activity_serializer(**data) - item = item.to_model(model, save=False) + existing = model.find_existing_by_remote_id(data) + if existing: + data = existing.to_activity() + else: + data = get_data(data) + activity = model.activity_serializer(**data) + # this must exist because it's the object that triggered this function instance = origin_model.find_existing_by_remote_id(related_remote_id) if not instance: raise ValueError( 'Invalid related remote id: %s' % related_remote_id) - # edition.parent_work = instance, for example - setattr(item, related_field_name, instance) - item.save() + # set the origin's remote id on the activity so it will be there when + # the model instance is created + # edition.parentWork = instance, for example + setattr(activity, related_field_name, instance.remote_id) + activity.to_model(model) def resolve_remote_id(model, remote_id, refresh=False, save=True): diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index 7e45eacd..1e1d8d20 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -59,9 +59,7 @@ class Book(BookDataModel): subject_places = fields.ArrayField( models.CharField(max_length=255), blank=True, null=True, default=list ) - # TODO: include an annotation about the type of authorship (ie, translator) authors = fields.ManyToManyField('Author') - # preformatted authorship string for search and easier display cover = fields.ImageField( upload_to='covers/', blank=True, null=True, alt_field='alt_text') first_published_date = fields.DateTimeField(blank=True, null=True) From 365408e86b32f1eb53abdfccd500742b1fa22d86 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 23 Dec 2020 13:33:46 -0800 Subject: [PATCH 17/22] Handle reverse serialized fields with no serialized field --- bookwyrm/activitypub/base_activity.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index ff057a32..5edf3115 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -114,7 +114,7 @@ class ActivityObject: # creating a Work, model_field is 'editions' # creating a User, model field is 'key_pair' related_model = model_field.field.model - related_field_name = model_field.field.activitypub_field + related_field_name = model_field.field.name for item in values: set_related_field.delay( @@ -137,8 +137,8 @@ class ActivityObject: @app.task @transaction.atomic def set_related_field( - model_name, origin_model_name, - related_field_name, related_remote_id, data): + model_name, origin_model_name, related_field_name, + related_remote_id, data): ''' load reverse related fields (editions, attachments) without blocking ''' model = apps.get_model('bookwyrm.%s' % model_name, require_ready=True) origin_model = apps.get_model( @@ -164,8 +164,19 @@ def set_related_field( # set the origin's remote id on the activity so it will be there when # the model instance is created # edition.parentWork = instance, for example - setattr(activity, related_field_name, instance.remote_id) - activity.to_model(model) + model_field = getattr(model, related_field_name) + if hasattr(model_field, 'activitypub_field'): + setattr( + activity, + getattr(model_field, 'activitypub_field'), + instance.remote_id + ) + item = activity.to_model(model) + + # if the related field isn't serialized (attachments on Status), then + # we have to set it post-creation + if not hasattr(model_field, 'activitypub_field'): + setattr(item, related_field_name, instance) def resolve_remote_id(model, remote_id, refresh=False, save=True): From 25d72c5d1e9d9bd1d01c1280c6bfb4d1c058a15e Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 23 Dec 2020 13:38:36 -0800 Subject: [PATCH 18/22] Save attachments on incoming statuses --- bookwyrm/activitypub/base_activity.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index 5edf3115..7ef0920f 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -177,6 +177,7 @@ def set_related_field( # we have to set it post-creation if not hasattr(model_field, 'activitypub_field'): setattr(item, related_field_name, instance) + item.save() def resolve_remote_id(model, remote_id, refresh=False, save=True): From 313e389fc678e935fddee9646fea901b110b1452 Mon Sep 17 00:00:00 2001 From: Andrew Radev Date: Thu, 24 Dec 2020 12:56:22 +0200 Subject: [PATCH 19/22] Fix typo in Patreon link --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index cfbe0524..5662d1d5 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,7 +1,7 @@ # These are supported funding model platforms github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] -patreon: bookwrym +patreon: bookwyrm open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel From 9f74e95b00c07e2f4418870d26a8da6b79ab1a8b Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 27 Dec 2020 13:32:27 -0800 Subject: [PATCH 20/22] stylistic cleanup of import model tests --- bookwyrm/tests/models/test_import_model.py | 28 ++++++++++++---------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/bookwyrm/tests/models/test_import_model.py b/bookwyrm/tests/models/test_import_model.py index c703d08a..4833ed16 100644 --- a/bookwyrm/tests/models/test_import_model.py +++ b/bookwyrm/tests/models/test_import_model.py @@ -54,11 +54,11 @@ class ImportJob(TestCase): user = models.User.objects.create_user( 'mouse', 'mouse@mouse.mouse', 'mouseword', local=True) job = models.ImportJob.objects.create(user=user) - models.ImportItem.objects.create( + self.item_1 = models.ImportItem.objects.create( job=job, index=1, data=currently_reading_data) - models.ImportItem.objects.create( + self.item_2 = models.ImportItem.objects.create( job=job, index=2, data=read_data) - models.ImportItem.objects.create( + self.item_3 = models.ImportItem.objects.create( job=job, index=3, data=unknown_read_data) @@ -72,8 +72,7 @@ class ImportJob(TestCase): def test_shelf(self): ''' converts to the local shelf typology ''' expected = 'reading' - item = models.ImportItem.objects.get(index=1) - self.assertEqual(item.shelf, expected) + self.assertEqual(self.item_1.shelf, expected) def test_date_added(self): @@ -91,21 +90,26 @@ class ImportJob(TestCase): def test_currently_reading_reads(self): + ''' infer currently reading dates where available ''' expected = [models.ReadThrough( - start_date=datetime.datetime(2019, 4, 9, 0, 0, tzinfo=timezone.utc))] + start_date=datetime.datetime(2019, 4, 9, 0, 0, tzinfo=timezone.utc) + )] actual = models.ImportItem.objects.get(index=1) self.assertEqual(actual.reads[0].start_date, expected[0].start_date) self.assertEqual(actual.reads[0].finish_date, expected[0].finish_date) def test_read_reads(self): - actual = models.ImportItem.objects.get(index=2) - self.assertEqual(actual.reads[0].start_date, datetime.datetime(2019, 4, 9, 0, 0, tzinfo=timezone.utc)) - self.assertEqual(actual.reads[0].finish_date, datetime.datetime(2019, 4, 12, 0, 0, tzinfo=timezone.utc)) + ''' infer read dates where available ''' + actual = self.item_2 + self.assertEqual( + actual.reads[0].start_date, + datetime.datetime(2019, 4, 9, 0, 0, tzinfo=timezone.utc)) + self.assertEqual( + actual.reads[0].finish_date, + datetime.datetime(2019, 4, 12, 0, 0, tzinfo=timezone.utc)) def test_unread_reads(self): + ''' handle books with no read dates ''' expected = [] actual = models.ImportItem.objects.get(index=3) self.assertEqual(actual.reads, expected) - - - From 97a5364b70df6361a41116b7ad0663e04f270628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anton=20Str=C3=B6mkvist?= Date: Sun, 27 Dec 2020 23:29:43 +0100 Subject: [PATCH 21/22] Fix docstring for to_reject_activity --- bookwyrm/models/relationship.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/models/relationship.py b/bookwyrm/models/relationship.py index debe2ace..0f3c1dab 100644 --- a/bookwyrm/models/relationship.py +++ b/bookwyrm/models/relationship.py @@ -54,7 +54,7 @@ class UserRelationship(ActivitypubMixin, BookWyrmModel): def to_reject_activity(self): - ''' generate an Accept for this follow request ''' + ''' generate a Reject for this follow request ''' return activitypub.Reject( id=self.get_remote_id(status='rejects'), actor=self.user_object.remote_id, From ac261d7b1a641952978855f2717dff4688b3f521 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 27 Dec 2020 14:27:18 -0800 Subject: [PATCH 22/22] Send connector with search result also fix typo in get_work_from_edition_data function --- bookwyrm/connectors/abstract_connector.py | 3 +- bookwyrm/connectors/bookwyrm_connector.py | 1 + bookwyrm/connectors/openlibrary.py | 3 +- bookwyrm/connectors/self_connector.py | 9 +--- bookwyrm/models/import_job.py | 4 +- bookwyrm/tests/models/test_import_model.py | 57 +++++++++++++++++++++- 6 files changed, 65 insertions(+), 12 deletions(-) diff --git a/bookwyrm/connectors/abstract_connector.py b/bookwyrm/connectors/abstract_connector.py index 86ac7435..ce1184d8 100644 --- a/bookwyrm/connectors/abstract_connector.py +++ b/bookwyrm/connectors/abstract_connector.py @@ -168,7 +168,7 @@ class AbstractConnector(AbstractMinimalConnector): ''' every work needs at least one edition ''' @abstractmethod - def get_work_from_edition_date(self, data): + def get_work_from_edition_data(self, data): ''' every edition needs a work ''' @abstractmethod @@ -228,6 +228,7 @@ class SearchResult: key: str author: str year: str + connector: object confidence: int = 1 def __repr__(self): diff --git a/bookwyrm/connectors/bookwyrm_connector.py b/bookwyrm/connectors/bookwyrm_connector.py index e4d32fd3..3c6f4614 100644 --- a/bookwyrm/connectors/bookwyrm_connector.py +++ b/bookwyrm/connectors/bookwyrm_connector.py @@ -13,4 +13,5 @@ class Connector(AbstractMinimalConnector): return data def format_search_result(self, search_result): + search_result['connector'] = self return SearchResult(**search_result) diff --git a/bookwyrm/connectors/openlibrary.py b/bookwyrm/connectors/openlibrary.py index 3b60c307..c59829d6 100644 --- a/bookwyrm/connectors/openlibrary.py +++ b/bookwyrm/connectors/openlibrary.py @@ -85,7 +85,7 @@ class Connector(AbstractConnector): return pick_default_edition(data['entries']) - def get_work_from_edition_date(self, data): + def get_work_from_edition_data(self, data): try: key = data['works'][0]['key'] except (IndexError, KeyError): @@ -123,6 +123,7 @@ class Connector(AbstractConnector): title=search_result.get('title'), key=key, author=', '.join(author), + connector=self, year=search_result.get('first_publish_year'), ) diff --git a/bookwyrm/connectors/self_connector.py b/bookwyrm/connectors/self_connector.py index 8d31c8a1..cad98249 100644 --- a/bookwyrm/connectors/self_connector.py +++ b/bookwyrm/connectors/self_connector.py @@ -51,28 +51,23 @@ class Connector(AbstractConnector): author=search_result.author_text, year=search_result.published_date.year if \ search_result.published_date else None, + connector=self, confidence=search_result.rank, ) - def get_remote_id_from_data(self, data): - pass - def is_work_data(self, data): pass def get_edition_from_work_data(self, data): pass - def get_work_from_edition_date(self, data): + def get_work_from_edition_data(self, data): pass def get_authors_from_data(self, data): return None - def get_cover_from_data(self, data): - return None - def parse_search_data(self, data): ''' it's already in the right format, don't even worry about it ''' return data diff --git a/bookwyrm/models/import_job.py b/bookwyrm/models/import_job.py index 835094cd..576dd07d 100644 --- a/bookwyrm/models/import_job.py +++ b/bookwyrm/models/import_job.py @@ -76,7 +76,7 @@ class ImportItem(models.Model): ) if search_result: # raises ConnectorException - return books_manager.get_or_create_book(search_result.key) + return search_result.connector.get_or_create_book(search_result.key) return None @@ -91,7 +91,7 @@ class ImportItem(models.Model): ) if search_result: # raises ConnectorException - return books_manager.get_or_create_book(search_result.key) + return search_result.connector.get_or_create_book(search_result.key) return None diff --git a/bookwyrm/tests/models/test_import_model.py b/bookwyrm/tests/models/test_import_model.py index 4833ed16..d393d968 100644 --- a/bookwyrm/tests/models/test_import_model.py +++ b/bookwyrm/tests/models/test_import_model.py @@ -1,9 +1,15 @@ ''' testing models ''' import datetime +import json +import pathlib +from unittest.mock import patch + from django.utils import timezone from django.test import TestCase +import responses -from bookwyrm import models +from bookwyrm import books_manager, models +from bookwyrm.connectors.abstract_connector import SearchResult class ImportJob(TestCase): @@ -113,3 +119,52 @@ class ImportJob(TestCase): expected = [] actual = models.ImportItem.objects.get(index=3) self.assertEqual(actual.reads, expected) + + + @responses.activate + def test_get_book_from_isbn(self): + ''' search and load books by isbn (9780356506999) ''' + connector_info = models.Connector.objects.create( + identifier='openlibrary.org', + name='OpenLibrary', + connector_file='openlibrary', + base_url='https://openlibrary.org', + books_url='https://openlibrary.org', + covers_url='https://covers.openlibrary.org', + search_url='https://openlibrary.org/search?q=', + priority=3, + ) + connector = books_manager.load_connector(connector_info) + result = SearchResult( + title='Test Result', + key='https://openlibrary.org/works/OL1234W', + author='An Author', + year='1980', + connector=connector, + ) + + + datafile = pathlib.Path(__file__).parent.joinpath( + '../data/ol_edition.json') + bookdata = json.loads(datafile.read_bytes()) + responses.add( + responses.GET, + 'https://openlibrary.org/works/OL1234W', + json=bookdata, + status=200) + responses.add( + responses.GET, + 'https://openlibrary.org//works/OL15832982W', + json=bookdata, + status=200) + responses.add( + responses.GET, + 'https://openlibrary.org//authors/OL382982A.json', + json={'name': 'test author'}, + status=200) + + with patch('bookwyrm.books_manager.first_search_result') as search: + search.return_value = result + book = self.item_1.get_book_from_isbn() + + self.assertEqual(book.title, 'Sabriel')