Notification emails #80

Merged
Oleg-Komarov merged 31 commits from notifications into main 2024-04-18 16:11:20 +02:00
6 changed files with 82 additions and 16 deletions
Showing only changes of commit ec61798c03 - Show all commits

View File

@ -3,14 +3,17 @@ class Verb:
changing the values will result in a mismatch with historical values stored in db. changing the values will result in a mismatch with historical values stored in db.
""" """
SUBMITTED_FOR_REVIEW = 'submitted for review'
REPORTED_EXTENSION = 'reported extension'
REPORTED_REVIEW = 'reported review'
APPROVED = 'approved' APPROVED = 'approved'
COMMENTED = 'commented' COMMENTED = 'commented'
RATED_EXTENSION = 'rated extension'
REPORTED_EXTENSION = 'reported extension'
REPORTED_REVIEW = 'reported review'
REQUESTED_CHANGES = 'requested changes' REQUESTED_CHANGES = 'requested changes'
REQUESTED_REVIEW = 'requested review' REQUESTED_REVIEW = 'requested review'
SUBMITTED_FOR_REVIEW = 'submitted for review'
RATED_EXTENSION = 'rated extension'
class Flag:
AUTHOR = 'author'
MODERATOR = 'moderator'
REVIEWER = 'reviewer'

View File

@ -5,6 +5,7 @@ from django.contrib.auth.models import Group
from django.db.models.signals import pre_save, post_save, post_delete from django.db.models.signals import pre_save, post_save, post_delete
from django.dispatch import receiver from django.dispatch import receiver
from constants.activity import Flag
import extensions.models import extensions.models
import extensions.tasks import extensions.tasks
import files.models import files.models
@ -91,6 +92,6 @@ def _setup_followers(
return return
for user in instance.authors.all(): for user in instance.authors.all():
follow(user, instance, send_action=False, flag='author') follow(user, instance, send_action=False, flag=Flag.AUTHOR)
for user in Group.objects.get(name='moderators').user_set.all(): for user in Group.objects.get(name='moderators').user_set.all():
follow(user, instance, send_action=False, flag='moderator') follow(user, instance, send_action=False, flag=Flag.MODERATOR)

View File

@ -0,0 +1,38 @@
# Generated by Django 4.2.11 on 2024-04-15 10:18
from actstream.actions import follow
from django.contrib.auth.models import Group
from django.db import migrations
from constants.activity import Flag
def setup_followers(apps, schema_editor):
# !!! not using apps.get_model('extensions', 'Extension')
# because it doesn't work with actstream.registry:
#
# File ".../site-packages/actstream/actions.py", line 34, in follow
# check(obj)
# File ".../site-packages/actstream/registry.py", line 105, in check
# raise ImproperlyConfigured(
# django.core.exceptions.ImproperlyConfigured: The model Extension is not registered.
# Please use actstream.registry to register it.
#
# if this ever causes issues in the future, delete this code or find a workaround
from extensions.models import Extension
for extension in Extension.objects.all():
for user in extension.authors.all():
follow(user, extension, send_action=False, flag=Flag.AUTHOR)
for user in Group.objects.get(name='moderators').user_set.all():
follow(user, extension, send_action=False, flag=Flag.MODERATOR)
class Migration(migrations.Migration):
dependencies = [
('notifications', '0001_initial'),
]
operations = [
migrations.RunPython(setup_followers, reverse_code=migrations.RunPython.noop),
]

View File

@ -1,13 +1,26 @@
import logging import logging
from actstream.models import Action, followers from actstream.models import Action, Follow
from django.contrib.auth import get_user_model
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
from constants.activity import Flag, Verb
from notifications.models import Notification from notifications.models import Notification
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
VERB2FLAGS = {
Verb.APPROVED: [Flag.AUTHOR, Flag.MODERATOR, Flag.REVIEWER],
Verb.COMMENTED: [Flag.AUTHOR, Flag.MODERATOR, Flag.REVIEWER],
Verb.RATED_EXTENSION: [Flag.AUTHOR],
Verb.REPORTED_EXTENSION: [Flag.MODERATOR],
Verb.REPORTED_REVIEW: [Flag.MODERATOR],
Verb.REQUESTED_CHANGES: [Flag.AUTHOR, Flag.MODERATOR, Flag.REVIEWER],
Verb.REQUESTED_REVIEW: [Flag.MODERATOR, Flag.REVIEWER],
Verb.SUBMITTED_FOR_REVIEW: [Flag.MODERATOR],
}
@receiver(post_save, sender=Action) @receiver(post_save, sender=Action)
def _create_notifications( def _create_notifications(
@ -23,11 +36,21 @@ def _create_notifications(
return return
if not instance.target: if not instance.target:
logger.warning('ignoring an unexpected Action without a target, verb={instance.verb}') logger.warning(f'ignoring an unexpected Action without a target, verb={instance.verb}')
return return
notifications = [] notifications = []
for recipient in followers(instance.target):
flags = VERB2FLAGS.get(instance.verb, None)
if not flags:
logger.warning(f'no follower flags for verb={instance.verb}, nobody will be notified')
return
followers = Follow.objects.for_object(instance.target).filter(flag__in=flags)
user_ids = followers.values_list('user', flat=True)
followers = get_user_model().objects.filter(id__in=user_ids)
for recipient in followers:
if recipient == instance.actor: if recipient == instance.actor:
continue continue
notifications.append(Notification(recipient=recipient, action=instance)) notifications.append(Notification(recipient=recipient, action=instance))

View File

@ -3,7 +3,7 @@ from actstream.actions import follow
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
from constants.activity import Verb from constants.activity import Flag, Verb
from reviewers.models import ApprovalActivity from reviewers.models import ApprovalActivity
@ -23,7 +23,7 @@ def _create_action_from_review_and_follow(
# automatically follow after an interaction # automatically follow after an interaction
# if a user had unfollowed this extension before, # if a user had unfollowed this extension before,
# we are making them a follower again # we are making them a follower again
follow(instance.user, instance.extension, send_action=False) follow(instance.user, instance.extension, send_action=False, flag=Flag.REVIEWER)
activity_type2verb = { activity_type2verb = {
ApprovalActivity.ActivityType.APPROVED: Verb.APPROVED, ApprovalActivity.ActivityType.APPROVED: Verb.APPROVED,

View File

@ -9,6 +9,7 @@ from django.dispatch import receiver
from blender_id_oauth_client import signals as bid_signals from blender_id_oauth_client import signals as bid_signals
from constants.activity import Flag
from extensions.models import Extension from extensions.models import Extension
from users.blender_id import BIDSession from users.blender_id import BIDSession
@ -44,7 +45,7 @@ def update_user(
def update_moderator_follows(instance, action, model, reverse, pk_set, **kwargs): def update_moderator_follows(instance, action, model, reverse, pk_set, **kwargs):
"""Users becoming moderators should follow all extensions, """Users becoming moderators should follow all extensions,
and users that stop being moderators should no longer follow all extensions. and users that stop being moderators should no longer follow all extensions.
The flag='moderator' is used to avoid deleting follow relations that were created in contexts The flag=Flag.MODERATOR is used to avoid deleting follow relations that were created in contexts
other than moderator's duties. other than moderator's duties.
""" """
moderators = Group.objects.get(name='moderators') moderators = Group.objects.get(name='moderators')
@ -62,7 +63,7 @@ def update_moderator_follows(instance, action, model, reverse, pk_set, **kwargs)
for user in users: for user in users:
if action == 'post_remove': if action == 'post_remove':
for extension in extensions: for extension in extensions:
unfollow(user, extension, send_action=False, flag='moderator') unfollow(user, extension, send_action=False, flag=Flag.MODERATOR)
elif action == 'post_add': elif action == 'post_add':
for extension in extensions: for extension in extensions:
follow(user, extension, send_action=False, flag='moderator') follow(user, extension, send_action=False, flag=Flag.MODERATOR)