Skip to content

Commit

Permalink
Merge pull request #4407 from unicef/mailjet-changes-send-to-call-rec…
Browse files Browse the repository at this point in the history
…ipients

[219876] [219849] Apply Mailjet for Send API token; Change payment notification recipients logic
  • Loading branch information
pkujawa authored Nov 6, 2024
2 parents 3e1b2a7 + fa67564 commit 6ef7a33
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 54 deletions.
10 changes: 4 additions & 6 deletions src/hct_mis_api/api/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from django.contrib.admin.models import LogEntry
from django.contrib.admin.templatetags.admin_urls import admin_urlname
from django.core.exceptions import ValidationError
from django.core.mail import send_mail
from django.db.models import QuerySet
from django.db.transaction import atomic
from django.forms import Form
Expand Down Expand Up @@ -134,11 +133,10 @@ def _get_email_context(self, request: HttpRequest, obj: Any) -> Dict[str, Any]:

def _send_token_email(self, request: HttpRequest, obj: Any, template: str) -> None:
try:
send_mail(
f"HOPE API Token {obj} infos",
template.format(**self._get_email_context(request, obj)),
None,
recipient_list=[obj.user.email],
user = obj.user
user.email_user(
subject=f"HOPE API Token {obj} infos",
text_body=template.format(**self._get_email_context(request, obj)),
)
self.message_user(request, f"Email sent to {obj.user.email}", messages.SUCCESS)
except OSError:
Expand Down
37 changes: 21 additions & 16 deletions src/hct_mis_api/apps/payment/notifications.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import logging
from typing import Any, Dict, List
from typing import Any, Dict

from django.conf import settings
from django.db.models import Q, QuerySet
Expand Down Expand Up @@ -37,18 +37,22 @@ class PaymentNotification:
ACTION_SEND_FOR_APPROVAL: {
"action_name": "sent for approval",
"subject": "Payment pending for Approval",
"recipient_title": "Approver",
},
ACTION_APPROVE: {
"action_name": "approved",
"subject": "Payment pending for Authorization",
"recipient_title": "Authorizer",
},
ACTION_AUTHORIZE: {
"action_name": "authorized",
"subject": "Payment pending for Release",
"recipient_title": "Reviewer",
},
ACTION_REVIEW: {
"action_name": "released",
"subject": "Payment is Released",
"recipient_title": "Reviewer",
},
}

Expand All @@ -61,8 +65,9 @@ def __init__(self, payment_plan: PaymentPlan, action: str, action_user: User, ac
self.payment_plan_creation_date = self.payment_plan.created_at
self.email_subject = self.ACTION_PREPARE_EMAIL_BODIES_MAP[self.action]["subject"]
self.action_name = self.ACTION_PREPARE_EMAIL_BODIES_MAP[self.action]["action_name"]
self.recipient_title = self.ACTION_PREPARE_EMAIL_BODIES_MAP[self.action]["recipient_title"]
self.user_recipients = self._prepare_user_recipients()
self.emails = self._prepare_emails()
self.email = self._prepare_email()
self.enable_email_notification = self.payment_plan.business_area.enable_email_notification

def _prepare_user_recipients(self) -> QuerySet[User]:
Expand Down Expand Up @@ -93,22 +98,23 @@ def _prepare_user_recipients(self) -> QuerySet[User]:
if permission in DEFAULT_PERMISSIONS_LIST_FOR_IS_UNICEF_PARTNER
else Q()
)
users = User.objects.filter(
(Q(user_roles__in=user_roles) & program_access_q) | Q(partner_role_q & program_access_q) | unicef_q
).distinct()
users = (
User.objects.filter(
(Q(user_roles__in=user_roles) & program_access_q) | Q(partner_role_q & program_access_q) | unicef_q
)
.exclude(id=self.action_user.id)
.distinct()
)
if settings.ENV == "prod":
users = users.exclude(is_superuser=True)
return users

def _prepare_emails(self) -> List[MailjetClient]:
return [self._prepare_email(user) for user in self.user_recipients.exclude(id=self.action_user.id)]

def _prepare_email(self, user_recipient: User) -> MailjetClient:
body_variables = self._prepare_body_variables(user_recipient)
def _prepare_email(self) -> MailjetClient:
body_variables = self._prepare_body_variables()
email = MailjetClient(
mailjet_template_id=config.MAILJET_TEMPLATE_PAYMENT_PLAN_NOTIFICATION,
subject=self.email_subject,
recipients=[user_recipient.email],
recipients=[user_recipient.email for user_recipient in self.user_recipients],
ccs=[self.action_user.email],
variables=body_variables,
)
Expand All @@ -117,16 +123,15 @@ def _prepare_email(self, user_recipient: User) -> MailjetClient:
def send_email_notification(self) -> None:
if config.SEND_PAYMENT_PLANS_NOTIFICATION and self.enable_email_notification:
try:
for email in self.emails:
email.send_email()
self.email.send_email()
except Exception as e: # pragma: no cover
logger.exception(e)

def _prepare_body_variables(self, user_recipient: User) -> Dict[str, Any]:
def _prepare_body_variables(self) -> Dict[str, Any]:
protocol = "https" if settings.SOCIAL_AUTH_REDIRECT_IS_HTTPS else "http"
variables = {
"first_name": user_recipient.first_name,
"last_name": user_recipient.last_name,
"first_name": "Payment Plan",
"last_name": self.recipient_title,
"action_name": self.action_name,
"payment_plan_url": (
f"{protocol}://{settings.FRONTEND_HOST}/{self.payment_plan.business_area.slug}/programs/"
Expand Down
1 change: 0 additions & 1 deletion src/hct_mis_api/apps/utils/mailjet.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ def send_email(self) -> bool:

email_body = self._get_email_body()
attachments = {"Attachments": self.attachments} if self.attachments else {}

data = {
"Messages": [
{
Expand Down
73 changes: 73 additions & 0 deletions tests/unit/api/test_api_token.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import json
from datetime import datetime
from typing import Any
from unittest.mock import patch

from django.conf import settings
from django.http import HttpRequest
from django.test import TestCase, override_settings

from constance.test import override_config

from hct_mis_api.api.admin import TOKEN_INFO_EMAIL, APITokenAdmin
from hct_mis_api.api.models import Grant
from hct_mis_api.apps.account.fixtures import UserFactory
from hct_mis_api.apps.core.fixtures import create_afghanistan
from tests.unit.api.factories import APITokenFactory


class TestApiToken(TestCase):
@classmethod
def setUpClass(cls) -> None:
super().setUpClass()
cls.afg = create_afghanistan()
cls.user = UserFactory(
email="[email protected]",
)
cls.token = APITokenFactory(
user=cls.user,
grants=[
Grant.API_READ_ONLY.name,
Grant.API_RDI_UPLOAD.name,
Grant.API_PROGRAM_CREATE.name,
],
valid_to=datetime(2050, 1, 1),
)
cls.token.valid_for.set([cls.afg])

@patch("hct_mis_api.apps.utils.mailjet.requests.post")
@patch.object(APITokenAdmin, "message_user", return_value=None)
@patch.object(APITokenAdmin, "__init__", return_value=None)
@override_settings(EMAIL_SUBJECT_PREFIX="test")
@override_config(ENABLE_MAILJET=True)
def test_send_api_token(
self, mocked_requests_init: Any, mocked_requests_user: Any, mocked_requests_post: Any
) -> None:
request = HttpRequest()

APITokenAdmin()._send_token_email(request, self.token, TOKEN_INFO_EMAIL)

mocked_requests_post.assert_called_once()

expected_data = json.dumps(
{
"Messages": [
{
"From": {"Email": settings.DEFAULT_EMAIL, "Name": settings.DEFAULT_EMAIL_DISPLAY},
"Subject": f"[test] HOPE API Token {self.token} infos",
"To": [
{
"Email": "[email protected]",
},
],
"Cc": [],
"TextPart": f"\nDear {self.user.first_name},\n\nplease find below API token infos\n\nName: {self.token}\nKey: {self.token.key}\nGrants: {self.token.grants}\nExpires: {self.token.valid_to}\nBusiness Areas: {', '.join(self.token.valid_for.values_list('name', flat=True))}\n\nRegards\n\nThe HOPE Team\n",
}
]
}
)
mocked_requests_post.assert_called_with(
"https://api.mailjet.com/v3.1/send",
auth=(settings.MAILJET_API_KEY, settings.MAILJET_SECRET_KEY),
data=expected_data,
)
68 changes: 37 additions & 31 deletions tests/unit/apps/payment/test_payment_notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ def test_send_email_notification(self, mock_send: Any) -> None:
payment_notification.send_email_notification()
self.assertEqual(
mock_send.call_count,
3,
1,
)

@mock.patch("hct_mis_api.apps.payment.notifications.MailjetClient.send_email")
Expand All @@ -276,8 +276,7 @@ def test_send_email_notification_subject_test_env(self, mock_send: Any) -> None:
self.user_action_user,
f"{timezone.now():%-d %B %Y}",
)
for mailjet_client in payment_notification.emails:
self.assertEqual(mailjet_client.subject, "[test] Payment pending for Approval")
self.assertEqual(payment_notification.email.subject, "[test] Payment pending for Approval")

@mock.patch("hct_mis_api.apps.payment.notifications.MailjetClient.send_email")
@override_config(SEND_PAYMENT_PLANS_NOTIFICATION=True)
Expand All @@ -289,8 +288,7 @@ def test_send_email_notification_subject_prod_env(self, mock_send: Any) -> None:
self.user_action_user,
f"{timezone.now():%-d %B %Y}",
)
for mailjet_client in payment_notification.emails:
self.assertEqual(mailjet_client.subject, "Payment pending for Approval")
self.assertEqual(payment_notification.email.subject, "Payment pending for Approval")

@mock.patch("hct_mis_api.apps.utils.mailjet.requests.post")
@override_config(
Expand All @@ -305,14 +303,18 @@ def test_send_email_notification_catch_all_email(self, mock_post: Any) -> None:
f"{timezone.now():%-d %B %Y}",
)
payment_notification.send_email_notification()
for mailjet_client in payment_notification.emails:
self.assertEqual(
mailjet_client.recipients,
["[email protected]", "[email protected]"],
)
self.assertEqual(len(payment_notification.email.recipients), 2)
self.assertIn(
"[email protected]",
payment_notification.email.recipients,
)
self.assertIn(
"[email protected]",
payment_notification.email.recipients,
)
self.assertEqual(
mock_post.call_count,
3,
1,
)

@mock.patch("hct_mis_api.apps.utils.mailjet.requests.post")
Expand All @@ -327,18 +329,22 @@ def test_send_email_notification_without_catch_all_email(self, mock_post: Any) -
f"{timezone.now():%-d %B %Y}",
)
payment_notification.send_email_notification()
for mailjet_client in payment_notification.emails:
self.assertIn(
mailjet_client.recipients[0],
[
self.user_with_approval_permission_partner_unicef.email,
self.user_with_approval_permission_partner_with_program_access.email,
self.user_with_partner_action_permissions_and_program_access.email,
],
)
self.assertEqual(len(payment_notification.email.recipients), 3)
self.assertIn(
self.user_with_approval_permission_partner_unicef.email,
payment_notification.email.recipients,
)
self.assertIn(
self.user_with_approval_permission_partner_with_program_access.email,
payment_notification.email.recipients,
)
self.assertIn(
self.user_with_partner_action_permissions_and_program_access.email,
payment_notification.email.recipients,
)
self.assertEqual(
mock_post.call_count,
3,
1,
)

@mock.patch("hct_mis_api.apps.utils.mailjet.requests.post")
Expand All @@ -356,16 +362,16 @@ def test_send_email_notification_exclude_superuser(self, mock_post: Any) -> None
f"{timezone.now():%-d %B %Y}",
)
payment_notification.send_email_notification()
for mailjet_client in payment_notification.emails:
self.assertIn(
mailjet_client.recipients[0],
[
self.user_with_approval_permission_partner_with_program_access.email,
self.user_with_partner_action_permissions_and_program_access.email,
],
)
self.assertNotIn(self.user_with_approval_permission_partner_unicef.email, payment_notification.emails)
self.assertEqual(len(payment_notification.email.recipients), 2)
self.assertIn(
self.user_with_approval_permission_partner_with_program_access.email,
payment_notification.email.recipients,
)
self.assertIn(
self.user_with_partner_action_permissions_and_program_access.email,
payment_notification.email.recipients,
)
self.assertEqual(
mock_post.call_count,
2,
1,
)

0 comments on commit 6ef7a33

Please sign in to comment.