From 72eb94315a3636e88dddc825c1705c798cd7bc23 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Fri, 11 Dec 2020 16:39:58 -0800 Subject: [PATCH 01/63] Adds shelf info to book page - includes change shelf button - WIP button for switching to the current edition --- bookwyrm/templates/book.html | 97 ++++--------------- bookwyrm/templates/snippets/readthrough.html | 80 +++++++++++++++ .../snippets/switch_edition_button.html | 3 + bookwyrm/views.py | 21 ++-- 4 files changed, 116 insertions(+), 85 deletions(-) create mode 100644 bookwyrm/templates/snippets/readthrough.html create mode 100644 bookwyrm/templates/snippets/switch_edition_button.html diff --git a/bookwyrm/templates/book.html b/bookwyrm/templates/book.html index 51fbdafc6..eee228984 100644 --- a/bookwyrm/templates/book.html +++ b/bookwyrm/templates/book.html @@ -91,87 +91,26 @@ {% endif %} - {% for readthrough in readthroughs %} -
- - -
- -
- - -
- + {# user's relationship to the book #}
- - + {% for shelf in user_shelves %} +

+ This edition is on your {{ shelf.shelf.name }} shelf. + {% include 'snippets/shelf_selector.html' with current=shelf.shelf %} +

+ {% endfor %} + + {% for shelf in other_edition_shelves %} +

+ A different edition of this book is on your {{ shelf.shelf.name }} shelf. + {% include 'snippets/switch_edition_button.html' with desired_edition=book %} +

+ {% endfor %} + + {% for readthrough in readthroughs %} + {% include 'snippets/readthrough.html' with readthrough=readthrough %} + {% endfor %}
- {% endfor %} {% if request.user.is_authenticated %}
diff --git a/bookwyrm/templates/snippets/readthrough.html b/bookwyrm/templates/snippets/readthrough.html new file mode 100644 index 000000000..4d6ca03aa --- /dev/null +++ b/bookwyrm/templates/snippets/readthrough.html @@ -0,0 +1,80 @@ +{% load humanize %} +
+ + +
+ +
+ + +
+ +
+ + +
diff --git a/bookwyrm/templates/snippets/switch_edition_button.html b/bookwyrm/templates/snippets/switch_edition_button.html new file mode 100644 index 000000000..3fa09f4f1 --- /dev/null +++ b/bookwyrm/templates/snippets/switch_edition_button.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/bookwyrm/views.py b/bookwyrm/views.py index e0feaee75..7c27eade3 100644 --- a/bookwyrm/views.py +++ b/bookwyrm/views.py @@ -5,8 +5,7 @@ from django.contrib.auth.decorators import login_required, permission_required from django.contrib.postgres.search import TrigramSimilarity from django.core.paginator import Paginator from django.db.models import Avg, Q -from django.http import HttpResponseBadRequest, HttpResponseNotFound,\ - JsonResponse +from django.http import HttpResponseNotFound, JsonResponse from django.core.exceptions import PermissionDenied from django.shortcuts import get_object_or_404, redirect from django.template.response import TemplateResponse @@ -558,8 +557,7 @@ def book_page(request, book_id): prev_page = '/book/%s/?page=%d' % \ (book_id, reviews_page.previous_page_number()) - user_tags = [] - readthroughs = [] + user_tags = readthroughs = user_shelves = other_edition_shelves = [] if request.user.is_authenticated: user_tags = models.Tag.objects.filter( book=book, user=request.user @@ -570,6 +568,16 @@ def book_page(request, book_id): book=book, ).order_by('start_date') + user_shelves = models.ShelfBook.objects.filter( + added_by=request.user, book=book + ) + + other_edition_shelves = models.ShelfBook.objects.filter( + ~Q(book=book), + added_by=request.user, + book__parent_work=book.parent_work, + ) + rating = reviews.aggregate(Avg('rating')) tags = models.Tag.objects.filter( book=book @@ -585,6 +593,8 @@ def book_page(request, book_id): 'rating': rating['rating__avg'], 'tags': tags, 'user_tags': user_tags, + 'user_shelves': user_shelves, + 'other_edition_shelves': other_edition_shelves, 'readthroughs': readthroughs, 'path': '/book/%s' % book_id, 'info_fields': [ @@ -628,10 +638,9 @@ def editions_page(request, book_id): encoder=ActivityEncoder ) - editions = models.Edition.objects.filter(parent_work=work).all() data = { 'title': 'Editions of %s' % work.title, - 'editions': editions, + 'editions': work.edition_set.all(), 'work': work, } return TemplateResponse(request, 'editions.html', data) From 2d7f8ada6139a39a6962840908b5473f778b5e6e Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Fri, 11 Dec 2020 16:57:38 -0800 Subject: [PATCH 02/63] Functional switch editions button --- bookwyrm/models/status.py | 2 +- bookwyrm/templates/book.html | 2 +- .../snippets/switch_edition_button.html | 8 +++--- bookwyrm/urls.py | 17 ++++++------ bookwyrm/view_actions.py | 27 +++++++++++++++++++ 5 files changed, 43 insertions(+), 13 deletions(-) diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index 9d45379cc..07e25119c 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -283,7 +283,7 @@ class Boost(Status): class ReadThrough(BookWyrmModel): ''' Store progress through a book in the database. ''' user = models.ForeignKey('User', on_delete=models.PROTECT) - book = models.ForeignKey('Book', on_delete=models.PROTECT) + book = models.ForeignKey('Edition', on_delete=models.PROTECT) pages_read = models.IntegerField( null=True, blank=True) diff --git a/bookwyrm/templates/book.html b/bookwyrm/templates/book.html index eee228984..c71120160 100644 --- a/bookwyrm/templates/book.html +++ b/bookwyrm/templates/book.html @@ -103,7 +103,7 @@ {% for shelf in other_edition_shelves %}

A different edition of this book is on your {{ shelf.shelf.name }} shelf. - {% include 'snippets/switch_edition_button.html' with desired_edition=book %} + {% include 'snippets/switch_edition_button.html' with edition=book %}

{% endfor %} diff --git a/bookwyrm/templates/snippets/switch_edition_button.html b/bookwyrm/templates/snippets/switch_edition_button.html index 3fa09f4f1..9771535a0 100644 --- a/bookwyrm/templates/snippets/switch_edition_button.html +++ b/bookwyrm/templates/snippets/switch_edition_button.html @@ -1,3 +1,5 @@ -
- -
+
+ {% csrf_token %} + + +
diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index a9792038e..01d6c56fa 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -97,15 +97,16 @@ urlpatterns = [ re_path(r'^edit-profile/?$', actions.edit_profile), - re_path(r'^import-data/?', actions.import_data), - re_path(r'^retry-import/?', actions.retry_import), - re_path(r'^resolve-book/?', actions.resolve_book), - 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'^import-data/?$', actions.import_data), + re_path(r'^retry-import/?$', actions.retry_import), + re_path(r'^resolve-book/?$', actions.resolve_book), + 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-readthrough/?', actions.edit_readthrough), - re_path(r'^delete-readthrough/?', actions.delete_readthrough), + re_path(r'^switch-edition/?$', actions.switch_edition), + re_path(r'^edit-readthrough/?$', actions.edit_readthrough), + re_path(r'^delete-readthrough/?$', actions.delete_readthrough), re_path(r'^rate/?$', actions.rate), re_path(r'^review/?$', actions.review), diff --git a/bookwyrm/view_actions.py b/bookwyrm/view_actions.py index bb862007d..bd8baf731 100644 --- a/bookwyrm/view_actions.py +++ b/bookwyrm/view_actions.py @@ -10,6 +10,7 @@ from django.contrib.auth import authenticate, login, logout from django.contrib.auth.decorators import login_required, permission_required from django.core.exceptions import PermissionDenied from django.core.files.base import ContentFile +from django.db import transaction from django.http import HttpResponseBadRequest, HttpResponseNotFound from django.shortcuts import get_object_or_404, redirect from django.template.response import TemplateResponse @@ -244,6 +245,32 @@ def edit_book(request, book_id): return redirect('/book/%s' % book.id) +@login_required +@require_POST +@transaction.atomic +def switch_edition(request): + ''' switch your copy of a book to a different edition ''' + edition_id = request.POST.get('edition') + new_edition = get_object_or_404(models.Edition, id=edition_id) + shelfbooks = models.ShelfBook.objects.filter( + book__parent_work=new_edition.parent_work, + added_by=request.user + ) + for shelfbook in shelfbooks.all(): + shelfbook.book = new_edition + shelfbook.save() + + readthroughs = models.ReadThrough.objects.filter( + book__parent_work=new_edition.parent_work, + user=request.user + ) + for readthrough in readthroughs.all(): + readthrough.book = new_edition + readthrough.save() + + return redirect('/book/%d' % new_edition.id) + + @login_required @require_POST def upload_cover(request, book_id): From aacf5b7ba41d7ac5196aaecf1d8cab3cd5f9c398 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 12 Dec 2020 18:00:39 -0800 Subject: [PATCH 03/63] fields for content warnings --- bookwyrm/activitypub/note.py | 1 + bookwyrm/models/status.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/bookwyrm/activitypub/note.py b/bookwyrm/activitypub/note.py index df28bf8de..263ddb2a6 100644 --- a/bookwyrm/activitypub/note.py +++ b/bookwyrm/activitypub/note.py @@ -23,6 +23,7 @@ class Note(ActivityObject): cc: List[str] = field(default_factory=lambda: []) replies: Dict = field(default_factory=lambda: {}) inReplyTo: str = '' + summary: str = '' tag: List[Link] = field(default_factory=lambda: []) attachment: List[Image] = field(default_factory=lambda: []) sensitive: bool = False diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index 55036f2c9..308a3c8c0 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -23,6 +23,8 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): default='public', choices=PrivacyLevels.choices ) + content_warning = fields.CharField( + max_length=150, blank=True, null=True, activitypub_field='summary') sensitive = fields.BooleanField(default=False) # the created date can't be this, because of receiving federated posts published_date = fields.DateTimeField( @@ -68,7 +70,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): **kwargs ) - def to_activity(self, pure=False): + def to_activity(self, pure=False):# pylint: disable=arguments-differ ''' return tombstone if the status is deleted ''' if self.deleted: return activitypub.Tombstone( From 710fbc949b95d9e3c616b16a1d3cac8e0796c8d4 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 15 Dec 2020 15:52:22 -0800 Subject: [PATCH 04/63] Better username validator and remove trailing whitespace --- bookwyrm/models/fields.py | 11 +++++++++-- bookwyrm/view_actions.py | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/bookwyrm/models/fields.py b/bookwyrm/models/fields.py index f6142e374..b8efc71d0 100644 --- a/bookwyrm/models/fields.py +++ b/bookwyrm/models/fields.py @@ -5,7 +5,6 @@ from uuid import uuid4 import dateutil.parser from dateutil.parser import ParserError -from django.contrib.auth.models import AbstractUser from django.contrib.postgres.fields import ArrayField as DjangoArrayField from django.core.exceptions import ValidationError from django.core.files.base import ContentFile @@ -25,6 +24,14 @@ def validate_remote_id(value): params={'value': value}, ) +def validate_username(value): + ''' make sure usernames look okay ''' + if not re.match(r'^[A-Za-z\-_\.]+$', value): + raise ValidationError( + _('%(value)s is not a valid remote_id'), + params={'value': value}, + ) + class ActivitypubFieldMixin: ''' make a database field serializable ''' @@ -134,7 +141,7 @@ class UsernameField(ActivitypubFieldMixin, models.CharField): _('username'), max_length=150, unique=True, - validators=[AbstractUser.username_validator], + validators=[validate_username], error_messages={ 'unique': _('A user with that username already exists.'), }, diff --git a/bookwyrm/view_actions.py b/bookwyrm/view_actions.py index 7126b1b22..26106190b 100644 --- a/bookwyrm/view_actions.py +++ b/bookwyrm/view_actions.py @@ -66,7 +66,7 @@ def register(request): if not form.is_valid(): errors = True - username = form.data['username'] + username = form.data['username'].strip() email = form.data['email'] password = form.data['password'] From bde75766f2a28b98bf997adb5d370b6cd761aee9 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 15 Dec 2020 16:36:22 -0800 Subject: [PATCH 05/63] test for registration and password reset --- bookwyrm/tests/test_view_actions.py | 239 ++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 bookwyrm/tests/test_view_actions.py diff --git a/bookwyrm/tests/test_view_actions.py b/bookwyrm/tests/test_view_actions.py new file mode 100644 index 000000000..bb0fcdb24 --- /dev/null +++ b/bookwyrm/tests/test_view_actions.py @@ -0,0 +1,239 @@ +''' test for app action functionality ''' +from unittest.mock import patch + +from django.core.exceptions import PermissionDenied +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.settings import DOMAIN + + +#pylint: disable=too-many-public-methods +class ViewActions(TestCase): + ''' a lot here: all handlers for receiving activitypub requests ''' + def setUp(self): + ''' we need basic things, like users ''' + self.local_user = models.User.objects.create_user( + 'mouse', 'mouse@mouse.com', 'mouseword', local=True) + self.local_user.remote_id = 'https://example.com/user/mouse' + self.local_user.save() + with patch('bookwyrm.models.user.set_remote_server.delay'): + 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.status = models.Status.objects.create( + user=self.local_user, + content='Test status', + remote_id='https://example.com/status/1', + ) + self.settings = models.SiteSettings.objects.create(id=1) + self.factory = RequestFactory() + + + def test_register(self): + ''' create a user ''' + self.assertEqual(models.User.objects.count(), 2) + request = self.factory.post( + 'register/', + { + 'username': 'nutria-user.user_nutria', + 'password': 'mouseword', + 'email': 'aa@bb.cccc' + }) + with patch('bookwyrm.view_actions.login'): + response = actions.register(request) + self.assertEqual(models.User.objects.count(), 3) + self.assertEqual(response.status_code, 302) + nutria = models.User.objects.last() + self.assertEqual(nutria.username, 'nutria-user.user_nutria@%s' % DOMAIN) + self.assertEqual(nutria.localname, 'nutria-user.user_nutria') + self.assertEqual(nutria.local, True) + + def test_register_trailing_space(self): + ''' django handles this so weirdly ''' + request = self.factory.post( + 'register/', + { + 'username': 'nutria ', + 'password': 'mouseword', + 'email': 'aa@bb.ccc' + }) + with patch('bookwyrm.view_actions.login'): + response = actions.register(request) + self.assertEqual(models.User.objects.count(), 3) + self.assertEqual(response.status_code, 302) + nutria = models.User.objects.last() + self.assertEqual(nutria.username, 'nutria@%s' % DOMAIN) + self.assertEqual(nutria.localname, 'nutria') + self.assertEqual(nutria.local, True) + + def test_register_invalid_email(self): + ''' gotta have an email ''' + self.assertEqual(models.User.objects.count(), 2) + request = self.factory.post( + 'register/', + { + 'username': 'nutria', + 'password': 'mouseword', + 'email': 'aa' + }) + response = actions.register(request) + self.assertEqual(models.User.objects.count(), 2) + self.assertEqual(response.template_name, 'login.html') + + def test_register_invalid_username(self): + ''' gotta have an email ''' + self.assertEqual(models.User.objects.count(), 2) + request = self.factory.post( + 'register/', + { + 'username': 'nut@ria', + 'password': 'mouseword', + 'email': 'aa@bb.ccc' + }) + response = actions.register(request) + self.assertEqual(models.User.objects.count(), 2) + self.assertEqual(response.template_name, 'login.html') + + request = self.factory.post( + 'register/', + { + 'username': 'nutr ia', + 'password': 'mouseword', + 'email': 'aa@bb.ccc' + }) + response = actions.register(request) + self.assertEqual(models.User.objects.count(), 2) + self.assertEqual(response.template_name, 'login.html') + + request = self.factory.post( + 'register/', + { + 'username': 'nut@ria', + 'password': 'mouseword', + 'email': 'aa@bb.ccc' + }) + response = actions.register(request) + self.assertEqual(models.User.objects.count(), 2) + self.assertEqual(response.template_name, 'login.html') + + + def test_register_closed_instance(self): + ''' you can't just register ''' + self.settings.allow_registration = False + self.settings.save() + request = self.factory.post( + 'register/', + { + 'username': 'nutria ', + 'password': 'mouseword', + 'email': 'aa@bb.ccc' + }) + with self.assertRaises(PermissionDenied): + actions.register(request) + + def test_register_invite(self): + ''' you can't just register ''' + self.settings.allow_registration = False + self.settings.save() + models.SiteInvite.objects.create( + code='testcode', user=self.local_user, use_limit=1) + self.assertEqual(models.SiteInvite.objects.get().times_used, 0) + + request = self.factory.post( + 'register/', + { + 'username': 'nutria', + 'password': 'mouseword', + 'email': 'aa@bb.ccc', + 'invite_code': 'testcode' + }) + with patch('bookwyrm.view_actions.login'): + response = actions.register(request) + self.assertEqual(models.User.objects.count(), 3) + self.assertEqual(response.status_code, 302) + self.assertEqual(models.SiteInvite.objects.get().times_used, 1) + + # invalid invite + request = self.factory.post( + 'register/', + { + 'username': 'nutria2', + 'password': 'mouseword', + 'email': 'aa@bb.ccc', + 'invite_code': 'testcode' + }) + response = actions.register(request) + self.assertEqual(models.User.objects.count(), 3) + + # bad invite code + request = self.factory.post( + 'register/', + { + 'username': 'nutria3', + 'password': 'mouseword', + 'email': 'aa@bb.ccc', + 'invite_code': 'dkfkdjgdfkjgkdfj' + }) + with self.assertRaises(Http404): + response = actions.register(request) + self.assertEqual(models.User.objects.count(), 3) + + + def test_password_reset_request(self): + ''' send 'em an email ''' + request = self.factory.post('', {'email': 'aa@bb.ccc'}) + resp = actions.password_reset_request(request) + self.assertEqual(resp.status_code, 302) + + request = self.factory.post( + '', {'email': 'mouse@mouse.com'}) + with patch('bookwyrm.emailing.send_email.delay'): + resp = actions.password_reset_request(request) + self.assertEqual(resp.template_name, 'password_reset_request.html') + + self.assertEqual( + models.PasswordReset.objects.get().user, self.local_user) + + def test_password_reset(self): + ''' reset from code ''' + code = models.PasswordReset.objects.create(user=self.local_user) + request = self.factory.post('', { + 'reset-code': code.code, + 'password': 'hi', + 'confirm-password': 'hi' + }) + with patch('bookwyrm.view_actions.login'): + resp = actions.password_reset(request) + self.assertEqual(resp.status_code, 302) + self.assertFalse(models.PasswordReset.objects.exists()) + + def test_password_reset_wrong_code(self): + ''' reset from code ''' + models.PasswordReset.objects.create(user=self.local_user) + request = self.factory.post('', { + 'reset-code': 'jhgdkfjgdf', + 'password': 'hi', + 'confirm-password': 'hi' + }) + resp = actions.password_reset(request) + self.assertEqual(resp.template_name, 'password_reset.html') + self.assertTrue(models.PasswordReset.objects.exists()) + + def test_password_reset_mismatch(self): + ''' reset from code ''' + code = models.PasswordReset.objects.create(user=self.local_user) + request = self.factory.post('', { + 'reset-code': code.code, + 'password': 'hi', + 'confirm-password': 'hihi' + }) + resp = actions.password_reset(request) + self.assertEqual(resp.template_name, 'password_reset.html') + self.assertTrue(models.PasswordReset.objects.exists()) From fabf880a94cefbb9bdbcbeabdd7f9516e68707e5 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 15 Dec 2020 16:50:10 -0800 Subject: [PATCH 06/63] Adds post attribute to resolve book endpoint --- bookwyrm/view_actions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bookwyrm/view_actions.py b/bookwyrm/view_actions.py index 26106190b..f193e1277 100644 --- a/bookwyrm/view_actions.py +++ b/bookwyrm/view_actions.py @@ -215,6 +215,7 @@ def edit_profile(request): return redirect('/user/%s' % request.user.localname) +@require_POST def resolve_book(request): ''' figure out the local path to a book from a remote_id ''' remote_id = request.POST.get('remote_id') From af823cf6458090e343c0b7b8f471a5d38873b187 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 15 Dec 2020 17:53:20 -0800 Subject: [PATCH 07/63] Merge migration --- bookwyrm/migrations/0023_merge_20201216_0112.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 bookwyrm/migrations/0023_merge_20201216_0112.py diff --git a/bookwyrm/migrations/0023_merge_20201216_0112.py b/bookwyrm/migrations/0023_merge_20201216_0112.py new file mode 100644 index 000000000..e3af48496 --- /dev/null +++ b/bookwyrm/migrations/0023_merge_20201216_0112.py @@ -0,0 +1,14 @@ +# Generated by Django 3.0.7 on 2020-12-16 01:12 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0017_auto_20201212_0059'), + ('bookwyrm', '0022_auto_20201212_1744'), + ] + + operations = [ + ] From 948e938040bd2dfa0aadb7959a2ab717b7553a89 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 15 Dec 2020 18:16:33 -0800 Subject: [PATCH 08/63] Handles outdated Add with Book type should be edition. --- bookwyrm/models/fields.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bookwyrm/models/fields.py b/bookwyrm/models/fields.py index b8efc71d0..5e12f5d56 100644 --- a/bookwyrm/models/fields.py +++ b/bookwyrm/models/fields.py @@ -283,6 +283,8 @@ class TagField(ManyToManyField): for link_json in value: link = activitypub.Link(**link_json) tag_type = link.type if link.type != 'Mention' else 'Person' + if tag_type == 'Book': + tag_type = 'Edition' if tag_type != self.related_model.activity_serializer.type: # tags can contain multiple types continue From da05b99bb8ce80391175689149a6587a8f89895d Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 15 Dec 2020 18:57:17 -0800 Subject: [PATCH 09/63] Fixes incoming shelve activity --- bookwyrm/activitypub/verbs.py | 4 ++-- bookwyrm/incoming.py | 1 - bookwyrm/models/shelf.py | 4 ++-- bookwyrm/tests/test_incoming.py | 22 ++++++++++++++++++++++ bw-dev | 3 ++- 5 files changed, 28 insertions(+), 6 deletions(-) diff --git a/bookwyrm/activitypub/verbs.py b/bookwyrm/activitypub/verbs.py index e890d81fc..7c6279279 100644 --- a/bookwyrm/activitypub/verbs.py +++ b/bookwyrm/activitypub/verbs.py @@ -3,7 +3,7 @@ from dataclasses import dataclass from typing import List from .base_activity import ActivityObject, Signature -from .book import Book +from .book import Edition @dataclass(init=False) class Verb(ActivityObject): @@ -73,7 +73,7 @@ class Add(Verb): @dataclass(init=False) class AddBook(Verb): '''Add activity that's aware of the book obj ''' - target: Book + target: Edition type: str = 'Add' diff --git a/bookwyrm/incoming.py b/bookwyrm/incoming.py index 9c8c28878..556c34a27 100644 --- a/bookwyrm/incoming.py +++ b/bookwyrm/incoming.py @@ -57,7 +57,6 @@ def shared_inbox(request): 'Announce': handle_boost, 'Add': { 'Edition': handle_add, - 'Work': handle_add, }, 'Undo': { 'Follow': handle_unfollow, diff --git a/bookwyrm/models/shelf.py b/bookwyrm/models/shelf.py index 68f3614fb..69df43b4c 100644 --- a/bookwyrm/models/shelf.py +++ b/bookwyrm/models/shelf.py @@ -3,7 +3,7 @@ import re from django.db import models from bookwyrm import activitypub -from .base_model import BookWyrmModel +from .base_model import ActivitypubMixin, BookWyrmModel from .base_model import OrderedCollectionMixin from . import fields @@ -51,7 +51,7 @@ class Shelf(OrderedCollectionMixin, BookWyrmModel): unique_together = ('user', 'identifier') -class ShelfBook(BookWyrmModel): +class ShelfBook(ActivitypubMixin, BookWyrmModel): ''' many to many join table for books and shelves ''' book = fields.ForeignKey( 'Edition', on_delete=models.PROTECT, activitypub_field='object') diff --git a/bookwyrm/tests/test_incoming.py b/bookwyrm/tests/test_incoming.py index d8e85ef2a..a317ef714 100644 --- a/bookwyrm/tests/test_incoming.py +++ b/bookwyrm/tests/test_incoming.py @@ -433,6 +433,28 @@ class Incoming(TestCase): boosted_status=self.status, user=self.remote_user) incoming.handle_unboost(activity) + + def test_handle_add_book(self): + ''' shelving a book ''' + book = models.Edition.objects.create( + title='Test', remote_id='https://bookwyrm.social/book/37292') + shelf = models.Shelf.objects.create( + user=self.remote_user, name='Test Shelf') + shelf.remote_id = 'https://bookwyrm.social/user/mouse/shelf/to-read' + shelf.save() + + activity = { + "id": "https://bookwyrm.social/shelfbook/6189#add", + "type": "Add", + "actor": "hhttps://example.com/users/rat", + "object": "https://bookwyrm.social/book/37292", + "target": "https://bookwyrm.social/user/mouse/shelf/to-read", + "@context": "https://www.w3.org/ns/activitystreams" + } + incoming.handle_add(activity) + self.assertEqual(shelf.books.first(), book) + + def test_handle_update_user(self): ''' update an existing user ''' datafile = pathlib.Path(__file__).parent.joinpath( diff --git a/bw-dev b/bw-dev index 53c8e52d9..bf5e8f758 100755 --- a/bw-dev +++ b/bw-dev @@ -57,7 +57,8 @@ case "$1" in clean ;; makemigrations) - execweb python manage.py makemigrations + shift 1 + execweb python manage.py makemigrations "$@" ;; migrate) execweb python manage.py rename_app fedireads bookwyrm From 729e50de63f52e9b863c7815b970242880db0f00 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 16 Dec 2020 09:15:26 -0800 Subject: [PATCH 10/63] Show consistent book status regardless of edition --- bookwyrm/templates/snippets/shelve_button.html | 18 +++++++++--------- bookwyrm/templatetags/bookwyrm_tags.py | 9 +++++---- bookwyrm/views.py | 2 +- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/bookwyrm/templates/snippets/shelve_button.html b/bookwyrm/templates/snippets/shelve_button.html index d452169eb..afbdc9700 100644 --- a/bookwyrm/templates/snippets/shelve_button.html +++ b/bookwyrm/templates/snippets/shelve_button.html @@ -4,24 +4,24 @@ {% with book.id|uuid as uuid %} {% active_shelf book as active_shelf %}
- {% if active_shelf.identifier == 'read' %} + {% if active_shelf.shelf.identifier == 'read' %} - {% elif active_shelf.identifier == 'reading' %} + {% elif active_shelf.shelf.identifier == 'reading' %} - {% include 'snippets/finish_reading_modal.html' %} - {% elif active_shelf.identifier == 'to-read' %} + {% include 'snippets/finish_reading_modal.html' with book=active_shelf.book %} + {% elif active_shelf.shelf.identifier == 'to-read' %} - {% include 'snippets/start_reading_modal.html' %} + {% include 'snippets/start_reading_modal.html' with book=active_shelf.book %} {% else %}
{% csrf_token %} - +
@@ -40,17 +40,17 @@
+ {% endif %} {% endwith %} {% endif %} diff --git a/bookwyrm/templates/snippets/switch_edition_button.html b/bookwyrm/templates/snippets/switch_edition_button.html index 9771535a0..685aed7c3 100644 --- a/bookwyrm/templates/snippets/switch_edition_button.html +++ b/bookwyrm/templates/snippets/switch_edition_button.html @@ -1,5 +1,5 @@ {% csrf_token %} - + From d79a756813e2894fa6a4c28c28c05716fb4f20b8 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 16 Dec 2020 15:59:42 -0800 Subject: [PATCH 14/63] Fixes pure status serializer --- bookwyrm/models/base_model.py | 4 ++-- bookwyrm/models/status.py | 2 +- bookwyrm/outgoing.py | 5 ++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/bookwyrm/models/base_model.py b/bookwyrm/models/base_model.py index 08cc60529..0de61fd1b 100644 --- a/bookwyrm/models/base_model.py +++ b/bookwyrm/models/base_model.py @@ -151,9 +151,9 @@ class ActivitypubMixin: return self.activity_serializer(**activity).serialize() - def to_create_activity(self, user): + def to_create_activity(self, user, **kwargs): ''' returns the object wrapped in a Create activity ''' - activity_object = self.to_activity() + activity_object = self.to_activity(**kwargs) signer = pkcs1_15.new(RSA.import_key(user.key_pair.private_key)) content = activity_object['content'] diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index f2d0279fe..fcf4a2907 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -78,7 +78,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): activity['replies'] = self.to_replies() # "pure" serialization for non-bookwyrm instances - if pure: + if pure and hasattr(self, 'pure_content'): activity['content'] = self.pure_content if 'name' in activity: activity['name'] = self.pure_name diff --git a/bookwyrm/outgoing.py b/bookwyrm/outgoing.py index 38b482824..475bfc218 100644 --- a/bookwyrm/outgoing.py +++ b/bookwyrm/outgoing.py @@ -252,9 +252,8 @@ def handle_status(user, form): broadcast(user, status.to_create_activity(user), software='bookwyrm') # re-format the activity for non-bookwyrm servers - if hasattr(status, 'pure_activity_serializer'): - remote_activity = status.to_create_activity(user, pure=True) - broadcast(user, remote_activity, software='other') + remote_activity = status.to_create_activity(user, pure=True) + broadcast(user, remote_activity, software='other') def handle_tag(user, tag): From f1926ce76d79d5a9917f7e75ca7c4352e9af4176 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 16 Dec 2020 16:20:40 -0800 Subject: [PATCH 15/63] Avoid duplicate notifitions And render html --- bookwyrm/incoming.py | 5 ++++- bookwyrm/templates/notifications.html | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/bookwyrm/incoming.py b/bookwyrm/incoming.py index 556c34a27..78e7b9707 100644 --- a/bookwyrm/incoming.py +++ b/bookwyrm/incoming.py @@ -218,7 +218,9 @@ def handle_create(activity): status = activity.to_model(model) # create a notification if this is a reply + notified = [] if status.reply_parent and status.reply_parent.user.local: + notified.append(status.reply_parent.user) status_builder.create_notification( status.reply_parent.user, 'REPLY', @@ -226,7 +228,8 @@ def handle_create(activity): related_status=status, ) if status.mention_users.exists(): - for mentioned_user in status.mention_users.all(): + for mentioned_user in status.mention_users.all() and \ + mentioned_user not in notified: if not mentioned_user.local: continue status_builder.create_notification( diff --git a/bookwyrm/templates/notifications.html b/bookwyrm/templates/notifications.html index f31df76d8..ddcbc0fda 100644 --- a/bookwyrm/templates/notifications.html +++ b/bookwyrm/templates/notifications.html @@ -54,7 +54,7 @@
{{ notification.related_status.published_date | post_date }} From a3c7d324d67fe29202e76fb332b7c328e61faf3c Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 16 Dec 2020 16:47:05 -0800 Subject: [PATCH 16/63] Sanitize incoming html --- .../migrations/0025_auto_20201217_0046.py | 39 +++++++++++++++++++ bookwyrm/models/author.py | 2 +- bookwyrm/models/book.py | 2 +- bookwyrm/models/fields.py | 10 +++++ bookwyrm/models/status.py | 4 +- bookwyrm/models/user.py | 2 +- bookwyrm/sanitize_html.py | 2 +- bookwyrm/tests/test_sanitize_html.py | 12 +++--- 8 files changed, 62 insertions(+), 11 deletions(-) create mode 100644 bookwyrm/migrations/0025_auto_20201217_0046.py diff --git a/bookwyrm/migrations/0025_auto_20201217_0046.py b/bookwyrm/migrations/0025_auto_20201217_0046.py new file mode 100644 index 000000000..a3ffe8c13 --- /dev/null +++ b/bookwyrm/migrations/0025_auto_20201217_0046.py @@ -0,0 +1,39 @@ +# Generated by Django 3.0.7 on 2020-12-17 00:46 + +import bookwyrm.models.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0024_merge_20201216_1721'), + ] + + operations = [ + migrations.AlterField( + model_name='author', + name='bio', + field=bookwyrm.models.fields.HtmlField(blank=True, null=True), + ), + migrations.AlterField( + model_name='book', + name='description', + field=bookwyrm.models.fields.HtmlField(blank=True, null=True), + ), + migrations.AlterField( + model_name='quotation', + name='quote', + field=bookwyrm.models.fields.HtmlField(), + ), + migrations.AlterField( + model_name='status', + name='content', + field=bookwyrm.models.fields.HtmlField(blank=True, null=True), + ), + migrations.AlterField( + model_name='user', + name='summary', + field=bookwyrm.models.fields.HtmlField(default=''), + ), + ] diff --git a/bookwyrm/models/author.py b/bookwyrm/models/author.py index 331d2dd6f..47714d4ec 100644 --- a/bookwyrm/models/author.py +++ b/bookwyrm/models/author.py @@ -25,7 +25,7 @@ class Author(ActivitypubMixin, BookWyrmModel): aliases = fields.ArrayField( models.CharField(max_length=255), blank=True, default=list ) - bio = fields.TextField(null=True, blank=True) + bio = fields.HtmlField(null=True, blank=True) def save(self, *args, **kwargs): ''' can't be abstract for query reasons, but you shouldn't USE it ''' diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index bcd4bc046..3080a1158 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -36,7 +36,7 @@ class Book(ActivitypubMixin, BookWyrmModel): title = fields.CharField(max_length=255) sort_title = fields.CharField(max_length=255, blank=True, null=True) subtitle = fields.CharField(max_length=255, blank=True, null=True) - description = fields.TextField(blank=True, null=True) + description = fields.HtmlField(blank=True, null=True) languages = fields.ArrayField( models.CharField(max_length=255), blank=True, default=list ) diff --git a/bookwyrm/models/fields.py b/bookwyrm/models/fields.py index 5e12f5d56..529337150 100644 --- a/bookwyrm/models/fields.py +++ b/bookwyrm/models/fields.py @@ -12,6 +12,7 @@ from django.db import models from django.utils import timezone from django.utils.translation import gettext_lazy as _ from bookwyrm import activitypub +from bookwyrm.sanitize_html import InputHtmlParser from bookwyrm.settings import DOMAIN from bookwyrm.connectors import get_image @@ -362,6 +363,15 @@ class DateTimeField(ActivitypubFieldMixin, models.DateTimeField): except (ParserError, TypeError): return None +class HtmlField(ActivitypubFieldMixin, models.TextField): + ''' a text field for storing html ''' + def field_from_activity(self, value): + if not value or value == MISSING: + return None + sanitizer = InputHtmlParser() + sanitizer.feed(value) + return sanitizer.get_output() + class ArrayField(ActivitypubFieldMixin, DjangoArrayField): ''' activitypub-aware array field ''' def field_to_activity(self, value): diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index fcf4a2907..66114e7cc 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -14,7 +14,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): ''' any post, like a reply to a review, etc ''' user = fields.ForeignKey( 'User', on_delete=models.PROTECT, activitypub_field='attributedTo') - content = fields.TextField(blank=True, null=True) + content = fields.HtmlField(blank=True, null=True) mention_users = fields.TagField('User', related_name='mention_user') mention_books = fields.TagField('Edition', related_name='mention_book') local = models.BooleanField(default=True) @@ -134,7 +134,7 @@ class Comment(Status): class Quotation(Status): ''' like a review but without a rating and transient ''' - quote = fields.TextField() + quote = fields.HtmlField() book = fields.ForeignKey( 'Edition', on_delete=models.PROTECT, activitypub_field='inReplyToBook') diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index 63549d360..9d66eb5cf 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -42,7 +42,7 @@ class User(OrderedCollectionPageMixin, AbstractUser): blank=True, ) outbox = fields.RemoteIdField(unique=True) - summary = fields.TextField(default='') + summary = fields.HtmlField(default='') local = models.BooleanField(default=False) bookwyrm_user = fields.BooleanField(default=True) localname = models.CharField( diff --git a/bookwyrm/sanitize_html.py b/bookwyrm/sanitize_html.py index 9c5ca73ac..933fc43c6 100644 --- a/bookwyrm/sanitize_html.py +++ b/bookwyrm/sanitize_html.py @@ -1,7 +1,7 @@ ''' html parser to clean up incoming text from unknown sources ''' from html.parser import HTMLParser -class InputHtmlParser(HTMLParser): +class InputHtmlParser(HTMLParser):#pylint: disable=abstract-method ''' Removes any html that isn't allowed_tagsed from a block ''' def __init__(self): diff --git a/bookwyrm/tests/test_sanitize_html.py b/bookwyrm/tests/test_sanitize_html.py index 3344a9347..58d94311c 100644 --- a/bookwyrm/tests/test_sanitize_html.py +++ b/bookwyrm/tests/test_sanitize_html.py @@ -1,34 +1,36 @@ +''' make sure only valid html gets to the app ''' from django.test import TestCase from bookwyrm.sanitize_html import InputHtmlParser - class Sanitizer(TestCase): + ''' sanitizer tests ''' def test_no_html(self): + ''' just text ''' input_text = 'no html ' parser = InputHtmlParser() parser.feed(input_text) output = parser.get_output() self.assertEqual(input_text, output) - def test_valid_html(self): + ''' leave the html untouched ''' input_text = 'yes html' parser = InputHtmlParser() parser.feed(input_text) output = parser.get_output() self.assertEqual(input_text, output) - def test_valid_html_attrs(self): + ''' and don't remove attributes ''' input_text = 'yes html' parser = InputHtmlParser() parser.feed(input_text) output = parser.get_output() self.assertEqual(input_text, output) - def test_invalid_html(self): + ''' remove all html when the html is malformed ''' input_text = 'yes html' parser = InputHtmlParser() parser.feed(input_text) @@ -41,8 +43,8 @@ class Sanitizer(TestCase): output = parser.get_output() self.assertEqual('yes html ', output) - def test_disallowed_html(self): + ''' remove disallowed html but keep allowed html ''' input_text = '
yes html
' parser = InputHtmlParser() parser.feed(input_text) From 42167af3e9dad8aa040b74f66f4b50c0ea98a77b Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 16 Dec 2020 18:39:18 -0800 Subject: [PATCH 17/63] Tests fro html field --- bookwyrm/tests/models/test_fields.py | 63 +++++++++++++++++++++------- 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/bookwyrm/tests/models/test_fields.py b/bookwyrm/tests/models/test_fields.py index 8c86b23ce..81b1d528a 100644 --- a/bookwyrm/tests/models/test_fields.py +++ b/bookwyrm/tests/models/test_fields.py @@ -21,31 +21,23 @@ from bookwyrm.activitypub.base_activity import ActivityObject from bookwyrm.models import fields, User, Status from bookwyrm.models.base_model import ActivitypubMixin, BookWyrmModel +#pylint: disable=too-many-public-methods class ActivitypubFields(TestCase): ''' overwrites standard model feilds to work with activitypub ''' def test_validate_remote_id(self): ''' should look like a url ''' - self.assertIsNone(fields.validate_remote_id( - 'http://www.example.com' - )) - self.assertIsNone(fields.validate_remote_id( - 'https://www.example.com' - )) - self.assertIsNone(fields.validate_remote_id( - 'http://example.com/dlfjg-23/x' - )) + self.assertIsNone(fields.validate_remote_id('http://www.example.com')) + self.assertIsNone(fields.validate_remote_id('https://www.example.com')) + self.assertIsNone(fields.validate_remote_id('http://exle.com/dlg-23/x')) self.assertRaises( ValidationError, fields.validate_remote_id, - 'http:/example.com/dlfjg-23/x' - ) + 'http:/example.com/dlfjg-23/x') self.assertRaises( ValidationError, fields.validate_remote_id, - 'www.example.com/dlfjg-23/x' - ) + 'www.example.com/dlfjg-23/x') self.assertRaises( ValidationError, fields.validate_remote_id, - 'http://www.example.com/dlfjg 23/x' - ) + 'http://www.example.com/dlfjg 23/x') def test_activitypub_field_mixin(self): ''' generic mixin with super basic to and from functionality ''' @@ -71,6 +63,38 @@ class ActivitypubFields(TestCase): instance.name = 'snake_case_name' self.assertEqual(instance.get_activitypub_field(), 'snakeCaseName') + def test_set_field_from_activity(self): + ''' setter from entire json blob ''' + @dataclass + class TestModel: + ''' real simple mock ''' + field_name: str + + mock_model = TestModel(field_name='bip') + TestActivity = namedtuple('test', ('fieldName', 'unrelated')) + data = TestActivity(fieldName='hi', unrelated='bfkjh') + + instance = fields.ActivitypubFieldMixin() + instance.name = 'field_name' + + instance.set_field_from_activity(mock_model, data) + self.assertEqual(mock_model.field_name, 'hi') + + def test_set_activity_from_field(self): + ''' set json field given entire model ''' + @dataclass + class TestModel: + ''' real simple mock ''' + field_name: str + unrelated: str + mock_model = TestModel(field_name='bip', unrelated='field') + instance = fields.ActivitypubFieldMixin() + instance.name = 'field_name' + + data = {} + instance.set_activity_from_field(data, mock_model) + self.assertEqual(data['fieldName'], 'bip') + def test_remote_id_field(self): ''' just sets some defaults on charfield ''' instance = fields.RemoteIdField() @@ -408,3 +432,12 @@ class ActivitypubFields(TestCase): ''' idk why it makes them strings but probably for a good reason ''' instance = fields.ArrayField(fields.IntegerField) self.assertEqual(instance.field_to_activity([0, 1]), ['0', '1']) + + + def test_html_field(self): + ''' sanitizes html, the sanitizer has its own tests ''' + instance = fields.HtmlField() + self.assertEqual( + instance.field_from_activity('

hi

'), + '

hi

' + ) From f7cb52598144aa896e8d9518dbb67b03b1b8b70c Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 16 Dec 2020 18:40:43 -0800 Subject: [PATCH 18/63] Fixes logic error --- bookwyrm/incoming.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bookwyrm/incoming.py b/bookwyrm/incoming.py index 78e7b9707..c1c15ca93 100644 --- a/bookwyrm/incoming.py +++ b/bookwyrm/incoming.py @@ -228,9 +228,8 @@ def handle_create(activity): related_status=status, ) if status.mention_users.exists(): - for mentioned_user in status.mention_users.all() and \ - mentioned_user not in notified: - if not mentioned_user.local: + for mentioned_user in status.mention_users.all(): + if not mentioned_user.local or mentioned_user in notified: continue status_builder.create_notification( mentioned_user, From b796686483e26011a22954673d143128b150e3b9 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 16 Dec 2020 19:20:15 -0800 Subject: [PATCH 19/63] Adds cw field --- .../migrations/0026_status_content_warning.py | 19 +++++++++++++++++++ bookwyrm/models/status.py | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 bookwyrm/migrations/0026_status_content_warning.py diff --git a/bookwyrm/migrations/0026_status_content_warning.py b/bookwyrm/migrations/0026_status_content_warning.py new file mode 100644 index 000000000..f4e494db9 --- /dev/null +++ b/bookwyrm/migrations/0026_status_content_warning.py @@ -0,0 +1,19 @@ +# Generated by Django 3.0.7 on 2020-12-17 03:17 + +import bookwyrm.models.fields +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0025_auto_20201217_0046'), + ] + + operations = [ + migrations.AddField( + model_name='status', + name='content_warning', + field=bookwyrm.models.fields.CharField(blank=True, max_length=500, null=True), + ), + ] diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index 18b9431e4..49fbad55f 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -19,7 +19,7 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): mention_books = fields.TagField('Edition', related_name='mention_book') local = models.BooleanField(default=True) content_warning = fields.CharField( - max_length=150, blank=True, null=True, activitypub_field='summary') + max_length=500, blank=True, null=True, activitypub_field='summary') privacy = fields.PrivacyField(max_length=255) sensitive = fields.BooleanField(default=False) # created date is different than publish date because of federated posts From 0d42b9cf8f765e2b6338b064f4d213a4130d2793 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 16 Dec 2020 19:50:36 -0800 Subject: [PATCH 20/63] Display status cw's --- .../templates/snippets/status_content.html | 77 +++++++++++-------- 1 file changed, 47 insertions(+), 30 deletions(-) diff --git a/bookwyrm/templates/snippets/status_content.html b/bookwyrm/templates/snippets/status_content.html index 2ef06421a..ff87b3b6a 100644 --- a/bookwyrm/templates/snippets/status_content.html +++ b/bookwyrm/templates/snippets/status_content.html @@ -1,38 +1,55 @@ {% load bookwyrm_tags %}
- {% if status.status_type == 'Review' %} -

- {% if status.name %}{{ status.name }}
{% endif %} - {% include 'snippets/stars.html' with rating=status.rating %} -

- {% endif %} - - {% if status.quote %} -
-
{{ status.quote }}
- -

— {% include 'snippets/book_titleby.html' with book=status.book %}

-
- {% endif %} - - {% if status.content and status.status_type != 'GeneratedNote' and status.status_type != 'Boost' %} - {% include 'snippets/trimmed_text.html' with full=status.content|safe %} - {% endif %} - {% if status.attachments %} -
-
- {% for attachment in status.attachments.all %} -
-
- - {{ attachment.caption }} - -
-
- {% endfor %} + {% if status.content_warning %} +
+

{{ status.content_warning }}

+ +
+ + {% endif %} +
{% if not hide_book %} From 172c36b6416c05997e18ce0b75a6ed143bf34ae2 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 16 Dec 2020 20:10:50 -0800 Subject: [PATCH 21/63] Adds content warning field in status forms --- bookwyrm/activitypub/note.py | 2 +- bookwyrm/forms.py | 12 ++++++++---- .../templates/snippets/create_status_form.html | 10 ++++++++++ bookwyrm/templates/snippets/reply_form.html | 8 ++++++++ bookwyrm/templates/snippets/status_content.html | 16 +++++++++------- 5 files changed, 36 insertions(+), 12 deletions(-) diff --git a/bookwyrm/activitypub/note.py b/bookwyrm/activitypub/note.py index 263ddb2a6..b478c96dd 100644 --- a/bookwyrm/activitypub/note.py +++ b/bookwyrm/activitypub/note.py @@ -54,7 +54,7 @@ class Comment(Note): class Review(Comment): ''' a full book review ''' name: str - rating: int + rating: int = None type: str = 'Review' diff --git a/bookwyrm/forms.py b/bookwyrm/forms.py index a2c3e24bb..454836bb7 100644 --- a/bookwyrm/forms.py +++ b/bookwyrm/forms.py @@ -60,25 +60,29 @@ class RatingForm(CustomForm): class ReviewForm(CustomForm): class Meta: model = models.Review - fields = ['user', 'book', 'name', 'content', 'rating', 'privacy'] + fields = [ + 'user', 'book', 'name', 'content', 'content_warning', 'rating', + 'privacy'] class CommentForm(CustomForm): class Meta: model = models.Comment - fields = ['user', 'book', 'content', 'privacy'] + fields = ['user', 'book', 'content', 'content_warning', 'privacy'] class QuotationForm(CustomForm): class Meta: model = models.Quotation - fields = ['user', 'book', 'quote', 'content', 'privacy'] + fields = [ + 'user', 'book', 'quote', 'content', 'content_warning', 'privacy'] class ReplyForm(CustomForm): class Meta: model = models.Status - fields = ['user', 'content', 'reply_parent', 'privacy'] + fields = [ + 'user', 'content', 'content_warning', 'reply_parent', 'privacy'] class EditUserForm(CustomForm): diff --git a/bookwyrm/templates/snippets/create_status_form.html b/bookwyrm/templates/snippets/create_status_form.html index d6aa3fb3c..70062db4d 100644 --- a/bookwyrm/templates/snippets/create_status_form.html +++ b/bookwyrm/templates/snippets/create_status_form.html @@ -26,6 +26,16 @@
{% endif %} + +
+ + + +
+ {% if type == 'quote' %} {% else %} diff --git a/bookwyrm/templates/snippets/reply_form.html b/bookwyrm/templates/snippets/reply_form.html index d0a0f6b9d..65aa3e46a 100644 --- a/bookwyrm/templates/snippets/reply_form.html +++ b/bookwyrm/templates/snippets/reply_form.html @@ -6,6 +6,14 @@
+
+ + + +
diff --git a/bookwyrm/templates/snippets/status_content.html b/bookwyrm/templates/snippets/status_content.html index ff87b3b6a..6e4a6b983 100644 --- a/bookwyrm/templates/snippets/status_content.html +++ b/bookwyrm/templates/snippets/status_content.html @@ -1,5 +1,14 @@ {% load bookwyrm_tags %}
+ {% if status.status_type == 'Review' %} +
+

+ {% if status.name %}{{ status.name }}
{% endif %} +

+

{% include 'snippets/stars.html' with rating=status.rating %}

+
+ {% endif %} + {% if status.content_warning %}

{{ status.content_warning }}

@@ -16,13 +25,6 @@ {% endif %} - {% if status.status_type == 'Review' %} -

- {% if status.name %}{{ status.name }}
{% endif %} - {% include 'snippets/stars.html' with rating=status.rating %} -

- {% endif %} - {% if status.quote %}
{{ status.quote }}
From 34a2afc788061da8dc8f8472dbea68315c29fd9d Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 17 Dec 2020 11:05:37 -0800 Subject: [PATCH 22/63] Change text on cw button --- .../snippets/content_warning_field.html | 18 ++++++++++++++++++ .../templates/snippets/create_status_form.html | 9 +-------- bookwyrm/templates/snippets/reply_form.html | 11 ++--------- 3 files changed, 21 insertions(+), 17 deletions(-) create mode 100644 bookwyrm/templates/snippets/content_warning_field.html diff --git a/bookwyrm/templates/snippets/content_warning_field.html b/bookwyrm/templates/snippets/content_warning_field.html new file mode 100644 index 000000000..1787c0a86 --- /dev/null +++ b/bookwyrm/templates/snippets/content_warning_field.html @@ -0,0 +1,18 @@ +
+
+ + +
+ +
+ + +
+
+ diff --git a/bookwyrm/templates/snippets/create_status_form.html b/bookwyrm/templates/snippets/create_status_form.html index 70062db4d..a0c6b817d 100644 --- a/bookwyrm/templates/snippets/create_status_form.html +++ b/bookwyrm/templates/snippets/create_status_form.html @@ -27,14 +27,7 @@ {% endif %} -
- - - -
+ {% include 'snippets/content_warning_field.html' %} {% if type == 'quote' %} diff --git a/bookwyrm/templates/snippets/reply_form.html b/bookwyrm/templates/snippets/reply_form.html index 65aa3e46a..787a3ac54 100644 --- a/bookwyrm/templates/snippets/reply_form.html +++ b/bookwyrm/templates/snippets/reply_form.html @@ -6,19 +6,12 @@
-
- - - -
+ + {% include 'snippets/content_warning_field.html' with parent_status=activity %}
-
{% include 'snippets/privacy_select.html' with current=activity.privacy %} From 2799ed68e30c8078c13cd0e4bc423a27bc71df82 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 17 Dec 2020 11:32:09 -0800 Subject: [PATCH 23/63] Propogate content warning from parent --- bookwyrm/forms.py | 15 +++++++++++---- bookwyrm/outgoing.py | 6 +++++- .../templates/snippets/content_warning_field.html | 15 ++++++++------- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/bookwyrm/forms.py b/bookwyrm/forms.py index 454836bb7..1422b4b9a 100644 --- a/bookwyrm/forms.py +++ b/bookwyrm/forms.py @@ -61,28 +61,35 @@ class ReviewForm(CustomForm): class Meta: model = models.Review fields = [ - 'user', 'book', 'name', 'content', 'content_warning', 'rating', + 'user', 'book', + 'name', 'content', 'rating', + 'content_warning', 'sensitive', 'privacy'] class CommentForm(CustomForm): class Meta: model = models.Comment - fields = ['user', 'book', 'content', 'content_warning', 'privacy'] + fields = [ + 'user', 'book', 'content', + 'content_warning', 'sensitive', + 'privacy'] class QuotationForm(CustomForm): class Meta: model = models.Quotation fields = [ - 'user', 'book', 'quote', 'content', 'content_warning', 'privacy'] + 'user', 'book', 'quote', 'content', + 'content_warning', 'sensitive', 'privacy'] class ReplyForm(CustomForm): class Meta: model = models.Status fields = [ - 'user', 'content', 'content_warning', 'reply_parent', 'privacy'] + 'user', 'content', 'content_warning', 'sensitive', + 'reply_parent', 'privacy'] class EditUserForm(CustomForm): diff --git a/bookwyrm/outgoing.py b/bookwyrm/outgoing.py index 475bfc218..65a253e95 100644 --- a/bookwyrm/outgoing.py +++ b/bookwyrm/outgoing.py @@ -209,7 +209,11 @@ def handle_delete_status(user, status): def handle_status(user, form): ''' generic handler for statuses ''' - status = form.save() + status = form.save(commit=False) + if not status.sensitive and status.content_warning: + # the cw text field remains populated hen you click "remove" + status.content_warning = None + status.save() # inspect the text for user tags text = status.content diff --git a/bookwyrm/templates/snippets/content_warning_field.html b/bookwyrm/templates/snippets/content_warning_field.html index 1787c0a86..e2f561f24 100644 --- a/bookwyrm/templates/snippets/content_warning_field.html +++ b/bookwyrm/templates/snippets/content_warning_field.html @@ -1,18 +1,19 @@ +{% with uuid as uuid %}
- +
- +
- +{% endwith %} From 04ba5abf450eb23282590f847cd23415bf237ff8 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 17 Dec 2020 12:02:59 -0800 Subject: [PATCH 24/63] Fixes infinite recursion when loading books Also fixes bug in serializing page nubmers --- bookwyrm/activitypub/book.py | 2 +- bookwyrm/models/book.py | 3 ++- bookwyrm/models/fields.py | 10 ++++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/bookwyrm/activitypub/book.py b/bookwyrm/activitypub/book.py index ae9c334da..93cd384fe 100644 --- a/bookwyrm/activitypub/book.py +++ b/bookwyrm/activitypub/book.py @@ -38,7 +38,7 @@ class Edition(Book): isbn13: str = '' oclcNumber: str = '' asin: str = '' - pages: str = '' + pages: int = None physicalFormat: str = '' publishers: List[str] = field(default_factory=lambda: []) diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index 3080a1158..8d441e8ba 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -92,7 +92,8 @@ class Work(OrderedCollectionPageMixin, Book): default_edition = fields.ForeignKey( 'Edition', on_delete=models.PROTECT, - null=True + null=True, + load_remote=False ) def get_default_edition(self): diff --git a/bookwyrm/models/fields.py b/bookwyrm/models/fields.py index 529337150..082ac53ba 100644 --- a/bookwyrm/models/fields.py +++ b/bookwyrm/models/fields.py @@ -100,12 +100,19 @@ class ActivitypubFieldMixin: class ActivitypubRelatedFieldMixin(ActivitypubFieldMixin): ''' default (de)serialization for foreign key and one to one ''' + def __init__(self, *args, load_remote=True, **kwargs): + self.load_remote = load_remote + super().__init__(*args, **kwargs) + def field_from_activity(self, value): if not value: return None related_model = self.related_model if isinstance(value, dict) and value.get('id'): + if not self.load_remote: + # only look in the local database + return related_model.find_existing(value) # this is an activitypub object, which we can deserialize activity_serializer = related_model.activity_serializer return activity_serializer(**value).to_model(related_model) @@ -116,6 +123,9 @@ class ActivitypubRelatedFieldMixin(ActivitypubFieldMixin): # we don't know what this is, ignore it return None # gets or creates the model field from the remote id + if not self.load_remote: + # only look in the local database + return related_model.find_existing_by_remote_id(value) return activitypub.resolve_remote_id(related_model, value) From 39dc0501a5a1abff0a1b2ff67aa4f3b1c734c745 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 17 Dec 2020 12:30:49 -0800 Subject: [PATCH 25/63] Make alt text a model property --- bookwyrm/models/book.py | 15 +++++++++++ bookwyrm/templates/snippets/book_cover.html | 4 +-- bookwyrm/templates/snippets/cover_alt.html | 2 -- bookwyrm/templatetags/bookwyrm_tags.py | 14 ----------- bookwyrm/tests/models/test_book_model.py | 26 +++++++++++++++++++ bookwyrm/tests/test_templatetags.py | 28 --------------------- 6 files changed, 43 insertions(+), 46 deletions(-) delete mode 100644 bookwyrm/templates/snippets/cover_alt.html diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index 3080a1158..c28f0a237 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -58,6 +58,21 @@ class Book(ActivitypubMixin, BookWyrmModel): objects = InheritanceManager() + @property + def edition_info(self): + ''' properties of this edition, as a string ''' + items = [ + self.physical_format if isinstance(self, models.Edition) else None, + self.languages[0] + ' language' if self.languages and \ + self.languages[0] != 'English' else None, + str(self.published_date.year) if self.published_date else None, + ] + return ', '.join(i for i in items if i) + + def alt_text(self): + ''' image alt test ''' + return '%s cover (%s)' % (self.title, self.edition_info) + def save(self, *args, **kwargs): ''' can't be abstract for query reasons, but you shouldn't USE it ''' if not isinstance(self, Edition) and not isinstance(self, Work): diff --git a/bookwyrm/templates/snippets/book_cover.html b/bookwyrm/templates/snippets/book_cover.html index ceeef4262..6d15b37f8 100644 --- a/bookwyrm/templates/snippets/book_cover.html +++ b/bookwyrm/templates/snippets/book_cover.html @@ -1,13 +1,13 @@ {% load bookwyrm_tags %}
{% if book.cover %} - {% include 'snippets/cover_alt.html' with book=book %} +{{ book.alt_text }} {% else %}
No cover

{{ book.title }}

-

({{ book|edition_info }})

+

({{ book.edition_info }})

{% endif %} diff --git a/bookwyrm/templates/snippets/cover_alt.html b/bookwyrm/templates/snippets/cover_alt.html deleted file mode 100644 index 0cccc2e1e..000000000 --- a/bookwyrm/templates/snippets/cover_alt.html +++ /dev/null @@ -1,2 +0,0 @@ -{% load bookwyrm_tags %} -'{{ book.title }}' Cover ({{ book|edition_info }}) diff --git a/bookwyrm/templatetags/bookwyrm_tags.py b/bookwyrm/templatetags/bookwyrm_tags.py index e6460d584..91934d48d 100644 --- a/bookwyrm/templatetags/bookwyrm_tags.py +++ b/bookwyrm/templatetags/bookwyrm_tags.py @@ -97,20 +97,6 @@ def get_boosted(boost): ).get() -@register.filter(name='edition_info') -def get_edition_info(book): - ''' paperback, French language, 1982 ''' - if not book: - return '' - items = [ - book.physical_format if isinstance(book, models.Edition) else None, - book.languages[0] + ' language' if book.languages and \ - book.languages[0] != 'English' else None, - str(book.published_date.year) if book.published_date else None, - ] - return ', '.join(i for i in items if i) - - @register.filter(name='book_description') def get_book_description(book): ''' use the work's text if the book doesn't have it ''' diff --git a/bookwyrm/tests/models/test_book_model.py b/bookwyrm/tests/models/test_book_model.py index ff56b5d8c..76c9829eb 100644 --- a/bookwyrm/tests/models/test_book_model.py +++ b/bookwyrm/tests/models/test_book_model.py @@ -1,5 +1,7 @@ ''' testing models ''' +from dateutil.parser import parse from django.test import TestCase +from django.utils import timezone from bookwyrm import models, settings from bookwyrm.models.book import isbn_10_to_13, isbn_13_to_10 @@ -56,3 +58,27 @@ class Book(TestCase): isbn_13 = '978-1788-16167-1' isbn_10 = isbn_13_to_10(isbn_13) self.assertEqual(isbn_10, '178816167X') + + + def test_get_edition_info(self): + ''' text slug about an edition ''' + book = models.Book.object.create(title='Test Edition') + self.assertEqual(book.edition_info, '') + + book.physical_format = 'worm' + book.save() + self.assertEqual(book.edition_info, 'worm') + + book.languages = ['English'] + book.save() + self.assertEqual(book.edition_info, 'worm') + + book.languages = ['Glorbish', 'English'] + book.save() + self.assertEqual(book.edition_info, 'worm, Glorbish language') + + book.published_date = timezone.make_aware(parse('2020')) + book.save() + self.assertEqual(book.edition_info, 'worm, Glorbish language, 2020') + self.assertEqual( + book.alt_text, 'Test Edition cover (worm, Glorbish language, 2020)') diff --git a/bookwyrm/tests/test_templatetags.py b/bookwyrm/tests/test_templatetags.py index 67d28848c..6956553e5 100644 --- a/bookwyrm/tests/test_templatetags.py +++ b/bookwyrm/tests/test_templatetags.py @@ -158,34 +158,6 @@ class TemplateTags(TestCase): self.assertEqual(boosted, status) - def test_get_edition_info(self): - ''' text slug about an edition ''' - self.assertEqual( - bookwyrm_tags.get_edition_info(self.book), '') - - self.book.physical_format = 'worm' - self.book.save() - self.assertEqual( - bookwyrm_tags.get_edition_info(self.book), 'worm') - - self.book.languages = ['English'] - self.book.save() - self.assertEqual( - bookwyrm_tags.get_edition_info(self.book), 'worm') - - self.book.languages = ['Glorbish', 'English'] - self.book.save() - self.assertEqual( - bookwyrm_tags.get_edition_info(self.book), - 'worm, Glorbish language') - - self.book.published_date = timezone.make_aware(parse('2020')) - self.book.save() - self.assertEqual( - bookwyrm_tags.get_edition_info(self.book), - 'worm, Glorbish language, 2020') - - def test_get_book_description(self): ''' grab it from the edition or the parent ''' work = models.Work.objects.create(title='Test Work') From 530198adea8c136d3506e59b879b0d38ae66952f Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 17 Dec 2020 12:46:05 -0800 Subject: [PATCH 26/63] Serialize alt text of images --- bookwyrm/models/book.py | 6 ++++-- bookwyrm/models/fields.py | 22 ++++++++++++++++++---- bookwyrm/models/status.py | 6 +++--- bookwyrm/models/user.py | 8 +++++++- bookwyrm/templates/snippets/avatar.html | 2 +- bookwyrm/tests/models/test_book_model.py | 2 +- 6 files changed, 34 insertions(+), 12 deletions(-) diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index c28f0a237..b20cb6ef1 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -52,7 +52,8 @@ class Book(ActivitypubMixin, BookWyrmModel): authors = fields.ManyToManyField('Author') # preformatted authorship string for search and easier display author_text = models.CharField(max_length=255, blank=True, null=True) - cover = fields.ImageField(upload_to='covers/', blank=True, null=True) + cover = fields.ImageField( + upload_to='covers/', blank=True, null=True, alt_field='alt_text') first_published_date = fields.DateTimeField(blank=True, null=True) published_date = fields.DateTimeField(blank=True, null=True) @@ -62,13 +63,14 @@ class Book(ActivitypubMixin, BookWyrmModel): def edition_info(self): ''' properties of this edition, as a string ''' items = [ - self.physical_format if isinstance(self, models.Edition) else None, + self.physical_format, self.languages[0] + ' language' if self.languages and \ self.languages[0] != 'English' else None, str(self.published_date.year) if self.published_date else None, ] return ', '.join(i for i in items if i) + @property def alt_text(self): ''' image alt test ''' return '%s cover (%s)' % (self.title, self.edition_info) diff --git a/bookwyrm/models/fields.py b/bookwyrm/models/fields.py index 529337150..e2ccc1ba2 100644 --- a/bookwyrm/models/fields.py +++ b/bookwyrm/models/fields.py @@ -295,18 +295,22 @@ class TagField(ManyToManyField): return items -def image_serializer(value): +def image_serializer(value, alt): ''' helper for serializing images ''' if value and hasattr(value, 'url'): url = value.url else: return None url = 'https://%s%s' % (DOMAIN, url) - return activitypub.Image(url=url) + return activitypub.Image(url=url, name=alt) class ImageField(ActivitypubFieldMixin, models.ImageField): ''' activitypub-aware image field ''' + def __init__(self, *args, alt_field=None, **kwargs): + self.alt_field = alt_field + super().__init__(*args, **kwargs) + # pylint: disable=arguments-differ def set_field_from_activity(self, instance, data, save=True): ''' helper function for assinging a value to the field ''' @@ -316,9 +320,19 @@ class ImageField(ActivitypubFieldMixin, models.ImageField): return getattr(instance, self.name).save(*formatted, save=save) + def set_activity_from_field(self, activity, instance): + value = getattr(instance, self.name) + if value is None: + return + alt_text = getattr(instance, self.alt_field) + formatted = self.field_to_activity(value, alt_text) - def field_to_activity(self, value): - return image_serializer(value) + key = self.get_activitypub_field() + activity[key] = formatted + + + def field_to_activity(self, value, alt=None): + return image_serializer(value, alt) def field_from_activity(self, value): diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index 49fbad55f..b358554c1 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -86,11 +86,11 @@ class Status(OrderedCollectionPageMixin, BookWyrmModel): activity['name'] = self.pure_name activity['type'] = self.pure_type activity['attachment'] = [ - image_serializer(b.cover) for b in self.mention_books.all() \ - if b.cover] + image_serializer(b.cover, b.alt_text) \ + for b in self.mention_books.all()[:4] if b.cover] if hasattr(self, 'book'): activity['attachment'].append( - image_serializer(self.book.cover) + image_serializer(self.book.cover, self.book.alt_text) ) return activity diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index 9d66eb5cf..d5db9949b 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -53,7 +53,8 @@ class User(OrderedCollectionPageMixin, AbstractUser): # name is your display name, which you can change at will name = fields.CharField(max_length=100, default='') avatar = fields.ImageField( - upload_to='avatars/', blank=True, null=True, activitypub_field='icon') + upload_to='avatars/', blank=True, null=True, + activitypub_field='icon', alt_field='alt_text') followers = fields.ManyToManyField( 'self', link_only=True, @@ -90,6 +91,11 @@ class User(OrderedCollectionPageMixin, AbstractUser): last_active_date = models.DateTimeField(auto_now=True) manually_approves_followers = fields.BooleanField(default=False) + @property + def alt_text(self): + ''' alt text with username ''' + return 'avatar for %s' % (self.localname or self.username) + @property def display_name(self): ''' show the cleanest version of the user's name possible ''' diff --git a/bookwyrm/templates/snippets/avatar.html b/bookwyrm/templates/snippets/avatar.html index da4b5fd2f..ca49075cf 100644 --- a/bookwyrm/templates/snippets/avatar.html +++ b/bookwyrm/templates/snippets/avatar.html @@ -1,3 +1,3 @@ {% load bookwyrm_tags %} -avatar for {{ user|username }} +{{ user.alt_text }} diff --git a/bookwyrm/tests/models/test_book_model.py b/bookwyrm/tests/models/test_book_model.py index 76c9829eb..e8b310842 100644 --- a/bookwyrm/tests/models/test_book_model.py +++ b/bookwyrm/tests/models/test_book_model.py @@ -62,7 +62,7 @@ class Book(TestCase): def test_get_edition_info(self): ''' text slug about an edition ''' - book = models.Book.object.create(title='Test Edition') + book = models.Book.objects.create(title='Test Edition') self.assertEqual(book.edition_info, '') book.physical_format = 'worm' From 3f1bf4e1d6cf032421a65732cc8fba27600c66df Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 17 Dec 2020 12:57:56 -0800 Subject: [PATCH 27/63] Set max width on cover container --- bookwyrm/static/css/format.css | 1 + 1 file changed, 1 insertion(+) diff --git a/bookwyrm/static/css/format.css b/bookwyrm/static/css/format.css index 9e8a24ba9..dd5c45eb3 100644 --- a/bookwyrm/static/css/format.css +++ b/bookwyrm/static/css/format.css @@ -65,6 +65,7 @@ input.toggle-control:checked ~ .modal.toggle-content { .cover-container { height: 250px; width: max-content; + max-width: 250px; } .cover-container.is-medium { height: 150px; From 60239c867a04e770fd7b8f542773a617e149dc87 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 17 Dec 2020 13:01:08 -0800 Subject: [PATCH 28/63] Updates unit tests --- bookwyrm/tests/models/test_book_model.py | 2 +- bookwyrm/tests/models/test_fields.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/bookwyrm/tests/models/test_book_model.py b/bookwyrm/tests/models/test_book_model.py index e8b310842..a52133eaf 100644 --- a/bookwyrm/tests/models/test_book_model.py +++ b/bookwyrm/tests/models/test_book_model.py @@ -62,7 +62,7 @@ class Book(TestCase): def test_get_edition_info(self): ''' text slug about an edition ''' - book = models.Book.objects.create(title='Test Edition') + book = models.Edition.objects.create(title='Test Edition') self.assertEqual(book.edition_info, '') book.physical_format = 'worm' diff --git a/bookwyrm/tests/models/test_fields.py b/bookwyrm/tests/models/test_fields.py index 81b1d528a..10c674d99 100644 --- a/bookwyrm/tests/models/test_fields.py +++ b/bookwyrm/tests/models/test_fields.py @@ -393,17 +393,19 @@ class ActivitypubFields(TestCase): ContentFile(output.getvalue()) ) - output = fields.image_serializer(user.avatar) + output = fields.image_serializer(user.avatar, alt='alt text') self.assertIsNotNone( re.match( r'.*\.jpg', output.url, ) ) + self.assertEqual(output.name, 'alt text') self.assertEqual(output.type, 'Image') instance = fields.ImageField() + output = fields.image_serializer(user.avatar, alt=None) self.assertEqual(instance.field_to_activity(user.avatar), output) responses.add( From dccd9ac0524d5669a9c9324a5332885bb94f9fba Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 17 Dec 2020 13:14:44 -0800 Subject: [PATCH 29/63] uuids for content warners loaded correctly --- bookwyrm/templates/snippets/content_warning_field.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bookwyrm/templates/snippets/content_warning_field.html b/bookwyrm/templates/snippets/content_warning_field.html index e2f561f24..9249602af 100644 --- a/bookwyrm/templates/snippets/content_warning_field.html +++ b/bookwyrm/templates/snippets/content_warning_field.html @@ -1,4 +1,5 @@ -{% with uuid as uuid %} +{% load bookwyrm_tags %} +{% with 0|uuid as uuid %}
From 5cadd48aef436df09c3aa4785791a6d936dd6be3 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 17 Dec 2020 13:21:21 -0800 Subject: [PATCH 30/63] Fixes rate action --- bookwyrm/activitypub/note.py | 2 +- bookwyrm/templates/snippets/rate_action.html | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/bookwyrm/activitypub/note.py b/bookwyrm/activitypub/note.py index b478c96dd..72fbe5fc2 100644 --- a/bookwyrm/activitypub/note.py +++ b/bookwyrm/activitypub/note.py @@ -53,7 +53,7 @@ class Comment(Note): @dataclass(init=False) class Review(Comment): ''' a full book review ''' - name: str + name: str = None rating: int = None type: str = 'Review' diff --git a/bookwyrm/templates/snippets/rate_action.html b/bookwyrm/templates/snippets/rate_action.html index b9c443ce5..49cb87ed6 100644 --- a/bookwyrm/templates/snippets/rate_action.html +++ b/bookwyrm/templates/snippets/rate_action.html @@ -4,7 +4,9 @@ {% for i in '12345'|make_list %}
{% csrf_token %} + +