From ebcacfc6c50658f39fcc5a50ea72e6c67ae42c9c Mon Sep 17 00:00:00 2001 From: Levi Bard Date: Thu, 9 Nov 2023 12:57:45 +0100 Subject: [PATCH 1/6] Fix `bw-dev initdb` --- bw-dev | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bw-dev b/bw-dev index 57441f013..6769f4bcd 100755 --- a/bw-dev +++ b/bw-dev @@ -91,7 +91,7 @@ case "$CMD" in $DOCKER_COMPOSE run --rm --service-ports web ;; initdb) - initdb "@" + initdb "$@" ;; resetdb) prod_error From 99a9dbe5f4134b76f5e90da7df25f1b0e7210f16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adeodato=20Sim=C3=B3?= Date: Thu, 9 Nov 2023 21:36:05 -0300 Subject: [PATCH 2/6] Create NotificationType as class, not through API This way, we need not list every value again to create the enum. N.B.: enum values are now accessed as `models.NotificationType.FOO`, instead of `models.Notification.FOO`. --- bookwyrm/models/__init__.py | 2 +- bookwyrm/models/antispam.py | 3 +- bookwyrm/models/group.py | 13 +++---- bookwyrm/models/move.py | 5 ++- bookwyrm/models/notification.py | 42 +++++++++++----------- bookwyrm/tests/models/test_notification.py | 24 ++++++------- bookwyrm/views/group.py | 14 +++++--- 7 files changed, 53 insertions(+), 50 deletions(-) diff --git a/bookwyrm/models/__init__.py b/bookwyrm/models/__init__.py index c455c751f..e1c862f2b 100644 --- a/bookwyrm/models/__init__.py +++ b/bookwyrm/models/__init__.py @@ -34,7 +34,7 @@ from .site import PasswordReset, InviteRequest from .announcement import Announcement from .antispam import EmailBlocklist, IPBlocklist, AutoMod, automod_task -from .notification import Notification +from .notification import Notification, NotificationType from .hashtag import Hashtag diff --git a/bookwyrm/models/antispam.py b/bookwyrm/models/antispam.py index 94d978ec4..1067cbf1d 100644 --- a/bookwyrm/models/antispam.py +++ b/bookwyrm/models/antispam.py @@ -10,6 +10,7 @@ from django.utils.translation import gettext_lazy as _ from bookwyrm.tasks import app, MISC from .base_model import BookWyrmModel +from .notification import NotificationType from .user import User @@ -80,7 +81,7 @@ def automod_task(): with transaction.atomic(): for admin in admins: notification, _ = notification_model.objects.get_or_create( - user=admin, notification_type=notification_model.REPORT, read=False + user=admin, notification_type=NotificationType.REPORT, read=False ) notification.related_reports.set(reports) diff --git a/bookwyrm/models/group.py b/bookwyrm/models/group.py index 003b23d02..d02b56ab1 100644 --- a/bookwyrm/models/group.py +++ b/bookwyrm/models/group.py @@ -1,5 +1,4 @@ """ do book related things with other users """ -from django.apps import apps from django.db import models, IntegrityError, transaction from django.db.models import Q from bookwyrm.settings import DOMAIN @@ -143,26 +142,28 @@ class GroupMemberInvitation(models.Model): @transaction.atomic def accept(self): """turn this request into the real deal""" + # pylint: disable-next=import-outside-toplevel + from .notification import Notification, NotificationType # circular dependency + GroupMember.from_request(self) - model = apps.get_model("bookwyrm.Notification", require_ready=True) # tell the group owner - model.notify( + Notification.notify( self.group.user, self.user, related_group=self.group, - notification_type=model.ACCEPT, + notification_type=NotificationType.ACCEPT, ) # let the other members know about it for membership in self.group.memberships.all(): member = membership.user if member not in (self.user, self.group.user): - model.notify( + Notification.notify( member, self.user, related_group=self.group, - notification_type=model.JOIN, + notification_type=NotificationType.JOIN, ) def reject(self): diff --git a/bookwyrm/models/move.py b/bookwyrm/models/move.py index a5bf9d76d..d6d8ef78f 100644 --- a/bookwyrm/models/move.py +++ b/bookwyrm/models/move.py @@ -6,7 +6,7 @@ from bookwyrm import activitypub from .activitypub_mixin import ActivityMixin from .base_model import BookWyrmModel from . import fields -from .notification import Notification +from .notification import Notification, NotificationType class Move(ActivityMixin, BookWyrmModel): @@ -49,7 +49,6 @@ class MoveUser(Move): # only allow if the source is listed in the target's alsoKnownAs if self.user in self.target.also_known_as.all(): - self.user.also_known_as.add(self.target.id) self.user.update_active_date() self.user.moved_to = self.target.remote_id @@ -65,7 +64,7 @@ class MoveUser(Move): for follower in self.user.followers.all(): if follower.local: Notification.notify( - follower, self.user, notification_type=Notification.MOVE + follower, self.user, notification_type=NotificationType.MOVE ) else: diff --git a/bookwyrm/models/notification.py b/bookwyrm/models/notification.py index 093c25c65..46b88f5e5 100644 --- a/bookwyrm/models/notification.py +++ b/bookwyrm/models/notification.py @@ -6,7 +6,7 @@ from . import Boost, Favorite, GroupMemberInvitation, ImportJob, LinkDomain from . import ListItem, Report, Status, User, UserFollowRequest -class Notification(BookWyrmModel): +class NotificationType(models.TextChoices): """you've been tagged, liked, followed, etc""" # Status interactions @@ -43,12 +43,9 @@ class Notification(BookWyrmModel): # Migrations MOVE = "MOVE" - # pylint: disable=line-too-long - NotificationType = models.TextChoices( - # there has got be a better way to do this - "NotificationType", - f"{FAVORITE} {REPLY} {MENTION} {TAG} {FOLLOW} {FOLLOW_REQUEST} {BOOST} {IMPORT} {ADD} {REPORT} {LINK_DOMAIN} {INVITE} {ACCEPT} {JOIN} {LEAVE} {REMOVE} {GROUP_PRIVACY} {GROUP_NAME} {GROUP_DESCRIPTION} {MOVE}", - ) + +class Notification(BookWyrmModel): + """a notification object""" user = models.ForeignKey("User", on_delete=models.CASCADE) read = models.BooleanField(default=False) @@ -93,11 +90,11 @@ class Notification(BookWyrmModel): user=user, related_users=related_user, related_list_items__book_list=list_item.book_list, - notification_type=Notification.ADD, + notification_type=NotificationType.ADD, ).first() if not notification: notification = cls.objects.create( - user=user, notification_type=Notification.ADD + user=user, notification_type=NotificationType.ADD ) notification.related_users.add(related_user) notification.related_list_items.add(list_item) @@ -124,7 +121,7 @@ def notify_on_fav(sender, instance, *args, **kwargs): instance.status.user, instance.user, related_status=instance.status, - notification_type=Notification.FAVORITE, + notification_type=NotificationType.FAVORITE, ) @@ -138,7 +135,7 @@ def notify_on_unfav(sender, instance, *args, **kwargs): instance.status.user, instance.user, related_status=instance.status, - notification_type=Notification.FAVORITE, + notification_type=NotificationType.FAVORITE, ) @@ -163,7 +160,7 @@ def notify_user_on_mention(sender, instance, *args, **kwargs): instance.reply_parent.user, instance.user, related_status=instance, - notification_type=Notification.REPLY, + notification_type=NotificationType.REPLY, ) for mention_user in instance.mention_users.all(): @@ -175,7 +172,7 @@ def notify_user_on_mention(sender, instance, *args, **kwargs): Notification.notify( mention_user, instance.user, - notification_type=Notification.MENTION, + notification_type=NotificationType.MENTION, related_status=instance, ) @@ -194,7 +191,7 @@ def notify_user_on_boost(sender, instance, *args, **kwargs): instance.boosted_status.user, instance.user, related_status=instance.boosted_status, - notification_type=Notification.BOOST, + notification_type=NotificationType.BOOST, ) @@ -206,7 +203,7 @@ def notify_user_on_unboost(sender, instance, *args, **kwargs): instance.boosted_status.user, instance.user, related_status=instance.boosted_status, - notification_type=Notification.BOOST, + notification_type=NotificationType.BOOST, ) @@ -221,7 +218,7 @@ def notify_user_on_import_complete( return Notification.objects.get_or_create( user=instance.user, - notification_type=Notification.IMPORT, + notification_type=NotificationType.IMPORT, related_import=instance, ) @@ -240,7 +237,7 @@ def notify_admins_on_report(sender, instance, created, *args, **kwargs): for admin in admins: notification, _ = Notification.objects.get_or_create( user=admin, - notification_type=Notification.REPORT, + notification_type=NotificationType.REPORT, read=False, ) notification.related_reports.add(instance) @@ -260,7 +257,7 @@ def notify_admins_on_link_domain(sender, instance, created, *args, **kwargs): for admin in admins: notification, _ = Notification.objects.get_or_create( user=admin, - notification_type=Notification.LINK_DOMAIN, + notification_type=NotificationType.LINK_DOMAIN, read=False, ) notification.related_link_domains.add(instance) @@ -274,7 +271,7 @@ def notify_user_on_group_invite(sender, instance, *args, **kwargs): instance.user, instance.group.user, related_group=instance.group, - notification_type=Notification.INVITE, + notification_type=NotificationType.INVITE, ) @@ -312,11 +309,12 @@ def notify_user_on_follow(sender, instance, created, *args, **kwargs): notification = Notification.objects.filter( user=instance.user_object, related_users=instance.user_subject, - notification_type=Notification.FOLLOW_REQUEST, + notification_type=NotificationType.FOLLOW_REQUEST, ).first() if not notification: notification = Notification.objects.create( - user=instance.user_object, notification_type=Notification.FOLLOW_REQUEST + user=instance.user_object, + notification_type=NotificationType.FOLLOW_REQUEST, ) notification.related_users.set([instance.user_subject]) notification.read = False @@ -326,6 +324,6 @@ def notify_user_on_follow(sender, instance, created, *args, **kwargs): Notification.notify( instance.user_object, instance.user_subject, - notification_type=Notification.FOLLOW, + notification_type=NotificationType.FOLLOW, read=False, ) diff --git a/bookwyrm/tests/models/test_notification.py b/bookwyrm/tests/models/test_notification.py index 0e4fe91c7..1c412e1b4 100644 --- a/bookwyrm/tests/models/test_notification.py +++ b/bookwyrm/tests/models/test_notification.py @@ -43,7 +43,7 @@ class Notification(TestCase): def test_notification(self): """New notifications are unread""" notification = models.Notification.objects.create( - user=self.local_user, notification_type=models.Notification.FAVORITE + user=self.local_user, notification_type=models.NotificationType.FAVORITE ) self.assertFalse(notification.read) @@ -52,7 +52,7 @@ class Notification(TestCase): models.Notification.notify( self.local_user, self.remote_user, - notification_type=models.Notification.FAVORITE, + notification_type=models.NotificationType.FAVORITE, ) self.assertTrue(models.Notification.objects.exists()) @@ -61,7 +61,7 @@ class Notification(TestCase): models.Notification.notify( self.local_user, self.remote_user, - notification_type=models.Notification.FAVORITE, + notification_type=models.NotificationType.FAVORITE, ) self.assertEqual(models.Notification.objects.count(), 1) notification = models.Notification.objects.get() @@ -70,7 +70,7 @@ class Notification(TestCase): models.Notification.notify( self.local_user, self.another_user, - notification_type=models.Notification.FAVORITE, + notification_type=models.NotificationType.FAVORITE, ) self.assertEqual(models.Notification.objects.count(), 1) notification.refresh_from_db() @@ -92,7 +92,7 @@ class Notification(TestCase): models.Notification.notify( self.remote_user, self.local_user, - notification_type=models.Notification.FAVORITE, + notification_type=models.NotificationType.FAVORITE, ) self.assertFalse(models.Notification.objects.exists()) @@ -101,7 +101,7 @@ class Notification(TestCase): models.Notification.notify( self.local_user, self.local_user, - notification_type=models.Notification.FAVORITE, + notification_type=models.NotificationType.FAVORITE, ) self.assertFalse(models.Notification.objects.exists()) @@ -154,14 +154,14 @@ class Notification(TestCase): models.Notification.notify( self.local_user, self.remote_user, - notification_type=models.Notification.FAVORITE, + notification_type=models.NotificationType.FAVORITE, ) self.assertTrue(models.Notification.objects.exists()) models.Notification.unnotify( self.local_user, self.remote_user, - notification_type=models.Notification.FAVORITE, + notification_type=models.NotificationType.FAVORITE, ) self.assertFalse(models.Notification.objects.exists()) @@ -170,25 +170,25 @@ class Notification(TestCase): models.Notification.notify( self.local_user, self.remote_user, - notification_type=models.Notification.FAVORITE, + notification_type=models.NotificationType.FAVORITE, ) models.Notification.notify( self.local_user, self.another_user, - notification_type=models.Notification.FAVORITE, + notification_type=models.NotificationType.FAVORITE, ) self.assertTrue(models.Notification.objects.exists()) models.Notification.unnotify( self.local_user, self.remote_user, - notification_type=models.Notification.FAVORITE, + notification_type=models.NotificationType.FAVORITE, ) self.assertTrue(models.Notification.objects.exists()) models.Notification.unnotify( self.local_user, self.another_user, - notification_type=models.Notification.FAVORITE, + notification_type=models.NotificationType.FAVORITE, ) self.assertFalse(models.Notification.objects.exists()) diff --git a/bookwyrm/views/group.py b/bookwyrm/views/group.py index 1ccfd6849..5ac4954e8 100644 --- a/bookwyrm/views/group.py +++ b/bookwyrm/views/group.py @@ -13,9 +13,11 @@ from django.contrib.postgres.search import TrigramSimilarity from django.db.models.functions import Greatest from bookwyrm import forms, models +from bookwyrm.models import NotificationType from bookwyrm.suggested_users import suggested_users from .helpers import get_user_from_username, maybe_redirect_local_path + # pylint: disable=no-self-use class Group(View): """group page""" @@ -59,11 +61,11 @@ class Group(View): model = apps.get_model("bookwyrm.Notification", require_ready=True) for field in form.changed_data: notification_type = ( - model.GROUP_PRIVACY + NotificationType.GROUP_PRIVACY if field == "privacy" - else model.GROUP_NAME + else NotificationType.GROUP_NAME if field == "name" - else model.GROUP_DESCRIPTION + else NotificationType.GROUP_DESCRIPTION if field == "description" else None ) @@ -251,7 +253,9 @@ def remove_member(request): memberships = models.GroupMember.objects.filter(group=group) model = apps.get_model("bookwyrm.Notification", require_ready=True) - notification_type = model.LEAVE if user == request.user else model.REMOVE + notification_type = ( + NotificationType.LEAVE if user == request.user else NotificationType.REMOVE + ) # let the other members know about it for membership in memberships: member = membership.user @@ -264,7 +268,7 @@ def remove_member(request): ) # let the user (now ex-member) know as well, if they were removed - if notification_type == model.REMOVE: + if notification_type == NotificationType.REMOVE: model.notify( user, None, related_group=group, notification_type=notification_type ) From a884825b3c264edb2a302397881a00e13a49cc7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adeodato=20Sim=C3=B3?= Date: Mon, 13 Nov 2023 19:40:43 -0300 Subject: [PATCH 3/6] Check no missing migrations in django-tests workflow --- .github/workflows/django-tests.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/django-tests.yml b/.github/workflows/django-tests.yml index da11fe09e..78b6e142e 100644 --- a/.github/workflows/django-tests.yml +++ b/.github/workflows/django-tests.yml @@ -32,6 +32,15 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt + - name: Check migrations up-to-date + run: | + python ./manage.py makemigrations --check + env: + SECRET_KEY: beepbeep + DOMAIN: your.domain.here + EMAIL_HOST: "" + EMAIL_HOST_USER: "" + EMAIL_HOST_PASSWORD: "" - name: Run Tests env: SECRET_KEY: beepbeep From b81170c14983c565175a4851434e9d0b55c4737d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adeodato=20Sim=C3=B3?= Date: Mon, 13 Nov 2023 19:45:26 -0300 Subject: [PATCH 4/6] Add missing migration from #3099 --- ...85_alter_notification_notification_type.py | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 bookwyrm/migrations/0185_alter_notification_notification_type.py diff --git a/bookwyrm/migrations/0185_alter_notification_notification_type.py b/bookwyrm/migrations/0185_alter_notification_notification_type.py new file mode 100644 index 000000000..dd070c634 --- /dev/null +++ b/bookwyrm/migrations/0185_alter_notification_notification_type.py @@ -0,0 +1,42 @@ +# Generated by Django 3.2.20 on 2023-11-13 22:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0184_auto_20231106_0421"), + ] + + operations = [ + migrations.AlterField( + model_name="notification", + name="notification_type", + field=models.CharField( + choices=[ + ("FAVORITE", "Favorite"), + ("BOOST", "Boost"), + ("REPLY", "Reply"), + ("MENTION", "Mention"), + ("TAG", "Tag"), + ("FOLLOW", "Follow"), + ("FOLLOW_REQUEST", "Follow Request"), + ("IMPORT", "Import"), + ("ADD", "Add"), + ("REPORT", "Report"), + ("LINK_DOMAIN", "Link Domain"), + ("INVITE", "Invite"), + ("ACCEPT", "Accept"), + ("JOIN", "Join"), + ("LEAVE", "Leave"), + ("REMOVE", "Remove"), + ("GROUP_PRIVACY", "Group Privacy"), + ("GROUP_NAME", "Group Name"), + ("GROUP_DESCRIPTION", "Group Description"), + ("MOVE", "Move"), + ], + max_length=255, + ), + ), + ] From 01d43818981b9aa6bb8a755b51c7a4a51dc08bc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adeodato=20Sim=C3=B3?= Date: Sat, 11 Nov 2023 03:52:05 -0300 Subject: [PATCH 5/6] Create notifications for incoming invite requests Closes: #2066 --- .../0186_invite_request_notification.py | 48 ++++++++++ bookwyrm/models/notification.py | 31 +++++-- bookwyrm/templates/notifications/item.html | 2 + .../notifications/items/invite_request.html | 20 +++++ bookwyrm/tests/models/test_notification.py | 87 +++++++++++++++++++ 5 files changed, 182 insertions(+), 6 deletions(-) create mode 100644 bookwyrm/migrations/0186_invite_request_notification.py create mode 100644 bookwyrm/templates/notifications/items/invite_request.html diff --git a/bookwyrm/migrations/0186_invite_request_notification.py b/bookwyrm/migrations/0186_invite_request_notification.py new file mode 100644 index 000000000..3680b1de7 --- /dev/null +++ b/bookwyrm/migrations/0186_invite_request_notification.py @@ -0,0 +1,48 @@ +# Generated by Django 3.2.20 on 2023-11-14 10:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0185_alter_notification_notification_type"), + ] + + operations = [ + migrations.AddField( + model_name="notification", + name="related_invite_requests", + field=models.ManyToManyField(to="bookwyrm.InviteRequest"), + ), + migrations.AlterField( + model_name="notification", + name="notification_type", + field=models.CharField( + choices=[ + ("FAVORITE", "Favorite"), + ("BOOST", "Boost"), + ("REPLY", "Reply"), + ("MENTION", "Mention"), + ("TAG", "Tag"), + ("FOLLOW", "Follow"), + ("FOLLOW_REQUEST", "Follow Request"), + ("IMPORT", "Import"), + ("ADD", "Add"), + ("REPORT", "Report"), + ("LINK_DOMAIN", "Link Domain"), + ("INVITE_REQUEST", "Invite Request"), + ("INVITE", "Invite"), + ("ACCEPT", "Accept"), + ("JOIN", "Join"), + ("LEAVE", "Leave"), + ("REMOVE", "Remove"), + ("GROUP_PRIVACY", "Group Privacy"), + ("GROUP_NAME", "Group Name"), + ("GROUP_DESCRIPTION", "Group Description"), + ("MOVE", "Move"), + ], + max_length=255, + ), + ), + ] diff --git a/bookwyrm/models/notification.py b/bookwyrm/models/notification.py index 46b88f5e5..d056c05b3 100644 --- a/bookwyrm/models/notification.py +++ b/bookwyrm/models/notification.py @@ -4,6 +4,7 @@ from django.dispatch import receiver from .base_model import BookWyrmModel from . import Boost, Favorite, GroupMemberInvitation, ImportJob, LinkDomain from . import ListItem, Report, Status, User, UserFollowRequest +from .site import InviteRequest class NotificationType(models.TextChoices): @@ -29,6 +30,7 @@ class NotificationType(models.TextChoices): # Admin REPORT = "REPORT" LINK_DOMAIN = "LINK_DOMAIN" + INVITE_REQUEST = "INVITE_REQUEST" # Groups INVITE = "INVITE" @@ -64,8 +66,9 @@ class Notification(BookWyrmModel): related_list_items = models.ManyToManyField( "ListItem", symmetrical=False, related_name="notifications" ) - related_reports = models.ManyToManyField("Report", symmetrical=False) - related_link_domains = models.ManyToManyField("LinkDomain", symmetrical=False) + related_reports = models.ManyToManyField("Report") + related_link_domains = models.ManyToManyField("LinkDomain") + related_invite_requests = models.ManyToManyField("InviteRequest") @classmethod @transaction.atomic @@ -233,8 +236,7 @@ def notify_admins_on_report(sender, instance, created, *args, **kwargs): return # moderators and superusers should be notified - admins = User.admins() - for admin in admins: + for admin in User.admins(): notification, _ = Notification.objects.get_or_create( user=admin, notification_type=NotificationType.REPORT, @@ -253,8 +255,7 @@ def notify_admins_on_link_domain(sender, instance, created, *args, **kwargs): return # moderators and superusers should be notified - admins = User.admins() - for admin in admins: + for admin in User.admins(): notification, _ = Notification.objects.get_or_create( user=admin, notification_type=NotificationType.LINK_DOMAIN, @@ -263,6 +264,24 @@ def notify_admins_on_link_domain(sender, instance, created, *args, **kwargs): notification.related_link_domains.add(instance) +@receiver(models.signals.post_save, sender=InviteRequest) +@transaction.atomic +# pylint: disable=unused-argument +def notify_admins_on_invite_request(sender, instance, created, *args, **kwargs): + """need to handle a new invite request""" + if not created: + return + + # moderators and superusers should be notified + for admin in User.admins(): + notification, _ = Notification.objects.get_or_create( + user=admin, + notification_type=NotificationType.INVITE_REQUEST, + read=False, + ) + notification.related_invite_requests.add(instance) + + @receiver(models.signals.post_save, sender=GroupMemberInvitation) # pylint: disable=unused-argument def notify_user_on_group_invite(sender, instance, *args, **kwargs): diff --git a/bookwyrm/templates/notifications/item.html b/bookwyrm/templates/notifications/item.html index 7e7f0da27..a69790f52 100644 --- a/bookwyrm/templates/notifications/item.html +++ b/bookwyrm/templates/notifications/item.html @@ -21,6 +21,8 @@ {% include 'notifications/items/report.html' %} {% elif notification.notification_type == 'LINK_DOMAIN' %} {% include 'notifications/items/link_domain.html' %} +{% elif notification.notification_type == 'INVITE_REQUEST' %} + {% include 'notifications/items/invite_request.html' %} {% elif notification.notification_type == 'INVITE' %} {% include 'notifications/items/invite.html' %} {% elif notification.notification_type == 'ACCEPT' %} diff --git a/bookwyrm/templates/notifications/items/invite_request.html b/bookwyrm/templates/notifications/items/invite_request.html new file mode 100644 index 000000000..acc08d5d0 --- /dev/null +++ b/bookwyrm/templates/notifications/items/invite_request.html @@ -0,0 +1,20 @@ +{% extends 'notifications/items/layout.html' %} +{% load humanize %} +{% load i18n %} + +{% block primary_link %}{% spaceless %} +{% url 'settings-invite-requests' %} +{% endspaceless %}{% endblock %} + +{% block icon %} + +{% endblock %} + +{% block description %} + {% url 'settings-invite-requests' as path %} + {% blocktrans trimmed count counter=notification.related_invite_requests.count with display_count=notification.related_invite_requests.count|intcomma %} + New invite request awaiting response + {% plural %} + {{ display_count }} new invite requests awaiting response + {% endblocktrans %} +{% endblock %} diff --git a/bookwyrm/tests/models/test_notification.py b/bookwyrm/tests/models/test_notification.py index 1c412e1b4..352b7631d 100644 --- a/bookwyrm/tests/models/test_notification.py +++ b/bookwyrm/tests/models/test_notification.py @@ -192,3 +192,90 @@ class Notification(TestCase): notification_type=models.NotificationType.FAVORITE, ) self.assertFalse(models.Notification.objects.exists()) + + +class NotifyInviteRequest(TestCase): + """let admins know of invite requests""" + + def setUp(self): + """ensure there is one admin""" + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch( + "bookwyrm.activitystreams.populate_stream_task.delay" + ), patch("bookwyrm.lists_stream.populate_lists_task.delay"): + self.local_user = models.User.objects.create_user( + "mouse@local.com", + "mouse@mouse.mouse", + "password", + local=True, + localname="mouse", + is_superuser=True, + ) + + def test_invite_request_triggers_notification(self): + """requesting an invite notifies the admin""" + admin = models.User.objects.filter(is_superuser=True).first() + request = models.InviteRequest.objects.create(email="user@example.com") + + self.assertEqual(models.Notification.objects.count(), 1) + + notification = models.Notification.objects.first() + self.assertEqual(notification.user, admin) + self.assertEqual( + notification.notification_type, models.NotificationType.INVITE_REQUEST + ) + self.assertEqual(notification.related_invite_requests.count(), 1) + self.assertEqual(notification.related_invite_requests.first(), request) + + def test_notify_only_created(self): + """updating an invite request does not trigger a notification""" + request = models.InviteRequest.objects.create(email="user@example.com") + notification = models.Notification.objects.first() + + notification.delete() + self.assertEqual(models.Notification.objects.count(), 0) + + request.ignored = True + request.save() + self.assertEqual(models.Notification.objects.count(), 0) + + def test_notify_grouping(self): + """invites group into the same notification, until read""" + requests = [ + models.InviteRequest.objects.create(email="user1@example.com"), + models.InviteRequest.objects.create(email="user2@example.com"), + ] + self.assertEqual(models.Notification.objects.count(), 1) + + notification = models.Notification.objects.first() + self.assertEqual(notification.related_invite_requests.count(), 2) + self.assertCountEqual(notification.related_invite_requests.all(), requests) + + notification.read = True + notification.save() + + request = models.InviteRequest.objects.create(email="user3@example.com") + _, notification = models.Notification.objects.all() + + self.assertEqual(models.Notification.objects.count(), 2) + self.assertEqual(notification.related_invite_requests.count(), 1) + self.assertEqual(notification.related_invite_requests.first(), request) + + def test_notify_multiple_admins(self): + """all admins are notified""" + with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch( + "bookwyrm.activitystreams.populate_stream_task.delay" + ), patch("bookwyrm.lists_stream.populate_lists_task.delay"): + self.local_user = models.User.objects.create_user( + "admin@local.com", + "admin@example.com", + "password", + local=True, + localname="root", + is_superuser=True, + ) + models.InviteRequest.objects.create(email="user@example.com") + admins = models.User.objects.filter(is_superuser=True).all() + notifications = models.Notification.objects.all() + + self.assertEqual(len(notifications), 2) + self.assertCountEqual([notif.user for notif in notifications], admins) From 63530294d4d240453276a4854f9c52bbd0e670a7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Nov 2023 23:35:55 +0000 Subject: [PATCH 6/6] Bump aiohttp from 3.8.5 to 3.8.6 Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.8.5 to 3.8.6. - [Release notes](https://github.com/aio-libs/aiohttp/releases) - [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst) - [Commits](https://github.com/aio-libs/aiohttp/compare/v3.8.5...v3.8.6) --- updated-dependencies: - dependency-name: aiohttp dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b01e110bf..36192f148 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -aiohttp==3.8.5 +aiohttp==3.8.6 bleach==5.0.1 celery==5.2.7 colorthief==0.2.1