diff --git a/backend/api/submissions/mutations.py b/backend/api/submissions/mutations.py index 0e610b59ac..10768b6f37 100644 --- a/backend/api/submissions/mutations.py +++ b/backend/api/submissions/mutations.py @@ -1,3 +1,7 @@ +from urllib.parse import urljoin +from django.conf import settings +from grants.tasks import get_name +from notifications.models import EmailTemplate, EmailTemplateIdentifier from strawberry.scalars import JSON from django.db import transaction @@ -383,6 +387,24 @@ def send_submission( "cfp", ) + email_template = EmailTemplate.objects.for_conference( + conference + ).get_by_identifier(EmailTemplateIdentifier.proposal_received_confirmation) + + proposal_url = urljoin( + settings.FRONTEND_URL, + f"/submission/{instance.hashid}", + ) + + email_template.send_email( + recipient=request.user, + placeholders={ + "user_name": get_name(request.user, "there"), + "proposal_title": instance.title.localize("en"), + "proposal_url": proposal_url, + }, + ) + def _notify_new_submission(): notify_new_cfp_submission.delay( submission_id=instance.id, diff --git a/backend/api/submissions/tests/test_send_submission.py b/backend/api/submissions/tests/test_send_submission.py index 0a319aa8b9..ea853a6400 100644 --- a/backend/api/submissions/tests/test_send_submission.py +++ b/backend/api/submissions/tests/test_send_submission.py @@ -1,3 +1,5 @@ +from notifications.models import EmailTemplateIdentifier +from notifications.tests.factories import EmailTemplateFactory from privacy_policy.models import PrivacyPolicyAcceptanceRecord from files_upload.tests.factories import FileFactory from conferences.tests.factories import ( @@ -138,7 +140,11 @@ def _submit_proposal(client, conference, submission, **kwargs): ) -def test_submit_talk(graphql_client, user, django_capture_on_commit_callbacks, mocker): +def test_submit_talk( + graphql_client, user, django_capture_on_commit_callbacks, mocker, settings +): + settings.FRONTEND_URL = "http://testserver" + mock_email_template = mocker.patch("api.submissions.mutations.EmailTemplate") mock_notify = mocker.patch("api.submissions.mutations.notify_new_cfp_submission") graphql_client.force_login(user) @@ -151,6 +157,11 @@ def test_submit_talk(graphql_client, user, django_capture_on_commit_callbacks, m audience_levels=("Beginner",), ) + EmailTemplateFactory( + conference=conference, + identifier=EmailTemplateIdentifier.proposal_received_confirmation, + ) + speaker_photo = FileFactory().id 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 mock_notify.delay.assert_called_once() + mock_email_template.objects.for_conference().get_by_identifier().send_email.assert_called_once_with( + recipient=user, + placeholders={ + "user_name": user.full_name, + "proposal_title": "English", + "proposal_url": f"http://testserver/submission/{talk.hashid}", + }, + ) + def test_submit_talk_with_photo_to_upload(graphql_client, user, mocker): graphql_client.force_login(user) @@ -220,7 +240,10 @@ def test_submit_talk_with_photo_to_upload(graphql_client, user, mocker): durations=("50",), audience_levels=("Beginner",), ) - + EmailTemplateFactory( + conference=conference, + identifier=EmailTemplateIdentifier.proposal_received_confirmation, + ) speaker_photo = FileFactory().id resp, variables = _submit_talk( @@ -253,6 +276,11 @@ def test_submit_talk_without_photo_fails(graphql_client, user, mocker): audience_levels=("Beginner",), ) + EmailTemplateFactory( + conference=conference, + identifier=EmailTemplateIdentifier.proposal_received_confirmation, + ) + resp, variables = _submit_talk( graphql_client, conference, @@ -282,6 +310,11 @@ def test_submit_talk_with_existing_participant(graphql_client, user): audience_levels=("Beginner",), ) + EmailTemplateFactory( + conference=conference, + identifier=EmailTemplateIdentifier.proposal_received_confirmation, + ) + participant = Participant.objects.create( conference=conference, user_id=user.id, bio="old bio" ) @@ -339,6 +372,11 @@ def test_submit_talk_with_missing_data_of_other_language_fails(graphql_client, u audience_levels=("Beginner",), ) + EmailTemplateFactory( + conference=conference, + identifier=EmailTemplateIdentifier.proposal_received_confirmation, + ) + resp, _ = _submit_talk( graphql_client, conference, @@ -370,6 +408,11 @@ def test_submit_talk_with_missing_data_fails(graphql_client, user): audience_levels=("Beginner",), ) + EmailTemplateFactory( + conference=conference, + identifier=EmailTemplateIdentifier.proposal_received_confirmation, + ) + resp, _ = _submit_talk( graphql_client, conference, @@ -413,6 +456,11 @@ def test_submit_talk_with_multiple_languages(graphql_client, user): audience_levels=("Beginner",), ) + EmailTemplateFactory( + conference=conference, + identifier=EmailTemplateIdentifier.proposal_received_confirmation, + ) + resp, variables = _submit_talk( graphql_client, conference, @@ -707,6 +755,11 @@ def test_cannot_propose_a_talk_if_a_cfp_is_not_specified(graphql_client, user): audience_levels=("Beginner",), ) + EmailTemplateFactory( + conference=conference, + identifier=EmailTemplateIdentifier.proposal_received_confirmation, + ) + resp, _ = _submit_talk(graphql_client, conference) assert resp["data"]["sendSubmission"]["__typename"] == "SendSubmissionErrors" @@ -729,6 +782,11 @@ def test_same_user_can_propose_multiple_talks_to_the_same_conference( audience_levels=("Beginner",), ) + EmailTemplateFactory( + conference=conference, + identifier=EmailTemplateIdentifier.proposal_received_confirmation, + ) + resp, _ = _submit_talk(graphql_client, conference, title={"en": "My first talk"}) assert resp["data"]["sendSubmission"]["title"] == "My first talk" @@ -760,6 +818,11 @@ def test_submit_tutorial(graphql_client, user): audience_levels=("Beginner",), ) + EmailTemplateFactory( + conference=conference, + identifier=EmailTemplateIdentifier.proposal_received_confirmation, + ) + resp, _ = _submit_tutorial( graphql_client, conference, title={"en": "My first tutorial"} ) @@ -785,6 +848,11 @@ def test_submit_tutorial_and_talk_to_the_same_conference(graphql_client, user): audience_levels=("Beginner",), ) + EmailTemplateFactory( + conference=conference, + identifier=EmailTemplateIdentifier.proposal_received_confirmation, + ) + resp, _ = _submit_tutorial( graphql_client, conference, title={"en": "My first tutorial"} ) @@ -818,6 +886,11 @@ def test_notes_are_not_required(graphql_client, user): audience_levels=("Beginner",), ) + EmailTemplateFactory( + conference=conference, + identifier=EmailTemplateIdentifier.proposal_received_confirmation, + ) + resp, _ = _submit_tutorial(graphql_client, conference, notes="") assert resp["data"]["sendSubmission"]["__typename"] == "Submission" @@ -850,6 +923,16 @@ def test_same_user_can_submit_talks_to_different_conferences(graphql_client, use audience_levels=("Beginner",), ) + EmailTemplateFactory( + conference=conference1, + identifier=EmailTemplateIdentifier.proposal_received_confirmation, + ) + + EmailTemplateFactory( + conference=conference2, + identifier=EmailTemplateIdentifier.proposal_received_confirmation, + ) + resp, _ = _submit_talk(graphql_client, conference1, title={"en": "My first talk"}) assert resp["data"]["sendSubmission"]["title"] == "My first talk" @@ -889,6 +972,11 @@ def test_create_submission_tags(graphql_client, user): audience_levels=("Beginner",), ) + EmailTemplateFactory( + conference=conference, + identifier=EmailTemplateIdentifier.proposal_received_confirmation, + ) + python, _ = SubmissionTag.objects.get_or_create(name="python") graphql, _ = SubmissionTag.objects.get_or_create(name="GraphQL") diff --git a/backend/conftest.py b/backend/conftest.py index 683275e402..6c21c945de 100644 --- a/backend/conftest.py +++ b/backend/conftest.py @@ -18,7 +18,7 @@ @pytest.fixture() def user(db): - return UserFactory(email="simulated@user.it", is_staff=False) + return UserFactory(email="simulated@user.it", is_staff=False, full_name="Jane Doe") @pytest.fixture() diff --git a/backend/notifications/migrations/0019_alter_emailtemplate_identifier.py b/backend/notifications/migrations/0019_alter_emailtemplate_identifier.py new file mode 100644 index 0000000000..7f4106d583 --- /dev/null +++ b/backend/notifications/migrations/0019_alter_emailtemplate_identifier.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.1 on 2024-12-01 14:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('notifications', '0018_alter_emailtemplate_identifier'), + ] + + operations = [ + migrations.AlterField( + model_name='emailtemplate', + name='identifier', + 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'), + ), + ] diff --git a/backend/notifications/models.py b/backend/notifications/models.py index 72ff82b134..954438c85c 100644 --- a/backend/notifications/models.py +++ b/backend/notifications/models.py @@ -23,6 +23,10 @@ class EmailTemplateIdentifier(models.TextChoices): "proposal_scheduled_time_changed", _("Proposal scheduled time changed"), ) + proposal_received_confirmation = ( + "proposal_received_confirmation", + _("Proposal received confirmation"), + ) speaker_communication = "speaker_communication", _("Speaker communication") voucher_code = "voucher_code", _("Voucher code") @@ -88,6 +92,12 @@ class EmailTemplate(TimeStampedModel): *BASE_PLACEHOLDERS, "user_name", ], + EmailTemplateIdentifier.proposal_received_confirmation: [ + *BASE_PLACEHOLDERS, + "user_name", + "proposal_title", + "proposal_url", + ], EmailTemplateIdentifier.grant_approved: [ *BASE_PLACEHOLDERS, "reply_url",