From 7caaddbb222d6bab129a23786b25e6b804cb5f0b Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 2 Oct 2021 15:20:35 -0700 Subject: [PATCH 01/20] Get thread children with depth first recusive search --- bookwyrm/templates/feed/status.html | 14 +++++++++++++- bookwyrm/templates/feed/thread.html | 25 ------------------------- bookwyrm/views/feed.py | 20 ++++++++++++++++++++ 3 files changed, 33 insertions(+), 26 deletions(-) delete mode 100644 bookwyrm/templates/feed/thread.html diff --git a/bookwyrm/templates/feed/status.html b/bookwyrm/templates/feed/status.html index 903ca7907..429c657d0 100644 --- a/bookwyrm/templates/feed/status.html +++ b/bookwyrm/templates/feed/status.html @@ -9,7 +9,19 @@ -{% include 'feed/thread.html' with status=status depth=0 max_depth=6 is_root=True direction=0 %} +
+
+
+ {% include 'snippets/status/status.html' with status=status main=True %} +
+ + {% for child in children %} +
+ {% include 'snippets/status/status.html' with status=child %} +
+ {% endfor %} +
+
{% endblock %} diff --git a/bookwyrm/templates/feed/thread.html b/bookwyrm/templates/feed/thread.html deleted file mode 100644 index c1b624e3c..000000000 --- a/bookwyrm/templates/feed/thread.html +++ /dev/null @@ -1,25 +0,0 @@ -{% load status_display %} - -
-
-{% with depth=depth|add:1 %} - {% if depth <= max_depth and status.reply_parent and direction <= 0 %} - {% with direction=-1 %} - {% include 'feed/thread.html' with status=status|parent is_root=False %} - {% endwith %} - {% endif %} - - - {% include 'snippets/status/status.html' with status=status main=is_root %} -
- -{% if depth <= max_depth and direction >= 0 %} - {% for reply in status|replies %} - {% with direction=1 %} - {% include 'feed/thread.html' with status=reply is_root=False %} - {% endwith %} - {% endfor %} -{% endif %} -{% endwith %} -
- diff --git a/bookwyrm/views/feed.py b/bookwyrm/views/feed.py index 0c2647aea..f79b15153 100644 --- a/bookwyrm/views/feed.py +++ b/bookwyrm/views/feed.py @@ -109,10 +109,30 @@ class Status(View): status.to_activity(pure=not is_bookwyrm_request(request)) ) + children = models.Status.objects.select_subclasses().raw(""" + WITH RECURSIVE get_thread(depth, id, path) AS ( + + SELECT 1, st.id, ARRAY[st.id] + FROM bookwyrm_status st + WHERE reply_parent_id = '%s' + + UNION + + SELECT (gt.depth + 1), st.id, path || st.id + FROM get_thread gt, bookwyrm_status st + + WHERE st.reply_parent_id = gt.id AND depth < 5 + + ) + + SELECT * FROM get_thread ORDER BY path; + """, params=[status.id]) + data = { **feed_page_data(request.user), **{ "status": status, + "children": children, }, } return TemplateResponse(request, "feed/status.html", data) From cd571614898af7d825a73bfa9abd04bdc26d3b75 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 2 Oct 2021 16:55:05 -0700 Subject: [PATCH 02/20] Privacy filter for thread --- bookwyrm/views/feed.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/bookwyrm/views/feed.py b/bookwyrm/views/feed.py index f79b15153..92953057d 100644 --- a/bookwyrm/views/feed.py +++ b/bookwyrm/views/feed.py @@ -109,24 +109,31 @@ class Status(View): status.to_activity(pure=not is_bookwyrm_request(request)) ) + visible_thread = privacy_filter( + request.user, + models.Status.objects.filter(thread_id=status.thread_id) + ).values_list("id", flat=True) + visible_thread = list(visible_thread) + children = models.Status.objects.select_subclasses().raw(""" WITH RECURSIVE get_thread(depth, id, path) AS ( SELECT 1, st.id, ARRAY[st.id] FROM bookwyrm_status st - WHERE reply_parent_id = '%s' + WHERE reply_parent_id = '%s' AND id = ANY(%s) UNION SELECT (gt.depth + 1), st.id, path || st.id FROM get_thread gt, bookwyrm_status st - WHERE st.reply_parent_id = gt.id AND depth < 5 + WHERE st.reply_parent_id = gt.id AND depth < 5 AND st.id = ANY(%s) ) SELECT * FROM get_thread ORDER BY path; - """, params=[status.id]) + """, params=[status.id, visible_thread, visible_thread]) + data = { **feed_page_data(request.user), From 43f0440505e1163ca35eb59de2f8b6b42b1a9a56 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 2 Oct 2021 16:55:15 -0700 Subject: [PATCH 03/20] Improved privacy query --- bookwyrm/views/helpers.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bookwyrm/views/helpers.py b/bookwyrm/views/helpers.py index bd31fbbc8..7e469f7f0 100644 --- a/bookwyrm/views/helpers.py +++ b/bookwyrm/views/helpers.py @@ -61,8 +61,7 @@ def privacy_filter(viewer, queryset, privacy_levels=None, following_only=False): # exclude blocks from both directions if not viewer.is_anonymous: - blocked = models.User.objects.filter(id__in=viewer.blocks.all()).all() - queryset = queryset.exclude(Q(user__in=blocked) | Q(user__blocks=viewer)) + queryset = queryset.exclude(Q(user__blocked_by=viewer) | Q(user__blocks=viewer)) # you can't see followers only or direct messages if you're not logged in if viewer.is_anonymous: @@ -75,7 +74,7 @@ def privacy_filter(viewer, queryset, privacy_levels=None, following_only=False): if following_only: queryset = queryset.exclude( ~Q( # remove everythign except - Q(user__in=viewer.following.all()) + Q(user__followers=viewer) | Q(user=viewer) # user following | Q(mention_users=viewer) # is self # mentions user ), From 14ac8bb1b5a36aef2cb87134a143896dde5024b6 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 2 Oct 2021 16:56:23 -0700 Subject: [PATCH 04/20] Python formatting --- bookwyrm/views/feed.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/bookwyrm/views/feed.py b/bookwyrm/views/feed.py index 92953057d..b873224b7 100644 --- a/bookwyrm/views/feed.py +++ b/bookwyrm/views/feed.py @@ -110,12 +110,12 @@ class Status(View): ) visible_thread = privacy_filter( - request.user, - models.Status.objects.filter(thread_id=status.thread_id) + request.user, models.Status.objects.filter(thread_id=status.thread_id) ).values_list("id", flat=True) visible_thread = list(visible_thread) - children = models.Status.objects.select_subclasses().raw(""" + children = models.Status.objects.select_subclasses().raw( + """ WITH RECURSIVE get_thread(depth, id, path) AS ( SELECT 1, st.id, ARRAY[st.id] @@ -132,8 +132,9 @@ class Status(View): ) SELECT * FROM get_thread ORDER BY path; - """, params=[status.id, visible_thread, visible_thread]) - + """, + params=[status.id, visible_thread, visible_thread], + ) data = { **feed_page_data(request.user), From e1271dd07906df63e33f468f4b9b3900878b5c38 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 2 Oct 2021 19:04:01 -0700 Subject: [PATCH 05/20] Less costly migration --- bookwyrm/migrations/0101_auto_20210929_1847.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/migrations/0101_auto_20210929_1847.py b/bookwyrm/migrations/0101_auto_20210929_1847.py index 3fca28eac..bdda8484f 100644 --- a/bookwyrm/migrations/0101_auto_20210929_1847.py +++ b/bookwyrm/migrations/0101_auto_20210929_1847.py @@ -17,7 +17,7 @@ def infer_format(app_registry, schema_editor): for edition in editions: free_format = edition.physical_format_detail.lower() edition.physical_format = infer_physical_format(free_format) - edition.save() + edition.save(broadcast=False, update_fields=["physical_format"]) def reverse(app_registry, schema_editor): From c821aaa18e3b231c81327fc2c621dd2e4e235575 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 2 Oct 2021 18:24:54 -0700 Subject: [PATCH 06/20] Load status ancestors --- bookwyrm/templates/feed/status.html | 7 +++++++ bookwyrm/views/feed.py | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/bookwyrm/templates/feed/status.html b/bookwyrm/templates/feed/status.html index 429c657d0..8f354fb8d 100644 --- a/bookwyrm/templates/feed/status.html +++ b/bookwyrm/templates/feed/status.html @@ -11,6 +11,13 @@
+ {% for parent in ancestors %} + {% if parent %} +
+ {% include 'snippets/status/status.html' with status=parent %} +
+ {% endif %} + {% endfor %}
{% include 'snippets/status/status.html' with status=status main=True %}
diff --git a/bookwyrm/views/feed.py b/bookwyrm/views/feed.py index b873224b7..7f1bc22c2 100644 --- a/bookwyrm/views/feed.py +++ b/bookwyrm/views/feed.py @@ -114,6 +114,27 @@ class Status(View): ).values_list("id", flat=True) visible_thread = list(visible_thread) + ancestors = models.Status.objects.select_subclasses().raw( + """ + WITH RECURSIVE get_thread(depth, id, path) AS ( + + SELECT 1, st.id, ARRAY[st.id] + FROM bookwyrm_status st + WHERE id = '%s' AND id = ANY(%s) + + UNION + + SELECT (gt.depth + 1), st.reply_parent_id, path || st.id + FROM get_thread gt, bookwyrm_status st + + WHERE st.id = gt.id AND depth < 5 AND st.id = ANY(%s) + + ) + + SELECT * FROM get_thread ORDER BY path DESC; + """, + params=[status.reply_parent_id or 0, visible_thread, visible_thread], + ) children = models.Status.objects.select_subclasses().raw( """ WITH RECURSIVE get_thread(depth, id, path) AS ( @@ -141,6 +162,7 @@ class Status(View): **{ "status": status, "children": children, + "ancestors": ancestors, }, } return TemplateResponse(request, "feed/status.html", data) From 3c82230eedab24a6619e85032b096bd1a8f16618 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 2 Oct 2021 19:22:21 -0700 Subject: [PATCH 07/20] Load subclasses --- bookwyrm/templates/feed/status.html | 7 ++++--- bookwyrm/templatetags/bookwyrm_tags.py | 20 +++++++++++++------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/bookwyrm/templates/feed/status.html b/bookwyrm/templates/feed/status.html index 8f354fb8d..5febf4e22 100644 --- a/bookwyrm/templates/feed/status.html +++ b/bookwyrm/templates/feed/status.html @@ -1,5 +1,6 @@ {% extends 'feed/layout.html' %} {% load i18n %} +{% load bookwyrm_tags %} {% block panel %}
@@ -12,13 +13,13 @@
{% for parent in ancestors %} - {% if parent %} + {% if parent.id %}
- {% include 'snippets/status/status.html' with status=parent %} + {% include 'snippets/status/status.html' with status=parent|load_subclass %}
{% endif %} {% endfor %} -
+
{% include 'snippets/status/status.html' with status=status main=True %}
diff --git a/bookwyrm/templatetags/bookwyrm_tags.py b/bookwyrm/templatetags/bookwyrm_tags.py index e683f9c24..2e03c13b4 100644 --- a/bookwyrm/templatetags/bookwyrm_tags.py +++ b/bookwyrm/templatetags/bookwyrm_tags.py @@ -53,18 +53,24 @@ def get_next_shelf(current_shelf): return "to-read" +@register.filter(name="load_subclass") +def load_subclass(status): + """sometimes you didn't select_subclass""" + if hasattr(status, "quotation"): + return status.quotation + if hasattr(status, "review"): + return status.review + if hasattr(status, "comment"): + return status.comment + return status + + @register.simple_tag(takes_context=False) def related_status(notification): """for notifications""" if not notification.related_status: return None - if hasattr(notification.related_status, "quotation"): - return notification.related_status.quotation - if hasattr(notification.related_status, "review"): - return notification.related_status.review - if hasattr(notification.related_status, "comment"): - return notification.related_status.comment - return notification.related_status + return load_subclass(notification.related_status) @register.simple_tag(takes_context=True) From 9509c5e2884fb8be40981b8b69939115bcadc792 Mon Sep 17 00:00:00 2001 From: Hugh Rundle Date: Sun, 3 Oct 2021 19:41:38 +1100 Subject: [PATCH 08/20] new shelves can be given names always Previously new shelves created when a default shelf was selected did not provide the option to create a new unique name. Now they do. fixes #1491 --- bookwyrm/templates/shelf/create_shelf_form.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/templates/shelf/create_shelf_form.html b/bookwyrm/templates/shelf/create_shelf_form.html index e15e1cc1d..c3d2b5faa 100644 --- a/bookwyrm/templates/shelf/create_shelf_form.html +++ b/bookwyrm/templates/shelf/create_shelf_form.html @@ -7,7 +7,7 @@ {% block form %}
- {% include "shelf/form.html" with editable=shelf.editable form=create_form %} + {% include "shelf/form.html" with editable=True form=create_form %}
{% endblock %} From 9a5003f92a151f5936c787b7d772fc2e196ce716 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 3 Oct 2021 09:18:17 -0700 Subject: [PATCH 09/20] Don't let anonymous users search remote data --- bookwyrm/views/search.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bookwyrm/views/search.py b/bookwyrm/views/search.py index df891266d..4c19a1937 100644 --- a/bookwyrm/views/search.py +++ b/bookwyrm/views/search.py @@ -67,11 +67,11 @@ class Search(View): return TemplateResponse(request, f"search/{search_type}.html", data) -def book_search(query, _, min_confidence, search_remote=False): +def book_search(query, user, min_confidence, search_remote=False): """the real business is elsewhere""" # try a local-only search results = [{"results": search(query, min_confidence=min_confidence)}] - if results and not search_remote: + if not user.is_authenticated or (results and not search_remote): return results, False # if there were no local results, or the request was for remote, search all sources From 4787d854b836ed30003a9fafa8e3a89057e1b9ac Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 3 Oct 2021 09:19:19 -0700 Subject: [PATCH 10/20] require auth on resolve book endpoint --- bookwyrm/views/books/books.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bookwyrm/views/books/books.py b/bookwyrm/views/books/books.py index 9de647a24..298ba5a30 100644 --- a/bookwyrm/views/books/books.py +++ b/bookwyrm/views/books/books.py @@ -172,6 +172,7 @@ def add_description(request, book_id): return redirect("book", book.id) +@login_required @require_POST def resolve_book(request): """figure out the local path to a book from a remote_id""" From ca7967a3a376d2b71b7d4d8b0f4b6ae6c4687ec6 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 3 Oct 2021 09:29:24 -0700 Subject: [PATCH 11/20] Adds test for remote search for anonymous user --- bookwyrm/tests/views/test_search.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/bookwyrm/tests/views/test_search.py b/bookwyrm/tests/views/test_search.py index da35f5571..3299249a0 100644 --- a/bookwyrm/tests/views/test_search.py +++ b/bookwyrm/tests/views/test_search.py @@ -51,7 +51,7 @@ class Views(TestCase): data = json.loads(response.content) self.assertEqual(len(data), 1) self.assertEqual(data[0]["title"], "Test Book") - self.assertEqual(data[0]["key"], "https://%s/book/%d" % (DOMAIN, self.book.id)) + self.assertEqual(data[0]["key"], f"https://{DOMAIN}/book/{self.book.id}") def test_search_no_query(self): """just the search page""" @@ -91,12 +91,27 @@ class Views(TestCase): self.assertIsInstance(response, TemplateResponse) response.render() connector_results = response.context_data["results"] + self.assertEqual(len(connector_results), 2) self.assertEqual(connector_results[0]["results"][0].title, "Test Book") self.assertEqual( connector_results[1]["results"][0].title, "This Is How You Lose the Time War", ) + # don't search remote + request = self.factory.get("", {"q": "Test Book", "remote": True}) + anonymous_user = AnonymousUser + anonymous_user.is_authenticated = False + request.user = anonymous_user + with patch("bookwyrm.views.search.is_api_request") as is_api: + is_api.return_value = False + response = view(request) + self.assertIsInstance(response, TemplateResponse) + response.render() + connector_results = response.context_data["results"] + self.assertEqual(len(connector_results), 1) + self.assertEqual(connector_results[0]["results"][0].title, "Test Book") + def test_search_users(self): """searches remote connectors""" view = views.Search.as_view() From 7d8cd999263ba66efa34298be95796e9364f34e0 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 3 Oct 2021 09:38:41 -0700 Subject: [PATCH 12/20] Remove hard limit on search endpoints --- bookwyrm/views/search.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bookwyrm/views/search.py b/bookwyrm/views/search.py index 4c19a1937..f03206e3b 100644 --- a/bookwyrm/views/search.py +++ b/bookwyrm/views/search.py @@ -101,7 +101,7 @@ def user_search(query, viewer, *_): .filter( similarity__gt=0.5, ) - .order_by("-similarity")[:10] + .order_by("-similarity") ), None @@ -122,5 +122,5 @@ def list_search(query, viewer, *_): .filter( similarity__gt=0.1, ) - .order_by("-similarity")[:10] + .order_by("-similarity") ), None From 9059b78b57601c0dcdf79732515ab0150d59f2dc Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 3 Oct 2021 09:44:27 -0700 Subject: [PATCH 13/20] Fixes testing if endpoint got results --- bookwyrm/views/search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/views/search.py b/bookwyrm/views/search.py index 4c19a1937..33ce37066 100644 --- a/bookwyrm/views/search.py +++ b/bookwyrm/views/search.py @@ -71,7 +71,7 @@ def book_search(query, user, min_confidence, search_remote=False): """the real business is elsewhere""" # try a local-only search results = [{"results": search(query, min_confidence=min_confidence)}] - if not user.is_authenticated or (results and not search_remote): + if not user.is_authenticated or (results[0]["results"] and not search_remote): return results, False # if there were no local results, or the request was for remote, search all sources From 668f71f96c28141c37ede383a3b73b878245dfac Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 3 Oct 2021 11:19:03 -0700 Subject: [PATCH 14/20] Tests block and unblock activitystream signals --- .../tests/activitystreams/test_signals.py | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/bookwyrm/tests/activitystreams/test_signals.py b/bookwyrm/tests/activitystreams/test_signals.py index 1c94cc9f5..eb70d28e4 100644 --- a/bookwyrm/tests/activitystreams/test_signals.py +++ b/bookwyrm/tests/activitystreams/test_signals.py @@ -16,6 +16,9 @@ class ActivitystreamsSignals(TestCase): self.local_user = models.User.objects.create_user( "mouse", "mouse@mouse.mouse", "password", local=True, localname="mouse" ) + self.another_user = models.User.objects.create_user( + "fish", "fish@fish.fish", "password", local=True, localname="fish" + ) with patch("bookwyrm.models.user.set_remote_server.delay"): self.remote_user = models.User.objects.create_user( "rat", @@ -66,3 +69,49 @@ class ActivitystreamsSignals(TestCase): args = mock.call_args[0] self.assertEqual(args[0], "books") self.assertEqual(args[1], self.local_user.id) + + def test_remove_statuses_on_block(self, _): + """don't show statuses from blocked users""" + with patch("bookwyrm.activitystreams.remove_user_statuses_task.delay") as mock: + models.UserBlocks.objects.create( + user_subject=self.local_user, + user_object=self.remote_user, + ) + + args = mock.call_args[0] + self.assertEqual(args[0], self.local_user.id) + self.assertEqual(args[1], self.remote_user.id) + + def test_add_statuses_on_unblock(self, _): + """re-add statuses on unblock""" + with patch("bookwyrm.activitystreams.remove_user_statuses_task.delay"): + block = models.UserBlocks.objects.create( + user_subject=self.local_user, + user_object=self.remote_user, + ) + + with patch("bookwyrm.activitystreams.add_user_statuses_task.delay") as mock: + block.delete() + + args = mock.call_args[0] + kwargs = mock.call_args.kwargs + self.assertEqual(args[0], self.local_user.id) + self.assertEqual(args[1], self.remote_user.id) + self.assertEqual(kwargs["stream_list"], ["local", "books"]) + + def test_add_statuses_on_unblock_reciprocal_block(self, _): + """re-add statuses on unblock""" + with patch("bookwyrm.activitystreams.remove_user_statuses_task.delay"): + block = models.UserBlocks.objects.create( + user_subject=self.local_user, + user_object=self.remote_user, + ) + block = models.UserBlocks.objects.create( + user_subject=self.remote_user, + user_object=self.local_user, + ) + + with patch("bookwyrm.activitystreams.add_user_statuses_task.delay") as mock: + block.delete() + + self.assertEqual(mock.call_count, 0) From 0798ba028f5d52f0e9691befb80600ef498eb1ad Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 3 Oct 2021 11:46:26 -0700 Subject: [PATCH 15/20] Fixes unblock signal --- bookwyrm/activitystreams.py | 11 +++++++++-- bookwyrm/views/preferences/block.py | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/bookwyrm/activitystreams.py b/bookwyrm/activitystreams.py index 5e0969e56..1feb495b7 100644 --- a/bookwyrm/activitystreams.py +++ b/bookwyrm/activitystreams.py @@ -331,8 +331,15 @@ def remove_statuses_on_block(sender, instance, *args, **kwargs): @receiver(signals.post_delete, sender=models.UserBlocks) # pylint: disable=unused-argument def add_statuses_on_unblock(sender, instance, *args, **kwargs): - """remove statuses from all feeds on block""" - public_streams = [v for (k, v) in streams.items() if k != "home"] + """add statuses back to all feeds on unblock""" + # make sure there isn't a block in the other direction + if models.UserBlocks.objects.filter( + user_subject=instance.user_object, + user_object=instance.user_subject, + ).exists(): + return + + public_streams = [k for (k, v) in streams.items() if k != "home"] # add statuses back to streams with statuses from anyone if instance.user_subject.local: diff --git a/bookwyrm/views/preferences/block.py b/bookwyrm/views/preferences/block.py index 90b3be90c..1eccf4612 100644 --- a/bookwyrm/views/preferences/block.py +++ b/bookwyrm/views/preferences/block.py @@ -14,7 +14,7 @@ class Block(View): """blocking users""" def get(self, request): - """list of blocked users?""" + """list of blocked users""" return TemplateResponse(request, "preferences/blocks.html") def post(self, request, user_id): From 889930aa69bfe07523aa9dbb63fcce130df76785 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 3 Oct 2021 11:55:16 -0700 Subject: [PATCH 16/20] Fixes create book flow for search refactor --- bookwyrm/views/books/edit_book.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/bookwyrm/views/books/edit_book.py b/bookwyrm/views/books/edit_book.py index 94bd14155..1445dc011 100644 --- a/bookwyrm/views/books/edit_book.py +++ b/bookwyrm/views/books/edit_book.py @@ -10,8 +10,7 @@ from django.utils.datastructures import MultiValueDictKeyError from django.utils.decorators import method_decorator from django.views import View -from bookwyrm import forms, models -from bookwyrm.connectors import connector_manager +from bookwyrm import book_search, forms, models from bookwyrm.views.helpers import get_edition from .books import set_cover_from_url @@ -73,10 +72,9 @@ class EditBook(View): if not book: # check if this is an edition of an existing work author_text = book.author_text if book else add_author - data["book_matches"] = connector_manager.local_search( + data["book_matches"] = book_search.search( f'{form.cleaned_data.get("title")} {author_text}', min_confidence=0.5, - raw=True, )[:5] # either of the above cases requires additional confirmation From 5cd8109820f903a76589203a0ed2e65a74d83a95 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 3 Oct 2021 12:15:44 -0700 Subject: [PATCH 17/20] Adds missing connector migration --- .../0105_alter_connector_connector_file.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 bookwyrm/migrations/0105_alter_connector_connector_file.py diff --git a/bookwyrm/migrations/0105_alter_connector_connector_file.py b/bookwyrm/migrations/0105_alter_connector_connector_file.py new file mode 100644 index 000000000..a6f08f774 --- /dev/null +++ b/bookwyrm/migrations/0105_alter_connector_connector_file.py @@ -0,0 +1,25 @@ +# Generated by Django 3.2.5 on 2021-10-03 19:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0104_auto_20211001_2012"), + ] + + operations = [ + migrations.AlterField( + model_name="connector", + name="connector_file", + field=models.CharField( + choices=[ + ("openlibrary", "Openlibrary"), + ("inventaire", "Inventaire"), + ("bookwyrm_connector", "Bookwyrm Connector"), + ], + max_length=255, + ), + ), + ] From 23b021e87d10d5f835cbaa4a4a3644312fc6539b Mon Sep 17 00:00:00 2001 From: "OragePika, aka \"FANS DON'T CARE" <78921858+oragegu@users.noreply.github.com> Date: Sun, 3 Oct 2021 21:48:36 +0200 Subject: [PATCH 18/20] Update django.po --- locale/de_DE/LC_MESSAGES/django.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locale/de_DE/LC_MESSAGES/django.po b/locale/de_DE/LC_MESSAGES/django.po index 569330faa..4ee3b9f74 100644 --- a/locale/de_DE/LC_MESSAGES/django.po +++ b/locale/de_DE/LC_MESSAGES/django.po @@ -2118,7 +2118,7 @@ msgstr "Beziehungen" #, fuzzy, python-format #| msgid "Finish \"%(book_title)s\"" msgid "Finish \"%(book_title)s\"" -msgstr "\"%(book_title)s\" abschließen" +msgstr "\"%(book_title)s\"zu Ende gelesen" #: bookwyrm/templates/reading_progress/start.html:5 #, fuzzy, python-format From ea182525491fed697592801a5fcde5a6e85ed619 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sun, 3 Oct 2021 13:26:02 -0700 Subject: [PATCH 19/20] Fixes broken migration --- bookwyrm/migrations/0101_auto_20210929_1847.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/migrations/0101_auto_20210929_1847.py b/bookwyrm/migrations/0101_auto_20210929_1847.py index bdda8484f..3fca28eac 100644 --- a/bookwyrm/migrations/0101_auto_20210929_1847.py +++ b/bookwyrm/migrations/0101_auto_20210929_1847.py @@ -17,7 +17,7 @@ def infer_format(app_registry, schema_editor): for edition in editions: free_format = edition.physical_format_detail.lower() edition.physical_format = infer_physical_format(free_format) - edition.save(broadcast=False, update_fields=["physical_format"]) + edition.save() def reverse(app_registry, schema_editor): From 1aa3d67d6406c8bbb46ab77eb1e479e61c64a840 Mon Sep 17 00:00:00 2001 From: oragegu Date: Mon, 4 Oct 2021 02:44:37 +0200 Subject: [PATCH 20/20] localisation compiled --- locale/fr_FR/LC_MESSAGES/django.mo | Bin 46063 -> 45713 bytes locale/fr_FR/LC_MESSAGES/django.po | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/locale/fr_FR/LC_MESSAGES/django.mo b/locale/fr_FR/LC_MESSAGES/django.mo index 641b48438b36f55f2cfc193d1cebb82ec9c568f2..fef5a0bafdeb1d25cc2cf11890f510326708073f 100644 GIT binary patch delta 13605 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 delta 13879 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