mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-11-25 11:01:12 +00:00
Merge pull request #2082 from bookwyrm-social/notifications
Notifications refactor
This commit is contained in:
commit
0b7c8e8dc0
29 changed files with 1168 additions and 296 deletions
90
bookwyrm/migrations/0151_auto_20220705_0049.py
Normal file
90
bookwyrm/migrations/0151_auto_20220705_0049.py
Normal file
|
@ -0,0 +1,90 @@
|
|||
# Generated by Django 3.2.13 on 2022-07-05 00:49
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("bookwyrm", "0150_readthrough_stopped_date"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name="notification",
|
||||
name="related_book",
|
||||
),
|
||||
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_users",
|
||||
field=models.ManyToManyField(
|
||||
related_name="notifications", to=settings.AUTH_USER_MODEL
|
||||
),
|
||||
),
|
||||
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_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_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_list_item",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="notification",
|
||||
name="related_report",
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name="notification",
|
||||
name="related_user",
|
||||
),
|
||||
]
|
|
@ -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",
|
||||
),
|
||||
]
|
13
bookwyrm/migrations/0153_merge_20220706_2141.py
Normal file
13
bookwyrm/migrations/0153_merge_20220706_2141.py
Normal file
|
@ -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 = []
|
|
@ -3,7 +3,7 @@ from functools import reduce
|
|||
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=notification_model.REPORT, read=False
|
||||
)
|
||||
notification.related_repors.add(reports)
|
||||
|
||||
|
||||
def automod_users(reporter):
|
||||
|
|
|
@ -140,16 +140,6 @@ class GroupMemberInvitation(models.Model):
|
|||
# make an invitation
|
||||
super().save(*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,
|
||||
related_group=self.group,
|
||||
notification_type=notification_type,
|
||||
)
|
||||
|
||||
@transaction.atomic
|
||||
def accept(self):
|
||||
"""turn this request into the real deal"""
|
||||
|
@ -157,25 +147,24 @@ class GroupMemberInvitation(models.Model):
|
|||
|
||||
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",
|
||||
notification_type=model.ACCEPT,
|
||||
)
|
||||
|
||||
# let the other members know about it
|
||||
for membership in self.group.memberships.all():
|
||||
member = membership.user
|
||||
if member not in (self.user, self.group.user):
|
||||
model.objects.create(
|
||||
user=member,
|
||||
related_user=self.user,
|
||||
model.notify(
|
||||
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()
|
||||
|
|
|
@ -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
|
||||
|
@ -151,34 +150,12 @@ class ListItem(CollectionItemMixin, BookWyrmModel):
|
|||
collection_field = "book_list"
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""create a notification too"""
|
||||
created = not bool(self.id)
|
||||
"""Update the list's date"""
|
||||
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="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="ADD",
|
||||
)
|
||||
|
||||
def raise_not_deletable(self, viewer):
|
||||
"""the associated user OR the list owner can delete"""
|
||||
if self.book_list.user == viewer:
|
||||
|
|
|
@ -1,77 +1,123 @@
|
|||
""" 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
|
||||
|
||||
# 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",
|
||||
)
|
||||
from . import Boost, Favorite, GroupMemberInvitation, ImportJob, ListItem, Report
|
||||
from . import Status, User, UserFollowRequest
|
||||
|
||||
|
||||
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"
|
||||
|
||||
# Imports
|
||||
IMPORT = "IMPORT"
|
||||
|
||||
# List activity
|
||||
ADD = "ADD"
|
||||
|
||||
# Admin
|
||||
REPORT = "REPORT"
|
||||
|
||||
# Groups
|
||||
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)
|
||||
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"
|
||||
read = models.BooleanField(default=False)
|
||||
notification_type = models.CharField(
|
||||
max_length=255, choices=NotificationType.choices
|
||||
)
|
||||
|
||||
related_users = models.ManyToManyField(
|
||||
"User", 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)
|
||||
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
|
||||
related_list_items = models.ManyToManyField(
|
||||
"ListItem", symmetrical=False, related_name="notifications"
|
||||
)
|
||||
related_reports = models.ManyToManyField("Report", symmetrical=False)
|
||||
|
||||
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_book=self.related_book,
|
||||
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():
|
||||
@classmethod
|
||||
@transaction.atomic
|
||||
def notify(cls, user, related_user, **kwargs):
|
||||
"""Create a notification"""
|
||||
if related_user and (not user.local or user == related_user):
|
||||
return
|
||||
super().save(*args, **kwargs)
|
||||
notification, _ = cls.objects.get_or_create(user=user, **kwargs)
|
||||
if related_user:
|
||||
notification.related_users.add(related_user)
|
||||
notification.read = False
|
||||
notification.save()
|
||||
|
||||
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",
|
||||
@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"""
|
||||
try:
|
||||
notification = cls.objects.filter(user=user, **kwargs).get()
|
||||
except Notification.DoesNotExist:
|
||||
return
|
||||
notification.related_users.remove(related_user)
|
||||
if not notification.related_users.count():
|
||||
notification.delete()
|
||||
|
||||
|
||||
@receiver(models.signals.post_save, sender=Favorite)
|
||||
# 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(
|
||||
user=instance.status.user,
|
||||
notification_type="FAVORITE",
|
||||
related_user=instance.user,
|
||||
Notification.notify(
|
||||
instance.status.user,
|
||||
instance.user,
|
||||
related_status=instance.status,
|
||||
notification_type=Notification.FAVORITE,
|
||||
)
|
||||
|
||||
|
||||
|
@ -81,15 +127,16 @@ 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,
|
||||
Notification.unnotify(
|
||||
instance.status.user,
|
||||
instance.user,
|
||||
related_status=instance.status,
|
||||
notification_type="FAVORITE",
|
||||
).delete()
|
||||
notification_type=Notification.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"""
|
||||
|
@ -105,22 +152,23 @@ 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=Notification.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_type="MENTION",
|
||||
related_user=instance.user,
|
||||
Notification.notify(
|
||||
mention_user,
|
||||
instance.user,
|
||||
notification_type=Notification.MENTION,
|
||||
related_status=instance,
|
||||
)
|
||||
|
||||
|
@ -135,11 +183,11 @@ 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",
|
||||
notification_type=Notification.BOOST,
|
||||
)
|
||||
|
||||
|
||||
|
@ -147,12 +195,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()
|
||||
notification_type=Notification.BOOST,
|
||||
)
|
||||
|
||||
|
||||
@receiver(models.signals.post_save, sender=ImportJob)
|
||||
|
@ -166,12 +214,13 @@ def notify_user_on_import_complete(
|
|||
return
|
||||
Notification.objects.create(
|
||||
user=instance.user,
|
||||
notification_type="IMPORT",
|
||||
notification_type=Notification.IMPORT,
|
||||
related_import=instance,
|
||||
)
|
||||
|
||||
|
||||
@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"""
|
||||
|
@ -181,8 +230,72 @@ 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_type=Notification.REPORT,
|
||||
read=False,
|
||||
)
|
||||
notification.related_reports.add(instance)
|
||||
|
||||
|
||||
@receiver(models.signals.post_save, sender=GroupMemberInvitation)
|
||||
# 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,
|
||||
)
|
||||
|
||||
|
||||
@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:
|
||||
# keep the related_user singular, group the items
|
||||
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)
|
||||
|
||||
|
||||
@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,
|
||||
)
|
||||
|
|
|
@ -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,14 +147,6 @@ class UserFollowRequest(ActivitypubMixin, UserRelationship):
|
|||
if not manually_approves:
|
||||
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=notification_type,
|
||||
)
|
||||
|
||||
def get_accept_reject_id(self, status):
|
||||
"""get id for sending an accept or reject of a local user"""
|
||||
|
||||
|
|
|
@ -13,8 +13,34 @@
|
|||
|
||||
{% block description %}
|
||||
|
||||
{% if other_user_count == 0 %}
|
||||
|
||||
{% blocktrans trimmed with group_name=notification.related_group.name group_path=notification.related_group.local_path %}
|
||||
accepted your invitation to join group "<a href="{{ group_path }}">{{ group_name }}</a>"
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a>
|
||||
accepted your invitation to join group
|
||||
"<a href="{{ group_path }}">{{ group_name }}</a>"
|
||||
{% endblocktrans %}
|
||||
|
||||
{% elif other_user_count == 1 %}
|
||||
|
||||
{% blocktrans trimmed with group_name=notification.related_group.name group_path=notification.related_group.local_path %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a>
|
||||
and
|
||||
<a href="{{ second_user_link }}">{{ second_user }}</a>
|
||||
accepted your invitation to join group
|
||||
"<a href="{{ group_path }}">{{ group_name }}</a>"
|
||||
{% endblocktrans %}
|
||||
|
||||
{% else %}
|
||||
|
||||
{% blocktrans trimmed with group_name=notification.related_group.name group_path=notification.related_group.local_path %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a>
|
||||
and
|
||||
{{ other_user_display_count }} others
|
||||
accepted your invitation to join group
|
||||
"<a href="{{ group_path }}">{{ group_name }}</a>"
|
||||
{% endblocktrans %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -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 %}
|
||||
|
||||
{% if notification.related_list_item.approved %}
|
||||
{% blocktrans trimmed with list_path=notification.related_list_item.book_list.local_path %}
|
||||
{% url 'list' related_list.id as list_path %}
|
||||
{% url 'list-curate' related_list.id as list_curate_path %}
|
||||
|
||||
added <em><a href="{{ book_path }}">{{ book_title }}</a></em> to your list "<a href="{{ list_path }}">{{ list_name }}</a>"
|
||||
|
||||
{% endblocktrans %}
|
||||
{% if notification.related_list_items.count == 1 %}
|
||||
{% if related_list.curation != "curated" %}
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a>
|
||||
added <em><a href="{{ book_path }}">{{ book_title }}</a></em>
|
||||
to your list "<a href="{{ list_path }}">{{ list_name }}</a>"
|
||||
{% endblocktrans %}
|
||||
{% else %}
|
||||
{% url 'list-curate' notification.related_list_item.book_list.id as list_path %}
|
||||
{% blocktrans trimmed with list_path=list_path %}
|
||||
|
||||
suggested adding <em><a href="{{ book_path }}">{{ book_title }}</a></em> to your list "<a href="{{ list_path }}">{{ list_name }}</a>"
|
||||
|
||||
{% endblocktrans %}
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a>
|
||||
suggested adding <em><a href="{{ book_path }}">{{ book_title }}</a></em>
|
||||
to your list "<a href="{{ list_curate_path }}">{{ list_name }}</a>"
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% elif notification.related_list_items.count == 2 %}
|
||||
{% if related_list.curation != "curated" %}
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a>
|
||||
added <em><a href="{{ book_path }}">{{ book_title }}</a></em>
|
||||
and <em><a href="{{ second_book_path }}">{{ second_book_title }}</a></em>
|
||||
to your list "<a href="{{ list_path }}">{{ list_name }}</a>"
|
||||
{% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a>
|
||||
suggested adding <em><a href="{{ book_path }}">{{ book_title }}</a></em>
|
||||
and <em><a href="{{ second_book_path }}">{{ second_book_title }}</a></em>
|
||||
to your list "<a href="{{ list_curate_path }}">{{ list_name }}</a>"
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% with count=notification.related_list_items.count|add:"-2" %}
|
||||
{% with display_count=count|intcomma %}
|
||||
{% if related_list.curation != "curated" %}
|
||||
|
||||
{% blocktrans trimmed count counter=count %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a>
|
||||
added <em><a href="{{ book_path }}">{{ book_title }}</a></em>,
|
||||
<em><a href="{{ second_book_path }}">{{ second_book_title }}</a></em>,
|
||||
and {{ display_count }} other book
|
||||
to your list "<a href="{{ list_path }}">{{ list_name }}</a>"
|
||||
{% plural %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a>
|
||||
added <em><a href="{{ book_path }}">{{ book_title }}</a></em>,
|
||||
<em><a href="{{ second_book_path }}">{{ second_book_title }}</a></em>,
|
||||
and {{ display_count }} other books
|
||||
to your list "<a href="{{ list_path }}">{{ list_name }}</a>"
|
||||
{% endblocktrans %}
|
||||
|
||||
{% else %}
|
||||
|
||||
{% blocktrans trimmed count counter=count %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a>
|
||||
suggested adding <em><a href="{{ book_path }}">{{ book_title }}</a></em>,
|
||||
<em><a href="{{ second_book_path }}">{{ second_book_title }}</a></em>,
|
||||
and {{ display_count }} other book
|
||||
to your list "<a href="{{ list_curate_path }}">{{ list_name }}</a>"
|
||||
{% plural %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a>
|
||||
suggested adding <em><a href="{{ book_path }}">{{ book_title }}</a></em>,
|
||||
<em><a href="{{ second_book_path }}">{{ second_book_title }}</a></em>,
|
||||
and {{ display_count }} other books
|
||||
to your list "<a href="{{ list_curate_path }}">{{ list_name }}</a>"
|
||||
{% endblocktrans %}
|
||||
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
|
|
|
@ -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 <a href="{{ related_path }}">review of <em>{{ book_title }}</em></a>
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a> boosted your <a href="{{ related_path }}">review of <em>{{ book_title }}</em></a>
|
||||
{% endblocktrans %}
|
||||
|
||||
{% endblocktrans %}
|
||||
{% elif other_user_count == 1 %}
|
||||
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a>
|
||||
and
|
||||
<a href="{{ second_user_link }}">{{ second_user }}</a>
|
||||
boosted your <a href="{{ related_path }}">review of <em>{{ book_title }}</em></a>
|
||||
{% endblocktrans %}
|
||||
|
||||
{% else %}
|
||||
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a> and {{ other_user_display_count }} others boosted your <a href="{{ related_path }}">review of <em>{{ book_title }}</em></a>
|
||||
{% endblocktrans %}
|
||||
|
||||
{% endif %}
|
||||
{% elif related_status.status_type == 'Comment' %}
|
||||
{% blocktrans trimmed %}
|
||||
{% if other_user_count == 0 %}
|
||||
|
||||
boosted your <a href="{{ related_path }}">comment on<em>{{ book_title }}</em></a>
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a> boosted your <a href="{{ related_path }}">comment on <em>{{ book_title }}</em></a>
|
||||
{% endblocktrans %}
|
||||
|
||||
{% endblocktrans %}
|
||||
{% elif other_user_count == 1 %}
|
||||
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a>
|
||||
and
|
||||
<a href="{{ second_user_link }}">{{ second_user }}</a>
|
||||
boosted your <a href="{{ related_path }}">comment on <em>{{ book_title }}</em></a>
|
||||
{% endblocktrans %}
|
||||
|
||||
{% else %}
|
||||
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a> and {{ other_user_display_count }} others boosted your <a href="{{ related_path }}">comment on <em>{{ book_title }}</em></a>
|
||||
{% endblocktrans %}
|
||||
|
||||
{% endif %}
|
||||
{% elif related_status.status_type == 'Quotation' %}
|
||||
{% blocktrans trimmed %}
|
||||
{% if other_user_count == 0 %}
|
||||
|
||||
boosted your <a href="{{ related_path }}">quote from <em>{{ book_title }}</em></a>
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a> boosted your <a href="{{ related_path }}">quote from <em>{{ book_title }}</em></a>
|
||||
{% endblocktrans %}
|
||||
|
||||
{% endblocktrans %}
|
||||
{% elif other_user_count == 1 %}
|
||||
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a>
|
||||
and
|
||||
<a href="{{ second_user_link }}">{{ second_user }}</a>
|
||||
boosted your <a href="{{ related_path }}">quote from <em>{{ book_title }}</em></a>
|
||||
{% endblocktrans %}
|
||||
|
||||
{% else %}
|
||||
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a> and {{ other_user_display_count }} others boosted your <a href="{{ related_path }}">quote from <em>{{ book_title }}</em></a>
|
||||
{% endblocktrans %}
|
||||
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% blocktrans trimmed %}
|
||||
{% if other_user_count == 0 %}
|
||||
|
||||
boosted your <a href="{{ related_path }}">status</a>
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a> boosted your <a href="{{ related_path }}">status</a>
|
||||
{% endblocktrans %}
|
||||
|
||||
{% endblocktrans %}
|
||||
{% elif other_user_count == 1 %}
|
||||
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a>
|
||||
and
|
||||
<a href="{{ second_user_link }}">{{ second_user }}</a>
|
||||
boosted your <a href="{{ related_path }}">status</a>
|
||||
{% endblocktrans %}
|
||||
|
||||
{% else %}
|
||||
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a> and {{ other_user_display_count }} others boosted your <a href="{{ related_path }}">status</a>
|
||||
{% endblocktrans %}
|
||||
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% endwith %}
|
||||
|
|
|
@ -16,29 +16,98 @@
|
|||
{% with related_status.local_path as related_path %}
|
||||
|
||||
{% if related_status.status_type == 'Review' %}
|
||||
{% blocktrans trimmed %}
|
||||
{% if other_user_count == 0 %}
|
||||
|
||||
liked your <a href="{{ related_path }}">review of <em>{{ book_title }}</em></a>
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a> liked your <a href="{{ related_path }}">review of <em>{{ book_title }}</em></a>
|
||||
{% endblocktrans %}
|
||||
|
||||
{% endblocktrans %}
|
||||
{% elif other_user_count == 1 %}
|
||||
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a>
|
||||
and
|
||||
<a href="{{ second_user_link }}">{{ second_user }}</a>
|
||||
liked your <a href="{{ related_path }}">review of <em>{{ book_title }}</em></a>
|
||||
{% endblocktrans %}
|
||||
|
||||
{% else %}
|
||||
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a> and {{ other_user_display_count }} others liked your <a href="{{ related_path }}">review of <em>{{ book_title }}</em></a>
|
||||
{% endblocktrans %}
|
||||
|
||||
{% endif %}
|
||||
{% elif related_status.status_type == 'Comment' %}
|
||||
{% blocktrans trimmed %}
|
||||
{% if other_user_count == 0 %}
|
||||
|
||||
liked your <a href="{{ related_path }}">comment on <em>{{ book_title }}</em></a>
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a> liked your <a href="{{ related_path }}">comment on <em>{{ book_title }}</em></a>
|
||||
{% endblocktrans %}
|
||||
|
||||
{% endblocktrans %}
|
||||
{% elif other_user_count == 1 %}
|
||||
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a>
|
||||
and
|
||||
<a href="{{ second_user_link }}">{{ second_user }}</a>
|
||||
liked your <a href="{{ related_path }}">comment on <em>{{ book_title }}</em></a>
|
||||
{% endblocktrans %}
|
||||
|
||||
{% else %}
|
||||
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a> and {{ other_user_display_count }} others liked your <a href="{{ related_path }}">comment on <em>{{ book_title }}</em></a>
|
||||
{% endblocktrans %}
|
||||
|
||||
{% endif %}
|
||||
{% elif related_status.status_type == 'Quotation' %}
|
||||
{% blocktrans trimmed %}
|
||||
{% if other_user_count == 0 %}
|
||||
|
||||
liked your <a href="{{ related_path }}">quote from <em>{{ book_title }}</em></a>
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a> liked your <a href="{{ related_path }}">quote from <em>{{ book_title }}</em></a>
|
||||
{% endblocktrans %}
|
||||
|
||||
{% endblocktrans %}
|
||||
{% elif other_user_count == 1 %}
|
||||
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a>
|
||||
and
|
||||
<a href="{{ second_user_link }}">{{ second_user }}</a>
|
||||
liked your <a href="{{ related_path }}">quote from <em>{{ book_title }}</em></a>
|
||||
{% endblocktrans %}
|
||||
|
||||
{% else %}
|
||||
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a> and {{ other_user_display_count }} others liked your <a href="{{ related_path }}">quote from <em>{{ book_title }}</em></a>
|
||||
{% endblocktrans %}
|
||||
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% blocktrans trimmed %}
|
||||
{% if other_user_count == 0 %}
|
||||
|
||||
liked your <a href="{{ related_path }}">status</a>
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a> liked your <a href="{{ related_path }}">status</a>
|
||||
{% endblocktrans %}
|
||||
|
||||
{% elif other_user_count == 1 %}
|
||||
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a>
|
||||
and
|
||||
<a href="{{ second_user_link }}">{{ second_user }}</a>
|
||||
liked your <a href="{{ related_path }}">status</a>
|
||||
{% endblocktrans %}
|
||||
|
||||
{% else %}
|
||||
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a> and {{ other_user_display_count }} others liked your <a href="{{ related_path }}">status</a>
|
||||
{% endblocktrans %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
|
||||
{% endwith %}
|
||||
|
|
|
@ -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,6 +12,19 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block description %}
|
||||
{% trans "followed you" %}
|
||||
{% include 'snippets/follow_button.html' with user=notification.related_user %}
|
||||
{% if related_user_count == 1 %}
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a> followed you
|
||||
{% endblocktrans %}
|
||||
{% elif related_user_count == 2 %}
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a> and
|
||||
<a href="{{ second_user_link }}">{{ second_user }}</a> followed you
|
||||
{% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a> and {{ other_user_display_count }} others followed you
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -3,13 +3,19 @@
|
|||
{% load i18n %}
|
||||
{% load utilities %}
|
||||
|
||||
{% block primary_link %}{% spaceless %}
|
||||
{% url 'user-followers' request.user.localname %}
|
||||
{% endspaceless %}{% endblock %}
|
||||
|
||||
{% block icon %}
|
||||
<span class="icon icon-local"></span>
|
||||
{% endblock %}
|
||||
|
||||
{% block description %}
|
||||
{% trans "sent you a follow request" %}
|
||||
{% blocktrans trimmed %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a> sent you a follow request
|
||||
{% endblocktrans %}
|
||||
<div class="row shrink">
|
||||
{% include 'snippets/follow_request_buttons.html' with user=notification.related_user %}
|
||||
{% include 'snippets/follow_request_buttons.html' with user=notification.related_users.first %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -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 "<a href="{{ group_path }}">{{ group_name }}</a>"
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a>
|
||||
invited you to join the group
|
||||
"<a href="{{ group_path }}">{{ group_name }}</a>"
|
||||
{% endblocktrans %}
|
||||
|
||||
<div class="row shrink">
|
||||
{% include 'snippets/join_invitation_buttons.html' with group=notification.related_group %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
{% load notification_page_tags %}
|
||||
{% load humanize %}
|
||||
{% related_status notification as related_status %}
|
||||
|
||||
{% with related_users=notification.related_users.all.distinct %}
|
||||
{% with related_user_count=notification.related_users.count %}
|
||||
<div class="notification {% if notification.id in unread %}has-background-primary{% endif %}">
|
||||
<div class="columns is-mobile {% if notification.id in unread %}has-text-white{% else %}has-text-more-muted{% endif %}">
|
||||
<div class="column is-narrow is-size-3">
|
||||
|
@ -9,14 +13,43 @@
|
|||
</div>
|
||||
|
||||
<div class="column is-clipped">
|
||||
{% if related_user_count > 1 %}
|
||||
<div class="block">
|
||||
<ul class="is-flex">
|
||||
{% for user in related_users|slice:10 %}
|
||||
<li class="mr-2">
|
||||
<a href="{{ user.local_path }}">
|
||||
{% include 'snippets/avatar.html' with user=user %}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="block content">
|
||||
<p>
|
||||
{% if notification.related_user %}
|
||||
<a href="{{ notification.related_user.local_path }}">{% include 'snippets/avatar.html' with user=notification.related_user %}
|
||||
{{ notification.related_user.display_name }}</a>
|
||||
{% endif %}
|
||||
{% if related_user_count == 1 %}
|
||||
{% with user=related_users.first %}
|
||||
{% spaceless %}
|
||||
<a href="{{ user.local_path }}" class="mr-2">
|
||||
{% include 'snippets/avatar.html' with user=user %}
|
||||
</a>
|
||||
{% 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 %}
|
||||
</p>
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
|
||||
{% if related_status %}
|
||||
|
@ -27,4 +60,5 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endwith %}
|
||||
{% endwith %}
|
||||
|
|
|
@ -13,8 +13,34 @@
|
|||
|
||||
{% block description %}
|
||||
|
||||
{% if other_user_count == 0 %}
|
||||
|
||||
{% blocktrans trimmed with group_name=notification.related_group.name group_path=notification.related_group.local_path %}
|
||||
has left your group "<a href="{{ group_path }}">{{ group_name }}</a>"
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a>
|
||||
has left your group
|
||||
"<a href="{{ group_path }}">{{ group_name }}</a>"
|
||||
{% endblocktrans %}
|
||||
|
||||
{% elif other_user_count == 1 %}
|
||||
|
||||
{% blocktrans trimmed with group_name=notification.related_group.name group_path=notification.related_group.local_path %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a>
|
||||
and
|
||||
<a href="{{ second_user_link }}">{{ second_user }}</a>
|
||||
have left your group
|
||||
"<a href="{{ group_path }}">{{ group_name }}</a>"
|
||||
{% endblocktrans %}
|
||||
|
||||
{% else %}
|
||||
|
||||
{% blocktrans trimmed with group_name=notification.related_group.name group_path=notification.related_group.local_path %}
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a>
|
||||
and
|
||||
{{ other_user_display_count }} others
|
||||
have left your group
|
||||
"<a href="{{ group_path }}">{{ group_name }}</a>"
|
||||
{% endblocktrans %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -19,25 +19,25 @@
|
|||
{% if related_status.status_type == 'Review' %}
|
||||
{% blocktrans trimmed %}
|
||||
|
||||
mentioned you in a <a href="{{ related_path }}">review of <em>{{ book_title }}</em></a>
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a> mentioned you in a <a href="{{ related_path }}">review of <em>{{ book_title }}</em></a>
|
||||
|
||||
{% endblocktrans %}
|
||||
{% elif related_status.status_type == 'Comment' %}
|
||||
{% blocktrans trimmed %}
|
||||
|
||||
mentioned you in a <a href="{{ related_path }}">comment on <em>{{ book_title }}</em></a>
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a> mentioned you in a <a href="{{ related_path }}">comment on <em>{{ book_title }}</em></a>
|
||||
|
||||
{% endblocktrans %}
|
||||
{% elif related_status.status_type == 'Quotation' %}
|
||||
{% blocktrans trimmed %}
|
||||
|
||||
mentioned you in a <a href="{{ related_path }}">quote from <em>{{ book_title }}</em></a>
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a> mentioned you in a <a href="{{ related_path }}">quote from <em>{{ book_title }}</em></a>
|
||||
|
||||
{% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans trimmed %}
|
||||
|
||||
mentioned you in a <a href="{{ related_path }}">status</a>
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a> mentioned you in a <a href="{{ related_path }}">status</a>
|
||||
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
|
|
|
@ -20,25 +20,25 @@
|
|||
{% if related_status.reply_parent.status_type == 'Review' %}
|
||||
{% blocktrans trimmed %}
|
||||
|
||||
<a href="{{ related_path }}">replied</a> to your <a href="{{ parent_path }}">review of <em>{{ book_title }}</em></a>
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a> <a href="{{ related_path }}">replied</a> to your <a href="{{ parent_path }}">review of <em>{{ book_title }}</em></a>
|
||||
|
||||
{% endblocktrans %}
|
||||
{% elif related_status.reply_parent.status_type == 'Comment' %}
|
||||
{% blocktrans trimmed %}
|
||||
|
||||
<a href="{{ related_path }}">replied</a> to your <a href="{{ parent_path }}">comment on <em>{{ book_title }}</em></a>
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a> <a href="{{ related_path }}">replied</a> to your <a href="{{ parent_path }}">comment on <em>{{ book_title }}</em></a>
|
||||
|
||||
{% endblocktrans %}
|
||||
{% elif related_status.reply_parent.status_type == 'Quotation' %}
|
||||
{% blocktrans trimmed %}
|
||||
|
||||
<a href="{{ related_path }}">replied</a> to your <a href="{{ parent_path }}">quote from <em>{{ book_title }}</em></a>
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a> <a href="{{ related_path }}">replied</a> to your <a href="{{ parent_path }}">quote from <em>{{ book_title }}</em></a>
|
||||
|
||||
{% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans trimmed %}
|
||||
|
||||
<a href="{{ related_path }}">replied</a> to your <a href="{{ parent_path }}">status</a>
|
||||
<a href="{{ related_user_link }}">{{ related_user }}</a> <a href="{{ related_path }}">replied</a> to your <a href="{{ parent_path }}">status</a>
|
||||
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
{% extends 'notifications/items/layout.html' %}
|
||||
|
||||
{% load humanize %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block primary_link %}{% spaceless %}
|
||||
{% url 'settings-report' notification.related_report.id %}
|
||||
{% url 'settings-reports' %}
|
||||
{% endspaceless %}{% endblock %}
|
||||
|
||||
{% block icon %}
|
||||
|
@ -11,6 +11,10 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block description %}
|
||||
{% url 'settings-report' notification.related_report.id as path %}
|
||||
{% blocktrans %}A new <a href="{{ path }}">report</a> 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 <a href="{{ path }}">report</a> needs moderation
|
||||
{% plural %}
|
||||
{{ display_count }} new <a href="{{ path }}">reports</a> need moderation
|
||||
{% endblocktrans %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -16,11 +16,11 @@
|
|||
<select>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="barcode-status" class="block">
|
||||
<div class="grant-access is-hidden">
|
||||
<span class="icon icon-lock"></span>
|
||||
<span class="is-size-5">{% trans "Requesting camera..." %}</span></br>
|
||||
<span class="is-size-5">{% trans "Requesting camera..." %}</span><br/>
|
||||
<span>{% trans "Grant access to the camera to scan a book's barcode." %}</span>
|
||||
</div>
|
||||
<div class="access-denied is-hidden">
|
||||
|
|
|
@ -7,6 +7,7 @@ from bookwyrm import models, settings
|
|||
|
||||
|
||||
@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 @@ class List(TestCase):
|
|||
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 @@ class List(TestCase):
|
|||
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 @@ class List(TestCase):
|
|||
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 @@ class List(TestCase):
|
|||
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)
|
||||
|
|
183
bookwyrm/tests/models/test_notification.py
Normal file
183
bookwyrm/tests/models/test_notification.py
Normal file
|
@ -0,0 +1,183 @@
|
|||
""" 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)
|
||||
|
||||
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())
|
|
@ -394,18 +394,6 @@ class Status(TestCase):
|
|||
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"""
|
||||
|
|
|
@ -257,6 +257,29 @@ class GroupViews(TestCase):
|
|||
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(
|
||||
|
|
|
@ -60,7 +60,7 @@ class InteractionViews(TestCase):
|
|||
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 @@ class InteractionViews(TestCase):
|
|||
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, *_):
|
||||
|
|
|
@ -25,10 +25,12 @@ class NotificationViews(TestCase):
|
|||
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()
|
||||
|
||||
|
@ -42,27 +44,31 @@ class NotificationViews(TestCase):
|
|||
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.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,
|
||||
)
|
||||
|
@ -74,6 +80,200 @@ class NotificationViews(TestCase):
|
|||
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(
|
||||
|
|
|
@ -59,11 +59,11 @@ class Group(View):
|
|||
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 @@ class Group(View):
|
|||
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 = model.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)
|
||||
|
|
|
@ -15,16 +15,17 @@ class Notifications(View):
|
|||
"""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_status__reply_parent",
|
||||
"related_group",
|
||||
"related_import",
|
||||
"related_report",
|
||||
"related_user",
|
||||
"related_book",
|
||||
"related_list_item",
|
||||
"related_list_item__book",
|
||||
)
|
||||
.prefetch_related(
|
||||
"related_reports",
|
||||
"related_users",
|
||||
"related_list_items",
|
||||
)
|
||||
)
|
||||
if notification_type == "mentions":
|
||||
|
|
Loading…
Reference in a new issue