From c9f9c0de5850d7a8aa625daff965c1e9537e76e0 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 8 Sep 2021 09:21:15 -0700 Subject: [PATCH 01/11] Fixes boosts task --- bookwyrm/activitystreams.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/activitystreams.py b/bookwyrm/activitystreams.py index c32dfa35d..18870853b 100644 --- a/bookwyrm/activitystreams.py +++ b/bookwyrm/activitystreams.py @@ -469,7 +469,7 @@ def handle_boost_task(boost_id): old_versions = models.Boost.objects.filter( boosted_status__id=boosted.id, created_date__lt=instance.created_date, - ).values_list("id", flat=True) + ) for stream in streams.values(): audience = stream.get_stores_for_object(instance) From a5ef8aa6e928c890d09880b5a2415a0ccf213b9a Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 8 Sep 2021 09:28:32 -0700 Subject: [PATCH 02/11] Adds flag on user model to hide suggested users --- .../0089_user_show_suggested_users.py | 18 ++++++++++++++++++ bookwyrm/models/user.py | 4 ++++ 2 files changed, 22 insertions(+) create mode 100644 bookwyrm/migrations/0089_user_show_suggested_users.py diff --git a/bookwyrm/migrations/0089_user_show_suggested_users.py b/bookwyrm/migrations/0089_user_show_suggested_users.py new file mode 100644 index 000000000..047bb974c --- /dev/null +++ b/bookwyrm/migrations/0089_user_show_suggested_users.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.4 on 2021-09-08 16:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0088_auto_20210905_2233"), + ] + + operations = [ + migrations.AddField( + model_name="user", + name="show_suggested_users", + field=models.BooleanField(default=True), + ), + ] diff --git a/bookwyrm/models/user.py b/bookwyrm/models/user.py index 4b03f6656..7500669f7 100644 --- a/bookwyrm/models/user.py +++ b/bookwyrm/models/user.py @@ -122,8 +122,12 @@ class User(OrderedCollectionPageMixin, AbstractUser): updated_date = models.DateTimeField(auto_now=True) last_active_date = models.DateTimeField(default=timezone.now) manually_approves_followers = fields.BooleanField(default=False) + + # options to turn features on and off show_goal = models.BooleanField(default=True) + show_suggested_users = models.BooleanField(default=True) discoverable = fields.BooleanField(default=False) + preferred_timezone = models.CharField( choices=[(str(tz), str(tz)) for tz in pytz.all_timezones], default=str(pytz.utc), From bb29f33ca8256a2706f5ddd15b53e8a85919770c Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 8 Sep 2021 09:38:36 -0700 Subject: [PATCH 03/11] Fixes goal prompt not appearing in feed --- bookwyrm/templates/feed/feed.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bookwyrm/templates/feed/feed.html b/bookwyrm/templates/feed/feed.html index 265a467a7..5c4e14e60 100644 --- a/bookwyrm/templates/feed/feed.html +++ b/bookwyrm/templates/feed/feed.html @@ -22,7 +22,7 @@ {% blocktrans with tab_key=tab.key %}load 0 unread status(es){% endblocktrans %} -{% if request.user.show_goal and not goal and tab.key == streams.first.key %} +{% if request.user.show_goal and not goal and tab.key == 'home' %} {% now 'Y' as year %}
{% include 'snippets/goal_card.html' with year=year %} From 527c0542af193885b546ff6e1a6871deaf674804 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 8 Sep 2021 10:02:06 -0700 Subject: [PATCH 04/11] View to hide suggestions --- bookwyrm/templates/feed/feed.html | 4 ++-- bookwyrm/templates/feed/suggested_users.html | 11 ++++++++++- bookwyrm/urls.py | 1 + bookwyrm/views/__init__.py | 2 +- bookwyrm/views/user.py | 12 ++++++++++++ 5 files changed, 26 insertions(+), 4 deletions(-) diff --git a/bookwyrm/templates/feed/feed.html b/bookwyrm/templates/feed/feed.html index 5c4e14e60..b8e351c9f 100644 --- a/bookwyrm/templates/feed/feed.html +++ b/bookwyrm/templates/feed/feed.html @@ -37,7 +37,7 @@

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

- {% if suggested_users %} + {% if request.user.show_suggested_users and suggested_users %} {# suggested users for when things are very lonely #} {% include 'feed/suggested_users.html' with suggested_users=suggested_users %} {% endif %} @@ -46,7 +46,7 @@ {% for activity in activities %} -{% if not activities.number > 1 and forloop.counter0 == 2 and suggested_users %} +{% if request.user.show_suggested_users and not activities.number > 1 and forloop.counter0 == 2 and suggested_users %} {# suggested users on the first page, two statuses down #} {% include 'feed/suggested_users.html' with suggested_users=suggested_users %} {% endif %} diff --git a/bookwyrm/templates/feed/suggested_users.html b/bookwyrm/templates/feed/suggested_users.html index 1de1ae139..4e9f822b5 100644 --- a/bookwyrm/templates/feed/suggested_users.html +++ b/bookwyrm/templates/feed/suggested_users.html @@ -1,6 +1,15 @@ {% load i18n %}
-

{% trans "Who to follow" %}

+
+
+

{% trans "Who to follow" %}

+
+
+ {% csrf_token %} + {% trans "Don't show suggested users" as button_text %} + +
+
{% include 'snippets/suggested_users.html' with suggested_users=suggested_users %} {% trans "View directory" %}
diff --git a/bookwyrm/urls.py b/bookwyrm/urls.py index 991114fad..9ae1b8228 100644 --- a/bookwyrm/urls.py +++ b/bookwyrm/urls.py @@ -215,6 +215,7 @@ urlpatterns = [ views.Following.as_view(), name="user-following", ), + re_path(r"^hide-suggestions/?$", views.hide_suggestions, name="hide-suggestions"), # lists re_path(r"%s/lists/?$" % USER_PATH, views.UserLists.as_view(), name="user-lists"), re_path(r"^list/?$", views.Lists.as_view(), name="lists"), diff --git a/bookwyrm/views/__init__.py b/bookwyrm/views/__init__.py index 841026a54..5142d532d 100644 --- a/bookwyrm/views/__init__.py +++ b/bookwyrm/views/__init__.py @@ -43,6 +43,6 @@ from .shelf import shelve, unshelve from .site import Site from .status import CreateStatus, DeleteStatus, DeleteAndRedraft from .updates import get_notification_count, get_unread_status_count -from .user import User, Followers, Following +from .user import User, Followers, Following, hide_suggestions from .user_admin import UserAdmin, UserAdminList from .wellknown import * diff --git a/bookwyrm/views/user.py b/bookwyrm/views/user.py index 87e42b4e9..e335b02cb 100644 --- a/bookwyrm/views/user.py +++ b/bookwyrm/views/user.py @@ -1,8 +1,11 @@ """ non-interactive pages """ +from django.contrib.auth.decorators import login_required from django.core.paginator import Paginator +from django.shortcuts import redirect from django.template.response import TemplateResponse from django.utils import timezone from django.views import View +from django.views.decorators.http import require_POST from bookwyrm import models from bookwyrm.activitypub import ActivitypubResponse @@ -118,3 +121,12 @@ class Following(View): "follow_list": paginated.get_page(request.GET.get("page")), } return TemplateResponse(request, "user/relationships/following.html", data) + + +@require_POST +@login_required +def hide_suggestions(request): + """not everyone wants user suggestions""" + request.user.show_suggested_users = False + request.user.save(broadcast=False, update_fields=["show_suggested_users"]) + return redirect(request.headers.get("Referer", "/")) From 90800c190f4f068a403523f6452c10f333d8bbb9 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 8 Sep 2021 10:06:26 -0700 Subject: [PATCH 05/11] Show suggested users toggle in user prefs page --- bookwyrm/forms.py | 1 + bookwyrm/templates/preferences/edit_user.html | 20 ++++++++++--------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/bookwyrm/forms.py b/bookwyrm/forms.py index 690526050..96a4e3030 100644 --- a/bookwyrm/forms.py +++ b/bookwyrm/forms.py @@ -134,6 +134,7 @@ class EditUserForm(CustomForm): "email", "summary", "show_goal", + "show_suggested_users", "manually_approves_followers", "default_post_privacy", "discoverable", diff --git a/bookwyrm/templates/preferences/edit_user.html b/bookwyrm/templates/preferences/edit_user.html index 6732b8ef6..4dc792f9b 100644 --- a/bookwyrm/templates/preferences/edit_user.html +++ b/bookwyrm/templates/preferences/edit_user.html @@ -43,9 +43,19 @@
+ + + {% url 'directory' as path %} +

{% blocktrans %}Your account will show up in the directory, and may be recommended to other BookWyrm users.{% endblocktrans %}

-
- - {% url 'directory' as path %} -

{% blocktrans %}Your account will show up in the directory, and may be recommended to other BookWyrm users.{% endblocktrans %}

-
From b826d29b0d626d83653f4011946352e370fb5896 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 8 Sep 2021 10:20:44 -0700 Subject: [PATCH 06/11] Generate image task failing when transaction hasn't completed --- bookwyrm/models/book.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bookwyrm/models/book.py b/bookwyrm/models/book.py index 8bed69249..ec1c14ec0 100644 --- a/bookwyrm/models/book.py +++ b/bookwyrm/models/book.py @@ -4,6 +4,7 @@ import re from django.contrib.postgres.search import SearchVectorField from django.contrib.postgres.indexes import GinIndex from django.db import models +from django.db import transaction from django.dispatch import receiver from model_utils import FieldTracker from model_utils.managers import InheritanceManager @@ -361,4 +362,6 @@ def preview_image(instance, *args, **kwargs): changed_fields = instance.field_tracker.changed() if len(changed_fields) > 0: - generate_edition_preview_image_task.delay(instance.id) + transaction.on_commit( + lambda: generate_edition_preview_image_task.delay(instance.id) + ) From e06e507c8d3296d9e639ff0764c7d4135c53369a Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 8 Sep 2021 11:14:41 -0700 Subject: [PATCH 07/11] Cleans up suggested users logic --- bookwyrm/suggested_users.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/bookwyrm/suggested_users.py b/bookwyrm/suggested_users.py index 92902938a..6fbdee88d 100644 --- a/bookwyrm/suggested_users.py +++ b/bookwyrm/suggested_users.py @@ -43,7 +43,6 @@ class SuggestedUsers(RedisStore): ~Q(id=user.id), ~Q(followers=user), ~Q(follower_requests=user), - bookwyrm_user=True, ) def get_stores_for_object(self, obj): @@ -103,7 +102,9 @@ class SuggestedUsers(RedisStore): 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) + models.User.objects.filter( + discoverable=True, is_active=True, bookwyrm_user=True, *args, **kwargs + ) .exclude(Q(id__in=viewer.blocks.all()) | Q(blocks=viewer)) .annotate( mutuals=Count( @@ -178,15 +179,21 @@ def update_suggestions_on_unfollow(sender, instance, **kwargs): @receiver(signals.post_save, sender=models.User) # pylint: disable=unused-argument, too-many-arguments -def add_new_user(sender, instance, created, update_fields=None, **kwargs): - """a new user, wow how cool""" +def updated_user(sender, instance, created, update_fields=None, **kwargs): + """an updated user, neat""" # a new user is found, create suggestions for them if created and instance.local: rerank_suggestions_task.delay(instance.id) + # we know what fields were updated and discoverability didn't change if update_fields and not "discoverable" in update_fields: return + # deleted the user + if not instance.is_active: + remove_user_task.delay(instance.id) + return + # this happens on every save, not just when discoverability changes, annoyingly if instance.discoverable: rerank_user_task.delay(instance.id, update_only=False) From 2e2ee723339a434698b3f9910ec54d4d4fd386f7 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 8 Sep 2021 11:18:55 -0700 Subject: [PATCH 08/11] Fixes inactive or mastodon users showing up in suggestions They shouldn't be there, but just to be safe --- bookwyrm/suggested_users.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/bookwyrm/suggested_users.py b/bookwyrm/suggested_users.py index 6fbdee88d..cbd336590 100644 --- a/bookwyrm/suggested_users.py +++ b/bookwyrm/suggested_users.py @@ -85,10 +85,12 @@ class SuggestedUsers(RedisStore): 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]: + for user_id, rank in values: counts = self.get_counts_from_rank(rank) try: - user = models.User.objects.get(id=user_id) + user = models.User.objects.get( + id=user_id, is_active=True, bookwyrm_user=True + ) except models.User.DoesNotExist as err: # if this happens, the suggestions are janked way up logger.exception(err) @@ -96,6 +98,8 @@ class SuggestedUsers(RedisStore): user.mutuals = counts["mutuals"] # user.shared_books = counts["shared_books"] results.append(user) + if len(results) >= 5: + break return results @@ -179,14 +183,16 @@ def update_suggestions_on_unfollow(sender, instance, **kwargs): @receiver(signals.post_save, sender=models.User) # pylint: disable=unused-argument, too-many-arguments -def updated_user(sender, instance, created, update_fields=None, **kwargs): +def update_user(sender, instance, created, update_fields=None, **kwargs): """an updated user, neat""" # a new user is found, create suggestions for them if created and instance.local: rerank_suggestions_task.delay(instance.id) # we know what fields were updated and discoverability didn't change - if update_fields and not "discoverable" in update_fields: + if not instance.bookwyrm_user or ( + update_fields and not "discoverable" in update_fields + ): return # deleted the user @@ -201,6 +207,9 @@ def updated_user(sender, instance, created, update_fields=None, **kwargs): remove_user_task.delay(instance.id) +# ------------------- TASKS + + @app.task(queue="low_priority") def rerank_suggestions_task(user_id): """do the hard work in celery""" From d3b3dd6d996620643d4dd9a27427b1a4344440f1 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 8 Sep 2021 11:38:22 -0700 Subject: [PATCH 09/11] Update suggestions on domain block --- bookwyrm/models/federated_server.py | 4 ++-- bookwyrm/suggested_users.py | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/bookwyrm/models/federated_server.py b/bookwyrm/models/federated_server.py index e297c46c4..114f387b8 100644 --- a/bookwyrm/models/federated_server.py +++ b/bookwyrm/models/federated_server.py @@ -28,7 +28,7 @@ class FederatedServer(BookWyrmModel): def block(self): """block a server""" self.status = "blocked" - self.save() + self.save(update_fields="status") # deactivate all associated users self.user_set.filter(is_active=True).update( @@ -45,7 +45,7 @@ class FederatedServer(BookWyrmModel): def unblock(self): """unblock a server""" self.status = "federated" - self.save() + self.save(update_fields="status") self.user_set.filter(deactivation_reason="domain_block").update( is_active=True, deactivation_reason=None diff --git a/bookwyrm/suggested_users.py b/bookwyrm/suggested_users.py index cbd336590..953945d1f 100644 --- a/bookwyrm/suggested_users.py +++ b/bookwyrm/suggested_users.py @@ -207,6 +207,19 @@ def update_user(sender, instance, created, update_fields=None, **kwargs): remove_user_task.delay(instance.id) +@receiver(signals.post_save, sender=models.FederatedServer) +def domain_level_update(sender, instance, created, update_fields=None, **kwargs): + """remove users on a domain block""" + if not update_fields or "status" not in update_fields: + return + + userset = instance.user_set.values_list("id", flat=True) + if instance.status == "blocked": + bulk_remove_users_task.delay(userset) + return + bulk_add_users_task.delay(userset) + + # ------------------- TASKS @@ -235,3 +248,17 @@ 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) + + +@app.task(queue="low_priority") +def bulk_remove_users_task(user_ids): + """remove a bunch of users from recs""" + for user in models.User.objects.filter(id__in=user_ids): + suggested_users.remove_object_from_related_stores(user) + + +@app.task(queue="low_priority") +def bulk_add_users_task(user_ids): + """remove a bunch of users from recs""" + for user in models.User.objects.filter(id__in=user_ids): + suggested_users.rerank_obj(user, update_only=False) From 88a65b0b883aa0e0b5f9912a7d09467222759e8b Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 8 Sep 2021 11:47:36 -0700 Subject: [PATCH 10/11] Only re-rank on bookwyrm instances --- bookwyrm/models/federated_server.py | 4 ++-- bookwyrm/suggested_users.py | 19 +++++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/bookwyrm/models/federated_server.py b/bookwyrm/models/federated_server.py index 114f387b8..2be13b38b 100644 --- a/bookwyrm/models/federated_server.py +++ b/bookwyrm/models/federated_server.py @@ -28,7 +28,7 @@ class FederatedServer(BookWyrmModel): def block(self): """block a server""" self.status = "blocked" - self.save(update_fields="status") + self.save(update_fields=["status"]) # deactivate all associated users self.user_set.filter(is_active=True).update( @@ -45,7 +45,7 @@ class FederatedServer(BookWyrmModel): def unblock(self): """unblock a server""" self.status = "federated" - self.save(update_fields="status") + self.save(update_fields=["status"]) self.user_set.filter(deactivation_reason="domain_block").update( is_active=True, deactivation_reason=None diff --git a/bookwyrm/suggested_users.py b/bookwyrm/suggested_users.py index 953945d1f..c93077347 100644 --- a/bookwyrm/suggested_users.py +++ b/bookwyrm/suggested_users.py @@ -210,14 +210,17 @@ def update_user(sender, instance, created, update_fields=None, **kwargs): @receiver(signals.post_save, sender=models.FederatedServer) def domain_level_update(sender, instance, created, update_fields=None, **kwargs): """remove users on a domain block""" - if not update_fields or "status" not in update_fields: + if ( + not update_fields + or "status" not in update_fields + or instance.application_type != "bookwyrm" + ): return - userset = instance.user_set.values_list("id", flat=True) if instance.status == "blocked": - bulk_remove_users_task.delay(userset) + bulk_remove_instance_task.delay(instance.id) return - bulk_add_users_task.delay(userset) + bulk_add_instance_task.delay(instance.id) # ------------------- TASKS @@ -251,14 +254,14 @@ def remove_suggestion_task(user_id, suggested_user_id): @app.task(queue="low_priority") -def bulk_remove_users_task(user_ids): +def bulk_remove_instance_task(instance_id): """remove a bunch of users from recs""" - for user in models.User.objects.filter(id__in=user_ids): + for user in models.User.objects.filter(federated_server__id=instance_id): suggested_users.remove_object_from_related_stores(user) @app.task(queue="low_priority") -def bulk_add_users_task(user_ids): +def bulk_add_instance_task(instance_id): """remove a bunch of users from recs""" - for user in models.User.objects.filter(id__in=user_ids): + for user in models.User.objects.filter(federated_server__id=instance_id): suggested_users.rerank_obj(user, update_only=False) From 4db5677509286c19795a05b61d19f324efb33ae6 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 8 Sep 2021 12:06:23 -0700 Subject: [PATCH 11/11] Fixes filters --- bookwyrm/suggested_users.py | 7 +++---- bookwyrm/tests/views/test_federation.py | 10 ++++++++-- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/bookwyrm/suggested_users.py b/bookwyrm/suggested_users.py index c93077347..883430614 100644 --- a/bookwyrm/suggested_users.py +++ b/bookwyrm/suggested_users.py @@ -43,6 +43,7 @@ class SuggestedUsers(RedisStore): ~Q(id=user.id), ~Q(followers=user), ~Q(follower_requests=user), + bookwyrm_user=True, ) def get_stores_for_object(self, obj): @@ -106,9 +107,7 @@ class SuggestedUsers(RedisStore): 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, bookwyrm_user=True, *args, **kwargs - ) + models.User.objects.filter(discoverable=True, is_active=True, *args, **kwargs) .exclude(Q(id__in=viewer.blocks.all()) | Q(blocks=viewer)) .annotate( mutuals=Count( @@ -196,7 +195,7 @@ def update_user(sender, instance, created, update_fields=None, **kwargs): return # deleted the user - if not instance.is_active: + if not created and not instance.is_active: remove_user_task.delay(instance.id) return diff --git a/bookwyrm/tests/views/test_federation.py b/bookwyrm/tests/views/test_federation.py index ce5de7e33..493aaf93b 100644 --- a/bookwyrm/tests/views/test_federation.py +++ b/bookwyrm/tests/views/test_federation.py @@ -80,7 +80,9 @@ class FederationViews(TestCase): request.user = self.local_user request.user.is_superuser = True - view(request, server.id) + with patch("bookwyrm.suggested_users.bulk_remove_instance_task.delay") as mock: + view(request, server.id) + self.assertEqual(mock.call_count, 1) server.refresh_from_db() self.remote_user.refresh_from_db() @@ -118,7 +120,11 @@ class FederationViews(TestCase): request.user = self.local_user request.user.is_superuser = True - views.federation.unblock_server(request, server.id) + with patch("bookwyrm.suggested_users.bulk_add_instance_task.delay") as mock: + views.federation.unblock_server(request, server.id) + self.assertEqual(mock.call_count, 1) + self.assertEqual(mock.call_args[0][0], server.id) + server.refresh_from_db() self.remote_user.refresh_from_db() self.assertEqual(server.status, "federated")