Skip to content

Commit 520d926

Browse files
committed
feat(eap): Add email setup and template
1 parent a685718 commit 520d926

18 files changed

+755
-13
lines changed

assets

eap/dev_views.py

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
from django.http import HttpResponse
2+
from django.template import loader
3+
from rest_framework import permissions
4+
from rest_framework.views import APIView
5+
6+
7+
class EAPEmailPreview(APIView):
8+
permission_classes = [permissions.IsAuthenticated]
9+
10+
def get(self, request):
11+
type_param = request.GET.get("type")
12+
13+
template_map = {
14+
"registration": "email/eap/registration.html",
15+
"submission": "email/eap/submission.html",
16+
"feedback_to_national_society": "email/eap/feedback_to_national_society.html",
17+
"resubmission_of_revised_eap": "email/eap/re-submission.html",
18+
"feedback_for_revised_eap": "email/eap/feedback_to_revised_eap.html",
19+
"technically_validated_eap": "email/eap/technically_validated_eap.html",
20+
"pending_pfa": "email/eap/pending_pfa.html",
21+
"approved_eap": "email/eap/approved.html",
22+
"reminder": "email/eap/reminder.html",
23+
}
24+
25+
if type_param not in template_map:
26+
valid_values = ", ".join(template_map.keys())
27+
return HttpResponse(
28+
f"Invalid 'type' parameter. Please use one of the following values: {valid_values}.",
29+
)
30+
31+
context_map = {
32+
"registration": {
33+
"eap_type_display": "FULL EAP",
34+
"country_name": "Test Country",
35+
"national_society": "Test National Society",
36+
"supporting_partners": [
37+
{"society_name": "Partner 1"},
38+
{"society_name": "Partner 2"},
39+
],
40+
"disaster_type": "Flood",
41+
"ns_contact_name": "Test registration name",
42+
"ns_contact_email": "[email protected]",
43+
"ns_contact_phone": "1234567890",
44+
},
45+
"submission": {
46+
"eap_type_display": "SIMPLIFIED EAP",
47+
"country_name": "Test Country",
48+
"national_society": "Test National Society",
49+
"people_targated": 100,
50+
"supporting_partners": [
51+
{"society_name": "Partner NS 1"},
52+
{"society_name": "Partner NS 2"},
53+
],
54+
"disaster_type": "Flood",
55+
"total_budget": "250,000 CHF",
56+
"ns_contact_name": "Test Ns Contact name",
57+
"ns_contact_email": "[email protected]",
58+
"ns_contact_phone": "+977-9800000000",
59+
},
60+
"feedback_to_national_society": {
61+
"eap_type_display": "FULL EAP",
62+
"country_name": "Test Country",
63+
"national_society": "Test National Society",
64+
},
65+
"resubmission_of_revised_eap": {
66+
"eap_type_display": "SIMPLIFIED EAP",
67+
"country_name": "Test Country",
68+
"national_society": "Test National Society",
69+
"supporting_partners": [
70+
{"society_name": "Partner NS 1"},
71+
{"society_name": "Partner NS 2"},
72+
],
73+
"version": 2 or 3,
74+
"people_targated": 100,
75+
"disaster_type": "Flood",
76+
"total_budget": "250,000 CHF",
77+
"ns_contact_name": "Test Ns Contact name",
78+
"ns_contact_email": "[email protected]",
79+
"ns_contact_phone": "+977-9800000000",
80+
},
81+
"feedback_for_revised_eap": {
82+
"eap_type_display": "FULL EAP",
83+
"country_name": "Test Country",
84+
"national_society": "Test National Society",
85+
"version": 2,
86+
},
87+
"technically_validated_eap": {
88+
"eap_type_display": "FULL EAP",
89+
"country_name": "Test Country",
90+
"national_society": "Test National Society",
91+
"disaster_type": "Flood",
92+
},
93+
"pending_pfa": {
94+
"eap_type_display": "FULL EAP",
95+
"country_name": "Test Country",
96+
"national_society": "Test National Society",
97+
"disaster_type": "Flood",
98+
},
99+
"approved_eap": {
100+
"eap_type_display": "FULL EAP",
101+
"country_name": "Test Country",
102+
"national_society": "Test National Society",
103+
"disaster_type": "Flood",
104+
},
105+
"reminder": {
106+
"eap_type_display": "FULL EAP",
107+
"country_name": "Test Country",
108+
"national_society": "Test National Society",
109+
"disaster_type": "Flood",
110+
},
111+
}
112+
113+
context = context_map.get(type_param)
114+
if context is None:
115+
return HttpResponse("No context found for the email preview.")
116+
template_file = template_map[type_param]
117+
template = loader.get_template(template_file)
118+
return HttpResponse(template.render(context, request))

eap/serializers.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import typing
22

33
from django.contrib.auth.models import User
4+
from django.db import transaction
45
from django.utils import timezone
56
from django.utils.translation import gettext
67
from rest_framework import serializers
@@ -29,6 +30,7 @@
2930
TimeFrame,
3031
YearsTimeFrameChoices,
3132
)
33+
from eap.tasks import send_new_eap_registration_email
3234
from eap.utils import (
3335
has_country_permission,
3436
is_user_ifrc_admin,
@@ -174,6 +176,16 @@ class Meta:
174176
"modified_by",
175177
]
176178

179+
def create(self, validated_data: dict[str, typing.Any]):
180+
instance = super().create(validated_data)
181+
182+
transaction.on_commit(
183+
lambda: send_new_eap_registration_email.delay(
184+
instance.id,
185+
)
186+
)
187+
return instance
188+
177189
def update(self, instance: EAPRegistration, validated_data: dict[str, typing.Any]) -> dict[str, typing.Any]:
178190
# NOTE: Cannot update once EAP application is being created.
179191
if instance.has_eap_application:

eap/tasks.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import logging
2+
3+
from celery import shared_task
4+
from django.conf import settings
5+
from django.contrib.auth import get_user_model
6+
from django.template.loader import render_to_string
7+
8+
from eap.models import EAPRegistration
9+
from eap.utils import (
10+
get_coordinator_emails_by_region,
11+
get_eap_registration_email_context,
12+
)
13+
from notifications.notification import send_notification
14+
15+
User = get_user_model()
16+
17+
logger = logging.getLogger(__name__)
18+
19+
20+
@shared_task
21+
def send_new_eap_registration_email(eap_registration_id: int):
22+
23+
instance = EAPRegistration.objects.filter(id=eap_registration_id).first()
24+
if not instance:
25+
return None
26+
27+
regional_coordinator_emails = get_coordinator_emails_by_region(instance.country.region)
28+
29+
recipients = [
30+
settings.EMAIL_EAP_DREF_ANTICIPATORY_PILLAR,
31+
instance.ifrc_contact_email,
32+
]
33+
cc_recipients = list(
34+
set(
35+
[
36+
instance.national_society_contact_email,
37+
*settings.EMAIL_EAP_DREF_AA_GLOBAL_TEAM,
38+
*regional_coordinator_emails,
39+
]
40+
)
41+
)
42+
email_context = get_eap_registration_email_context(instance)
43+
email_subject = (
44+
f"[{instance.get_eap_type_display() if instance.get_eap_type_display() else 'EAP'} IN DEVELOPMENT] "
45+
f"{instance.country} {instance.disaster_type}"
46+
)
47+
email_body = render_to_string("email/eap/registration.html", email_context)
48+
email_type = "New EAP Registration"
49+
50+
send_notification(
51+
subject=email_subject,
52+
recipients=recipients,
53+
html=email_body,
54+
mailtype=email_type,
55+
cc_recipients=cc_recipients,
56+
)
57+
58+
return email_context

eap/test_views.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,8 @@ def test_list_eap_registration(self):
122122
self.assertEqual(response.status_code, 200)
123123
self.assertEqual(len(response.data["results"]), 5)
124124

125-
def test_create_eap_registration(self):
125+
@mock.patch("eap.tasks.send_eap_registration_email")
126+
def test_create_eap_registration(self, send_eap_registration_email):
126127
url = "/api/v2/eap-registration/"
127128
data = {
128129
"eap_type": EAPType.FULL_EAP,
@@ -157,6 +158,7 @@ def test_create_eap_registration(self):
157158
self.disaster_type.id,
158159
},
159160
)
161+
self.assertTrue(send_eap_registration_email)
160162

161163
def test_retrieve_eap_registration(self):
162164
eap_registration = EAPRegistrationFactory.create(

eap/utils.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,56 @@
11
import os
22
from typing import Any, Dict, Set, TypeVar
33

4+
from django.conf import settings
45
from django.contrib.auth.models import Permission, User
56
from django.core.exceptions import ValidationError
67
from django.db import models
78

9+
from api.models import Region, RegionName
10+
11+
REGION_EMAIL_MAP = {
12+
RegionName.AFRICA: settings.EMAIL_EAP_AFRICA_COORDINATORS,
13+
RegionName.AMERICAS: settings.EMAIL_EAP_AMERICAS_COORDINATORS,
14+
RegionName.ASIA_PACIFIC: settings.EMAIL_EAP_ASIA_PACIFIC_COORDINATORS,
15+
RegionName.EUROPE: settings.EMAIL_EAP_EUROPE_COORDINATORS,
16+
RegionName.MENA: settings.EMAIL_EAP_MENA_COORDINATORS,
17+
}
18+
19+
20+
def get_coordinator_emails_by_region(region: Region | None) -> list[str]:
21+
"""
22+
This function uses the REGION_EMAIL_MAP dictionary to map Region name to the corresponding list of email addresses.
23+
Args:
24+
region: Region instance for which the coordinator emails are needed.
25+
Returns:
26+
List of email addresses corresponding to the region coordinators.
27+
Returns an empty list if the region is None or not found in the mapping.
28+
"""
29+
if not region:
30+
return []
31+
32+
return REGION_EMAIL_MAP.get(region.name, [])
33+
34+
35+
def get_eap_registration_email_context(instance):
36+
from eap.serializers import EAPRegistrationSerializer
37+
38+
eap_registration_data = EAPRegistrationSerializer(instance).data
39+
40+
email_context = {
41+
"registration_id": eap_registration_data["id"],
42+
"eap_type_display": eap_registration_data["eap_type_display"],
43+
"country_name": eap_registration_data["country_details"]["name"],
44+
"national_society": eap_registration_data["national_society_details"]["society_name"],
45+
"supporting_partners": eap_registration_data["partners_details"],
46+
"disaster_type": eap_registration_data["disaster_type_details"]["name"],
47+
"ns_contact_name": eap_registration_data["national_society_contact_name"],
48+
"ns_contact_email": eap_registration_data["national_society_contact_email"],
49+
"ns_contact_phone": eap_registration_data["national_society_contact_phone_number"],
50+
"frontend_url": settings.GO_WEB_URL,
51+
}
52+
return email_context
53+
854

955
def has_country_permission(user: User, country_id: int) -> bool:
1056
"""Checks if the user has country admin permission."""

main/settings.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,14 @@
6868
EMAIL_USER=(str, None),
6969
EMAIL_PASS=(str, None),
7070
DEBUG_EMAIL=(bool, False), # This was 0/1 before
71+
# EAP-EMAILS
72+
EMAIL_EAP_DREF_ANTICIPATORY_PILLAR=(str, None),
73+
EMAIL_EAP_DREF_AA_GLOBAL_TEAM=(list, None),
74+
EMAIL_EAP_AFRICA_COORDINATORS=(list, None),
75+
EMAIL_EAP_AMERICAS_COORDINATORS=(list, None),
76+
EMAIL_EAP_ASIA_PACIFIC_COORDINATORS=(list, None),
77+
EMAIL_EAP_EUROPE_COORDINATORS=(list, None),
78+
EMAIL_EAP_MENA_COORDINATORS=(list, None),
7179
# TEST_EMAILS=(list, ['[email protected]']), # maybe later
7280
# Translation
7381
# Translator Available:
@@ -198,6 +206,7 @@ def parse_domain(*env_keys: str) -> str:
198206
ALLOWED_HOSTS = [
199207
"localhost",
200208
"0.0.0.0",
209+
"127.0.0.1",
201210
urlparse(GO_API_URL).hostname,
202211
*env("DJANGO_ADDITIONAL_ALLOWED_HOSTS"),
203212
]
@@ -581,6 +590,15 @@ def parse_domain(*env_keys: str) -> str:
581590
DEBUG_EMAIL = env("DEBUG_EMAIL")
582591
# TEST_EMAILS = env('TEST_EMAILS') # maybe later
583592

593+
# EAP-Email
594+
EMAIL_EAP_DREF_ANTICIPATORY_PILLAR = env("EMAIL_EAP_DREF_ANTICIPATORY_PILLAR")
595+
EMAIL_EAP_DREF_AA_GLOBAL_TEAM = env("EMAIL_EAP_DREF_AA_GLOBAL_TEAM")
596+
EMAIL_EAP_AFRICA_COORDINATORS = env("EMAIL_EAP_AFRICA_COORDINATORS")
597+
EMAIL_EAP_AMERICAS_COORDINATORS = env("EMAIL_EAP_AMERICAS_COORDINATORS")
598+
EMAIL_EAP_ASIA_PACIFIC_COORDINATORS = env("EMAIL_EAP_ASIA_PACIFIC_COORDINATORS")
599+
EMAIL_EAP_EUROPE_COORDINATORS = env("EMAIL_EAP_EUROPE_COORDINATORS")
600+
EMAIL_EAP_MENA_COORDINATORS = env("EMAIL_EAP_MENA_COORDINATORS")
601+
584602
DATA_UPLOAD_MAX_MEMORY_SIZE = 104857600 # default 2621440, 2.5MB -> 100MB
585603
# default 1000, was not enough for Mozambique Cyclone Idai data
586604
# second 2000, was not enouch for Global COVID Emergency

main/urls.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
from deployments import drf_views as deployment_views
5858
from dref import views as dref_views
5959
from eap import views as eap_views
60+
from eap.dev_views import EAPEmailPreview
6061
from flash_update import views as flash_views
6162
from lang import views as lang_views
6263
from local_units import views as local_units_views
@@ -287,6 +288,7 @@
287288
# For django versions before 2.0:
288289
# url(r'^__debug__/', include(debug_toolbar.urls)),
289290
url(r"^dev/email-preview/local-units/", LocalUnitsEmailPreview.as_view()),
291+
url(r"^dev/email-preview/eap/", EAPEmailPreview.as_view()),
290292
]
291293
+ urlpatterns
292294
+ static.static(

0 commit comments

Comments
 (0)