We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Previews not loading correctly when using local image storage 
**Describe the bug**
Previews not loading correctly when using local image storage 

**To Reproduce**
In status update previews are not shown, for example:
https://ziurkes.group.lt/user/athinkingmeat/quotation/167#anchor-167
https://ziurkes.group.lt/user/athinkingmeat/review/166#anchor-166

**Expected behavior**
they work for books though:
https://ziurkes.group.lt/book/7

**Examples**
https://twitter.com/DievaiDemonai/status/1459865136781578243

**Instance**
https://ziurkes.group.lt/

**Additional context**
Using cloudflare and also caddy instead of nginx to serve files, but it seems that the variable is not generated in template itself, not that the file is not being served.





--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `bookwyrm/views/feed.py`
Content:
```
1 """ non-interactive pages """
2 from django.contrib.auth.decorators import login_required
3 from django.core.paginator import Paginator
4 from django.db.models import Q
5 from django.http import HttpResponseNotFound, Http404
6 from django.shortcuts import get_object_or_404
7 from django.template.response import TemplateResponse
8 from django.utils import timezone
9 from django.utils.decorators import method_decorator
10 from django.views import View
11 
12 from bookwyrm import activitystreams, forms, models
13 from bookwyrm.models.user import FeedFilterChoices
14 from bookwyrm.activitypub import ActivitypubResponse
15 from bookwyrm.settings import PAGE_LENGTH, STREAMS
16 from bookwyrm.suggested_users import suggested_users
17 from .helpers import filter_stream_by_status_type, get_user_from_username
18 from .helpers import is_api_request, is_bookwyrm_request
19 
20 
21 # pylint: disable= no-self-use
22 @method_decorator(login_required, name="dispatch")
23 class Feed(View):
24     """activity stream"""
25 
26     def post(self, request, tab):
27         """save feed settings form, with a silent validation fail"""
28         settings_saved = False
29         form = forms.FeedStatusTypesForm(request.POST, instance=request.user)
30         if form.is_valid():
31             form.save()
32             settings_saved = True
33 
34         return self.get(request, tab, settings_saved)
35 
36     def get(self, request, tab, settings_saved=False):
37         """user's homepage with activity feed"""
38         tab = [s for s in STREAMS if s["key"] == tab]
39         tab = tab[0] if tab else STREAMS[0]
40 
41         activities = activitystreams.streams[tab["key"]].get_activity_stream(
42             request.user
43         )
44         filtered_activities = filter_stream_by_status_type(
45             activities,
46             allowed_types=request.user.feed_status_types,
47         )
48         paginated = Paginator(filtered_activities, PAGE_LENGTH)
49 
50         suggestions = suggested_users.get_suggestions(request.user)
51 
52         data = {
53             **feed_page_data(request.user),
54             **{
55                 "user": request.user,
56                 "activities": paginated.get_page(request.GET.get("page")),
57                 "suggested_users": suggestions,
58                 "tab": tab,
59                 "streams": STREAMS,
60                 "goal_form": forms.GoalForm(),
61                 "feed_status_types_options": FeedFilterChoices,
62                 "allowed_status_types": request.user.feed_status_types,
63                 "settings_saved": settings_saved,
64                 "path": f"/{tab['key']}",
65             },
66         }
67         return TemplateResponse(request, "feed/feed.html", data)
68 
69 
70 @method_decorator(login_required, name="dispatch")
71 class DirectMessage(View):
72     """dm view"""
73 
74     def get(self, request, username=None):
75         """like a feed but for dms only"""
76         # remove fancy subclasses of status, keep just good ol' notes
77         activities = (
78             models.Status.privacy_filter(request.user, privacy_levels=["direct"])
79             .filter(
80                 review__isnull=True,
81                 comment__isnull=True,
82                 quotation__isnull=True,
83                 generatednote__isnull=True,
84             )
85             .order_by("-published_date")
86         )
87 
88         user = None
89         if username:
90             try:
91                 user = get_user_from_username(request.user, username)
92             except Http404:
93                 pass
94         if user:
95             activities = activities.filter(Q(user=user) | Q(mention_users=user))
96 
97         paginated = Paginator(activities, PAGE_LENGTH)
98         data = {
99             **feed_page_data(request.user),
100             **{
101                 "user": request.user,
102                 "partner": user,
103                 "activities": paginated.get_page(request.GET.get("page")),
104                 "path": "/direct-messages",
105             },
106         }
107         return TemplateResponse(request, "feed/direct_messages.html", data)
108 
109 
110 class Status(View):
111     """get posting"""
112 
113     def get(self, request, username, status_id):
114         """display a particular status (and replies, etc)"""
115         user = get_user_from_username(request.user, username)
116         status = get_object_or_404(
117             models.Status.objects.select_subclasses(),
118             user=user,
119             id=status_id,
120             deleted=False,
121         )
122         # make sure the user is authorized to see the status
123         status.raise_visible_to_user(request.user)
124 
125         if is_api_request(request):
126             return ActivitypubResponse(
127                 status.to_activity(pure=not is_bookwyrm_request(request))
128             )
129 
130         visible_thread = (
131             models.Status.privacy_filter(request.user)
132             .filter(thread_id=status.thread_id)
133             .values_list("id", flat=True)
134         )
135         visible_thread = list(visible_thread)
136 
137         ancestors = models.Status.objects.select_subclasses().raw(
138             """
139             WITH RECURSIVE get_thread(depth, id, path) AS (
140 
141                 SELECT 1, st.id, ARRAY[st.id]
142                 FROM bookwyrm_status st
143                 WHERE id = '%s' AND id = ANY(%s)
144 
145                 UNION
146 
147                 SELECT (gt.depth + 1), st.reply_parent_id, path || st.id
148                 FROM get_thread gt, bookwyrm_status st
149 
150                 WHERE st.id = gt.id AND depth < 5 AND st.id = ANY(%s)
151 
152             )
153 
154             SELECT * FROM get_thread ORDER BY path DESC;
155         """,
156             params=[status.reply_parent_id or 0, visible_thread, visible_thread],
157         )
158         children = models.Status.objects.select_subclasses().raw(
159             """
160             WITH RECURSIVE get_thread(depth, id, path) AS (
161 
162                 SELECT 1, st.id, ARRAY[st.id]
163                 FROM bookwyrm_status st
164                 WHERE reply_parent_id = '%s' AND id = ANY(%s)
165 
166                 UNION
167 
168                 SELECT (gt.depth + 1), st.id, path || st.id
169                 FROM get_thread gt, bookwyrm_status st
170 
171                 WHERE st.reply_parent_id = gt.id AND depth < 5 AND st.id = ANY(%s)
172 
173             )
174 
175             SELECT * FROM get_thread ORDER BY path;
176         """,
177             params=[status.id, visible_thread, visible_thread],
178         )
179 
180         data = {
181             **feed_page_data(request.user),
182             **{
183                 "status": status,
184                 "children": children,
185                 "ancestors": ancestors,
186             },
187         }
188         return TemplateResponse(request, "feed/status.html", data)
189 
190 
191 class Replies(View):
192     """replies page (a json view of status)"""
193 
194     def get(self, request, username, status_id):
195         """ordered collection of replies to a status"""
196         # the html view is the same as Status
197         if not is_api_request(request):
198             status_view = Status.as_view()
199             return status_view(request, username, status_id)
200 
201         # the json view is different than Status
202         status = models.Status.objects.get(id=status_id)
203         if status.user.localname != username:
204             return HttpResponseNotFound()
205         status.raise_visible_to_user(request.user)
206 
207         return ActivitypubResponse(status.to_replies(**request.GET))
208 
209 
210 def feed_page_data(user):
211     """info we need for every feed page"""
212     if not user.is_authenticated:
213         return {}
214 
215     goal = models.AnnualGoal.objects.filter(user=user, year=timezone.now().year).first()
216     return {
217         "suggested_books": get_suggested_books(user),
218         "goal": goal,
219         "goal_form": forms.GoalForm(),
220     }
221 
222 
223 def get_suggested_books(user, max_books=5):
224     """helper to get a user's recent books"""
225     book_count = 0
226     preset_shelves = [("reading", max_books), ("read", 2), ("to-read", max_books)]
227     suggested_books = []
228     for (preset, shelf_max) in preset_shelves:
229         limit = (
230             shelf_max
231             if shelf_max < (max_books - book_count)
232             else max_books - book_count
233         )
234         shelf = user.shelf_set.get(identifier=preset)
235         if not shelf.books.exists():
236             continue
237 
238         shelf_preview = {
239             "name": shelf.name,
240             "identifier": shelf.identifier,
241             "books": models.Edition.viewer_aware_objects(user)
242             .filter(
243                 shelfbook__shelf=shelf,
244             )
245             .order_by("-shelfbook__shelved_date")
246             .prefetch_related("authors")[:limit],
247         }
248         suggested_books.append(shelf_preview)
249         book_count += len(shelf_preview["books"])
250     return suggested_books
251
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/bookwyrm/views/feed.py b/bookwyrm/views/feed.py
--- a/bookwyrm/views/feed.py
+++ b/bookwyrm/views/feed.py
@@ -177,12 +177,19 @@
             params=[status.id, visible_thread, visible_thread],
         )
 
+        preview = None
+        if hasattr(status, "book"):
+            preview = status.book.preview_image
+        elif status.mention_books.exists():
+            preview = status.mention_books.first().preview_image
+
         data = {
             **feed_page_data(request.user),
             **{
                 "status": status,
                 "children": children,
                 "ancestors": ancestors,
+                "preview": preview,
             },
         }
         return TemplateResponse(request, "feed/status.html", data)

{"golden_diff": "diff --git a/bookwyrm/views/feed.py b/bookwyrm/views/feed.py\n--- a/bookwyrm/views/feed.py\n+++ b/bookwyrm/views/feed.py\n@@ -177,12 +177,19 @@\n             params=[status.id, visible_thread, visible_thread],\n         )\n \n+        preview = None\n+        if hasattr(status, \"book\"):\n+            preview = status.book.preview_image\n+        elif status.mention_books.exists():\n+            preview = status.mention_books.first().preview_image\n+\n         data = {\n             **feed_page_data(request.user),\n             **{\n                 \"status\": status,\n                 \"children\": children,\n                 \"ancestors\": ancestors,\n+                \"preview\": preview,\n             },\n         }\n         return TemplateResponse(request, \"feed/status.html\", data)\n", "issue": "Previews not loading correctly when using local image storage \n**Describe the bug**\r\nPreviews not loading correctly when using local image storage \r\n\r\n**To Reproduce**\r\nIn status update previews are not shown, for example:\r\nhttps://ziurkes.group.lt/user/athinkingmeat/quotation/167#anchor-167\r\nhttps://ziurkes.group.lt/user/athinkingmeat/review/166#anchor-166\r\n\r\n**Expected behavior**\r\nthey work for books though:\r\nhttps://ziurkes.group.lt/book/7\r\n\r\n**Examples**\r\nhttps://twitter.com/DievaiDemonai/status/1459865136781578243\r\n\r\n**Instance**\r\nhttps://ziurkes.group.lt/\r\n\r\n**Additional context**\r\nUsing cloudflare and also caddy instead of nginx to serve files, but it seems that the variable is not generated in template itself, not that the file is not being served.\r\n\r\n\r\n\r\n\n", "before_files": [{"content": "\"\"\" non-interactive pages \"\"\"\nfrom django.contrib.auth.decorators import login_required\nfrom django.core.paginator import Paginator\nfrom django.db.models import Q\nfrom django.http import HttpResponseNotFound, Http404\nfrom django.shortcuts import get_object_or_404\nfrom django.template.response import TemplateResponse\nfrom django.utils import timezone\nfrom django.utils.decorators import method_decorator\nfrom django.views import View\n\nfrom bookwyrm import activitystreams, forms, models\nfrom bookwyrm.models.user import FeedFilterChoices\nfrom bookwyrm.activitypub import ActivitypubResponse\nfrom bookwyrm.settings import PAGE_LENGTH, STREAMS\nfrom bookwyrm.suggested_users import suggested_users\nfrom .helpers import filter_stream_by_status_type, get_user_from_username\nfrom .helpers import is_api_request, is_bookwyrm_request\n\n\n# pylint: disable= no-self-use\n@method_decorator(login_required, name=\"dispatch\")\nclass Feed(View):\n    \"\"\"activity stream\"\"\"\n\n    def post(self, request, tab):\n        \"\"\"save feed settings form, with a silent validation fail\"\"\"\n        settings_saved = False\n        form = forms.FeedStatusTypesForm(request.POST, instance=request.user)\n        if form.is_valid():\n            form.save()\n            settings_saved = True\n\n        return self.get(request, tab, settings_saved)\n\n    def get(self, request, tab, settings_saved=False):\n        \"\"\"user's homepage with activity feed\"\"\"\n        tab = [s for s in STREAMS if s[\"key\"] == tab]\n        tab = tab[0] if tab else STREAMS[0]\n\n        activities = activitystreams.streams[tab[\"key\"]].get_activity_stream(\n            request.user\n        )\n        filtered_activities = filter_stream_by_status_type(\n            activities,\n            allowed_types=request.user.feed_status_types,\n        )\n        paginated = Paginator(filtered_activities, PAGE_LENGTH)\n\n        suggestions = suggested_users.get_suggestions(request.user)\n\n        data = {\n            **feed_page_data(request.user),\n            **{\n                \"user\": request.user,\n                \"activities\": paginated.get_page(request.GET.get(\"page\")),\n                \"suggested_users\": suggestions,\n                \"tab\": tab,\n                \"streams\": STREAMS,\n                \"goal_form\": forms.GoalForm(),\n                \"feed_status_types_options\": FeedFilterChoices,\n                \"allowed_status_types\": request.user.feed_status_types,\n                \"settings_saved\": settings_saved,\n                \"path\": f\"/{tab['key']}\",\n            },\n        }\n        return TemplateResponse(request, \"feed/feed.html\", data)\n\n\n@method_decorator(login_required, name=\"dispatch\")\nclass DirectMessage(View):\n    \"\"\"dm view\"\"\"\n\n    def get(self, request, username=None):\n        \"\"\"like a feed but for dms only\"\"\"\n        # remove fancy subclasses of status, keep just good ol' notes\n        activities = (\n            models.Status.privacy_filter(request.user, privacy_levels=[\"direct\"])\n            .filter(\n                review__isnull=True,\n                comment__isnull=True,\n                quotation__isnull=True,\n                generatednote__isnull=True,\n            )\n            .order_by(\"-published_date\")\n        )\n\n        user = None\n        if username:\n            try:\n                user = get_user_from_username(request.user, username)\n            except Http404:\n                pass\n        if user:\n            activities = activities.filter(Q(user=user) | Q(mention_users=user))\n\n        paginated = Paginator(activities, PAGE_LENGTH)\n        data = {\n            **feed_page_data(request.user),\n            **{\n                \"user\": request.user,\n                \"partner\": user,\n                \"activities\": paginated.get_page(request.GET.get(\"page\")),\n                \"path\": \"/direct-messages\",\n            },\n        }\n        return TemplateResponse(request, \"feed/direct_messages.html\", data)\n\n\nclass Status(View):\n    \"\"\"get posting\"\"\"\n\n    def get(self, request, username, status_id):\n        \"\"\"display a particular status (and replies, etc)\"\"\"\n        user = get_user_from_username(request.user, username)\n        status = get_object_or_404(\n            models.Status.objects.select_subclasses(),\n            user=user,\n            id=status_id,\n            deleted=False,\n        )\n        # make sure the user is authorized to see the status\n        status.raise_visible_to_user(request.user)\n\n        if is_api_request(request):\n            return ActivitypubResponse(\n                status.to_activity(pure=not is_bookwyrm_request(request))\n            )\n\n        visible_thread = (\n            models.Status.privacy_filter(request.user)\n            .filter(thread_id=status.thread_id)\n            .values_list(\"id\", flat=True)\n        )\n        visible_thread = list(visible_thread)\n\n        ancestors = models.Status.objects.select_subclasses().raw(\n            \"\"\"\n            WITH RECURSIVE get_thread(depth, id, path) AS (\n\n                SELECT 1, st.id, ARRAY[st.id]\n                FROM bookwyrm_status st\n                WHERE id = '%s' AND id = ANY(%s)\n\n                UNION\n\n                SELECT (gt.depth + 1), st.reply_parent_id, path || st.id\n                FROM get_thread gt, bookwyrm_status st\n\n                WHERE st.id = gt.id AND depth < 5 AND st.id = ANY(%s)\n\n            )\n\n            SELECT * FROM get_thread ORDER BY path DESC;\n        \"\"\",\n            params=[status.reply_parent_id or 0, visible_thread, visible_thread],\n        )\n        children = models.Status.objects.select_subclasses().raw(\n            \"\"\"\n            WITH RECURSIVE get_thread(depth, id, path) AS (\n\n                SELECT 1, st.id, ARRAY[st.id]\n                FROM bookwyrm_status st\n                WHERE reply_parent_id = '%s' AND id = ANY(%s)\n\n                UNION\n\n                SELECT (gt.depth + 1), st.id, path || st.id\n                FROM get_thread gt, bookwyrm_status st\n\n                WHERE st.reply_parent_id = gt.id AND depth < 5 AND st.id = ANY(%s)\n\n            )\n\n            SELECT * FROM get_thread ORDER BY path;\n        \"\"\",\n            params=[status.id, visible_thread, visible_thread],\n        )\n\n        data = {\n            **feed_page_data(request.user),\n            **{\n                \"status\": status,\n                \"children\": children,\n                \"ancestors\": ancestors,\n            },\n        }\n        return TemplateResponse(request, \"feed/status.html\", data)\n\n\nclass Replies(View):\n    \"\"\"replies page (a json view of status)\"\"\"\n\n    def get(self, request, username, status_id):\n        \"\"\"ordered collection of replies to a status\"\"\"\n        # the html view is the same as Status\n        if not is_api_request(request):\n            status_view = Status.as_view()\n            return status_view(request, username, status_id)\n\n        # the json view is different than Status\n        status = models.Status.objects.get(id=status_id)\n        if status.user.localname != username:\n            return HttpResponseNotFound()\n        status.raise_visible_to_user(request.user)\n\n        return ActivitypubResponse(status.to_replies(**request.GET))\n\n\ndef feed_page_data(user):\n    \"\"\"info we need for every feed page\"\"\"\n    if not user.is_authenticated:\n        return {}\n\n    goal = models.AnnualGoal.objects.filter(user=user, year=timezone.now().year).first()\n    return {\n        \"suggested_books\": get_suggested_books(user),\n        \"goal\": goal,\n        \"goal_form\": forms.GoalForm(),\n    }\n\n\ndef get_suggested_books(user, max_books=5):\n    \"\"\"helper to get a user's recent books\"\"\"\n    book_count = 0\n    preset_shelves = [(\"reading\", max_books), (\"read\", 2), (\"to-read\", max_books)]\n    suggested_books = []\n    for (preset, shelf_max) in preset_shelves:\n        limit = (\n            shelf_max\n            if shelf_max < (max_books - book_count)\n            else max_books - book_count\n        )\n        shelf = user.shelf_set.get(identifier=preset)\n        if not shelf.books.exists():\n            continue\n\n        shelf_preview = {\n            \"name\": shelf.name,\n            \"identifier\": shelf.identifier,\n            \"books\": models.Edition.viewer_aware_objects(user)\n            .filter(\n                shelfbook__shelf=shelf,\n            )\n            .order_by(\"-shelfbook__shelved_date\")\n            .prefetch_related(\"authors\")[:limit],\n        }\n        suggested_books.append(shelf_preview)\n        book_count += len(shelf_preview[\"books\"])\n    return suggested_books\n", "path": "bookwyrm/views/feed.py"}], "after_files": [{"content": "\"\"\" non-interactive pages \"\"\"\nfrom django.contrib.auth.decorators import login_required\nfrom django.core.paginator import Paginator\nfrom django.db.models import Q\nfrom django.http import HttpResponseNotFound, Http404\nfrom django.shortcuts import get_object_or_404\nfrom django.template.response import TemplateResponse\nfrom django.utils import timezone\nfrom django.utils.decorators import method_decorator\nfrom django.views import View\n\nfrom bookwyrm import activitystreams, forms, models\nfrom bookwyrm.models.user import FeedFilterChoices\nfrom bookwyrm.activitypub import ActivitypubResponse\nfrom bookwyrm.settings import PAGE_LENGTH, STREAMS\nfrom bookwyrm.suggested_users import suggested_users\nfrom .helpers import filter_stream_by_status_type, get_user_from_username\nfrom .helpers import is_api_request, is_bookwyrm_request\n\n\n# pylint: disable= no-self-use\n@method_decorator(login_required, name=\"dispatch\")\nclass Feed(View):\n    \"\"\"activity stream\"\"\"\n\n    def post(self, request, tab):\n        \"\"\"save feed settings form, with a silent validation fail\"\"\"\n        settings_saved = False\n        form = forms.FeedStatusTypesForm(request.POST, instance=request.user)\n        if form.is_valid():\n            form.save()\n            settings_saved = True\n\n        return self.get(request, tab, settings_saved)\n\n    def get(self, request, tab, settings_saved=False):\n        \"\"\"user's homepage with activity feed\"\"\"\n        tab = [s for s in STREAMS if s[\"key\"] == tab]\n        tab = tab[0] if tab else STREAMS[0]\n\n        activities = activitystreams.streams[tab[\"key\"]].get_activity_stream(\n            request.user\n        )\n        filtered_activities = filter_stream_by_status_type(\n            activities,\n            allowed_types=request.user.feed_status_types,\n        )\n        paginated = Paginator(filtered_activities, PAGE_LENGTH)\n\n        suggestions = suggested_users.get_suggestions(request.user)\n\n        data = {\n            **feed_page_data(request.user),\n            **{\n                \"user\": request.user,\n                \"activities\": paginated.get_page(request.GET.get(\"page\")),\n                \"suggested_users\": suggestions,\n                \"tab\": tab,\n                \"streams\": STREAMS,\n                \"goal_form\": forms.GoalForm(),\n                \"feed_status_types_options\": FeedFilterChoices,\n                \"allowed_status_types\": request.user.feed_status_types,\n                \"settings_saved\": settings_saved,\n                \"path\": f\"/{tab['key']}\",\n            },\n        }\n        return TemplateResponse(request, \"feed/feed.html\", data)\n\n\n@method_decorator(login_required, name=\"dispatch\")\nclass DirectMessage(View):\n    \"\"\"dm view\"\"\"\n\n    def get(self, request, username=None):\n        \"\"\"like a feed but for dms only\"\"\"\n        # remove fancy subclasses of status, keep just good ol' notes\n        activities = (\n            models.Status.privacy_filter(request.user, privacy_levels=[\"direct\"])\n            .filter(\n                review__isnull=True,\n                comment__isnull=True,\n                quotation__isnull=True,\n                generatednote__isnull=True,\n            )\n            .order_by(\"-published_date\")\n        )\n\n        user = None\n        if username:\n            try:\n                user = get_user_from_username(request.user, username)\n            except Http404:\n                pass\n        if user:\n            activities = activities.filter(Q(user=user) | Q(mention_users=user))\n\n        paginated = Paginator(activities, PAGE_LENGTH)\n        data = {\n            **feed_page_data(request.user),\n            **{\n                \"user\": request.user,\n                \"partner\": user,\n                \"activities\": paginated.get_page(request.GET.get(\"page\")),\n                \"path\": \"/direct-messages\",\n            },\n        }\n        return TemplateResponse(request, \"feed/direct_messages.html\", data)\n\n\nclass Status(View):\n    \"\"\"get posting\"\"\"\n\n    def get(self, request, username, status_id):\n        \"\"\"display a particular status (and replies, etc)\"\"\"\n        user = get_user_from_username(request.user, username)\n        status = get_object_or_404(\n            models.Status.objects.select_subclasses(),\n            user=user,\n            id=status_id,\n            deleted=False,\n        )\n        # make sure the user is authorized to see the status\n        status.raise_visible_to_user(request.user)\n\n        if is_api_request(request):\n            return ActivitypubResponse(\n                status.to_activity(pure=not is_bookwyrm_request(request))\n            )\n\n        visible_thread = (\n            models.Status.privacy_filter(request.user)\n            .filter(thread_id=status.thread_id)\n            .values_list(\"id\", flat=True)\n        )\n        visible_thread = list(visible_thread)\n\n        ancestors = models.Status.objects.select_subclasses().raw(\n            \"\"\"\n            WITH RECURSIVE get_thread(depth, id, path) AS (\n\n                SELECT 1, st.id, ARRAY[st.id]\n                FROM bookwyrm_status st\n                WHERE id = '%s' AND id = ANY(%s)\n\n                UNION\n\n                SELECT (gt.depth + 1), st.reply_parent_id, path || st.id\n                FROM get_thread gt, bookwyrm_status st\n\n                WHERE st.id = gt.id AND depth < 5 AND st.id = ANY(%s)\n\n            )\n\n            SELECT * FROM get_thread ORDER BY path DESC;\n        \"\"\",\n            params=[status.reply_parent_id or 0, visible_thread, visible_thread],\n        )\n        children = models.Status.objects.select_subclasses().raw(\n            \"\"\"\n            WITH RECURSIVE get_thread(depth, id, path) AS (\n\n                SELECT 1, st.id, ARRAY[st.id]\n                FROM bookwyrm_status st\n                WHERE reply_parent_id = '%s' AND id = ANY(%s)\n\n                UNION\n\n                SELECT (gt.depth + 1), st.id, path || st.id\n                FROM get_thread gt, bookwyrm_status st\n\n                WHERE st.reply_parent_id = gt.id AND depth < 5 AND st.id = ANY(%s)\n\n            )\n\n            SELECT * FROM get_thread ORDER BY path;\n        \"\"\",\n            params=[status.id, visible_thread, visible_thread],\n        )\n\n        preview = None\n        if hasattr(status, \"book\"):\n            preview = status.book.preview_image\n        elif status.mention_books.exists():\n            preview = status.mention_books.first().preview_image\n\n        data = {\n            **feed_page_data(request.user),\n            **{\n                \"status\": status,\n                \"children\": children,\n                \"ancestors\": ancestors,\n                \"preview\": preview,\n            },\n        }\n        return TemplateResponse(request, \"feed/status.html\", data)\n\n\nclass Replies(View):\n    \"\"\"replies page (a json view of status)\"\"\"\n\n    def get(self, request, username, status_id):\n        \"\"\"ordered collection of replies to a status\"\"\"\n        # the html view is the same as Status\n        if not is_api_request(request):\n            status_view = Status.as_view()\n            return status_view(request, username, status_id)\n\n        # the json view is different than Status\n        status = models.Status.objects.get(id=status_id)\n        if status.user.localname != username:\n            return HttpResponseNotFound()\n        status.raise_visible_to_user(request.user)\n\n        return ActivitypubResponse(status.to_replies(**request.GET))\n\n\ndef feed_page_data(user):\n    \"\"\"info we need for every feed page\"\"\"\n    if not user.is_authenticated:\n        return {}\n\n    goal = models.AnnualGoal.objects.filter(user=user, year=timezone.now().year).first()\n    return {\n        \"suggested_books\": get_suggested_books(user),\n        \"goal\": goal,\n        \"goal_form\": forms.GoalForm(),\n    }\n\n\ndef get_suggested_books(user, max_books=5):\n    \"\"\"helper to get a user's recent books\"\"\"\n    book_count = 0\n    preset_shelves = [(\"reading\", max_books), (\"read\", 2), (\"to-read\", max_books)]\n    suggested_books = []\n    for (preset, shelf_max) in preset_shelves:\n        limit = (\n            shelf_max\n            if shelf_max < (max_books - book_count)\n            else max_books - book_count\n        )\n        shelf = user.shelf_set.get(identifier=preset)\n        if not shelf.books.exists():\n            continue\n\n        shelf_preview = {\n            \"name\": shelf.name,\n            \"identifier\": shelf.identifier,\n            \"books\": models.Edition.viewer_aware_objects(user)\n            .filter(\n                shelfbook__shelf=shelf,\n            )\n            .order_by(\"-shelfbook__shelved_date\")\n            .prefetch_related(\"authors\")[:limit],\n        }\n        suggested_books.append(shelf_preview)\n        book_count += len(shelf_preview[\"books\"])\n    return suggested_books\n", "path": "bookwyrm/views/feed.py"}]}