Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jt/notifications config UI and models #35310

Draft
wants to merge 4 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 44 additions & 14 deletions corehq/messaging/scheduling/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
SMSSurveyContent,
TimedEvent,
TimedSchedule,
ProjectNotificationContent,
)
from corehq.messaging.scheduling.scheduling_partitioned.models import (
CaseScheduleInstanceMixin,
Expand Down Expand Up @@ -268,7 +269,8 @@ def clean_subject(self):
cleaned_value = self._clean_message_field('subject')
return self._validate_fcm_message_length(cleaned_value, self.FCM_SUBJECT_MAX_LENGTH)

if self.schedule_form.cleaned_data.get('content') != ScheduleForm.CONTENT_EMAIL:
if self.schedule_form.cleaned_data.get('content') not in (ScheduleForm.CONTENT_EMAIL,
ScheduleForm.CONTENT_PROJECT_NOTIFICATION):
return None

return self._clean_message_field('subject')
Expand All @@ -285,7 +287,8 @@ def clean_message(self):
return self._validate_fcm_message_length(cleaned_value, self.FCM_MESSAGE_MAX_LENGTH)

if self.schedule_form.cleaned_data.get('content') not in (ScheduleForm.CONTENT_SMS,
ScheduleForm.CONTENT_EMAIL):
ScheduleForm.CONTENT_EMAIL,
ScheduleForm.CONTENT_PROJECT_NOTIFICATION):
return None

return self._clean_message_field('message')
Expand Down Expand Up @@ -430,6 +433,14 @@ def distill_content(self):
subject=self.cleaned_data['subject'],
message=self.cleaned_data['message'],
)
elif self.schedule_form.cleaned_data['content'] == ScheduleForm.CONTENT_PROJECT_NOTIFICATION:
if RICH_TEXT_EMAILS.enabled(self.domain):
return self._distill_rich_text_project_notification()
else:
return ProjectNotificationContent(
subject=self.cleaned_data['subject'],
message=self.cleaned_data['message'],
)
elif self.schedule_form.cleaned_data['content'] == ScheduleForm.CONTENT_SMS_SURVEY:
combined_id = self.cleaned_data['app_and_form_unique_id']
app_id, form_unique_id = split_combined_id(combined_id)
Expand Down Expand Up @@ -457,11 +468,12 @@ def distill_content(self):
else:
raise ValueError("Unexpected value for content: '%s'" % self.schedule_form.cleaned_data['content'])

def _distill_rich_text_email(self):
@classmethod
def _sanitize_rich_text_message(cls, html_content):
plaintext_message = {}
html_message = {}
css_sanitizer = CSSSanitizer(allowed_css_properties=ALLOWED_CSS_PROPERTIES)
for lang, content in self.cleaned_data['html_message'].items():
for lang, content in html_content.items():
# remove everything except the body for plaintext
soup = BeautifulSoup(content, features='lxml')
try:
Expand All @@ -475,12 +487,24 @@ def _distill_rich_text_email(self):
css_sanitizer=css_sanitizer,
strip=True,
)
return plaintext_message, html_message

def _distill_rich_text_email(self):
plaintext_message, html_message = self._sanitize_rich_text_message(self.cleaned_data['html_message'])
return EmailContent(
subject=self.cleaned_data['subject'],
message=plaintext_message,
html_message=html_message,
)

def _distill_rich_text_project_notification(self):
plaintext_message, html_message = self._sanitize_rich_text_message(self.cleaned_data['html_message'])
return ProjectNotificationContent(
subject=self.cleaned_data['subject'],
message=plaintext_message,
html_message=html_message,
)

def get_layout_fields(self):
if RICH_TEXT_EMAILS.enabled(self.domain):
message_fields = [
Expand All @@ -494,10 +518,10 @@ def get_layout_fields(self):
crispy.Div(template='scheduling/partials/rich_text_message_configuration.html'),
data_bind='with: html_message',
),
data_bind="visible: $root.content() === '%s' || ($root.content() === '%s' "
"&& fcm_message_type() === '%s')" %
(ScheduleForm.CONTENT_EMAIL, ScheduleForm.CONTENT_FCM_NOTIFICATION,
FCMNotificationContent.MESSAGE_TYPE_NOTIFICATION)
data_bind="visible: $root.content() === '%s' || $root.content() === '%s' "
"|| ($root.content() === '%s' && fcm_message_type() === '%s')" %
(ScheduleForm.CONTENT_EMAIL, ScheduleForm.CONTENT_PROJECT_NOTIFICATION,
ScheduleForm.CONTENT_FCM_NOTIFICATION, FCMNotificationContent.MESSAGE_TYPE_NOTIFICATION)
),
hqcrispy.B3MultiField(
_("Message"),
Expand Down Expand Up @@ -531,9 +555,10 @@ def get_layout_fields(self):
),
data_bind=(
"visible: $root.content() === '%s' || $root.content() === '%s' "
"|| $root.content() === '%s' "
"|| $root.content() === '%s' || $root.content() === '%s'"
"|| ($root.content() === '%s' && fcm_message_type() === '%s')" %
(ScheduleForm.CONTENT_SMS, ScheduleForm.CONTENT_EMAIL, ScheduleForm.CONTENT_SMS_CALLBACK,
(ScheduleForm.CONTENT_SMS, ScheduleForm.CONTENT_EMAIL,
ScheduleForm.CONTENT_PROJECT_NOTIFICATION, ScheduleForm.CONTENT_SMS_CALLBACK,
ScheduleForm.CONTENT_FCM_NOTIFICATION, FCMNotificationContent.MESSAGE_TYPE_NOTIFICATION)
),
),
Expand Down Expand Up @@ -563,10 +588,10 @@ def get_layout_fields(self):
crispy.Div(template='scheduling/partials/message_configuration.html'),
data_bind='with: subject',
),
data_bind="visible: $root.content() === '%s' || ($root.content() === '%s' "
"&& fcm_message_type() === '%s')" %
(ScheduleForm.CONTENT_EMAIL, ScheduleForm.CONTENT_FCM_NOTIFICATION,
FCMNotificationContent.MESSAGE_TYPE_NOTIFICATION)
data_bind="visible: $root.content() === '%s' || $root.content() === '%s' "
"|| ($root.content() === '%s' && fcm_message_type() === '%s')" %
(ScheduleForm.CONTENT_EMAIL, ScheduleForm.CONTENT_PROJECT_NOTIFICATION,
ScheduleForm.CONTENT_FCM_NOTIFICATION, FCMNotificationContent.MESSAGE_TYPE_NOTIFICATION)
),
*message_fields,
crispy.Div(
Expand Down Expand Up @@ -1154,6 +1179,7 @@ class ScheduleForm(Form):
CONTENT_SMS_CALLBACK = 'sms_callback'
CONTENT_CUSTOM_SMS = 'custom_sms'
CONTENT_FCM_NOTIFICATION = 'fcm_notification'
CONTENT_PROJECT_NOTIFICATION = 'project_notification'

YES = 'Y'
NO = 'N'
Expand Down Expand Up @@ -1740,6 +1766,10 @@ def add_additional_content_types(self):
(self.CONTENT_SMS_SURVEY, _("SMS Survey")),
]

self.fields['content'].choices += [
(self.CONTENT_PROJECT_NOTIFICATION, _("In-Product Notification")),
]

if self.initial_schedule:
if self.initial_schedule.memoized_uses_ivr_survey:
self.fields['content'].choices += [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Generated by Django 4.2.16 on 2024-10-30 23:29

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('scheduling', '0027_emailcontent_html_message'),
]

operations = [
migrations.CreateModel(
name='ProjectNotificationContent',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('subject', models.JSONField(default=dict)),
('message', models.JSONField(default=dict)),
('html_message', models.JSONField(default=dict, null=True)),
],
options={
'abstract': False,
},
),
migrations.AddField(
model_name='alertevent',
name='project_notification_content',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='scheduling.projectnotificationcontent'),
),
migrations.AddField(
model_name='casepropertytimedevent',
name='project_notification_content',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='scheduling.projectnotificationcontent'),
),
migrations.AddField(
model_name='randomtimedevent',
name='project_notification_content',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='scheduling.projectnotificationcontent'),
),
migrations.AddField(
model_name='timedevent',
name='project_notification_content',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='scheduling.projectnotificationcontent'),
),
]
8 changes: 7 additions & 1 deletion corehq/messaging/scheduling/models/abstract.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ def delete(self, *args, **kwargs):
class ContentForeignKeyMixin(models.Model):
sms_content = models.ForeignKey('scheduling.SMSContent', null=True, on_delete=models.CASCADE)
email_content = models.ForeignKey('scheduling.EmailContent', null=True, on_delete=models.CASCADE)
project_notification_content = models.ForeignKey('scheduling.ProjectNotificationContent', null=True,
on_delete=models.CASCADE)
sms_survey_content = models.ForeignKey('scheduling.SMSSurveyContent', null=True, on_delete=models.CASCADE)
ivr_survey_content = models.ForeignKey('scheduling.IVRSurveyContent', null=True, on_delete=models.CASCADE)
custom_content = models.ForeignKey('scheduling.CustomContent', null=True, on_delete=models.CASCADE)
Expand All @@ -252,6 +254,8 @@ def content(self):
return self.sms_content
elif self.email_content_id:
return self.email_content
elif self.project_notification_content_id:
return self.project_notification_content
elif self.sms_survey_content_id:
return self.sms_survey_content
elif self.ivr_survey_content_id:
Expand All @@ -276,7 +280,7 @@ def memoized_content(self):

@content.setter
def content(self, value):
from corehq.messaging.scheduling.models import (SMSContent, EmailContent,
from corehq.messaging.scheduling.models import (SMSContent, EmailContent, ProjectNotificationContent,
SMSSurveyContent, IVRSurveyContent, CustomContent, SMSCallbackContent, FCMNotificationContent)

self.sms_content = None
Expand All @@ -290,6 +294,8 @@ def content(self, value):
self.sms_content = value
elif isinstance(value, EmailContent):
self.email_content = value
elif isinstance(value, ProjectNotificationContent):
self.project_notification_content = value
elif isinstance(value, SMSSurveyContent):
self.sms_survey_content = value
elif isinstance(value, IVRSurveyContent):
Expand Down
16 changes: 16 additions & 0 deletions corehq/messaging/scheduling/models/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,22 @@ def get_recipient_email(self, recipient):
return email_address


class ProjectNotificationContent(Content):
subject = models.JSONField(default=dict)
message = models.JSONField(default=dict)
html_message = models.JSONField(default=dict, null=True)

def create_copy(self):
"""
See Content.create_copy() for docstring
"""
return EmailContent(
subject=deepcopy(self.subject),
message=deepcopy(self.message),
html_message=deepcopy(self.html_message),
)


class SMSSurveyContent(Content):
app_id = models.CharField(max_length=126, null=True)
form_unique_id = models.CharField(max_length=126)
Expand Down
1 change: 1 addition & 0 deletions migrations.lock
Original file line number Diff line number Diff line change
Expand Up @@ -890,6 +890,7 @@ scheduling
0025_schedule_and_broadcast_deleted_on
0026_add_model_fcm_notification_content
0027_emailcontent_html_message
0028_projectnotificationcontent_and_more
scheduling_partitioned
0001_initial
0002_case_schedule_instances
Expand Down
Loading