Skip to content

Commit f08a731

Browse files
authored
Send confirmation email when the user submits a proposal (#4196)
1 parent 46c7657 commit f08a731

File tree

5 files changed

+141
-3
lines changed

5 files changed

+141
-3
lines changed

backend/api/submissions/mutations.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
from urllib.parse import urljoin
2+
from django.conf import settings
3+
from grants.tasks import get_name
4+
from notifications.models import EmailTemplate, EmailTemplateIdentifier
15
from strawberry.scalars import JSON
26

37
from django.db import transaction
@@ -383,6 +387,24 @@ def send_submission(
383387
"cfp",
384388
)
385389

390+
email_template = EmailTemplate.objects.for_conference(
391+
conference
392+
).get_by_identifier(EmailTemplateIdentifier.proposal_received_confirmation)
393+
394+
proposal_url = urljoin(
395+
settings.FRONTEND_URL,
396+
f"/submission/{instance.hashid}",
397+
)
398+
399+
email_template.send_email(
400+
recipient=request.user,
401+
placeholders={
402+
"user_name": get_name(request.user, "there"),
403+
"proposal_title": instance.title.localize("en"),
404+
"proposal_url": proposal_url,
405+
},
406+
)
407+
386408
def _notify_new_submission():
387409
notify_new_cfp_submission.delay(
388410
submission_id=instance.id,

backend/api/submissions/tests/test_send_submission.py

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from notifications.models import EmailTemplateIdentifier
2+
from notifications.tests.factories import EmailTemplateFactory
13
from privacy_policy.models import PrivacyPolicyAcceptanceRecord
24
from files_upload.tests.factories import FileFactory
35
from conferences.tests.factories import (
@@ -138,7 +140,11 @@ def _submit_proposal(client, conference, submission, **kwargs):
138140
)
139141

140142

141-
def test_submit_talk(graphql_client, user, django_capture_on_commit_callbacks, mocker):
143+
def test_submit_talk(
144+
graphql_client, user, django_capture_on_commit_callbacks, mocker, settings
145+
):
146+
settings.FRONTEND_URL = "http://testserver"
147+
mock_email_template = mocker.patch("api.submissions.mutations.EmailTemplate")
142148
mock_notify = mocker.patch("api.submissions.mutations.notify_new_cfp_submission")
143149
graphql_client.force_login(user)
144150

@@ -151,6 +157,11 @@ def test_submit_talk(graphql_client, user, django_capture_on_commit_callbacks, m
151157
audience_levels=("Beginner",),
152158
)
153159

160+
EmailTemplateFactory(
161+
conference=conference,
162+
identifier=EmailTemplateIdentifier.proposal_received_confirmation,
163+
)
164+
154165
speaker_photo = FileFactory().id
155166

156167
with django_capture_on_commit_callbacks(execute=True):
@@ -208,6 +219,15 @@ def test_submit_talk(graphql_client, user, django_capture_on_commit_callbacks, m
208219

209220
mock_notify.delay.assert_called_once()
210221

222+
mock_email_template.objects.for_conference().get_by_identifier().send_email.assert_called_once_with(
223+
recipient=user,
224+
placeholders={
225+
"user_name": user.full_name,
226+
"proposal_title": "English",
227+
"proposal_url": f"http://testserver/submission/{talk.hashid}",
228+
},
229+
)
230+
211231

212232
def test_submit_talk_with_photo_to_upload(graphql_client, user, mocker):
213233
graphql_client.force_login(user)
@@ -220,7 +240,10 @@ def test_submit_talk_with_photo_to_upload(graphql_client, user, mocker):
220240
durations=("50",),
221241
audience_levels=("Beginner",),
222242
)
223-
243+
EmailTemplateFactory(
244+
conference=conference,
245+
identifier=EmailTemplateIdentifier.proposal_received_confirmation,
246+
)
224247
speaker_photo = FileFactory().id
225248

226249
resp, variables = _submit_talk(
@@ -253,6 +276,11 @@ def test_submit_talk_without_photo_fails(graphql_client, user, mocker):
253276
audience_levels=("Beginner",),
254277
)
255278

279+
EmailTemplateFactory(
280+
conference=conference,
281+
identifier=EmailTemplateIdentifier.proposal_received_confirmation,
282+
)
283+
256284
resp, variables = _submit_talk(
257285
graphql_client,
258286
conference,
@@ -282,6 +310,11 @@ def test_submit_talk_with_existing_participant(graphql_client, user):
282310
audience_levels=("Beginner",),
283311
)
284312

313+
EmailTemplateFactory(
314+
conference=conference,
315+
identifier=EmailTemplateIdentifier.proposal_received_confirmation,
316+
)
317+
285318
participant = Participant.objects.create(
286319
conference=conference, user_id=user.id, bio="old bio"
287320
)
@@ -339,6 +372,11 @@ def test_submit_talk_with_missing_data_of_other_language_fails(graphql_client, u
339372
audience_levels=("Beginner",),
340373
)
341374

375+
EmailTemplateFactory(
376+
conference=conference,
377+
identifier=EmailTemplateIdentifier.proposal_received_confirmation,
378+
)
379+
342380
resp, _ = _submit_talk(
343381
graphql_client,
344382
conference,
@@ -370,6 +408,11 @@ def test_submit_talk_with_missing_data_fails(graphql_client, user):
370408
audience_levels=("Beginner",),
371409
)
372410

411+
EmailTemplateFactory(
412+
conference=conference,
413+
identifier=EmailTemplateIdentifier.proposal_received_confirmation,
414+
)
415+
373416
resp, _ = _submit_talk(
374417
graphql_client,
375418
conference,
@@ -413,6 +456,11 @@ def test_submit_talk_with_multiple_languages(graphql_client, user):
413456
audience_levels=("Beginner",),
414457
)
415458

459+
EmailTemplateFactory(
460+
conference=conference,
461+
identifier=EmailTemplateIdentifier.proposal_received_confirmation,
462+
)
463+
416464
resp, variables = _submit_talk(
417465
graphql_client,
418466
conference,
@@ -707,6 +755,11 @@ def test_cannot_propose_a_talk_if_a_cfp_is_not_specified(graphql_client, user):
707755
audience_levels=("Beginner",),
708756
)
709757

758+
EmailTemplateFactory(
759+
conference=conference,
760+
identifier=EmailTemplateIdentifier.proposal_received_confirmation,
761+
)
762+
710763
resp, _ = _submit_talk(graphql_client, conference)
711764

712765
assert resp["data"]["sendSubmission"]["__typename"] == "SendSubmissionErrors"
@@ -729,6 +782,11 @@ def test_same_user_can_propose_multiple_talks_to_the_same_conference(
729782
audience_levels=("Beginner",),
730783
)
731784

785+
EmailTemplateFactory(
786+
conference=conference,
787+
identifier=EmailTemplateIdentifier.proposal_received_confirmation,
788+
)
789+
732790
resp, _ = _submit_talk(graphql_client, conference, title={"en": "My first talk"})
733791

734792
assert resp["data"]["sendSubmission"]["title"] == "My first talk"
@@ -760,6 +818,11 @@ def test_submit_tutorial(graphql_client, user):
760818
audience_levels=("Beginner",),
761819
)
762820

821+
EmailTemplateFactory(
822+
conference=conference,
823+
identifier=EmailTemplateIdentifier.proposal_received_confirmation,
824+
)
825+
763826
resp, _ = _submit_tutorial(
764827
graphql_client, conference, title={"en": "My first tutorial"}
765828
)
@@ -785,6 +848,11 @@ def test_submit_tutorial_and_talk_to_the_same_conference(graphql_client, user):
785848
audience_levels=("Beginner",),
786849
)
787850

851+
EmailTemplateFactory(
852+
conference=conference,
853+
identifier=EmailTemplateIdentifier.proposal_received_confirmation,
854+
)
855+
788856
resp, _ = _submit_tutorial(
789857
graphql_client, conference, title={"en": "My first tutorial"}
790858
)
@@ -818,6 +886,11 @@ def test_notes_are_not_required(graphql_client, user):
818886
audience_levels=("Beginner",),
819887
)
820888

889+
EmailTemplateFactory(
890+
conference=conference,
891+
identifier=EmailTemplateIdentifier.proposal_received_confirmation,
892+
)
893+
821894
resp, _ = _submit_tutorial(graphql_client, conference, notes="")
822895

823896
assert resp["data"]["sendSubmission"]["__typename"] == "Submission"
@@ -850,6 +923,16 @@ def test_same_user_can_submit_talks_to_different_conferences(graphql_client, use
850923
audience_levels=("Beginner",),
851924
)
852925

926+
EmailTemplateFactory(
927+
conference=conference1,
928+
identifier=EmailTemplateIdentifier.proposal_received_confirmation,
929+
)
930+
931+
EmailTemplateFactory(
932+
conference=conference2,
933+
identifier=EmailTemplateIdentifier.proposal_received_confirmation,
934+
)
935+
853936
resp, _ = _submit_talk(graphql_client, conference1, title={"en": "My first talk"})
854937

855938
assert resp["data"]["sendSubmission"]["title"] == "My first talk"
@@ -889,6 +972,11 @@ def test_create_submission_tags(graphql_client, user):
889972
audience_levels=("Beginner",),
890973
)
891974

975+
EmailTemplateFactory(
976+
conference=conference,
977+
identifier=EmailTemplateIdentifier.proposal_received_confirmation,
978+
)
979+
892980
python, _ = SubmissionTag.objects.get_or_create(name="python")
893981
graphql, _ = SubmissionTag.objects.get_or_create(name="GraphQL")
894982

backend/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
@pytest.fixture()
2020
def user(db):
21-
return UserFactory(email="[email protected]", is_staff=False)
21+
return UserFactory(email="[email protected]", is_staff=False, full_name="Jane Doe")
2222

2323

2424
@pytest.fixture()
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 5.1.1 on 2024-12-01 14:41
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('notifications', '0018_alter_emailtemplate_identifier'),
10+
]
11+
12+
operations = [
13+
migrations.AlterField(
14+
model_name='emailtemplate',
15+
name='identifier',
16+
field=models.CharField(choices=[('proposal_accepted', 'Proposal accepted'), ('proposal_rejected', 'Proposal rejected'), ('proposal_in_waiting_list', 'Proposal in waiting list'), ('proposal_scheduled_time_changed', 'Proposal scheduled time changed'), ('proposal_received_confirmation', 'Proposal received confirmation'), ('speaker_communication', 'Speaker communication'), ('voucher_code', 'Voucher code'), ('reset_password', '[System] Reset password'), ('grant_application_confirmation', 'Grant application confirmation'), ('grant_approved', 'Grant approved'), ('grant_rejected', 'Grant rejected'), ('grant_waiting_list', 'Grant waiting list'), ('grant_waiting_list_update', 'Grant waiting list update'), ('grant_voucher_code', 'Grant voucher code'), ('sponsorship_brochure', 'Sponsorship brochure'), ('custom', 'Custom')], max_length=200, verbose_name='identifier'),
17+
),
18+
]

backend/notifications/models.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ class EmailTemplateIdentifier(models.TextChoices):
2323
"proposal_scheduled_time_changed",
2424
_("Proposal scheduled time changed"),
2525
)
26+
proposal_received_confirmation = (
27+
"proposal_received_confirmation",
28+
_("Proposal received confirmation"),
29+
)
2630
speaker_communication = "speaker_communication", _("Speaker communication")
2731

2832
voucher_code = "voucher_code", _("Voucher code")
@@ -88,6 +92,12 @@ class EmailTemplate(TimeStampedModel):
8892
*BASE_PLACEHOLDERS,
8993
"user_name",
9094
],
95+
EmailTemplateIdentifier.proposal_received_confirmation: [
96+
*BASE_PLACEHOLDERS,
97+
"user_name",
98+
"proposal_title",
99+
"proposal_url",
100+
],
91101
EmailTemplateIdentifier.grant_approved: [
92102
*BASE_PLACEHOLDERS,
93103
"reply_url",

0 commit comments

Comments
 (0)