mirror of
https://github.com/bookwyrm-social/bookwyrm.git
synced 2024-06-02 21:39:23 +00:00
Merge from 'main' into stable_dates
This commit is contained in:
commit
8dbfba17d6
9
.github/workflows/django-tests.yml
vendored
9
.github/workflows/django-tests.yml
vendored
|
@ -32,6 +32,15 @@ jobs:
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
|
- name: Check migrations up-to-date
|
||||||
|
run: |
|
||||||
|
python ./manage.py makemigrations --check
|
||||||
|
env:
|
||||||
|
SECRET_KEY: beepbeep
|
||||||
|
DOMAIN: your.domain.here
|
||||||
|
EMAIL_HOST: ""
|
||||||
|
EMAIL_HOST_USER: ""
|
||||||
|
EMAIL_HOST_PASSWORD: ""
|
||||||
- name: Run Tests
|
- name: Run Tests
|
||||||
env:
|
env:
|
||||||
SECRET_KEY: beepbeep
|
SECRET_KEY: beepbeep
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
# Generated by Django 3.2.20 on 2023-11-13 22:39
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("bookwyrm", "0184_auto_20231106_0421"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="notification",
|
||||||
|
name="notification_type",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("FAVORITE", "Favorite"),
|
||||||
|
("BOOST", "Boost"),
|
||||||
|
("REPLY", "Reply"),
|
||||||
|
("MENTION", "Mention"),
|
||||||
|
("TAG", "Tag"),
|
||||||
|
("FOLLOW", "Follow"),
|
||||||
|
("FOLLOW_REQUEST", "Follow Request"),
|
||||||
|
("IMPORT", "Import"),
|
||||||
|
("ADD", "Add"),
|
||||||
|
("REPORT", "Report"),
|
||||||
|
("LINK_DOMAIN", "Link Domain"),
|
||||||
|
("INVITE", "Invite"),
|
||||||
|
("ACCEPT", "Accept"),
|
||||||
|
("JOIN", "Join"),
|
||||||
|
("LEAVE", "Leave"),
|
||||||
|
("REMOVE", "Remove"),
|
||||||
|
("GROUP_PRIVACY", "Group Privacy"),
|
||||||
|
("GROUP_NAME", "Group Name"),
|
||||||
|
("GROUP_DESCRIPTION", "Group Description"),
|
||||||
|
("MOVE", "Move"),
|
||||||
|
],
|
||||||
|
max_length=255,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
48
bookwyrm/migrations/0186_invite_request_notification.py
Normal file
48
bookwyrm/migrations/0186_invite_request_notification.py
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
# Generated by Django 3.2.20 on 2023-11-14 10:02
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("bookwyrm", "0185_alter_notification_notification_type"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="notification",
|
||||||
|
name="related_invite_requests",
|
||||||
|
field=models.ManyToManyField(to="bookwyrm.InviteRequest"),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="notification",
|
||||||
|
name="notification_type",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("FAVORITE", "Favorite"),
|
||||||
|
("BOOST", "Boost"),
|
||||||
|
("REPLY", "Reply"),
|
||||||
|
("MENTION", "Mention"),
|
||||||
|
("TAG", "Tag"),
|
||||||
|
("FOLLOW", "Follow"),
|
||||||
|
("FOLLOW_REQUEST", "Follow Request"),
|
||||||
|
("IMPORT", "Import"),
|
||||||
|
("ADD", "Add"),
|
||||||
|
("REPORT", "Report"),
|
||||||
|
("LINK_DOMAIN", "Link Domain"),
|
||||||
|
("INVITE_REQUEST", "Invite Request"),
|
||||||
|
("INVITE", "Invite"),
|
||||||
|
("ACCEPT", "Accept"),
|
||||||
|
("JOIN", "Join"),
|
||||||
|
("LEAVE", "Leave"),
|
||||||
|
("REMOVE", "Remove"),
|
||||||
|
("GROUP_PRIVACY", "Group Privacy"),
|
||||||
|
("GROUP_NAME", "Group Name"),
|
||||||
|
("GROUP_DESCRIPTION", "Group Description"),
|
||||||
|
("MOVE", "Move"),
|
||||||
|
],
|
||||||
|
max_length=255,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
|
@ -7,7 +7,7 @@ from django.db import migrations, models
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
("bookwyrm", "0184_auto_20231106_0421"),
|
("bookwyrm", "0186_invite_request_notification"),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
|
@ -34,7 +34,7 @@ from .site import PasswordReset, InviteRequest
|
||||||
from .announcement import Announcement
|
from .announcement import Announcement
|
||||||
from .antispam import EmailBlocklist, IPBlocklist, AutoMod, automod_task
|
from .antispam import EmailBlocklist, IPBlocklist, AutoMod, automod_task
|
||||||
|
|
||||||
from .notification import Notification
|
from .notification import Notification, NotificationType
|
||||||
|
|
||||||
from .hashtag import Hashtag
|
from .hashtag import Hashtag
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from bookwyrm.tasks import app, MISC
|
from bookwyrm.tasks import app, MISC
|
||||||
from .base_model import BookWyrmModel
|
from .base_model import BookWyrmModel
|
||||||
|
from .notification import NotificationType
|
||||||
from .user import User
|
from .user import User
|
||||||
|
|
||||||
|
|
||||||
|
@ -80,7 +81,7 @@ def automod_task():
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
for admin in admins:
|
for admin in admins:
|
||||||
notification, _ = notification_model.objects.get_or_create(
|
notification, _ = notification_model.objects.get_or_create(
|
||||||
user=admin, notification_type=notification_model.REPORT, read=False
|
user=admin, notification_type=NotificationType.REPORT, read=False
|
||||||
)
|
)
|
||||||
notification.related_reports.set(reports)
|
notification.related_reports.set(reports)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
""" do book related things with other users """
|
""" do book related things with other users """
|
||||||
from django.apps import apps
|
|
||||||
from django.db import models, IntegrityError, transaction
|
from django.db import models, IntegrityError, transaction
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from bookwyrm.settings import DOMAIN
|
from bookwyrm.settings import DOMAIN
|
||||||
|
@ -143,26 +142,28 @@ class GroupMemberInvitation(models.Model):
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def accept(self):
|
def accept(self):
|
||||||
"""turn this request into the real deal"""
|
"""turn this request into the real deal"""
|
||||||
|
# pylint: disable-next=import-outside-toplevel
|
||||||
|
from .notification import Notification, NotificationType # circular dependency
|
||||||
|
|
||||||
GroupMember.from_request(self)
|
GroupMember.from_request(self)
|
||||||
|
|
||||||
model = apps.get_model("bookwyrm.Notification", require_ready=True)
|
|
||||||
# tell the group owner
|
# tell the group owner
|
||||||
model.notify(
|
Notification.notify(
|
||||||
self.group.user,
|
self.group.user,
|
||||||
self.user,
|
self.user,
|
||||||
related_group=self.group,
|
related_group=self.group,
|
||||||
notification_type=model.ACCEPT,
|
notification_type=NotificationType.ACCEPT,
|
||||||
)
|
)
|
||||||
|
|
||||||
# let the other members know about it
|
# let the other members know about it
|
||||||
for membership in self.group.memberships.all():
|
for membership in self.group.memberships.all():
|
||||||
member = membership.user
|
member = membership.user
|
||||||
if member not in (self.user, self.group.user):
|
if member not in (self.user, self.group.user):
|
||||||
model.notify(
|
Notification.notify(
|
||||||
member,
|
member,
|
||||||
self.user,
|
self.user,
|
||||||
related_group=self.group,
|
related_group=self.group,
|
||||||
notification_type=model.JOIN,
|
notification_type=NotificationType.JOIN,
|
||||||
)
|
)
|
||||||
|
|
||||||
def reject(self):
|
def reject(self):
|
||||||
|
|
|
@ -6,7 +6,7 @@ from bookwyrm import activitypub
|
||||||
from .activitypub_mixin import ActivityMixin
|
from .activitypub_mixin import ActivityMixin
|
||||||
from .base_model import BookWyrmModel
|
from .base_model import BookWyrmModel
|
||||||
from . import fields
|
from . import fields
|
||||||
from .notification import Notification
|
from .notification import Notification, NotificationType
|
||||||
|
|
||||||
|
|
||||||
class Move(ActivityMixin, BookWyrmModel):
|
class Move(ActivityMixin, BookWyrmModel):
|
||||||
|
@ -49,7 +49,6 @@ class MoveUser(Move):
|
||||||
|
|
||||||
# only allow if the source is listed in the target's alsoKnownAs
|
# only allow if the source is listed in the target's alsoKnownAs
|
||||||
if self.user in self.target.also_known_as.all():
|
if self.user in self.target.also_known_as.all():
|
||||||
|
|
||||||
self.user.also_known_as.add(self.target.id)
|
self.user.also_known_as.add(self.target.id)
|
||||||
self.user.update_active_date()
|
self.user.update_active_date()
|
||||||
self.user.moved_to = self.target.remote_id
|
self.user.moved_to = self.target.remote_id
|
||||||
|
@ -65,7 +64,7 @@ class MoveUser(Move):
|
||||||
for follower in self.user.followers.all():
|
for follower in self.user.followers.all():
|
||||||
if follower.local:
|
if follower.local:
|
||||||
Notification.notify(
|
Notification.notify(
|
||||||
follower, self.user, notification_type=Notification.MOVE
|
follower, self.user, notification_type=NotificationType.MOVE
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -4,9 +4,10 @@ from django.dispatch import receiver
|
||||||
from .base_model import BookWyrmModel
|
from .base_model import BookWyrmModel
|
||||||
from . import Boost, Favorite, GroupMemberInvitation, ImportJob, LinkDomain
|
from . import Boost, Favorite, GroupMemberInvitation, ImportJob, LinkDomain
|
||||||
from . import ListItem, Report, Status, User, UserFollowRequest
|
from . import ListItem, Report, Status, User, UserFollowRequest
|
||||||
|
from .site import InviteRequest
|
||||||
|
|
||||||
|
|
||||||
class Notification(BookWyrmModel):
|
class NotificationType(models.TextChoices):
|
||||||
"""you've been tagged, liked, followed, etc"""
|
"""you've been tagged, liked, followed, etc"""
|
||||||
|
|
||||||
# Status interactions
|
# Status interactions
|
||||||
|
@ -29,6 +30,7 @@ class Notification(BookWyrmModel):
|
||||||
# Admin
|
# Admin
|
||||||
REPORT = "REPORT"
|
REPORT = "REPORT"
|
||||||
LINK_DOMAIN = "LINK_DOMAIN"
|
LINK_DOMAIN = "LINK_DOMAIN"
|
||||||
|
INVITE_REQUEST = "INVITE_REQUEST"
|
||||||
|
|
||||||
# Groups
|
# Groups
|
||||||
INVITE = "INVITE"
|
INVITE = "INVITE"
|
||||||
|
@ -43,12 +45,9 @@ class Notification(BookWyrmModel):
|
||||||
# Migrations
|
# Migrations
|
||||||
MOVE = "MOVE"
|
MOVE = "MOVE"
|
||||||
|
|
||||||
# pylint: disable=line-too-long
|
|
||||||
NotificationType = models.TextChoices(
|
class Notification(BookWyrmModel):
|
||||||
# there has got be a better way to do this
|
"""a notification object"""
|
||||||
"NotificationType",
|
|
||||||
f"{FAVORITE} {REPLY} {MENTION} {TAG} {FOLLOW} {FOLLOW_REQUEST} {BOOST} {IMPORT} {ADD} {REPORT} {LINK_DOMAIN} {INVITE} {ACCEPT} {JOIN} {LEAVE} {REMOVE} {GROUP_PRIVACY} {GROUP_NAME} {GROUP_DESCRIPTION} {MOVE}",
|
|
||||||
)
|
|
||||||
|
|
||||||
user = models.ForeignKey("User", on_delete=models.CASCADE)
|
user = models.ForeignKey("User", on_delete=models.CASCADE)
|
||||||
read = models.BooleanField(default=False)
|
read = models.BooleanField(default=False)
|
||||||
|
@ -67,8 +66,9 @@ class Notification(BookWyrmModel):
|
||||||
related_list_items = models.ManyToManyField(
|
related_list_items = models.ManyToManyField(
|
||||||
"ListItem", symmetrical=False, related_name="notifications"
|
"ListItem", symmetrical=False, related_name="notifications"
|
||||||
)
|
)
|
||||||
related_reports = models.ManyToManyField("Report", symmetrical=False)
|
related_reports = models.ManyToManyField("Report")
|
||||||
related_link_domains = models.ManyToManyField("LinkDomain", symmetrical=False)
|
related_link_domains = models.ManyToManyField("LinkDomain")
|
||||||
|
related_invite_requests = models.ManyToManyField("InviteRequest")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
|
@ -93,11 +93,11 @@ class Notification(BookWyrmModel):
|
||||||
user=user,
|
user=user,
|
||||||
related_users=related_user,
|
related_users=related_user,
|
||||||
related_list_items__book_list=list_item.book_list,
|
related_list_items__book_list=list_item.book_list,
|
||||||
notification_type=Notification.ADD,
|
notification_type=NotificationType.ADD,
|
||||||
).first()
|
).first()
|
||||||
if not notification:
|
if not notification:
|
||||||
notification = cls.objects.create(
|
notification = cls.objects.create(
|
||||||
user=user, notification_type=Notification.ADD
|
user=user, notification_type=NotificationType.ADD
|
||||||
)
|
)
|
||||||
notification.related_users.add(related_user)
|
notification.related_users.add(related_user)
|
||||||
notification.related_list_items.add(list_item)
|
notification.related_list_items.add(list_item)
|
||||||
|
@ -124,7 +124,7 @@ def notify_on_fav(sender, instance, *args, **kwargs):
|
||||||
instance.status.user,
|
instance.status.user,
|
||||||
instance.user,
|
instance.user,
|
||||||
related_status=instance.status,
|
related_status=instance.status,
|
||||||
notification_type=Notification.FAVORITE,
|
notification_type=NotificationType.FAVORITE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -138,7 +138,7 @@ def notify_on_unfav(sender, instance, *args, **kwargs):
|
||||||
instance.status.user,
|
instance.status.user,
|
||||||
instance.user,
|
instance.user,
|
||||||
related_status=instance.status,
|
related_status=instance.status,
|
||||||
notification_type=Notification.FAVORITE,
|
notification_type=NotificationType.FAVORITE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -163,7 +163,7 @@ def notify_user_on_mention(sender, instance, *args, **kwargs):
|
||||||
instance.reply_parent.user,
|
instance.reply_parent.user,
|
||||||
instance.user,
|
instance.user,
|
||||||
related_status=instance,
|
related_status=instance,
|
||||||
notification_type=Notification.REPLY,
|
notification_type=NotificationType.REPLY,
|
||||||
)
|
)
|
||||||
|
|
||||||
for mention_user in instance.mention_users.all():
|
for mention_user in instance.mention_users.all():
|
||||||
|
@ -175,7 +175,7 @@ def notify_user_on_mention(sender, instance, *args, **kwargs):
|
||||||
Notification.notify(
|
Notification.notify(
|
||||||
mention_user,
|
mention_user,
|
||||||
instance.user,
|
instance.user,
|
||||||
notification_type=Notification.MENTION,
|
notification_type=NotificationType.MENTION,
|
||||||
related_status=instance,
|
related_status=instance,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -194,7 +194,7 @@ def notify_user_on_boost(sender, instance, *args, **kwargs):
|
||||||
instance.boosted_status.user,
|
instance.boosted_status.user,
|
||||||
instance.user,
|
instance.user,
|
||||||
related_status=instance.boosted_status,
|
related_status=instance.boosted_status,
|
||||||
notification_type=Notification.BOOST,
|
notification_type=NotificationType.BOOST,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -206,7 +206,7 @@ def notify_user_on_unboost(sender, instance, *args, **kwargs):
|
||||||
instance.boosted_status.user,
|
instance.boosted_status.user,
|
||||||
instance.user,
|
instance.user,
|
||||||
related_status=instance.boosted_status,
|
related_status=instance.boosted_status,
|
||||||
notification_type=Notification.BOOST,
|
notification_type=NotificationType.BOOST,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -221,7 +221,7 @@ def notify_user_on_import_complete(
|
||||||
return
|
return
|
||||||
Notification.objects.get_or_create(
|
Notification.objects.get_or_create(
|
||||||
user=instance.user,
|
user=instance.user,
|
||||||
notification_type=Notification.IMPORT,
|
notification_type=NotificationType.IMPORT,
|
||||||
related_import=instance,
|
related_import=instance,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -236,11 +236,10 @@ def notify_admins_on_report(sender, instance, created, *args, **kwargs):
|
||||||
return
|
return
|
||||||
|
|
||||||
# moderators and superusers should be notified
|
# moderators and superusers should be notified
|
||||||
admins = User.admins()
|
for admin in User.admins():
|
||||||
for admin in admins:
|
|
||||||
notification, _ = Notification.objects.get_or_create(
|
notification, _ = Notification.objects.get_or_create(
|
||||||
user=admin,
|
user=admin,
|
||||||
notification_type=Notification.REPORT,
|
notification_type=NotificationType.REPORT,
|
||||||
read=False,
|
read=False,
|
||||||
)
|
)
|
||||||
notification.related_reports.add(instance)
|
notification.related_reports.add(instance)
|
||||||
|
@ -256,16 +255,33 @@ def notify_admins_on_link_domain(sender, instance, created, *args, **kwargs):
|
||||||
return
|
return
|
||||||
|
|
||||||
# moderators and superusers should be notified
|
# moderators and superusers should be notified
|
||||||
admins = User.admins()
|
for admin in User.admins():
|
||||||
for admin in admins:
|
|
||||||
notification, _ = Notification.objects.get_or_create(
|
notification, _ = Notification.objects.get_or_create(
|
||||||
user=admin,
|
user=admin,
|
||||||
notification_type=Notification.LINK_DOMAIN,
|
notification_type=NotificationType.LINK_DOMAIN,
|
||||||
read=False,
|
read=False,
|
||||||
)
|
)
|
||||||
notification.related_link_domains.add(instance)
|
notification.related_link_domains.add(instance)
|
||||||
|
|
||||||
|
|
||||||
|
@receiver(models.signals.post_save, sender=InviteRequest)
|
||||||
|
@transaction.atomic
|
||||||
|
# pylint: disable=unused-argument
|
||||||
|
def notify_admins_on_invite_request(sender, instance, created, *args, **kwargs):
|
||||||
|
"""need to handle a new invite request"""
|
||||||
|
if not created:
|
||||||
|
return
|
||||||
|
|
||||||
|
# moderators and superusers should be notified
|
||||||
|
for admin in User.admins():
|
||||||
|
notification, _ = Notification.objects.get_or_create(
|
||||||
|
user=admin,
|
||||||
|
notification_type=NotificationType.INVITE_REQUEST,
|
||||||
|
read=False,
|
||||||
|
)
|
||||||
|
notification.related_invite_requests.add(instance)
|
||||||
|
|
||||||
|
|
||||||
@receiver(models.signals.post_save, sender=GroupMemberInvitation)
|
@receiver(models.signals.post_save, sender=GroupMemberInvitation)
|
||||||
# pylint: disable=unused-argument
|
# pylint: disable=unused-argument
|
||||||
def notify_user_on_group_invite(sender, instance, *args, **kwargs):
|
def notify_user_on_group_invite(sender, instance, *args, **kwargs):
|
||||||
|
@ -274,7 +290,7 @@ def notify_user_on_group_invite(sender, instance, *args, **kwargs):
|
||||||
instance.user,
|
instance.user,
|
||||||
instance.group.user,
|
instance.group.user,
|
||||||
related_group=instance.group,
|
related_group=instance.group,
|
||||||
notification_type=Notification.INVITE,
|
notification_type=NotificationType.INVITE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -312,11 +328,12 @@ def notify_user_on_follow(sender, instance, created, *args, **kwargs):
|
||||||
notification = Notification.objects.filter(
|
notification = Notification.objects.filter(
|
||||||
user=instance.user_object,
|
user=instance.user_object,
|
||||||
related_users=instance.user_subject,
|
related_users=instance.user_subject,
|
||||||
notification_type=Notification.FOLLOW_REQUEST,
|
notification_type=NotificationType.FOLLOW_REQUEST,
|
||||||
).first()
|
).first()
|
||||||
if not notification:
|
if not notification:
|
||||||
notification = Notification.objects.create(
|
notification = Notification.objects.create(
|
||||||
user=instance.user_object, notification_type=Notification.FOLLOW_REQUEST
|
user=instance.user_object,
|
||||||
|
notification_type=NotificationType.FOLLOW_REQUEST,
|
||||||
)
|
)
|
||||||
notification.related_users.set([instance.user_subject])
|
notification.related_users.set([instance.user_subject])
|
||||||
notification.read = False
|
notification.read = False
|
||||||
|
@ -326,6 +343,6 @@ def notify_user_on_follow(sender, instance, created, *args, **kwargs):
|
||||||
Notification.notify(
|
Notification.notify(
|
||||||
instance.user_object,
|
instance.user_object,
|
||||||
instance.user_subject,
|
instance.user_subject,
|
||||||
notification_type=Notification.FOLLOW,
|
notification_type=NotificationType.FOLLOW,
|
||||||
read=False,
|
read=False,
|
||||||
)
|
)
|
||||||
|
|
|
@ -21,6 +21,8 @@
|
||||||
{% include 'notifications/items/report.html' %}
|
{% include 'notifications/items/report.html' %}
|
||||||
{% elif notification.notification_type == 'LINK_DOMAIN' %}
|
{% elif notification.notification_type == 'LINK_DOMAIN' %}
|
||||||
{% include 'notifications/items/link_domain.html' %}
|
{% include 'notifications/items/link_domain.html' %}
|
||||||
|
{% elif notification.notification_type == 'INVITE_REQUEST' %}
|
||||||
|
{% include 'notifications/items/invite_request.html' %}
|
||||||
{% elif notification.notification_type == 'INVITE' %}
|
{% elif notification.notification_type == 'INVITE' %}
|
||||||
{% include 'notifications/items/invite.html' %}
|
{% include 'notifications/items/invite.html' %}
|
||||||
{% elif notification.notification_type == 'ACCEPT' %}
|
{% elif notification.notification_type == 'ACCEPT' %}
|
||||||
|
|
20
bookwyrm/templates/notifications/items/invite_request.html
Normal file
20
bookwyrm/templates/notifications/items/invite_request.html
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{% extends 'notifications/items/layout.html' %}
|
||||||
|
{% load humanize %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block primary_link %}{% spaceless %}
|
||||||
|
{% url 'settings-invite-requests' %}
|
||||||
|
{% endspaceless %}{% endblock %}
|
||||||
|
|
||||||
|
{% block icon %}
|
||||||
|
<span class="icon icon-envelope"></span>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block description %}
|
||||||
|
{% url 'settings-invite-requests' as path %}
|
||||||
|
{% blocktrans trimmed count counter=notification.related_invite_requests.count with display_count=notification.related_invite_requests.count|intcomma %}
|
||||||
|
New <a href="{{ path }}">invite request</a> awaiting response
|
||||||
|
{% plural %}
|
||||||
|
{{ display_count }} new <a href="{{ path }}">invite requests</a> awaiting response
|
||||||
|
{% endblocktrans %}
|
||||||
|
{% endblock %}
|
|
@ -43,7 +43,7 @@ class Notification(TestCase):
|
||||||
def test_notification(self):
|
def test_notification(self):
|
||||||
"""New notifications are unread"""
|
"""New notifications are unread"""
|
||||||
notification = models.Notification.objects.create(
|
notification = models.Notification.objects.create(
|
||||||
user=self.local_user, notification_type=models.Notification.FAVORITE
|
user=self.local_user, notification_type=models.NotificationType.FAVORITE
|
||||||
)
|
)
|
||||||
self.assertFalse(notification.read)
|
self.assertFalse(notification.read)
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ class Notification(TestCase):
|
||||||
models.Notification.notify(
|
models.Notification.notify(
|
||||||
self.local_user,
|
self.local_user,
|
||||||
self.remote_user,
|
self.remote_user,
|
||||||
notification_type=models.Notification.FAVORITE,
|
notification_type=models.NotificationType.FAVORITE,
|
||||||
)
|
)
|
||||||
self.assertTrue(models.Notification.objects.exists())
|
self.assertTrue(models.Notification.objects.exists())
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ class Notification(TestCase):
|
||||||
models.Notification.notify(
|
models.Notification.notify(
|
||||||
self.local_user,
|
self.local_user,
|
||||||
self.remote_user,
|
self.remote_user,
|
||||||
notification_type=models.Notification.FAVORITE,
|
notification_type=models.NotificationType.FAVORITE,
|
||||||
)
|
)
|
||||||
self.assertEqual(models.Notification.objects.count(), 1)
|
self.assertEqual(models.Notification.objects.count(), 1)
|
||||||
notification = models.Notification.objects.get()
|
notification = models.Notification.objects.get()
|
||||||
|
@ -70,7 +70,7 @@ class Notification(TestCase):
|
||||||
models.Notification.notify(
|
models.Notification.notify(
|
||||||
self.local_user,
|
self.local_user,
|
||||||
self.another_user,
|
self.another_user,
|
||||||
notification_type=models.Notification.FAVORITE,
|
notification_type=models.NotificationType.FAVORITE,
|
||||||
)
|
)
|
||||||
self.assertEqual(models.Notification.objects.count(), 1)
|
self.assertEqual(models.Notification.objects.count(), 1)
|
||||||
notification.refresh_from_db()
|
notification.refresh_from_db()
|
||||||
|
@ -92,7 +92,7 @@ class Notification(TestCase):
|
||||||
models.Notification.notify(
|
models.Notification.notify(
|
||||||
self.remote_user,
|
self.remote_user,
|
||||||
self.local_user,
|
self.local_user,
|
||||||
notification_type=models.Notification.FAVORITE,
|
notification_type=models.NotificationType.FAVORITE,
|
||||||
)
|
)
|
||||||
self.assertFalse(models.Notification.objects.exists())
|
self.assertFalse(models.Notification.objects.exists())
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ class Notification(TestCase):
|
||||||
models.Notification.notify(
|
models.Notification.notify(
|
||||||
self.local_user,
|
self.local_user,
|
||||||
self.local_user,
|
self.local_user,
|
||||||
notification_type=models.Notification.FAVORITE,
|
notification_type=models.NotificationType.FAVORITE,
|
||||||
)
|
)
|
||||||
self.assertFalse(models.Notification.objects.exists())
|
self.assertFalse(models.Notification.objects.exists())
|
||||||
|
|
||||||
|
@ -154,14 +154,14 @@ class Notification(TestCase):
|
||||||
models.Notification.notify(
|
models.Notification.notify(
|
||||||
self.local_user,
|
self.local_user,
|
||||||
self.remote_user,
|
self.remote_user,
|
||||||
notification_type=models.Notification.FAVORITE,
|
notification_type=models.NotificationType.FAVORITE,
|
||||||
)
|
)
|
||||||
self.assertTrue(models.Notification.objects.exists())
|
self.assertTrue(models.Notification.objects.exists())
|
||||||
|
|
||||||
models.Notification.unnotify(
|
models.Notification.unnotify(
|
||||||
self.local_user,
|
self.local_user,
|
||||||
self.remote_user,
|
self.remote_user,
|
||||||
notification_type=models.Notification.FAVORITE,
|
notification_type=models.NotificationType.FAVORITE,
|
||||||
)
|
)
|
||||||
self.assertFalse(models.Notification.objects.exists())
|
self.assertFalse(models.Notification.objects.exists())
|
||||||
|
|
||||||
|
@ -170,25 +170,112 @@ class Notification(TestCase):
|
||||||
models.Notification.notify(
|
models.Notification.notify(
|
||||||
self.local_user,
|
self.local_user,
|
||||||
self.remote_user,
|
self.remote_user,
|
||||||
notification_type=models.Notification.FAVORITE,
|
notification_type=models.NotificationType.FAVORITE,
|
||||||
)
|
)
|
||||||
models.Notification.notify(
|
models.Notification.notify(
|
||||||
self.local_user,
|
self.local_user,
|
||||||
self.another_user,
|
self.another_user,
|
||||||
notification_type=models.Notification.FAVORITE,
|
notification_type=models.NotificationType.FAVORITE,
|
||||||
)
|
)
|
||||||
self.assertTrue(models.Notification.objects.exists())
|
self.assertTrue(models.Notification.objects.exists())
|
||||||
|
|
||||||
models.Notification.unnotify(
|
models.Notification.unnotify(
|
||||||
self.local_user,
|
self.local_user,
|
||||||
self.remote_user,
|
self.remote_user,
|
||||||
notification_type=models.Notification.FAVORITE,
|
notification_type=models.NotificationType.FAVORITE,
|
||||||
)
|
)
|
||||||
self.assertTrue(models.Notification.objects.exists())
|
self.assertTrue(models.Notification.objects.exists())
|
||||||
|
|
||||||
models.Notification.unnotify(
|
models.Notification.unnotify(
|
||||||
self.local_user,
|
self.local_user,
|
||||||
self.another_user,
|
self.another_user,
|
||||||
notification_type=models.Notification.FAVORITE,
|
notification_type=models.NotificationType.FAVORITE,
|
||||||
)
|
)
|
||||||
self.assertFalse(models.Notification.objects.exists())
|
self.assertFalse(models.Notification.objects.exists())
|
||||||
|
|
||||||
|
|
||||||
|
class NotifyInviteRequest(TestCase):
|
||||||
|
"""let admins know of invite requests"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""ensure there is one admin"""
|
||||||
|
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch(
|
||||||
|
"bookwyrm.activitystreams.populate_stream_task.delay"
|
||||||
|
), patch("bookwyrm.lists_stream.populate_lists_task.delay"):
|
||||||
|
self.local_user = models.User.objects.create_user(
|
||||||
|
"mouse@local.com",
|
||||||
|
"mouse@mouse.mouse",
|
||||||
|
"password",
|
||||||
|
local=True,
|
||||||
|
localname="mouse",
|
||||||
|
is_superuser=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_invite_request_triggers_notification(self):
|
||||||
|
"""requesting an invite notifies the admin"""
|
||||||
|
admin = models.User.objects.filter(is_superuser=True).first()
|
||||||
|
request = models.InviteRequest.objects.create(email="user@example.com")
|
||||||
|
|
||||||
|
self.assertEqual(models.Notification.objects.count(), 1)
|
||||||
|
|
||||||
|
notification = models.Notification.objects.first()
|
||||||
|
self.assertEqual(notification.user, admin)
|
||||||
|
self.assertEqual(
|
||||||
|
notification.notification_type, models.NotificationType.INVITE_REQUEST
|
||||||
|
)
|
||||||
|
self.assertEqual(notification.related_invite_requests.count(), 1)
|
||||||
|
self.assertEqual(notification.related_invite_requests.first(), request)
|
||||||
|
|
||||||
|
def test_notify_only_created(self):
|
||||||
|
"""updating an invite request does not trigger a notification"""
|
||||||
|
request = models.InviteRequest.objects.create(email="user@example.com")
|
||||||
|
notification = models.Notification.objects.first()
|
||||||
|
|
||||||
|
notification.delete()
|
||||||
|
self.assertEqual(models.Notification.objects.count(), 0)
|
||||||
|
|
||||||
|
request.ignored = True
|
||||||
|
request.save()
|
||||||
|
self.assertEqual(models.Notification.objects.count(), 0)
|
||||||
|
|
||||||
|
def test_notify_grouping(self):
|
||||||
|
"""invites group into the same notification, until read"""
|
||||||
|
requests = [
|
||||||
|
models.InviteRequest.objects.create(email="user1@example.com"),
|
||||||
|
models.InviteRequest.objects.create(email="user2@example.com"),
|
||||||
|
]
|
||||||
|
self.assertEqual(models.Notification.objects.count(), 1)
|
||||||
|
|
||||||
|
notification = models.Notification.objects.first()
|
||||||
|
self.assertEqual(notification.related_invite_requests.count(), 2)
|
||||||
|
self.assertCountEqual(notification.related_invite_requests.all(), requests)
|
||||||
|
|
||||||
|
notification.read = True
|
||||||
|
notification.save()
|
||||||
|
|
||||||
|
request = models.InviteRequest.objects.create(email="user3@example.com")
|
||||||
|
_, notification = models.Notification.objects.all()
|
||||||
|
|
||||||
|
self.assertEqual(models.Notification.objects.count(), 2)
|
||||||
|
self.assertEqual(notification.related_invite_requests.count(), 1)
|
||||||
|
self.assertEqual(notification.related_invite_requests.first(), request)
|
||||||
|
|
||||||
|
def test_notify_multiple_admins(self):
|
||||||
|
"""all admins are notified"""
|
||||||
|
with patch("bookwyrm.suggested_users.rerank_suggestions_task.delay"), patch(
|
||||||
|
"bookwyrm.activitystreams.populate_stream_task.delay"
|
||||||
|
), patch("bookwyrm.lists_stream.populate_lists_task.delay"):
|
||||||
|
self.local_user = models.User.objects.create_user(
|
||||||
|
"admin@local.com",
|
||||||
|
"admin@example.com",
|
||||||
|
"password",
|
||||||
|
local=True,
|
||||||
|
localname="root",
|
||||||
|
is_superuser=True,
|
||||||
|
)
|
||||||
|
models.InviteRequest.objects.create(email="user@example.com")
|
||||||
|
admins = models.User.objects.filter(is_superuser=True).all()
|
||||||
|
notifications = models.Notification.objects.all()
|
||||||
|
|
||||||
|
self.assertEqual(len(notifications), 2)
|
||||||
|
self.assertCountEqual([notif.user for notif in notifications], admins)
|
||||||
|
|
|
@ -13,9 +13,11 @@ from django.contrib.postgres.search import TrigramSimilarity
|
||||||
from django.db.models.functions import Greatest
|
from django.db.models.functions import Greatest
|
||||||
|
|
||||||
from bookwyrm import forms, models
|
from bookwyrm import forms, models
|
||||||
|
from bookwyrm.models import NotificationType
|
||||||
from bookwyrm.suggested_users import suggested_users
|
from bookwyrm.suggested_users import suggested_users
|
||||||
from .helpers import get_user_from_username, maybe_redirect_local_path
|
from .helpers import get_user_from_username, maybe_redirect_local_path
|
||||||
|
|
||||||
|
|
||||||
# pylint: disable=no-self-use
|
# pylint: disable=no-self-use
|
||||||
class Group(View):
|
class Group(View):
|
||||||
"""group page"""
|
"""group page"""
|
||||||
|
@ -59,11 +61,11 @@ class Group(View):
|
||||||
model = apps.get_model("bookwyrm.Notification", require_ready=True)
|
model = apps.get_model("bookwyrm.Notification", require_ready=True)
|
||||||
for field in form.changed_data:
|
for field in form.changed_data:
|
||||||
notification_type = (
|
notification_type = (
|
||||||
model.GROUP_PRIVACY
|
NotificationType.GROUP_PRIVACY
|
||||||
if field == "privacy"
|
if field == "privacy"
|
||||||
else model.GROUP_NAME
|
else NotificationType.GROUP_NAME
|
||||||
if field == "name"
|
if field == "name"
|
||||||
else model.GROUP_DESCRIPTION
|
else NotificationType.GROUP_DESCRIPTION
|
||||||
if field == "description"
|
if field == "description"
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
|
@ -251,7 +253,9 @@ def remove_member(request):
|
||||||
|
|
||||||
memberships = models.GroupMember.objects.filter(group=group)
|
memberships = models.GroupMember.objects.filter(group=group)
|
||||||
model = apps.get_model("bookwyrm.Notification", require_ready=True)
|
model = apps.get_model("bookwyrm.Notification", require_ready=True)
|
||||||
notification_type = model.LEAVE if user == request.user else model.REMOVE
|
notification_type = (
|
||||||
|
NotificationType.LEAVE if user == request.user else NotificationType.REMOVE
|
||||||
|
)
|
||||||
# let the other members know about it
|
# let the other members know about it
|
||||||
for membership in memberships:
|
for membership in memberships:
|
||||||
member = membership.user
|
member = membership.user
|
||||||
|
@ -264,7 +268,7 @@ def remove_member(request):
|
||||||
)
|
)
|
||||||
|
|
||||||
# let the user (now ex-member) know as well, if they were removed
|
# let the user (now ex-member) know as well, if they were removed
|
||||||
if notification_type == model.REMOVE:
|
if notification_type == NotificationType.REMOVE:
|
||||||
model.notify(
|
model.notify(
|
||||||
user, None, related_group=group, notification_type=notification_type
|
user, None, related_group=group, notification_type=notification_type
|
||||||
)
|
)
|
||||||
|
|
2
bw-dev
2
bw-dev
|
@ -91,7 +91,7 @@ case "$CMD" in
|
||||||
$DOCKER_COMPOSE run --rm --service-ports web
|
$DOCKER_COMPOSE run --rm --service-ports web
|
||||||
;;
|
;;
|
||||||
initdb)
|
initdb)
|
||||||
initdb "@"
|
initdb "$@"
|
||||||
;;
|
;;
|
||||||
resetdb)
|
resetdb)
|
||||||
prod_error
|
prod_error
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
aiohttp==3.8.5
|
aiohttp==3.8.6
|
||||||
bleach==5.0.1
|
bleach==5.0.1
|
||||||
celery==5.2.7
|
celery==5.2.7
|
||||||
colorthief==0.2.1
|
colorthief==0.2.1
|
||||||
|
|
Loading…
Reference in a new issue