From aa2950daf35041d3e4d4e9b9b330d885daab6e70 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 13 Mar 2021 18:24:35 -0800 Subject: [PATCH 01/16] Render rating status from template --- bookwyrm/models/status.py | 4 +++- bookwyrm/templates/snippets/generated_status/rating.html | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 bookwyrm/templates/snippets/generated_status/rating.html diff --git a/bookwyrm/models/status.py b/bookwyrm/models/status.py index c5e69936f..0e18dc1bc 100644 --- a/bookwyrm/models/status.py +++ b/bookwyrm/models/status.py @@ -5,6 +5,7 @@ import re from django.apps import apps from django.core.validators import MaxValueValidator, MinValueValidator from django.db import models +from django.template.loader import get_template from django.utils import timezone from model_utils.managers import InheritanceManager @@ -309,7 +310,8 @@ class ReviewRating(Review): @property def pure_content(self): - return 'Rated "{}": {:d} stars'.format(self.book.title, self.rating) + template = get_template("snippets/generated_status/rating.html") + return template.render({"book": self.book, "rating": self.rating}).strip() activity_serializer = activitypub.Rating pure_type = "Note" diff --git a/bookwyrm/templates/snippets/generated_status/rating.html b/bookwyrm/templates/snippets/generated_status/rating.html new file mode 100644 index 000000000..e4e42fca5 --- /dev/null +++ b/bookwyrm/templates/snippets/generated_status/rating.html @@ -0,0 +1,5 @@ +{% load i18n %}{% load humanize %}{{% spaceless %} + +{% blocktrans with title=book.title path=book.local_path rating=rating %}Rated {{ title }} : {{ rating }} stars{% endblocktrans %} + +{% endspaceless %} From 220f2c1eb4b214887ce43df95c0407f6043f5ea8 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 15 Mar 2021 13:55:48 -0700 Subject: [PATCH 02/16] Fixes image attachments --- bookwyrm/activitypub/image.py | 4 +-- bookwyrm/models/activitypub_mixin.py | 6 +++- bookwyrm/models/attachment.py | 6 +++- bookwyrm/tests/views/test_feed.py | 45 ++++++++++++++++++++++++++-- 4 files changed, 55 insertions(+), 6 deletions(-) diff --git a/bookwyrm/activitypub/image.py b/bookwyrm/activitypub/image.py index 248e7a4ad..931de977b 100644 --- a/bookwyrm/activitypub/image.py +++ b/bookwyrm/activitypub/image.py @@ -9,5 +9,5 @@ class Image(ActivityObject): url: str name: str = "" - type: str = "Image" - id: str = "" + type: str = "Document" + id: str = None diff --git a/bookwyrm/models/activitypub_mixin.py b/bookwyrm/models/activitypub_mixin.py index 0a3c33a16..23ac16424 100644 --- a/bookwyrm/models/activitypub_mixin.py +++ b/bookwyrm/models/activitypub_mixin.py @@ -445,7 +445,11 @@ def unfurl_related_field(related_field, sort_field=None): unfurl_related_field(i) for i in related_field.order_by(sort_field).all() ] if related_field.reverse_unfurl: - return related_field.field_to_activity() + # if it's a one-to-one (key pair) + if hasattr(related_field, "field_to_activity"): + return related_field.field_to_activity() + # if it's one-to-many (attachments) + return related_field.to_activity() return related_field.remote_id diff --git a/bookwyrm/models/attachment.py b/bookwyrm/models/attachment.py index 0cd2c111f..8d2238a14 100644 --- a/bookwyrm/models/attachment.py +++ b/bookwyrm/models/attachment.py @@ -25,7 +25,11 @@ class Image(Attachment): """ an image attachment """ image = fields.ImageField( - upload_to="status/", null=True, blank=True, activitypub_field="url" + upload_to="status/", + null=True, + blank=True, + activitypub_field="url", + alt_field="caption", ) caption = fields.TextField(null=True, blank=True, activitypub_field="name") diff --git a/bookwyrm/tests/views/test_feed.py b/bookwyrm/tests/views/test_feed.py index c54be0061..426684676 100644 --- a/bookwyrm/tests/views/test_feed.py +++ b/bookwyrm/tests/views/test_feed.py @@ -1,5 +1,10 @@ """ test for app action functionality """ +from io import BytesIO from unittest.mock import patch +import pathlib + +from PIL import Image +from django.core.files.base import ContentFile from django.template.response import TemplateResponse from django.test import TestCase from django.test.client import RequestFactory @@ -9,8 +14,8 @@ from bookwyrm import views from bookwyrm.activitypub import ActivitypubResponse -class FeedMessageViews(TestCase): - """ dms """ +class FeedViews(TestCase): + """ activity feed, statuses, dms """ def setUp(self): """ we need basic test data and mocks """ @@ -59,6 +64,42 @@ class FeedMessageViews(TestCase): self.assertIsInstance(result, ActivitypubResponse) self.assertEqual(result.status_code, 200) + def test_status_page_with_image(self): + """ there are so many views, this just makes sure it LOADS """ + view = views.Status.as_view() + + image_file = pathlib.Path(__file__).parent.joinpath( + "../../static/images/default_avi.jpg" + ) + image = Image.open(image_file) + output = BytesIO() + image.save(output, format=image.format) + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + status = models.Review.objects.create( + content="hi", + user=self.local_user, + book=self.book, + ) + attachment = models.Image.objects.create( + status=status, caption="alt text here" + ) + attachment.image.save("test.jpg", ContentFile(output.getvalue())) + + request = self.factory.get("") + request.user = self.local_user + with patch("bookwyrm.views.feed.is_api_request") as is_api: + is_api.return_value = False + result = view(request, "mouse", status.id) + self.assertIsInstance(result, TemplateResponse) + result.render() + self.assertEqual(result.status_code, 200) + + with patch("bookwyrm.views.feed.is_api_request") as is_api: + is_api.return_value = True + result = view(request, "mouse", status.id) + self.assertIsInstance(result, ActivitypubResponse) + self.assertEqual(result.status_code, 200) + def test_replies_page(self): """ there are so many views, this just makes sure it LOADS """ view = views.Replies.as_view() From 6bd0f94269e317a0e36bf7ea0cba191ad6d38dd3 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 15 Mar 2021 14:09:04 -0700 Subject: [PATCH 03/16] Adds plural version for rating text --- bookwyrm/templates/snippets/generated_status/rating.html | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/bookwyrm/templates/snippets/generated_status/rating.html b/bookwyrm/templates/snippets/generated_status/rating.html index e4e42fca5..13afd94de 100644 --- a/bookwyrm/templates/snippets/generated_status/rating.html +++ b/bookwyrm/templates/snippets/generated_status/rating.html @@ -1,5 +1,3 @@ -{% load i18n %}{% load humanize %}{{% spaceless %} +{% load i18n %}{% load humanize %} -{% blocktrans with title=book.title path=book.local_path rating=rating %}Rated {{ title }} : {{ rating }} stars{% endblocktrans %} - -{% endspaceless %} +{% blocktrans with title=book.title path=book.remote_id rating=rating count counter=rating %}Rated {{ title }}: {{ rating }} star{% plural %}Rated {{ title }}: {{ rating }} stars{% endblocktrans %} From 6e6417cb91afe204bd5a4b04256cb682203d74ff Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 15 Mar 2021 14:26:15 -0700 Subject: [PATCH 07/16] Updates readme from production copy --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3d5ea4a7a..414c01645 100644 --- a/README.md +++ b/README.md @@ -169,13 +169,15 @@ Instructions for running BookWyrm in production: - Set a secure database password for postgres - Update your nginx configuration in `nginx/default.conf` - Replace `your-domain.com` with your domain name + - If you aren't using the `www` subdomain, remove the www.your-domain.com version of the domain from the `server_name` in the first server block in `nginx/default.conf` and remove the `-d www.${DOMAIN}` flag at the end of the `certbot` command in `docker-compose.yml`. - If you are running another web-server on your host machine, you will need to follow the [reverse-proxy instructions](#running-bookwyrm-behind-a-reverse-proxy) - Run the application (this should also set up a Certbot ssl cert for your domain) with `docker-compose up --build`, and make sure all the images build successfully - If you are running other services on your host machine, you may run into errors where services fail when attempting to bind to a port. See the [troubleshooting guide](#port-conflicts) for advice on resolving this. - When docker has built successfully, stop the process with `CTRL-C` - - Comment out the `command: certonly...` line in `docker-compose.yml` + - Comment out the `command: certonly...` line in `docker-compose.yml`, and uncomment the following line (`command: renew ...`) so that the certificate will be automatically renewed. + - Uncomment the https redirect and `server` block in `nginx/default.conf` (lines 17-48). - Run docker-compose in the background with: `docker-compose up -d` - Initialize the database with: `./bw-dev initdb` From f0a936f15ebfd5d73527ac3105341d35f2e82074 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 15 Mar 2021 14:44:03 -0700 Subject: [PATCH 08/16] Add books to lists from the book page --- bookwyrm/templates/book.html | 22 +++++++++++++++++++++- bookwyrm/templates/lists/list.html | 3 ++- bookwyrm/urls.py | 2 +- bookwyrm/views/list.py | 6 +++--- 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/bookwyrm/templates/book.html b/bookwyrm/templates/book.html index ee1ea270b..a96c60962 100644 --- a/bookwyrm/templates/book.html +++ b/bookwyrm/templates/book.html @@ -233,7 +233,7 @@ {% endif %} - {% if lists.exists %} + {% if lists.exists or request.user.list_set.exists %}

{% trans "Lists" %}

+ + {% if request.user.list_set.exists %} +
+ {% csrf_token %} + + +
+
+ +
+
+ +
+
+
+ {% endif %}
{% endif %} diff --git a/bookwyrm/templates/lists/list.html b/bookwyrm/templates/lists/list.html index ddac04f3b..b4f3e5dee 100644 --- a/bookwyrm/templates/lists/list.html +++ b/bookwyrm/templates/lists/list.html @@ -83,9 +83,10 @@

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

-
+ {% csrf_token %} +
diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index 3ef2a79b8..05382e93f 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -118,7 +118,7 @@ urlpatterns = [ re_path(r"^list/?$", views.Lists.as_view(), name="lists"), re_path(r"^list/(?P\d+)(.json)?/?$", views.List.as_view(), name="list"), re_path( - r"^list/(?P\d+)/add/?$", views.list.add_book, name="list-add-book" + r"^list/add-book/?$", views.list.add_book, name="list-add-book" ), re_path( r"^list/(?P\d+)/remove/?$", diff --git a/bookwyrm/views/list.py b/bookwyrm/views/list.py index ba3200d1e..eb82a0f15 100644 --- a/bookwyrm/views/list.py +++ b/bookwyrm/views/list.py @@ -173,9 +173,9 @@ class Curate(View): @require_POST -def add_book(request, list_id): +def add_book(request): """ put a book on a list """ - book_list = get_object_or_404(models.List, id=list_id) + book_list = get_object_or_404(models.List, id=request.POST.get('list')) if not object_visible_to_user(request.user, book_list): return HttpResponseNotFound() @@ -204,7 +204,7 @@ def add_book(request, list_id): # if the book is already on the list, don't flip out pass - return redirect("list", list_id) + return redirect("list", book_list.id) @require_POST From f79bb60d81a2bff5ac5c6ff8e49a81d67fda0387 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 15 Mar 2021 15:08:20 -0700 Subject: [PATCH 09/16] Adds ability to reset default edition based on computer ranks --- bookwyrm/models/book.py | 19 ++++++++++++++++--- bookwyrm/templates/editions.html | 2 +- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index 66b539bbc..5306950c5 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -1,7 +1,7 @@ """ database schema for books and shelves """ import re -from django.db import models +from django.db import models, transaction from model_utils.managers import InheritanceManager from bookwyrm import activitypub @@ -148,6 +148,15 @@ class Work(OrderedCollectionPageMixin, Book): """ in case the default edition is not set """ return self.default_edition or self.editions.order_by("-edition_rank").first() + @transaction.atomic() + def reset_default_edition(self): + """ sets a new default edition based on computed rank """ + self.default_edition = None + # editions are re-ranked implicitly + self.save() + self.default_edition = self.get_default_edition() + self.save() + def to_edition_list(self, **kwargs): """ an ordered collection of editions """ return self.to_ordered_collection( @@ -200,9 +209,13 @@ class Edition(Book): activity_serializer = activitypub.Edition name_field = "title" - def get_rank(self): + def get_rank(self, ignore_default=False): """ calculate how complete the data is on this edition """ - if self.parent_work and self.parent_work.default_edition == self: + if ( + not ignore_default + and self.parent_work + and self.parent_work.default_edition == self + ): # default edition has the highest rank return 20 rank = 0 diff --git a/bookwyrm/templates/editions.html b/bookwyrm/templates/editions.html index 38147a86e..f83197579 100644 --- a/bookwyrm/templates/editions.html +++ b/bookwyrm/templates/editions.html @@ -6,7 +6,7 @@ {% block content %}
-

{% blocktrans with path=work.local_path work_title=work.title %}Editions of "{{ work_title }}"{% endblocktrans %}

+

{% blocktrans with work_path=work.local_path work_title=work.title %}Editions of "{{ work_title }}"{% endblocktrans %}

{% include 'snippets/book_tiles.html' with books=editions %}
From b1335d28111c82606503b8cf4a78b546edb51cf4 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 15 Mar 2021 15:33:05 -0700 Subject: [PATCH 12/16] Formatting --- bookwyrm/urls.py | 4 +--- bookwyrm/views/list.py | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index 05382e93f..199827aef 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -117,9 +117,7 @@ urlpatterns = [ # lists re_path(r"^list/?$", views.Lists.as_view(), name="lists"), re_path(r"^list/(?P\d+)(.json)?/?$", views.List.as_view(), name="list"), - re_path( - r"^list/add-book/?$", views.list.add_book, name="list-add-book" - ), + re_path(r"^list/add-book/?$", views.list.add_book, name="list-add-book"), re_path( r"^list/(?P\d+)/remove/?$", views.list.remove_book, diff --git a/bookwyrm/views/list.py b/bookwyrm/views/list.py index eb82a0f15..91475d480 100644 --- a/bookwyrm/views/list.py +++ b/bookwyrm/views/list.py @@ -175,7 +175,7 @@ class Curate(View): @require_POST def add_book(request): """ put a book on a list """ - book_list = get_object_or_404(models.List, id=request.POST.get('list')) + book_list = get_object_or_404(models.List, id=request.POST.get("list")) if not object_visible_to_user(request.user, book_list): return HttpResponseNotFound() From 0037a22cb63da0b799fe6d27055e03d32bae5624 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 15 Mar 2021 15:41:11 -0700 Subject: [PATCH 13/16] Updates list tests --- bookwyrm/tests/views/test_list.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/bookwyrm/tests/views/test_list.py b/bookwyrm/tests/views/test_list.py index cc895ad17..c2c754539 100644 --- a/bookwyrm/tests/views/test_list.py +++ b/bookwyrm/tests/views/test_list.py @@ -271,11 +271,12 @@ class ListViews(TestCase): "", { "book": self.book.id, + "list": self.list.id, }, ) request.user = self.local_user - views.list.add_book(request, self.list.id) + views.list.add_book(request) item = self.list.listitem_set.get() self.assertEqual(item.book, self.book) self.assertEqual(item.user, self.local_user) @@ -300,11 +301,12 @@ class ListViews(TestCase): "", { "book": self.book.id, + "list": self.list.id, }, ) request.user = self.rat - views.list.add_book(request, self.list.id) + views.list.add_book(request) item = self.list.listitem_set.get() self.assertEqual(item.book, self.book) self.assertEqual(item.user, self.rat) @@ -330,11 +332,12 @@ class ListViews(TestCase): "", { "book": self.book.id, + "list": self.list.id, }, ) request.user = self.rat - views.list.add_book(request, self.list.id) + views.list.add_book(request) item = self.list.listitem_set.get() self.assertEqual(item.book, self.book) self.assertEqual(item.user, self.rat) @@ -360,11 +363,12 @@ class ListViews(TestCase): "", { "book": self.book.id, + "list": self.list.id, }, ) request.user = self.local_user - views.list.add_book(request, self.list.id) + views.list.add_book(request) item = self.list.listitem_set.get() self.assertEqual(item.book, self.book) self.assertEqual(item.user, self.local_user) From 20bcbb1a3ca8b1895a1fb351d130a37ef23c4254 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 15 Mar 2021 15:44:26 -0700 Subject: [PATCH 14/16] Updates image type to Document in status model tests --- bookwyrm/tests/models/test_status_model.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bookwyrm/tests/models/test_status_model.py b/bookwyrm/tests/models/test_status_model.py index b2ee69b87..1dcf56339 100644 --- a/bookwyrm/tests/models/test_status_model.py +++ b/bookwyrm/tests/models/test_status_model.py @@ -169,7 +169,7 @@ class Status(TestCase): self.assertEqual(activity["type"], "Note") self.assertEqual(activity["sensitive"], False) self.assertIsInstance(activity["attachment"], list) - self.assertEqual(activity["attachment"][0].type, "Image") + self.assertEqual(activity["attachment"][0].type, "Document") self.assertEqual( activity["attachment"][0].url, "https://%s%s" % (settings.DOMAIN, self.book.cover.url), @@ -200,7 +200,7 @@ class Status(TestCase): 'test content

(comment on "Test Edition")

' % self.book.remote_id, ) - self.assertEqual(activity["attachment"][0].type, "Image") + self.assertEqual(activity["attachment"][0].type, "Document") self.assertEqual( activity["attachment"][0].url, "https://%s%s" % (settings.DOMAIN, self.book.cover.url), @@ -238,7 +238,7 @@ class Status(TestCase): 'a sickening sense

-- "Test Edition"

' "test content" % self.book.remote_id, ) - self.assertEqual(activity["attachment"][0].type, "Image") + self.assertEqual(activity["attachment"][0].type, "Document") self.assertEqual( activity["attachment"][0].url, "https://%s%s" % (settings.DOMAIN, self.book.cover.url), @@ -278,7 +278,7 @@ class Status(TestCase): activity["name"], 'Review of "%s" (3 stars): Review name' % self.book.title ) self.assertEqual(activity["content"], "test content") - self.assertEqual(activity["attachment"][0].type, "Image") + self.assertEqual(activity["attachment"][0].type, "Document") self.assertEqual( activity["attachment"][0].url, "https://%s%s" % (settings.DOMAIN, self.book.cover.url), From 5a24c97cb5cc521b6883f6c41e765b191f01a574 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 15 Mar 2021 15:45:18 -0700 Subject: [PATCH 15/16] Updates image type in fields tests --- bookwyrm/tests/models/test_fields.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/tests/models/test_fields.py b/bookwyrm/tests/models/test_fields.py index 522d16f94..28faf52c3 100644 --- a/bookwyrm/tests/models/test_fields.py +++ b/bookwyrm/tests/models/test_fields.py @@ -404,7 +404,7 @@ class ActivitypubFields(TestCase): ) ) self.assertEqual(output.name, "alt text") - self.assertEqual(output.type, "Image") + self.assertEqual(output.type, "Document") instance = fields.ImageField() From bb8ce74f5205650e9b2b27d7fe0a1d49d877758e Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 15 Mar 2021 15:57:00 -0700 Subject: [PATCH 16/16] Updates base activity tests for updated Image type --- bookwyrm/tests/activitypub/test_base_activity.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bookwyrm/tests/activitypub/test_base_activity.py b/bookwyrm/tests/activitypub/test_base_activity.py index b3e282619..f3d3decde 100644 --- a/bookwyrm/tests/activitypub/test_base_activity.py +++ b/bookwyrm/tests/activitypub/test_base_activity.py @@ -123,7 +123,7 @@ class BaseActivity(TestCase): summary="", publicKey={"id": "hi", "owner": self.user.remote_id, "publicKeyPem": "hi"}, endpoints={}, - icon={"type": "Image", "url": "http://www.example.com/image.jpg"}, + icon={"type": "Document", "url": "http://www.example.com/image.jpg"}, ) responses.add( @@ -194,7 +194,7 @@ class BaseActivity(TestCase): { "url": "http://www.example.com/image.jpg", "name": "alt text", - "type": "Image", + "type": "Document", } ], ) @@ -224,7 +224,7 @@ class BaseActivity(TestCase): data = { "url": "http://www.example.com/image.jpg", "name": "alt text", - "type": "Image", + "type": "Document", } responses.add( responses.GET,