Skip to content

Commit b652530

Browse files
authored
Ability to notify submissions status change (#4353)
1 parent 073383e commit b652530

File tree

3 files changed

+135
-2
lines changed

3 files changed

+135
-2
lines changed

backend/notifications/models.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,8 @@ class EmailTemplate(TimeStampedModel):
6363
*BASE_PLACEHOLDERS,
6464
"conference_name",
6565
"proposal_title",
66-
"invitation_url",
66+
"proposal_type",
6767
"speaker_name",
68-
"is_reminder",
6968
],
7069
EmailTemplateIdentifier.proposal_scheduled: [
7170
*BASE_PLACEHOLDERS,

backend/submissions/admin.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
from django.urls import reverse
2+
from grants.tasks import get_name
3+
from notifications.models import EmailTemplate, EmailTemplateIdentifier
24
from custom_admin.admin import (
35
confirm_pending_status,
46
reset_pending_status_back_to_status,
@@ -8,6 +10,7 @@
810

911
from django import forms
1012
from django.db.models import F
13+
from django.db import transaction
1114
from django.contrib import admin, messages
1215
from django.utils.html import mark_safe
1316
from django.utils.translation import gettext_lazy as _
@@ -322,6 +325,51 @@ class SubmissionCommentAdmin(admin.ModelAdmin):
322325
list_display = ("submission", "author", "text")
323326

324327

328+
NEW_STATUS_TO_EMAIL_TEMPLATE = {
329+
Submission.STATUS.accepted: EmailTemplateIdentifier.proposal_accepted,
330+
Submission.STATUS.rejected: EmailTemplateIdentifier.proposal_rejected,
331+
Submission.STATUS.waiting_list: EmailTemplateIdentifier.proposal_in_waiting_list,
332+
}
333+
334+
335+
@admin.action(description="Apply and notify status change")
336+
@validate_single_conference_selection
337+
@transaction.atomic
338+
def apply_and_notify_status_change(modeladmin, request, queryset):
339+
conference = queryset.first().conference
340+
objs = list(queryset.select_related("speaker").prefetch_related("type"))
341+
count = len(objs)
342+
343+
for submission in objs:
344+
submission.status = submission.pending_status
345+
placeholders = {
346+
"conference_name": conference.name.localize("en"),
347+
"proposal_title": submission.title.localize("en"),
348+
"proposal_type": submission.type.name,
349+
"speaker_name": get_name(submission.speaker, "there"),
350+
}
351+
352+
template = NEW_STATUS_TO_EMAIL_TEMPLATE[submission.status]
353+
email_template = EmailTemplate.objects.for_conference(
354+
conference
355+
).get_by_identifier(template)
356+
email_template.send_email(
357+
recipient=submission.speaker,
358+
placeholders=placeholders,
359+
)
360+
361+
Submission.objects.bulk_update(
362+
objs,
363+
["status"],
364+
)
365+
366+
messages.add_message(
367+
request,
368+
messages.SUCCESS,
369+
f"Confirmed and notified {count} proposals",
370+
)
371+
372+
325373
@admin.register(SubmissionConfirmPendingStatusProxy)
326374
class SubmissionConfirmPendingStatusProxyAdmin(admin.ModelAdmin):
327375
list_display = (
@@ -338,6 +386,7 @@ class SubmissionConfirmPendingStatusProxyAdmin(admin.ModelAdmin):
338386
search_fields = ("speaker__full_name", "speaker__email", "title")
339387
list_display_links = None
340388
actions = [
389+
apply_and_notify_status_change,
341390
confirm_pending_status,
342391
reset_pending_status_back_to_status,
343392
]

backend/submissions/tests/test_admin.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
from unittest.mock import call
2+
from conferences.tests.factories import ConferenceFactory
3+
from notifications.tests.factories import EmailTemplateFactory
4+
from notifications.models import EmailTemplateIdentifier, SentEmail
25
import pytest
36
from submissions.admin import (
7+
apply_and_notify_status_change,
48
send_proposal_in_waiting_list_email_action,
59
send_proposal_rejected_email_action,
610
)
@@ -59,3 +63,84 @@ def test_send_proposal_in_waiting_list_email_action(rf, mocker):
5963
],
6064
any_order=True,
6165
)
66+
67+
68+
def test_apply_and_notify_status_change(rf, mocker):
69+
mocker.patch("submissions.admin.messages")
70+
71+
conference = ConferenceFactory()
72+
proposal_accepted_template = EmailTemplateFactory(
73+
identifier=EmailTemplateIdentifier.proposal_accepted,
74+
conference=conference,
75+
)
76+
proposal_rejected_template = EmailTemplateFactory(
77+
identifier=EmailTemplateIdentifier.proposal_rejected,
78+
conference=conference,
79+
)
80+
proposal_in_waiting_list_template = EmailTemplateFactory(
81+
identifier=EmailTemplateIdentifier.proposal_in_waiting_list,
82+
conference=conference,
83+
)
84+
85+
accepted_submission = SubmissionFactory(
86+
conference=conference,
87+
status=Submission.STATUS.proposed,
88+
pending_status=Submission.STATUS.accepted,
89+
speaker__full_name="Marco",
90+
)
91+
92+
rejected_submission = SubmissionFactory(
93+
conference=conference,
94+
status=Submission.STATUS.proposed,
95+
pending_status=Submission.STATUS.rejected,
96+
speaker__full_name="Jane",
97+
)
98+
99+
waiting_list_proposal = SubmissionFactory(
100+
conference=conference,
101+
status=Submission.STATUS.proposed,
102+
pending_status=Submission.STATUS.waiting_list,
103+
speaker__full_name="John",
104+
)
105+
106+
apply_and_notify_status_change(
107+
None,
108+
rf.post("/"),
109+
queryset=Submission.objects.filter(status=Submission.STATUS.proposed),
110+
)
111+
112+
assert SentEmail.objects.filter(
113+
recipient=accepted_submission.speaker,
114+
email_template=proposal_accepted_template,
115+
conference=conference,
116+
placeholders={
117+
"conference_name": conference.name.localize("en"),
118+
"proposal_title": accepted_submission.title.localize("en"),
119+
"proposal_type": accepted_submission.type.name,
120+
"speaker_name": "Marco",
121+
},
122+
).exists()
123+
124+
assert SentEmail.objects.filter(
125+
recipient=rejected_submission.speaker,
126+
email_template=proposal_rejected_template,
127+
conference=conference,
128+
placeholders={
129+
"conference_name": conference.name.localize("en"),
130+
"proposal_title": rejected_submission.title.localize("en"),
131+
"proposal_type": rejected_submission.type.name,
132+
"speaker_name": "Jane",
133+
},
134+
).exists()
135+
136+
assert SentEmail.objects.filter(
137+
recipient=waiting_list_proposal.speaker,
138+
email_template=proposal_in_waiting_list_template,
139+
conference=conference,
140+
placeholders={
141+
"conference_name": conference.name.localize("en"),
142+
"proposal_title": waiting_list_proposal.title.localize("en"),
143+
"proposal_type": waiting_list_proposal.type.name,
144+
"speaker_name": "John",
145+
},
146+
).exists()

0 commit comments

Comments
 (0)