Skip to content

Commit 9b8ff36

Browse files
committed
feat(resources): add email notifications for contact and demo requests
- Add email notification functionality for tool demo requests to notify the tool owner. - Add email notification for contact to admin
1 parent e5f9faf commit 9b8ff36

File tree

6 files changed

+126
-20
lines changed

6 files changed

+126
-20
lines changed

apps/resources/graphql/inputs.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,6 @@ class RequestDemoInput:
1717
email: str
1818
content: str
1919
national_society: str
20-
tool: int
20+
tool: strawberry.ID
2121
captcha_hashkey: str
2222
captcha_code: str

apps/resources/serializers.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
import typing
22

33
from captcha.serializers import CaptchaModelSerializer
4+
from django.db import transaction
45
from rest_framework import serializers
56

67
from apps.resources.models import ContactRequest, RequestDemo
78
from apps.tool_picker.models import Tool
89

10+
from .tasks import send_contact_request_email, send_demo_request_email
11+
912

1013
class ContactRequestSerializer(CaptchaModelSerializer):
1114
captcha_code = serializers.CharField(write_only=True)
@@ -26,7 +29,11 @@ class Meta:
2629
def create(self, validated_data: dict[str, typing.Any]):
2730
validated_data.pop("captcha_code", None)
2831
validated_data.pop("captcha_hashkey", None)
29-
return super().create(validated_data)
32+
instance = super().create(validated_data)
33+
transaction.on_commit(
34+
lambda: send_contact_request_email.delay(instance.id), # type: ignore[reportIncompatibleVariableOverride]
35+
)
36+
return instance
3037

3138

3239
class RequestDemoSerializer(CaptchaModelSerializer):
@@ -53,4 +60,8 @@ class Meta:
5360
def create(self, validated_data: dict[str, typing.Any]):
5461
validated_data.pop("captcha_code", None)
5562
validated_data.pop("captcha_hashkey", None)
56-
return super().create(validated_data)
63+
instance = super().create(validated_data)
64+
transaction.on_commit(
65+
lambda: send_demo_request_email.delay(instance.id), # type: ignore[reportIncompatibleVariableOverride]
66+
)
67+
return instance

apps/resources/tasks.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import logging
2+
3+
from celery import shared_task
4+
from django.conf import settings
5+
from django.template.loader import render_to_string
6+
7+
from apps.resources.models import ContactRequest, RequestDemo
8+
from apps.resources.utils import get_contact_request_email_context, get_demo_request_email_context
9+
from utils.email.service import send_email
10+
11+
logger = logging.getLogger(__name__)
12+
13+
14+
@shared_task
15+
def send_contact_request_email(contact_id: int):
16+
instance = ContactRequest.objects.filter(id=contact_id).first()
17+
if not instance:
18+
return None
19+
20+
recipient = settings.EMAIL_TO
21+
email_subject = "You got a new feedback!"
22+
email_context = get_contact_request_email_context(instance)
23+
html_body = render_to_string("email/contact_request.html", email_context)
24+
25+
send_email(
26+
subject=email_subject,
27+
to_email=[recipient],
28+
html=html_body,
29+
)
30+
return True
31+
32+
33+
@shared_task
34+
def send_demo_request_email(request_demo_id: int):
35+
instance = (
36+
RequestDemo.objects.select_related("tool").prefetch_related("tool__tool_owners").filter(id=request_demo_id).first()
37+
)
38+
if not instance:
39+
return None
40+
41+
tool_owners = instance.tool.tool_owners.all()
42+
43+
if not tool_owners.exists():
44+
logger.info("skipping cause there are no tool owners associated with this tool")
45+
return False
46+
47+
recipients = list(tool_owners.values_list("email", flat=True))
48+
email_subject = "You got a new request for demo!"
49+
email_context = get_demo_request_email_context(instance)
50+
html = render_to_string("email/request_demo.html", email_context)
51+
send_email(
52+
subject=email_subject,
53+
to_email=recipients,
54+
html=html,
55+
cc_email=[settings.EMAIL_TO],
56+
)
57+
return True

apps/resources/tests/mutation_test.py

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import typing
2+
from unittest.mock import call, patch
23

34
from captcha.models import CaptchaStore
45

@@ -44,14 +45,16 @@ def setUpClass(cls):
4445
cls.user = UserFactory.create(email="test@gmail.com")
4546

4647
def _create_contact_request_mutation(self, data: dict[str, str], **kwargs: typing.Any):
47-
return self.query_check(
48-
query=self.Mutation.CREATE_CONTACT_REQUEST,
49-
variables={
50-
"data": data,
51-
},
52-
)
53-
54-
def test_create_contact_request(self):
48+
with self.captureOnCommitCallbacks(execute=True):
49+
return self.query_check(
50+
query=self.Mutation.CREATE_CONTACT_REQUEST,
51+
variables={
52+
"data": data,
53+
},
54+
)
55+
56+
@patch("apps.resources.serializers.send_contact_request_email.delay")
57+
def test_create_contact_request(self, mock_requests): # type: ignore[reportMissingParameterType]
5558
# Generates CAPTCHA key and value
5659
captcha_key = CaptchaStore.generate_key()
5760
captcha_obj = CaptchaStore.objects.get(hashkey=captcha_key)
@@ -81,6 +84,9 @@ def test_create_contact_request(self):
8184
"nationalSociety": contact_request.national_society,
8285
}
8386

87+
mock_requests.assert_called_once()
88+
mock_requests.assert_has_calls([call(contact_request.pk)])
89+
8490
def test_create_contact_request_without_captcha(self):
8591
contact_request_data = {
8692
"name": "John",
@@ -157,14 +163,16 @@ def setUpClass(cls):
157163
cls.tool = ToolFactory.create()
158164

159165
def _create_tool_demo_request_mutation(self, data: dict[str, str | int], **kwargs: typing.Any):
160-
return self.query_check(
161-
query=self.Mutation.CREATE_DEMO_REQUEST,
162-
variables={
163-
"data": data,
164-
},
165-
)
166-
167-
def test_create_tool_demo_request(self):
166+
with self.captureOnCommitCallbacks(execute=True):
167+
return self.query_check(
168+
query=self.Mutation.CREATE_DEMO_REQUEST,
169+
variables={
170+
"data": data,
171+
},
172+
)
173+
174+
@patch("apps.resources.serializers.send_demo_request_email.delay")
175+
def test_create_tool_demo_request(self, mock_requests): # type: ignore[reportMissingParameterType]
168176
captcha_key = CaptchaStore.generate_key()
169177
captcha_obj = CaptchaStore.objects.get(hashkey=captcha_key)
170178
captcha_code = captcha_obj.response
@@ -196,6 +204,8 @@ def test_create_tool_demo_request(self):
196204
"pk": self.gID(demo_request.tool.pk),
197205
},
198206
}
207+
mock_requests.assert_called_once()
208+
mock_requests.assert_has_calls([call(demo_request.pk)])
199209

200210
def test_create_tool_demo_request_without_captcha(self):
201211
tool_demo_request_data = {

apps/resources/utils.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from apps.resources.models import ContactRequest, RequestDemo
2+
3+
4+
def get_contact_request_email_context(instance: ContactRequest):
5+
from .serializers import ContactRequestSerializer
6+
7+
data = dict(ContactRequestSerializer(instance).data)
8+
9+
return {
10+
"name": data["name"],
11+
"email": data["email"],
12+
"national_society": data["national_society"],
13+
"content": data["content"],
14+
}
15+
16+
17+
def get_demo_request_email_context(instance: RequestDemo):
18+
from .serializers import RequestDemoSerializer
19+
20+
data = dict(RequestDemoSerializer(instance).data)
21+
tool_name = instance.tool.name
22+
return {
23+
"name": data["name"],
24+
"email": data["email"],
25+
"national_society": data["national_society"],
26+
"content": data["content"],
27+
"tool": tool_name,
28+
}

schema.graphql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ input RequestDemoInput {
285285
email: String!
286286
content: String!
287287
nationalSociety: String!
288-
tool: Int!
288+
tool: ID!
289289
captchaHashkey: String!
290290
captchaCode: String!
291291
}

0 commit comments

Comments
 (0)