From cd65ac72902470ca0bdddfe7c2cdc9228c044488 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 30 Jan 2021 11:46:15 -0800 Subject: [PATCH 01/69] Fixes incorrect tempalte path in threaded status page --- bookwyrm/templates/feed/thread.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bookwyrm/templates/feed/thread.html b/bookwyrm/templates/feed/thread.html index 589702ce6..aa67d5bb5 100644 --- a/bookwyrm/templates/feed/thread.html +++ b/bookwyrm/templates/feed/thread.html @@ -4,7 +4,7 @@ {% with depth=depth|add:1 %} {% if depth <= max_depth and status.reply_parent and direction <= 0 %} {% with direction=-1 %} - {% include 'snippets/thread.html' with status=status|parent is_root=False %} + {% include 'feed/thread.html' with status=status|parent is_root=False %} {% endwith %} {% endif %} @@ -13,7 +13,7 @@ {% if depth <= max_depth and direction >= 0 %} {% for reply in status|replies %} {% with direction=1 %} - {% include 'snippets/thread.html' with status=reply is_root=False %} + {% include 'feed/thread.html' with status=reply is_root=False %} {% endwith %} {% endfor %} {% endif %} From ac4a178e8319e45388b9e1e0afefb67f5c8b749f Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 30 Jan 2021 12:03:50 -0800 Subject: [PATCH 02/69] Fixes shelf page template path bug --- bookwyrm/templates/{ => user}/shelf.html | 28 +++++++++++------------- bookwyrm/tests/views/test_shelf.py | 2 +- bookwyrm/views/shelf.py | 2 +- 3 files changed, 15 insertions(+), 17 deletions(-) rename bookwyrm/templates/{ => user}/shelf.html (89%) diff --git a/bookwyrm/templates/shelf.html b/bookwyrm/templates/user/shelf.html similarity index 89% rename from bookwyrm/templates/shelf.html rename to bookwyrm/templates/user/shelf.html index da599c7d2..c0083e176 100644 --- a/bookwyrm/templates/shelf.html +++ b/bookwyrm/templates/user/shelf.html @@ -1,21 +1,19 @@ -{% extends 'layout.html' %} +{% extends 'user/user_layout.html' %} {% load bookwyrm_tags %} -{% block content %} -
-
-

- {% if is_self %}Your - {% else %} - {% include 'snippets/username.html' with user=user possessive=True %} - {% endif %} - shelves -

-
-
- -{% include 'snippets/user_header.html' with user=user %} +{% block header %} +
+

+ {% if is_self %}Your + {% else %} + {% include 'snippets/username.html' with user=user possessive=True %} + {% endif %} + shelves +

+
+{% endblock %} +{% block panel %}
diff --git a/bookwyrm/tests/views/test_shelf.py b/bookwyrm/tests/views/test_shelf.py index 35e07953e..95150fe9d 100644 --- a/bookwyrm/tests/views/test_shelf.py +++ b/bookwyrm/tests/views/test_shelf.py @@ -41,7 +41,7 @@ class ShelfViews(TestCase): is_api.return_value = False result = view(request, self.local_user.username, shelf.identifier) self.assertIsInstance(result, TemplateResponse) - self.assertEqual(result.template_name, 'shelf.html') + self.assertEqual(result.template_name, 'user/shelf.html') self.assertEqual(result.status_code, 200) with patch('bookwyrm.views.shelf.is_api_request') as is_api: diff --git a/bookwyrm/views/shelf.py b/bookwyrm/views/shelf.py index ae8a494ba..e4c492615 100644 --- a/bookwyrm/views/shelf.py +++ b/bookwyrm/views/shelf.py @@ -62,7 +62,7 @@ class Shelf(View): 'books': [b.book for b in books], } - return TemplateResponse(request, 'shelf.html', data) + return TemplateResponse(request, 'user/shelf.html', data) @method_decorator(login_required, name='dispatch') # pylint: disable=unused-argument From b3bd6822b20423ed018a1d3b9d675d348e08132d Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 30 Jan 2021 12:16:57 -0800 Subject: [PATCH 03/69] Make sure templates render rather than checking template paths --- bookwyrm/tests/views/test_authentication.py | 10 +++++----- bookwyrm/tests/views/test_author.py | 10 +++++++--- bookwyrm/tests/views/test_block.py | 3 ++- bookwyrm/tests/views/test_book.py | 7 ++++--- bookwyrm/tests/views/test_federation.py | 3 ++- bookwyrm/tests/views/test_feed.py | 9 +++++---- bookwyrm/tests/views/test_import.py | 5 +++-- bookwyrm/tests/views/test_invite.py | 5 +++-- bookwyrm/tests/views/test_landing.py | 9 ++++----- bookwyrm/tests/views/test_notifications.py | 3 ++- bookwyrm/tests/views/test_password.py | 13 +++++++------ bookwyrm/tests/views/test_search.py | 5 +++-- bookwyrm/tests/views/test_shelf.py | 3 ++- bookwyrm/tests/views/test_tag.py | 3 ++- bookwyrm/tests/views/test_user.py | 9 +++++---- 15 files changed, 56 insertions(+), 41 deletions(-) diff --git a/bookwyrm/tests/views/test_authentication.py b/bookwyrm/tests/views/test_authentication.py index 655772084..dc52719c9 100644 --- a/bookwyrm/tests/views/test_authentication.py +++ b/bookwyrm/tests/views/test_authentication.py @@ -33,7 +33,7 @@ class AuthenticationViews(TestCase): result = login(request) self.assertIsInstance(result, TemplateResponse) - self.assertEqual(result.template_name, 'login.html') + result.render() self.assertEqual(result.status_code, 200) request.user = self.local_user @@ -94,7 +94,7 @@ class AuthenticationViews(TestCase): }) response = view(request) self.assertEqual(models.User.objects.count(), 1) - self.assertEqual(response.template_name, 'login.html') + response.render() def test_register_invalid_username(self): ''' gotta have an email ''' @@ -109,7 +109,7 @@ class AuthenticationViews(TestCase): }) response = view(request) self.assertEqual(models.User.objects.count(), 1) - self.assertEqual(response.template_name, 'login.html') + response.render() request = self.factory.post( 'register/', @@ -120,7 +120,7 @@ class AuthenticationViews(TestCase): }) response = view(request) self.assertEqual(models.User.objects.count(), 1) - self.assertEqual(response.template_name, 'login.html') + response.render() request = self.factory.post( 'register/', @@ -131,7 +131,7 @@ class AuthenticationViews(TestCase): }) response = view(request) self.assertEqual(models.User.objects.count(), 1) - self.assertEqual(response.template_name, 'login.html') + response.render() def test_register_closed_instance(self): diff --git a/bookwyrm/tests/views/test_author.py b/bookwyrm/tests/views/test_author.py index c00972f39..c92c47501 100644 --- a/bookwyrm/tests/views/test_author.py +++ b/bookwyrm/tests/views/test_author.py @@ -34,6 +34,7 @@ class AuthorViews(TestCase): remote_id='https://example.com/book/1', parent_work=self.work ) + models.SiteSettings.objects.create() def test_author_page(self): @@ -45,7 +46,8 @@ class AuthorViews(TestCase): is_api.return_value = False result = view(request, author.id) self.assertIsInstance(result, TemplateResponse) - self.assertEqual(result.template_name, 'author.html') + result.render() + self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200) request = self.factory.get('') @@ -66,7 +68,8 @@ class AuthorViews(TestCase): result = view(request, author.id) self.assertIsInstance(result, TemplateResponse) - self.assertEqual(result.template_name, 'edit_author.html') + result.render() + self.assertEqual(result.status_code, 200) self.assertEqual(result.status_code, 200) @@ -116,4 +119,5 @@ class AuthorViews(TestCase): resp = view(request, author.id) author.refresh_from_db() self.assertEqual(author.name, 'Test Author') - self.assertEqual(resp.template_name, 'edit_author.html') + resp.render() + self.assertEqual(resp.status_code, 200) diff --git a/bookwyrm/tests/views/test_block.py b/bookwyrm/tests/views/test_block.py index f3a0c2f8b..6f85f2822 100644 --- a/bookwyrm/tests/views/test_block.py +++ b/bookwyrm/tests/views/test_block.py @@ -23,6 +23,7 @@ class BlockViews(TestCase): inbox='https://example.com/users/rat/inbox', outbox='https://example.com/users/rat/outbox', ) + models.SiteSettings.objects.create() def test_block_get(self): @@ -32,7 +33,7 @@ class BlockViews(TestCase): request.user = self.local_user result = view(request) self.assertIsInstance(result, TemplateResponse) - self.assertEqual(result.template_name, 'preferences/blocks.html') + result.render() self.assertEqual(result.status_code, 200) def test_block_post(self): diff --git a/bookwyrm/tests/views/test_book.py b/bookwyrm/tests/views/test_book.py index 8306b8037..32a407d60 100644 --- a/bookwyrm/tests/views/test_book.py +++ b/bookwyrm/tests/views/test_book.py @@ -33,6 +33,7 @@ class BookViews(TestCase): remote_id='https://example.com/book/1', parent_work=self.work ) + models.SiteSettings.objects.create() def test_book_page(self): @@ -44,7 +45,7 @@ class BookViews(TestCase): is_api.return_value = False result = view(request, self.book.id) self.assertIsInstance(result, TemplateResponse) - self.assertEqual(result.template_name, 'book.html') + result.render() self.assertEqual(result.status_code, 200) request = self.factory.get('') @@ -63,7 +64,7 @@ class BookViews(TestCase): request.user.is_superuser = True result = view(request, self.book.id) self.assertIsInstance(result, TemplateResponse) - self.assertEqual(result.template_name, 'edit_book.html') + result.render() self.assertEqual(result.status_code, 200) @@ -116,7 +117,7 @@ class BookViews(TestCase): is_api.return_value = False result = view(request, self.work.id) self.assertIsInstance(result, TemplateResponse) - self.assertEqual(result.template_name, 'editions.html') + result.render() self.assertEqual(result.status_code, 200) request = self.factory.get('') diff --git a/bookwyrm/tests/views/test_federation.py b/bookwyrm/tests/views/test_federation.py index 2a182a21d..70cf41f6e 100644 --- a/bookwyrm/tests/views/test_federation.py +++ b/bookwyrm/tests/views/test_federation.py @@ -15,6 +15,7 @@ class FederationViews(TestCase): self.local_user = models.User.objects.create_user( 'mouse@local.com', 'mouse@mouse.mouse', 'password', local=True, localname='mouse') + models.SiteSettings.objects.create() def test_federation_page(self): @@ -25,5 +26,5 @@ class FederationViews(TestCase): request.user.is_superuser = True result = view(request) self.assertIsInstance(result, TemplateResponse) - self.assertEqual(result.template_name, 'settings/federation.html') + result.render() self.assertEqual(result.status_code, 200) diff --git a/bookwyrm/tests/views/test_feed.py b/bookwyrm/tests/views/test_feed.py index da597a7cf..879dd2b69 100644 --- a/bookwyrm/tests/views/test_feed.py +++ b/bookwyrm/tests/views/test_feed.py @@ -21,6 +21,7 @@ class FeedMessageViews(TestCase): title='Example Edition', remote_id='https://example.com/book/1', ) + models.SiteSettings.objects.create() def test_feed(self): @@ -30,7 +31,7 @@ class FeedMessageViews(TestCase): request.user = self.local_user result = view(request, 'local') self.assertIsInstance(result, TemplateResponse) - self.assertEqual(result.template_name, 'feed/feed.html') + result.render() self.assertEqual(result.status_code, 200) @@ -45,7 +46,7 @@ class FeedMessageViews(TestCase): is_api.return_value = False result = view(request, 'mouse', status.id) self.assertIsInstance(result, TemplateResponse) - self.assertEqual(result.template_name, 'feed/status.html') + result.render() self.assertEqual(result.status_code, 200) with patch('bookwyrm.views.feed.is_api_request') as is_api: @@ -66,7 +67,7 @@ class FeedMessageViews(TestCase): is_api.return_value = False result = view(request, 'mouse', status.id) self.assertIsInstance(result, TemplateResponse) - self.assertEqual(result.template_name, 'feed/status.html') + result.render() self.assertEqual(result.status_code, 200) with patch('bookwyrm.views.feed.is_api_request') as is_api: @@ -83,7 +84,7 @@ class FeedMessageViews(TestCase): request.user = self.local_user result = view(request) self.assertIsInstance(result, TemplateResponse) - self.assertEqual(result.template_name, 'feed/direct_messages.html') + result.render() self.assertEqual(result.status_code, 200) diff --git a/bookwyrm/tests/views/test_import.py b/bookwyrm/tests/views/test_import.py index 14209f24b..ba8f2457b 100644 --- a/bookwyrm/tests/views/test_import.py +++ b/bookwyrm/tests/views/test_import.py @@ -16,6 +16,7 @@ class ImportViews(TestCase): self.local_user = models.User.objects.create_user( 'mouse@local.com', 'mouse@mouse.mouse', 'password', local=True, localname='mouse') + models.SiteSettings.objects.create() def test_import_page(self): @@ -25,7 +26,7 @@ class ImportViews(TestCase): request.user = self.local_user result = view(request) self.assertIsInstance(result, TemplateResponse) - self.assertEqual(result.template_name, 'import.html') + result.render() self.assertEqual(result.status_code, 200) @@ -39,5 +40,5 @@ class ImportViews(TestCase): async_result.return_value = [] result = view(request, import_job.id) self.assertIsInstance(result, TemplateResponse) - self.assertEqual(result.template_name, 'import_status.html') + result.render() self.assertEqual(result.status_code, 200) diff --git a/bookwyrm/tests/views/test_invite.py b/bookwyrm/tests/views/test_invite.py index 857416881..e93e7209b 100644 --- a/bookwyrm/tests/views/test_invite.py +++ b/bookwyrm/tests/views/test_invite.py @@ -18,6 +18,7 @@ class InviteViews(TestCase): self.local_user = models.User.objects.create_user( 'mouse@local.com', 'mouse@mouse.mouse', 'password', local=True, localname='mouse') + models.SiteSettings.objects.create() def test_invite_page(self): @@ -32,7 +33,7 @@ class InviteViews(TestCase): invite.return_value = True result = view(request, 'hi') self.assertIsInstance(result, TemplateResponse) - self.assertEqual(result.template_name, 'invite.html') + result.render() self.assertEqual(result.status_code, 200) @@ -44,5 +45,5 @@ class InviteViews(TestCase): request.user.is_superuser = True result = view(request) self.assertIsInstance(result, TemplateResponse) - self.assertEqual(result.template_name, 'settings/manage_invites.html') + result.render() self.assertEqual(result.status_code, 200) diff --git a/bookwyrm/tests/views/test_landing.py b/bookwyrm/tests/views/test_landing.py index 8576af496..5e0e50cff 100644 --- a/bookwyrm/tests/views/test_landing.py +++ b/bookwyrm/tests/views/test_landing.py @@ -18,6 +18,7 @@ class LandingViews(TestCase): local=True, localname='mouse') self.anonymous_user = AnonymousUser self.anonymous_user.is_authenticated = False + models.SiteSettings.objects.create() def test_home_page(self): @@ -27,13 +28,13 @@ class LandingViews(TestCase): request.user = self.local_user result = view(request) self.assertEqual(result.status_code, 200) - self.assertEqual(result.template_name, 'feed/feed.html') + result.render() request.user = self.anonymous_user result = view(request) self.assertIsInstance(result, TemplateResponse) self.assertEqual(result.status_code, 200) - self.assertEqual(result.template_name, 'discover.html') + result.render() def test_about_page(self): @@ -43,7 +44,7 @@ class LandingViews(TestCase): request.user = self.local_user result = view(request) self.assertIsInstance(result, TemplateResponse) - self.assertEqual(result.template_name, 'about.html') + result.render() self.assertEqual(result.status_code, 200) @@ -53,5 +54,3 @@ class LandingViews(TestCase): request = self.factory.get('') result = view(request) self.assertIsInstance(result, TemplateResponse) - self.assertEqual(result.template_name, 'discover.html') - self.assertEqual(result.status_code, 200) diff --git a/bookwyrm/tests/views/test_notifications.py b/bookwyrm/tests/views/test_notifications.py index 683424d5a..24fbde1e6 100644 --- a/bookwyrm/tests/views/test_notifications.py +++ b/bookwyrm/tests/views/test_notifications.py @@ -15,6 +15,7 @@ class NotificationViews(TestCase): self.local_user = models.User.objects.create_user( 'mouse@local.com', 'mouse@mouse.mouse', 'password', local=True, localname='mouse') + models.SiteSettings.objects.create() def test_notifications_page(self): ''' there are so many views, this just makes sure it LOADS ''' @@ -23,7 +24,7 @@ class NotificationViews(TestCase): request.user = self.local_user result = view(request) self.assertIsInstance(result, TemplateResponse) - self.assertEqual(result.template_name, 'notifications.html') + result.render() self.assertEqual(result.status_code, 200) def test_clear_notifications(self): diff --git a/bookwyrm/tests/views/test_password.py b/bookwyrm/tests/views/test_password.py index 8cac2b13c..9fc37fdb9 100644 --- a/bookwyrm/tests/views/test_password.py +++ b/bookwyrm/tests/views/test_password.py @@ -19,6 +19,7 @@ class PasswordViews(TestCase): local=True, localname='mouse') self.anonymous_user = AnonymousUser self.anonymous_user.is_authenticated = False + models.SiteSettings.objects.create(id=1) def test_password_reset_request(self): @@ -29,7 +30,7 @@ class PasswordViews(TestCase): result = view(request) self.assertIsInstance(result, TemplateResponse) - self.assertEqual(result.template_name, 'password_reset_request.html') + result.render() self.assertEqual(result.status_code, 200) @@ -43,7 +44,7 @@ class PasswordViews(TestCase): request = self.factory.post('', {'email': 'mouse@mouse.com'}) with patch('bookwyrm.emailing.send_email.delay'): resp = view(request) - self.assertEqual(resp.template_name, 'password_reset_request.html') + resp.render() self.assertEqual( models.PasswordReset.objects.get().user, self.local_user) @@ -56,7 +57,7 @@ class PasswordViews(TestCase): request.user = self.anonymous_user result = view(request, code.code) self.assertIsInstance(result, TemplateResponse) - self.assertEqual(result.template_name, 'password_reset.html') + result.render() self.assertEqual(result.status_code, 200) @@ -82,7 +83,7 @@ class PasswordViews(TestCase): 'confirm-password': 'hi' }) resp = view(request, 'jhgdkfjgdf') - self.assertEqual(resp.template_name, 'password_reset.html') + resp.render() self.assertTrue(models.PasswordReset.objects.exists()) def test_password_reset_mismatch(self): @@ -94,7 +95,7 @@ class PasswordViews(TestCase): 'confirm-password': 'hihi' }) resp = view(request, code.code) - self.assertEqual(resp.template_name, 'password_reset.html') + resp.render() self.assertTrue(models.PasswordReset.objects.exists()) @@ -106,7 +107,7 @@ class PasswordViews(TestCase): result = view(request) self.assertIsInstance(result, TemplateResponse) - self.assertEqual(result.template_name, 'preferences/change_password.html') + result.render() self.assertEqual(result.status_code, 200) diff --git a/bookwyrm/tests/views/test_search.py b/bookwyrm/tests/views/test_search.py index 3f1d78503..e5cba99c9 100644 --- a/bookwyrm/tests/views/test_search.py +++ b/bookwyrm/tests/views/test_search.py @@ -33,6 +33,7 @@ class ShelfViews(TestCase): connector_file='self_connector', local=True ) + models.SiteSettings.objects.create() def test_search_json_response(self): @@ -89,7 +90,7 @@ class ShelfViews(TestCase): manager.return_value = [search_result] response = view(request) self.assertIsInstance(response, TemplateResponse) - self.assertEqual(response.template_name, 'search_results.html') + response.render() self.assertEqual( response.context_data['book_results'][0].title, 'Gideon the Ninth') @@ -103,6 +104,6 @@ class ShelfViews(TestCase): with patch('bookwyrm.connectors.connector_manager.search'): response = view(request) self.assertIsInstance(response, TemplateResponse) - self.assertEqual(response.template_name, 'search_results.html') + response.render() self.assertEqual( response.context_data['user_results'][0], self.local_user) diff --git a/bookwyrm/tests/views/test_shelf.py b/bookwyrm/tests/views/test_shelf.py index 95150fe9d..f67bbc562 100644 --- a/bookwyrm/tests/views/test_shelf.py +++ b/bookwyrm/tests/views/test_shelf.py @@ -29,6 +29,7 @@ class ShelfViews(TestCase): identifier='test-shelf', user=self.local_user ) + models.SiteSettings.objects.create() def test_shelf_page(self): @@ -41,7 +42,7 @@ class ShelfViews(TestCase): is_api.return_value = False result = view(request, self.local_user.username, shelf.identifier) self.assertIsInstance(result, TemplateResponse) - self.assertEqual(result.template_name, 'user/shelf.html') + result.render() self.assertEqual(result.status_code, 200) with patch('bookwyrm.views.shelf.is_api_request') as is_api: diff --git a/bookwyrm/tests/views/test_tag.py b/bookwyrm/tests/views/test_tag.py index 1556139ca..3dfef9a11 100644 --- a/bookwyrm/tests/views/test_tag.py +++ b/bookwyrm/tests/views/test_tag.py @@ -33,6 +33,7 @@ class TagViews(TestCase): remote_id='https://example.com/book/1', parent_work=self.work ) + models.SiteSettings.objects.create() def test_tag_page(self): @@ -46,7 +47,7 @@ class TagViews(TestCase): is_api.return_value = False result = view(request, tag.identifier) self.assertIsInstance(result, TemplateResponse) - self.assertEqual(result.template_name, 'tag.html') + result.render() self.assertEqual(result.status_code, 200) request = self.factory.get('') diff --git a/bookwyrm/tests/views/test_user.py b/bookwyrm/tests/views/test_user.py index 61fcdb641..616a6575f 100644 --- a/bookwyrm/tests/views/test_user.py +++ b/bookwyrm/tests/views/test_user.py @@ -23,6 +23,7 @@ class UserViews(TestCase): self.rat = models.User.objects.create_user( 'rat@local.com', 'rat@rat.rat', 'password', local=True, localname='rat') + models.SiteSettings.objects.create() def test_user_page(self): @@ -34,7 +35,7 @@ class UserViews(TestCase): is_api.return_value = False result = view(request, 'mouse') self.assertIsInstance(result, TemplateResponse) - self.assertEqual(result.template_name, 'user/user.html') + result.render() self.assertEqual(result.status_code, 200) with patch('bookwyrm.views.user.is_api_request') as is_api: @@ -65,7 +66,7 @@ class UserViews(TestCase): is_api.return_value = False result = view(request, 'mouse') self.assertIsInstance(result, TemplateResponse) - self.assertEqual(result.template_name, 'user/followers.html') + result.render() self.assertEqual(result.status_code, 200) with patch('bookwyrm.views.user.is_api_request') as is_api: @@ -96,7 +97,7 @@ class UserViews(TestCase): is_api.return_value = False result = view(request, 'mouse') self.assertIsInstance(result, TemplateResponse) - self.assertEqual(result.template_name, 'user/following.html') + result.render() self.assertEqual(result.status_code, 200) with patch('bookwyrm.views.user.is_api_request') as is_api: @@ -125,7 +126,7 @@ class UserViews(TestCase): request.user = self.local_user result = view(request) self.assertIsInstance(result, TemplateResponse) - self.assertEqual(result.template_name, 'preferences/edit_user.html') + result.render() self.assertEqual(result.status_code, 200) From 661d49d9cc69915003ed14d3f4f4d4b797d5d4a6 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 30 Jan 2021 17:19:01 -0800 Subject: [PATCH 04/69] Ignore openlibrary editions with little to no metadata Also fixes the isbn problem --- bookwyrm/connectors/openlibrary.py | 31 +++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/bookwyrm/connectors/openlibrary.py b/bookwyrm/connectors/openlibrary.py index 55355131c..cd196d274 100644 --- a/bookwyrm/connectors/openlibrary.py +++ b/bookwyrm/connectors/openlibrary.py @@ -27,9 +27,9 @@ class Connector(AbstractConnector): Mapping('series', formatter=get_first), Mapping('seriesNumber', remote_field='series_number'), Mapping('subjects'), - Mapping('subjectPlaces'), - Mapping('isbn13', formatter=get_first), - Mapping('isbn10', formatter=get_first), + Mapping('subjectPlaces', remote_field='subject_places'), + Mapping('isbn13', remote_field='isbn_13', formatter=get_first), + Mapping('isbn10', remote_field='isbn_10', formatter=get_first), Mapping('lccn', formatter=get_first), Mapping( 'oclcNumber', remote_field='oclc_numbers', @@ -144,9 +144,34 @@ class Connector(AbstractConnector): # we can mass download edition data from OL to avoid repeatedly querying edition_options = self.load_edition_data(work.openlibrary_key) for edition_data in edition_options.get('entries'): + # does this edition have ANY interesting data? + if ignore_edition(edition_data): + continue self.create_edition_from_data(work, edition_data) +def ignore_edition(edition_data): + ''' don't load a million editions that have no metadata ''' + # an isbn, we love to see it + if edition_data.get('isbn_13') or edition_data.get('isbn_10'): + print(edition_data.get('isbn_10')) + return False + # grudgingly, oclc can stay + if edition_data.get('oclc_numbers'): + print(edition_data.get('oclc_numbers')) + return False + # if it has a cover it can stay + if edition_data.get('covers'): + print(edition_data.get('covers')) + return False + # keep non-english editions + if edition_data.get('languages') and \ + 'languages/eng' not in str(edition_data.get('languages')): + print(edition_data.get('languages')) + return False + return True + + def get_description(description_blob): ''' descriptions can be a string or a dict ''' if isinstance(description_blob, dict): From 9833f5a03da6ae2b4a8d298c99506316ccac0fb5 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 30 Jan 2021 17:36:24 -0800 Subject: [PATCH 05/69] Tests creating editions --- .../connectors/test_openlibrary_connector.py | 16 ++++++++++++++++ bookwyrm/tests/data/ol_edition.json | 1 + 2 files changed, 17 insertions(+) diff --git a/bookwyrm/tests/connectors/test_openlibrary_connector.py b/bookwyrm/tests/connectors/test_openlibrary_connector.py index dc4c5f5b1..c277ba045 100644 --- a/bookwyrm/tests/connectors/test_openlibrary_connector.py +++ b/bookwyrm/tests/connectors/test_openlibrary_connector.py @@ -190,3 +190,19 @@ class Openlibrary(TestCase): ''' detect if the loaded json is an edition ''' edition = pick_default_edition(self.edition_list_data['entries']) self.assertEqual(edition['key'], '/books/OL9788823M') + + + def test_create_edition_from_data(self): + ''' okay but can it actually create an edition with proper metadata ''' + work = models.Work.objects.create(title='Hello') + result = self.connector.create_edition_from_data( + work, self.edition_data) + self.assertEqual(result.parent_work, work) + self.assertEqual(result.title, 'Sabriel') + self.assertEqual(result.isbn_10, '0060273224') + self.assertIsNotNone(result.description) + self.assertEqual(result.languages[0], 'English') + self.assertEqual(result.publishers[0], 'Harper Trophy') + self.assertEqual(result.pages, 491) + self.assertEqual(result.subjects[0], 'Fantasy.') + self.assertEqual(result.physical_format, 'Hardcover') diff --git a/bookwyrm/tests/data/ol_edition.json b/bookwyrm/tests/data/ol_edition.json index 459e9dff4..2423364b1 100644 --- a/bookwyrm/tests/data/ol_edition.json +++ b/bookwyrm/tests/data/ol_edition.json @@ -9,6 +9,7 @@ "518848" ] }, + "physical_format": "Hardcover", "lc_classifications": [ "PZ7.N647 Sab 1995" ], From 45ac13a7fffe00337127b5b82d6a569cc2703ed3 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 30 Jan 2021 18:30:07 -0800 Subject: [PATCH 06/69] Clear unused editions with poor metadata --- .../management/commands/remove_editions.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 bookwyrm/management/commands/remove_editions.py diff --git a/bookwyrm/management/commands/remove_editions.py b/bookwyrm/management/commands/remove_editions.py new file mode 100644 index 000000000..c5153f44b --- /dev/null +++ b/bookwyrm/management/commands/remove_editions.py @@ -0,0 +1,34 @@ +''' PROCEED WITH CAUTION: this permanently deletes book data ''' +from django.core.management.base import BaseCommand +from django.db.models import Count, Q +from bookwyrm import models + + +def remove_editions(): + ''' combine duplicate editions and update related models ''' + # not in use + filters = {'%s__isnull' % r.name: True \ + for r in models.Edition._meta.related_objects} + # no cover, no identifying fields + filters['cover'] = '' + null_fields = {'%s__isnull' % f: True for f in \ + ['isbn_10', 'isbn_13', 'oclc_number']} + + editions = models.Edition.objects.filter( + Q(languages=[]) | Q(languages__contains=['English']), + **filters, **null_fields + ).annotate(Count('parent_work__editions')).filter( + # mustn't be the only edition for the work + parent_work__editions__count__gt=1 + ) + print(editions.count()) + editions.delete() + + +class Command(BaseCommand): + ''' dedplucate allllll the book data models ''' + help = 'merges duplicate book data' + # pylint: disable=no-self-use,unused-argument + def handle(self, *args, **options): + ''' run deudplications ''' + remove_editions() From fe088f21fb495b27beee8b1ca178ddc7176f9830 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 30 Jan 2021 21:00:36 -0800 Subject: [PATCH 07/69] Model and migration for lists --- .../migrations/0041_auto_20210131_0500.py | 65 +++++++++++++++ bookwyrm/models/__init__.py | 1 + bookwyrm/models/list.py | 83 +++++++++++++++++++ 3 files changed, 149 insertions(+) create mode 100644 bookwyrm/migrations/0041_auto_20210131_0500.py create mode 100644 bookwyrm/models/list.py diff --git a/bookwyrm/migrations/0041_auto_20210131_0500.py b/bookwyrm/migrations/0041_auto_20210131_0500.py new file mode 100644 index 000000000..eb00e3f37 --- /dev/null +++ b/bookwyrm/migrations/0041_auto_20210131_0500.py @@ -0,0 +1,65 @@ +# Generated by Django 3.0.7 on 2021-01-31 05:00 + +import bookwyrm.models.base_model +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', '0040_auto_20210122_0057'), + ] + + operations = [ + migrations.CreateModel( + name='List', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_date', models.DateTimeField(auto_now_add=True)), + ('updated_date', models.DateTimeField(auto_now=True)), + ('remote_id', bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id])), + ('name', bookwyrm.models.fields.CharField(max_length=100)), + ('description', bookwyrm.models.fields.TextField(blank=True, null=True)), + ('privacy', bookwyrm.models.fields.CharField(choices=[('public', 'Public'), ('unlisted', 'Unlisted'), ('followers', 'Followers'), ('direct', 'Direct')], default='public', max_length=255)), + ('curation', bookwyrm.models.fields.CharField(choices=[('closed', 'Closed'), ('open', 'Open'), ('moderated', 'Moderated')], default='closed', max_length=255)), + ], + options={ + 'abstract': False, + }, + bases=(bookwyrm.models.base_model.OrderedCollectionMixin, models.Model), + ), + migrations.CreateModel( + name='ListItem', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_date', models.DateTimeField(auto_now_add=True)), + ('updated_date', models.DateTimeField(auto_now=True)), + ('remote_id', bookwyrm.models.fields.RemoteIdField(max_length=255, null=True, validators=[bookwyrm.models.fields.validate_remote_id])), + ('notes', bookwyrm.models.fields.TextField(blank=True, null=True)), + ('approved', models.BooleanField(default=True)), + ('order', bookwyrm.models.fields.IntegerField(blank=True, null=True)), + ('added_by', bookwyrm.models.fields.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), + ('book', bookwyrm.models.fields.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='bookwyrm.Edition')), + ('book_list', bookwyrm.models.fields.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='bookwyrm.List')), + ('endorsement', models.ManyToManyField(related_name='endorsers', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ('-created_date',), + 'unique_together': {('book', 'book_list')}, + }, + bases=(bookwyrm.models.base_model.ActivitypubMixin, models.Model), + ), + migrations.AddField( + model_name='list', + name='books', + field=models.ManyToManyField(through='bookwyrm.ListItem', to='bookwyrm.Edition'), + ), + migrations.AddField( + model_name='list', + name='user', + field=bookwyrm.models.fields.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/bookwyrm/models/__init__.py b/bookwyrm/models/__init__.py index b232e98fa..0aef63850 100644 --- a/bookwyrm/models/__init__.py +++ b/bookwyrm/models/__init__.py @@ -7,6 +7,7 @@ from .author import Author from .connector import Connector from .shelf import Shelf, ShelfBook +from .list import List, ListItem from .status import Status, GeneratedNote, Review, Comment, Quotation from .status import Boost diff --git a/bookwyrm/models/list.py b/bookwyrm/models/list.py new file mode 100644 index 000000000..2bc49261a --- /dev/null +++ b/bookwyrm/models/list.py @@ -0,0 +1,83 @@ +''' make a list of books!! ''' +from django.db import models + +from bookwyrm import activitypub +from .base_model import ActivitypubMixin, BookWyrmModel +from .base_model import OrderedCollectionMixin +from . import fields + + +CurationType = models.TextChoices('Curation', [ + 'closed', + 'open', + 'moderated', +]) + +class List(OrderedCollectionMixin, BookWyrmModel): + ''' a list of books ''' + name = fields.CharField(max_length=100) + user = fields.ForeignKey( + 'User', on_delete=models.PROTECT, activitypub_field='owner') + description = fields.TextField(blank=True, null=True) + privacy = fields.CharField( + max_length=255, + default='public', + choices=fields.PrivacyLevels.choices + ) + curation = fields.CharField( + max_length=255, + default='closed', + choices=CurationType.choices + ) + books = models.ManyToManyField( + 'Edition', + symmetrical=False, + through='ListItem', + through_fields=('book_list', 'book'), + ) + @property + def collection_queryset(self): + ''' list of books for this shelf, overrides OrderedCollectionMixin ''' + return self.books.all().order_by('listitem') + + +class ListItem(ActivitypubMixin, BookWyrmModel): + ''' ok ''' + book = fields.ForeignKey( + 'Edition', on_delete=models.PROTECT, activitypub_field='object') + book_list = fields.ForeignKey( + 'List', on_delete=models.CASCADE, activitypub_field='target') + added_by = fields.ForeignKey( + 'User', + on_delete=models.PROTECT, + activitypub_field='actor' + ) + notes = fields.TextField(blank=True, null=True) + approved = models.BooleanField(default=True) + order = fields.IntegerField(blank=True, null=True) + endorsement = models.ManyToManyField('User', related_name='endorsers') + + activity_serializer = activitypub.AddBook + + def to_add_activity(self, user): + ''' AP for shelving a book''' + return activitypub.Add( + id='%s#add' % self.remote_id, + actor=user.remote_id, + object=self.book.to_activity(), + target=self.book_list.remote_id, + ).serialize() + + def to_remove_activity(self, user): + ''' AP for un-shelving a book''' + return activitypub.Remove( + id='%s#remove' % self.remote_id, + actor=user.remote_id, + object=self.book.to_activity(), + target=self.book_list.to_activity() + ).serialize() + + class Meta: + ''' an opinionated constraint! you can't put a book on a list twice ''' + unique_together = ('book', 'book_list') + ordering = ('-created_date',) From af65509527dfa4d46b63267bfb49e6d642f4efbd Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 30 Jan 2021 21:03:50 -0800 Subject: [PATCH 08/69] stub test for list model --- bookwyrm/tests/models/test_list.py | 32 ++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 bookwyrm/tests/models/test_list.py diff --git a/bookwyrm/tests/models/test_list.py b/bookwyrm/tests/models/test_list.py new file mode 100644 index 000000000..bca63e169 --- /dev/null +++ b/bookwyrm/tests/models/test_list.py @@ -0,0 +1,32 @@ +''' testing models ''' +from django.test import TestCase + +from bookwyrm import models, settings + + +class List(TestCase): + ''' some activitypub oddness ahead ''' + def setUp(self): + ''' look, a list ''' + self.user = models.User.objects.create_user( + 'mouse', 'mouse@mouse.mouse', 'mouseword', + local=True, localname='mouse') + self.list = models.List.objects.create( + name='Test List', user=self.user) + + def test_remote_id(self): + ''' shelves use custom remote ids ''' + expected_id = 'https://%s/user/mouse/list/%d' % \ + (settings.DOMAIN, self.list.id) + self.assertEqual(self.list.get_remote_id(), expected_id) + + + def test_to_activity(self): + ''' jsonify it ''' + activity_json = self.list.to_activity() + self.assertIsInstance(activity_json, dict) + self.assertEqual(activity_json['id'], self.list.remote_id) + self.assertEqual(activity_json['totalItems'], 0) + self.assertEqual(activity_json['type'], 'OrderedCollection') + self.assertEqual(activity_json['name'], 'Test List') + self.assertEqual(activity_json['owner'], self.user.remote_id) From 0815b36ec9af2b44f0c4eeab19dcfa188f4603c3 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 30 Jan 2021 21:33:41 -0800 Subject: [PATCH 09/69] Adds basic view and template files --- bookwyrm/templates/layout.html | 5 ++- bookwyrm/templates/lists/list.html | 7 ++++ bookwyrm/templates/lists/lists.html | 16 +++++++++ bookwyrm/urls.py | 9 +++-- bookwyrm/views/__init__.py | 1 + bookwyrm/views/list.py | 53 +++++++++++++++++++++++++++++ 6 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 bookwyrm/templates/lists/list.html create mode 100644 bookwyrm/templates/lists/lists.html create mode 100644 bookwyrm/views/list.py diff --git a/bookwyrm/templates/layout.html b/bookwyrm/templates/layout.html index 817829706..1a6c065ea 100644 --- a/bookwyrm/templates/layout.html +++ b/bookwyrm/templates/layout.html @@ -53,13 +53,16 @@ From 1e9189d43c952d4529d274268f84a33d92922664 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 31 Jan 2021 10:56:40 -0800 Subject: [PATCH 17/69] Suggest recently edited books if we're out of user books --- bookwyrm/templates/lists/list.html | 6 +++--- bookwyrm/templates/snippets/privacy-icons.html | 16 ++++++++-------- bookwyrm/views/list.py | 10 +++++++++- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/bookwyrm/templates/lists/list.html b/bookwyrm/templates/lists/list.html index 8f171bb45..38be3edf8 100644 --- a/bookwyrm/templates/lists/list.html +++ b/bookwyrm/templates/lists/list.html @@ -5,7 +5,7 @@

{{ list.name }} {% include 'snippets/privacy-icons.html' with item=list %}

-

Created by {% include 'snippets/username.html' with user=list.user %}

+

Created {% if list.curation != 'open' %} and curated{% endif %} by {% include 'snippets/username.html' with user=list.user %}

{% include 'snippets/trimmed_text.html' with full=list.description %}
{% if request.user == list.user %} @@ -64,7 +64,7 @@ {% if not list.curation == 'closed' or request.user == list.user %}
-

Add Books

+

{% if list.curation == 'open' or request.user == list.user %}Add{% else %}Suggest{% endif %} Books

{% for book in suggested_books %}
@@ -75,7 +75,7 @@
{% csrf_token %} - +
diff --git a/bookwyrm/templates/snippets/privacy-icons.html b/bookwyrm/templates/snippets/privacy-icons.html index 793fbc8b2..c917f553b 100644 --- a/bookwyrm/templates/snippets/privacy-icons.html +++ b/bookwyrm/templates/snippets/privacy-icons.html @@ -1,18 +1,18 @@ {% if item.privacy == 'public' %} - - Public post + + Public {% elif item.privacy == 'unlisted' %} - - Unlisted post + + Unlisted {% elif item.privacy == 'followers' %} - - Followers-only post + + Followers-only {% else %} - - Private post + + Private {% endif %} diff --git a/bookwyrm/views/list.py b/bookwyrm/views/list.py index 63f99c4aa..c126102d0 100644 --- a/bookwyrm/views/list.py +++ b/bookwyrm/views/list.py @@ -55,11 +55,19 @@ class List(View): suggestions = request.user.shelfbook_set.filter( ~Q(book__in=book_list.books.all()) ) + suggestions = [s.book for s in suggestions[:5]] + if len(suggestions) < 5: + suggestions += [s.default_edition for s in \ + models.Work.objects.filter( + ~Q(editions__in=book_list.books.all()), + ).order_by('-updated_date') + ][:5 - len(suggestions)] + data = { 'title': '%s | Lists' % book_list.name, 'list': book_list, - 'suggested_books': [s.book for s in suggestions[:5]], + 'suggested_books': suggestions, 'list_form': forms.ListForm(instance=book_list), } return TemplateResponse(request, 'lists/list.html', data) From 6a68fe9475cf2eb9fdaecc4683e7096a2d9efbd6 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 31 Jan 2021 11:11:26 -0800 Subject: [PATCH 18/69] Search for books to add to lists --- bookwyrm/connectors/connector_manager.py | 4 ++-- bookwyrm/connectors/self_connector.py | 11 +++++++--- bookwyrm/templates/layout.html | 2 +- bookwyrm/templates/lists/list.html | 17 ++++++++++++++ bookwyrm/views/list.py | 28 +++++++++++++++--------- 5 files changed, 46 insertions(+), 16 deletions(-) diff --git a/bookwyrm/connectors/connector_manager.py b/bookwyrm/connectors/connector_manager.py index d3b01f7ae..a63a788eb 100644 --- a/bookwyrm/connectors/connector_manager.py +++ b/bookwyrm/connectors/connector_manager.py @@ -35,10 +35,10 @@ def search(query, min_confidence=0.1): return results -def local_search(query, min_confidence=0.1): +def local_search(query, min_confidence=0.1, raw=False): ''' only look at local search results ''' connector = load_connector(models.Connector.objects.get(local=True)) - return connector.search(query, min_confidence=min_confidence) + return connector.search(query, min_confidence=min_confidence, raw=raw) def first_search_result(query, min_confidence=0.1): diff --git a/bookwyrm/connectors/self_connector.py b/bookwyrm/connectors/self_connector.py index c5d58a595..f57fbc1cc 100644 --- a/bookwyrm/connectors/self_connector.py +++ b/bookwyrm/connectors/self_connector.py @@ -11,7 +11,8 @@ from .abstract_connector import AbstractConnector, SearchResult class Connector(AbstractConnector): ''' instantiate a connector ''' - def search(self, query, min_confidence=0.1): + # pylint: disable=arguments-differ + def search(self, query, min_confidence=0.1, raw=False): ''' search your local database ''' if not query: return [] @@ -22,10 +23,14 @@ class Connector(AbstractConnector): results = search_title_author(query, min_confidence) search_results = [] for result in results: - search_results.append(self.format_search_result(result)) + if raw: + search_results.append(result) + else: + search_results.append(self.format_search_result(result)) if len(search_results) >= 10: break - search_results.sort(key=lambda r: r.confidence, reverse=True) + if not raw: + search_results.sort(key=lambda r: r.confidence, reverse=True) return search_results diff --git a/bookwyrm/templates/layout.html b/bookwyrm/templates/layout.html index 1a6c065ea..fe8a75094 100644 --- a/bookwyrm/templates/layout.html +++ b/bookwyrm/templates/layout.html @@ -59,10 +59,10 @@ Feed - {% endif %} Lists + {% endif %}
- {% if readthroughs.exists %} + {% if request.user.is_authenticated %}

Your reading activity

@@ -155,6 +155,9 @@ {% include 'snippets/toggle/open_button.html' with text="Add read dates" icon="plus" class="is-small" controls_text="add-readthrough" %}
+ {% if not readthroughs.exists %} +

You don't have any reading activity for this book.

+ {% endif %}
{% if request.user == list.user %}
- {% include 'snippets/toggle/open_button.html' with text="Edit list" icon="pencil" controls_text="edit-list" %} + {% include 'snippets/toggle/open_button.html' with text="Edit list" icon="pencil" controls_text="edit-list" focus="edit-list-header" %}
{% endif %}