From 18ba33e05032f51183d2818a91007a298001ec28 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 5 Apr 2021 13:49:21 -0700 Subject: [PATCH 01/47] Uses redis for storing suggested users --- bookwyrm/suggested_users.py | 106 ++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 bookwyrm/suggested_users.py diff --git a/bookwyrm/suggested_users.py b/bookwyrm/suggested_users.py new file mode 100644 index 00000000..38dc37b0 --- /dev/null +++ b/bookwyrm/suggested_users.py @@ -0,0 +1,106 @@ +""" store recommended follows in redis """ +import math.floor +from django.dispatch import receiver +from django.db.models import signals, Q + +from bookwyrm import models +from bookwyrm.redis_store import RedisStore, r +from bookwyrm.views.helpers import get_annotated_users + + +class SuggestedUsers(RedisStore): + """ suggested users for a user """ + + max_length = 30 + + def get_rank(self, obj): + """ get computed rank """ + return obj.mutuals + (1.0 - (1.0 / (obj.shared_books + 1))) + + def store_id(self, user): # pylint: disable=no-self-use + """ the key used to store this user's recs """ + return "{:d}-suggestions".format(user.id) + + def get_counts_from_rank(self, rank): # pylint: disable=no-self-use + """ calculate mutuals count and shared books count from rank """ + return { + "mutuals": math.floor(rank), + "shared_books": int(1 / (-1 * (1 % rank - 1))), + } + + def get_objects_for_store(self, store): + """ a list of potential follows for a user """ + user = models.User.objects.get(id=store.split("-")[0]) + + return get_annotated_users( + user, + ~Q(id=user.id), + ~Q(followers=user), + ~Q(follower_requests=user), + bookwyrm_user=True, + ) + + def get_stores_for_object(self, obj): + """ given a user, who might want to follow them """ + return models.User.objects.filter( + local=True, + ).exclude(user_following=obj) + + def rerank_obj(self, obj): + """ update all the instances of this user with new ranks """ + stores = self.get_stores_for_object(obj) + pipeline = r.pipeline() + for store in stores: + pipeline.zadd(store, self.get_value(obj), xx=True) + pipeline.execute() + + def rerank_user_suggestions(self, user): + """ update the ranks of the follows suggested to a user """ + self.populate_store(self.store_id(user)) + + +suggested_users = SuggestedUsers() + + +@receiver(signals.post_save, sender=models.UserFollows) +# pylint: disable=unused-argument +def update_suggestions_on_follow(sender, instance, created, *args, **kwargs): + """ remove a follow from the recs and update the ranks""" + if ( + not created + or not instance.user_subject.local + or not instance.user_object.discoverable + ): + return + suggested_users.bulk_remove_objects_from_store( + [instance.user_object], instance.user_subject + ) + suggested_users.rerank_obj(instance.user_object) + + +@receiver(signals.post_save, sender=models.ShelfBook) +@receiver(signals.post_delete, sender=models.ShelfBook) +# pylint: disable=unused-argument +def update_rank_on_shelving(sender, instance, *args, **kwargs): + """ when a user shelves or unshelves a book, re-compute their rank """ + if not instance.user.discoverable: + return + suggested_users.rerank_obj(instance.user) + + +@receiver(signals.post_save, sender=models.User) +# pylint: disable=unused-argument, too-many-arguments +def add_or_remove_on_discoverability_change( + sender, instance, created, raw, using, update_fields, **kwargs +): + """ make a user (un)discoverable """ + if not "discoverable" in update_fields: + return + + if created: + suggested_users.rerank_user_suggestions(instance) + + if instance.discoverable: + suggested_users.add_object_to_related_stores(instance) + elif not created and not instance.discoverable: + suggested_users.remove_object_from_related_stores(instance) From 03e5da12ddea4debf364b8ee734af7e8284a49e8 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 6 Apr 2021 08:31:18 -0700 Subject: [PATCH 02/47] Call suggestions redis in feed --- bookwyrm/redis_store.py | 4 ++-- bookwyrm/suggested_users.py | 15 ++++++++++++++- bookwyrm/views/feed.py | 7 ++++--- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/bookwyrm/redis_store.py b/bookwyrm/redis_store.py index 4236d6df..5a9bb2f6 100644 --- a/bookwyrm/redis_store.py +++ b/bookwyrm/redis_store.py @@ -56,9 +56,9 @@ class RedisStore(ABC): pipeline.zrem(store, -1, obj.id) pipeline.execute() - def get_store(self, store): # pylint: disable=no-self-use + def get_store(self, store, **kwargs): # pylint: disable=no-self-use """ load the values in a store """ - return r.zrevrange(store, 0, -1) + return r.zrevrange(store, 0, -1, **kwargs) def populate_store(self, store): """ go from zero to a store """ diff --git a/bookwyrm/suggested_users.py b/bookwyrm/suggested_users.py index 38dc37b0..c6e6af85 100644 --- a/bookwyrm/suggested_users.py +++ b/bookwyrm/suggested_users.py @@ -11,7 +11,7 @@ from bookwyrm.views.helpers import get_annotated_users class SuggestedUsers(RedisStore): """ suggested users for a user """ - max_length = 30 + max_length = 10 def get_rank(self, obj): """ get computed rank """ @@ -58,6 +58,19 @@ class SuggestedUsers(RedisStore): """ update the ranks of the follows suggested to a user """ self.populate_store(self.store_id(user)) + def get_suggestions(self, user): + """ get suggestions """ + values = self.get_store(self.store_id(user), withscores=True) + results = [] + # annotate users with mutuals and shared book counts + for user_id, rank in values[:5]: + counts = self.get_counts_from_rank(rank) + user = models.User.objects.get(id=user_id) + user.mutuals = counts["mutuals"] + user.shared_books = counts["shared_books"] + results.append(user) + return results + suggested_users = SuggestedUsers() diff --git a/bookwyrm/views/feed.py b/bookwyrm/views/feed.py index cda11586..71e486f2 100644 --- a/bookwyrm/views/feed.py +++ b/bookwyrm/views/feed.py @@ -11,7 +11,8 @@ from django.views import View from bookwyrm import activitystreams, forms, models from bookwyrm.activitypub import ActivitypubResponse from bookwyrm.settings import PAGE_LENGTH, STREAMS -from .helpers import get_user_from_username, privacy_filter, get_suggested_users +from bookwyrm.suggested_users import suggested_users +from .helpers import get_user_from_username, privacy_filter from .helpers import is_api_request, is_bookwyrm_request, object_visible_to_user @@ -33,14 +34,14 @@ class Feed(View): activities = activitystreams.streams[tab].get_activity_stream(request.user) paginated = Paginator(activities, PAGE_LENGTH) - suggested_users = get_suggested_users(request.user) + suggestions = suggested_users.get_suggestions(request.user) data = { **feed_page_data(request.user), **{ "user": request.user, "activities": paginated.page(page), - "suggested_users": suggested_users, + "suggested_users": suggestions, "tab": tab, "goal_form": forms.GoalForm(), "path": "/%s" % tab, From dda21195de2ae563da65e1f4471d01baec7d9879 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Fri, 23 Apr 2021 16:34:04 -0700 Subject: [PATCH 03/47] Correct calls to annotated user set --- bookwyrm/suggested_users.py | 25 ++++++++++++++++++------- bookwyrm/views/directory.py | 2 +- bookwyrm/views/helpers.py | 14 +++++++------- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/bookwyrm/suggested_users.py b/bookwyrm/suggested_users.py index c6e6af85..eb1e8ebc 100644 --- a/bookwyrm/suggested_users.py +++ b/bookwyrm/suggested_users.py @@ -1,5 +1,5 @@ """ store recommended follows in redis """ -import math.floor +import math from django.dispatch import receiver from django.db.models import signals, Q @@ -41,17 +41,28 @@ class SuggestedUsers(RedisStore): ) def get_stores_for_object(self, obj): + return [self.store_id(u) for u in self.get_users_for_object(obj)] + + def get_users_for_object(self, obj): # pylint: disable=no-self-use """ given a user, who might want to follow them """ return models.User.objects.filter( local=True, - ).exclude(user_following=obj) + ).exclude(following=obj) def rerank_obj(self, obj): """ update all the instances of this user with new ranks """ - stores = self.get_stores_for_object(obj) pipeline = r.pipeline() - for store in stores: - pipeline.zadd(store, self.get_value(obj), xx=True) + for store_user in self.get_users_for_object(obj): + annotated_user = get_annotated_users( + store_user, + id=obj.id, + ).first() + + pipeline.zadd( + self.store_id(store_user), + self.get_value(annotated_user), + xx=True + ) pipeline.execute() def rerank_user_suggestions(self, user): @@ -107,13 +118,13 @@ def add_or_remove_on_discoverability_change( sender, instance, created, raw, using, update_fields, **kwargs ): """ make a user (un)discoverable """ - if not "discoverable" in update_fields: + if not update_fields or not "discoverable" in update_fields: return if created: suggested_users.rerank_user_suggestions(instance) if instance.discoverable: - suggested_users.add_object_to_related_stores(instance) + suggested_users.rerank_obj(instance) elif not created and not instance.discoverable: suggested_users.remove_object_from_related_stores(instance) diff --git a/bookwyrm/views/directory.py b/bookwyrm/views/directory.py index 9504734e..ae9dbe74 100644 --- a/bookwyrm/views/directory.py +++ b/bookwyrm/views/directory.py @@ -46,5 +46,5 @@ class Directory(View): def post(self, request): """ join the directory """ request.user.discoverable = True - request.user.save() + request.user.save(update_fields=["discoverable"]) return redirect("directory") diff --git a/bookwyrm/views/helpers.py b/bookwyrm/views/helpers.py index 75c5da8f..f133d4ab 100644 --- a/bookwyrm/views/helpers.py +++ b/bookwyrm/views/helpers.py @@ -207,27 +207,27 @@ def get_suggested_users(user): ) -def get_annotated_users(user, *args, **kwargs): +def get_annotated_users(viewer, *args, **kwargs): """ Users, annotated with things they have in common """ return ( models.User.objects.filter(discoverable=True, is_active=True, *args, **kwargs) - .exclude(Q(id__in=user.blocks.all()) | Q(blocks=user)) + .exclude(Q(id__in=viewer.blocks.all()) | Q(blocks=viewer)) .annotate( mutuals=Count( "following", filter=Q( - ~Q(id=user.id), - ~Q(id__in=user.following.all()), - following__in=user.following.all(), + ~Q(id=viewer.id), + ~Q(id__in=viewer.following.all()), + following__in=viewer.following.all(), ), distinct=True, ), shared_books=Count( "shelfbook", filter=Q( - ~Q(id=user.id), + ~Q(id=viewer.id), shelfbook__book__parent_work__in=[ - s.book.parent_work for s in user.shelfbook_set.all() + s.book.parent_work for s in viewer.shelfbook_set.all() ], ), distinct=True, From 9880bdc75bdfbc50e9e5c0acf0ed4c989d89934a Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Fri, 23 Apr 2021 18:26:48 -0700 Subject: [PATCH 04/47] Move anntotated users quuery into suggested users module --- bookwyrm/suggested_users.py | 36 ++++++++++++++++++++++++---- bookwyrm/views/directory.py | 4 ++-- bookwyrm/views/get_started.py | 3 +-- bookwyrm/views/helpers.py | 44 ----------------------------------- 4 files changed, 35 insertions(+), 52 deletions(-) diff --git a/bookwyrm/suggested_users.py b/bookwyrm/suggested_users.py index eb1e8ebc..2f2b750a 100644 --- a/bookwyrm/suggested_users.py +++ b/bookwyrm/suggested_users.py @@ -1,17 +1,16 @@ """ store recommended follows in redis """ import math from django.dispatch import receiver -from django.db.models import signals, Q +from django.db.models import signals, Count, Q from bookwyrm import models from bookwyrm.redis_store import RedisStore, r -from bookwyrm.views.helpers import get_annotated_users class SuggestedUsers(RedisStore): """ suggested users for a user """ - max_length = 10 + max_length = 30 def get_rank(self, obj): """ get computed rank """ @@ -25,7 +24,7 @@ class SuggestedUsers(RedisStore): """ calculate mutuals count and shared books count from rank """ return { "mutuals": math.floor(rank), - "shared_books": int(1 / (-1 * (1 % rank - 1))), + "shared_books": int(1 / (-1 * (1 % rank - 1))) if rank else 0, } def get_objects_for_store(self, store): @@ -83,6 +82,35 @@ class SuggestedUsers(RedisStore): return results +def get_annotated_users(viewer, *args, **kwargs): + """ Users, annotated with things they have in common """ + return ( + models.User.objects.filter(discoverable=True, is_active=True, *args, **kwargs) + .exclude(Q(id__in=viewer.blocks.all()) | Q(blocks=viewer)) + .annotate( + mutuals=Count( + "following", + filter=Q( + ~Q(id=viewer.id), + ~Q(id__in=viewer.following.all()), + following__in=viewer.following.all(), + ), + distinct=True, + ), + shared_books=Count( + "shelfbook", + filter=Q( + ~Q(id=viewer.id), + shelfbook__book__parent_work__in=[ + s.book.parent_work for s in viewer.shelfbook_set.all() + ], + ), + distinct=True, + ), + ) + ) + + suggested_users = SuggestedUsers() diff --git a/bookwyrm/views/directory.py b/bookwyrm/views/directory.py index ae9dbe74..52e65c19 100644 --- a/bookwyrm/views/directory.py +++ b/bookwyrm/views/directory.py @@ -6,7 +6,7 @@ from django.template.response import TemplateResponse from django.views import View from django.utils.decorators import method_decorator -from .helpers import get_annotated_users +from bookwyrm import suggested_users # pylint: disable=no-self-use @method_decorator(login_required, name="dispatch") @@ -29,7 +29,7 @@ class Directory(View): if scope == "local": filters["local"] = True - users = get_annotated_users(request.user, **filters) + users = suggested_users.get_annotated_users(request.user, **filters) sort = request.GET.get("sort") if sort == "recent": users = users.order_by("-last_active_date") diff --git a/bookwyrm/views/get_started.py b/bookwyrm/views/get_started.py index a21723a3..92da0c19 100644 --- a/bookwyrm/views/get_started.py +++ b/bookwyrm/views/get_started.py @@ -13,7 +13,6 @@ from django.views import View from bookwyrm import forms, models from bookwyrm.connectors import connector_manager -from .helpers import get_suggested_users from .user import save_user_form @@ -120,7 +119,7 @@ class GetStartedUsers(View): ) if user_results.count() < 5: - suggested_users = get_suggested_users(request.user) + suggested_users = None#get_suggested_users(request.user) data = { "suggested_users": list(user_results) + list(suggested_users), diff --git a/bookwyrm/views/helpers.py b/bookwyrm/views/helpers.py index f133d4ab..057027d4 100644 --- a/bookwyrm/views/helpers.py +++ b/bookwyrm/views/helpers.py @@ -190,47 +190,3 @@ def get_discover_books(): .order_by("-review__published_date__max")[:6] ) ) - - -def get_suggested_users(user): - """ bookwyrm users you don't already know """ - return ( - get_annotated_users( - user, - ~Q(id=user.id), - ~Q(followers=user), - ~Q(follower_requests=user), - bookwyrm_user=True, - ) - .order_by("-mutuals", "-last_active_date") - .all()[:5] - ) - - -def get_annotated_users(viewer, *args, **kwargs): - """ Users, annotated with things they have in common """ - return ( - models.User.objects.filter(discoverable=True, is_active=True, *args, **kwargs) - .exclude(Q(id__in=viewer.blocks.all()) | Q(blocks=viewer)) - .annotate( - mutuals=Count( - "following", - filter=Q( - ~Q(id=viewer.id), - ~Q(id__in=viewer.following.all()), - following__in=viewer.following.all(), - ), - distinct=True, - ), - shared_books=Count( - "shelfbook", - filter=Q( - ~Q(id=viewer.id), - shelfbook__book__parent_work__in=[ - s.book.parent_work for s in viewer.shelfbook_set.all() - ], - ), - distinct=True, - ), - ) - ) From 4fb85ced5f3a9ab1bd57de402bfb26216bed4fae Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 24 Apr 2021 11:16:35 -0700 Subject: [PATCH 05/47] Updates logic for new and newly discoverable users --- bookwyrm/suggested_users.py | 13 +++++++------ bookwyrm/views/user.py | 6 +++++- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/bookwyrm/suggested_users.py b/bookwyrm/suggested_users.py index 2f2b750a..ec6e5caa 100644 --- a/bookwyrm/suggested_users.py +++ b/bookwyrm/suggested_users.py @@ -48,7 +48,7 @@ class SuggestedUsers(RedisStore): local=True, ).exclude(following=obj) - def rerank_obj(self, obj): + def rerank_obj(self, obj, update_only=True): """ update all the instances of this user with new ranks """ pipeline = r.pipeline() for store_user in self.get_users_for_object(obj): @@ -60,7 +60,7 @@ class SuggestedUsers(RedisStore): pipeline.zadd( self.store_id(store_user), self.get_value(annotated_user), - xx=True + xx=update_only ) pipeline.execute() @@ -146,13 +146,14 @@ def add_or_remove_on_discoverability_change( sender, instance, created, raw, using, update_fields, **kwargs ): """ make a user (un)discoverable """ - if not update_fields or not "discoverable" in update_fields: - return - if created: suggested_users.rerank_user_suggestions(instance) + if not created and (not update_fields or not "discoverable" in update_fields): + return + if instance.discoverable: - suggested_users.rerank_obj(instance) + suggested_users.rerank_obj(instance, update_only=False) + elif not created and not instance.discoverable: suggested_users.remove_object_from_related_stores(instance) diff --git a/bookwyrm/views/user.py b/bookwyrm/views/user.py index aba804d8..6cbb82ef 100644 --- a/bookwyrm/views/user.py +++ b/bookwyrm/views/user.py @@ -181,7 +181,11 @@ def save_user_form(form): extension = form.files["avatar"].name.split(".")[-1] filename = "%s.%s" % (uuid4(), extension) user.avatar.save(filename, image, save=False) - user.save() + + updated_fields = None + if form.initial["discoverable"] != form.cleaned_data["discoverable"]: + updated_fields = ["discoverable"] + user.save(updated_fields=updated_fields) return user From 5d9cfe0276109ecfc20f233b41aaf909975b09ed Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 26 Apr 2021 10:34:40 -0700 Subject: [PATCH 06/47] Fixes followers/following logic on suggested users --- bookwyrm/suggested_users.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bookwyrm/suggested_users.py b/bookwyrm/suggested_users.py index ec6e5caa..482935ff 100644 --- a/bookwyrm/suggested_users.py +++ b/bookwyrm/suggested_users.py @@ -89,11 +89,11 @@ def get_annotated_users(viewer, *args, **kwargs): .exclude(Q(id__in=viewer.blocks.all()) | Q(blocks=viewer)) .annotate( mutuals=Count( - "following", + "followers", filter=Q( ~Q(id=viewer.id), ~Q(id__in=viewer.following.all()), - following__in=viewer.following.all(), + followers__in=viewer.following.all(), ), distinct=True, ), From 32b3a02a17df9279fe0081da04254750f944d4a8 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 26 Apr 2021 11:09:24 -0700 Subject: [PATCH 07/47] Fixes reverse rank calculation --- bookwyrm/suggested_users.py | 2 +- bookwyrm/tests/test_suggested_users.py | 69 ++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 bookwyrm/tests/test_suggested_users.py diff --git a/bookwyrm/suggested_users.py b/bookwyrm/suggested_users.py index 171e8d37..9bc51aa2 100644 --- a/bookwyrm/suggested_users.py +++ b/bookwyrm/suggested_users.py @@ -24,7 +24,7 @@ class SuggestedUsers(RedisStore): """calculate mutuals count and shared books count from rank""" return { "mutuals": math.floor(rank), - "shared_books": int(1 / (-1 * (1 % rank - 1))) if rank else 0, + "shared_books": int(1 / (-1 * (rank % 1 - 1))) - 1, } def get_objects_for_store(self, store): diff --git a/bookwyrm/tests/test_suggested_users.py b/bookwyrm/tests/test_suggested_users.py new file mode 100644 index 00000000..0b37cd1a --- /dev/null +++ b/bookwyrm/tests/test_suggested_users.py @@ -0,0 +1,69 @@ +""" testing user follow suggestions """ +from collections import namedtuple +from unittest.mock import patch + +from django.test import TestCase + +from bookwyrm import models +from bookwyrm.suggested_users import suggested_users + + +@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") +@patch("bookwyrm.activitystreams.ActivityStream.add_status") +class SuggestedUsers(TestCase): + """using redis to build activity streams""" + + def setUp(self): + """use a test csv""" + self.local_user = models.User.objects.create_user( + "mouse", "mouse@mouse.mouse", "password", local=True, localname="mouse" + ) + self.book = models.Edition.objects.create(title="test book") + + def test_get_ramk(self, *_): + """a float that reflects both the mutuals count and shared books""" + Mock = namedtuple("AnnotatedUserMock", ("mutuals", "shared_books")) + annotated_user_mock = Mock(3, 27) + rank = suggested_users.get_rank(annotated_user_mock) + self.assertEqual(rank, 3.9642857142857144) + + def test_store_id(self, *_): + """redis key generation""" + self.assertEqual( + suggested_users.store_id(self.local_user), + "{:d}-suggestions".format(self.local_user.id), + ) + + def test_get_counts_from_rank(self, *_): + """reverse the rank computation to get the mutuals and shared books counts""" + counts = suggested_users.get_counts_from_rank(3.9642857142857144) + self.assertEqual(counts["mutuals"], 3) + self.assertEqual(counts["shared_books"], 27) + + def test_get_objects_for_store(self, *_): + """list of people to follow for a given user""" + + mutual_user = models.User.objects.create_user( + "rat", "rat@local.rat", "password", local=True, localname="rat" + ) + suggestable_user = models.User.objects.create_user( + "nutria", + "nutria@nutria.nutria", + "password", + local=True, + localname="nutria", + discoverable=True, + ) + + # you follow rat + mutual_user.followers.add(self.local_user) + # rat follows the suggested user + suggestable_user.followers.add(mutual_user) + + results = suggested_users.get_objects_for_store( + "{:d}-suggestions".format(self.local_user.id) + ) + self.assertEqual(results.count(), 1) + match = results.first() + self.assertEqual(match.id, suggestable_user.id) + self.assertEqual(match.mutuals, 1) From 2ff79c99530af999cada1d5166c5e25c329b223d Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 22 May 2021 09:55:38 -0700 Subject: [PATCH 08/47] Fixes python formatting --- bookwyrm/views/get_started.py | 4 ++-- bookwyrm/views/helpers.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bookwyrm/views/get_started.py b/bookwyrm/views/get_started.py index 1bed03df..bae23212 100644 --- a/bookwyrm/views/get_started.py +++ b/bookwyrm/views/get_started.py @@ -120,9 +120,9 @@ class GetStartedUsers(View): if user_results.count() < 5: suggested_users = [] # TODO: get_suggested_users(request.user) - user_results = list(user_results) + list(suggested_users)) + user_results = list(user_results) + list(suggested_users) data = { "suggested_users": user_results, } - return TemplateResponse(request, "get_started/users.html", data) \ No newline at end of file + return TemplateResponse(request, "get_started/users.html", data) diff --git a/bookwyrm/views/helpers.py b/bookwyrm/views/helpers.py index b4dcc904..169e59b4 100644 --- a/bookwyrm/views/helpers.py +++ b/bookwyrm/views/helpers.py @@ -172,4 +172,4 @@ def get_discover_books(): .annotate(Max("review__published_date")) .order_by("-review__published_date__max")[:6] ) - ) \ No newline at end of file + ) From f98576bc252a7cc97a671b8b19975d473b9d4d63 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 22 May 2021 11:01:35 -0700 Subject: [PATCH 09/47] Only create suggestions stores for local users --- bookwyrm/suggested_users.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bookwyrm/suggested_users.py b/bookwyrm/suggested_users.py index 9bc51aa2..a6d09ecd 100644 --- a/bookwyrm/suggested_users.py +++ b/bookwyrm/suggested_users.py @@ -146,14 +146,18 @@ def add_or_remove_on_discoverability_change( sender, instance, created, raw, using, update_fields, **kwargs ): """make a user (un)discoverable""" - if created: + if created and instance.local: + # a new user is found, create suggestions for them suggested_users.rerank_user_suggestions(instance) if not created and (not update_fields or not "discoverable" in update_fields): + # this is just a regular old user update, not related to discoverability return if instance.discoverable: + # add this user to all suitable stores suggested_users.rerank_obj(instance, update_only=False) elif not created and not instance.discoverable: + # remove this user from all suitable stores suggested_users.remove_object_from_related_stores(instance) From 0044dc65875a628c998af147664c5ea5bc6cd497 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 22 May 2021 11:20:39 -0700 Subject: [PATCH 10/47] Show suggested users when the feed is empty --- bookwyrm/suggested_users.py | 2 ++ bookwyrm/templates/feed/feed.html | 16 ++++++---- bookwyrm/templates/feed/suggested_users.html | 29 ++++--------------- bookwyrm/templates/get_started/users.html | 2 +- .../templates/snippets/suggested_users.html | 25 ++++++++++++++++ 5 files changed, 43 insertions(+), 31 deletions(-) create mode 100644 bookwyrm/templates/snippets/suggested_users.html diff --git a/bookwyrm/suggested_users.py b/bookwyrm/suggested_users.py index a6d09ecd..7b20461a 100644 --- a/bookwyrm/suggested_users.py +++ b/bookwyrm/suggested_users.py @@ -66,6 +66,8 @@ class SuggestedUsers(RedisStore): def rerank_user_suggestions(self, user): """update the ranks of the follows suggested to a user""" + if not user.local: + raise ValueError('Attempting to create suggestions for remote user: ', user.id) self.populate_store(self.store_id(user)) def get_suggestions(self, user): diff --git a/bookwyrm/templates/feed/feed.html b/bookwyrm/templates/feed/feed.html index 21e71ae1..78b03436 100644 --- a/bookwyrm/templates/feed/feed.html +++ b/bookwyrm/templates/feed/feed.html @@ -44,18 +44,22 @@ {# activity feed #} {% if not activities %} -

{% trans "There aren't any activities right now! Try following a user to get started" %}

+
+

{% trans "There aren't any activities right now! Try following a user to get started" %}

+ + {% if suggested_users %} + {# suggested users for when things are very lonely #} + {% include 'feed/suggested_users.html' with suggested_users=suggested_users %} +
+{% endif %} + {% endif %} {% for activity in activities %} {% if not activities.number > 1 and forloop.counter0 == 2 and suggested_users %} {# suggested users on the first page, two statuses down #} -
-

{% trans "Who to follow" %}

- {% include 'feed/suggested_users.html' with suggested_users=suggested_users %} - View directory -
+{% include 'feed/suggested_users.html' with suggested_users=suggested_users %} {% endif %}
{% include 'snippets/status/status.html' with status=activity %} diff --git a/bookwyrm/templates/feed/suggested_users.html b/bookwyrm/templates/feed/suggested_users.html index eb146f7e..c095faa5 100644 --- a/bookwyrm/templates/feed/suggested_users.html +++ b/bookwyrm/templates/feed/suggested_users.html @@ -1,25 +1,6 @@ {% load i18n %} -{% load utilities %} -{% load humanize %} -
- {% for user in suggested_users %} -
-
- - {% include 'snippets/avatar.html' with user=user large=True %} - {{ user.display_name|truncatechars:10 }} - @{{ user|username|truncatechars:8 }} - - {% include 'snippets/follow_button.html' with user=user minimal=True %} - {% if user.mutuals %} -

- {% blocktrans with mutuals=user.mutuals|intcomma count counter=user.mutuals %}{{ mutuals }} follower you follow{% plural %}{{ mutuals }} followers you follow{% endblocktrans %} -

- {% elif user.shared_books %} -

{% blocktrans with shared_books=user.shared_books|intcomma count counter=user.shared_books %}{{ shared_books }} book on your shelves{% plural %}{{ shared_books }} books on your shelves{% endblocktrans %}

- {% endif %} -
-
- {% endfor %} -
- +
+

{% trans "Who to follow" %}

+ {% include 'snippets/suggested_users.html' with suggested_users=suggested_users %} + View directory +
diff --git a/bookwyrm/templates/get_started/users.html b/bookwyrm/templates/get_started/users.html index 259f06d3..753691fb 100644 --- a/bookwyrm/templates/get_started/users.html +++ b/bookwyrm/templates/get_started/users.html @@ -22,7 +22,7 @@
- {% include 'feed/suggested_users.html' with suggested_users=suggested_users %} + {% include 'snippets/suggested_users.html' with suggested_users=suggested_users %} {% endblock %} diff --git a/bookwyrm/templates/snippets/suggested_users.html b/bookwyrm/templates/snippets/suggested_users.html new file mode 100644 index 00000000..eb146f7e --- /dev/null +++ b/bookwyrm/templates/snippets/suggested_users.html @@ -0,0 +1,25 @@ +{% load i18n %} +{% load utilities %} +{% load humanize %} +
+ {% for user in suggested_users %} +
+
+ + {% include 'snippets/avatar.html' with user=user large=True %} + {{ user.display_name|truncatechars:10 }} + @{{ user|username|truncatechars:8 }} + + {% include 'snippets/follow_button.html' with user=user minimal=True %} + {% if user.mutuals %} +

+ {% blocktrans with mutuals=user.mutuals|intcomma count counter=user.mutuals %}{{ mutuals }} follower you follow{% plural %}{{ mutuals }} followers you follow{% endblocktrans %} +

+ {% elif user.shared_books %} +

{% blocktrans with shared_books=user.shared_books|intcomma count counter=user.shared_books %}{{ shared_books }} book on your shelves{% plural %}{{ shared_books }} books on your shelves{% endblocktrans %}

+ {% endif %} +
+
+ {% endfor %} +
+ From 29130d5f44b1f5d87e9003536421da2de03ceac8 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 22 May 2021 11:53:20 -0700 Subject: [PATCH 11/47] Detect new users and users with updated discoverability --- bookwyrm/suggested_users.py | 38 ++++++++++++++++++++++++++----------- bookwyrm/views/directory.py | 2 +- bookwyrm/views/user.py | 5 +---- 3 files changed, 29 insertions(+), 16 deletions(-) diff --git a/bookwyrm/suggested_users.py b/bookwyrm/suggested_users.py index 7b20461a..90613191 100644 --- a/bookwyrm/suggested_users.py +++ b/bookwyrm/suggested_users.py @@ -67,7 +67,7 @@ class SuggestedUsers(RedisStore): def rerank_user_suggestions(self, user): """update the ranks of the follows suggested to a user""" if not user.local: - raise ValueError('Attempting to create suggestions for remote user: ', user.id) + raise ValueError("Trying to create suggestions for remote user: ", user.id) self.populate_store(self.store_id(user)) def get_suggestions(self, user): @@ -144,22 +144,38 @@ def update_rank_on_shelving(sender, instance, *args, **kwargs): @receiver(signals.post_save, sender=models.User) # pylint: disable=unused-argument, too-many-arguments -def add_or_remove_on_discoverability_change( - sender, instance, created, raw, using, update_fields, **kwargs -): - """make a user (un)discoverable""" - if created and instance.local: - # a new user is found, create suggestions for them - suggested_users.rerank_user_suggestions(instance) +def add_new_user(sender, instance, created, **kwargs): + """a new user, wow how cool""" + if not created or not instance.local: + return + # a new user is found, create suggestions for them + suggested_users.rerank_user_suggestions(instance) - if not created and (not update_fields or not "discoverable" in update_fields): - # this is just a regular old user update, not related to discoverability + if instance.discoverable: + # idk why this would happen, but the new user is already discoverable + # so we should add them to the suggestions + suggested_users.rerank_obj(instance, update_only=False) + + +@receiver(signals.pre_save, sender=models.User) +# pylint: disable=unused-argument, too-many-arguments +def set_discoverability(sender, instance, **kwargs): + """make a user (un)discoverable""" + if not instance.id: + # this means the user was created, which is handled in `add_new_user` return + was_discoverable = models.User.objects.get(id=instance.id).discoverable + if was_discoverable == instance.discoverable: + # no change in discoverability, who cares + return + + # the user is newly available if instance.discoverable: # add this user to all suitable stores suggested_users.rerank_obj(instance, update_only=False) - elif not created and not instance.discoverable: + # the user is newly un-available + else: # remove this user from all suitable stores suggested_users.remove_object_from_related_stores(instance) diff --git a/bookwyrm/views/directory.py b/bookwyrm/views/directory.py index 108537cc..0dc3f8f5 100644 --- a/bookwyrm/views/directory.py +++ b/bookwyrm/views/directory.py @@ -40,5 +40,5 @@ class Directory(View): def post(self, request): """join the directory""" request.user.discoverable = True - request.user.save(update_fields=["discoverable"]) + request.user.save() return redirect("directory") diff --git a/bookwyrm/views/user.py b/bookwyrm/views/user.py index 2ab38a43..9684a68f 100644 --- a/bookwyrm/views/user.py +++ b/bookwyrm/views/user.py @@ -157,10 +157,7 @@ def save_user_form(form): filename = "%s.%s" % (uuid4(), extension) user.avatar.save(filename, image, save=False) - updated_fields = None - if form.initial["discoverable"] != form.cleaned_data["discoverable"]: - updated_fields = ["discoverable"] - user.save(updated_fields=updated_fields) + user.save() return user From f849d785a5955f977ad7eb8892d6a8590c9620e2 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 22 May 2021 12:10:14 -0700 Subject: [PATCH 12/47] Functional un-discoverable setting --- bookwyrm/suggested_users.py | 34 +++++----------------------------- 1 file changed, 5 insertions(+), 29 deletions(-) diff --git a/bookwyrm/suggested_users.py b/bookwyrm/suggested_users.py index 90613191..ae60a769 100644 --- a/bookwyrm/suggested_users.py +++ b/bookwyrm/suggested_users.py @@ -146,36 +146,12 @@ def update_rank_on_shelving(sender, instance, *args, **kwargs): # pylint: disable=unused-argument, too-many-arguments def add_new_user(sender, instance, created, **kwargs): """a new user, wow how cool""" - if not created or not instance.local: - return - # a new user is found, create suggestions for them - suggested_users.rerank_user_suggestions(instance) + if created and instance.local: + # a new user is found, create suggestions for them + suggested_users.rerank_user_suggestions(instance) + # TODO: this happens on every save, not just when discoverability changes if instance.discoverable: - # idk why this would happen, but the new user is already discoverable - # so we should add them to the suggestions suggested_users.rerank_obj(instance, update_only=False) - - -@receiver(signals.pre_save, sender=models.User) -# pylint: disable=unused-argument, too-many-arguments -def set_discoverability(sender, instance, **kwargs): - """make a user (un)discoverable""" - if not instance.id: - # this means the user was created, which is handled in `add_new_user` - return - - was_discoverable = models.User.objects.get(id=instance.id).discoverable - if was_discoverable == instance.discoverable: - # no change in discoverability, who cares - return - - # the user is newly available - if instance.discoverable: - # add this user to all suitable stores - suggested_users.rerank_obj(instance, update_only=False) - - # the user is newly un-available - else: - # remove this user from all suitable stores + elif not created: suggested_users.remove_object_from_related_stores(instance) From 42699a8d252f08883a5c7ebc46dd9ea5dc27c46b Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 22 May 2021 12:20:23 -0700 Subject: [PATCH 13/47] Update your own suggestions on shelve --- bookwyrm/suggested_users.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bookwyrm/suggested_users.py b/bookwyrm/suggested_users.py index ae60a769..3136803d 100644 --- a/bookwyrm/suggested_users.py +++ b/bookwyrm/suggested_users.py @@ -137,6 +137,11 @@ def update_suggestions_on_follow(sender, instance, created, *args, **kwargs): # pylint: disable=unused-argument def update_rank_on_shelving(sender, instance, *args, **kwargs): """when a user shelves or unshelves a book, re-compute their rank""" + # if it's a local user, re-calculate who is rec'ed to them + if instance.user.local: + suggested_users.rerank_user_suggestions(instance.user) + + # if the user is discoverable, update their rankings if not instance.user.discoverable: return suggested_users.rerank_obj(instance.user) From 9250b8b85dc1a0c7bb9d300c0cf781e9f8c03df1 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 22 May 2021 12:56:37 -0700 Subject: [PATCH 14/47] Handle follow/unfollow --- bookwyrm/suggested_users.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/bookwyrm/suggested_users.py b/bookwyrm/suggested_users.py index 3136803d..d0937cfd 100644 --- a/bookwyrm/suggested_users.py +++ b/bookwyrm/suggested_users.py @@ -70,6 +70,10 @@ class SuggestedUsers(RedisStore): raise ValueError("Trying to create suggestions for remote user: ", user.id) self.populate_store(self.store_id(user)) + def remove_suggestion(self, user, suggested_user): + """take a user out of someone's suggestions""" + self.bulk_remove_objects_from_store([suggested_user], self.store_id(user)) + def get_suggestions(self, user): """get suggestions""" values = self.get_store(self.store_id(user), withscores=True) @@ -120,18 +124,22 @@ suggested_users = SuggestedUsers() # pylint: disable=unused-argument def update_suggestions_on_follow(sender, instance, created, *args, **kwargs): """remove a follow from the recs and update the ranks""" - if ( - not created - or not instance.user_subject.local - or not instance.user_object.discoverable - ): + if not created or not instance.user_object.discoverable: return - suggested_users.bulk_remove_objects_from_store( - [instance.user_object], instance.user_subject - ) + + if instance.user_subject.local: + suggested_users.remove_suggestion(instance.user_subject, instance.user_object) suggested_users.rerank_obj(instance.user_object) +@receiver(signals.post_delete, sender=models.UserFollows) +# pylint: disable=unused-argument +def update_suggestions_on_unfollow(sender, instance, **kwargs): + """update rankings, but don't re-suggest because it was probably intentional""" + if instance.user_object.discoverable: + suggested_users.rerank_obj(instance.user_object) + + @receiver(signals.post_save, sender=models.ShelfBook) @receiver(signals.post_delete, sender=models.ShelfBook) # pylint: disable=unused-argument From 644e5926db4cbb116285a18832a62ccb95a8aa70 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 22 May 2021 14:05:59 -0700 Subject: [PATCH 15/47] Remove suggested users on block --- bookwyrm/suggested_users.py | 12 +++++++++++- bookwyrm/templates/get_started/users.html | 2 +- bookwyrm/views/get_started.py | 11 ++++++----- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/bookwyrm/suggested_users.py b/bookwyrm/suggested_users.py index d0937cfd..a98da13e 100644 --- a/bookwyrm/suggested_users.py +++ b/bookwyrm/suggested_users.py @@ -132,6 +132,16 @@ def update_suggestions_on_follow(sender, instance, created, *args, **kwargs): suggested_users.rerank_obj(instance.user_object) +@receiver(signals.post_save, sender=models.UserBlocks) +# pylint: disable=unused-argument +def update_suggestions_on_block(sender, instance, *args, **kwargs): + """remove blocked users from recs""" + if instance.user_subject.local: + suggested_users.remove_suggestion(instance.user_subject, instance.user_object) + if instance.user_object.local: + suggested_users.remove_suggestion(instance.user_object, instance.user_subject) + + @receiver(signals.post_delete, sender=models.UserFollows) # pylint: disable=unused-argument def update_suggestions_on_unfollow(sender, instance, **kwargs): @@ -163,7 +173,7 @@ def add_new_user(sender, instance, created, **kwargs): # a new user is found, create suggestions for them suggested_users.rerank_user_suggestions(instance) - # TODO: this happens on every save, not just when discoverability changes + # this happens on every save, not just when discoverability changes, annoyingly if instance.discoverable: suggested_users.rerank_obj(instance, update_only=False) elif not created: diff --git a/bookwyrm/templates/get_started/users.html b/bookwyrm/templates/get_started/users.html index 753691fb..7ec7ed9d 100644 --- a/bookwyrm/templates/get_started/users.html +++ b/bookwyrm/templates/get_started/users.html @@ -9,7 +9,7 @@
- {% if request.GET.query and not user_results %} + {% if request.GET.query and no_results %}

{% blocktrans with query=request.GET.query %}No users found for "{{ query }}"{% endblocktrans %}

{% endif %}
diff --git a/bookwyrm/views/get_started.py b/bookwyrm/views/get_started.py index bae23212..eeef1d9e 100644 --- a/bookwyrm/views/get_started.py +++ b/bookwyrm/views/get_started.py @@ -13,6 +13,7 @@ from django.views import View from bookwyrm import forms, models from bookwyrm.connectors import connector_manager +from bookwyrm.suggested_users import suggested_users from .user import save_user_form @@ -117,12 +118,12 @@ class GetStartedUsers(View): ) .order_by("-similarity")[:5] ) + data = {"no_results": not user_results} if user_results.count() < 5: - suggested_users = [] # TODO: get_suggested_users(request.user) - user_results = list(user_results) + list(suggested_users) + user_results = list(user_results) + suggested_users.get_suggestions( + request.user + ) - data = { - "suggested_users": user_results, - } + data["suggested_users"] = user_results return TemplateResponse(request, "get_started/users.html", data) From edfc27a3cdccdee39d8f2881fd6143075bc9cd6c Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 22 May 2021 15:53:07 -0700 Subject: [PATCH 16/47] Moves suggestion logic to celery --- bookwyrm/redis_store.py | 2 +- bookwyrm/suggested_users.py | 63 +++++++++++++++++++++++++++---------- celerywyrm/celery.py | 1 + 3 files changed, 49 insertions(+), 17 deletions(-) diff --git a/bookwyrm/redis_store.py b/bookwyrm/redis_store.py index b38c0e67..fa5c73a5 100644 --- a/bookwyrm/redis_store.py +++ b/bookwyrm/redis_store.py @@ -50,7 +50,7 @@ class RedisStore(ABC): pipeline.execute() def bulk_remove_objects_from_store(self, objs, store): - """remoev a list of objects from a given store""" + """remove a list of objects from a given store""" pipeline = r.pipeline() for obj in objs[: self.max_length]: pipeline.zrem(store, -1, obj.id) diff --git a/bookwyrm/suggested_users.py b/bookwyrm/suggested_users.py index a98da13e..bce3e5f1 100644 --- a/bookwyrm/suggested_users.py +++ b/bookwyrm/suggested_users.py @@ -5,6 +5,7 @@ from django.db.models import signals, Count, Q from bookwyrm import models from bookwyrm.redis_store import RedisStore, r +from bookwyrm.tasks import app class SuggestedUsers(RedisStore): @@ -18,6 +19,8 @@ class SuggestedUsers(RedisStore): def store_id(self, user): # pylint: disable=no-self-use """the key used to store this user's recs""" + if isinstance(user, int): + return "{:d}-suggestions".format(user) return "{:d}-suggestions".format(user.id) def get_counts_from_rank(self, rank): # pylint: disable=no-self-use @@ -46,7 +49,9 @@ class SuggestedUsers(RedisStore): """given a user, who might want to follow them""" return models.User.objects.filter( local=True, - ).exclude(following=obj) + ).exclude( + Q(id=obj.id) | Q(followers=obj) | Q(id__in=obj.blocks.all()) | Q(blocks=obj) + ) def rerank_obj(self, obj, update_only=True): """update all the instances of this user with new ranks""" @@ -56,6 +61,8 @@ class SuggestedUsers(RedisStore): store_user, id=obj.id, ).first() + if not annotated_user: + continue pipeline.zadd( self.store_id(store_user), @@ -66,8 +73,6 @@ class SuggestedUsers(RedisStore): def rerank_user_suggestions(self, user): """update the ranks of the follows suggested to a user""" - if not user.local: - raise ValueError("Trying to create suggestions for remote user: ", user.id) self.populate_store(self.store_id(user)) def remove_suggestion(self, user, suggested_user): @@ -128,8 +133,8 @@ def update_suggestions_on_follow(sender, instance, created, *args, **kwargs): return if instance.user_subject.local: - suggested_users.remove_suggestion(instance.user_subject, instance.user_object) - suggested_users.rerank_obj(instance.user_object) + remove_suggestion_task.delay(instance.user_subject.id, instance.user_object.id) + rerank_user_task.delay(instance.user_object.id) @receiver(signals.post_save, sender=models.UserBlocks) @@ -137,9 +142,9 @@ def update_suggestions_on_follow(sender, instance, created, *args, **kwargs): def update_suggestions_on_block(sender, instance, *args, **kwargs): """remove blocked users from recs""" if instance.user_subject.local: - suggested_users.remove_suggestion(instance.user_subject, instance.user_object) + remove_suggestion_task.delay(instance.user_subject.id, instance.user_object.id) if instance.user_object.local: - suggested_users.remove_suggestion(instance.user_object, instance.user_subject) + remove_suggestion_task.delay(instance.user_object.id, instance.user_subject.id) @receiver(signals.post_delete, sender=models.UserFollows) @@ -147,7 +152,7 @@ def update_suggestions_on_block(sender, instance, *args, **kwargs): def update_suggestions_on_unfollow(sender, instance, **kwargs): """update rankings, but don't re-suggest because it was probably intentional""" if instance.user_object.discoverable: - suggested_users.rerank_obj(instance.user_object) + rerank_user_task.delay(instance.user_object.id) @receiver(signals.post_save, sender=models.ShelfBook) @@ -157,24 +162,50 @@ def update_rank_on_shelving(sender, instance, *args, **kwargs): """when a user shelves or unshelves a book, re-compute their rank""" # if it's a local user, re-calculate who is rec'ed to them if instance.user.local: - suggested_users.rerank_user_suggestions(instance.user) + rerank_suggestions_task.delay(instance.user.id) # if the user is discoverable, update their rankings - if not instance.user.discoverable: - return - suggested_users.rerank_obj(instance.user) + if instance.user.discoverable: + rerank_user_task.delay(instance.user.id) @receiver(signals.post_save, sender=models.User) # pylint: disable=unused-argument, too-many-arguments def add_new_user(sender, instance, created, **kwargs): """a new user, wow how cool""" + # a new user is found, create suggestions for them if created and instance.local: - # a new user is found, create suggestions for them - suggested_users.rerank_user_suggestions(instance) + rerank_suggestions_task.delay(instance.id) # this happens on every save, not just when discoverability changes, annoyingly if instance.discoverable: - suggested_users.rerank_obj(instance, update_only=False) + rerank_user_task.delay(instance.id, update_only=False) elif not created: - suggested_users.remove_object_from_related_stores(instance) + remove_user_task.delay(instance.id) + + +@app.task +def rerank_suggestions_task(user_id): + """do the hard work in celery""" + suggested_users.rerank_user_suggestions(user_id) + + +@app.task +def rerank_user_task(user_id, update_only=False): + """do the hard work in celery""" + user = models.User.objects.get(id=user_id) + suggested_users.rerank_obj(user, update_only=update_only) + + +@app.task +def remove_user_task(user_id): + """do the hard work in celery""" + user = models.User.objects.get(id=user_id) + suggested_users.remove_object_from_related_stores(user) + + +@app.task +def remove_suggestion_task(user_id, suggested_user_id): + """remove a specific user from a specific user's suggestions""" + suggested_user = models.User.objects.get(id=suggested_user_id) + suggested_users.remove_suggestion(user_id, suggested_user) diff --git a/celerywyrm/celery.py b/celerywyrm/celery.py index 4af8e281..fcf75b04 100644 --- a/celerywyrm/celery.py +++ b/celerywyrm/celery.py @@ -25,4 +25,5 @@ app.autodiscover_tasks(["bookwyrm"], related_name="connectors.abstract_connector app.autodiscover_tasks(["bookwyrm"], related_name="emailing") app.autodiscover_tasks(["bookwyrm"], related_name="goodreads_import") app.autodiscover_tasks(["bookwyrm"], related_name="models.user") +app.autodiscover_tasks(["bookwyrm"], related_name="suggested_users") app.autodiscover_tasks(["bookwyrm"], related_name="views.inbox") From 5174260351c659530b0616e82e95f74eb94f7570 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 22 May 2021 15:57:08 -0700 Subject: [PATCH 17/47] Python formatting --- bookwyrm/suggested_users.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bookwyrm/suggested_users.py b/bookwyrm/suggested_users.py index bce3e5f1..2aee6d88 100644 --- a/bookwyrm/suggested_users.py +++ b/bookwyrm/suggested_users.py @@ -47,9 +47,7 @@ class SuggestedUsers(RedisStore): def get_users_for_object(self, obj): # pylint: disable=no-self-use """given a user, who might want to follow them""" - return models.User.objects.filter( - local=True, - ).exclude( + return models.User.objects.filter(local=True,).exclude( Q(id=obj.id) | Q(followers=obj) | Q(id__in=obj.blocks.all()) | Q(blocks=obj) ) From 98e537280e74183a2c0ae4828816a3b6defc29b9 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 22 May 2021 16:10:11 -0700 Subject: [PATCH 18/47] Adds management command for populating suggestions --- .../management/commands/populate_streams.py | 8 +---- .../commands/populate_suggestions.py | 25 +++++++++++++ bookwyrm/management/commands/ratings_bot.py | 35 +++++++++++++++++++ bw-dev | 5 ++- 4 files changed, 65 insertions(+), 8 deletions(-) create mode 100644 bookwyrm/management/commands/populate_suggestions.py create mode 100644 bookwyrm/management/commands/ratings_bot.py diff --git a/bookwyrm/management/commands/populate_streams.py b/bookwyrm/management/commands/populate_streams.py index 04f6bf6e..f8aa21a5 100644 --- a/bookwyrm/management/commands/populate_streams.py +++ b/bookwyrm/management/commands/populate_streams.py @@ -1,12 +1,6 @@ """ Re-create user streams """ from django.core.management.base import BaseCommand -import redis - -from bookwyrm import activitystreams, models, settings - -r = redis.Redis( - host=settings.REDIS_ACTIVITY_HOST, port=settings.REDIS_ACTIVITY_PORT, db=0 -) +from bookwyrm import activitystreams, models def populate_streams(): diff --git a/bookwyrm/management/commands/populate_suggestions.py b/bookwyrm/management/commands/populate_suggestions.py new file mode 100644 index 00000000..32495497 --- /dev/null +++ b/bookwyrm/management/commands/populate_suggestions.py @@ -0,0 +1,25 @@ +""" Populate suggested users """ +from django.core.management.base import BaseCommand + +from bookwyrm import models +from bookwyrm.suggested_users import rerank_suggestions_task + + +def populate_suggestions(): + """build all the streams for all the users""" + users = models.User.objects.filter( + local=True, + is_active=True, + ).values_list("id", flat=True) + for user in users: + rerank_suggestions_task.delay(user) + + +class Command(BaseCommand): + """start all over with user suggestions""" + + help = "Populate suggested users for all users" + # pylint: disable=no-self-use,unused-argument + def handle(self, *args, **options): + """run builder""" + populate_suggestions() diff --git a/bookwyrm/management/commands/ratings_bot.py b/bookwyrm/management/commands/ratings_bot.py new file mode 100644 index 00000000..57cd8499 --- /dev/null +++ b/bookwyrm/management/commands/ratings_bot.py @@ -0,0 +1,35 @@ +""" we have the goodreads ratings......... """ +from datetime import datetime +from django.core.management.base import BaseCommand +from django.utils import timezone +from bookwyrm import models + + +def get_ratings(): + """find and set ratings based on goodreads import lines""" + import_items = models.ImportItem.objects.filter(book__isnull=False).all() + user = models.User.objects.get(localname="goodreads-average-ratings") + for item in import_items: + rating = item.data.get("Average Rating") + if ( + not rating + or models.ReviewRating.objects.filter(user=user, book=item.book).exists() + ): + continue + models.ReviewRating.objects.create( + user=user, + rating=float(rating), + book=item.book.edition, + published_date=timezone.make_aware(datetime(2000, 1, 1)), # long ago + privacy="followers", + ) + + +class Command(BaseCommand): + """dedplucate allllll the book data models""" + + help = "merges duplicate book data" + # pylint: disable=no-self-use,unused-argument + def handle(self, *args, **options): + """run deudplications""" + get_ratings() diff --git a/bw-dev b/bw-dev index c2b63bc1..0b583083 100755 --- a/bw-dev +++ b/bw-dev @@ -107,7 +107,10 @@ case "$CMD" in populate_streams) runweb python manage.py populate_streams ;; + populate_suggestions) + runweb python manage.py populate_suggestions + ;; *) - echo "Unrecognised command. Try: build, clean, up, initdb, resetdb, makemigrations, migrate, bash, shell, dbshell, restart_celery, test, pytest, test_report, black, populate_feeds" + echo "Unrecognised command. Try: build, clean, up, initdb, resetdb, makemigrations, migrate, bash, shell, dbshell, restart_celery, test, pytest, test_report, black, populate_streams, populate_suggestions" ;; esac From b29ca222279c05fa00a0ed8cc6aed7734e9f6b14 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Thu, 27 May 2021 17:29:24 -0700 Subject: [PATCH 19/47] A couple test mocks --- bookwyrm/tests/test_suggested_users.py | 18 ++++++++++++++---- bw-dev | 2 +- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/bookwyrm/tests/test_suggested_users.py b/bookwyrm/tests/test_suggested_users.py index 0b37cd1a..af296624 100644 --- a/bookwyrm/tests/test_suggested_users.py +++ b/bookwyrm/tests/test_suggested_users.py @@ -15,12 +15,13 @@ class SuggestedUsers(TestCase): def setUp(self): """use a test csv""" - self.local_user = models.User.objects.create_user( - "mouse", "mouse@mouse.mouse", "password", local=True, localname="mouse" - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse", "mouse@mouse.mouse", "password", local=True, localname="mouse" + ) self.book = models.Edition.objects.create(title="test book") - def test_get_ramk(self, *_): + def test_get_rank(self, *_): """a float that reflects both the mutuals count and shared books""" Mock = namedtuple("AnnotatedUserMock", ("mutuals", "shared_books")) annotated_user_mock = Mock(3, 27) @@ -67,3 +68,12 @@ class SuggestedUsers(TestCase): match = results.first() self.assertEqual(match.id, suggestable_user.id) self.assertEqual(match.mutuals, 1) + + def test_create_user_signal(self, *_): + """build suggestions for new users""" + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") as mock: + models.User.objects.create_user( + "nutria", "nutria@nu.tria", "password", local=True, localname="nutria" + ) + + self.assertEqual(mock.call_count, 1) diff --git a/bw-dev b/bw-dev index 0b583083..08bb5bc2 100755 --- a/bw-dev +++ b/bw-dev @@ -84,7 +84,7 @@ case "$CMD" in runweb coverage run --source='.' --omit="*/test*,celerywyrm*,bookwyrm/migrations/*" manage.py test "$@" ;; pytest) - runweb pytest --no-cov-on-fail "$@" + execweb pytest --no-cov-on-fail "$@" ;; collectstatic) runweb python manage.py collectstatic --no-input From 98ae5868894c0ec1c8469f615c885c4e2218417c Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Fri, 18 Jun 2021 16:52:12 -0700 Subject: [PATCH 20/47] Newline in following template --- bookwyrm/templates/user/relationships/following.html | 1 + 1 file changed, 1 insertion(+) diff --git a/bookwyrm/templates/user/relationships/following.html b/bookwyrm/templates/user/relationships/following.html index 5689bc61..475d40ab 100644 --- a/bookwyrm/templates/user/relationships/following.html +++ b/bookwyrm/templates/user/relationships/following.html @@ -12,3 +12,4 @@ {% blocktrans with username=user.display_name %}{{ username }} isn't following any users{% endblocktrans %} {% endblock %} + From beb42b17f17785fcdc496c3173d9a515514e1ec0 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Fri, 18 Jun 2021 16:56:33 -0700 Subject: [PATCH 21/47] Removes unused import --- bookwyrm/views/helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/views/helpers.py b/bookwyrm/views/helpers.py index 371bde8a..42aa48f0 100644 --- a/bookwyrm/views/helpers.py +++ b/bookwyrm/views/helpers.py @@ -2,7 +2,7 @@ import re from requests import HTTPError from django.core.exceptions import FieldError -from django.db.models import Count, Max, Q +from django.db.models import Max, Q from django.http import Http404 from bookwyrm import activitypub, models From c00b35dc7c25e7ed6a3f4746d9899e1cf9c0e5dd Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 2 Aug 2021 17:55:17 -0700 Subject: [PATCH 22/47] Stray file --- bookwyrm/management/commands/ratings_bot.py | 35 --------------------- 1 file changed, 35 deletions(-) delete mode 100644 bookwyrm/management/commands/ratings_bot.py diff --git a/bookwyrm/management/commands/ratings_bot.py b/bookwyrm/management/commands/ratings_bot.py deleted file mode 100644 index 57cd8499..00000000 --- a/bookwyrm/management/commands/ratings_bot.py +++ /dev/null @@ -1,35 +0,0 @@ -""" we have the goodreads ratings......... """ -from datetime import datetime -from django.core.management.base import BaseCommand -from django.utils import timezone -from bookwyrm import models - - -def get_ratings(): - """find and set ratings based on goodreads import lines""" - import_items = models.ImportItem.objects.filter(book__isnull=False).all() - user = models.User.objects.get(localname="goodreads-average-ratings") - for item in import_items: - rating = item.data.get("Average Rating") - if ( - not rating - or models.ReviewRating.objects.filter(user=user, book=item.book).exists() - ): - continue - models.ReviewRating.objects.create( - user=user, - rating=float(rating), - book=item.book.edition, - published_date=timezone.make_aware(datetime(2000, 1, 1)), # long ago - privacy="followers", - ) - - -class Command(BaseCommand): - """dedplucate allllll the book data models""" - - help = "merges duplicate book data" - # pylint: disable=no-self-use,unused-argument - def handle(self, *args, **options): - """run deudplications""" - get_ratings() From 01334b6613c214126e85843874f94ed16049fead Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 2 Aug 2021 17:55:30 -0700 Subject: [PATCH 23/47] Fixes merge wonkiness in bw-dev --- bw-dev | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bw-dev b/bw-dev index 5818a616..e5e46e5f 100755 --- a/bw-dev +++ b/bw-dev @@ -126,9 +126,6 @@ case "$CMD" in populate_suggestions) runweb python manage.py populate_suggestions ;; - *) - echo "Unrecognised command. Try: build, clean, up, initdb, resetdb, makemigrations, migrate, bash, shell, dbshell, restart_celery, test, pytest, test_report, black, populate_streams, populate_suggestions" - ;; generate_preview_images) runweb python manage.py generate_preview_images $@ ;; @@ -173,6 +170,7 @@ case "$CMD" in echo " clean" echo " black" echo " populate_streams" + echo " populate_suggestions" echo " generate_preview_images [--all]" echo " copy_media_to_s3" echo " set_cors_to_s3 [cors file]" From df9787dd7a7ae29ca86808a132881d77e743b948 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 2 Aug 2021 18:14:44 -0700 Subject: [PATCH 24/47] Removes stale data before doing repopulation This probably is only an issue when there are very few users, like my test instance --- bookwyrm/redis_store.py | 4 ++++ bookwyrm/suggested_users.py | 11 ++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/bookwyrm/redis_store.py b/bookwyrm/redis_store.py index fa5c73a5..578cb550 100644 --- a/bookwyrm/redis_store.py +++ b/bookwyrm/redis_store.py @@ -65,6 +65,10 @@ class RedisStore(ABC): pipeline = r.pipeline() queryset = self.get_objects_for_store(store) + # first, remove everything currently in it + pipeline.delete(store) + + # now, add everything back for obj in queryset[: self.max_length]: pipeline.zadd(store, self.get_value(obj)) diff --git a/bookwyrm/suggested_users.py b/bookwyrm/suggested_users.py index 2aee6d88..e588bc9e 100644 --- a/bookwyrm/suggested_users.py +++ b/bookwyrm/suggested_users.py @@ -1,5 +1,6 @@ """ store recommended follows in redis """ import math +import logging from django.dispatch import receiver from django.db.models import signals, Count, Q @@ -8,6 +9,9 @@ from bookwyrm.redis_store import RedisStore, r from bookwyrm.tasks import app +logger = logging.getLogger(__name__) + + class SuggestedUsers(RedisStore): """suggested users for a user""" @@ -84,7 +88,12 @@ class SuggestedUsers(RedisStore): # annotate users with mutuals and shared book counts for user_id, rank in values[:5]: counts = self.get_counts_from_rank(rank) - user = models.User.objects.get(id=user_id) + try: + user = models.User.objects.get(id=user_id) + except models.User.DoesNotExist as err: + # if this happens, the suggestions are janked way up + logger.exception(err) + continue user.mutuals = counts["mutuals"] user.shared_books = counts["shared_books"] results.append(user) From ee7bdc956a6340f7a294b0b9313ea3da4de08987 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 3 Aug 2021 07:43:03 -0700 Subject: [PATCH 25/47] Streamline saves on user create --- bookwyrm/models/user.py | 3 +-- bookwyrm/suggested_users.py | 5 ++++- bookwyrm/tests/test_activitystreams.py | 21 +++++++++++---------- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index 49458a2e..b87ffd7d 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -243,7 +243,6 @@ class User(OrderedCollectionPageMixin, AbstractUser): # generate a username that uses the domain (webfinger format) actor_parts = urlparse(self.remote_id) self.username = "%s@%s" % (self.username, actor_parts.netloc) - super().save(*args, **kwargs) # this user already exists, no need to populate fields if not created: @@ -276,7 +275,7 @@ class User(OrderedCollectionPageMixin, AbstractUser): self.key_pair = KeyPair.objects.create( remote_id="%s/#main-key" % self.remote_id ) - self.save(broadcast=False) + self.save(broadcast=False, update_fields=["key_pair"]) shelves = [ { diff --git a/bookwyrm/suggested_users.py b/bookwyrm/suggested_users.py index e588bc9e..b48b4e74 100644 --- a/bookwyrm/suggested_users.py +++ b/bookwyrm/suggested_users.py @@ -178,12 +178,15 @@ def update_rank_on_shelving(sender, instance, *args, **kwargs): @receiver(signals.post_save, sender=models.User) # pylint: disable=unused-argument, too-many-arguments -def add_new_user(sender, instance, created, **kwargs): +def add_new_user(sender, instance, created, update_fields=None, **kwargs): """a new user, wow how cool""" # a new user is found, create suggestions for them if created and instance.local: rerank_suggestions_task.delay(instance.id) + if update_fields and not "discoverable" in update_fields: + return + # this happens on every save, not just when discoverability changes, annoyingly if instance.discoverable: rerank_user_task.delay(instance.id, update_only=False) diff --git a/bookwyrm/tests/test_activitystreams.py b/bookwyrm/tests/test_activitystreams.py index 0dd8ffe3..f4747971 100644 --- a/bookwyrm/tests/test_activitystreams.py +++ b/bookwyrm/tests/test_activitystreams.py @@ -11,16 +11,17 @@ class Activitystreams(TestCase): def setUp(self): """use a test csv""" - 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( - "nutria", - "nutria@nutria.nutria", - "password", - local=True, - localname="nutria", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + 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( + "nutria", + "nutria@nutria.nutria", + "password", + local=True, + localname="nutria", + ) with patch("bookwyrm.models.user.set_remote_server.delay"): self.remote_user = models.User.objects.create_user( "rat", From a84a744e8d828f52d27ced5e6bd8c73b44d181c4 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 3 Aug 2021 08:48:15 -0700 Subject: [PATCH 26/47] Track changed fields in activity to model code --- bookwyrm/activitypub/base_activity.py | 18 ++++++++--- bookwyrm/models/fields.py | 19 ++++++++--- bookwyrm/tests/activitypub/test_author.py | 4 +-- .../tests/activitypub/test_base_activity.py | 32 ++++++++++--------- bookwyrm/tests/test_emailing.py | 15 +++++---- bookwyrm/tests/test_preview_images.py | 27 ++++++++-------- bookwyrm/tests/test_signing.py | 27 ++++++++-------- bookwyrm/tests/test_templatetags.py | 15 +++++---- 8 files changed, 91 insertions(+), 66 deletions(-) diff --git a/bookwyrm/activitypub/base_activity.py b/bookwyrm/activitypub/base_activity.py index 81762388..da32fbaf 100644 --- a/bookwyrm/activitypub/base_activity.py +++ b/bookwyrm/activitypub/base_activity.py @@ -106,6 +106,7 @@ class ActivityObject: value = field.default setattr(self, field.name, value) + # pylint: disable=too-many-locals,too-many-branches def to_model(self, model=None, instance=None, allow_create=True, save=True): """convert from an activity to a model instance""" model = model or get_model_from_type(self.type) @@ -126,27 +127,36 @@ class ActivityObject: return None instance = instance or model() + # keep track of what we've changed + update_fields = [] for field in instance.simple_fields: try: - field.set_field_from_activity(instance, self) + changed = field.set_field_from_activity(instance, self) + if changed: + update_fields.append(field.name) except AttributeError as e: raise ActivitySerializerError(e) # image fields have to be set after other fields because they can save # too early and jank up users for field in instance.image_fields: - field.set_field_from_activity(instance, self, save=save) + changed = field.set_field_from_activity(instance, self, save=save) + if changed: + update_fields.append(field.name) if not save: return instance with transaction.atomic(): + # can't force an update on fields unless the object already exists in the db + if not instance.id: + update_fields = None # we can't set many to many and reverse fields on an unsaved object try: try: - instance.save(broadcast=False) + instance.save(broadcast=False, update_fields=update_fields) except TypeError: - instance.save() + instance.save(update_fields=update_fields) except IntegrityError as e: raise ActivitySerializerError(e) diff --git a/bookwyrm/models/fields.py b/bookwyrm/models/fields.py index 3b369e84..02efe675 100644 --- a/bookwyrm/models/fields.py +++ b/bookwyrm/models/fields.py @@ -67,7 +67,7 @@ class ActivitypubFieldMixin: super().__init__(*args, **kwargs) def set_field_from_activity(self, instance, data): - """helper function for assinging a value to the field""" + """helper function for assinging a value to the field. Returns if changed""" try: value = getattr(data, self.get_activitypub_field()) except AttributeError: @@ -77,8 +77,14 @@ class ActivitypubFieldMixin: value = getattr(data, "actor") formatted = self.field_from_activity(value) if formatted is None or formatted is MISSING or formatted == {}: - return + return False + + # the field is unchanged + if getattr(instance, self.name) == formatted: + return False + setattr(instance, self.name, formatted) + return True def set_activity_from_field(self, activity, instance): """update the json object""" @@ -205,6 +211,7 @@ class PrivacyField(ActivitypubFieldMixin, models.CharField): # pylint: disable=invalid-name def set_field_from_activity(self, instance, data): + original = getattr(instance, self.name) to = data.to cc = data.cc if to == [self.public]: @@ -215,6 +222,7 @@ class PrivacyField(ActivitypubFieldMixin, models.CharField): setattr(instance, self.name, "unlisted") else: setattr(instance, self.name, "followers") + return original == getattr(instance, self.name) def set_activity_from_field(self, activity, instance): # explicitly to anyone mentioned (statuses only) @@ -270,9 +278,10 @@ class ManyToManyField(ActivitypubFieldMixin, models.ManyToManyField): value = getattr(data, self.get_activitypub_field()) formatted = self.field_from_activity(value) if formatted is None or formatted is MISSING: - return + return False getattr(instance, self.name).set(formatted) instance.save(broadcast=False) + return True def field_to_activity(self, value): if self.link_only: @@ -373,8 +382,10 @@ class ImageField(ActivitypubFieldMixin, models.ImageField): value = getattr(data, self.get_activitypub_field()) formatted = self.field_from_activity(value) if formatted is None or formatted is MISSING: - return + return False + getattr(instance, self.name).save(*formatted, save=save) + return True def set_activity_from_field(self, activity, instance): value = getattr(instance, self.name) diff --git a/bookwyrm/tests/activitypub/test_author.py b/bookwyrm/tests/activitypub/test_author.py index 6d65974a..0a344340 100644 --- a/bookwyrm/tests/activitypub/test_author.py +++ b/bookwyrm/tests/activitypub/test_author.py @@ -1,6 +1,4 @@ -import datetime - -from unittest.mock import patch +"""test author serializer""" from django.test import TestCase from bookwyrm import models diff --git a/bookwyrm/tests/activitypub/test_base_activity.py b/bookwyrm/tests/activitypub/test_base_activity.py index 77844a22..ba9abad9 100644 --- a/bookwyrm/tests/activitypub/test_base_activity.py +++ b/bookwyrm/tests/activitypub/test_base_activity.py @@ -20,16 +20,18 @@ from bookwyrm import models @patch("bookwyrm.activitystreams.ActivityStream.add_status") +@patch("bookwyrm.suggested_users.rerank_user_task.delay") class BaseActivity(TestCase): """the super class for model-linked activitypub dataclasses""" def setUp(self): """we're probably going to re-use this so why copy/paste""" - self.user = models.User.objects.create_user( - "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.user = models.User.objects.create_user( + "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" + ) self.user.remote_id = "http://example.com/a/b" - self.user.save(broadcast=False) + self.user.save(broadcast=False, update_fields=["remote_id"]) datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_user.json") self.userdata = json.loads(datafile.read_bytes()) @@ -44,24 +46,24 @@ class BaseActivity(TestCase): image.save(output, format=image.format) self.image_data = output.getvalue() - def test_init(self, _): + def test_init(self, *_): """simple successfuly init""" instance = ActivityObject(id="a", type="b") self.assertTrue(hasattr(instance, "id")) self.assertTrue(hasattr(instance, "type")) - def test_init_missing(self, _): + def test_init_missing(self, *_): """init with missing required params""" with self.assertRaises(ActivitySerializerError): ActivityObject() - def test_init_extra_fields(self, _): + def test_init_extra_fields(self, *_): """init ignoring additional fields""" instance = ActivityObject(id="a", type="b", fish="c") self.assertTrue(hasattr(instance, "id")) self.assertTrue(hasattr(instance, "type")) - def test_init_default_field(self, _): + def test_init_default_field(self, *_): """replace an existing required field with a default field""" @dataclass(init=False) @@ -74,7 +76,7 @@ class BaseActivity(TestCase): self.assertEqual(instance.id, "a") self.assertEqual(instance.type, "TestObject") - def test_serialize(self, _): + def test_serialize(self, *_): """simple function for converting dataclass to dict""" instance = ActivityObject(id="a", type="b") serialized = instance.serialize() @@ -83,7 +85,7 @@ class BaseActivity(TestCase): self.assertEqual(serialized["type"], "b") @responses.activate - def test_resolve_remote_id(self, _): + def test_resolve_remote_id(self, *_): """look up or load remote data""" # existing item result = resolve_remote_id("http://example.com/a/b", model=models.User) @@ -105,14 +107,14 @@ class BaseActivity(TestCase): self.assertEqual(result.remote_id, "https://example.com/user/mouse") self.assertEqual(result.name, "MOUSE?? MOUSE!!") - def test_to_model_invalid_model(self, _): + def test_to_model_invalid_model(self, *_): """catch mismatch between activity type and model type""" instance = ActivityObject(id="a", type="b") with self.assertRaises(ActivitySerializerError): instance.to_model(model=models.User) @responses.activate - def test_to_model_image(self, _): + def test_to_model_image(self, *_): """update an image field""" activity = activitypub.Person( id=self.user.remote_id, @@ -145,7 +147,7 @@ class BaseActivity(TestCase): self.assertEqual(self.user.name, "New Name") self.assertEqual(self.user.key_pair.public_key, "hi") - def test_to_model_many_to_many(self, _): + def test_to_model_many_to_many(self, *_): """annoying that these all need special handling""" with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create( @@ -176,7 +178,7 @@ class BaseActivity(TestCase): self.assertEqual(status.mention_books.first(), book) @responses.activate - def test_to_model_one_to_many(self, _): + def test_to_model_one_to_many(self, *_): """these are reversed relationships, where the secondary object keys the primary object but not vice versa""" with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): @@ -215,7 +217,7 @@ class BaseActivity(TestCase): self.assertIsNone(status.attachments.first()) @responses.activate - def test_set_related_field(self, _): + def test_set_related_field(self, *_): """celery task to add back-references to created objects""" with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): status = models.Status.objects.create( diff --git a/bookwyrm/tests/test_emailing.py b/bookwyrm/tests/test_emailing.py index 4f8b16eb..4c3d86a4 100644 --- a/bookwyrm/tests/test_emailing.py +++ b/bookwyrm/tests/test_emailing.py @@ -14,13 +14,14 @@ class Emailing(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.mouse", - "password", - local=True, - localname="mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) models.SiteSettings.objects.create() def test_invite_email(self, email_mock): diff --git a/bookwyrm/tests/test_preview_images.py b/bookwyrm/tests/test_preview_images.py index 04e4c591..37638c27 100644 --- a/bookwyrm/tests/test_preview_images.py +++ b/bookwyrm/tests/test_preview_images.py @@ -1,5 +1,6 @@ """ test generating preview images """ import pathlib +from unittest.mock import patch from PIL import Image from django.test import TestCase @@ -8,7 +9,6 @@ from django.core.files.uploadedfile import SimpleUploadedFile from django.db.models.fields.files import ImageFieldFile from bookwyrm import models, settings - from bookwyrm.preview_images import ( generate_site_preview_image_task, generate_edition_preview_image_task, @@ -29,18 +29,19 @@ class PreviewImages(TestCase): avatar_file = pathlib.Path(__file__).parent.joinpath( "../static/images/no_cover.jpg" ) - self.local_user = models.User.objects.create_user( - "possum@local.com", - "possum@possum.possum", - "password", - local=True, - localname="possum", - avatar=SimpleUploadedFile( - avatar_file, - open(avatar_file, "rb").read(), - content_type="image/jpeg", - ), - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "possum@local.com", + "possum@possum.possum", + "password", + local=True, + localname="possum", + avatar=SimpleUploadedFile( + avatar_file, + open(avatar_file, "rb").read(), + content_type="image/jpeg", + ), + ) self.work = models.Work.objects.create(title="Test Work") self.edition = models.Edition.objects.create( diff --git a/bookwyrm/tests/test_signing.py b/bookwyrm/tests/test_signing.py index 01b25904..d9a87dd5 100644 --- a/bookwyrm/tests/test_signing.py +++ b/bookwyrm/tests/test_signing.py @@ -37,19 +37,20 @@ class Signature(TestCase): def setUp(self): """create users and test data""" - self.mouse = models.User.objects.create_user( - "mouse@%s" % DOMAIN, - "mouse@example.com", - "", - local=True, - localname="mouse", - ) - self.rat = models.User.objects.create_user( - "rat@%s" % DOMAIN, "rat@example.com", "", local=True, localname="rat" - ) - self.cat = models.User.objects.create_user( - "cat@%s" % DOMAIN, "cat@example.com", "", local=True, localname="cat" - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.mouse = models.User.objects.create_user( + "mouse@%s" % DOMAIN, + "mouse@example.com", + "", + local=True, + localname="mouse", + ) + self.rat = models.User.objects.create_user( + "rat@%s" % DOMAIN, "rat@example.com", "", local=True, localname="rat" + ) + self.cat = models.User.objects.create_user( + "cat@%s" % DOMAIN, "cat@example.com", "", local=True, localname="cat" + ) private_key, public_key = create_key_pair() diff --git a/bookwyrm/tests/test_templatetags.py b/bookwyrm/tests/test_templatetags.py index 8e16c9d3..3c5d8b25 100644 --- a/bookwyrm/tests/test_templatetags.py +++ b/bookwyrm/tests/test_templatetags.py @@ -22,13 +22,14 @@ class TemplateTags(TestCase): def setUp(self): """create some filler objects""" - self.user = models.User.objects.create_user( - "mouse@example.com", - "mouse@mouse.mouse", - "mouseword", - local=True, - localname="mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.user = models.User.objects.create_user( + "mouse@example.com", + "mouse@mouse.mouse", + "mouseword", + local=True, + localname="mouse", + ) with patch("bookwyrm.models.user.set_remote_server.delay"): self.remote_user = models.User.objects.create_user( "rat", From cfbe1b29cddb857357a73b42d5a309a9ec18f39d Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 3 Aug 2021 09:12:22 -0700 Subject: [PATCH 27/47] Pass update fields when ticking last active date --- bookwyrm/models/favorite.py | 2 +- bookwyrm/models/readthrough.py | 4 +-- .../connectors/test_abstract_connector.py | 10 ++++--- .../tests/importers/test_goodreads_import.py | 30 +++++++++++-------- .../importers/test_librarything_import.py | 16 +++++----- bookwyrm/views/authentication.py | 2 +- 6 files changed, 36 insertions(+), 28 deletions(-) diff --git a/bookwyrm/models/favorite.py b/bookwyrm/models/favorite.py index c4518119..9ab300b3 100644 --- a/bookwyrm/models/favorite.py +++ b/bookwyrm/models/favorite.py @@ -30,7 +30,7 @@ class Favorite(ActivityMixin, BookWyrmModel): def save(self, *args, **kwargs): """update user active time""" self.user.last_active_date = timezone.now() - self.user.save(broadcast=False) + self.user.save(broadcast=False, update_fields=["last_active_date"]) super().save(*args, **kwargs) if self.status.user.local and self.status.user != self.user: diff --git a/bookwyrm/models/readthrough.py b/bookwyrm/models/readthrough.py index 664daa13..df341c8b 100644 --- a/bookwyrm/models/readthrough.py +++ b/bookwyrm/models/readthrough.py @@ -30,7 +30,7 @@ class ReadThrough(BookWyrmModel): def save(self, *args, **kwargs): """update user active time""" self.user.last_active_date = timezone.now() - self.user.save(broadcast=False) + self.user.save(broadcast=False, update_fields=["last_active_date"]) super().save(*args, **kwargs) def create_update(self): @@ -55,5 +55,5 @@ class ProgressUpdate(BookWyrmModel): def save(self, *args, **kwargs): """update user active time""" self.user.last_active_date = timezone.now() - self.user.save(broadcast=False) + self.user.save(broadcast=False, update_fields=["last_active_date"]) super().save(*args, **kwargs) diff --git a/bookwyrm/tests/connectors/test_abstract_connector.py b/bookwyrm/tests/connectors/test_abstract_connector.py index 5c50e4b7..8ce4c96b 100644 --- a/bookwyrm/tests/connectors/test_abstract_connector.py +++ b/bookwyrm/tests/connectors/test_abstract_connector.py @@ -119,10 +119,12 @@ class AbstractConnector(TestCase): @responses.activate def test_get_or_create_author(self): """load an author""" - self.connector.author_mappings = [ - Mapping("id"), - Mapping("name"), - ] + self.connector.author_mappings = ( + [ # pylint: disable=attribute-defined-outside-init + Mapping("id"), + Mapping("name"), + ] + ) responses.add( responses.GET, diff --git a/bookwyrm/tests/importers/test_goodreads_import.py b/bookwyrm/tests/importers/test_goodreads_import.py index 8a0b25ec..e92bdb35 100644 --- a/bookwyrm/tests/importers/test_goodreads_import.py +++ b/bookwyrm/tests/importers/test_goodreads_import.py @@ -16,9 +16,12 @@ from bookwyrm.settings import DOMAIN def make_date(*args): + """helper function to easily generate a date obj""" return datetime.datetime(*args, tzinfo=pytz.UTC) +# pylint: disable=consider-using-with +@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") class GoodreadsImport(TestCase): """importing from goodreads csv""" @@ -27,9 +30,10 @@ class GoodreadsImport(TestCase): self.importer = GoodreadsImporter() datafile = pathlib.Path(__file__).parent.joinpath("../data/goodreads.csv") self.csv = open(datafile, "r", encoding=self.importer.encoding) - self.user = models.User.objects.create_user( - "mouse", "mouse@mouse.mouse", "password", local=True - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.user = models.User.objects.create_user( + "mouse", "mouse@mouse.mouse", "password", local=True + ) models.Connector.objects.create( identifier=DOMAIN, @@ -49,7 +53,7 @@ class GoodreadsImport(TestCase): parent_work=work, ) - def test_create_job(self): + def test_create_job(self, _): """creates the import job entry and checks csv""" import_job = self.importer.create_job(self.user, self.csv, False, "public") self.assertEqual(import_job.user, self.user) @@ -65,7 +69,7 @@ class GoodreadsImport(TestCase): self.assertEqual(import_items[2].index, 2) self.assertEqual(import_items[2].data["Book Id"], "28694510") - def test_create_retry_job(self): + def test_create_retry_job(self, _): """trying again with items that didn't import""" import_job = self.importer.create_job(self.user, self.csv, False, "unlisted") import_items = models.ImportItem.objects.filter(job=import_job).all()[:2] @@ -83,7 +87,7 @@ class GoodreadsImport(TestCase): self.assertEqual(retry_items[1].index, 1) self.assertEqual(retry_items[1].data["Book Id"], "52691223") - def test_start_import(self): + def test_start_import(self, _): """begin loading books""" import_job = self.importer.create_job(self.user, self.csv, False, "unlisted") MockTask = namedtuple("Task", ("id")) @@ -95,7 +99,7 @@ class GoodreadsImport(TestCase): self.assertEqual(import_job.task_id, "7") @responses.activate - def test_import_data(self): + def test_import_data(self, _): """resolve entry""" import_job = self.importer.create_job(self.user, self.csv, False, "unlisted") book = models.Edition.objects.create(title="Test Book") @@ -110,7 +114,7 @@ class GoodreadsImport(TestCase): import_item = models.ImportItem.objects.get(job=import_job, index=0) self.assertEqual(import_item.book.id, book.id) - def test_handle_imported_book(self): + def test_handle_imported_book(self, _): """goodreads import added a book, this adds related connections""" shelf = self.user.shelf_set.filter(identifier="read").first() self.assertIsNone(shelf.books.first()) @@ -141,7 +145,7 @@ class GoodreadsImport(TestCase): self.assertEqual(readthrough.start_date, make_date(2020, 10, 21)) self.assertEqual(readthrough.finish_date, make_date(2020, 10, 25)) - def test_handle_imported_book_already_shelved(self): + def test_handle_imported_book_already_shelved(self, _): """goodreads import added a book, this adds related connections""" with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): shelf = self.user.shelf_set.filter(identifier="to-read").first() @@ -179,7 +183,7 @@ class GoodreadsImport(TestCase): self.assertEqual(readthrough.start_date, make_date(2020, 10, 21)) self.assertEqual(readthrough.finish_date, make_date(2020, 10, 25)) - def test_handle_import_twice(self): + def test_handle_import_twice(self, _): """re-importing books""" shelf = self.user.shelf_set.filter(identifier="read").first() import_job = models.ImportJob.objects.create(user=self.user) @@ -212,7 +216,7 @@ class GoodreadsImport(TestCase): self.assertEqual(readthrough.finish_date, make_date(2020, 10, 25)) @patch("bookwyrm.activitystreams.ActivityStream.add_status") - def test_handle_imported_book_review(self, _): + def test_handle_imported_book_review(self, *_): """goodreads review import""" import_job = models.ImportJob.objects.create(user=self.user) datafile = pathlib.Path(__file__).parent.joinpath("../data/goodreads.csv") @@ -234,7 +238,7 @@ class GoodreadsImport(TestCase): self.assertEqual(review.privacy, "unlisted") @patch("bookwyrm.activitystreams.ActivityStream.add_status") - def test_handle_imported_book_rating(self, _): + def test_handle_imported_book_rating(self, *_): """goodreads rating import""" import_job = models.ImportJob.objects.create(user=self.user) datafile = pathlib.Path(__file__).parent.joinpath( @@ -257,7 +261,7 @@ class GoodreadsImport(TestCase): self.assertEqual(review.published_date, make_date(2019, 7, 8)) self.assertEqual(review.privacy, "unlisted") - def test_handle_imported_book_reviews_disabled(self): + def test_handle_imported_book_reviews_disabled(self, _): """goodreads review import""" import_job = models.ImportJob.objects.create(user=self.user) datafile = pathlib.Path(__file__).parent.joinpath("../data/goodreads.csv") diff --git a/bookwyrm/tests/importers/test_librarything_import.py b/bookwyrm/tests/importers/test_librarything_import.py index 0bdfb263..c6f2176b 100644 --- a/bookwyrm/tests/importers/test_librarything_import.py +++ b/bookwyrm/tests/importers/test_librarything_import.py @@ -19,6 +19,8 @@ def make_date(*args): return datetime.datetime(*args, tzinfo=pytz.UTC) +# pylint: disable=consider-using-with +@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") class LibrarythingImport(TestCase): """importing from librarything tsv""" @@ -51,7 +53,7 @@ class LibrarythingImport(TestCase): parent_work=work, ) - def test_create_job(self): + def test_create_job(self, _): """creates the import job entry and checks csv""" import_job = self.importer.create_job(self.user, self.csv, False, "public") self.assertEqual(import_job.user, self.user) @@ -67,7 +69,7 @@ class LibrarythingImport(TestCase): self.assertEqual(import_items[2].index, 2) self.assertEqual(import_items[2].data["Book Id"], "5015399") - def test_create_retry_job(self): + def test_create_retry_job(self, _): """trying again with items that didn't import""" import_job = self.importer.create_job(self.user, self.csv, False, "unlisted") import_items = models.ImportItem.objects.filter(job=import_job).all()[:2] @@ -86,7 +88,7 @@ class LibrarythingImport(TestCase): self.assertEqual(retry_items[1].data["Book Id"], "5015319") @responses.activate - def test_import_data(self): + def test_import_data(self, _): """resolve entry""" import_job = self.importer.create_job(self.user, self.csv, False, "unlisted") book = models.Edition.objects.create(title="Test Book") @@ -101,7 +103,7 @@ class LibrarythingImport(TestCase): import_item = models.ImportItem.objects.get(job=import_job, index=0) self.assertEqual(import_item.book.id, book.id) - def test_handle_imported_book(self): + def test_handle_imported_book(self, _): """librarything import added a book, this adds related connections""" shelf = self.user.shelf_set.filter(identifier="read").first() self.assertIsNone(shelf.books.first()) @@ -131,7 +133,7 @@ class LibrarythingImport(TestCase): self.assertEqual(readthrough.start_date, make_date(2007, 4, 16)) self.assertEqual(readthrough.finish_date, make_date(2007, 5, 8)) - def test_handle_imported_book_already_shelved(self): + def test_handle_imported_book_already_shelved(self, _): """librarything import added a book, this adds related connections""" with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): shelf = self.user.shelf_set.filter(identifier="to-read").first() @@ -163,7 +165,7 @@ class LibrarythingImport(TestCase): self.assertEqual(readthrough.start_date, make_date(2007, 4, 16)) self.assertEqual(readthrough.finish_date, make_date(2007, 5, 8)) - def test_handle_import_twice(self): + def test_handle_import_twice(self, _): """re-importing books""" shelf = self.user.shelf_set.filter(identifier="read").first() import_job = models.ImportJob.objects.create(user=self.user) @@ -216,7 +218,7 @@ class LibrarythingImport(TestCase): self.assertEqual(review.published_date, make_date(2007, 5, 8)) self.assertEqual(review.privacy, "unlisted") - def test_handle_imported_book_reviews_disabled(self): + def test_handle_imported_book_reviews_disabled(self, _): """librarything review import""" import_job = models.ImportJob.objects.create(user=self.user) datafile = pathlib.Path(__file__).parent.joinpath("../data/librarything.tsv") diff --git a/bookwyrm/views/authentication.py b/bookwyrm/views/authentication.py index bfb49248..003e94b0 100644 --- a/bookwyrm/views/authentication.py +++ b/bookwyrm/views/authentication.py @@ -50,7 +50,7 @@ class Login(View): # successful login login(request, user) user.last_active_date = timezone.now() - user.save(broadcast=False) + user.save(broadcast=False, update_fields=["last_active_date"]) return redirect(request.GET.get("next", "/")) # login errors From 88967e589b8002a1b85f60e825e90be8cff1909f Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 3 Aug 2021 09:46:04 -0700 Subject: [PATCH 28/47] Uses better mock pattern in relationships model --- .../tests/models/test_relationship_models.py | 90 ++++++++----------- 1 file changed, 39 insertions(+), 51 deletions(-) diff --git a/bookwyrm/tests/models/test_relationship_models.py b/bookwyrm/tests/models/test_relationship_models.py index d629b5c7..f66cf109 100644 --- a/bookwyrm/tests/models/test_relationship_models.py +++ b/bookwyrm/tests/models/test_relationship_models.py @@ -1,4 +1,5 @@ """ testing models """ +import json from unittest.mock import patch from django.test import TestCase @@ -20,25 +21,21 @@ class Relationship(TestCase): inbox="https://example.com/users/rat/inbox", outbox="https://example.com/users/rat/outbox", ) - self.local_user = models.User.objects.create_user( - "mouse", "mouse@mouse.com", "mouseword", local=True, localname="mouse" - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse", "mouse@mouse.com", "mouseword", local=True, localname="mouse" + ) self.local_user.remote_id = "http://local.com/user/mouse" - self.local_user.save(broadcast=False) + self.local_user.save(broadcast=False, update_fields=["remote_id"]) def test_user_follows_from_request(self): """convert a follow request into a follow""" - real_broadcast = models.UserFollowRequest.broadcast - - def mock_broadcast(_, activity, user): - """introspect what's being sent out""" - self.assertEqual(user.remote_id, self.local_user.remote_id) - self.assertEqual(activity["type"], "Follow") - - models.UserFollowRequest.broadcast = mock_broadcast - request = models.UserFollowRequest.objects.create( - user_subject=self.local_user, user_object=self.remote_user - ) + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock: + request = models.UserFollowRequest.objects.create( + user_subject=self.local_user, user_object=self.remote_user + ) + activity = json.loads(mock.call_args[0][1]) + self.assertEqual(activity["type"], "Follow") self.assertEqual( request.remote_id, "http://local.com/user/mouse#follows/%d" % request.id ) @@ -51,7 +48,6 @@ class Relationship(TestCase): self.assertEqual(rel.status, "follows") self.assertEqual(rel.user_subject, self.local_user) self.assertEqual(rel.user_object, self.remote_user) - models.UserFollowRequest.broadcast = real_broadcast def test_user_follows_from_request_custom_remote_id(self): """store a specific remote id for a relationship provided by remote""" @@ -70,36 +66,26 @@ class Relationship(TestCase): self.assertEqual(rel.user_subject, self.local_user) self.assertEqual(rel.user_object, self.remote_user) - def test_follow_request_activity(self): + @patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") + def test_follow_request_activity(self, broadcast_mock): """accept a request and make it a relationship""" - real_broadcast = models.UserFollowRequest.broadcast - - def mock_broadcast(_, activity, user): - self.assertEqual(user.remote_id, self.local_user.remote_id) - self.assertEqual(activity["actor"], self.local_user.remote_id) - self.assertEqual(activity["object"], self.remote_user.remote_id) - self.assertEqual(activity["type"], "Follow") - - models.UserFollowRequest.broadcast = mock_broadcast models.UserFollowRequest.objects.create( user_subject=self.local_user, user_object=self.remote_user, ) - models.UserFollowRequest.broadcast = real_broadcast + activity = json.loads(broadcast_mock.call_args[0][1]) + self.assertEqual(activity["actor"], self.local_user.remote_id) + self.assertEqual(activity["object"], self.remote_user.remote_id) + self.assertEqual(activity["type"], "Follow") - def test_follow_request_accept(self): + @patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") + def test_follow_request_accept(self, broadcast_mock): """accept a request and make it a relationship""" - real_broadcast = models.UserFollowRequest.broadcast - - def mock_broadcast(_, activity, user): - self.assertEqual(user.remote_id, self.local_user.remote_id) - self.assertEqual(activity["type"], "Accept") - self.assertEqual(activity["actor"], self.local_user.remote_id) - self.assertEqual(activity["object"]["id"], "https://www.hi.com/") - self.local_user.manually_approves_followers = True - self.local_user.save(broadcast=False) - models.UserFollowRequest.broadcast = mock_broadcast + self.local_user.save( + broadcast=False, update_fields=["manually_approves_followers"] + ) + request = models.UserFollowRequest.objects.create( user_subject=self.remote_user, user_object=self.local_user, @@ -107,32 +93,34 @@ class Relationship(TestCase): ) request.accept() + activity = json.loads(broadcast_mock.call_args[0][1]) + self.assertEqual(activity["type"], "Accept") + self.assertEqual(activity["actor"], self.local_user.remote_id) + self.assertEqual(activity["object"]["id"], "https://www.hi.com/") + self.assertFalse(models.UserFollowRequest.objects.exists()) self.assertTrue(models.UserFollows.objects.exists()) rel = models.UserFollows.objects.get() self.assertEqual(rel.user_subject, self.remote_user) self.assertEqual(rel.user_object, self.local_user) - models.UserFollowRequest.broadcast = real_broadcast - def test_follow_request_reject(self): + @patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") + def test_follow_request_reject(self, broadcast_mock): """accept a request and make it a relationship""" - real_broadcast = models.UserFollowRequest.broadcast - - def mock_reject(_, activity, user): - self.assertEqual(user.remote_id, self.local_user.remote_id) - self.assertEqual(activity["type"], "Reject") - self.assertEqual(activity["actor"], self.local_user.remote_id) - self.assertEqual(activity["object"]["id"], request.remote_id) - - models.UserFollowRequest.broadcast = mock_reject self.local_user.manually_approves_followers = True - self.local_user.save(broadcast=False) + self.local_user.save( + broadcast=False, update_fields=["manually_approves_followers"] + ) request = models.UserFollowRequest.objects.create( user_subject=self.remote_user, user_object=self.local_user, ) request.reject() + activity = json.loads(broadcast_mock.call_args[0][1]) + self.assertEqual(activity["type"], "Reject") + self.assertEqual(activity["actor"], self.local_user.remote_id) + self.assertEqual(activity["object"]["id"], request.remote_id) + self.assertFalse(models.UserFollowRequest.objects.exists()) self.assertFalse(models.UserFollows.objects.exists()) - models.UserFollowRequest.broadcast = real_broadcast From be044bce0de604052c832e9f4be84b785bd84283 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 3 Aug 2021 10:25:53 -0700 Subject: [PATCH 29/47] Updates mocks --- bookwyrm/suggested_users.py | 4 +- .../importers/test_librarything_import.py | 9 +-- .../tests/management/test_populate_streams.py | 21 +++---- .../tests/models/test_activitypub_mixin.py | 7 ++- bookwyrm/tests/models/test_base_model.py | 7 ++- bookwyrm/tests/models/test_import_model.py | 7 ++- bookwyrm/tests/models/test_list.py | 7 ++- .../tests/models/test_readthrough_model.py | 8 ++- bookwyrm/tests/models/test_shelf_model.py | 16 +++--- bookwyrm/tests/models/test_status_model.py | 7 ++- bookwyrm/tests/models/test_user_model.py | 19 ++++--- bookwyrm/tests/views/test_announcements.py | 15 ++--- bookwyrm/tests/views/test_authentication.py | 30 +++++----- bookwyrm/tests/views/test_author.py | 17 +++--- bookwyrm/tests/views/test_block.py | 15 ++--- bookwyrm/tests/views/test_book.py | 17 +++--- bookwyrm/tests/views/test_directory.py | 50 ++++++++++++---- bookwyrm/tests/views/test_edit_user.py | 21 +++---- bookwyrm/tests/views/test_federation.py | 15 ++--- bookwyrm/tests/views/test_feed.py | 15 ++--- bookwyrm/tests/views/test_follow.py | 57 ++++++++++--------- bookwyrm/tests/views/test_get_started.py | 15 ++--- bookwyrm/tests/views/test_goal.py | 33 +++++------ bookwyrm/tests/views/test_helpers.py | 48 ++++++++-------- bookwyrm/tests/views/test_import.py | 15 ++--- bookwyrm/tests/views/test_interaction.py | 17 +++--- bookwyrm/tests/views/test_invite.py | 15 ++--- bookwyrm/tests/views/test_isbn.py | 17 +++--- bookwyrm/tests/views/test_landing.py | 15 ++--- bookwyrm/tests/views/test_list.py | 33 +++++------ bookwyrm/tests/views/test_list_actions.py | 33 +++++------ bookwyrm/tests/views/test_notifications.py | 16 +++--- bookwyrm/tests/views/test_outbox.py | 17 +++--- bookwyrm/tests/views/test_password.py | 15 ++--- bookwyrm/tests/views/test_reading.py | 17 +++--- bookwyrm/tests/views/test_readthrough.py | 7 ++- bookwyrm/tests/views/test_reports.py | 30 +++++----- bookwyrm/tests/views/test_rss_feed.py | 7 ++- bookwyrm/tests/views/test_search.py | 17 +++--- bookwyrm/tests/views/test_shelf.py | 17 +++--- bookwyrm/tests/views/test_status.py | 46 ++++++++------- bookwyrm/tests/views/test_updates.py | 15 ++--- bookwyrm/tests/views/test_user.py | 21 +++---- bookwyrm/tests/views/test_user_admin.py | 15 ++--- bookwyrm/tests/views/test_wellknown.py | 21 +++---- 45 files changed, 473 insertions(+), 393 deletions(-) diff --git a/bookwyrm/suggested_users.py b/bookwyrm/suggested_users.py index b48b4e74..d8a8f865 100644 --- a/bookwyrm/suggested_users.py +++ b/bookwyrm/suggested_users.py @@ -148,9 +148,9 @@ def update_suggestions_on_follow(sender, instance, created, *args, **kwargs): # pylint: disable=unused-argument def update_suggestions_on_block(sender, instance, *args, **kwargs): """remove blocked users from recs""" - if instance.user_subject.local: + if instance.user_subject.local and instance.user_object.discoverable: remove_suggestion_task.delay(instance.user_subject.id, instance.user_object.id) - if instance.user_object.local: + if instance.user_object.local and instance.user_subject.discoverable: remove_suggestion_task.delay(instance.user_object.id, instance.user_subject.id) diff --git a/bookwyrm/tests/importers/test_librarything_import.py b/bookwyrm/tests/importers/test_librarything_import.py index c6f2176b..504951c2 100644 --- a/bookwyrm/tests/importers/test_librarything_import.py +++ b/bookwyrm/tests/importers/test_librarything_import.py @@ -31,9 +31,10 @@ class LibrarythingImport(TestCase): # Librarything generates latin encoded exports... self.csv = open(datafile, "r", encoding=self.importer.encoding) - self.user = models.User.objects.create_user( - "mmai", "mmai@mmai.mmai", "password", local=True - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.user = models.User.objects.create_user( + "mmai", "mmai@mmai.mmai", "password", local=True + ) models.Connector.objects.create( identifier=DOMAIN, @@ -197,7 +198,7 @@ class LibrarythingImport(TestCase): self.assertEqual(readthrough.finish_date, make_date(2007, 5, 8)) @patch("bookwyrm.activitystreams.ActivityStream.add_status") - def test_handle_imported_book_review(self, _): + def test_handle_imported_book_review(self, *_): """librarything review import""" import_job = models.ImportJob.objects.create(user=self.user) datafile = pathlib.Path(__file__).parent.joinpath("../data/librarything.tsv") diff --git a/bookwyrm/tests/management/test_populate_streams.py b/bookwyrm/tests/management/test_populate_streams.py index ddbce7d3..ee7a96d7 100644 --- a/bookwyrm/tests/management/test_populate_streams.py +++ b/bookwyrm/tests/management/test_populate_streams.py @@ -12,16 +12,17 @@ class Activitystreams(TestCase): def setUp(self): """we need some stuff""" - 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( - "nutria", - "nutria@nutria.nutria", - "password", - local=True, - localname="nutria", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + 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( + "nutria", + "nutria@nutria.nutria", + "password", + local=True, + localname="nutria", + ) with patch("bookwyrm.models.user.set_remote_server.delay"): self.remote_user = models.User.objects.create_user( "rat", diff --git a/bookwyrm/tests/models/test_activitypub_mixin.py b/bookwyrm/tests/models/test_activitypub_mixin.py index 6d8fe270..aea87e8d 100644 --- a/bookwyrm/tests/models/test_activitypub_mixin.py +++ b/bookwyrm/tests/models/test_activitypub_mixin.py @@ -27,9 +27,10 @@ class ActivitypubMixins(TestCase): def setUp(self): """shared data""" - self.local_user = models.User.objects.create_user( - "mouse", "mouse@mouse.com", "mouseword", local=True, localname="mouse" - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse", "mouse@mouse.com", "mouseword", local=True, localname="mouse" + ) self.local_user.remote_id = "http://example.com/a/b" self.local_user.save(broadcast=False) with patch("bookwyrm.models.user.set_remote_server.delay"): diff --git a/bookwyrm/tests/models/test_base_model.py b/bookwyrm/tests/models/test_base_model.py index e140887a..43cd5d3f 100644 --- a/bookwyrm/tests/models/test_base_model.py +++ b/bookwyrm/tests/models/test_base_model.py @@ -13,9 +13,10 @@ class BaseModel(TestCase): def setUp(self): """shared data""" - self.local_user = models.User.objects.create_user( - "mouse", "mouse@mouse.com", "mouseword", local=True, localname="mouse" - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse", "mouse@mouse.com", "mouseword", local=True, localname="mouse" + ) with patch("bookwyrm.models.user.set_remote_server.delay"): self.remote_user = models.User.objects.create_user( "rat", diff --git a/bookwyrm/tests/models/test_import_model.py b/bookwyrm/tests/models/test_import_model.py index 76a914d1..15a75a96 100644 --- a/bookwyrm/tests/models/test_import_model.py +++ b/bookwyrm/tests/models/test_import_model.py @@ -59,9 +59,10 @@ class ImportJob(TestCase): unknown_read_data["Exclusive Shelf"] = "read" unknown_read_data["Date Read"] = "" - user = models.User.objects.create_user( - "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + user = models.User.objects.create_user( + "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" + ) job = models.ImportJob.objects.create(user=user) self.item_1 = models.ImportItem.objects.create( job=job, index=1, data=currently_reading_data diff --git a/bookwyrm/tests/models/test_list.py b/bookwyrm/tests/models/test_list.py index 8f5bd497..6e1ff809 100644 --- a/bookwyrm/tests/models/test_list.py +++ b/bookwyrm/tests/models/test_list.py @@ -11,9 +11,10 @@ class List(TestCase): def setUp(self): """look, a list""" - self.local_user = models.User.objects.create_user( - "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" + ) work = models.Work.objects.create(title="hello") self.book = models.Edition.objects.create(title="hi", parent_work=work) diff --git a/bookwyrm/tests/models/test_readthrough_model.py b/bookwyrm/tests/models/test_readthrough_model.py index a8d74e54..596753f7 100644 --- a/bookwyrm/tests/models/test_readthrough_model.py +++ b/bookwyrm/tests/models/test_readthrough_model.py @@ -1,4 +1,5 @@ """ testing models """ +from unittest.mock import patch from django.test import TestCase from django.core.exceptions import ValidationError @@ -10,9 +11,10 @@ class ReadThrough(TestCase): def setUp(self): """look, a shelf""" - self.user = models.User.objects.create_user( - "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.user = models.User.objects.create_user( + "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" + ) self.work = models.Work.objects.create(title="Example Work") self.edition = models.Edition.objects.create( diff --git a/bookwyrm/tests/models/test_shelf_model.py b/bookwyrm/tests/models/test_shelf_model.py index 911df059..f6fd6850 100644 --- a/bookwyrm/tests/models/test_shelf_model.py +++ b/bookwyrm/tests/models/test_shelf_model.py @@ -7,18 +7,20 @@ from bookwyrm import models, settings # pylint: disable=unused-argument +@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") class Shelf(TestCase): """some activitypub oddness ahead""" def setUp(self): """look, a shelf""" - self.local_user = models.User.objects.create_user( - "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" + ) work = models.Work.objects.create(title="Test Work") self.book = models.Edition.objects.create(title="test book", parent_work=work) - def test_remote_id(self): + def test_remote_id(self, _): """shelves use custom remote ids""" with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): shelf = models.Shelf.objects.create( @@ -27,7 +29,7 @@ class Shelf(TestCase): expected_id = "https://%s/user/mouse/books/test-shelf" % settings.DOMAIN self.assertEqual(shelf.get_remote_id(), expected_id) - def test_to_activity(self): + def test_to_activity(self, _): """jsonify it""" with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): shelf = models.Shelf.objects.create( @@ -41,7 +43,7 @@ class Shelf(TestCase): self.assertEqual(activity_json["name"], "Test Shelf") self.assertEqual(activity_json["owner"], self.local_user.remote_id) - def test_create_update_shelf(self): + def test_create_update_shelf(self, _): """create and broadcast shelf creation""" with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") as mock: @@ -62,7 +64,7 @@ class Shelf(TestCase): self.assertEqual(activity["object"]["name"], "arthur russel") self.assertEqual(shelf.name, "arthur russel") - def test_shelve(self): + def test_shelve(self, _): """create and broadcast shelf creation""" with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): shelf = models.Shelf.objects.create( diff --git a/bookwyrm/tests/models/test_status_model.py b/bookwyrm/tests/models/test_status_model.py index 355caab9..f8113549 100644 --- a/bookwyrm/tests/models/test_status_model.py +++ b/bookwyrm/tests/models/test_status_model.py @@ -22,9 +22,10 @@ class Status(TestCase): def setUp(self): """useful things for creating a status""" - self.local_user = models.User.objects.create_user( - "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" + ) with patch("bookwyrm.models.user.set_remote_server.delay"): self.remote_user = models.User.objects.create_user( "rat", diff --git a/bookwyrm/tests/models/test_user_model.py b/bookwyrm/tests/models/test_user_model.py index b2791379..0868bc4b 100644 --- a/bookwyrm/tests/models/test_user_model.py +++ b/bookwyrm/tests/models/test_user_model.py @@ -11,15 +11,16 @@ from bookwyrm.settings import DOMAIN # pylint: disable=missing-function-docstring class User(TestCase): def setUp(self): - self.user = models.User.objects.create_user( - "mouse@%s" % DOMAIN, - "mouse@mouse.mouse", - "mouseword", - local=True, - localname="mouse", - name="hi", - bookwyrm_user=False, - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.user = models.User.objects.create_user( + "mouse@%s" % DOMAIN, + "mouse@mouse.mouse", + "mouseword", + local=True, + localname="mouse", + name="hi", + bookwyrm_user=False, + ) def test_computed_fields(self): """username instead of id here""" diff --git a/bookwyrm/tests/views/test_announcements.py b/bookwyrm/tests/views/test_announcements.py index 8df6302c..bd1371de 100644 --- a/bookwyrm/tests/views/test_announcements.py +++ b/bookwyrm/tests/views/test_announcements.py @@ -12,13 +12,14 @@ class AnnouncementViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.mouse", - "password", - local=True, - localname="mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) models.SiteSettings.objects.create() diff --git a/bookwyrm/tests/views/test_authentication.py b/bookwyrm/tests/views/test_authentication.py index 22f3c20f..10531f51 100644 --- a/bookwyrm/tests/views/test_authentication.py +++ b/bookwyrm/tests/views/test_authentication.py @@ -13,25 +13,27 @@ from bookwyrm.settings import DOMAIN # pylint: disable=too-many-public-methods +@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") class AuthenticationViews(TestCase): """login and password management""" def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "password", - local=True, - localname="mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "password", + local=True, + localname="mouse", + ) self.anonymous_user = AnonymousUser self.anonymous_user.is_authenticated = False self.settings = models.SiteSettings.objects.create(id=1) - def test_login_get(self): + def test_login_get(self, _): """there are so many views, this just makes sure it LOADS""" login = views.Login.as_view() request = self.factory.get("") @@ -47,7 +49,7 @@ class AuthenticationViews(TestCase): self.assertEqual(result.url, "/") self.assertEqual(result.status_code, 302) - def test_register(self): + def test_register(self, _): """create a user""" view = views.Register.as_view() self.assertEqual(models.User.objects.count(), 1) @@ -68,7 +70,7 @@ class AuthenticationViews(TestCase): self.assertEqual(nutria.localname, "nutria-user.user_nutria") self.assertEqual(nutria.local, True) - def test_register_trailing_space(self): + def test_register_trailing_space(self, _): """django handles this so weirdly""" view = views.Register.as_view() request = self.factory.post( @@ -84,7 +86,7 @@ class AuthenticationViews(TestCase): self.assertEqual(nutria.localname, "nutria") self.assertEqual(nutria.local, True) - def test_register_invalid_email(self): + def test_register_invalid_email(self, _): """gotta have an email""" view = views.Register.as_view() self.assertEqual(models.User.objects.count(), 1) @@ -95,7 +97,7 @@ class AuthenticationViews(TestCase): self.assertEqual(models.User.objects.count(), 1) response.render() - def test_register_invalid_username(self): + def test_register_invalid_username(self, _): """gotta have an email""" view = views.Register.as_view() self.assertEqual(models.User.objects.count(), 1) @@ -123,7 +125,7 @@ class AuthenticationViews(TestCase): self.assertEqual(models.User.objects.count(), 1) response.render() - def test_register_closed_instance(self): + def test_register_closed_instance(self, _): """you can't just register""" view = views.Register.as_view() self.settings.allow_registration = False @@ -135,7 +137,7 @@ class AuthenticationViews(TestCase): with self.assertRaises(PermissionDenied): view(request) - def test_register_invite(self): + def test_register_invite(self, _): """you can't just register""" view = views.Register.as_view() self.settings.allow_registration = False diff --git a/bookwyrm/tests/views/test_author.py b/bookwyrm/tests/views/test_author.py index 0169bbe8..6157b651 100644 --- a/bookwyrm/tests/views/test_author.py +++ b/bookwyrm/tests/views/test_author.py @@ -17,14 +17,15 @@ class AuthorViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - remote_id="https://example.com/users/mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", + ) self.group = Group.objects.create(name="editor") self.group.permissions.add( Permission.objects.create( diff --git a/bookwyrm/tests/views/test_block.py b/bookwyrm/tests/views/test_block.py index c6fc9eb8..11283869 100644 --- a/bookwyrm/tests/views/test_block.py +++ b/bookwyrm/tests/views/test_block.py @@ -14,13 +14,14 @@ class BlockViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.mouse", - "password", - local=True, - localname="mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) with patch("bookwyrm.models.user.set_remote_server.delay"): self.remote_user = models.User.objects.create_user( "rat", diff --git a/bookwyrm/tests/views/test_book.py b/bookwyrm/tests/views/test_book.py index 73f2cad1..47e32229 100644 --- a/bookwyrm/tests/views/test_book.py +++ b/bookwyrm/tests/views/test_book.py @@ -23,14 +23,15 @@ class BookViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - remote_id="https://example.com/users/mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", + ) self.group = Group.objects.create(name="editor") self.group.permissions.add( Permission.objects.create( diff --git a/bookwyrm/tests/views/test_directory.py b/bookwyrm/tests/views/test_directory.py index c2026cca..bdda8101 100644 --- a/bookwyrm/tests/views/test_directory.py +++ b/bookwyrm/tests/views/test_directory.py @@ -1,4 +1,7 @@ """ test for app action functionality """ +from unittest.mock import patch + +from django.contrib.auth.models import AnonymousUser from django.template.response import TemplateResponse from django.test import TestCase from django.test.client import RequestFactory @@ -12,15 +15,25 @@ class DirectoryViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - remote_id="https://example.com/users/mouse", - ) - self.rat = models.User.objects.create_user( + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", + ) + + models.SiteSettings.objects.create() + self.anonymous_user = AnonymousUser + self.anonymous_user.is_authenticated = False + + @patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") + @patch("bookwyrm.suggested_users.rerank_user_task.delay") + def test_directory_page(self, *_): + """there are so many views, this just makes sure it LOADS""" + models.User.objects.create_user( "rat@local.com", "rat@rat.com", "ratword", @@ -29,10 +42,16 @@ class DirectoryViews(TestCase): remote_id="https://example.com/users/rat", discoverable=True, ) + view = views.Directory.as_view() + request = self.factory.get("") + request.user = self.local_user - models.SiteSettings.objects.create() + result = view(request) + self.assertIsInstance(result, TemplateResponse) + result.render() + self.assertEqual(result.status_code, 200) - def test_directory_page(self): + def test_directory_page_empty(self): """there are so many views, this just makes sure it LOADS""" view = views.Directory.as_view() request = self.factory.get("") @@ -42,3 +61,12 @@ class DirectoryViews(TestCase): self.assertIsInstance(result, TemplateResponse) result.render() self.assertEqual(result.status_code, 200) + + def test_directory_page_logged_out(self): + """there are so many views, this just makes sure it LOADS""" + view = views.Directory.as_view() + request = self.factory.get("") + request.user = self.anonymous_user + + result = view(request) + self.assertEqual(result.status_code, 302) diff --git a/bookwyrm/tests/views/test_edit_user.py b/bookwyrm/tests/views/test_edit_user.py index 7c0825b1..e9f90ad6 100644 --- a/bookwyrm/tests/views/test_edit_user.py +++ b/bookwyrm/tests/views/test_edit_user.py @@ -21,16 +21,17 @@ class EditUserViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.mouse", - "password", - local=True, - localname="mouse", - ) - self.rat = models.User.objects.create_user( - "rat@local.com", "rat@rat.rat", "password", local=True, localname="rat" - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) + self.rat = models.User.objects.create_user( + "rat@local.com", "rat@rat.rat", "password", local=True, localname="rat" + ) self.book = models.Edition.objects.create(title="test") with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): diff --git a/bookwyrm/tests/views/test_federation.py b/bookwyrm/tests/views/test_federation.py index 6db65b78..2ada24c6 100644 --- a/bookwyrm/tests/views/test_federation.py +++ b/bookwyrm/tests/views/test_federation.py @@ -15,13 +15,14 @@ class FederationViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.mouse", - "password", - local=True, - localname="mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) with patch("bookwyrm.models.user.set_remote_server.delay"): self.remote_user = models.User.objects.create_user( "rat", diff --git a/bookwyrm/tests/views/test_feed.py b/bookwyrm/tests/views/test_feed.py index a6a3d967..aad72b0c 100644 --- a/bookwyrm/tests/views/test_feed.py +++ b/bookwyrm/tests/views/test_feed.py @@ -22,13 +22,14 @@ class FeedViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.mouse", - "password", - local=True, - localname="mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) self.book = models.Edition.objects.create( parent_work=models.Work.objects.create(title="hi"), title="Example Edition", diff --git a/bookwyrm/tests/views/test_follow.py b/bookwyrm/tests/views/test_follow.py index 45e60d3c..122cf3c6 100644 --- a/bookwyrm/tests/views/test_follow.py +++ b/bookwyrm/tests/views/test_follow.py @@ -10,20 +10,21 @@ from django.test.client import RequestFactory from bookwyrm import models, views -class BookViews(TestCase): - """books books books""" +class FollowViews(TestCase): + """follows""" def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - remote_id="https://example.com/users/mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", + ) with patch("bookwyrm.models.user.set_remote_server"): self.remote_user = models.User.objects.create_user( "rat", @@ -66,15 +67,16 @@ class BookViews(TestCase): def test_handle_follow_local_manually_approves(self): """send a follow request""" - rat = models.User.objects.create_user( - "rat@local.com", - "rat@rat.com", - "ratword", - local=True, - localname="rat", - remote_id="https://example.com/users/rat", - manually_approves_followers=True, - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + rat = models.User.objects.create_user( + "rat@local.com", + "rat@rat.com", + "ratword", + local=True, + localname="rat", + remote_id="https://example.com/users/rat", + manually_approves_followers=True, + ) request = self.factory.post("", {"user": rat}) request.user = self.local_user self.assertEqual(models.UserFollowRequest.objects.count(), 0) @@ -89,14 +91,15 @@ class BookViews(TestCase): def test_handle_follow_local(self): """send a follow request""" - rat = models.User.objects.create_user( - "rat@local.com", - "rat@rat.com", - "ratword", - local=True, - localname="rat", - remote_id="https://example.com/users/rat", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + rat = models.User.objects.create_user( + "rat@local.com", + "rat@rat.com", + "ratword", + local=True, + localname="rat", + remote_id="https://example.com/users/rat", + ) request = self.factory.post("", {"user": rat}) request.user = self.local_user self.assertEqual(models.UserFollowRequest.objects.count(), 0) diff --git a/bookwyrm/tests/views/test_get_started.py b/bookwyrm/tests/views/test_get_started.py index 1c55da08..c741415c 100644 --- a/bookwyrm/tests/views/test_get_started.py +++ b/bookwyrm/tests/views/test_get_started.py @@ -13,13 +13,14 @@ class GetStartedViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.mouse", - "password", - local=True, - localname="mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) self.book = models.Edition.objects.create( parent_work=models.Work.objects.create(title="hi"), title="Example Edition", diff --git a/bookwyrm/tests/views/test_goal.py b/bookwyrm/tests/views/test_goal.py index 4e8f6ee2..ad615ba6 100644 --- a/bookwyrm/tests/views/test_goal.py +++ b/bookwyrm/tests/views/test_goal.py @@ -16,22 +16,23 @@ class GoalViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - remote_id="https://example.com/users/mouse", - ) - self.rat = models.User.objects.create_user( - "rat@local.com", - "rat@rat.com", - "ratword", - local=True, - localname="rat", - remote_id="https://example.com/users/rat", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", + ) + self.rat = models.User.objects.create_user( + "rat@local.com", + "rat@rat.com", + "ratword", + local=True, + localname="rat", + remote_id="https://example.com/users/rat", + ) self.book = models.Edition.objects.create( title="Example Edition", remote_id="https://example.com/book/1", diff --git a/bookwyrm/tests/views/test_helpers.py b/bookwyrm/tests/views/test_helpers.py index 874b913e..bfa9d82e 100644 --- a/bookwyrm/tests/views/test_helpers.py +++ b/bookwyrm/tests/views/test_helpers.py @@ -13,21 +13,23 @@ from bookwyrm.settings import USER_AGENT @patch("bookwyrm.activitystreams.ActivityStream.add_status") +@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") class ViewsHelpers(TestCase): """viewing and creating statuses""" def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "mouseword", - local=True, - discoverable=True, - localname="mouse", - remote_id="https://example.com/users/mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + discoverable=True, + localname="mouse", + remote_id="https://example.com/users/mouse", + ) with patch("bookwyrm.models.user.set_remote_server.delay"): self.remote_user = models.User.objects.create_user( "rat", @@ -53,12 +55,12 @@ class ViewsHelpers(TestCase): name="Test Shelf", identifier="test-shelf", user=self.local_user ) - def test_get_edition(self, _): + def test_get_edition(self, *_): """given an edition or a work, returns an edition""" self.assertEqual(views.helpers.get_edition(self.book.id), self.book) self.assertEqual(views.helpers.get_edition(self.work.id), self.book) - def test_get_user_from_username(self, _): + def test_get_user_from_username(self, *_): """works for either localname or username""" self.assertEqual( views.helpers.get_user_from_username(self.local_user, "mouse"), @@ -71,7 +73,7 @@ class ViewsHelpers(TestCase): with self.assertRaises(Http404): views.helpers.get_user_from_username(self.local_user, "mojfse@example.com") - def test_is_api_request(self, _): + def test_is_api_request(self, *_): """should it return html or json""" request = self.factory.get("/path") request.headers = {"Accept": "application/json"} @@ -85,12 +87,12 @@ class ViewsHelpers(TestCase): request.headers = {"Accept": "Praise"} self.assertFalse(views.helpers.is_api_request(request)) - def test_is_api_request_no_headers(self, _): + def test_is_api_request_no_headers(self, *_): """should it return html or json""" request = self.factory.get("/path") self.assertFalse(views.helpers.is_api_request(request)) - def test_is_bookwyrm_request(self, _): + def test_is_bookwyrm_request(self, *_): """checks if a request came from a bookwyrm instance""" request = self.factory.get("", {"q": "Test Book"}) self.assertFalse(views.helpers.is_bookwyrm_request(request)) @@ -105,7 +107,7 @@ class ViewsHelpers(TestCase): request = self.factory.get("", {"q": "Test Book"}, HTTP_USER_AGENT=USER_AGENT) self.assertTrue(views.helpers.is_bookwyrm_request(request)) - def test_existing_user(self, _): + def test_existing_user(self, *_): """simple database lookup by username""" result = views.helpers.handle_remote_webfinger("@mouse@local.com") self.assertEqual(result, self.local_user) @@ -117,7 +119,7 @@ class ViewsHelpers(TestCase): self.assertEqual(result, self.local_user) @responses.activate - def test_load_user(self, _): + def test_load_user(self, *_): """find a remote user using webfinger""" username = "mouse@example.com" wellknown = { @@ -147,7 +149,7 @@ class ViewsHelpers(TestCase): self.assertIsInstance(result, models.User) self.assertEqual(result.username, "mouse@example.com") - def test_user_on_blocked_server(self, _): + def test_user_on_blocked_server(self, *_): """find a remote user using webfinger""" models.FederatedServer.objects.create( server_name="example.com", status="blocked" @@ -156,7 +158,7 @@ class ViewsHelpers(TestCase): result = views.helpers.handle_remote_webfinger("@mouse@example.com") self.assertIsNone(result) - def test_handle_reading_status_to_read(self, _): + def test_handle_reading_status_to_read(self, *_): """posts shelve activities""" shelf = self.local_user.shelf_set.get(identifier="to-read") with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): @@ -168,7 +170,7 @@ class ViewsHelpers(TestCase): self.assertEqual(status.mention_books.first(), self.book) self.assertEqual(status.content, "wants to read") - def test_handle_reading_status_reading(self, _): + def test_handle_reading_status_reading(self, *_): """posts shelve activities""" shelf = self.local_user.shelf_set.get(identifier="reading") with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): @@ -180,7 +182,7 @@ class ViewsHelpers(TestCase): self.assertEqual(status.mention_books.first(), self.book) self.assertEqual(status.content, "started reading") - def test_handle_reading_status_read(self, _): + def test_handle_reading_status_read(self, *_): """posts shelve activities""" shelf = self.local_user.shelf_set.get(identifier="read") with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): @@ -192,7 +194,7 @@ class ViewsHelpers(TestCase): self.assertEqual(status.mention_books.first(), self.book) self.assertEqual(status.content, "finished reading") - def test_handle_reading_status_other(self, _): + def test_handle_reading_status_other(self, *_): """posts shelve activities""" with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): views.helpers.handle_reading_status( @@ -200,7 +202,7 @@ class ViewsHelpers(TestCase): ) self.assertFalse(models.GeneratedNote.objects.exists()) - def test_get_annotated_users(self, _): + def test_get_annotated_users(self, *_): """list of people you might know""" user_1 = models.User.objects.create_user( "nutria@local.com", @@ -247,7 +249,7 @@ class ViewsHelpers(TestCase): self.assertEqual(remote_user_annotated.mutuals, 0) self.assertEqual(remote_user_annotated.shared_books, 0) - def test_get_annotated_users_counts(self, _): + def test_get_annotated_users_counts(self, *_): """correct counting for multiple shared attributed""" user_1 = models.User.objects.create_user( "nutria@local.com", diff --git a/bookwyrm/tests/views/test_import.py b/bookwyrm/tests/views/test_import.py index 22694623..13c0ef5d 100644 --- a/bookwyrm/tests/views/test_import.py +++ b/bookwyrm/tests/views/test_import.py @@ -14,13 +14,14 @@ class ImportViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.mouse", - "password", - local=True, - localname="mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) models.SiteSettings.objects.create() def test_import_page(self): diff --git a/bookwyrm/tests/views/test_interaction.py b/bookwyrm/tests/views/test_interaction.py index 3867f57d..d83fc119 100644 --- a/bookwyrm/tests/views/test_interaction.py +++ b/bookwyrm/tests/views/test_interaction.py @@ -15,14 +15,15 @@ class InteractionViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - remote_id="https://example.com/users/mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", + ) with patch("bookwyrm.models.user.set_remote_server"): self.remote_user = models.User.objects.create_user( "rat", diff --git a/bookwyrm/tests/views/test_invite.py b/bookwyrm/tests/views/test_invite.py index 7b5071b3..1eaf57c0 100644 --- a/bookwyrm/tests/views/test_invite.py +++ b/bookwyrm/tests/views/test_invite.py @@ -16,13 +16,14 @@ class InviteViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.mouse", - "password", - local=True, - localname="mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) models.SiteSettings.objects.create() def test_invite_page(self): diff --git a/bookwyrm/tests/views/test_isbn.py b/bookwyrm/tests/views/test_isbn.py index 2aedd3ce..7c413e8b 100644 --- a/bookwyrm/tests/views/test_isbn.py +++ b/bookwyrm/tests/views/test_isbn.py @@ -16,14 +16,15 @@ class IsbnViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - remote_id="https://example.com/users/mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", + ) self.work = models.Work.objects.create(title="Test Work") self.book = models.Edition.objects.create( title="Test Book", diff --git a/bookwyrm/tests/views/test_landing.py b/bookwyrm/tests/views/test_landing.py index 864e48f7..4690ed1b 100644 --- a/bookwyrm/tests/views/test_landing.py +++ b/bookwyrm/tests/views/test_landing.py @@ -15,13 +15,14 @@ class LandingViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.mouse", - "password", - local=True, - localname="mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) self.anonymous_user = AnonymousUser self.anonymous_user.is_authenticated = False models.SiteSettings.objects.create() diff --git a/bookwyrm/tests/views/test_list.py b/bookwyrm/tests/views/test_list.py index 399892e3..988d8d4a 100644 --- a/bookwyrm/tests/views/test_list.py +++ b/bookwyrm/tests/views/test_list.py @@ -17,22 +17,23 @@ class ListViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - remote_id="https://example.com/users/mouse", - ) - self.rat = models.User.objects.create_user( - "rat@local.com", - "rat@rat.com", - "ratword", - local=True, - localname="rat", - remote_id="https://example.com/users/rat", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", + ) + self.rat = models.User.objects.create_user( + "rat@local.com", + "rat@rat.com", + "ratword", + local=True, + localname="rat", + remote_id="https://example.com/users/rat", + ) work = models.Work.objects.create(title="Work") self.book = models.Edition.objects.create( title="Example Edition", diff --git a/bookwyrm/tests/views/test_list_actions.py b/bookwyrm/tests/views/test_list_actions.py index a5986782..2339427c 100644 --- a/bookwyrm/tests/views/test_list_actions.py +++ b/bookwyrm/tests/views/test_list_actions.py @@ -15,22 +15,23 @@ class ListActionViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - remote_id="https://example.com/users/mouse", - ) - self.rat = models.User.objects.create_user( - "rat@local.com", - "rat@rat.com", - "ratword", - local=True, - localname="rat", - remote_id="https://example.com/users/rat", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", + ) + self.rat = models.User.objects.create_user( + "rat@local.com", + "rat@rat.com", + "ratword", + local=True, + localname="rat", + remote_id="https://example.com/users/rat", + ) work = models.Work.objects.create(title="Work") self.book = models.Edition.objects.create( diff --git a/bookwyrm/tests/views/test_notifications.py b/bookwyrm/tests/views/test_notifications.py index 182753f9..af6aac13 100644 --- a/bookwyrm/tests/views/test_notifications.py +++ b/bookwyrm/tests/views/test_notifications.py @@ -1,4 +1,5 @@ """ test for app action functionality """ +from unittest.mock import patch from django.template.response import TemplateResponse from django.test import TestCase from django.test.client import RequestFactory @@ -13,13 +14,14 @@ class NotificationViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.mouse", - "password", - local=True, - localname="mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) models.SiteSettings.objects.create() def test_notifications_page(self): diff --git a/bookwyrm/tests/views/test_outbox.py b/bookwyrm/tests/views/test_outbox.py index f89258e5..fe493bb8 100644 --- a/bookwyrm/tests/views/test_outbox.py +++ b/bookwyrm/tests/views/test_outbox.py @@ -18,14 +18,15 @@ class OutboxView(TestCase): def setUp(self): """we'll need some data""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - remote_id="https://example.com/users/mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", + ) work = models.Work.objects.create(title="Test Work") self.book = models.Edition.objects.create( title="Example Edition", diff --git a/bookwyrm/tests/views/test_password.py b/bookwyrm/tests/views/test_password.py index ec686db7..5f59e9e3 100644 --- a/bookwyrm/tests/views/test_password.py +++ b/bookwyrm/tests/views/test_password.py @@ -15,13 +15,14 @@ class PasswordViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "password", - local=True, - localname="mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "password", + local=True, + localname="mouse", + ) self.anonymous_user = AnonymousUser self.anonymous_user.is_authenticated = False models.SiteSettings.objects.create(id=1) diff --git a/bookwyrm/tests/views/test_reading.py b/bookwyrm/tests/views/test_reading.py index 00bd62c7..ab1fc70a 100644 --- a/bookwyrm/tests/views/test_reading.py +++ b/bookwyrm/tests/views/test_reading.py @@ -15,14 +15,15 @@ class ReadingViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - remote_id="https://example.com/users/mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", + ) with patch("bookwyrm.models.user.set_remote_server.delay"): self.remote_user = models.User.objects.create_user( "rat", diff --git a/bookwyrm/tests/views/test_readthrough.py b/bookwyrm/tests/views/test_readthrough.py index 764490d8..ae87ebdc 100644 --- a/bookwyrm/tests/views/test_readthrough.py +++ b/bookwyrm/tests/views/test_readthrough.py @@ -21,9 +21,10 @@ class ReadThrough(TestCase): title="Example Edition", parent_work=self.work ) - self.user = models.User.objects.create_user( - "cinco", "cinco@example.com", "seissiete", local=True, localname="cinco" - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.user = models.User.objects.create_user( + "cinco", "cinco@example.com", "seissiete", local=True, localname="cinco" + ) with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): self.client.force_login(self.user) diff --git a/bookwyrm/tests/views/test_reports.py b/bookwyrm/tests/views/test_reports.py index 84539489..bb32bcb1 100644 --- a/bookwyrm/tests/views/test_reports.py +++ b/bookwyrm/tests/views/test_reports.py @@ -1,4 +1,5 @@ """ test for app action functionality """ +from unittest.mock import patch from django.template.response import TemplateResponse from django.test import TestCase from django.test.client import RequestFactory @@ -12,20 +13,21 @@ class ReportViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.mouse", - "password", - local=True, - localname="mouse", - ) - self.rat = models.User.objects.create_user( - "rat@local.com", - "rat@mouse.mouse", - "password", - local=True, - localname="rat", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) + self.rat = models.User.objects.create_user( + "rat@local.com", + "rat@mouse.mouse", + "password", + local=True, + localname="rat", + ) models.SiteSettings.objects.create() def test_reports_page(self): diff --git a/bookwyrm/tests/views/test_rss_feed.py b/bookwyrm/tests/views/test_rss_feed.py index eacb3c93..3608b043 100644 --- a/bookwyrm/tests/views/test_rss_feed.py +++ b/bookwyrm/tests/views/test_rss_feed.py @@ -14,9 +14,10 @@ class RssFeedView(TestCase): """test data""" self.site = models.SiteSettings.objects.create() - self.user = models.User.objects.create_user( - "rss_user", "rss@test.rss", "password", local=True - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.user = models.User.objects.create_user( + "rss_user", "rss@test.rss", "password", local=True + ) work = models.Work.objects.create(title="Test Work") self.book = models.Edition.objects.create( diff --git a/bookwyrm/tests/views/test_search.py b/bookwyrm/tests/views/test_search.py index ab2c910d..3da6f866 100644 --- a/bookwyrm/tests/views/test_search.py +++ b/bookwyrm/tests/views/test_search.py @@ -19,14 +19,15 @@ class Views(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - remote_id="https://example.com/users/mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", + ) self.work = models.Work.objects.create(title="Test Work") self.book = models.Edition.objects.create( title="Test Book", diff --git a/bookwyrm/tests/views/test_shelf.py b/bookwyrm/tests/views/test_shelf.py index 239b3318..ccba4c73 100644 --- a/bookwyrm/tests/views/test_shelf.py +++ b/bookwyrm/tests/views/test_shelf.py @@ -16,14 +16,15 @@ class ShelfViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - remote_id="https://example.com/users/mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", + ) self.work = models.Work.objects.create(title="Test Work") self.book = models.Edition.objects.create( title="Example Edition", diff --git a/bookwyrm/tests/views/test_status.py b/bookwyrm/tests/views/test_status.py index a20e98b9..2049963c 100644 --- a/bookwyrm/tests/views/test_status.py +++ b/bookwyrm/tests/views/test_status.py @@ -10,20 +10,22 @@ from bookwyrm.settings import DOMAIN # pylint: disable=invalid-name @patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") +@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") class StatusViews(TestCase): """viewing and creating statuses""" def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - remote_id="https://example.com/users/mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + remote_id="https://example.com/users/mouse", + ) with patch("bookwyrm.models.user.set_remote_server"): self.remote_user = models.User.objects.create_user( "rat", @@ -43,7 +45,7 @@ class StatusViews(TestCase): ) models.SiteSettings.objects.create() - def test_handle_status(self, _): + def test_handle_status(self, *_): """create a status""" view = views.CreateStatus.as_view() form = forms.CommentForm( @@ -66,7 +68,7 @@ class StatusViews(TestCase): self.assertEqual(status.user, self.local_user) self.assertEqual(status.book, self.book) - def test_handle_status_reply(self, _): + def test_handle_status_reply(self, *_): """create a status in reply to an existing status""" view = views.CreateStatus.as_view() user = models.User.objects.create_user( @@ -96,7 +98,7 @@ class StatusViews(TestCase): self.assertEqual(status.user, user) self.assertEqual(models.Notification.objects.get().user, self.local_user) - def test_handle_status_mentions(self, _): + def test_handle_status_mentions(self, *_): """@mention a user in a post""" view = views.CreateStatus.as_view() user = models.User.objects.create_user( @@ -128,7 +130,7 @@ class StatusViews(TestCase): status.content, '

hi @rat

' % user.remote_id ) - def test_handle_status_reply_with_mentions(self, _): + def test_handle_status_reply_with_mentions(self, *_): """reply to a post with an @mention'ed user""" view = views.CreateStatus.as_view() user = models.User.objects.create_user( @@ -172,7 +174,7 @@ class StatusViews(TestCase): self.assertFalse(self.remote_user in reply.mention_users.all()) self.assertTrue(self.local_user in reply.mention_users.all()) - def test_delete_and_redraft(self, _): + def test_delete_and_redraft(self, *_): """delete and re-draft a status""" view = views.DeleteAndRedraft.as_view() request = self.factory.post("") @@ -193,7 +195,7 @@ class StatusViews(TestCase): status.refresh_from_db() self.assertTrue(status.deleted) - def test_delete_and_redraft_invalid_status_type_rating(self, _): + def test_delete_and_redraft_invalid_status_type_rating(self, *_): """you can't redraft generated statuses""" view = views.DeleteAndRedraft.as_view() request = self.factory.post("") @@ -213,7 +215,7 @@ class StatusViews(TestCase): status.refresh_from_db() self.assertFalse(status.deleted) - def test_delete_and_redraft_invalid_status_type_generated_note(self, _): + def test_delete_and_redraft_invalid_status_type_generated_note(self, *_): """you can't redraft generated statuses""" view = views.DeleteAndRedraft.as_view() request = self.factory.post("") @@ -233,7 +235,7 @@ class StatusViews(TestCase): status.refresh_from_db() self.assertFalse(status.deleted) - def test_find_mentions(self, _): + def test_find_mentions(self, *_): """detect and look up @ mentions of users""" user = models.User.objects.create_user( "nutria@%s" % DOMAIN, @@ -279,7 +281,7 @@ class StatusViews(TestCase): ("@nutria@%s" % DOMAIN, user), ) - def test_format_links(self, _): + def test_format_links(self, *_): """find and format urls into a tags""" url = "http://www.fish.com/" self.assertEqual( @@ -302,7 +304,7 @@ class StatusViews(TestCase): "?q=arkady+strugatsky&mode=everything" % url, ) - def test_to_markdown(self, _): + def test_to_markdown(self, *_): """this is mostly handled in other places, but nonetheless""" text = "_hi_ and http://fish.com is rad" result = views.status.to_markdown(text) @@ -311,7 +313,7 @@ class StatusViews(TestCase): '

hi and fish.com ' "is rad

", ) - def test_to_markdown_detect_url(self, _): + def test_to_markdown_detect_url(self, *_): """this is mostly handled in other places, but nonetheless""" text = "http://fish.com/@hello#okay" result = views.status.to_markdown(text) @@ -320,7 +322,7 @@ class StatusViews(TestCase): '

fish.com/@hello#okay

', ) - def test_to_markdown_link(self, _): + def test_to_markdown_link(self, *_): """this is mostly handled in other places, but nonetheless""" text = "[hi](http://fish.com) is rad" result = views.status.to_markdown(text) @@ -346,7 +348,7 @@ class StatusViews(TestCase): status.refresh_from_db() self.assertTrue(status.deleted) - def test_handle_delete_status_permission_denied(self, _): + def test_handle_delete_status_permission_denied(self, *_): """marks a status as deleted""" view = views.DeleteStatus.as_view() with patch("bookwyrm.activitystreams.ActivityStream.add_status"): @@ -360,7 +362,7 @@ class StatusViews(TestCase): status.refresh_from_db() self.assertFalse(status.deleted) - def test_handle_delete_status_moderator(self, mock): + def test_handle_delete_status_moderator(self, mock, _): """marks a status as deleted""" view = views.DeleteStatus.as_view() with patch("bookwyrm.activitystreams.ActivityStream.add_status"): diff --git a/bookwyrm/tests/views/test_updates.py b/bookwyrm/tests/views/test_updates.py index fb003f8d..627b756e 100644 --- a/bookwyrm/tests/views/test_updates.py +++ b/bookwyrm/tests/views/test_updates.py @@ -15,13 +15,14 @@ class UpdateViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.mouse", - "password", - local=True, - localname="mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) models.SiteSettings.objects.create() def test_get_notification_count(self): diff --git a/bookwyrm/tests/views/test_user.py b/bookwyrm/tests/views/test_user.py index 478d2e8f..5e3680d2 100644 --- a/bookwyrm/tests/views/test_user.py +++ b/bookwyrm/tests/views/test_user.py @@ -17,16 +17,17 @@ class UserViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.mouse", - "password", - local=True, - localname="mouse", - ) - self.rat = models.User.objects.create_user( - "rat@local.com", "rat@rat.rat", "password", local=True, localname="rat" - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) + self.rat = models.User.objects.create_user( + "rat@local.com", "rat@rat.rat", "password", local=True, localname="rat" + ) self.book = models.Edition.objects.create(title="test") with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): models.ShelfBook.objects.create( diff --git a/bookwyrm/tests/views/test_user_admin.py b/bookwyrm/tests/views/test_user_admin.py index a044a22c..42f2d7e8 100644 --- a/bookwyrm/tests/views/test_user_admin.py +++ b/bookwyrm/tests/views/test_user_admin.py @@ -14,13 +14,14 @@ class UserAdminViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.mouse", - "password", - local=True, - localname="mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) models.SiteSettings.objects.create() def test_user_admin_list_page(self): diff --git a/bookwyrm/tests/views/test_wellknown.py b/bookwyrm/tests/views/test_wellknown.py index 244921d4..4a3eb579 100644 --- a/bookwyrm/tests/views/test_wellknown.py +++ b/bookwyrm/tests/views/test_wellknown.py @@ -16,16 +16,17 @@ class UserViews(TestCase): def setUp(self): """we need basic test data and mocks""" self.factory = RequestFactory() - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.mouse", - "password", - local=True, - localname="mouse", - ) - models.User.objects.create_user( - "rat@local.com", "rat@rat.rat", "password", local=True, localname="rat" - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + ) + models.User.objects.create_user( + "rat@local.com", "rat@rat.rat", "password", local=True, localname="rat" + ) with patch("bookwyrm.models.user.set_remote_server.delay"): models.User.objects.create_user( "rat", From 19acfeeb726d0b8d0c04dc601d4749b1134161d7 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 3 Aug 2021 10:44:48 -0700 Subject: [PATCH 30/47] Removes delete redis action --- bookwyrm/redis_store.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/bookwyrm/redis_store.py b/bookwyrm/redis_store.py index 578cb550..fa5c73a5 100644 --- a/bookwyrm/redis_store.py +++ b/bookwyrm/redis_store.py @@ -65,10 +65,6 @@ class RedisStore(ABC): pipeline = r.pipeline() queryset = self.get_objects_for_store(store) - # first, remove everything currently in it - pipeline.delete(store) - - # now, add everything back for obj in queryset[: self.max_length]: pipeline.zadd(store, self.get_value(obj)) From 2b6423792c646c89e68eab1004bd45e94f4d6f95 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 3 Aug 2021 11:47:52 -0700 Subject: [PATCH 31/47] Use update_fields when saving preview images --- bookwyrm/preview_images.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bookwyrm/preview_images.py b/bookwyrm/preview_images.py index 29c4961c..4f85bb56 100644 --- a/bookwyrm/preview_images.py +++ b/bookwyrm/preview_images.py @@ -338,9 +338,9 @@ def save_and_cleanup(image, instance=None): save_without_broadcast = isinstance(instance, (models.Book, models.User)) if save_without_broadcast: - instance.save(broadcast=False) + instance.save(broadcast=False, update_fields=["preview_image"]) else: - instance.save() + instance.save(update_fields=["preview_image"]) # Clean up old file after saving if old_path and default_storage.exists(old_path): From fc93e08767b5a720a4d0c11b2146b1d391701f70 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 3 Aug 2021 11:56:33 -0700 Subject: [PATCH 32/47] Fixes missing mocks in suggested user test --- bookwyrm/tests/test_suggested_users.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bookwyrm/tests/test_suggested_users.py b/bookwyrm/tests/test_suggested_users.py index af296624..26b9beee 100644 --- a/bookwyrm/tests/test_suggested_users.py +++ b/bookwyrm/tests/test_suggested_users.py @@ -41,6 +41,8 @@ class SuggestedUsers(TestCase): self.assertEqual(counts["mutuals"], 3) self.assertEqual(counts["shared_books"], 27) + @patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") + @patch("bookwyrm.suggested_users.rerank_user_task.delay") def test_get_objects_for_store(self, *_): """list of people to follow for a given user""" From 1aff6322ea22fa20174bb89601676b12c69d89b1 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 3 Aug 2021 11:59:49 -0700 Subject: [PATCH 33/47] Makes test user undiscoverable --- bookwyrm/tests/data/ap_user.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/tests/data/ap_user.json b/bookwyrm/tests/data/ap_user.json index bc4488e3..63c8a7e8 100644 --- a/bookwyrm/tests/data/ap_user.json +++ b/bookwyrm/tests/data/ap_user.json @@ -28,7 +28,7 @@ }, "bookwyrmUser": true, "manuallyApprovesFollowers": false, - "discoverable": true, + "discoverable": false, "devices": "https://friend.camp/users/tripofmice/collections/devices", "tag": [], "icon": { From b059cbdd186e08ef3f5ded93fde739b7c1150521 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 3 Aug 2021 12:02:47 -0700 Subject: [PATCH 34/47] Adds missing import and mock --- bookwyrm/tests/activitypub/test_base_activity.py | 1 + bookwyrm/tests/views/test_announcements.py | 1 + 2 files changed, 2 insertions(+) diff --git a/bookwyrm/tests/activitypub/test_base_activity.py b/bookwyrm/tests/activitypub/test_base_activity.py index ba9abad9..b3f16add 100644 --- a/bookwyrm/tests/activitypub/test_base_activity.py +++ b/bookwyrm/tests/activitypub/test_base_activity.py @@ -21,6 +21,7 @@ from bookwyrm import models @patch("bookwyrm.activitystreams.ActivityStream.add_status") @patch("bookwyrm.suggested_users.rerank_user_task.delay") +@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") class BaseActivity(TestCase): """the super class for model-linked activitypub dataclasses""" diff --git a/bookwyrm/tests/views/test_announcements.py b/bookwyrm/tests/views/test_announcements.py index bd1371de..16ef81e9 100644 --- a/bookwyrm/tests/views/test_announcements.py +++ b/bookwyrm/tests/views/test_announcements.py @@ -1,4 +1,5 @@ """ test for app action functionality """ +from unittest.mock import patch from django.template.response import TemplateResponse from django.test import TestCase from django.test.client import RequestFactory From ebc06802884357fc7cb13a1090584b0990fcde07 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 3 Aug 2021 12:21:11 -0700 Subject: [PATCH 35/47] Fixes logic error in detecting changed fields when serializing --- bookwyrm/models/fields.py | 2 +- bookwyrm/tests/activitypub/test_base_activity.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/bookwyrm/models/fields.py b/bookwyrm/models/fields.py index 02efe675..b58f8174 100644 --- a/bookwyrm/models/fields.py +++ b/bookwyrm/models/fields.py @@ -80,7 +80,7 @@ class ActivitypubFieldMixin: return False # the field is unchanged - if getattr(instance, self.name) == formatted: + if hasattr(instance, self.name) and getattr(instance, self.name) == formatted: return False setattr(instance, self.name, formatted) diff --git a/bookwyrm/tests/activitypub/test_base_activity.py b/bookwyrm/tests/activitypub/test_base_activity.py index b3f16add..0758fe99 100644 --- a/bookwyrm/tests/activitypub/test_base_activity.py +++ b/bookwyrm/tests/activitypub/test_base_activity.py @@ -21,6 +21,7 @@ from bookwyrm import models @patch("bookwyrm.activitystreams.ActivityStream.add_status") @patch("bookwyrm.suggested_users.rerank_user_task.delay") +@patch("bookwyrm.suggested_users.remove_user_task.delay") @patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") class BaseActivity(TestCase): """the super class for model-linked activitypub dataclasses""" From ee9c5a2a4b3cf2f11692ecab8146c6102c5a537e Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 3 Aug 2021 12:27:42 -0700 Subject: [PATCH 36/47] Adds mock to fields --- bookwyrm/tests/models/test_fields.py | 1 + bookwyrm/tests/views/test_helpers.py | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/bookwyrm/tests/models/test_fields.py b/bookwyrm/tests/models/test_fields.py index c234ffd0..8489b49f 100644 --- a/bookwyrm/tests/models/test_fields.py +++ b/bookwyrm/tests/models/test_fields.py @@ -186,6 +186,7 @@ class ActivitypubFields(TestCase): @patch("bookwyrm.models.activitypub_mixin.ObjectMixin.broadcast") @patch("bookwyrm.activitystreams.ActivityStream.add_status") + @patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") def test_privacy_field_set_activity_from_field(self, *_): """translate between to/cc fields and privacy""" user = User.objects.create_user( diff --git a/bookwyrm/tests/views/test_helpers.py b/bookwyrm/tests/views/test_helpers.py index bfa9d82e..b30ac8ca 100644 --- a/bookwyrm/tests/views/test_helpers.py +++ b/bookwyrm/tests/views/test_helpers.py @@ -21,15 +21,16 @@ class ViewsHelpers(TestCase): """we need basic test data and mocks""" self.factory = RequestFactory() with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): - self.local_user = models.User.objects.create_user( - "mouse@local.com", - "mouse@mouse.com", - "mouseword", - local=True, - discoverable=True, - localname="mouse", - remote_id="https://example.com/users/mouse", - ) + with patch("bookwyrm.suggested_users.rerank_user_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.com", + "mouseword", + local=True, + discoverable=True, + localname="mouse", + remote_id="https://example.com/users/mouse", + ) with patch("bookwyrm.models.user.set_remote_server.delay"): self.remote_user = models.User.objects.create_user( "rat", From 1f2fea4a8eb64a44e3580b755a3a0062da0b31eb Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 3 Aug 2021 12:33:48 -0700 Subject: [PATCH 37/47] Use update_fields for password_change --- bookwyrm/views/password.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bookwyrm/views/password.py b/bookwyrm/views/password.py index 91a379f3..18fcb02c 100644 --- a/bookwyrm/views/password.py +++ b/bookwyrm/views/password.py @@ -76,7 +76,7 @@ class PasswordReset(View): return TemplateResponse(request, "password_reset.html", data) user.set_password(new_password) - user.save(broadcast=False) + user.save(broadcast=False, update_fields=["password"]) login(request, user) reset_code.delete() return redirect("/") @@ -100,6 +100,6 @@ class ChangePassword(View): return redirect("preferences/password") request.user.set_password(new_password) - request.user.save(broadcast=False) + request.user.save(broadcast=False, update_fields=["password"]) login(request, request.user) return redirect(request.user.local_path) From 777d177c671b3607560dbae9eea011c79fbd9130 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 3 Aug 2021 12:41:41 -0700 Subject: [PATCH 38/47] Adds more mocks --- bookwyrm/tests/views/test_helpers.py | 21 +++++++++++---------- bookwyrm/tests/views/test_reading.py | 13 +++++++------ 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/bookwyrm/tests/views/test_helpers.py b/bookwyrm/tests/views/test_helpers.py index b30ac8ca..11d0ad7b 100644 --- a/bookwyrm/tests/views/test_helpers.py +++ b/bookwyrm/tests/views/test_helpers.py @@ -32,16 +32,17 @@ class ViewsHelpers(TestCase): remote_id="https://example.com/users/mouse", ) 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", - discoverable=True, - inbox="https://example.com/users/rat/inbox", - outbox="https://example.com/users/rat/outbox", - ) + with patch("bookwyrm.suggested_users.rerank_user_task.delay"): + self.remote_user = models.User.objects.create_user( + "rat", + "rat@rat.com", + "ratword", + local=False, + remote_id="https://example.com/users/rat", + discoverable=True, + inbox="https://example.com/users/rat/inbox", + outbox="https://example.com/users/rat/outbox", + ) self.work = models.Work.objects.create(title="Test Work") self.book = models.Edition.objects.create( title="Test Book", diff --git a/bookwyrm/tests/views/test_reading.py b/bookwyrm/tests/views/test_reading.py index ab1fc70a..bebd9f5a 100644 --- a/bookwyrm/tests/views/test_reading.py +++ b/bookwyrm/tests/views/test_reading.py @@ -9,6 +9,7 @@ from bookwyrm import models, views @patch("bookwyrm.activitystreams.ActivityStream.add_status") +@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") class ReadingViews(TestCase): """viewing and creating statuses""" @@ -41,7 +42,7 @@ class ReadingViews(TestCase): parent_work=self.work, ) - def test_start_reading(self, _): + def test_start_reading(self, *_): """begin a book""" shelf = self.local_user.shelf_set.get(identifier=models.Shelf.READING) self.assertFalse(shelf.books.exists()) @@ -72,7 +73,7 @@ class ReadingViews(TestCase): self.assertEqual(readthrough.user, self.local_user) self.assertEqual(readthrough.book, self.book) - def test_start_reading_reshelf(self, _): + def test_start_reading_reshelf(self, *_): """begin a book""" to_read_shelf = self.local_user.shelf_set.get(identifier=models.Shelf.TO_READ) with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): @@ -92,7 +93,7 @@ class ReadingViews(TestCase): self.assertFalse(to_read_shelf.books.exists()) self.assertEqual(shelf.books.get(), self.book) - def test_finish_reading(self, _): + def test_finish_reading(self, *_): """begin a book""" shelf = self.local_user.shelf_set.get(identifier=models.Shelf.READ_FINISHED) self.assertFalse(shelf.books.exists()) @@ -128,7 +129,7 @@ class ReadingViews(TestCase): self.assertEqual(readthrough.user, self.local_user) self.assertEqual(readthrough.book, self.book) - def test_edit_readthrough(self, _): + def test_edit_readthrough(self, *_): """adding dates to an ongoing readthrough""" start = timezone.make_aware(dateutil.parser.parse("2021-01-03")) readthrough = models.ReadThrough.objects.create( @@ -155,7 +156,7 @@ class ReadingViews(TestCase): self.assertEqual(readthrough.finish_date.day, 7) self.assertEqual(readthrough.book, self.book) - def test_delete_readthrough(self, _): + def test_delete_readthrough(self, *_): """remove a readthrough""" readthrough = models.ReadThrough.objects.create( book=self.book, user=self.local_user @@ -172,7 +173,7 @@ class ReadingViews(TestCase): views.delete_readthrough(request) self.assertFalse(models.ReadThrough.objects.filter(id=readthrough.id).exists()) - def test_create_readthrough(self, _): + def test_create_readthrough(self, *_): """adding new read dates""" request = self.factory.post( "", From c23f341980fca4c69343e5a00bb8e802369170bc Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 3 Aug 2021 12:48:44 -0700 Subject: [PATCH 39/47] Move annotated user function tests --- bookwyrm/tests/test_suggested_users.py | 94 +++++++++++++++++++++++++- bookwyrm/tests/views/test_edit_user.py | 3 +- bookwyrm/tests/views/test_helpers.py | 93 +------------------------ 3 files changed, 96 insertions(+), 94 deletions(-) diff --git a/bookwyrm/tests/test_suggested_users.py b/bookwyrm/tests/test_suggested_users.py index 26b9beee..d950858d 100644 --- a/bookwyrm/tests/test_suggested_users.py +++ b/bookwyrm/tests/test_suggested_users.py @@ -5,7 +5,7 @@ from unittest.mock import patch from django.test import TestCase from bookwyrm import models -from bookwyrm.suggested_users import suggested_users +from bookwyrm.suggested_users import suggested_users, get_annotated_users @patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") @@ -79,3 +79,95 @@ class SuggestedUsers(TestCase): ) self.assertEqual(mock.call_count, 1) + + def test_get_annotated_users(self, *_): + """list of people you might know""" + user_1 = models.User.objects.create_user( + "nutria@local.com", + "nutria@nutria.com", + "nutriaword", + local=True, + localname="nutria", + discoverable=True, + ) + user_2 = models.User.objects.create_user( + "fish@local.com", + "fish@fish.com", + "fishword", + local=True, + localname="fish", + ) + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + # 1 shared follow + self.local_user.following.add(user_2) + user_1.followers.add(user_2) + + # 1 shared book + models.ShelfBook.objects.create( + user=self.local_user, + book=self.book, + shelf=self.local_user.shelf_set.first(), + ) + models.ShelfBook.objects.create( + user=user_1, book=self.book, shelf=user_1.shelf_set.first() + ) + + result = views.helpers.get_annotated_users(self.local_user) + self.assertEqual(result.count(), 3) + self.assertTrue(user_1 in result) + self.assertFalse(user_2 in result) + self.assertTrue(self.local_user in result) + self.assertTrue(self.remote_user in result) + + user_1_annotated = result.get(id=user_1.id) + self.assertEqual(user_1_annotated.mutuals, 1) + self.assertEqual(user_1_annotated.shared_books, 1) + + remote_user_annotated = result.get(id=self.remote_user.id) + self.assertEqual(remote_user_annotated.mutuals, 0) + self.assertEqual(remote_user_annotated.shared_books, 0) + + def test_get_annotated_users_counts(self, *_): + """correct counting for multiple shared attributed""" + user_1 = models.User.objects.create_user( + "nutria@local.com", + "nutria@nutria.com", + "nutriaword", + local=True, + localname="nutria", + discoverable=True, + ) + for i in range(3): + user = models.User.objects.create_user( + "{:d}@local.com".format(i), + "{:d}@nutria.com".format(i), + "password", + local=True, + localname=i, + ) + user.following.add(user_1) + user.followers.add(self.local_user) + + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + for i in range(3): + book = models.Edition.objects.create( + title=i, + parent_work=models.Work.objects.create(title=i), + ) + models.ShelfBook.objects.create( + user=self.local_user, + book=book, + shelf=self.local_user.shelf_set.first(), + ) + models.ShelfBook.objects.create( + user=user_1, book=book, shelf=user_1.shelf_set.first() + ) + + result = get_annotated_users( + self.local_user, + ~Q(id=self.local_user.id), + ~Q(followers=self.local_user), + ) + self.assertEqual(result.count(), 2) + user_1_annotated = result.get(id=user_1.id) + self.assertEqual(user_1_annotated.mutuals, 3) diff --git a/bookwyrm/tests/views/test_edit_user.py b/bookwyrm/tests/views/test_edit_user.py index e9f90ad6..570b2183 100644 --- a/bookwyrm/tests/views/test_edit_user.py +++ b/bookwyrm/tests/views/test_edit_user.py @@ -123,7 +123,8 @@ class EditUserViews(TestCase): result.render() self.assertEqual(result.status_code, 200) - def test_delete_user(self): + @patch("bookwyrm.suggested_users.rerank_suggestions_task") + def test_delete_user(self, _): """use a form to update a user""" view = views.DeleteUser.as_view() form = forms.DeleteUserForm() diff --git a/bookwyrm/tests/views/test_helpers.py b/bookwyrm/tests/views/test_helpers.py index 11d0ad7b..2cfb0da9 100644 --- a/bookwyrm/tests/views/test_helpers.py +++ b/bookwyrm/tests/views/test_helpers.py @@ -14,6 +14,7 @@ from bookwyrm.settings import USER_AGENT @patch("bookwyrm.activitystreams.ActivityStream.add_status") @patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") +@patch("bookwyrm.suggested_users.rerank_user_task.delay") class ViewsHelpers(TestCase): """viewing and creating statuses""" @@ -203,95 +204,3 @@ class ViewsHelpers(TestCase): self.local_user, self.shelf, self.book, "public" ) self.assertFalse(models.GeneratedNote.objects.exists()) - - def test_get_annotated_users(self, *_): - """list of people you might know""" - user_1 = models.User.objects.create_user( - "nutria@local.com", - "nutria@nutria.com", - "nutriaword", - local=True, - localname="nutria", - discoverable=True, - ) - user_2 = models.User.objects.create_user( - "fish@local.com", - "fish@fish.com", - "fishword", - local=True, - localname="fish", - ) - with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - # 1 shared follow - self.local_user.following.add(user_2) - user_1.followers.add(user_2) - - # 1 shared book - models.ShelfBook.objects.create( - user=self.local_user, - book=self.book, - shelf=self.local_user.shelf_set.first(), - ) - models.ShelfBook.objects.create( - user=user_1, book=self.book, shelf=user_1.shelf_set.first() - ) - - result = views.helpers.get_annotated_users(self.local_user) - self.assertEqual(result.count(), 3) - self.assertTrue(user_1 in result) - self.assertFalse(user_2 in result) - self.assertTrue(self.local_user in result) - self.assertTrue(self.remote_user in result) - - user_1_annotated = result.get(id=user_1.id) - self.assertEqual(user_1_annotated.mutuals, 1) - self.assertEqual(user_1_annotated.shared_books, 1) - - remote_user_annotated = result.get(id=self.remote_user.id) - self.assertEqual(remote_user_annotated.mutuals, 0) - self.assertEqual(remote_user_annotated.shared_books, 0) - - def test_get_annotated_users_counts(self, *_): - """correct counting for multiple shared attributed""" - user_1 = models.User.objects.create_user( - "nutria@local.com", - "nutria@nutria.com", - "nutriaword", - local=True, - localname="nutria", - discoverable=True, - ) - for i in range(3): - user = models.User.objects.create_user( - "{:d}@local.com".format(i), - "{:d}@nutria.com".format(i), - "password", - local=True, - localname=i, - ) - user.following.add(user_1) - user.followers.add(self.local_user) - - with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - for i in range(3): - book = models.Edition.objects.create( - title=i, - parent_work=models.Work.objects.create(title=i), - ) - models.ShelfBook.objects.create( - user=self.local_user, - book=book, - shelf=self.local_user.shelf_set.first(), - ) - models.ShelfBook.objects.create( - user=user_1, book=book, shelf=user_1.shelf_set.first() - ) - - result = views.helpers.get_annotated_users( - self.local_user, - ~Q(id=self.local_user.id), - ~Q(followers=self.local_user), - ) - self.assertEqual(result.count(), 2) - user_1_annotated = result.get(id=user_1.id) - self.assertEqual(user_1_annotated.mutuals, 3) From 5d2324a4a00e951ce6ad7abe6c4108c0b113de0f Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 3 Aug 2021 13:03:24 -0700 Subject: [PATCH 40/47] Edit user test mocks --- bookwyrm/tests/views/test_edit_user.py | 27 +++++++++++++------------ bookwyrm/tests/views/test_federation.py | 2 +- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/bookwyrm/tests/views/test_edit_user.py b/bookwyrm/tests/views/test_edit_user.py index 570b2183..0a86c486 100644 --- a/bookwyrm/tests/views/test_edit_user.py +++ b/bookwyrm/tests/views/test_edit_user.py @@ -15,6 +15,7 @@ from django.test.client import RequestFactory from bookwyrm import forms, models, views +@patch("bookwyrm.suggested_users.remove_user_task.delay") class EditUserViews(TestCase): """view user and edit profile""" @@ -33,19 +34,19 @@ class EditUserViews(TestCase): "rat@local.com", "rat@rat.rat", "password", local=True, localname="rat" ) - self.book = models.Edition.objects.create(title="test") - with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - models.ShelfBook.objects.create( - book=self.book, - user=self.local_user, - shelf=self.local_user.shelf_set.first(), - ) + self.book = models.Edition.objects.create(title="test") + with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): + models.ShelfBook.objects.create( + book=self.book, + user=self.local_user, + shelf=self.local_user.shelf_set.first(), + ) models.SiteSettings.objects.create() self.anonymous_user = AnonymousUser self.anonymous_user.is_authenticated = False - def test_edit_user_page(self): + def test_edit_user_page(self, _): """there are so many views, this just makes sure it LOADS""" view = views.EditUser.as_view() request = self.factory.get("") @@ -55,7 +56,7 @@ class EditUserViews(TestCase): result.render() self.assertEqual(result.status_code, 200) - def test_edit_user(self): + def test_edit_user(self, _): """use a form to update a user""" view = views.EditUser.as_view() form = forms.EditUserForm(instance=self.local_user) @@ -74,7 +75,7 @@ class EditUserViews(TestCase): self.assertEqual(self.local_user.name, "New Name") self.assertEqual(self.local_user.email, "wow@email.com") - def test_edit_user_avatar(self): + def test_edit_user_avatar(self, _): """use a form to update a user""" view = views.EditUser.as_view() form = forms.EditUserForm(instance=self.local_user) @@ -101,7 +102,7 @@ class EditUserViews(TestCase): self.assertEqual(self.local_user.avatar.width, 120) self.assertEqual(self.local_user.avatar.height, 120) - def test_crop_avatar(self): + def test_crop_avatar(self, _): """reduce that image size""" image_file = pathlib.Path(__file__).parent.joinpath( "../../static/images/no_cover.jpg" @@ -113,7 +114,7 @@ class EditUserViews(TestCase): image_result = Image.open(result) self.assertEqual(image_result.size, (120, 120)) - def test_delete_user_page(self): + def test_delete_user_page(self, _): """there are so many views, this just makes sure it LOADS""" view = views.DeleteUser.as_view() request = self.factory.get("") @@ -124,7 +125,7 @@ class EditUserViews(TestCase): self.assertEqual(result.status_code, 200) @patch("bookwyrm.suggested_users.rerank_suggestions_task") - def test_delete_user(self, _): + def test_delete_user(self, *_): """use a form to update a user""" view = views.DeleteUser.as_view() form = forms.DeleteUserForm() diff --git a/bookwyrm/tests/views/test_federation.py b/bookwyrm/tests/views/test_federation.py index 2ada24c6..7e7ef48f 100644 --- a/bookwyrm/tests/views/test_federation.py +++ b/bookwyrm/tests/views/test_federation.py @@ -163,7 +163,7 @@ class FederationViews(TestCase): """load a json file with a list of servers to block""" server = models.FederatedServer.objects.create(server_name="hi.there.com") self.remote_user.federated_server = server - self.remote_user.save() + self.remote_user.save(update_fields=["federated_server"]) data = [ {"instance": "server.name", "url": "https://explanation.url"}, # new server From f35855ce6991a92ec6177720cc683c9f266d3f15 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 3 Aug 2021 13:27:32 -0700 Subject: [PATCH 41/47] Moore moocks --- bookwyrm/tests/test_suggested_users.py | 10 +++++---- bookwyrm/tests/views/inbox/test_inbox.py | 15 ++++++------- bookwyrm/tests/views/inbox/test_inbox_add.py | 15 ++++++------- .../tests/views/inbox/test_inbox_announce.py | 17 ++++++++------- .../tests/views/inbox/test_inbox_block.py | 17 ++++++++------- .../tests/views/inbox/test_inbox_create.py | 17 ++++++++------- .../tests/views/inbox/test_inbox_delete.py | 17 ++++++++------- .../tests/views/inbox/test_inbox_follow.py | 21 ++++++++++--------- bookwyrm/tests/views/inbox/test_inbox_like.py | 17 ++++++++------- .../tests/views/inbox/test_inbox_remove.py | 17 ++++++++------- .../tests/views/inbox/test_inbox_update.py | 17 ++++++++------- bookwyrm/tests/views/test_helpers.py | 1 - bookwyrm/tests/views/test_landing.py | 3 ++- bookwyrm/tests/views/test_reports.py | 1 + bookwyrm/tests/views/test_shelf.py | 19 +++++++++-------- 15 files changed, 109 insertions(+), 95 deletions(-) diff --git a/bookwyrm/tests/test_suggested_users.py b/bookwyrm/tests/test_suggested_users.py index d950858d..13dfda39 100644 --- a/bookwyrm/tests/test_suggested_users.py +++ b/bookwyrm/tests/test_suggested_users.py @@ -2,6 +2,7 @@ from collections import namedtuple from unittest.mock import patch +from django.db.models import Q from django.test import TestCase from bookwyrm import models @@ -10,6 +11,8 @@ from bookwyrm.suggested_users import suggested_users, get_annotated_users @patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") @patch("bookwyrm.activitystreams.ActivityStream.add_status") +@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") +@patch("bookwyrm.suggested_users.rerank_user_task.delay") class SuggestedUsers(TestCase): """using redis to build activity streams""" @@ -41,8 +44,6 @@ class SuggestedUsers(TestCase): self.assertEqual(counts["mutuals"], 3) self.assertEqual(counts["shared_books"], 27) - @patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") - @patch("bookwyrm.suggested_users.rerank_user_task.delay") def test_get_objects_for_store(self, *_): """list of people to follow for a given user""" @@ -96,6 +97,7 @@ class SuggestedUsers(TestCase): "fishword", local=True, localname="fish", + discoverable=True, ) with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): # 1 shared follow @@ -112,8 +114,8 @@ class SuggestedUsers(TestCase): user=user_1, book=self.book, shelf=user_1.shelf_set.first() ) - result = views.helpers.get_annotated_users(self.local_user) - self.assertEqual(result.count(), 3) + result = get_annotated_users(self.local_user) + self.assertEqual(result.count(), 2) self.assertTrue(user_1 in result) self.assertFalse(user_2 in result) self.assertTrue(self.local_user in result) diff --git a/bookwyrm/tests/views/inbox/test_inbox.py b/bookwyrm/tests/views/inbox/test_inbox.py index 43032c62..9ca500c4 100644 --- a/bookwyrm/tests/views/inbox/test_inbox.py +++ b/bookwyrm/tests/views/inbox/test_inbox.py @@ -19,13 +19,14 @@ class Inbox(TestCase): self.client = Client() self.factory = RequestFactory() - local_user = models.User.objects.create_user( - "mouse@example.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + local_user = models.User.objects.create_user( + "mouse@example.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + ) local_user.remote_id = "https://example.com/user/mouse" local_user.save(broadcast=False) with patch("bookwyrm.models.user.set_remote_server.delay"): diff --git a/bookwyrm/tests/views/inbox/test_inbox_add.py b/bookwyrm/tests/views/inbox/test_inbox_add.py index 64be49b8..4280196b 100644 --- a/bookwyrm/tests/views/inbox/test_inbox_add.py +++ b/bookwyrm/tests/views/inbox/test_inbox_add.py @@ -13,13 +13,14 @@ class InboxAdd(TestCase): def setUp(self): """basic user and book data""" - local_user = models.User.objects.create_user( - "mouse@example.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + local_user = models.User.objects.create_user( + "mouse@example.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + ) local_user.remote_id = "https://example.com/user/mouse" local_user.save(broadcast=False) with patch("bookwyrm.models.user.set_remote_server.delay"): diff --git a/bookwyrm/tests/views/inbox/test_inbox_announce.py b/bookwyrm/tests/views/inbox/test_inbox_announce.py index 5866ed1f..8bc6cd1d 100644 --- a/bookwyrm/tests/views/inbox/test_inbox_announce.py +++ b/bookwyrm/tests/views/inbox/test_inbox_announce.py @@ -13,15 +13,16 @@ class InboxActivities(TestCase): def setUp(self): """basic user and book data""" - self.local_user = models.User.objects.create_user( - "mouse@example.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@example.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + ) self.local_user.remote_id = "https://example.com/user/mouse" - self.local_user.save(broadcast=False) + self.local_user.save(broadcast=False, update_fields=["remote_id"]) with patch("bookwyrm.models.user.set_remote_server.delay"): self.remote_user = models.User.objects.create_user( "rat", diff --git a/bookwyrm/tests/views/inbox/test_inbox_block.py b/bookwyrm/tests/views/inbox/test_inbox_block.py index 956cf538..ce650a00 100644 --- a/bookwyrm/tests/views/inbox/test_inbox_block.py +++ b/bookwyrm/tests/views/inbox/test_inbox_block.py @@ -12,15 +12,16 @@ class InboxBlock(TestCase): def setUp(self): """basic user and book data""" - self.local_user = models.User.objects.create_user( - "mouse@example.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@example.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + ) self.local_user.remote_id = "https://example.com/user/mouse" - self.local_user.save(broadcast=False) + self.local_user.save(broadcast=False, update_fields=["remote_id"]) with patch("bookwyrm.models.user.set_remote_server.delay"): self.remote_user = models.User.objects.create_user( "rat", diff --git a/bookwyrm/tests/views/inbox/test_inbox_create.py b/bookwyrm/tests/views/inbox/test_inbox_create.py index d2fdb8b6..f93354bf 100644 --- a/bookwyrm/tests/views/inbox/test_inbox_create.py +++ b/bookwyrm/tests/views/inbox/test_inbox_create.py @@ -16,15 +16,16 @@ class InboxCreate(TestCase): def setUp(self): """basic user and book data""" - self.local_user = models.User.objects.create_user( - "mouse@example.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@example.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + ) self.local_user.remote_id = "https://example.com/user/mouse" - self.local_user.save(broadcast=False) + self.local_user.save(broadcast=False, update_fields=["remote_id"]) with patch("bookwyrm.models.user.set_remote_server.delay"): self.remote_user = models.User.objects.create_user( "rat", diff --git a/bookwyrm/tests/views/inbox/test_inbox_delete.py b/bookwyrm/tests/views/inbox/test_inbox_delete.py index 617dcf6f..cc3299aa 100644 --- a/bookwyrm/tests/views/inbox/test_inbox_delete.py +++ b/bookwyrm/tests/views/inbox/test_inbox_delete.py @@ -13,15 +13,16 @@ class InboxActivities(TestCase): def setUp(self): """basic user and book data""" - self.local_user = models.User.objects.create_user( - "mouse@example.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@example.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + ) self.local_user.remote_id = "https://example.com/user/mouse" - self.local_user.save(broadcast=False) + self.local_user.save(broadcast=False, update_fields=["remote_id"]) with patch("bookwyrm.models.user.set_remote_server.delay"): self.remote_user = models.User.objects.create_user( "rat", diff --git a/bookwyrm/tests/views/inbox/test_inbox_follow.py b/bookwyrm/tests/views/inbox/test_inbox_follow.py index f5332b7a..9adce7ab 100644 --- a/bookwyrm/tests/views/inbox/test_inbox_follow.py +++ b/bookwyrm/tests/views/inbox/test_inbox_follow.py @@ -13,15 +13,16 @@ class InboxRelationships(TestCase): def setUp(self): """basic user and book data""" - self.local_user = models.User.objects.create_user( - "mouse@example.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@example.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + ) self.local_user.remote_id = "https://example.com/user/mouse" - self.local_user.save(broadcast=False) + self.local_user.save(broadcast=False, update_fields=["remote_id"]) with patch("bookwyrm.models.user.set_remote_server.delay"): self.remote_user = models.User.objects.create_user( "rat", @@ -102,7 +103,7 @@ class InboxRelationships(TestCase): } self.local_user.manually_approves_followers = True - self.local_user.save(broadcast=False) + self.local_user.save(broadcast=False, update_fields=["manually_approves_followers"]) with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): views.inbox.activity_task(activity) @@ -124,7 +125,7 @@ class InboxRelationships(TestCase): def test_undo_follow_request(self): """the requester cancels a follow request""" self.local_user.manually_approves_followers = True - self.local_user.save(broadcast=False) + self.local_user.save(broadcast=False, update_fields=["manually_approves_followers"]) with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): request = models.UserFollowRequest.objects.create( user_subject=self.remote_user, user_object=self.local_user diff --git a/bookwyrm/tests/views/inbox/test_inbox_like.py b/bookwyrm/tests/views/inbox/test_inbox_like.py index 433d5ba4..56f21e89 100644 --- a/bookwyrm/tests/views/inbox/test_inbox_like.py +++ b/bookwyrm/tests/views/inbox/test_inbox_like.py @@ -12,15 +12,16 @@ class InboxActivities(TestCase): def setUp(self): """basic user and book data""" - self.local_user = models.User.objects.create_user( - "mouse@example.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@example.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + ) self.local_user.remote_id = "https://example.com/user/mouse" - self.local_user.save(broadcast=False) + self.local_user.save(broadcast=False, update_fields=["remote_id"]) with patch("bookwyrm.models.user.set_remote_server.delay"): self.remote_user = models.User.objects.create_user( "rat", diff --git a/bookwyrm/tests/views/inbox/test_inbox_remove.py b/bookwyrm/tests/views/inbox/test_inbox_remove.py index 4e78480a..3d64fcb2 100644 --- a/bookwyrm/tests/views/inbox/test_inbox_remove.py +++ b/bookwyrm/tests/views/inbox/test_inbox_remove.py @@ -12,15 +12,16 @@ class InboxRemove(TestCase): def setUp(self): """basic user and book data""" - self.local_user = models.User.objects.create_user( - "mouse@example.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@example.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + ) self.local_user.remote_id = "https://example.com/user/mouse" - self.local_user.save(broadcast=False) + self.local_user.save(broadcast=False, update_fields=["remote_id"]) with patch("bookwyrm.models.user.set_remote_server.delay"): self.remote_user = models.User.objects.create_user( "rat", diff --git a/bookwyrm/tests/views/inbox/test_inbox_update.py b/bookwyrm/tests/views/inbox/test_inbox_update.py index 242cfe91..77c7ab76 100644 --- a/bookwyrm/tests/views/inbox/test_inbox_update.py +++ b/bookwyrm/tests/views/inbox/test_inbox_update.py @@ -14,15 +14,16 @@ class InboxUpdate(TestCase): def setUp(self): """basic user and book data""" - self.local_user = models.User.objects.create_user( - "mouse@example.com", - "mouse@mouse.com", - "mouseword", - local=True, - localname="mouse", - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@example.com", + "mouse@mouse.com", + "mouseword", + local=True, + localname="mouse", + ) self.local_user.remote_id = "https://example.com/user/mouse" - self.local_user.save(broadcast=False) + self.local_user.save(broadcast=False, update_fields=["remote_id"]) with patch("bookwyrm.models.user.set_remote_server.delay"): self.remote_user = models.User.objects.create_user( "rat", diff --git a/bookwyrm/tests/views/test_helpers.py b/bookwyrm/tests/views/test_helpers.py index 2cfb0da9..b9a82e68 100644 --- a/bookwyrm/tests/views/test_helpers.py +++ b/bookwyrm/tests/views/test_helpers.py @@ -2,7 +2,6 @@ import json from unittest.mock import patch import pathlib -from django.db.models import Q from django.http import Http404 from django.test import TestCase from django.test.client import RequestFactory diff --git a/bookwyrm/tests/views/test_landing.py b/bookwyrm/tests/views/test_landing.py index 4690ed1b..4d1531e4 100644 --- a/bookwyrm/tests/views/test_landing.py +++ b/bookwyrm/tests/views/test_landing.py @@ -27,7 +27,8 @@ class LandingViews(TestCase): self.anonymous_user.is_authenticated = False models.SiteSettings.objects.create() - def test_home_page(self): + @patch("bookwyrm.suggested_users.SuggestedUsers.get_suggestions") + def test_home_page(self, _): """there are so many views, this just makes sure it LOADS""" view = views.Home.as_view() request = self.factory.get("") diff --git a/bookwyrm/tests/views/test_reports.py b/bookwyrm/tests/views/test_reports.py index bb32bcb1..d6034a94 100644 --- a/bookwyrm/tests/views/test_reports.py +++ b/bookwyrm/tests/views/test_reports.py @@ -116,6 +116,7 @@ class ReportViews(TestCase): report.refresh_from_db() self.assertFalse(report.resolved) + @patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") def test_suspend_user(self): """toggle whether a user is able to log in""" self.assertTrue(self.rat.is_active) diff --git a/bookwyrm/tests/views/test_shelf.py b/bookwyrm/tests/views/test_shelf.py index ccba4c73..44c4b9cb 100644 --- a/bookwyrm/tests/views/test_shelf.py +++ b/bookwyrm/tests/views/test_shelf.py @@ -10,6 +10,7 @@ from bookwyrm.activitypub import ActivitypubResponse @patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") +@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") class ShelfViews(TestCase): """tag views""" @@ -37,7 +38,7 @@ class ShelfViews(TestCase): ) models.SiteSettings.objects.create() - def test_shelf_page(self, _): + def test_shelf_page(self, *_): """there are so many views, this just makes sure it LOADS""" view = views.Shelf.as_view() shelf = self.local_user.shelf_set.first() @@ -64,7 +65,7 @@ class ShelfViews(TestCase): self.assertIsInstance(result, ActivitypubResponse) self.assertEqual(result.status_code, 200) - def test_edit_shelf_privacy(self, _): + def test_edit_shelf_privacy(self, *_): """set name or privacy on shelf""" view = views.Shelf.as_view() shelf = self.local_user.shelf_set.get(identifier="to-read") @@ -84,7 +85,7 @@ class ShelfViews(TestCase): self.assertEqual(shelf.privacy, "unlisted") - def test_edit_shelf_name(self, _): + def test_edit_shelf_name(self, *_): """change the name of an editable shelf""" view = views.Shelf.as_view() shelf = models.Shelf.objects.create(name="Test Shelf", user=self.local_user) @@ -101,7 +102,7 @@ class ShelfViews(TestCase): self.assertEqual(shelf.name, "cool name") self.assertEqual(shelf.identifier, "testshelf-%d" % shelf.id) - def test_edit_shelf_name_not_editable(self, _): + def test_edit_shelf_name_not_editable(self, *_): """can't change the name of an non-editable shelf""" view = views.Shelf.as_view() shelf = self.local_user.shelf_set.get(identifier="to-read") @@ -116,7 +117,7 @@ class ShelfViews(TestCase): self.assertEqual(shelf.name, "To Read") - def test_handle_shelve(self, _): + def test_handle_shelve(self, *_): """shelve a book""" request = self.factory.post( "", {"book": self.book.id, "shelf": self.shelf.identifier} @@ -134,7 +135,7 @@ class ShelfViews(TestCase): # make sure the book is on the shelf self.assertEqual(self.shelf.books.get(), self.book) - def test_handle_shelve_to_read(self, _): + def test_handle_shelve_to_read(self, *_): """special behavior for the to-read shelf""" shelf = models.Shelf.objects.get(identifier="to-read") request = self.factory.post( @@ -147,7 +148,7 @@ class ShelfViews(TestCase): # make sure the book is on the shelf self.assertEqual(shelf.books.get(), self.book) - def test_handle_shelve_reading(self, _): + def test_handle_shelve_reading(self, *_): """special behavior for the reading shelf""" shelf = models.Shelf.objects.get(identifier="reading") request = self.factory.post( @@ -160,7 +161,7 @@ class ShelfViews(TestCase): # make sure the book is on the shelf self.assertEqual(shelf.books.get(), self.book) - def test_handle_shelve_read(self, _): + def test_handle_shelve_read(self, *_): """special behavior for the read shelf""" shelf = models.Shelf.objects.get(identifier="read") request = self.factory.post( @@ -173,7 +174,7 @@ class ShelfViews(TestCase): # make sure the book is on the shelf self.assertEqual(shelf.books.get(), self.book) - def test_handle_unshelve(self, _): + def test_handle_unshelve(self, *_): """remove a book from a shelf""" with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): models.ShelfBook.objects.create( From eeb8ae19dbb57258bc112e67e01541099c9b14bb Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 3 Aug 2021 14:05:52 -0700 Subject: [PATCH 42/47] Python formatting --- bookwyrm/tests/views/inbox/test_inbox_follow.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bookwyrm/tests/views/inbox/test_inbox_follow.py b/bookwyrm/tests/views/inbox/test_inbox_follow.py index 9adce7ab..d1a4e0d0 100644 --- a/bookwyrm/tests/views/inbox/test_inbox_follow.py +++ b/bookwyrm/tests/views/inbox/test_inbox_follow.py @@ -103,7 +103,9 @@ class InboxRelationships(TestCase): } self.local_user.manually_approves_followers = True - self.local_user.save(broadcast=False, update_fields=["manually_approves_followers"]) + self.local_user.save( + broadcast=False, update_fields=["manually_approves_followers"] + ) with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): views.inbox.activity_task(activity) @@ -125,7 +127,9 @@ class InboxRelationships(TestCase): def test_undo_follow_request(self): """the requester cancels a follow request""" self.local_user.manually_approves_followers = True - self.local_user.save(broadcast=False, update_fields=["manually_approves_followers"]) + self.local_user.save( + broadcast=False, update_fields=["manually_approves_followers"] + ) with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): request = models.UserFollowRequest.objects.create( user_subject=self.remote_user, user_object=self.local_user From 3970df312a9390ea63e79859f4efa491d76155e5 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 3 Aug 2021 15:00:02 -0700 Subject: [PATCH 43/47] Fixes inbox tests --- bookwyrm/tests/views/inbox/test_inbox.py | 5 +++-- bookwyrm/tests/views/inbox/test_inbox_add.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bookwyrm/tests/views/inbox/test_inbox.py b/bookwyrm/tests/views/inbox/test_inbox.py index 9ca500c4..8e49d25a 100644 --- a/bookwyrm/tests/views/inbox/test_inbox.py +++ b/bookwyrm/tests/views/inbox/test_inbox.py @@ -28,7 +28,7 @@ class Inbox(TestCase): localname="mouse", ) local_user.remote_id = "https://example.com/user/mouse" - local_user.save(broadcast=False) + local_user.save(broadcast=False, update_fields=["remote_id"]) with patch("bookwyrm.models.user.set_remote_server.delay"): self.remote_user = models.User.objects.create_user( "rat", @@ -145,7 +145,8 @@ class Inbox(TestCase): ) self.assertTrue(views.inbox.is_blocked_activity(activity)) - def test_create_by_deactivated_user(self): + @patch("bookwyrm.suggested_users.remove_user_task.delay") + def test_create_by_deactivated_user(self, _): """don't let deactivated users post""" self.remote_user.delete(broadcast=False) self.assertTrue(self.remote_user.deleted) diff --git a/bookwyrm/tests/views/inbox/test_inbox_add.py b/bookwyrm/tests/views/inbox/test_inbox_add.py index 4280196b..07525c34 100644 --- a/bookwyrm/tests/views/inbox/test_inbox_add.py +++ b/bookwyrm/tests/views/inbox/test_inbox_add.py @@ -22,7 +22,7 @@ class InboxAdd(TestCase): localname="mouse", ) local_user.remote_id = "https://example.com/user/mouse" - local_user.save(broadcast=False) + local_user.save(broadcast=False, update_fields=["remote_id"]) with patch("bookwyrm.models.user.set_remote_server.delay"): self.remote_user = models.User.objects.create_user( "rat", From 630ee3b7661d9b9f9e627e8d0882221cf4296561 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 3 Aug 2021 15:22:51 -0700 Subject: [PATCH 44/47] Mocks for user admin --- bookwyrm/tests/views/test_user_admin.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bookwyrm/tests/views/test_user_admin.py b/bookwyrm/tests/views/test_user_admin.py index 42f2d7e8..86309045 100644 --- a/bookwyrm/tests/views/test_user_admin.py +++ b/bookwyrm/tests/views/test_user_admin.py @@ -48,7 +48,9 @@ class UserAdminViews(TestCase): result.render() self.assertEqual(result.status_code, 200) - def test_user_admin_page_post(self): + @patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") + @patch("bookwyrm.suggested_users.remove_user_task.delay") + def test_user_admin_page_post(self, *_): """set the user's group""" group = Group.objects.create(name="editor") self.assertEqual( From cbf5747308d3f638488178c15a51fb168b81f713 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 3 Aug 2021 16:21:29 -0700 Subject: [PATCH 45/47] Mock mocks mocks --- .../tests/models/test_activitypub_mixin.py | 4 +- bookwyrm/tests/models/test_fields.py | 53 ++++++++++--------- bookwyrm/tests/test_suggested_users.py | 22 ++++---- bookwyrm/tests/views/test_book.py | 3 +- bookwyrm/tests/views/test_federation.py | 6 ++- bookwyrm/tests/views/test_feed.py | 2 + bookwyrm/tests/views/test_follow.py | 8 ++- bookwyrm/tests/views/test_get_started.py | 7 ++- bookwyrm/tests/views/test_readthrough.py | 5 +- bookwyrm/tests/views/test_reports.py | 3 +- bookwyrm/tests/views/test_status.py | 4 +- bookwyrm/tests/views/test_user.py | 14 ++--- 12 files changed, 73 insertions(+), 58 deletions(-) diff --git a/bookwyrm/tests/models/test_activitypub_mixin.py b/bookwyrm/tests/models/test_activitypub_mixin.py index aea87e8d..01d06e02 100644 --- a/bookwyrm/tests/models/test_activitypub_mixin.py +++ b/bookwyrm/tests/models/test_activitypub_mixin.py @@ -32,7 +32,7 @@ class ActivitypubMixins(TestCase): "mouse", "mouse@mouse.com", "mouseword", local=True, localname="mouse" ) self.local_user.remote_id = "http://example.com/a/b" - self.local_user.save(broadcast=False) + self.local_user.save(broadcast=False, update_fields=["remote_id"]) with patch("bookwyrm.models.user.set_remote_server.delay"): self.remote_user = models.User.objects.create_user( "rat", @@ -190,7 +190,7 @@ class ActivitypubMixins(TestCase): def test_get_recipients_combine_inboxes(self, *_): """should combine users with the same shared_inbox""" self.remote_user.shared_inbox = "http://example.com/inbox" - self.remote_user.save(broadcast=False) + self.remote_user.save(broadcast=False, update_fields=["shared_inbox"]) with patch("bookwyrm.models.user.set_remote_server.delay"): another_remote_user = models.User.objects.create_user( "nutria", diff --git a/bookwyrm/tests/models/test_fields.py b/bookwyrm/tests/models/test_fields.py index 8489b49f..2520a2fd 100644 --- a/bookwyrm/tests/models/test_fields.py +++ b/bookwyrm/tests/models/test_fields.py @@ -24,10 +24,11 @@ from bookwyrm.models.base_model import BookWyrmModel from bookwyrm.models.activitypub_mixin import ActivitypubMixin # pylint: disable=too-many-public-methods -class ActivitypubFields(TestCase): +@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") +class ModelFields(TestCase): """overwrites standard model feilds to work with activitypub""" - def test_validate_remote_id(self): + 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")) @@ -44,7 +45,7 @@ class ActivitypubFields(TestCase): "http://www.example.com/dlfjg 23/x", ) - def test_activitypub_field_mixin(self): + def test_activitypub_field_mixin(self, _): """generic mixin with super basic to and from functionality""" instance = fields.ActivitypubFieldMixin() self.assertEqual(instance.field_to_activity("fish"), "fish") @@ -62,7 +63,7 @@ class ActivitypubFields(TestCase): instance.name = "snake_case_name" self.assertEqual(instance.get_activitypub_field(), "snakeCaseName") - def test_set_field_from_activity(self): + def test_set_field_from_activity(self, _): """setter from entire json blob""" @dataclass @@ -81,7 +82,7 @@ class ActivitypubFields(TestCase): instance.set_field_from_activity(mock_model, data) self.assertEqual(mock_model.field_name, "hi") - def test_set_activity_from_field(self): + def test_set_activity_from_field(self, _): """set json field given entire model""" @dataclass @@ -99,7 +100,7 @@ class ActivitypubFields(TestCase): instance.set_activity_from_field(data, mock_model) self.assertEqual(data["fieldName"], "bip") - def test_remote_id_field(self): + def test_remote_id_field(self, _): """just sets some defaults on charfield""" instance = fields.RemoteIdField() self.assertEqual(instance.max_length, 255) @@ -108,7 +109,7 @@ class ActivitypubFields(TestCase): with self.assertRaises(ValidationError): instance.run_validators("http://www.example.com/dlfjg 23/x") - def test_username_field(self): + def test_username_field(self, _): """again, just setting defaults on username field""" instance = fields.UsernameField() self.assertEqual(instance.activitypub_field, "preferredUsername") @@ -129,7 +130,7 @@ class ActivitypubFields(TestCase): self.assertEqual(instance.field_to_activity("test@example.com"), "test") - def test_privacy_field_defaults(self): + def test_privacy_field_defaults(self, _): """post privacy field's many default values""" instance = fields.PrivacyField() self.assertEqual(instance.max_length, 255) @@ -142,7 +143,7 @@ class ActivitypubFields(TestCase): instance.public, "https://www.w3.org/ns/activitystreams#Public" ) - def test_privacy_field_set_field_from_activity(self): + def test_privacy_field_set_field_from_activity(self, _): """translate between to/cc fields and privacy""" @dataclass(init=False) @@ -186,7 +187,6 @@ class ActivitypubFields(TestCase): @patch("bookwyrm.models.activitypub_mixin.ObjectMixin.broadcast") @patch("bookwyrm.activitystreams.ActivityStream.add_status") - @patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") def test_privacy_field_set_activity_from_field(self, *_): """translate between to/cc fields and privacy""" user = User.objects.create_user( @@ -231,7 +231,7 @@ class ActivitypubFields(TestCase): self.assertEqual(activity["to"], [user.remote_id]) self.assertEqual(activity["cc"], []) - def test_foreign_key(self): + def test_foreign_key(self, _): """should be able to format a related model""" instance = fields.ForeignKey("User", on_delete=models.CASCADE) Serializable = namedtuple("Serializable", ("to_activity", "remote_id")) @@ -240,7 +240,7 @@ class ActivitypubFields(TestCase): self.assertEqual(instance.field_to_activity(item), "https://e.b/c") @responses.activate - def test_foreign_key_from_activity_str(self): + def test_foreign_key_from_activity_str(self, _): """create a new object from a foreign key""" instance = fields.ForeignKey(User, on_delete=models.CASCADE) datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_user.json") @@ -267,7 +267,7 @@ class ActivitypubFields(TestCase): self.assertEqual(value.remote_id, "https://example.com/user/mouse") self.assertEqual(value.name, "MOUSE?? MOUSE!!") - def test_foreign_key_from_activity_dict(self): + def test_foreign_key_from_activity_dict(self, *_): """test recieving activity json""" instance = fields.ForeignKey(User, on_delete=models.CASCADE) datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_user.json") @@ -287,7 +287,7 @@ class ActivitypubFields(TestCase): self.assertEqual(value.name, "MOUSE?? MOUSE!!") # et cetera but we're not testing serializing user json - def test_foreign_key_from_activity_dict_existing(self): + def test_foreign_key_from_activity_dict_existing(self, _): """test receiving a dict of an existing object in the db""" instance = fields.ForeignKey(User, on_delete=models.CASCADE) datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_user.json") @@ -296,7 +296,7 @@ class ActivitypubFields(TestCase): "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" ) user.remote_id = "https://example.com/user/mouse" - user.save(broadcast=False) + user.save(broadcast=False, update_fields=["remote_id"]) User.objects.create_user( "rat", "rat@rat.rat", "ratword", local=True, localname="rat" @@ -306,7 +306,7 @@ class ActivitypubFields(TestCase): value = instance.field_from_activity(activitypub.Person(**userdata)) self.assertEqual(value, user) - def test_foreign_key_from_activity_str_existing(self): + def test_foreign_key_from_activity_str_existing(self, _): """test receiving a remote id of an existing object in the db""" instance = fields.ForeignKey(User, on_delete=models.CASCADE) user = User.objects.create_user( @@ -319,14 +319,14 @@ class ActivitypubFields(TestCase): value = instance.field_from_activity(user.remote_id) self.assertEqual(value, user) - def test_one_to_one_field(self): + def test_one_to_one_field(self, _): """a gussied up foreign key""" instance = fields.OneToOneField("User", on_delete=models.CASCADE) Serializable = namedtuple("Serializable", ("to_activity", "remote_id")) item = Serializable(lambda: {"a": "b"}, "https://e.b/c") self.assertEqual(instance.field_to_activity(item), {"a": "b"}) - def test_many_to_many_field(self): + def test_many_to_many_field(self, _): """lists!""" instance = fields.ManyToManyField("User") @@ -344,7 +344,7 @@ class ActivitypubFields(TestCase): self.assertEqual(instance.field_to_activity(items), "example.com/snake_case") @responses.activate - def test_many_to_many_field_from_activity(self): + def test_many_to_many_field_from_activity(self, _): """resolve related fields for a list, takes a list of remote ids""" instance = fields.ManyToManyField(User) datafile = pathlib.Path(__file__).parent.joinpath("../data/ap_user.json") @@ -364,7 +364,7 @@ class ActivitypubFields(TestCase): self.assertEqual(len(value), 1) self.assertIsInstance(value[0], User) - def test_tag_field(self): + def test_tag_field(self, _): """a special type of many to many field""" instance = fields.TagField("User") @@ -383,13 +383,14 @@ class ActivitypubFields(TestCase): self.assertEqual(result[0].name, "Name") self.assertEqual(result[0].type, "Serializable") - def test_tag_field_from_activity(self): + def test_tag_field_from_activity(self, _): """loadin' a list of items from Links""" # TODO @responses.activate @patch("bookwyrm.models.activitypub_mixin.ObjectMixin.broadcast") - def test_image_field(self, _): + @patch("bookwyrm.suggested_users.remove_user_task.delay") + def test_image_field(self, *_): """storing images""" user = User.objects.create_user( "mouse", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" @@ -427,7 +428,7 @@ class ActivitypubFields(TestCase): self.assertIsInstance(loaded_image, list) self.assertIsInstance(loaded_image[1], ContentFile) - def test_image_serialize(self): + def test_image_serialize(self, _): """make sure we're creating sensible image paths""" ValueMock = namedtuple("ValueMock", ("url")) value_mock = ValueMock("/images/fish.jpg") @@ -436,7 +437,7 @@ class ActivitypubFields(TestCase): self.assertEqual(result.url, "https://your.domain.here/images/fish.jpg") self.assertEqual(result.name, "hello") - def test_datetime_field(self): + def test_datetime_field(self, _): """this one is pretty simple, it just has to use isoformat""" instance = fields.DateTimeField() now = timezone.now() @@ -444,12 +445,12 @@ class ActivitypubFields(TestCase): self.assertEqual(instance.field_from_activity(now.isoformat()), now) self.assertEqual(instance.field_from_activity("bip"), None) - def test_array_field(self): + def test_array_field(self, _): """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): + def test_html_field(self, _): """sanitizes html, the sanitizer has its own tests""" instance = fields.HtmlField() self.assertEqual( diff --git a/bookwyrm/tests/test_suggested_users.py b/bookwyrm/tests/test_suggested_users.py index 13dfda39..41e8911e 100644 --- a/bookwyrm/tests/test_suggested_users.py +++ b/bookwyrm/tests/test_suggested_users.py @@ -13,6 +13,7 @@ from bookwyrm.suggested_users import suggested_users, get_annotated_users @patch("bookwyrm.activitystreams.ActivityStream.add_status") @patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") @patch("bookwyrm.suggested_users.rerank_user_task.delay") +@patch("bookwyrm.suggested_users.remove_user_task.delay") class SuggestedUsers(TestCase): """using redis to build activity streams""" @@ -22,7 +23,6 @@ class SuggestedUsers(TestCase): self.local_user = models.User.objects.create_user( "mouse", "mouse@mouse.mouse", "password", local=True, localname="mouse" ) - self.book = models.Edition.objects.create(title="test book") def test_get_rank(self, *_): """a float that reflects both the mutuals count and shared books""" @@ -97,7 +97,12 @@ class SuggestedUsers(TestCase): "fishword", local=True, localname="fish", - discoverable=True, + ) + work = models.Work.objects.create(title="Test Work") + book = models.Edition.objects.create( + title="Test Book", + remote_id="https://example.com/book/1", + parent_work=work, ) with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): # 1 shared follow @@ -107,28 +112,22 @@ class SuggestedUsers(TestCase): # 1 shared book models.ShelfBook.objects.create( user=self.local_user, - book=self.book, + book=book, shelf=self.local_user.shelf_set.first(), ) models.ShelfBook.objects.create( - user=user_1, book=self.book, shelf=user_1.shelf_set.first() + user=user_1, book=book, shelf=user_1.shelf_set.first() ) result = get_annotated_users(self.local_user) - self.assertEqual(result.count(), 2) + self.assertEqual(result.count(), 1) self.assertTrue(user_1 in result) self.assertFalse(user_2 in result) - self.assertTrue(self.local_user in result) - self.assertTrue(self.remote_user in result) user_1_annotated = result.get(id=user_1.id) self.assertEqual(user_1_annotated.mutuals, 1) self.assertEqual(user_1_annotated.shared_books, 1) - remote_user_annotated = result.get(id=self.remote_user.id) - self.assertEqual(remote_user_annotated.mutuals, 0) - self.assertEqual(remote_user_annotated.shared_books, 0) - def test_get_annotated_users_counts(self, *_): """correct counting for multiple shared attributed""" user_1 = models.User.objects.create_user( @@ -170,6 +169,5 @@ class SuggestedUsers(TestCase): ~Q(id=self.local_user.id), ~Q(followers=self.local_user), ) - self.assertEqual(result.count(), 2) user_1_annotated = result.get(id=user_1.id) self.assertEqual(user_1_annotated.mutuals, 3) diff --git a/bookwyrm/tests/views/test_book.py b/bookwyrm/tests/views/test_book.py index 47e32229..6f6116b2 100644 --- a/bookwyrm/tests/views/test_book.py +++ b/bookwyrm/tests/views/test_book.py @@ -201,7 +201,8 @@ class BookViews(TestCase): self.assertEqual(book.authors.first().name, "Sappho") self.assertEqual(book.authors.first(), book.parent_work.authors.first()) - def test_switch_edition(self): + @patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") + def test_switch_edition(self, _): """updates user's relationships to a book""" work = models.Work.objects.create(title="test work") edition1 = models.Edition.objects.create(title="first ed", parent_work=work) diff --git a/bookwyrm/tests/views/test_federation.py b/bookwyrm/tests/views/test_federation.py index 7e7ef48f..f43a9623 100644 --- a/bookwyrm/tests/views/test_federation.py +++ b/bookwyrm/tests/views/test_federation.py @@ -69,7 +69,7 @@ class FederationViews(TestCase): identifier="hi.there.com", ) self.remote_user.federated_server = server - self.remote_user.save() + self.remote_user.save(update_fields=["federated_server"]) self.assertEqual(server.status, "federated") @@ -108,7 +108,9 @@ class FederationViews(TestCase): self.remote_user.federated_server = server self.remote_user.is_active = False self.remote_user.deactivation_reason = "domain_block" - self.remote_user.save() + self.remote_user.save( + update_fields=["federated_server", "is_active", "deactivation_reason"] + ) request = self.factory.post("") request.user = self.local_user diff --git a/bookwyrm/tests/views/test_feed.py b/bookwyrm/tests/views/test_feed.py index aad72b0c..406310e4 100644 --- a/bookwyrm/tests/views/test_feed.py +++ b/bookwyrm/tests/views/test_feed.py @@ -16,6 +16,8 @@ from bookwyrm.activitypub import ActivitypubResponse @patch("bookwyrm.activitystreams.ActivityStream.get_activity_stream") @patch("bookwyrm.activitystreams.ActivityStream.add_status") +@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") +@patch("bookwyrm.suggested_users.remove_user_task.delay") class FeedViews(TestCase): """activity feed, statuses, dms""" diff --git a/bookwyrm/tests/views/test_follow.py b/bookwyrm/tests/views/test_follow.py index 122cf3c6..714284b0 100644 --- a/bookwyrm/tests/views/test_follow.py +++ b/bookwyrm/tests/views/test_follow.py @@ -130,7 +130,9 @@ class FollowViews(TestCase): def test_handle_accept(self): """accept a follow request""" self.local_user.manually_approves_followers = True - self.local_user.save(broadcast=False) + self.local_user.save( + broadcast=False, update_fields=["manually_approves_followers"] + ) request = self.factory.post("", {"user": self.remote_user.username}) request.user = self.local_user rel = models.UserFollowRequest.objects.create( @@ -147,7 +149,9 @@ class FollowViews(TestCase): def test_handle_reject(self): """reject a follow request""" self.local_user.manually_approves_followers = True - self.local_user.save(broadcast=False) + self.local_user.save( + broadcast=False, update_fields=["manually_approves_followers"] + ) request = self.factory.post("", {"user": self.remote_user.username}) request.user = self.local_user rel = models.UserFollowRequest.objects.create( diff --git a/bookwyrm/tests/views/test_get_started.py b/bookwyrm/tests/views/test_get_started.py index c741415c..31e5e86c 100644 --- a/bookwyrm/tests/views/test_get_started.py +++ b/bookwyrm/tests/views/test_get_started.py @@ -43,7 +43,9 @@ class GetStartedViews(TestCase): result.render() self.assertEqual(result.status_code, 200) - def test_profile_view_post(self): + @patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") + @patch("bookwyrm.suggested_users.rerank_user_task.delay") + def test_profile_view_post(self, *_): """save basic user details""" view = views.GetStartedProfile.as_view() form = forms.LimitedEditUserForm(instance=self.local_user) @@ -85,7 +87,8 @@ class GetStartedViews(TestCase): result.render() self.assertEqual(result.status_code, 200) - def test_books_view_post(self): + @patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") + def test_books_view_post(self, _): """shelve some books""" view = views.GetStartedBooks.as_view() data = {self.book.id: self.local_user.shelf_set.first().id} diff --git a/bookwyrm/tests/views/test_readthrough.py b/bookwyrm/tests/views/test_readthrough.py index ae87ebdc..618d9cf8 100644 --- a/bookwyrm/tests/views/test_readthrough.py +++ b/bookwyrm/tests/views/test_readthrough.py @@ -7,6 +7,7 @@ from django.utils import timezone from bookwyrm import models +@patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") @patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") class ReadThrough(TestCase): """readthrough tests""" @@ -29,7 +30,7 @@ class ReadThrough(TestCase): with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): self.client.force_login(self.user) - def test_create_basic_readthrough(self, delay_mock): + def test_create_basic_readthrough(self, delay_mock, _): """A basic readthrough doesn't create a progress update""" self.assertEqual(self.edition.readthrough_set.count(), 0) @@ -50,7 +51,7 @@ class ReadThrough(TestCase): self.assertEqual(readthroughs[0].finish_date, None) self.assertEqual(delay_mock.call_count, 1) - def test_create_progress_readthrough(self, delay_mock): + def test_create_progress_readthrough(self, delay_mock, _): """a readthrough with progress""" self.assertEqual(self.edition.readthrough_set.count(), 0) diff --git a/bookwyrm/tests/views/test_reports.py b/bookwyrm/tests/views/test_reports.py index d6034a94..7d39a007 100644 --- a/bookwyrm/tests/views/test_reports.py +++ b/bookwyrm/tests/views/test_reports.py @@ -117,7 +117,8 @@ class ReportViews(TestCase): self.assertFalse(report.resolved) @patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") - def test_suspend_user(self): + @patch("bookwyrm.suggested_users.remove_user_task.delay") + def test_suspend_user(self, *_): """toggle whether a user is able to log in""" self.assertTrue(self.rat.is_active) request = self.factory.post("") diff --git a/bookwyrm/tests/views/test_status.py b/bookwyrm/tests/views/test_status.py index 2049963c..d4edee0b 100644 --- a/bookwyrm/tests/views/test_status.py +++ b/bookwyrm/tests/views/test_status.py @@ -9,8 +9,8 @@ from bookwyrm.settings import DOMAIN # pylint: disable=invalid-name -@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") @patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") +@patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay") class StatusViews(TestCase): """viewing and creating statuses""" @@ -328,7 +328,7 @@ class StatusViews(TestCase): result = views.status.to_markdown(text) self.assertEqual(result, '

hi ' "is rad

") - def test_handle_delete_status(self, mock): + def test_handle_delete_status(self, mock, *_): """marks a status as deleted""" view = views.DeleteStatus.as_view() with patch("bookwyrm.activitystreams.ActivityStream.add_status"): diff --git a/bookwyrm/tests/views/test_user.py b/bookwyrm/tests/views/test_user.py index 5e3680d2..0efdf16a 100644 --- a/bookwyrm/tests/views/test_user.py +++ b/bookwyrm/tests/views/test_user.py @@ -30,11 +30,12 @@ class UserViews(TestCase): ) self.book = models.Edition.objects.create(title="test") with patch("bookwyrm.models.activitypub_mixin.broadcast_task.delay"): - models.ShelfBook.objects.create( - book=self.book, - user=self.local_user, - shelf=self.local_user.shelf_set.first(), - ) + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"): + models.ShelfBook.objects.create( + book=self.book, + user=self.local_user, + shelf=self.local_user.shelf_set.first(), + ) models.SiteSettings.objects.create() self.anonymous_user = AnonymousUser @@ -95,7 +96,8 @@ class UserViews(TestCase): self.assertIsInstance(result, ActivitypubResponse) self.assertEqual(result.status_code, 200) - def test_followers_page_blocked(self): + @patch("bookwyrm.suggested_users.rerank_suggestions_task.delay") + def test_followers_page_blocked(self, _): """there are so many views, this just makes sure it LOADS""" view = views.Followers.as_view() request = self.factory.get("") From 89acfa4f3e0bd1a062855fce22542da572e77a3b Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 4 Aug 2021 08:50:50 -0700 Subject: [PATCH 46/47] Patches stray redis calls --- bookwyrm/models/user.py | 2 +- bookwyrm/tests/models/test_user_model.py | 3 ++- bookwyrm/tests/views/inbox/test_inbox_delete.py | 3 ++- bookwyrm/tests/views/inbox/test_inbox_update.py | 3 ++- bookwyrm/tests/views/test_feed.py | 1 + bookwyrm/tests/views/test_get_started.py | 6 ++++-- 6 files changed, 12 insertions(+), 6 deletions(-) diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index b87ffd7d..f697d474 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -405,7 +405,7 @@ def set_remote_server(user_id): user = User.objects.get(id=user_id) actor_parts = urlparse(user.remote_id) user.federated_server = get_or_create_remote_server(actor_parts.netloc) - user.save(broadcast=False) + user.save(broadcast=False, update_fields=["federated_server"]) if user.bookwyrm_user and user.outbox: get_remote_reviews.delay(user.outbox) diff --git a/bookwyrm/tests/models/test_user_model.py b/bookwyrm/tests/models/test_user_model.py index 0868bc4b..177b2ad6 100644 --- a/bookwyrm/tests/models/test_user_model.py +++ b/bookwyrm/tests/models/test_user_model.py @@ -155,7 +155,8 @@ class User(TestCase): self.assertIsNone(server.application_type) self.assertIsNone(server.application_version) - def test_delete_user(self): + @patch("bookwyrm.suggested_users.remove_user_task.delay") + def test_delete_user(self, _): """deactivate a user""" self.assertTrue(self.user.is_active) with patch( diff --git a/bookwyrm/tests/views/inbox/test_inbox_delete.py b/bookwyrm/tests/views/inbox/test_inbox_delete.py index cc3299aa..1566c05a 100644 --- a/bookwyrm/tests/views/inbox/test_inbox_delete.py +++ b/bookwyrm/tests/views/inbox/test_inbox_delete.py @@ -106,7 +106,8 @@ class InboxActivities(TestCase): self.assertEqual(models.Notification.objects.count(), 1) self.assertEqual(models.Notification.objects.get(), notif) - def test_delete_user(self): + @patch("bookwyrm.suggested_users.remove_user_task.delay") + def test_delete_user(self, _): """delete a user""" self.assertTrue(models.User.objects.get(username="rat@example.com").is_active) activity = { diff --git a/bookwyrm/tests/views/inbox/test_inbox_update.py b/bookwyrm/tests/views/inbox/test_inbox_update.py index 77c7ab76..4abb0fa0 100644 --- a/bookwyrm/tests/views/inbox/test_inbox_update.py +++ b/bookwyrm/tests/views/inbox/test_inbox_update.py @@ -80,7 +80,8 @@ class InboxUpdate(TestCase): self.assertEqual(book_list.description, "summary text") self.assertEqual(book_list.remote_id, "https://example.com/list/22") - def test_update_user(self): + @patch("bookwyrm.suggested_users.rerank_user_task.delay") + def test_update_user(self, _): """update an existing user""" models.UserFollows.objects.create( user_subject=self.local_user, diff --git a/bookwyrm/tests/views/test_feed.py b/bookwyrm/tests/views/test_feed.py index 406310e4..8a38b808 100644 --- a/bookwyrm/tests/views/test_feed.py +++ b/bookwyrm/tests/views/test_feed.py @@ -39,6 +39,7 @@ class FeedViews(TestCase): ) models.SiteSettings.objects.create() + @patch("bookwyrm.suggested_users.SuggestedUsers.get_suggestions") def test_feed(self, *_): """there are so many views, this just makes sure it LOADS""" view = views.Feed.as_view() diff --git a/bookwyrm/tests/views/test_get_started.py b/bookwyrm/tests/views/test_get_started.py index 31e5e86c..eb9d67b5 100644 --- a/bookwyrm/tests/views/test_get_started.py +++ b/bookwyrm/tests/views/test_get_started.py @@ -106,7 +106,8 @@ class GetStartedViews(TestCase): self.assertEqual(shelfbook.book, self.book) self.assertEqual(shelfbook.user, self.local_user) - def test_users_view(self): + @patch("bookwyrm.suggested_users.SuggestedUsers.get_suggestions") + def test_users_view(self, _): """there are so many views, this just makes sure it LOADS""" view = views.GetStartedUsers.as_view() request = self.factory.get("") @@ -118,7 +119,8 @@ class GetStartedViews(TestCase): result.render() self.assertEqual(result.status_code, 200) - def test_users_view_with_query(self): + @patch("bookwyrm.suggested_users.SuggestedUsers.get_suggestions") + def test_users_view_with_query(self, _): """there are so many views, this just makes sure it LOADS""" view = views.GetStartedUsers.as_view() request = self.factory.get("?query=rat") From 6db6aa6cb16de214b5c11cff946f2ca0df04b489 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 4 Aug 2021 09:49:05 -0700 Subject: [PATCH 47/47] Fixes removing user from recs on follow --- bookwyrm/suggested_users.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bookwyrm/suggested_users.py b/bookwyrm/suggested_users.py index d8a8f865..3a95ef7f 100644 --- a/bookwyrm/suggested_users.py +++ b/bookwyrm/suggested_users.py @@ -141,7 +141,7 @@ def update_suggestions_on_follow(sender, instance, created, *args, **kwargs): if instance.user_subject.local: remove_suggestion_task.delay(instance.user_subject.id, instance.user_object.id) - rerank_user_task.delay(instance.user_object.id) + rerank_user_task.delay(instance.user_object.id, update_only=False) @receiver(signals.post_save, sender=models.UserBlocks) @@ -159,7 +159,7 @@ def update_suggestions_on_block(sender, instance, *args, **kwargs): def update_suggestions_on_unfollow(sender, instance, **kwargs): """update rankings, but don't re-suggest because it was probably intentional""" if instance.user_object.discoverable: - rerank_user_task.delay(instance.user_object.id) + rerank_user_task.delay(instance.user_object.id, update_only=False) @receiver(signals.post_save, sender=models.ShelfBook)