Skip to content

Commit

Permalink
[#2581] Refactor notifications for expiring actions/plans, messages, …
Browse files Browse the repository at this point in the history
…cases

    - replace cron jobs with celery tasks
    - explicitly mark internal utility functions for case notifications
  • Loading branch information
pi-sigma committed Jul 4, 2024
1 parent abc196e commit 42104b2
Show file tree
Hide file tree
Showing 28 changed files with 666 additions and 478 deletions.
1 change: 1 addition & 0 deletions src/open_inwoner/accounts/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ def __init__(self, user, *args, **kwargs):
self.fields["last_name"].required = True

# notifications
# TODO: add condition to check if notifications should be displayed
if not (user.login_type == LoginTypeChoices.digid and case_page_is_published()):
del self.fields["cases_notifications"]
if not inbox_page_is_published():
Expand Down
48 changes: 0 additions & 48 deletions src/open_inwoner/accounts/management/commands/actions_expire.py

This file was deleted.

This file was deleted.

10 changes: 10 additions & 0 deletions src/open_inwoner/accounts/managers.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from datetime import date

from django.contrib.auth.models import BaseUserManager
from django.db.models import Q, QuerySet

Expand Down Expand Up @@ -76,3 +78,11 @@ def visible(self):

def connected(self, user):
return self.filter(Q(created_by=user) | Q(is_for=user)).distinct()

def expiring_actions_user_ids(self, end_date: date):
"""
Returns:
id's of users with expiring actions
"""
user_ids = self.filter(end_date=end_date).values_list("is_for_id", flat=True)
return user_ids
9 changes: 9 additions & 0 deletions src/open_inwoner/accounts/notifications/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from .notify_expiring_actions import notify_about_expiring_actions
from .notify_expiring_plans import notify_about_expiring_plans
from .notify_messages import notify_about_messages

__all__ = [
"notify_about_expiring_actions",
"notify_about_expiring_plans",
"notify_about_messages",
]
39 changes: 39 additions & 0 deletions src/open_inwoner/accounts/notifications/notify_expiring_actions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import logging

from django.db.models.query import QuerySet
from django.urls import reverse

from mail_editor.helpers import find_template

from open_inwoner.accounts.models import Action, User
from open_inwoner.utils.url import build_absolute_url

logger = logging.getLogger(__name__)


def notify_about_expiring_actions(
receiver: User, objects: QuerySet[Action], channel: str
):
send = _channels[channel]

send(receiver=receiver, actions=objects)


def _send_email(receiver: User, actions: QuerySet[Action]):
actions_link = build_absolute_url(reverse("profile:action_list"))
template = find_template("expiring_action")
context = {
"actions": actions,
"actions_link": actions_link,
}

template.send_email([receiver.email], context)

logger.info(
f"The email was sent to the user {receiver} about {actions.count()} expiring actions"
)


_channels = {
"email": _send_email,
}
42 changes: 42 additions & 0 deletions src/open_inwoner/accounts/notifications/notify_expiring_plans.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import logging

from django.db.models.query import QuerySet
from django.urls import reverse

from mail_editor.helpers import find_template

from open_inwoner.accounts.models import User
from open_inwoner.plans.models import Plan
from open_inwoner.userfeed import hooks as userfeed_hooks
from open_inwoner.utils.url import build_absolute_url

logger = logging.getLogger(__name__)


def notify_about_expiring_plans(receiver: User, objects: QuerySet[Plan], channel: str):
for plan in objects:
userfeed_hooks.plan_expiring(receiver, plan)

send = _channels[channel]

send(receiver=receiver, plans=objects)


def _send_email(receiver: User, plans: QuerySet[Plan]):
plan_list_link = build_absolute_url(reverse("collaborate:plan_list"))
template = find_template("expiring_plan")
context = {
"plans": plans,
"plan_list_link": plan_list_link,
}

template.send_email([receiver.email], context)

logger.info(
f"The email was sent to the user {receiver} about {plans.count()} expiring plans"
)


_channels = {
"email": _send_email,
}
48 changes: 48 additions & 0 deletions src/open_inwoner/accounts/notifications/notify_messages.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import logging

from django.db.models import Count
from django.db.models.query import QuerySet
from django.urls import reverse

from mail_editor.helpers import find_template

from open_inwoner.accounts.models import Message, User
from open_inwoner.utils.url import build_absolute_url

logger = logging.getLogger(__name__)


def notify_about_messages(receiver: User, objects: QuerySet[Message], channel: str):
send = _channels[channel]

send(receiver=receiver, objects=objects)


def _send_email(receiver: User, objects: QuerySet[Message]):
inbox_url = build_absolute_url(reverse("inbox:index"))
template = find_template("new_messages")

messages_to_update = objects
total_messages = messages_to_update.count()
total_senders = messages_to_update.aggregate(
total_senders=Count("sender", distinct=True)
)["total_senders"]

context = {
"total_messages": total_messages,
"total_senders": total_senders,
"inbox_link": inbox_url,
}

template.send_email([receiver.email], context)

messages_to_update.update(sent=True)

logger.info(
f"The email was sent to the user {receiver} about {total_messages} new messages"
)


_channels = {
"email": _send_email,
}
116 changes: 116 additions & 0 deletions src/open_inwoner/accounts/tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
from datetime import date
from typing import Callable

from django.db.models import Q
from django.db.models.query import QuerySet
from django.utils.translation import gettext as _

from celery import group
from celery.result import allow_join_result

from open_inwoner.accounts.models import Action, Message, User
from open_inwoner.celery import app
from open_inwoner.plans.models import Plan
from open_inwoner.utils.constants import NotificationChannel

from .notifications import (
notify_about_expiring_actions,
notify_about_expiring_plans,
notify_about_messages,
)


@app.task
def send_email(receiver: User, objects: QuerySet, notify: Callable):
"""
Send email to `receiver` about `objects` using `notify`
"""
notify(
receiver=receiver,
objects=objects,
channel=NotificationChannel.email,
)


@app.task(name=_("Send emails about expiring actions"))
def send_emails_about_expiring_actions():
today = date.today()
user_ids = Action.objects.expiring_actions_user_ids(end_date=today)
receivers = User.objects.filter(
is_active=True, plans_notifications=True, pk__in=user_ids
).distinct()

task_group = group(
send_email.s(
receiver=receiver,
objects=Action.objects.filter(is_for=receiver, end_date=today),
notify=notify_about_expiring_actions,
)
for receiver in receivers
)

promise = task_group.apply_async()
with allow_join_result():
return promise.get()


@app.task(name=_("Send emails about expiring plans"))
def send_emails_about_expiring_plans():
today = date.today()
user_ids = Plan.objects.expiring_plans_user_ids(end_date=today)
receivers = User.objects.filter(
is_active=True, plans_notifications=True, pk__in=user_ids
).distinct()

task_group = group(
send_email.s(
receiver=receiver,
objects=Plan.objects.filter(end_date=today).filter(
Q(created_by=receiver) | Q(plan_contacts=receiver)
),
notify=notify_about_expiring_plans,
)
for receiver in receivers
)

promise = task_group.apply_async()
with allow_join_result():
return promise.get()


@app.task(name=_("Send emails about messages"))
def send_emails_about_messages():
receivers = User.objects.filter(
received_messages__seen=False,
received_messages__sent=False,
is_active=True,
messages_notifications=True,
).distinct()

email_data = []

for receiver in receivers:
messages_to_update = Message.objects.filter(
receiver=receiver,
seen=False,
sent=False,
)
email_data.append(
{
"receiver": receiver,
"messages_to_update": messages_to_update,
}
)

task_group = group(
send_email.s(
receiver=data["receiver"],
objects=data["messages_to_update"],
notify=notify_about_messages,
)
for data in email_data
)

promise = task_group.apply_async()
with allow_join_result():
return promise.get()
Loading

0 comments on commit 42104b2

Please sign in to comment.