Merge branch 'main' into production

This commit is contained in:
Mouse Reeve 2021-09-08 12:18:35 -07:00
commit 2022b3a035
14 changed files with 126 additions and 25 deletions

View file

@ -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)

View file

@ -134,6 +134,7 @@ class EditUserForm(CustomForm):
"email",
"summary",
"show_goal",
"show_suggested_users",
"manually_approves_followers",
"default_post_privacy",
"discoverable",

View file

@ -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),
),
]

View file

@ -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)
)

View file

@ -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

View file

@ -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),

View file

@ -86,10 +86,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)
@ -97,6 +99,8 @@ class SuggestedUsers(RedisStore):
user.mutuals = counts["mutuals"]
# user.shared_books = counts["shared_books"]
results.append(user)
if len(results) >= 5:
break
return results
@ -178,13 +182,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 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)
if update_fields and not "discoverable" in update_fields:
# we know what fields were updated and discoverability didn't change
if not instance.bookwyrm_user or (
update_fields and not "discoverable" in update_fields
):
return
# deleted the user
if not created and not instance.is_active:
remove_user_task.delay(instance.id)
return
# this happens on every save, not just when discoverability changes, annoyingly
@ -194,6 +206,25 @@ def add_new_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
or instance.application_type != "bookwyrm"
):
return
if instance.status == "blocked":
bulk_remove_instance_task.delay(instance.id)
return
bulk_add_instance_task.delay(instance.id)
# ------------------- TASKS
@app.task(queue="low_priority")
def rerank_suggestions_task(user_id):
"""do the hard work in celery"""
@ -219,3 +250,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_instance_task(instance_id):
"""remove a bunch of users from recs"""
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_instance_task(instance_id):
"""remove a bunch of users from recs"""
for user in models.User.objects.filter(federated_server__id=instance_id):
suggested_users.rerank_obj(user, update_only=False)

View file

@ -22,7 +22,7 @@
{% blocktrans with tab_key=tab.key %}load <span data-poll="stream/{{ tab_key }}">0</span> unread status(es){% endblocktrans %}
</a>
{% 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 %}
<section class="block">
{% include 'snippets/goal_card.html' with year=year %}
@ -37,7 +37,7 @@
<div class="block content">
<p>{% trans "There aren't any activities right now! Try following a user to get started" %}</p>
{% 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 %}

View file

@ -1,6 +1,15 @@
{% load i18n %}
<section class="block">
<h2 class="title is-5">{% trans "Who to follow" %}</h2>
<header class="columns">
<div class="column">
<h2 class="title is-5">{% trans "Who to follow" %}</h2>
</div>
<form class="column is-narrow" action="{% url 'hide-suggestions' %}" method="POST">
{% csrf_token %}
{% trans "Don't show suggested users" as button_text %}
<button type="submit" class="delete" title="{{ button_text }}">{{ button_text }}</button>
</form>
</header>
{% include 'snippets/suggested_users.html' with suggested_users=suggested_users %}
<a class="help" href="{% url 'directory' %}">{% trans "View directory" %} <span class="icon icon-arrow-right"></span></a>
</section>

View file

@ -43,9 +43,19 @@
</div>
<div class="block">
<label class="checkbox label" for="id_show_goal">
{% trans "Show set reading goal prompt in feed:" %}
{% trans "Show reading goal prompt in feed:" %}
{{ form.show_goal }}
</label>
<label class="checkbox label" for="id_show_goal">
{% trans "Show suggested users:" %}
{{ form.show_suggested_users }}
</label>
<label class="checkbox label" for="id_discoverable">
{% trans "Show this account in suggested users:" %}
{{ form.discoverable }}
</label>
{% url 'directory' as path %}
<p class="help">{% blocktrans %}Your account will show up in the <a href="{{ path }}">directory</a>, and may be recommended to other BookWyrm users.{% endblocktrans %}</p>
</div>
<div class="block">
<label class="checkbox label" for="id_manually_approves_followers">
@ -61,14 +71,6 @@
{{ form.default_post_privacy }}
</div>
</div>
<div class="block">
<label class="checkbox label" for="id_discoverable">
{% trans "Show this account in suggested users:" %}
{{ form.discoverable }}
</label>
{% url 'directory' as path %}
<p class="help">{% blocktrans %}Your account will show up in the <a href="{{ path }}">directory</a>, and may be recommended to other BookWyrm users.{% endblocktrans %}</p>
</div>
<div class="block">
<label class="label" for="id_preferred_timezone">{% trans "Preferred Timezone: " %}</label>
<div class="select">

View file

@ -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")

View file

@ -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"),

View file

@ -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 *

View file

@ -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", "/"))