From 8d266fda4dca59ac106f097d8f4a15fdd1c9ec16 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Fri, 8 Apr 2022 15:21:06 -0700 Subject: [PATCH 01/40] Removes unused `related_book` field on notification model --- .../0149_remove_notification_related_book.py | 17 +++++++++++++++++ bookwyrm/models/notification.py | 2 -- bookwyrm/views/notifications.py | 1 - 3 files changed, 17 insertions(+), 3 deletions(-) create mode 100644 bookwyrm/migrations/0149_remove_notification_related_book.py diff --git a/bookwyrm/migrations/0149_remove_notification_related_book.py b/bookwyrm/migrations/0149_remove_notification_related_book.py new file mode 100644 index 0000000000..c976af6c90 --- /dev/null +++ b/bookwyrm/migrations/0149_remove_notification_related_book.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.12 on 2022-04-08 22:20 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0148_alter_user_preferred_language"), + ] + + operations = [ + migrations.RemoveField( + model_name="notification", + name="related_book", + ), + ] diff --git a/bookwyrm/models/notification.py b/bookwyrm/models/notification.py index 417bf7591a..28c5b803ef 100644 --- a/bookwyrm/models/notification.py +++ b/bookwyrm/models/notification.py @@ -15,7 +15,6 @@ class Notification(BookWyrmModel): """you've been tagged, liked, followed, etc""" user = models.ForeignKey("User", on_delete=models.CASCADE) - related_book = models.ForeignKey("Edition", on_delete=models.CASCADE, null=True) related_user = models.ForeignKey( "User", on_delete=models.CASCADE, null=True, related_name="related_user" ) @@ -38,7 +37,6 @@ def save(self, *args, **kwargs): # there's probably a better way to do this if self.__class__.objects.filter( user=self.user, - related_book=self.related_book, related_user=self.related_user, related_group=self.related_group, related_status=self.related_status, diff --git a/bookwyrm/views/notifications.py b/bookwyrm/views/notifications.py index 0a7a62002d..e081b07c3d 100644 --- a/bookwyrm/views/notifications.py +++ b/bookwyrm/views/notifications.py @@ -22,7 +22,6 @@ def get(self, request, notification_type=None): "related_import", "related_report", "related_user", - "related_book", "related_list_item", "related_list_item__book", ) From a2a04da49356165e53cbcd557ad2b9ad2a1e75e1 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Sat, 9 Apr 2022 09:41:10 -0700 Subject: [PATCH 02/40] Adds many to many related items to notifications --- .../migrations/0150_auto_20220408_2236.py | 128 ++++++++++++++++++ bookwyrm/models/notification.py | 41 ++---- 2 files changed, 142 insertions(+), 27 deletions(-) create mode 100644 bookwyrm/migrations/0150_auto_20220408_2236.py diff --git a/bookwyrm/migrations/0150_auto_20220408_2236.py b/bookwyrm/migrations/0150_auto_20220408_2236.py new file mode 100644 index 0000000000..bf1c304878 --- /dev/null +++ b/bookwyrm/migrations/0150_auto_20220408_2236.py @@ -0,0 +1,128 @@ +# Generated by Django 3.2.12 on 2022-04-08 22:36 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0149_remove_notification_related_book"), + ] + + operations = [ + migrations.AddField( + model_name="notification", + name="related_groups", + field=models.ManyToManyField( + related_name="notifications", to="bookwyrm.Group" + ), + ), + migrations.AddField( + model_name="notification", + name="related_list_items", + field=models.ManyToManyField( + related_name="notifications", to="bookwyrm.ListItem" + ), + ), + migrations.AddField( + model_name="notification", + name="related_reports", + field=models.ManyToManyField(to="bookwyrm.Report"), + ), + migrations.AddField( + model_name="notification", + name="related_statuses", + field=models.ManyToManyField( + related_name="notifications", to="bookwyrm.Status" + ), + ), + migrations.AddField( + model_name="notification", + name="related_users", + field=models.ManyToManyField( + related_name="notifications", to=settings.AUTH_USER_MODEL + ), + ), + migrations.AlterField( + model_name="notification", + name="related_group", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="notifications_temp", + to="bookwyrm.group", + ), + ), + migrations.AlterField( + model_name="notification", + name="related_list_item", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="notifications_tmp", + to="bookwyrm.listitem", + ), + ), + migrations.AlterField( + model_name="notification", + name="related_report", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="notifications_tmp", + to="bookwyrm.report", + ), + ), + migrations.RunSQL( + sql=""" + INSERT INTO bookwyrm_notification_related_statuses (notification_id, status_id) + SELECT id, related_status_id + FROM bookwyrm_notification + WHERE bookwyrm_notification.related_status_id IS NOT NULL; + + INSERT INTO bookwyrm_notification_related_users (notification_id, user_id) + SELECT id, related_user_id + FROM bookwyrm_notification + WHERE bookwyrm_notification.related_user_id IS NOT NULL; + + INSERT INTO bookwyrm_notification_related_groups (notification_id, group_id) + SELECT id, related_group_id + FROM bookwyrm_notification + WHERE bookwyrm_notification.related_group_id IS NOT NULL; + + INSERT INTO bookwyrm_notification_related_list_items (notification_id, listitem_id) + SELECT id, related_list_item_id + FROM bookwyrm_notification + WHERE bookwyrm_notification.related_list_item_id IS NOT NULL; + + INSERT INTO bookwyrm_notification_related_reports (notification_id, report_id) + SELECT id, related_report_id + FROM bookwyrm_notification + WHERE bookwyrm_notification.related_report_id IS NOT NULL; + + """, + reverse_sql=migrations.RunSQL.noop, + ), + migrations.RemoveField( + model_name="notification", + name="related_group", + ), + migrations.RemoveField( + model_name="notification", + name="related_list_item", + ), + migrations.RemoveField( + model_name="notification", + name="related_report", + ), + migrations.RemoveField( + model_name="notification", + name="related_status", + ), + migrations.RemoveField( + model_name="notification", + name="related_user", + ), + ] diff --git a/bookwyrm/models/notification.py b/bookwyrm/models/notification.py index 28c5b803ef..7b8059e0fb 100644 --- a/bookwyrm/models/notification.py +++ b/bookwyrm/models/notification.py @@ -15,38 +15,25 @@ class Notification(BookWyrmModel): """you've been tagged, liked, followed, etc""" user = models.ForeignKey("User", on_delete=models.CASCADE) - related_user = models.ForeignKey( - "User", on_delete=models.CASCADE, null=True, related_name="related_user" - ) - related_group = models.ForeignKey( - "Group", on_delete=models.CASCADE, null=True, related_name="notifications" - ) - related_status = models.ForeignKey("Status", on_delete=models.CASCADE, null=True) - related_import = models.ForeignKey("ImportJob", on_delete=models.CASCADE, null=True) - related_list_item = models.ForeignKey( - "ListItem", on_delete=models.CASCADE, null=True - ) - related_report = models.ForeignKey("Report", on_delete=models.CASCADE, null=True) read = models.BooleanField(default=False) notification_type = models.CharField( max_length=255, choices=NotificationType.choices ) - def save(self, *args, **kwargs): - """save, but don't make dupes""" - # there's probably a better way to do this - if self.__class__.objects.filter( - user=self.user, - related_user=self.related_user, - related_group=self.related_group, - related_status=self.related_status, - related_import=self.related_import, - related_list_item=self.related_list_item, - related_report=self.related_report, - notification_type=self.notification_type, - ).exists(): - return - super().save(*args, **kwargs) + related_users = models.ManyToManyField( + "User", symmetrical=False, related_name="notifications" + ) + related_groups = models.ManyToManyField( + "Group", symmetrical=False, related_name="notifications" + ) + related_statuses = models.ManyToManyField( + "Status", symmetrical=False, related_name="notifications" + ) + related_import = models.ForeignKey("ImportJob", on_delete=models.CASCADE, null=True) + related_list_items = models.ManyToManyField( + "ListItem", symmetrical=False, related_name="notifications" + ) + related_reports = models.ManyToManyField("Report", symmetrical=False) class Meta: """checks if notifcation is in enum list for valid types""" From c7ecbb2fdf253e69d3155f04a4f581aa81e68775 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 4 Jul 2022 18:42:56 -0700 Subject: [PATCH 03/40] New migration file I don't know why it felt important to do this but it did. The migrations are in one file now and don't need a merge migration. --- .../0149_remove_notification_related_book.py | 17 ----------------- ...20408_2236.py => 0151_auto_20220705_0049.py} | 8 ++++++-- 2 files changed, 6 insertions(+), 19 deletions(-) delete mode 100644 bookwyrm/migrations/0149_remove_notification_related_book.py rename bookwyrm/migrations/{0150_auto_20220408_2236.py => 0151_auto_20220705_0049.py} (95%) diff --git a/bookwyrm/migrations/0149_remove_notification_related_book.py b/bookwyrm/migrations/0149_remove_notification_related_book.py deleted file mode 100644 index c976af6c90..0000000000 --- a/bookwyrm/migrations/0149_remove_notification_related_book.py +++ /dev/null @@ -1,17 +0,0 @@ -# Generated by Django 3.2.12 on 2022-04-08 22:20 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ("bookwyrm", "0148_alter_user_preferred_language"), - ] - - operations = [ - migrations.RemoveField( - model_name="notification", - name="related_book", - ), - ] diff --git a/bookwyrm/migrations/0150_auto_20220408_2236.py b/bookwyrm/migrations/0151_auto_20220705_0049.py similarity index 95% rename from bookwyrm/migrations/0150_auto_20220408_2236.py rename to bookwyrm/migrations/0151_auto_20220705_0049.py index bf1c304878..f91f6c74a8 100644 --- a/bookwyrm/migrations/0150_auto_20220408_2236.py +++ b/bookwyrm/migrations/0151_auto_20220705_0049.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.12 on 2022-04-08 22:36 +# Generated by Django 3.2.13 on 2022-07-05 00:49 from django.conf import settings from django.db import migrations, models @@ -8,10 +8,14 @@ class Migration(migrations.Migration): dependencies = [ - ("bookwyrm", "0149_remove_notification_related_book"), + ('bookwyrm', '0150_readthrough_stopped_date'), ] operations = [ + migrations.RemoveField( + model_name='notification', + name='related_book', + ), migrations.AddField( model_name="notification", name="related_groups", From 0cc2bc269e6304e2a468c4174dc7e4480875c8fc Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 4 Jul 2022 18:51:07 -0700 Subject: [PATCH 04/40] Updates view --- bookwyrm/views/notifications.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/bookwyrm/views/notifications.py b/bookwyrm/views/notifications.py index e081b07c3d..249b9725b6 100644 --- a/bookwyrm/views/notifications.py +++ b/bookwyrm/views/notifications.py @@ -16,14 +16,12 @@ def get(self, request, notification_type=None): notifications = ( request.user.notification_set.all() .order_by("-created_date") - .select_related( - "related_status", - "related_status__reply_parent", + .prefetch_related( + "related_statuses", "related_import", - "related_report", - "related_user", - "related_list_item", - "related_list_item__book", + "related_reports", + "related_users", + "related_list_items", ) ) if notification_type == "mentions": From a9a2da0957a4b5a4b193660f97e95a2099ea10e8 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 4 Jul 2022 19:20:04 -0700 Subject: [PATCH 05/40] Keep status as a single field --- .../migrations/0151_auto_20220705_0049.py | 16 ---------- bookwyrm/models/notification.py | 29 +++++++++++-------- 2 files changed, 17 insertions(+), 28 deletions(-) diff --git a/bookwyrm/migrations/0151_auto_20220705_0049.py b/bookwyrm/migrations/0151_auto_20220705_0049.py index f91f6c74a8..f442253437 100644 --- a/bookwyrm/migrations/0151_auto_20220705_0049.py +++ b/bookwyrm/migrations/0151_auto_20220705_0049.py @@ -35,13 +35,6 @@ class Migration(migrations.Migration): name="related_reports", field=models.ManyToManyField(to="bookwyrm.Report"), ), - migrations.AddField( - model_name="notification", - name="related_statuses", - field=models.ManyToManyField( - related_name="notifications", to="bookwyrm.Status" - ), - ), migrations.AddField( model_name="notification", name="related_users", @@ -81,11 +74,6 @@ class Migration(migrations.Migration): ), migrations.RunSQL( sql=""" - INSERT INTO bookwyrm_notification_related_statuses (notification_id, status_id) - SELECT id, related_status_id - FROM bookwyrm_notification - WHERE bookwyrm_notification.related_status_id IS NOT NULL; - INSERT INTO bookwyrm_notification_related_users (notification_id, user_id) SELECT id, related_user_id FROM bookwyrm_notification @@ -121,10 +109,6 @@ class Migration(migrations.Migration): model_name="notification", name="related_report", ), - migrations.RemoveField( - model_name="notification", - name="related_status", - ), migrations.RemoveField( model_name="notification", name="related_user", diff --git a/bookwyrm/models/notification.py b/bookwyrm/models/notification.py index 7b8059e0fb..85eecc6ca4 100644 --- a/bookwyrm/models/notification.py +++ b/bookwyrm/models/notification.py @@ -1,5 +1,5 @@ """ alert a user to activity """ -from django.db import models +from django.db import models, transaction from django.dispatch import receiver from .base_model import BookWyrmModel from . import Boost, Favorite, ImportJob, Report, Status, User @@ -26,9 +26,7 @@ class Notification(BookWyrmModel): related_groups = models.ManyToManyField( "Group", symmetrical=False, related_name="notifications" ) - related_statuses = models.ManyToManyField( - "Status", symmetrical=False, related_name="notifications" - ) + related_status = models.ForeignKey("Status", on_delete=models.CASCADE, null=True) related_import = models.ForeignKey("ImportJob", on_delete=models.CASCADE, null=True) related_list_items = models.ManyToManyField( "ListItem", symmetrical=False, related_name="notifications" @@ -47,17 +45,18 @@ class Meta: @receiver(models.signals.post_save, sender=Favorite) +@transaction.atomic() # pylint: disable=unused-argument def notify_on_fav(sender, instance, *args, **kwargs): """someone liked your content, you ARE loved""" if not instance.status.user.local or instance.status.user == instance.user: return - Notification.objects.create( + notification, _ = Notification.objects.get_or_create( user=instance.status.user, notification_type="FAVORITE", - related_user=instance.user, related_status=instance.status, ) + notification.related_users.add(instance.user) @receiver(models.signals.post_delete, sender=Favorite) @@ -66,12 +65,18 @@ def notify_on_unfav(sender, instance, *args, **kwargs): """oops, didn't like that after all""" if not instance.status.user.local: return - Notification.objects.filter( - user=instance.status.user, - related_user=instance.user, - related_status=instance.status, - notification_type="FAVORITE", - ).delete() + try: + notification = Notification.objects.filter( + user=instance.status.user, + related_users=instance.user, + related_status=instance.status, + notification_type="FAVORITE", + ).get() + except Notification.DoesNotExist: + return + notification.related_users.remove(instance.user) + if not notification.related_users.exists(): + notification.delete() @receiver(models.signals.post_save) From 62e57ac9319aae8b1b412a97231f67b29641bc6e Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 4 Jul 2022 19:48:10 -0700 Subject: [PATCH 06/40] Adds notify and unnotify helper class methods In the new paradigm, a notification related to a status has users added to it and removed from it, rather than a new notification being added every time. These helper functions make this behavior consistent. --- bookwyrm/models/notification.py | 89 ++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 34 deletions(-) diff --git a/bookwyrm/models/notification.py b/bookwyrm/models/notification.py index 85eecc6ca4..c24024ef3a 100644 --- a/bookwyrm/models/notification.py +++ b/bookwyrm/models/notification.py @@ -33,6 +33,31 @@ class Notification(BookWyrmModel): ) related_reports = models.ManyToManyField("Report", symmetrical=False) + @classmethod + @transaction.atomic + def notify(cls, user, related_user, **kwargs): + """Create a notification""" + if not user.local or user == related_user: + return + notification, _ = cls.objects.get_or_create( + user=user, + **kwargs + ) + notification.related_users.add(related_user) + notification.unread = True + notification.save() + + @classmethod + def unnotify(cls, user, related_user, **kwargs): + """Remove a user from a notification and delete it if that was the only user""" + try: + notification = cls.objects.filter(user=user, **kwargs).get() + except Notification.DoesNotExist: + return + notification.related_users.remove(related_user) + if not notification.related_users.exists(): + notification.delete() + class Meta: """checks if notifcation is in enum list for valid types""" @@ -49,14 +74,12 @@ class Meta: # pylint: disable=unused-argument def notify_on_fav(sender, instance, *args, **kwargs): """someone liked your content, you ARE loved""" - if not instance.status.user.local or instance.status.user == instance.user: - return - notification, _ = Notification.objects.get_or_create( - user=instance.status.user, - notification_type="FAVORITE", + Notification.notify( + instance.status.user, + instance.user, related_status=instance.status, + notification_type="FAVORITE", ) - notification.related_users.add(instance.user) @receiver(models.signals.post_delete, sender=Favorite) @@ -65,21 +88,16 @@ def notify_on_unfav(sender, instance, *args, **kwargs): """oops, didn't like that after all""" if not instance.status.user.local: return - try: - notification = Notification.objects.filter( - user=instance.status.user, - related_users=instance.user, - related_status=instance.status, - notification_type="FAVORITE", - ).get() - except Notification.DoesNotExist: - return - notification.related_users.remove(instance.user) - if not notification.related_users.exists(): - notification.delete() + Notification.unnotify( + instance.status.user, + instance.user, + related_status=instance.status, + notification_type="FAVORITE" + ) @receiver(models.signals.post_save) +@transaction.atomic # pylint: disable=unused-argument def notify_user_on_mention(sender, instance, *args, **kwargs): """creating and deleting statuses with @ mentions and replies""" @@ -95,27 +113,29 @@ def notify_user_on_mention(sender, instance, *args, **kwargs): and instance.reply_parent.user != instance.user and instance.reply_parent.user.local ): - Notification.objects.create( - user=instance.reply_parent.user, - notification_type="REPLY", - related_user=instance.user, + Notification.notify( + instance.reply_parent.user, + instance.user, related_status=instance, + notification_type="REPLY", ) + for mention_user in instance.mention_users.all(): # avoid double-notifying about this status if not mention_user.local or ( instance.reply_parent and mention_user == instance.reply_parent.user ): continue - Notification.objects.create( - user=mention_user, + Notification.notify( + mention_user, + instance.user, notification_type="MENTION", - related_user=instance.user, related_status=instance, ) @receiver(models.signals.post_save, sender=Boost) +@transaction.atomic # pylint: disable=unused-argument def notify_user_on_boost(sender, instance, *args, **kwargs): """boosting a status""" @@ -125,10 +145,10 @@ def notify_user_on_boost(sender, instance, *args, **kwargs): ): return - Notification.objects.create( - user=instance.boosted_status.user, + Notification.notify( + instance.boosted_status.user, + instance.user, related_status=instance.boosted_status, - related_user=instance.user, notification_type="BOOST", ) @@ -137,12 +157,12 @@ def notify_user_on_boost(sender, instance, *args, **kwargs): # pylint: disable=unused-argument def notify_user_on_unboost(sender, instance, *args, **kwargs): """unboosting a status""" - Notification.objects.filter( - user=instance.boosted_status.user, + Notification.unnotify( + instance.boosted_status.user, + instance.user, related_status=instance.boosted_status, - related_user=instance.user, notification_type="BOOST", - ).delete() + ) @receiver(models.signals.post_save, sender=ImportJob) @@ -162,6 +182,7 @@ def notify_user_on_import_complete( @receiver(models.signals.post_save, sender=Report) +@transaction.atomic # pylint: disable=unused-argument def notify_admins_on_report(sender, instance, *args, **kwargs): """something is up, make sure the admins know""" @@ -171,8 +192,8 @@ def notify_admins_on_report(sender, instance, *args, **kwargs): | models.Q(is_superuser=True) ).all() for admin in admins: - Notification.objects.create( + notification, _ = Notification.objects.get_or_create( user=admin, - related_report=instance, notification_type="REPORT", ) + notification.related_reports.add(instance) From dc8e61f316e13360edc5d3fbcf8a8ab9d84eb4bd Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 4 Jul 2022 19:57:58 -0700 Subject: [PATCH 07/40] Updates reports created in automod task --- bookwyrm/models/antispam.py | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/bookwyrm/models/antispam.py b/bookwyrm/models/antispam.py index f506b6f193..0616590263 100644 --- a/bookwyrm/models/antispam.py +++ b/bookwyrm/models/antispam.py @@ -3,7 +3,7 @@ import operator from django.apps import apps -from django.db import models +from django.db import models, transaction from django.db.models import Q from django.utils.translation import gettext_lazy as _ @@ -58,25 +58,20 @@ def automod_task(): return reporter = AutoMod.objects.first().created_by reports = automod_users(reporter) + automod_statuses(reporter) - if reports: - admins = User.objects.filter( - models.Q(user_permissions__name__in=["moderate_user", "moderate_post"]) - | models.Q(is_superuser=True) - ).all() - notification_model = apps.get_model( - "bookwyrm", "Notification", require_ready=True - ) + if not reports: + return + + admins = User.objects.filter( + models.Q(user_permissions__name__in=["moderate_user", "moderate_post"]) + | models.Q(is_superuser=True) + ).all() + notification_model = apps.get_model("bookwyrm", "Notification", require_ready=True) + with transaction.atomic(): for admin in admins: - notification_model.objects.bulk_create( - [ - notification_model( - user=admin, - related_report=r, - notification_type="REPORT", - ) - for r in reports - ] + notification, _ = notification_model.objects.get_or_create( + user=admin, notification_type="REPORT", unread=True ) + notification.related_repors.add(reports) def automod_users(reporter): From b193652a67924a7dc2213cd1a78d1bcdf5ea54d7 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 4 Jul 2022 19:58:27 -0700 Subject: [PATCH 08/40] Python formatting --- bookwyrm/migrations/0151_auto_20220705_0049.py | 6 +++--- bookwyrm/models/notification.py | 8 +++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/bookwyrm/migrations/0151_auto_20220705_0049.py b/bookwyrm/migrations/0151_auto_20220705_0049.py index f442253437..4748e0d7a7 100644 --- a/bookwyrm/migrations/0151_auto_20220705_0049.py +++ b/bookwyrm/migrations/0151_auto_20220705_0049.py @@ -8,13 +8,13 @@ class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0150_readthrough_stopped_date'), + ("bookwyrm", "0150_readthrough_stopped_date"), ] operations = [ migrations.RemoveField( - model_name='notification', - name='related_book', + model_name="notification", + name="related_book", ), migrations.AddField( model_name="notification", diff --git a/bookwyrm/models/notification.py b/bookwyrm/models/notification.py index c24024ef3a..2555df3dd4 100644 --- a/bookwyrm/models/notification.py +++ b/bookwyrm/models/notification.py @@ -39,10 +39,7 @@ def notify(cls, user, related_user, **kwargs): """Create a notification""" if not user.local or user == related_user: return - notification, _ = cls.objects.get_or_create( - user=user, - **kwargs - ) + notification, _ = cls.objects.get_or_create(user=user, **kwargs) notification.related_users.add(related_user) notification.unread = True notification.save() @@ -92,7 +89,7 @@ def notify_on_unfav(sender, instance, *args, **kwargs): instance.status.user, instance.user, related_status=instance.status, - notification_type="FAVORITE" + notification_type="FAVORITE", ) @@ -195,5 +192,6 @@ def notify_admins_on_report(sender, instance, *args, **kwargs): notification, _ = Notification.objects.get_or_create( user=admin, notification_type="REPORT", + unread=True, ) notification.related_reports.add(instance) From 72a8229a5ceddfa6f8f604419c7ff62527f36e7a Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 4 Jul 2022 20:05:39 -0700 Subject: [PATCH 09/40] Updates group notifications --- bookwyrm/models/group.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/bookwyrm/models/group.py b/bookwyrm/models/group.py index 05ed39a278..a2c37f96d8 100644 --- a/bookwyrm/models/group.py +++ b/bookwyrm/models/group.py @@ -142,12 +142,11 @@ def save(self, *args, **kwargs): # now send the invite model = apps.get_model("bookwyrm.Notification", require_ready=True) - notification_type = "INVITE" - model.objects.create( - user=self.user, - related_user=self.group.user, + model.notify( + self.user, + self.group.user, related_group=self.group, - notification_type=notification_type, + notification_type="INVITE", ) @transaction.atomic @@ -157,9 +156,9 @@ def accept(self): model = apps.get_model("bookwyrm.Notification", require_ready=True) # tell the group owner - model.objects.create( - user=self.group.user, - related_user=self.user, + model.notify( + self.group.user, + self.user, related_group=self.group, notification_type="ACCEPT", ) @@ -168,9 +167,9 @@ def accept(self): for membership in self.group.memberships.all(): member = membership.user if member not in (self.user, self.group.user): - model.objects.create( - user=member, - related_user=self.user, + model.notify( + member, + self.user, related_group=self.group, notification_type="JOIN", ) From 03f5a3f2c1de6720f7cc4958b9a84caa0e90d865 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 4 Jul 2022 20:19:18 -0700 Subject: [PATCH 10/40] Use enums for notification types --- ...ve_notification_notification_type_valid.py | 17 ++++++ bookwyrm/models/notification.py | 58 +++++++++++-------- 2 files changed, 51 insertions(+), 24 deletions(-) create mode 100644 bookwyrm/migrations/0152_remove_notification_notification_type_valid.py diff --git a/bookwyrm/migrations/0152_remove_notification_notification_type_valid.py b/bookwyrm/migrations/0152_remove_notification_notification_type_valid.py new file mode 100644 index 0000000000..7ca667725d --- /dev/null +++ b/bookwyrm/migrations/0152_remove_notification_notification_type_valid.py @@ -0,0 +1,17 @@ +# Generated by Django 3.2.13 on 2022-07-05 03:16 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('bookwyrm', '0151_auto_20220705_0049'), + ] + + operations = [ + migrations.RemoveConstraint( + model_name='notification', + name='notification_type_valid', + ), + ] diff --git a/bookwyrm/models/notification.py b/bookwyrm/models/notification.py index 2555df3dd4..55b977f77b 100644 --- a/bookwyrm/models/notification.py +++ b/bookwyrm/models/notification.py @@ -4,16 +4,36 @@ from .base_model import BookWyrmModel from . import Boost, Favorite, ImportJob, Report, Status, User -# pylint: disable=line-too-long -NotificationType = models.TextChoices( - "NotificationType", - "FAVORITE REPLY MENTION TAG FOLLOW FOLLOW_REQUEST BOOST IMPORT ADD REPORT INVITE ACCEPT JOIN LEAVE REMOVE GROUP_PRIVACY GROUP_NAME GROUP_DESCRIPTION", -) - class Notification(BookWyrmModel): """you've been tagged, liked, followed, etc""" + FAVORITE = "FAVORITE" + REPLY = "REPLY" + MENTION = "MENTION" + TAG = "TAG" + FOLLOW = "FOLLOW" + FOLLOW_REQUEST = "FOLLOW_REQUEST" + BOOST = "BOOST" + IMPORT = "IMPORT" + ADD = "ADD" + REPORT = "REPORT" + INVITE = "INVITE" + ACCEPT = "ACCEPT" + JOIN = "JOIN" + LEAVE = "LEAVE" + REMOVE = "REMOVE" + GROUP_PRIVACY = "GROUP_PRIVACY" + GROUP_NAME = "GROUP_NAME" + GROUP_DESCRIPTION = "GROUP_DESCRIPTION" + + # 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} {INVITE} {ACCEPT} {JOIN} {LEAVE} {REMOVE} {GROUP_PRIVACY} {GROUP_NAME} {GROUP_DESCRIPTION}", + ) + user = models.ForeignKey("User", on_delete=models.CASCADE) read = models.BooleanField(default=False) notification_type = models.CharField( @@ -55,16 +75,6 @@ def unnotify(cls, user, related_user, **kwargs): if not notification.related_users.exists(): notification.delete() - class Meta: - """checks if notifcation is in enum list for valid types""" - - constraints = [ - models.CheckConstraint( - check=models.Q(notification_type__in=NotificationType.values), - name="notification_type_valid", - ) - ] - @receiver(models.signals.post_save, sender=Favorite) @transaction.atomic() @@ -75,7 +85,7 @@ def notify_on_fav(sender, instance, *args, **kwargs): instance.status.user, instance.user, related_status=instance.status, - notification_type="FAVORITE", + notification_type=Notification.FAVORITE, ) @@ -89,7 +99,7 @@ def notify_on_unfav(sender, instance, *args, **kwargs): instance.status.user, instance.user, related_status=instance.status, - notification_type="FAVORITE", + notification_type=Notification.FAVORITE, ) @@ -114,7 +124,7 @@ def notify_user_on_mention(sender, instance, *args, **kwargs): instance.reply_parent.user, instance.user, related_status=instance, - notification_type="REPLY", + notification_type=Notification.REPLY, ) for mention_user in instance.mention_users.all(): @@ -126,7 +136,7 @@ def notify_user_on_mention(sender, instance, *args, **kwargs): Notification.notify( mention_user, instance.user, - notification_type="MENTION", + notification_type=Notification.MENTION, related_status=instance, ) @@ -146,7 +156,7 @@ def notify_user_on_boost(sender, instance, *args, **kwargs): instance.boosted_status.user, instance.user, related_status=instance.boosted_status, - notification_type="BOOST", + notification_type=Notification.BOOST, ) @@ -158,7 +168,7 @@ def notify_user_on_unboost(sender, instance, *args, **kwargs): instance.boosted_status.user, instance.user, related_status=instance.boosted_status, - notification_type="BOOST", + notification_type=Notification.BOOST, ) @@ -173,7 +183,7 @@ def notify_user_on_import_complete( return Notification.objects.create( user=instance.user, - notification_type="IMPORT", + notification_type=Notification.IMPORT, related_import=instance, ) @@ -191,7 +201,7 @@ def notify_admins_on_report(sender, instance, *args, **kwargs): for admin in admins: notification, _ = Notification.objects.get_or_create( user=admin, - notification_type="REPORT", + notification_type=Notification.REPORT, unread=True, ) notification.related_reports.add(instance) From aeefd5a3e9d2667de0579cf35758331a22dd3958 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 4 Jul 2022 20:24:29 -0700 Subject: [PATCH 11/40] Use signal for creating group invite notification --- bookwyrm/models/group.py | 14 ++------------ bookwyrm/models/notification.py | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/bookwyrm/models/group.py b/bookwyrm/models/group.py index a2c37f96d8..003b23d02c 100644 --- a/bookwyrm/models/group.py +++ b/bookwyrm/models/group.py @@ -140,15 +140,6 @@ def save(self, *args, **kwargs): # make an invitation super().save(*args, **kwargs) - # now send the invite - model = apps.get_model("bookwyrm.Notification", require_ready=True) - model.notify( - self.user, - self.group.user, - related_group=self.group, - notification_type="INVITE", - ) - @transaction.atomic def accept(self): """turn this request into the real deal""" @@ -160,7 +151,7 @@ def accept(self): self.group.user, self.user, related_group=self.group, - notification_type="ACCEPT", + notification_type=model.ACCEPT, ) # let the other members know about it @@ -171,10 +162,9 @@ def accept(self): member, self.user, related_group=self.group, - notification_type="JOIN", + notification_type=model.JOIN, ) def reject(self): """generate a Reject for this membership request""" - self.delete() diff --git a/bookwyrm/models/notification.py b/bookwyrm/models/notification.py index 55b977f77b..34c06ad2e2 100644 --- a/bookwyrm/models/notification.py +++ b/bookwyrm/models/notification.py @@ -2,7 +2,7 @@ from django.db import models, transaction from django.dispatch import receiver from .base_model import BookWyrmModel -from . import Boost, Favorite, ImportJob, Report, Status, User +from . import Boost, Favorite, GroupMemberInvitation, ImportJob, Report, Status, User class Notification(BookWyrmModel): @@ -205,3 +205,16 @@ def notify_admins_on_report(sender, instance, *args, **kwargs): unread=True, ) notification.related_reports.add(instance) + + +@receiver(models.signals.post_save, sender=GroupMemberInvitation) +@transaction.atomic +# pylint: disable=unused-argument +def notify_user_on_group_invite(sender, instance, *args, **kwargs): + """Cool kids club here we come""" + Notification.notify( + instance.user, + instance.group.user, + related_group=instance.group, + notification_type=Notification.INVITE, + ) From 9948dd235659ed82d1f6e930714f334392d84a2d Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 4 Jul 2022 20:26:38 -0700 Subject: [PATCH 12/40] Use enums in more models --- ...0152_remove_notification_notification_type_valid.py | 6 +++--- bookwyrm/models/antispam.py | 2 +- bookwyrm/models/list.py | 4 ++-- bookwyrm/models/relationship.py | 10 ++++++---- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/bookwyrm/migrations/0152_remove_notification_notification_type_valid.py b/bookwyrm/migrations/0152_remove_notification_notification_type_valid.py index 7ca667725d..f7471c0d25 100644 --- a/bookwyrm/migrations/0152_remove_notification_notification_type_valid.py +++ b/bookwyrm/migrations/0152_remove_notification_notification_type_valid.py @@ -6,12 +6,12 @@ class Migration(migrations.Migration): dependencies = [ - ('bookwyrm', '0151_auto_20220705_0049'), + ("bookwyrm", "0151_auto_20220705_0049"), ] operations = [ migrations.RemoveConstraint( - model_name='notification', - name='notification_type_valid', + model_name="notification", + name="notification_type_valid", ), ] diff --git a/bookwyrm/models/antispam.py b/bookwyrm/models/antispam.py index 0616590263..855ba9a389 100644 --- a/bookwyrm/models/antispam.py +++ b/bookwyrm/models/antispam.py @@ -69,7 +69,7 @@ def automod_task(): with transaction.atomic(): for admin in admins: notification, _ = notification_model.objects.get_or_create( - user=admin, notification_type="REPORT", unread=True + user=admin, notification_type=notification_model.REPORT, unread=True ) notification.related_repors.add(reports) diff --git a/bookwyrm/models/list.py b/bookwyrm/models/list.py index 0195020e00..db4dc58062 100644 --- a/bookwyrm/models/list.py +++ b/bookwyrm/models/list.py @@ -166,7 +166,7 @@ def save(self, *args, **kwargs): user=list_owner, related_user=self.user, related_list_item=self, - notification_type="ADD", + notification_type=model.ADD, ) if self.book_list.group: @@ -176,7 +176,7 @@ def save(self, *args, **kwargs): user=membership.user, related_user=self.user, related_list_item=self, - notification_type="ADD", + notification_type=model.ADD, ) def raise_not_deletable(self, viewer): diff --git a/bookwyrm/models/relationship.py b/bookwyrm/models/relationship.py index 3132005147..bc483805b1 100644 --- a/bookwyrm/models/relationship.py +++ b/bookwyrm/models/relationship.py @@ -149,10 +149,12 @@ def save(self, *args, broadcast=True, **kwargs): # pylint: disable=arguments-di self.accept() model = apps.get_model("bookwyrm.Notification", require_ready=True) - notification_type = "FOLLOW_REQUEST" if manually_approves else "FOLLOW" - model.objects.create( - user=self.user_object, - related_user=self.user_subject, + notification_type = ( + model.FOLLOW_REQUEST if manually_approves else model.FOLLOW + ) + model.notify( + self.user_object, + self.user_subject, notification_type=notification_type, ) From 801ba03aaf2b25a87f12e4466cce89bac2897c9d Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 4 Jul 2022 20:32:13 -0700 Subject: [PATCH 13/40] Keep group as a foreign key field --- .../migrations/0151_auto_20220705_0049.py | 26 ------------------- bookwyrm/models/notification.py | 4 +-- 2 files changed, 2 insertions(+), 28 deletions(-) diff --git a/bookwyrm/migrations/0151_auto_20220705_0049.py b/bookwyrm/migrations/0151_auto_20220705_0049.py index 4748e0d7a7..6010e38e57 100644 --- a/bookwyrm/migrations/0151_auto_20220705_0049.py +++ b/bookwyrm/migrations/0151_auto_20220705_0049.py @@ -16,13 +16,6 @@ class Migration(migrations.Migration): model_name="notification", name="related_book", ), - migrations.AddField( - model_name="notification", - name="related_groups", - field=models.ManyToManyField( - related_name="notifications", to="bookwyrm.Group" - ), - ), migrations.AddField( model_name="notification", name="related_list_items", @@ -42,16 +35,6 @@ class Migration(migrations.Migration): related_name="notifications", to=settings.AUTH_USER_MODEL ), ), - migrations.AlterField( - model_name="notification", - name="related_group", - field=models.ForeignKey( - null=True, - on_delete=django.db.models.deletion.CASCADE, - related_name="notifications_temp", - to="bookwyrm.group", - ), - ), migrations.AlterField( model_name="notification", name="related_list_item", @@ -79,11 +62,6 @@ class Migration(migrations.Migration): FROM bookwyrm_notification WHERE bookwyrm_notification.related_user_id IS NOT NULL; - INSERT INTO bookwyrm_notification_related_groups (notification_id, group_id) - SELECT id, related_group_id - FROM bookwyrm_notification - WHERE bookwyrm_notification.related_group_id IS NOT NULL; - INSERT INTO bookwyrm_notification_related_list_items (notification_id, listitem_id) SELECT id, related_list_item_id FROM bookwyrm_notification @@ -97,10 +75,6 @@ class Migration(migrations.Migration): """, reverse_sql=migrations.RunSQL.noop, ), - migrations.RemoveField( - model_name="notification", - name="related_group", - ), migrations.RemoveField( model_name="notification", name="related_list_item", diff --git a/bookwyrm/models/notification.py b/bookwyrm/models/notification.py index 34c06ad2e2..745a95342d 100644 --- a/bookwyrm/models/notification.py +++ b/bookwyrm/models/notification.py @@ -43,8 +43,8 @@ class Notification(BookWyrmModel): related_users = models.ManyToManyField( "User", symmetrical=False, related_name="notifications" ) - related_groups = models.ManyToManyField( - "Group", symmetrical=False, related_name="notifications" + related_group = models.ForeignKey( + "Group", on_delete=models.CASCADE, null=True, related_name="notifications" ) related_status = models.ForeignKey("Status", on_delete=models.CASCADE, null=True) related_import = models.ForeignKey("ImportJob", on_delete=models.CASCADE, null=True) From 971bed994257f6f5f8718e1ea7470e78ed72a44c Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 4 Jul 2022 21:32:35 -0700 Subject: [PATCH 14/40] Notification field is read, not unread Oops --- bookwyrm/models/antispam.py | 2 +- bookwyrm/models/notification.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bookwyrm/models/antispam.py b/bookwyrm/models/antispam.py index 855ba9a389..dd2a6df263 100644 --- a/bookwyrm/models/antispam.py +++ b/bookwyrm/models/antispam.py @@ -69,7 +69,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, unread=True + user=admin, notification_type=notification_model.REPORT, read=False ) notification.related_repors.add(reports) diff --git a/bookwyrm/models/notification.py b/bookwyrm/models/notification.py index 745a95342d..a11d53f851 100644 --- a/bookwyrm/models/notification.py +++ b/bookwyrm/models/notification.py @@ -61,7 +61,7 @@ def notify(cls, user, related_user, **kwargs): return notification, _ = cls.objects.get_or_create(user=user, **kwargs) notification.related_users.add(related_user) - notification.unread = True + notification.read = False notification.save() @classmethod @@ -72,7 +72,7 @@ def unnotify(cls, user, related_user, **kwargs): except Notification.DoesNotExist: return notification.related_users.remove(related_user) - if not notification.related_users.exists(): + if not notification.related_users.count(): notification.delete() @@ -202,7 +202,7 @@ def notify_admins_on_report(sender, instance, *args, **kwargs): notification, _ = Notification.objects.get_or_create( user=admin, notification_type=Notification.REPORT, - unread=True, + read=False, ) notification.related_reports.add(instance) From f76d661e07a2e4378732786ba2283626c410b273 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Mon, 4 Jul 2022 21:32:53 -0700 Subject: [PATCH 15/40] Updates templates for fav notifications --- .../templates/notifications/items/fav.html | 25 +++++++++-- .../templates/notifications/items/layout.html | 41 ++++++++++++++++--- .../templates/notifications/items/report.html | 3 +- bookwyrm/views/notifications.py | 7 +++- 4 files changed, 64 insertions(+), 12 deletions(-) diff --git a/bookwyrm/templates/notifications/items/fav.html b/bookwyrm/templates/notifications/items/fav.html index eb29ebc26f..52268a002d 100644 --- a/bookwyrm/templates/notifications/items/fav.html +++ b/bookwyrm/templates/notifications/items/fav.html @@ -34,11 +34,30 @@ {% endblocktrans %} {% else %} - {% blocktrans trimmed %} - liked your status + {% if other_user_count == 0 %} + + {% blocktrans trimmed %} + {{ related_user }} liked your status + {% endblocktrans %} + + {% elif other_user_count == 1 %} + + {% blocktrans trimmed %} + {{ related_user }} + and + {{ second_user }} + liked your status + {% endblocktrans %} + + {% else %} + + {% blocktrans trimmed %} + {{ related_user }} and {{ other_user_count }} others liked your status + {% endblocktrans %} + + {% endif %} - {% endblocktrans %} {% endif %} {% endwith %} diff --git a/bookwyrm/templates/notifications/items/layout.html b/bookwyrm/templates/notifications/items/layout.html index e7c3b31478..d236e55dd5 100644 --- a/bookwyrm/templates/notifications/items/layout.html +++ b/bookwyrm/templates/notifications/items/layout.html @@ -1,5 +1,7 @@ {% load notification_page_tags %} {% related_status notification as related_status %} + +{% with related_users=notification.related_users.all %}
@@ -9,13 +11,42 @@
+ {% if notification.related_users.count > 1 %} +
+ +
+ {% endif %}

- {% if notification.related_user %} - {% include 'snippets/avatar.html' with user=notification.related_user %} - {{ notification.related_user.display_name }} + {% if notification.related_users.count == 1 %} + {% with user=related_users.first %} + {% spaceless %} + + {% include 'snippets/avatar.html' with user=user %} + + {% endspaceless %} + {% endwith %} {% endif %} - {% block description %}{% endblock %} + + {% with related_user=related_users.first.display_name %} + {% with related_user_link=related_users.first.local_path %} + {% with second_user=related_users.all.1.display_name %} + {% with second_user_link=related_users.all.1.local_path %} + {% with other_user_count=notification.related_users.count|add:"-1" %} + {% block description %}{% endblock %} + {% endwith %} + {% endwith %} + {% endwith %} + {% endwith %} + {% endwith %}

@@ -27,4 +58,4 @@
- +{% endwith %} diff --git a/bookwyrm/templates/notifications/items/report.html b/bookwyrm/templates/notifications/items/report.html index fdd5f00946..62fbd8ddc3 100644 --- a/bookwyrm/templates/notifications/items/report.html +++ b/bookwyrm/templates/notifications/items/report.html @@ -3,7 +3,6 @@ {% load i18n %} {% block primary_link %}{% spaceless %} - {% url 'settings-report' notification.related_report.id %} {% endspaceless %}{% endblock %} {% block icon %} @@ -11,6 +10,6 @@ {% endblock %} {% block description %} - {% url 'settings-report' notification.related_report.id as path %} + {% url 'settings-report' as path %} {% blocktrans %}A new report needs moderation.{% endblocktrans %} {% endblock %} diff --git a/bookwyrm/views/notifications.py b/bookwyrm/views/notifications.py index 249b9725b6..f42a6aa7c0 100644 --- a/bookwyrm/views/notifications.py +++ b/bookwyrm/views/notifications.py @@ -15,9 +15,12 @@ def get(self, request, notification_type=None): """people are interacting with you, get hyped""" notifications = ( request.user.notification_set.all() - .order_by("-created_date") + .order_by("-updated_date") + .select_related( + "related_status", + "related_group", + ) .prefetch_related( - "related_statuses", "related_import", "related_reports", "related_users", From fc375bbab4cb94498c63d211b28134df646e0d07 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 5 Jul 2022 12:21:22 -0700 Subject: [PATCH 16/40] Finishes fav status translation strings --- .../templates/notifications/items/fav.html | 69 ++++++++++++++++--- 1 file changed, 60 insertions(+), 9 deletions(-) diff --git a/bookwyrm/templates/notifications/items/fav.html b/bookwyrm/templates/notifications/items/fav.html index 52268a002d..4b1e90dd6d 100644 --- a/bookwyrm/templates/notifications/items/fav.html +++ b/bookwyrm/templates/notifications/items/fav.html @@ -16,23 +16,74 @@ {% with related_status.local_path as related_path %} {% if related_status.status_type == 'Review' %} - {% blocktrans trimmed %} + {% if other_user_count == 0 %} + + {% blocktrans trimmed %} + {{ related_user }} liked your review of {{ book_title }} + {% endblocktrans %} + + {% elif other_user_count == 1 %} + + {% blocktrans trimmed %} + {{ related_user }} + and + {{ second_user }} + liked your review of {{ book_title }} + {% endblocktrans %} + + {% else %} - liked your review of {{ book_title }} + {% blocktrans trimmed %} + {{ related_user }} and {{ other_user_count }} others liked your review of {{ book_title }} + {% endblocktrans %} - {% endblocktrans %} + {% endif %} {% elif related_status.status_type == 'Comment' %} - {% blocktrans trimmed %} + {% if other_user_count == 0 %} - liked your comment on {{ book_title }} + {% blocktrans trimmed %} + {{ related_user }} liked your comment on {{ book_title }} + {% endblocktrans %} - {% endblocktrans %} + {% elif other_user_count == 1 %} + + {% blocktrans trimmed %} + {{ related_user }} + and + {{ second_user }} + liked your comment on {{ book_title }} + {% endblocktrans %} + + {% else %} + + {% blocktrans trimmed %} + {{ related_user }} and {{ other_user_count }} others liked your comment on {{ book_title }} + {% endblocktrans %} + + {% endif %} {% elif related_status.status_type == 'Quotation' %} - {% blocktrans trimmed %} + {% if other_user_count == 0 %} + + {% blocktrans trimmed %} + {{ related_user }} liked your quote from {{ book_title }} + {% endblocktrans %} - liked your quote from {{ book_title }} + {% elif other_user_count == 1 %} - {% endblocktrans %} + {% blocktrans trimmed %} + {{ related_user }} + and + {{ second_user }} + liked your quote from {{ book_title }} + {% endblocktrans %} + + {% else %} + + {% blocktrans trimmed %} + {{ related_user }} and {{ other_user_count }} others liked your quote from {{ book_title }} + {% endblocktrans %} + + {% endif %} {% else %} {% if other_user_count == 0 %} From ee71f5df21886ca3aa128a706f5a1c81c60aa7c9 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 5 Jul 2022 12:28:48 -0700 Subject: [PATCH 17/40] Updates language on boosts --- .../templates/notifications/items/boost.html | 92 ++++++++++++++++--- .../templates/notifications/items/fav.html | 1 - 2 files changed, 80 insertions(+), 13 deletions(-) diff --git a/bookwyrm/templates/notifications/items/boost.html b/bookwyrm/templates/notifications/items/boost.html index 5e3e115137..d297de6f83 100644 --- a/bookwyrm/templates/notifications/items/boost.html +++ b/bookwyrm/templates/notifications/items/boost.html @@ -16,29 +16,97 @@ {% with related_status.local_path as related_path %} {% if related_status.status_type == 'Review' %} - {% blocktrans trimmed %} + {% if other_user_count == 0 %} - boosted your review of {{ book_title }} + {% blocktrans trimmed %} + {{ related_user }} boosted your review of {{ book_title }} + {% endblocktrans %} - {% endblocktrans %} + {% elif other_user_count == 1 %} + + {% blocktrans trimmed %} + {{ related_user }} + and + {{ second_user }} + boosted your review of {{ book_title }} + {% endblocktrans %} + + {% else %} + + {% blocktrans trimmed %} + {{ related_user }} and {{ other_user_count }} others boosted your review of {{ book_title }} + {% endblocktrans %} + + {% endif %} {% elif related_status.status_type == 'Comment' %} - {% blocktrans trimmed %} + {% if other_user_count == 0 %} + + {% blocktrans trimmed %} + {{ related_user }} boosted your comment on {{ book_title }} + {% endblocktrans %} + + {% elif other_user_count == 1 %} + + {% blocktrans trimmed %} + {{ related_user }} + and + {{ second_user }} + boosted your comment on {{ book_title }} + {% endblocktrans %} + + {% else %} - boosted your comment on{{ book_title }} + {% blocktrans trimmed %} + {{ related_user }} and {{ other_user_count }} others boosted your comment on {{ book_title }} + {% endblocktrans %} - {% endblocktrans %} + {% endif %} {% elif related_status.status_type == 'Quotation' %} - {% blocktrans trimmed %} + {% if other_user_count == 0 %} - boosted your quote from {{ book_title }} + {% blocktrans trimmed %} + {{ related_user }} boosted your quote from {{ book_title }} + {% endblocktrans %} - {% endblocktrans %} + {% elif other_user_count == 1 %} + + {% blocktrans trimmed %} + {{ related_user }} + and + {{ second_user }} + boosted your quote from {{ book_title }} + {% endblocktrans %} + + {% else %} + + {% blocktrans trimmed %} + {{ related_user }} and {{ other_user_count }} others boosted your quote from {{ book_title }} + {% endblocktrans %} + + {% endif %} {% else %} - {% blocktrans trimmed %} + {% if other_user_count == 0 %} + + {% blocktrans trimmed %} + {{ related_user }} boosted your status + {% endblocktrans %} + + {% elif other_user_count == 1 %} + + {% blocktrans trimmed %} + {{ related_user }} + and + {{ second_user }} + boosted your status + {% endblocktrans %} + + {% else %} - boosted your status + {% blocktrans trimmed %} + {{ related_user }} and {{ other_user_count }} others boosted your status + {% endblocktrans %} - {% endblocktrans %} + {% endif %} {% endif %} {% endwith %} diff --git a/bookwyrm/templates/notifications/items/fav.html b/bookwyrm/templates/notifications/items/fav.html index 4b1e90dd6d..98b1387b8d 100644 --- a/bookwyrm/templates/notifications/items/fav.html +++ b/bookwyrm/templates/notifications/items/fav.html @@ -85,7 +85,6 @@ {% endif %} {% else %} - {% if other_user_count == 0 %} {% blocktrans trimmed %} From 9e94a13acf59e113148f9d20e8cfbe971353b123 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 5 Jul 2022 12:37:35 -0700 Subject: [PATCH 18/40] Updates mention and reply notifications --- bookwyrm/templates/notifications/items/mention.html | 8 ++++---- bookwyrm/templates/notifications/items/reply.html | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bookwyrm/templates/notifications/items/mention.html b/bookwyrm/templates/notifications/items/mention.html index e4e78a11ed..8640526358 100644 --- a/bookwyrm/templates/notifications/items/mention.html +++ b/bookwyrm/templates/notifications/items/mention.html @@ -19,25 +19,25 @@ {% if related_status.status_type == 'Review' %} {% blocktrans trimmed %} - mentioned you in a review of {{ book_title }} + {{ related_user }} mentioned you in a review of {{ book_title }} {% endblocktrans %} {% elif related_status.status_type == 'Comment' %} {% blocktrans trimmed %} - mentioned you in a comment on {{ book_title }} + {{ related_user }} mentioned you in a comment on {{ book_title }} {% endblocktrans %} {% elif related_status.status_type == 'Quotation' %} {% blocktrans trimmed %} - mentioned you in a quote from {{ book_title }} + {{ related_user }} mentioned you in a quote from {{ book_title }} {% endblocktrans %} {% else %} {% blocktrans trimmed %} - mentioned you in a status + {{ related_user }} mentioned you in a status {% endblocktrans %} {% endif %} diff --git a/bookwyrm/templates/notifications/items/reply.html b/bookwyrm/templates/notifications/items/reply.html index 30e7eff78a..099e22078c 100644 --- a/bookwyrm/templates/notifications/items/reply.html +++ b/bookwyrm/templates/notifications/items/reply.html @@ -20,25 +20,25 @@ {% if related_status.reply_parent.status_type == 'Review' %} {% blocktrans trimmed %} - replied to your review of {{ book_title }} + {{ related_user }} replied to your review of {{ book_title }} {% endblocktrans %} {% elif related_status.reply_parent.status_type == 'Comment' %} {% blocktrans trimmed %} - replied to your comment on {{ book_title }} + {{ related_user }} replied to your comment on {{ book_title }} {% endblocktrans %} {% elif related_status.reply_parent.status_type == 'Quotation' %} {% blocktrans trimmed %} - replied to your quote from {{ book_title }} + {{ related_user }} replied to your quote from {{ book_title }} {% endblocktrans %} {% else %} {% blocktrans trimmed %} - replied to your status + {{ related_user }} replied to your status {% endblocktrans %} {% endif %} From 79f3382eceb4a34dfe75c1f5bc222d1db5a0983a Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 5 Jul 2022 12:48:10 -0700 Subject: [PATCH 19/40] Updates reports notification --- bookwyrm/templates/notifications/items/report.html | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/bookwyrm/templates/notifications/items/report.html b/bookwyrm/templates/notifications/items/report.html index 62fbd8ddc3..5e95a685cb 100644 --- a/bookwyrm/templates/notifications/items/report.html +++ b/bookwyrm/templates/notifications/items/report.html @@ -1,5 +1,5 @@ {% extends 'notifications/items/layout.html' %} - +{% load humanize %} {% load i18n %} {% block primary_link %}{% spaceless %} @@ -10,6 +10,10 @@ {% endblock %} {% block description %} - {% url 'settings-report' as path %} - {% blocktrans %}A new report needs moderation.{% endblocktrans %} + {% url 'settings-reports' as path %} + {% blocktrans trimmed count counter=notification.related_reports.count with display_count=notification.related_reports.count|intcomma %} + A new report needs moderation + {% plural %} + {{ display_count }} new reports need moderation + {% endblocktrans %} {% endblock %} From c65381adf9e687309934ec0e9604f806c66035f9 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 5 Jul 2022 12:49:02 -0700 Subject: [PATCH 20/40] Comma format count of other users in a notification --- bookwyrm/templates/notifications/items/layout.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bookwyrm/templates/notifications/items/layout.html b/bookwyrm/templates/notifications/items/layout.html index d236e55dd5..f874f0791a 100644 --- a/bookwyrm/templates/notifications/items/layout.html +++ b/bookwyrm/templates/notifications/items/layout.html @@ -1,4 +1,5 @@ {% load notification_page_tags %} +{% load humanize %} {% related_status notification as related_status %} {% with related_users=notification.related_users.all %} @@ -40,7 +41,7 @@ {% with related_user_link=related_users.first.local_path %} {% with second_user=related_users.all.1.display_name %} {% with second_user_link=related_users.all.1.local_path %} - {% with other_user_count=notification.related_users.count|add:"-1" %} + {% with other_user_count=notification.related_users.count|add:"-1"|intcomma %} {% block description %}{% endblock %} {% endwith %} {% endwith %} From 736d29ea201632bcd47bf57d40777499a369bff8 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 5 Jul 2022 13:05:01 -0700 Subject: [PATCH 21/40] Updates group leave and remove notifications --- bookwyrm/models/notification.py | 5 ++-- .../templates/notifications/items/accept.html | 4 ++- .../templates/notifications/items/invite.html | 8 ++++-- .../templates/notifications/items/leave.html | 4 ++- bookwyrm/views/group.py | 28 +++++++++---------- 5 files changed, 28 insertions(+), 21 deletions(-) diff --git a/bookwyrm/models/notification.py b/bookwyrm/models/notification.py index a11d53f851..53fea7157e 100644 --- a/bookwyrm/models/notification.py +++ b/bookwyrm/models/notification.py @@ -57,10 +57,11 @@ class Notification(BookWyrmModel): @transaction.atomic def notify(cls, user, related_user, **kwargs): """Create a notification""" - if not user.local or user == related_user: + if related_user and (not user.local or user == related_user): return notification, _ = cls.objects.get_or_create(user=user, **kwargs) - notification.related_users.add(related_user) + if related_user: + notification.related_users.add(related_user) notification.read = False notification.save() diff --git a/bookwyrm/templates/notifications/items/accept.html b/bookwyrm/templates/notifications/items/accept.html index 5f26008f47..76b66343bb 100644 --- a/bookwyrm/templates/notifications/items/accept.html +++ b/bookwyrm/templates/notifications/items/accept.html @@ -14,7 +14,9 @@ {% block description %} {% blocktrans trimmed with group_name=notification.related_group.name group_path=notification.related_group.local_path %} - accepted your invitation to join group "{{ group_name }}" + {{ related_user }} + accepted your invitation to join group + "{{ group_name }}" {% endblocktrans %} {% endblock %} diff --git a/bookwyrm/templates/notifications/items/invite.html b/bookwyrm/templates/notifications/items/invite.html index aff416b07c..ab92be5a1e 100644 --- a/bookwyrm/templates/notifications/items/invite.html +++ b/bookwyrm/templates/notifications/items/invite.html @@ -12,11 +12,15 @@ {% endblock %} {% block description %} + {% blocktrans trimmed with group_name=notification.related_group.name group_path=notification.related_group.local_path %} -invited you to join the group "{{ group_name }}" + {{ related_user }} + invited you to join the group + "{{ group_name }}" {% endblocktrans %} +
{% include 'snippets/join_invitation_buttons.html' with group=notification.related_group %}
-{% endblock %} +{% endblock %} diff --git a/bookwyrm/templates/notifications/items/leave.html b/bookwyrm/templates/notifications/items/leave.html index c17a1986ea..54f76d8df4 100644 --- a/bookwyrm/templates/notifications/items/leave.html +++ b/bookwyrm/templates/notifications/items/leave.html @@ -14,7 +14,9 @@ {% block description %} {% blocktrans trimmed with group_name=notification.related_group.name group_path=notification.related_group.local_path %} - has left your group "{{ group_name }}" + {{ related_user }} + has left your group + "{{ group_name }}" {% endblocktrans %} {% endblock %} diff --git a/bookwyrm/views/group.py b/bookwyrm/views/group.py index 9c282e48fd..2eabf80246 100644 --- a/bookwyrm/views/group.py +++ b/bookwyrm/views/group.py @@ -59,11 +59,11 @@ def post(self, request, group_id): model = apps.get_model("bookwyrm.Notification", require_ready=True) for field in form.changed_data: notification_type = ( - "GROUP_PRIVACY" + model.GROUP_PRIVACY if field == "privacy" - else "GROUP_NAME" + else model.GROUP_NAME if field == "name" - else "GROUP_DESCRIPTION" + else model.GROUP_DESCRIPTION if field == "description" else None ) @@ -71,9 +71,9 @@ def post(self, request, group_id): for membership in memberships: member = membership.user if member != request.user: - model.objects.create( - user=member, - related_user=request.user, + model.notify( + member, + request.user, related_group=user_group, notification_type=notification_type, ) @@ -244,24 +244,22 @@ def remove_member(request): memberships = models.GroupMember.objects.filter(group=group) model = apps.get_model("bookwyrm.Notification", require_ready=True) - notification_type = "LEAVE" if user == request.user else "REMOVE" + notification_type = models.LEAVE if user == request.user else model.REMOVE # let the other members know about it for membership in memberships: member = membership.user if member != request.user: - model.objects.create( - user=member, - related_user=user, + model.notify( + member, + user, related_group=group, notification_type=notification_type, ) # let the user (now ex-member) know as well, if they were removed - if notification_type == "REMOVE": - model.objects.create( - user=user, - related_group=group, - notification_type=notification_type, + if notification_type == model.REMOVE: + model.notify( + user, None, related_group=group, notification_type=notification_type ) return redirect(group.local_path) From 41f42e33ed3d271b575866c805242855fcf9dc39 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 5 Jul 2022 13:28:09 -0700 Subject: [PATCH 22/40] Moves list add notification into notification model Right now notifications are a mix of post-save signals and clauses in the save function of the model. I'm not actually sure which is better, but I'm moving them to signals where it's straightforward to be consistent. --- bookwyrm/models/list.py | 22 ---------------------- bookwyrm/models/notification.py | 32 +++++++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/bookwyrm/models/list.py b/bookwyrm/models/list.py index db4dc58062..f280f1e283 100644 --- a/bookwyrm/models/list.py +++ b/bookwyrm/models/list.py @@ -1,7 +1,6 @@ """ make a list of books!! """ import uuid -from django.apps import apps from django.core.exceptions import PermissionDenied from django.db import models from django.db.models import Q @@ -152,32 +151,11 @@ class ListItem(CollectionItemMixin, BookWyrmModel): def save(self, *args, **kwargs): """create a notification too""" - created = not bool(self.id) super().save(*args, **kwargs) # tick the updated date on the parent list self.book_list.updated_date = timezone.now() self.book_list.save(broadcast=False, update_fields=["updated_date"]) - list_owner = self.book_list.user - model = apps.get_model("bookwyrm.Notification", require_ready=True) - # create a notification if somoene ELSE added to a local user's list - if created and list_owner.local and list_owner != self.user: - model.objects.create( - user=list_owner, - related_user=self.user, - related_list_item=self, - notification_type=model.ADD, - ) - - if self.book_list.group: - for membership in self.book_list.group.memberships.all(): - if membership.user != self.user: - model.objects.create( - user=membership.user, - related_user=self.user, - related_list_item=self, - notification_type=model.ADD, - ) def raise_not_deletable(self, viewer): """the associated user OR the list owner can delete""" diff --git a/bookwyrm/models/notification.py b/bookwyrm/models/notification.py index 53fea7157e..726dc1beb6 100644 --- a/bookwyrm/models/notification.py +++ b/bookwyrm/models/notification.py @@ -2,7 +2,8 @@ from django.db import models, transaction from django.dispatch import receiver from .base_model import BookWyrmModel -from . import Boost, Favorite, GroupMemberInvitation, ImportJob, Report, Status, User +from . import Boost, Favorite, GroupMemberInvitation, ImportJob, ListItem, Report +from . import Status, User class Notification(BookWyrmModel): @@ -219,3 +220,32 @@ def notify_user_on_group_invite(sender, instance, *args, **kwargs): related_group=instance.group, notification_type=Notification.INVITE, ) + + +@receiver(models.signals.post_save, sender=ListItem) +@transaction.atomic +# pylint: disable=unused-argument +def notify_user_on_list_item_add(sender, instance, created, *args, **kwargs): + """Someone added to your list""" + if not created: + return + + list_owner = instance.book_list.user + # create a notification if somoene ELSE added to a local user's list + if list_owner.local and list_owner != instance.user: + Notification.notify( + list_owner, + instance.user, + related_list_item=instance, + notification_type=Notification.ADD + ) + + if instance.book_list.group: + for membership in instance.book_list.group.memberships.all(): + if membership.user != instance.user: + Notification.notify( + membership.user, + instance.user, + related_list_item=instance, + notification_type=Notification.ADD, + ) From 8cbf8f62c776663562d20c43391493774e26fd53 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 5 Jul 2022 14:15:12 -0700 Subject: [PATCH 23/40] List add notifications --- bookwyrm/models/list.py | 2 +- bookwyrm/models/notification.py | 37 ++++--- .../templates/notifications/items/add.html | 96 ++++++++++++++++--- 3 files changed, 108 insertions(+), 27 deletions(-) diff --git a/bookwyrm/models/list.py b/bookwyrm/models/list.py index f280f1e283..1c27c29129 100644 --- a/bookwyrm/models/list.py +++ b/bookwyrm/models/list.py @@ -150,7 +150,7 @@ class ListItem(CollectionItemMixin, BookWyrmModel): collection_field = "book_list" def save(self, *args, **kwargs): - """create a notification too""" + """Update the list's date""" super().save(*args, **kwargs) # tick the updated date on the parent list self.book_list.updated_date = timezone.now() diff --git a/bookwyrm/models/notification.py b/bookwyrm/models/notification.py index 726dc1beb6..e7443b43ce 100644 --- a/bookwyrm/models/notification.py +++ b/bookwyrm/models/notification.py @@ -66,6 +66,27 @@ def notify(cls, user, related_user, **kwargs): notification.read = False notification.save() + @classmethod + @transaction.atomic + def notify_list_item(cls, user, list_item): + """ Group the notifications around the list items, not the user """ + related_user = list_item.user + notification = cls.objects.filter( + user=user, + related_users=related_user, + related_list_items__book_list=list_item.book_list, + notification_type=Notification.ADD + ).first() + if not notification: + notification = cls.objects.create( + user=user, + notification_type=Notification.ADD + ) + notification.related_users.add(related_user) + notification.related_list_items.add(list_item) + notification.read = False + notification.save() + @classmethod def unnotify(cls, user, related_user, **kwargs): """Remove a user from a notification and delete it if that was the only user""" @@ -79,7 +100,6 @@ def unnotify(cls, user, related_user, **kwargs): @receiver(models.signals.post_save, sender=Favorite) -@transaction.atomic() # pylint: disable=unused-argument def notify_on_fav(sender, instance, *args, **kwargs): """someone liked your content, you ARE loved""" @@ -144,7 +164,6 @@ def notify_user_on_mention(sender, instance, *args, **kwargs): @receiver(models.signals.post_save, sender=Boost) -@transaction.atomic # pylint: disable=unused-argument def notify_user_on_boost(sender, instance, *args, **kwargs): """boosting a status""" @@ -210,7 +229,6 @@ def notify_admins_on_report(sender, instance, *args, **kwargs): @receiver(models.signals.post_save, sender=GroupMemberInvitation) -@transaction.atomic # pylint: disable=unused-argument def notify_user_on_group_invite(sender, instance, *args, **kwargs): """Cool kids club here we come""" @@ -233,19 +251,16 @@ def notify_user_on_list_item_add(sender, instance, created, *args, **kwargs): list_owner = instance.book_list.user # create a notification if somoene ELSE added to a local user's list if list_owner.local and list_owner != instance.user: - Notification.notify( + # keep the related_user singular, group the items + Notification.notify_list_item( list_owner, - instance.user, - related_list_item=instance, - notification_type=Notification.ADD + instance ) if instance.book_list.group: for membership in instance.book_list.group.memberships.all(): if membership.user != instance.user: - Notification.notify( + Notification.notify_list_item( membership.user, - instance.user, - related_list_item=instance, - notification_type=Notification.ADD, + instance ) diff --git a/bookwyrm/templates/notifications/items/add.html b/bookwyrm/templates/notifications/items/add.html index 6a0183ebe9..fdd480ee11 100644 --- a/bookwyrm/templates/notifications/items/add.html +++ b/bookwyrm/templates/notifications/items/add.html @@ -1,14 +1,16 @@ {% extends 'notifications/items/layout.html' %} - {% load i18n %} {% load utilities %} +{% load humanize %} {% block primary_link %}{% spaceless %} -{% if notification.related_list_item.approved %} - {{ notification.related_list_item.book_list.local_path }} +{% with related_list=notification.related_list_items.first.book_list %} +{% if related_list.curation != "curated" %} + {{ related_list.local_path }} {% else %} - {% url 'list-curate' notification.related_list_item.book_list.id %} + {% url 'list-curate' related_list.id %} {% endif %} +{% endwith %} {% endspaceless %}{% endblock %} {% block icon %} @@ -16,25 +18,89 @@ {% endblock %} {% block description %} -{% with book_path=notification.related_list_item.book.local_path %} -{% with book_title=notification.related_list_item.book|book_title %} -{% with list_name=notification.related_list_item.book_list.name %} +{% with related_list=notification.related_list_items.first.book_list %} +{% with book_path=notification.related_list_items.first.book.local_path %} +{% with book_title=notification.related_list_items.first.book|book_title %} +{% with second_book_path=notification.related_list_items.all.1.book.local_path %} +{% with second_book_title=notification.related_list_items.all.1.book|book_title %} +{% with list_name=related_list.name %} + +{% url 'list' related_list.id as list_path %} +{% url 'list-curate' related_list.id as list_curate_path %} - {% if notification.related_list_item.approved %} - {% blocktrans trimmed with list_path=notification.related_list_item.book_list.local_path %} +{% if notification.related_list_items.count == 1 %} + {% if related_list.curation != "curated" %} + {% blocktrans trimmed %} + {{ related_user }} + added {{ book_title }} + to your list "{{ list_name }}" + {% endblocktrans %} + {% else %} + {% blocktrans trimmed %} + {{ related_user }} + suggested adding {{ book_title }} + to your list "{{ list_name }}" + {% endblocktrans %} + {% endif %} +{% elif notification.related_list_items.count == 2 %} + {% if related_list.curation != "curated" %} + {% blocktrans trimmed %} + {{ related_user }} + added {{ book_title }} + and {{ second_book_title }} + to your list "{{ list_name }}" + {% endblocktrans %} + {% else %} + {% blocktrans trimmed %} + {{ related_user }} + suggested adding {{ book_title }} + and {{ second_book_title }} + to your list "{{ list_name }}" + {% endblocktrans %} + {% endif %} +{% else %} + {% with count=notification.related_list_items.count|add:"-2" %} + {% with display_count=count|intcomma %} + {% if related_list.curation != "curated" %} - added {{ book_title }} to your list "{{ list_name }}" + {% blocktrans trimmed count counter=count %} + {{ related_user }} + added {{ book_title }}, + {{ second_book_title }}, + and {{ display_count }} other book + to your list "{{ list_name }}" + {% plural %} + {{ related_user }} + added {{ book_title }}, + {{ second_book_title }}, + and {{ display_count }} other books + to your list "{{ list_name }}" + {% endblocktrans %} - {% endblocktrans %} {% else %} - {% url 'list-curate' notification.related_list_item.book_list.id as list_path %} - {% blocktrans trimmed with list_path=list_path %} - suggested adding {{ book_title }} to your list "{{ list_name }}" + {% blocktrans trimmed count counter=count %} + {{ related_user }} + suggested adding {{ book_title }}, + {{ second_book_title }}, + and {{ display_count }} other book + to your list "{{ list_name }}" + {% plural %} + {{ related_user }} + suggested adding {{ book_title }}, + {{ second_book_title }}, + and {{ display_count }} other books + to your list "{{ list_name }}" + {% endblocktrans %} - {% endblocktrans %} {% endif %} + {% endwith %} + {% endwith %} +{% endif %} +{% endwith %} +{% endwith %} +{% endwith %} {% endwith %} {% endwith %} {% endwith %} From 7508ae9eaef9b2216606ef9beff8ffdd9c81c786 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 5 Jul 2022 14:25:42 -0700 Subject: [PATCH 24/40] Separate vars for user count and display counts --- .../templates/notifications/items/boost.html | 8 ++++---- .../templates/notifications/items/fav.html | 8 ++++---- .../templates/notifications/items/follow.html | 19 +++++++++++++++++-- .../templates/notifications/items/layout.html | 9 ++++++--- 4 files changed, 31 insertions(+), 13 deletions(-) diff --git a/bookwyrm/templates/notifications/items/boost.html b/bookwyrm/templates/notifications/items/boost.html index d297de6f83..a1a0a24fbf 100644 --- a/bookwyrm/templates/notifications/items/boost.html +++ b/bookwyrm/templates/notifications/items/boost.html @@ -34,7 +34,7 @@ {% else %} {% blocktrans trimmed %} - {{ related_user }} and {{ other_user_count }} others boosted your review of {{ book_title }} + {{ related_user }} and {{ other_user_display_count }} others boosted your review of {{ book_title }} {% endblocktrans %} {% endif %} @@ -57,7 +57,7 @@ {% else %} {% blocktrans trimmed %} - {{ related_user }} and {{ other_user_count }} others boosted your comment on {{ book_title }} + {{ related_user }} and {{ other_user_display_count }} others boosted your comment on {{ book_title }} {% endblocktrans %} {% endif %} @@ -80,7 +80,7 @@ {% else %} {% blocktrans trimmed %} - {{ related_user }} and {{ other_user_count }} others boosted your quote from {{ book_title }} + {{ related_user }} and {{ other_user_display_count }} others boosted your quote from {{ book_title }} {% endblocktrans %} {% endif %} @@ -103,7 +103,7 @@ {% else %} {% blocktrans trimmed %} - {{ related_user }} and {{ other_user_count }} others boosted your status + {{ related_user }} and {{ other_user_display_count }} others boosted your status {% endblocktrans %} {% endif %} diff --git a/bookwyrm/templates/notifications/items/fav.html b/bookwyrm/templates/notifications/items/fav.html index 98b1387b8d..0bface201f 100644 --- a/bookwyrm/templates/notifications/items/fav.html +++ b/bookwyrm/templates/notifications/items/fav.html @@ -34,7 +34,7 @@ {% else %} {% blocktrans trimmed %} - {{ related_user }} and {{ other_user_count }} others liked your review of {{ book_title }} + {{ related_user }} and {{ other_user_display_count }} others liked your review of {{ book_title }} {% endblocktrans %} {% endif %} @@ -57,7 +57,7 @@ {% else %} {% blocktrans trimmed %} - {{ related_user }} and {{ other_user_count }} others liked your comment on {{ book_title }} + {{ related_user }} and {{ other_user_display_count }} others liked your comment on {{ book_title }} {% endblocktrans %} {% endif %} @@ -80,7 +80,7 @@ {% else %} {% blocktrans trimmed %} - {{ related_user }} and {{ other_user_count }} others liked your quote from {{ book_title }} + {{ related_user }} and {{ other_user_display_count }} others liked your quote from {{ book_title }} {% endblocktrans %} {% endif %} @@ -103,7 +103,7 @@ {% else %} {% blocktrans trimmed %} - {{ related_user }} and {{ other_user_count }} others liked your status + {{ related_user }} and {{ other_user_display_count }} others liked your status {% endblocktrans %} {% endif %} diff --git a/bookwyrm/templates/notifications/items/follow.html b/bookwyrm/templates/notifications/items/follow.html index 3518e7b1b7..96b3eddf19 100644 --- a/bookwyrm/templates/notifications/items/follow.html +++ b/bookwyrm/templates/notifications/items/follow.html @@ -12,6 +12,21 @@ {% endblock %} {% block description %} - {% trans "followed you" %} - {% include 'snippets/follow_button.html' with user=notification.related_user %} + +{% if related_user_count == 1 %} + {% blocktrans trimmed %} + {{ related_user }} followed you + {% endblocktrans %} +{% elif related_user_count == 2 %} + {% blocktrans trimmed %} + {{ related_user }} and + {{ second_user }} followed you + {% endblocktrans %} +{% else %} + {% blocktrans trimmed %} + {{ related_user }}, + {{ second_user }}, and {{ other_user_display_count }} users followed you + {% endblocktrans %} +{% endif %} + {% endblock %} diff --git a/bookwyrm/templates/notifications/items/layout.html b/bookwyrm/templates/notifications/items/layout.html index f874f0791a..5b1d76ac7b 100644 --- a/bookwyrm/templates/notifications/items/layout.html +++ b/bookwyrm/templates/notifications/items/layout.html @@ -3,6 +3,7 @@ {% related_status notification as related_status %} {% with related_users=notification.related_users.all %} +{% with related_user_count=notification.related_users.count %}
@@ -12,7 +13,7 @@
- {% if notification.related_users.count > 1 %} + {% if related_user_count > 1 %}
    {% for user in related_users|slice:10 %} @@ -27,7 +28,7 @@ {% endif %} From e54c563865ee1c326e0abe804559e9fffbcd0695 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 5 Jul 2022 14:44:15 -0700 Subject: [PATCH 25/40] Consistent use of plurals --- bookwyrm/models/list.py | 1 - bookwyrm/models/notification.py | 17 +++++------------ .../templates/notifications/items/follow.html | 6 ++---- .../templates/notifications/items/layout.html | 7 ++++--- 4 files changed, 11 insertions(+), 20 deletions(-) diff --git a/bookwyrm/models/list.py b/bookwyrm/models/list.py index 1c27c29129..63dd5b23f6 100644 --- a/bookwyrm/models/list.py +++ b/bookwyrm/models/list.py @@ -156,7 +156,6 @@ def save(self, *args, **kwargs): self.book_list.updated_date = timezone.now() self.book_list.save(broadcast=False, update_fields=["updated_date"]) - def raise_not_deletable(self, viewer): """the associated user OR the list owner can delete""" if self.book_list.user == viewer: diff --git a/bookwyrm/models/notification.py b/bookwyrm/models/notification.py index e7443b43ce..330bd5b72a 100644 --- a/bookwyrm/models/notification.py +++ b/bookwyrm/models/notification.py @@ -69,18 +69,17 @@ def notify(cls, user, related_user, **kwargs): @classmethod @transaction.atomic def notify_list_item(cls, user, list_item): - """ Group the notifications around the list items, not the user """ + """Group the notifications around the list items, not the user""" related_user = list_item.user notification = cls.objects.filter( user=user, related_users=related_user, related_list_items__book_list=list_item.book_list, - notification_type=Notification.ADD + notification_type=Notification.ADD, ).first() if not notification: notification = cls.objects.create( - user=user, - notification_type=Notification.ADD + user=user, notification_type=Notification.ADD ) notification.related_users.add(related_user) notification.related_list_items.add(list_item) @@ -252,15 +251,9 @@ def notify_user_on_list_item_add(sender, instance, created, *args, **kwargs): # create a notification if somoene ELSE added to a local user's list if list_owner.local and list_owner != instance.user: # keep the related_user singular, group the items - Notification.notify_list_item( - list_owner, - instance - ) + Notification.notify_list_item(list_owner, instance) if instance.book_list.group: for membership in instance.book_list.group.memberships.all(): if membership.user != instance.user: - Notification.notify_list_item( - membership.user, - instance - ) + Notification.notify_list_item(membership.user, instance) diff --git a/bookwyrm/templates/notifications/items/follow.html b/bookwyrm/templates/notifications/items/follow.html index 96b3eddf19..7303bb8b85 100644 --- a/bookwyrm/templates/notifications/items/follow.html +++ b/bookwyrm/templates/notifications/items/follow.html @@ -4,7 +4,7 @@ {% load utilities %} {% block primary_link %}{% spaceless %} - {{ notification.related_user.local_path }} +{% url 'user-followers' request.user.localname %} {% endspaceless %}{% endblock %} {% block icon %} @@ -12,7 +12,6 @@ {% endblock %} {% block description %} - {% if related_user_count == 1 %} {% blocktrans trimmed %} {{ related_user }} followed you @@ -24,8 +23,7 @@ {% endblocktrans %} {% else %} {% blocktrans trimmed %} - {{ related_user }}, - {{ second_user }}, and {{ other_user_display_count }} users followed you + {{ related_user }} and {{ other_user_display_count }} others followed you {% endblocktrans %} {% endif %} diff --git a/bookwyrm/templates/notifications/items/layout.html b/bookwyrm/templates/notifications/items/layout.html index 5b1d76ac7b..b19f87eddf 100644 --- a/bookwyrm/templates/notifications/items/layout.html +++ b/bookwyrm/templates/notifications/items/layout.html @@ -2,7 +2,7 @@ {% load humanize %} {% related_status notification as related_status %} -{% with related_users=notification.related_users.all %} +{% with related_users=notification.related_users.all.distinct %} {% with related_user_count=notification.related_users.count %}
    @@ -40,8 +40,8 @@ {% with related_user=related_users.first.display_name %} {% with related_user_link=related_users.first.local_path %} - {% with second_user=related_users.all.1.display_name %} - {% with second_user_link=related_users.all.1.local_path %} + {% with second_user=related_users.1.display_name %} + {% with second_user_link=related_users.1.local_path %} {% with other_user_count=related_user_count|add:"-1" %} {% with other_user_display_count=other_user_count|intcomma %} {% block description %}{% endblock %} @@ -63,3 +63,4 @@
    {% endwith %} +{% endwith %} From 7fe722b595a76c411192d72cff82e6bd079fdbfe Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 5 Jul 2022 15:03:20 -0700 Subject: [PATCH 26/40] Fixes follow request notifications Since the main way to interact with them is by approving them in the notification, I didn't group them --- bookwyrm/models/notification.py | 33 ++++++++++++++++++- bookwyrm/models/relationship.py | 11 ------- .../notifications/items/follow_request.html | 6 ++-- 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/bookwyrm/models/notification.py b/bookwyrm/models/notification.py index 330bd5b72a..59ba59d6cd 100644 --- a/bookwyrm/models/notification.py +++ b/bookwyrm/models/notification.py @@ -3,7 +3,7 @@ from django.dispatch import receiver from .base_model import BookWyrmModel from . import Boost, Favorite, GroupMemberInvitation, ImportJob, ListItem, Report -from . import Status, User +from . import Status, User, UserFollowRequest class Notification(BookWyrmModel): @@ -257,3 +257,34 @@ def notify_user_on_list_item_add(sender, instance, created, *args, **kwargs): for membership in instance.book_list.group.memberships.all(): if membership.user != instance.user: Notification.notify_list_item(membership.user, instance) + + +@receiver(models.signals.post_save, sender=UserFollowRequest) +@transaction.atomic +# pylint: disable=unused-argument +def notify_user_on_follow(sender, instance, created, *args, **kwargs): + """Someone added to your list""" + if not created or not instance.user_object.local: + return + + manually_approves = instance.user_object.manually_approves_followers + if manually_approves: + # don't group notifications + notification = Notification.objects.filter( + user=instance.user_object, + related_users=instance.user_subject, + notification_type=Notification.FOLLOW_REQUEST, + ).first() + if not notification: + notification = Notification.objects.create( + user=instance.user_object, notification_type=Notification.FOLLOW_REQUEST + ) + notification.related_users.set([instance.user_subject]) + notification.read = False + notification.save() + else: + Notification.notify( + instance.user_object, + instance.user_subject, + notification_type=Notification.FOLLOW, + ) diff --git a/bookwyrm/models/relationship.py b/bookwyrm/models/relationship.py index bc483805b1..082294c0e0 100644 --- a/bookwyrm/models/relationship.py +++ b/bookwyrm/models/relationship.py @@ -1,5 +1,4 @@ """ defines relationships between users """ -from django.apps import apps from django.core.cache import cache from django.db import models, transaction, IntegrityError from django.db.models import Q @@ -148,16 +147,6 @@ def save(self, *args, broadcast=True, **kwargs): # pylint: disable=arguments-di if not manually_approves: self.accept() - model = apps.get_model("bookwyrm.Notification", require_ready=True) - notification_type = ( - model.FOLLOW_REQUEST if manually_approves else model.FOLLOW - ) - model.notify( - self.user_object, - self.user_subject, - notification_type=notification_type, - ) - def get_accept_reject_id(self, status): """get id for sending an accept or reject of a local user""" diff --git a/bookwyrm/templates/notifications/items/follow_request.html b/bookwyrm/templates/notifications/items/follow_request.html index 9cec8116aa..804c352827 100644 --- a/bookwyrm/templates/notifications/items/follow_request.html +++ b/bookwyrm/templates/notifications/items/follow_request.html @@ -8,8 +8,10 @@ {% endblock %} {% block description %} - {% trans "sent you a follow request" %} + {% blocktrans trimmed %} + {{ related_user }} sent you a follow request + {% endblocktrans %}
    - {% include 'snippets/follow_request_buttons.html' with user=notification.related_user %} + {% include 'snippets/follow_request_buttons.html' with user=notification.related_users.first %}
    {% endblock %} From 37bcb031f73529502c2642a8decf0677a12fbafc Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 5 Jul 2022 16:03:25 -0700 Subject: [PATCH 27/40] Adds notification model unit tests --- bookwyrm/tests/models/test_list.py | 48 ++----- bookwyrm/tests/models/test_notification.py | 139 +++++++++++++++++++++ bookwyrm/tests/models/test_status_model.py | 12 -- 3 files changed, 152 insertions(+), 47 deletions(-) create mode 100644 bookwyrm/tests/models/test_notification.py diff --git a/bookwyrm/tests/models/test_list.py b/bookwyrm/tests/models/test_list.py index de6957b575..f7e64c6f2e 100644 --- a/bookwyrm/tests/models/test_list.py +++ b/bookwyrm/tests/models/test_list.py @@ -7,6 +7,7 @@ @patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async") +@patch("bookwyrm.lists_stream.remove_list_task.delay") class List(TestCase): """some activitypub oddness ahead""" @@ -21,25 +22,15 @@ def setUp(self): work = models.Work.objects.create(title="hello") self.book = models.Edition.objects.create(title="hi", 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.apply_async" - ), patch("bookwyrm.lists_stream.remove_list_task.delay"): - book_list = models.List.objects.create( - name="Test List", user=self.local_user - ) + book_list = models.List.objects.create(name="Test List", user=self.local_user) expected_id = f"https://{settings.DOMAIN}/list/{book_list.id}" self.assertEqual(book_list.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.apply_async" - ), patch("bookwyrm.lists_stream.remove_list_task.delay"): - book_list = models.List.objects.create( - name="Test List", user=self.local_user - ) + book_list = models.List.objects.create(name="Test List", user=self.local_user) activity_json = book_list.to_activity() self.assertIsInstance(activity_json, dict) self.assertEqual(activity_json["id"], book_list.remote_id) @@ -48,14 +39,11 @@ def test_to_activity(self, _): self.assertEqual(activity_json["name"], "Test List") self.assertEqual(activity_json["owner"], self.local_user.remote_id) - def test_list_item(self, _): + def test_list_item(self, *_): """a list entry""" - with patch( - "bookwyrm.models.activitypub_mixin.broadcast_task.apply_async" - ), patch("bookwyrm.lists_stream.remove_list_task.delay"): - book_list = models.List.objects.create( - name="Test List", user=self.local_user, privacy="unlisted" - ) + book_list = models.List.objects.create( + name="Test List", user=self.local_user, privacy="unlisted" + ) item = models.ListItem.objects.create( book_list=book_list, @@ -68,14 +56,9 @@ def test_list_item(self, _): self.assertEqual(item.privacy, "unlisted") self.assertEqual(item.recipients, []) - def test_list_item_pending(self, _): + def test_list_item_pending(self, *_): """a list entry""" - with patch( - "bookwyrm.models.activitypub_mixin.broadcast_task.apply_async" - ), patch("bookwyrm.lists_stream.remove_list_task.delay"): - book_list = models.List.objects.create( - name="Test List", user=self.local_user - ) + book_list = models.List.objects.create(name="Test List", user=self.local_user) item = models.ListItem.objects.create( book_list=book_list, @@ -90,13 +73,8 @@ def test_list_item_pending(self, _): self.assertEqual(item.privacy, "direct") self.assertEqual(item.recipients, []) - def test_embed_key(self, _): + def test_embed_key(self, *_): """embed_key should never be empty""" - with patch( - "bookwyrm.models.activitypub_mixin.broadcast_task.apply_async" - ), patch("bookwyrm.lists_stream.remove_list_task.delay"): - book_list = models.List.objects.create( - name="Test List", user=self.local_user - ) + book_list = models.List.objects.create(name="Test List", user=self.local_user) self.assertIsInstance(book_list.embed_key, UUID) diff --git a/bookwyrm/tests/models/test_notification.py b/bookwyrm/tests/models/test_notification.py new file mode 100644 index 0000000000..0c16741e1a --- /dev/null +++ b/bookwyrm/tests/models/test_notification.py @@ -0,0 +1,139 @@ +""" testing models """ +from unittest.mock import patch +from django.test import TestCase +from bookwyrm import models + + +class Notification(TestCase): + """let people know things""" + + def setUp(self): # pylint: disable=invalid-name + """useful things for creating a notification""" + 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", "mouse@mouse.mouse", "mouseword", local=True, localname="mouse" + ) + self.another_user = models.User.objects.create_user( + "rat", "rat@rat.rat", "ratword", local=True, localname="rat" + ) + 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", + 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", + isbn_13="1234567890123", + remote_id="https://example.com/book/1", + parent_work=self.work, + ) + self.another_book = models.Edition.objects.create( + title="Second Test Book", + parent_work=models.Work.objects.create(title="Test Work"), + ) + + def test_notification(self): + """New notifications are unread""" + notification = models.Notification.objects.create( + user=self.local_user, notification_type=models.Notification.FAVORITE + ) + self.assertFalse(notification.read) + + def test_notify(self): + """Create a notification""" + models.Notification.notify( + self.local_user, + self.remote_user, + notification_type=models.Notification.FAVORITE, + ) + self.assertTrue(models.Notification.objects.exists()) + + def test_notify_grouping(self): + """Bundle notifications""" + models.Notification.notify( + self.local_user, + self.remote_user, + notification_type=models.Notification.FAVORITE, + ) + self.assertEqual(models.Notification.objects.count(), 1) + notification = models.Notification.objects.get() + self.assertEqual(notification.related_users.count(), 1) + + models.Notification.notify( + self.local_user, + self.another_user, + notification_type=models.Notification.FAVORITE, + ) + self.assertEqual(models.Notification.objects.count(), 1) + notification.refresh_from_db() + self.assertEqual(notification.related_users.count(), 2) + + def test_notify_remote(self): + """Don't create notifications for remote users""" + models.Notification.notify( + self.remote_user, + self.local_user, + notification_type=models.Notification.FAVORITE, + ) + self.assertFalse(models.Notification.objects.exists()) + + def test_notify_self(self): + """Don't create notifications for yourself""" + models.Notification.notify( + self.local_user, + self.local_user, + notification_type=models.Notification.FAVORITE, + ) + self.assertFalse(models.Notification.objects.exists()) + + @patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async") + @patch("bookwyrm.lists_stream.remove_list_task.delay") + def test_notify_list_item_own_list(self, *_): + """Don't add list item notification for your own list""" + test_list = models.List.objects.create(user=self.local_user, name="hi") + + models.ListItem.objects.create( + user=self.local_user, book=self.book, book_list=test_list, order=1 + ) + self.assertFalse(models.Notification.objects.exists()) + + @patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async") + @patch("bookwyrm.lists_stream.remove_list_task.delay") + def test_notify_list_item_remote(self, *_): + """Don't add list item notification for a remote user""" + test_list = models.List.objects.create(user=self.remote_user, name="hi") + + models.ListItem.objects.create( + user=self.local_user, book=self.book, book_list=test_list, order=1 + ) + self.assertFalse(models.Notification.objects.exists()) + + @patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async") + @patch("bookwyrm.lists_stream.remove_list_task.delay") + def test_notify_list_item(self, *_): + """Add list item notification""" + test_list = models.List.objects.create(user=self.local_user, name="hi") + list_item = models.ListItem.objects.create( + user=self.remote_user, book=self.book, book_list=test_list, order=2 + ) + notification = models.Notification.objects.get() + self.assertEqual(notification.related_users.count(), 1) + self.assertEqual(notification.related_users.first(), self.remote_user) + self.assertEqual(notification.related_list_items.count(), 1) + self.assertEqual(notification.related_list_items.first(), list_item) + + models.ListItem.objects.create( + user=self.remote_user, book=self.another_book, book_list=test_list, order=3 + ) + notification = models.Notification.objects.get() + self.assertEqual(notification.related_users.count(), 1) + self.assertEqual(notification.related_users.first(), self.remote_user) + self.assertEqual(notification.related_list_items.count(), 2) diff --git a/bookwyrm/tests/models/test_status_model.py b/bookwyrm/tests/models/test_status_model.py index 2f1e8670f0..aae34b0a50 100644 --- a/bookwyrm/tests/models/test_status_model.py +++ b/bookwyrm/tests/models/test_status_model.py @@ -394,18 +394,6 @@ def test_boost(self, *_): self.assertEqual(activity["type"], "Announce") self.assertEqual(activity, boost.to_activity(pure=True)) - def test_notification(self, *_): - """a simple model""" - notification = models.Notification.objects.create( - user=self.local_user, notification_type="FAVORITE" - ) - self.assertFalse(notification.read) - - with self.assertRaises(IntegrityError): - models.Notification.objects.create( - user=self.local_user, notification_type="GLORB" - ) - # pylint: disable=unused-argument def test_create_broadcast(self, one, two, broadcast_mock, *_): """should send out two verions of a status on create""" From 24349b0a4cd047c1551981663013a8119d4ed423 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Tue, 5 Jul 2022 16:20:27 -0700 Subject: [PATCH 28/40] Updates notification view tests --- bookwyrm/tests/views/test_interaction.py | 4 ++-- bookwyrm/tests/views/test_notifications.py | 26 +++++++++++++--------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/bookwyrm/tests/views/test_interaction.py b/bookwyrm/tests/views/test_interaction.py index 1d729f9a51..74878df7d7 100644 --- a/bookwyrm/tests/views/test_interaction.py +++ b/bookwyrm/tests/views/test_interaction.py @@ -60,7 +60,7 @@ def test_favorite(self, *_): notification = models.Notification.objects.get() self.assertEqual(notification.notification_type, "FAVORITE") self.assertEqual(notification.user, self.local_user) - self.assertEqual(notification.related_user, self.remote_user) + self.assertEqual(notification.related_users.first(), self.remote_user) def test_unfavorite(self, *_): """unfav a status""" @@ -98,7 +98,7 @@ def test_boost(self, *_): notification = models.Notification.objects.get() self.assertEqual(notification.notification_type, "BOOST") self.assertEqual(notification.user, self.local_user) - self.assertEqual(notification.related_user, self.remote_user) + self.assertEqual(notification.related_users.first(), self.remote_user) self.assertEqual(notification.related_status, status) def test_self_boost(self, *_): diff --git a/bookwyrm/tests/views/test_notifications.py b/bookwyrm/tests/views/test_notifications.py index 2a5cf79840..eb422f80dc 100644 --- a/bookwyrm/tests/views/test_notifications.py +++ b/bookwyrm/tests/views/test_notifications.py @@ -25,10 +25,12 @@ def setUp(self): local=True, localname="mouse", ) + self.another_user = models.User.objects.create_user( + "rat", "rat@rat.rat", "ratword", local=True, localname="rat" + ) with patch("bookwyrm.models.activitypub_mixin.broadcast_task.apply_async"): self.status = models.Status.objects.create( - content="hi", - user=self.local_user, + content="hi", user=self.local_user ) models.SiteSettings.objects.create() @@ -44,25 +46,29 @@ def test_notifications_page_empty(self): def test_notifications_page_notifications(self): """there are so many views, this just makes sure it LOADS""" - models.Notification.objects.create( - user=self.local_user, + models.Notification.notify( + self.local_user, + self.another_user, notification_type="FAVORITE", related_status=self.status, ) - models.Notification.objects.create( - user=self.local_user, + models.Notification.notify( + self.local_user, + self.another_user, notification_type="BOOST", related_status=self.status, ) - models.Notification.objects.create( - user=self.local_user, + models.Notification.notify( + self.local_user, + self.another_user, notification_type="MENTION", related_status=self.status, ) self.status.reply_parent = self.status self.status.save(broadcast=False) - models.Notification.objects.create( - user=self.local_user, + models.Notification.notify( + self.local_user, + self.another_user, notification_type="REPLY", related_status=self.status, ) From b61a4ab99492dc2cfe111111897b12cefa5a9284 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 6 Jul 2022 08:51:35 -0700 Subject: [PATCH 29/40] Adds tests for unnotify --- bookwyrm/tests/models/test_notification.py | 44 ++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/bookwyrm/tests/models/test_notification.py b/bookwyrm/tests/models/test_notification.py index 0c16741e1a..3d6025a5ff 100644 --- a/bookwyrm/tests/models/test_notification.py +++ b/bookwyrm/tests/models/test_notification.py @@ -137,3 +137,47 @@ def test_notify_list_item(self, *_): self.assertEqual(notification.related_users.count(), 1) self.assertEqual(notification.related_users.first(), self.remote_user) self.assertEqual(notification.related_list_items.count(), 2) + + def test_unnotify(self): + """Remove a notification""" + models.Notification.notify( + self.local_user, + self.remote_user, + notification_type=models.Notification.FAVORITE, + ) + self.assertTrue(models.Notification.objects.exists()) + + models.Notification.unnotify( + self.local_user, + self.remote_user, + notification_type=models.Notification.FAVORITE, + ) + self.assertFalse(models.Notification.objects.exists()) + + def test_unnotify_multiple_users(self): + """Remove a notification""" + models.Notification.notify( + self.local_user, + self.remote_user, + notification_type=models.Notification.FAVORITE, + ) + models.Notification.notify( + self.local_user, + self.another_user, + notification_type=models.Notification.FAVORITE, + ) + self.assertTrue(models.Notification.objects.exists()) + + models.Notification.unnotify( + self.local_user, + self.remote_user, + notification_type=models.Notification.FAVORITE, + ) + self.assertTrue(models.Notification.objects.exists()) + + models.Notification.unnotify( + self.local_user, + self.another_user, + notification_type=models.Notification.FAVORITE, + ) + self.assertFalse(models.Notification.objects.exists()) From 2cda9d556740296c6382ce3f7360bbc3185e0d3c Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 6 Jul 2022 14:27:51 -0700 Subject: [PATCH 30/40] Test leaving a group (as opposed to being removed) This test will catch my typo in generating the notifications --- bookwyrm/tests/views/test_group.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/bookwyrm/tests/views/test_group.py b/bookwyrm/tests/views/test_group.py index e43b040ac2..06266c60af 100644 --- a/bookwyrm/tests/views/test_group.py +++ b/bookwyrm/tests/views/test_group.py @@ -257,6 +257,30 @@ def test_remove_member_existing_member(self, _): self.assertEqual(notification.related_group, self.testgroup) self.assertEqual(notification.notification_type, "REMOVE") + def test_remove_member_remove_self(self, _): + """Leave a group""" + models.GroupMember.objects.create( + user=self.rat, + group=self.testgroup, + ) + request = self.factory.post( + "", + { + "group": self.testgroup.id, + "user": self.rat.localname, + }, + ) + request.user = self.rat + result = views.remove_member(request) + self.assertEqual(result.status_code, 302) + self.assertEqual(models.GroupMember.objects.count(), 1) + self.assertEqual(models.GroupMember.objects.first().user, self.local_user) + notification = models.Notification.objects.get() + self.assertEqual(notification.user, self.local_user) + self.assertEqual(notification.related_group, self.testgroup) + self.assertEqual(notification.notification_type, "LEAVE") + + def test_accept_membership(self, _): """accept an invite""" models.GroupMemberInvitation.objects.create( From dda61263295dcafb3039449ca061acad53bf0a85 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 6 Jul 2022 14:31:53 -0700 Subject: [PATCH 31/40] Fixes typo in group notification --- bookwyrm/tests/views/test_group.py | 1 - bookwyrm/views/group.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/bookwyrm/tests/views/test_group.py b/bookwyrm/tests/views/test_group.py index 06266c60af..b0a0c925a2 100644 --- a/bookwyrm/tests/views/test_group.py +++ b/bookwyrm/tests/views/test_group.py @@ -280,7 +280,6 @@ def test_remove_member_remove_self(self, _): self.assertEqual(notification.related_group, self.testgroup) self.assertEqual(notification.notification_type, "LEAVE") - def test_accept_membership(self, _): """accept an invite""" models.GroupMemberInvitation.objects.create( diff --git a/bookwyrm/views/group.py b/bookwyrm/views/group.py index 2eabf80246..469f787d3e 100644 --- a/bookwyrm/views/group.py +++ b/bookwyrm/views/group.py @@ -244,7 +244,7 @@ def remove_member(request): memberships = models.GroupMember.objects.filter(group=group) model = apps.get_model("bookwyrm.Notification", require_ready=True) - notification_type = models.LEAVE if user == request.user else model.REMOVE + notification_type = model.LEAVE if user == request.user else model.REMOVE # let the other members know about it for membership in memberships: member = membership.user From 221b5138e80be60f428d2d1c9a5245e44daab514 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 6 Jul 2022 14:42:58 -0700 Subject: [PATCH 32/40] Adds merge migration --- bookwyrm/migrations/0153_merge_20220706_2141.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 bookwyrm/migrations/0153_merge_20220706_2141.py diff --git a/bookwyrm/migrations/0153_merge_20220706_2141.py b/bookwyrm/migrations/0153_merge_20220706_2141.py new file mode 100644 index 0000000000..03959f9ef8 --- /dev/null +++ b/bookwyrm/migrations/0153_merge_20220706_2141.py @@ -0,0 +1,13 @@ +# Generated by Django 3.2.13 on 2022-07-06 21:41 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("bookwyrm", "0152_alter_report_user"), + ("bookwyrm", "0152_remove_notification_notification_type_valid"), + ] + + operations = [] From d3023f350dfd7cac952f039def2722ff253ace5f Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 6 Jul 2022 14:56:02 -0700 Subject: [PATCH 33/40] Adds tests to site admin view --- bookwyrm/tests/views/admin/test_site.py | 80 +++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 bookwyrm/tests/views/admin/test_site.py diff --git a/bookwyrm/tests/views/admin/test_site.py b/bookwyrm/tests/views/admin/test_site.py new file mode 100644 index 0000000000..6b228cf864 --- /dev/null +++ b/bookwyrm/tests/views/admin/test_site.py @@ -0,0 +1,80 @@ +""" 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 bookwyrm import forms, models, views +from bookwyrm.tests.validate_html import validate_html + + +class SiteSettingsViews(TestCase): + """Edit site settings""" + + def setUp(self): + """we need basic test data and mocks""" + self.factory = RequestFactory() + 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", + ) + + self.site = models.SiteSettings.objects.create() + + def test_site_get(self): + """there are so many views, this just makes sure it LOADS""" + view = views.Site.as_view() + request = self.factory.get("") + request.user = self.local_user + request.user.is_superuser = True + result = view(request) + self.assertIsInstance(result, TemplateResponse) + validate_html(result.render()) + self.assertEqual(result.status_code, 200) + + def test_site_post(self): + """there are so many views, this just makes sure it LOADS""" + view = views.Site.as_view() + form = forms.SiteForm() + form.data["name"] = "Name!" + form.data["instance_tagline"] = "hi" + form.data["instance_description"] = "blah" + form.data["registration_closed_text"] = "blah" + form.data["invite_request_text"] = "blah" + form.data["code_of_conduct"] = "blah" + form.data["privacy_policy"] = "blah" + request = self.factory.post("", form.data) + request.user = self.local_user + request.user.is_superuser = True + + result = view(request) + + self.assertIsInstance(result, TemplateResponse) + validate_html(result.render()) + self.assertEqual(result.status_code, 200) + + site = models.SiteSettings.objects.get() + self.assertEqual(site.name, "Name!") + + def test_site_post_invalid(self): + """there are so many views, this just makes sure it LOADS""" + view = views.Site.as_view() + form = forms.SiteForm() + request = self.factory.post("", form.data) + request.user = self.local_user + request.user.is_superuser = True + + result = view(request) + + self.assertIsInstance(result, TemplateResponse) + validate_html(result.render()) + self.assertEqual(result.status_code, 200) + + self.site.refresh_from_db() + self.assertEqual(self.site.name, "BookWyrm") From 04f9b9180d5df192bc5ad6ae00d06fff8731ace3 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 6 Jul 2022 15:07:46 -0700 Subject: [PATCH 34/40] Adds test file for utils --- bookwyrm/tests/test_utils.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 bookwyrm/tests/test_utils.py diff --git a/bookwyrm/tests/test_utils.py b/bookwyrm/tests/test_utils.py new file mode 100644 index 0000000000..1835489751 --- /dev/null +++ b/bookwyrm/tests/test_utils.py @@ -0,0 +1,13 @@ +""" test searching for books """ +import re +from django.test import TestCase + +from bookwyrm.utils import regex + + +class TestUtils(TestCase): + """utility functions""" + + def test_regex(self): + """Regexes used throughout the app""" + self.assertTrue(re.match(regex.DOMAIN, "xn--69aa8bzb.xn--y9a3aq")) From 4bb3a7e4cdc93708b3f34798b749d48e7a533b6b Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 6 Jul 2022 15:27:48 -0700 Subject: [PATCH 35/40] Removed incorrect and duplicate page indicator from content status --- bookwyrm/templates/snippets/status/content_status.html | 3 --- 1 file changed, 3 deletions(-) diff --git a/bookwyrm/templates/snippets/status/content_status.html b/bookwyrm/templates/snippets/status/content_status.html index d0d3851536..bee4af1dc5 100644 --- a/bookwyrm/templates/snippets/status/content_status.html +++ b/bookwyrm/templates/snippets/status/content_status.html @@ -112,9 +112,6 @@

    {% with full=status.content|safe no_trim=status.content_warning itemprop="reviewBody" %} {% include 'snippets/trimmed_text.html' %} {% endwith %} - {% if status.progress %} -
    {% trans "page" %} {{ status.progress }}
    - {% endif %} {% endif %} {% if status.attachments.exists %} From 6a5323c6e7b9b8e5e344c0cecd44b1659a0a7170 Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 6 Jul 2022 19:11:52 -0700 Subject: [PATCH 36/40] More tests for more notification types --- bookwyrm/tests/views/test_notifications.py | 196 ++++++++++++++++++++- 1 file changed, 195 insertions(+), 1 deletion(-) diff --git a/bookwyrm/tests/views/test_notifications.py b/bookwyrm/tests/views/test_notifications.py index eb422f80dc..8e5dfa2b51 100644 --- a/bookwyrm/tests/views/test_notifications.py +++ b/bookwyrm/tests/views/test_notifications.py @@ -44,7 +44,7 @@ def test_notifications_page_empty(self): validate_html(result.render()) self.assertEqual(result.status_code, 200) - def test_notifications_page_notifications(self): + def test_notifications_page_status_notifications(self): """there are so many views, this just makes sure it LOADS""" models.Notification.notify( self.local_user, @@ -80,6 +80,200 @@ def test_notifications_page_notifications(self): validate_html(result.render()) self.assertEqual(result.status_code, 200) + def test_notifications_page_follow_request(self): + """import completed notification""" + models.Notification.notify( + self.local_user, + self.another_user, + notification_type="FOLLOW_REQUEST", + ) + view = views.Notifications.as_view() + request = self.factory.get("") + request.user = self.local_user + result = view(request) + self.assertIsInstance(result, TemplateResponse) + validate_html(result.render()) + + def test_notifications_page_follows(self): + """import completed notification""" + models.Notification.notify( + self.local_user, + self.another_user, + notification_type="FOLLOW", + ) + view = views.Notifications.as_view() + request = self.factory.get("") + request.user = self.local_user + result = view(request) + self.assertIsInstance(result, TemplateResponse) + validate_html(result.render()) + + def test_notifications_page_report(self): + """import completed notification""" + report = models.Report.objects.create( + user=self.another_user, + reporter=self.local_user, + ) + notification = models.Notification.objects.create( + user=self.local_user, + notification_type="REPORT", + ) + notification.related_reports.add(report) + + view = views.Notifications.as_view() + request = self.factory.get("") + request.user = self.local_user + result = view(request) + self.assertIsInstance(result, TemplateResponse) + validate_html(result.render()) + + def test_notifications_page_import(self): + """import completed notification""" + import_job = models.ImportJob.objects.create(user=self.local_user, mappings={}) + models.Notification.objects.create( + user=self.local_user, notification_type="IMPORT", related_import=import_job + ) + view = views.Notifications.as_view() + request = self.factory.get("") + request.user = self.local_user + result = view(request) + self.assertIsInstance(result, TemplateResponse) + validate_html(result.render()) + self.assertEqual(result.status_code, 200) + + def test_notifications_page_list(self): + """Adding books to lists""" + book = models.Edition.objects.create(title="shape") + with patch( + "bookwyrm.models.activitypub_mixin.broadcast_task.apply_async" + ), patch("bookwyrm.lists_stream.remove_list_task.delay"): + book_list = models.List.objects.create(user=self.local_user, name="hi") + item = models.ListItem.objects.create( + book=book, user=self.another_user, book_list=book_list, order=1 + ) + models.Notification.notify_list_item(self.local_user, item) + view = views.Notifications.as_view() + request = self.factory.get("") + request.user = self.local_user + result = view(request) + self.assertIsInstance(result, TemplateResponse) + validate_html(result.render()) + self.assertEqual(result.status_code, 200) + + def test_notifications_page_group_invite(self): + """group related notifications""" + group = models.Group.objects.create(user=self.another_user, name="group") + models.Notification.notify( + self.local_user, + self.another_user, + notification_type="INVITE", + related_group=group, + ) + view = views.Notifications.as_view() + request = self.factory.get("") + request.user = self.local_user + result = view(request) + self.assertIsInstance(result, TemplateResponse) + validate_html(result.render()) + self.assertEqual(result.status_code, 200) + + def test_notifications_page_group_accept(self): + """group related notifications""" + group = models.Group.objects.create(user=self.another_user, name="group") + models.Notification.notify( + self.local_user, + self.another_user, + notification_type="ACCEPT", + related_group=group, + ) + view = views.Notifications.as_view() + request = self.factory.get("") + request.user = self.local_user + result = view(request) + self.assertIsInstance(result, TemplateResponse) + validate_html(result.render()) + self.assertEqual(result.status_code, 200) + + def test_notifications_page_group_join(self): + """group related notifications""" + group = models.Group.objects.create(user=self.another_user, name="group") + models.Notification.notify( + self.local_user, + self.another_user, + notification_type="JOIN", + related_group=group, + ) + view = views.Notifications.as_view() + request = self.factory.get("") + request.user = self.local_user + result = view(request) + self.assertIsInstance(result, TemplateResponse) + validate_html(result.render()) + self.assertEqual(result.status_code, 200) + + def test_notifications_page_group_leave(self): + """group related notifications""" + group = models.Group.objects.create(user=self.another_user, name="group") + models.Notification.notify( + self.local_user, + self.another_user, + notification_type="LEAVE", + related_group=group, + ) + view = views.Notifications.as_view() + request = self.factory.get("") + request.user = self.local_user + result = view(request) + self.assertIsInstance(result, TemplateResponse) + validate_html(result.render()) + self.assertEqual(result.status_code, 200) + + def test_notifications_page_group_remove(self): + """group related notifications""" + group = models.Group.objects.create(user=self.another_user, name="group") + models.Notification.notify( + self.local_user, + self.another_user, + notification_type="REMOVE", + related_group=group, + ) + view = views.Notifications.as_view() + request = self.factory.get("") + request.user = self.local_user + result = view(request) + self.assertIsInstance(result, TemplateResponse) + validate_html(result.render()) + self.assertEqual(result.status_code, 200) + + def test_notifications_page_group_changes(self): + """group related notifications""" + group = models.Group.objects.create(user=self.another_user, name="group") + models.Notification.notify( + self.local_user, + self.another_user, + notification_type="GROUP_PRIVACY", + related_group=group, + ) + models.Notification.notify( + self.local_user, + self.another_user, + notification_type="GROUP_NAME", + related_group=group, + ) + models.Notification.notify( + self.local_user, + self.another_user, + notification_type="GROUP_DESCRIPTION", + related_group=group, + ) + view = views.Notifications.as_view() + request = self.factory.get("") + request.user = self.local_user + result = view(request) + self.assertIsInstance(result, TemplateResponse) + validate_html(result.render()) + self.assertEqual(result.status_code, 200) + def test_clear_notifications(self): """erase notifications""" models.Notification.objects.create( From 26a1f75e91e1de752f5b0d39fadbff89f6c0180a Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 6 Jul 2022 19:15:48 -0700 Subject: [PATCH 37/40] Group notification types by feature --- bookwyrm/models/notification.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/bookwyrm/models/notification.py b/bookwyrm/models/notification.py index 59ba59d6cd..21a992b07d 100644 --- a/bookwyrm/models/notification.py +++ b/bookwyrm/models/notification.py @@ -9,16 +9,27 @@ class Notification(BookWyrmModel): """you've been tagged, liked, followed, etc""" + # Status interactions FAVORITE = "FAVORITE" + BOOST = "BOOST" REPLY = "REPLY" MENTION = "MENTION" TAG = "TAG" + + # Relationships FOLLOW = "FOLLOW" FOLLOW_REQUEST = "FOLLOW_REQUEST" - BOOST = "BOOST" + + # Imports IMPORT = "IMPORT" + + # List activity ADD = "ADD" + + # Admin REPORT = "REPORT" + + # Groups INVITE = "INVITE" ACCEPT = "ACCEPT" JOIN = "JOIN" From cded3e973dfbcae309b79e486000f6f29d6947be Mon Sep 17 00:00:00 2001 From: Mouse Reeve Date: Wed, 6 Jul 2022 19:16:14 -0700 Subject: [PATCH 38/40] Fixes html on notifications page --- .../notifications/items/follow_request.html | 4 ++ .../templates/notifications/items/layout.html | 46 +++++++++---------- .../templates/notifications/items/report.html | 1 + bookwyrm/templates/search/barcode_modal.html | 4 +- bookwyrm/views/notifications.py | 3 +- 5 files changed, 31 insertions(+), 27 deletions(-) diff --git a/bookwyrm/templates/notifications/items/follow_request.html b/bookwyrm/templates/notifications/items/follow_request.html index 804c352827..320c69e430 100644 --- a/bookwyrm/templates/notifications/items/follow_request.html +++ b/bookwyrm/templates/notifications/items/follow_request.html @@ -3,6 +3,10 @@ {% load i18n %} {% load utilities %} +{% block primary_link %}{% spaceless %} +{% url 'user-followers' request.user.localname %} +{% endspaceless %}{% endblock %} + {% block icon %} {% endblock %} diff --git a/bookwyrm/templates/notifications/items/layout.html b/bookwyrm/templates/notifications/items/layout.html index b19f87eddf..3830e7e40a 100644 --- a/bookwyrm/templates/notifications/items/layout.html +++ b/bookwyrm/templates/notifications/items/layout.html @@ -27,31 +27,29 @@

{% endif %}
-

- {% if related_user_count == 1 %} - {% with user=related_users.first %} - {% spaceless %} - - {% include 'snippets/avatar.html' with user=user %} - - {% endspaceless %} - {% endwith %} - {% endif %} - - {% with related_user=related_users.first.display_name %} - {% with related_user_link=related_users.first.local_path %} - {% with second_user=related_users.1.display_name %} - {% with second_user_link=related_users.1.local_path %} - {% with other_user_count=related_user_count|add:"-1" %} - {% with other_user_display_count=other_user_count|intcomma %} - {% block description %}{% endblock %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} - {% endwith %} + {% if related_user_count == 1 %} + {% with user=related_users.first %} + {% spaceless %} + + {% include 'snippets/avatar.html' with user=user %} + + {% endspaceless %} {% endwith %} -

+ {% endif %} + + {% with related_user=related_users.first.display_name %} + {% with related_user_link=related_users.first.local_path %} + {% with second_user=related_users.1.display_name %} + {% with second_user_link=related_users.1.local_path %} + {% with other_user_count=related_user_count|add:"-1" %} + {% with other_user_display_count=other_user_count|intcomma %} + {% block description %}{% endblock %} + {% endwith %} + {% endwith %} + {% endwith %} + {% endwith %} + {% endwith %} + {% endwith %}
{% if related_status %} diff --git a/bookwyrm/templates/notifications/items/report.html b/bookwyrm/templates/notifications/items/report.html index 5e95a685cb..44756ce167 100644 --- a/bookwyrm/templates/notifications/items/report.html +++ b/bookwyrm/templates/notifications/items/report.html @@ -3,6 +3,7 @@ {% load i18n %} {% block primary_link %}{% spaceless %} +{% url 'settings-reports' %} {% endspaceless %}{% endblock %} {% block icon %} diff --git a/bookwyrm/templates/search/barcode_modal.html b/bookwyrm/templates/search/barcode_modal.html index 07e95f59ef..70481b20a8 100644 --- a/bookwyrm/templates/search/barcode_modal.html +++ b/bookwyrm/templates/search/barcode_modal.html @@ -16,11 +16,11 @@
- +