Repository: bookwyrm-social/bookwyrm

Issue:
Images sent from mastodon aren't loading
BookWyrm expects an Image type but is receiving a Document


Discussion:

Resolving patch:
diff --git a/bookwyrm/activitypub/image.py b/bookwyrm/activitypub/image.py
--- 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
--- 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
--- 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")
 


Test patch:
diff --git a/bookwyrm/tests/activitypub/test_base_activity.py b/bookwyrm/tests/activitypub/test_base_activity.py
--- a/bookwyrm/tests/activitypub/test_base_activity.py
+++ b/bookwyrm/tests/activitypub/test_base_activity.py
@@ -123,7 +123,7 @@ def test_to_model_image(self):
             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 @@ def test_to_model_one_to_many(self):
                 {
                     "url": "http://www.example.com/image.jpg",
                     "name": "alt text",
-                    "type": "Image",
+                    "type": "Document",
                 }
             ],
         )
@@ -224,7 +224,7 @@ def test_set_related_field(self):
         data = {
             "url": "http://www.example.com/image.jpg",
             "name": "alt text",
-            "type": "Image",
+            "type": "Document",
         }
         responses.add(
             responses.GET,
diff --git a/bookwyrm/tests/models/test_fields.py b/bookwyrm/tests/models/test_fields.py
--- a/bookwyrm/tests/models/test_fields.py
+++ b/bookwyrm/tests/models/test_fields.py
@@ -404,7 +404,7 @@ def test_image_field(self, _):
             )
         )
         self.assertEqual(output.name, "alt text")
-        self.assertEqual(output.type, "Image")
+        self.assertEqual(output.type, "Document")
 
         instance = fields.ImageField()
 
diff --git a/bookwyrm/tests/models/test_status_model.py b/bookwyrm/tests/models/test_status_model.py
--- a/bookwyrm/tests/models/test_status_model.py
+++ b/bookwyrm/tests/models/test_status_model.py
@@ -169,7 +169,7 @@ def test_generated_note_to_pure_activity(self, _):
         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 @@ def test_comment_to_pure_activity(self, _):
             'test content<p>(comment on <a href="%s">"Test Edition"</a>)</p>'
             % 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 @@ def test_quotation_to_pure_activity(self, _):
             'a sickening sense <p>-- <a href="%s">"Test Edition"</a></p>'
             "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 @@ def test_review_to_pure_activity(self, _):
             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),
diff --git a/bookwyrm/tests/views/test_feed.py b/bookwyrm/tests/views/test_feed.py
--- 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.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 @@ def test_status_page(self):
         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()