Skip to content

Commit

Permalink
Custom project invitation template (#2506)
Browse files Browse the repository at this point in the history
* add support for custom project invitation email, subject

* add username to project invitation email data

* add type hints

* add test case

* use html for project invitation email message

* enhance project invitation email template

* handle project invitation email invited_by null
  • Loading branch information
kelvin-muchiri authored Nov 17, 2023
1 parent 9b356a1 commit 7fc8444
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 34 deletions.
11 changes: 0 additions & 11 deletions onadata/libs/templates/projects/invitation.txt

This file was deleted.

8 changes: 8 additions & 0 deletions onadata/libs/templates/projects/invitation_message.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{% load i18n %}{% blocktranslate %}
<p>Hello,</p>
<p>You have been added to {{project_name}} by a project admin allowing you to begin data collection.</p>
<p>To begin using {{deployment_name}}, please create an account first by clicking the link below:</p>
<p><a href='{{invitation_url}}'>{{invitation_url}}</a></p>
<p>Thanks,</p>
<p>The Team at {{ deployment_name }}</p>
{% endblocktranslate %}
112 changes: 99 additions & 13 deletions onadata/libs/tests/utils/test_email.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ def test_email_data_does_not_contain_newline_chars(self):
self.assertNotIn("\n", email_data.get("subject"))


@override_settings(DEFAULT_FROM_EMAIL="[email protected]")
class ProjectInvitationEmailTestCase(TestBase):
"""Tests for class ProjectInvitationEmail"""

Expand All @@ -166,32 +167,48 @@ def setUp(self) -> None:
self.user.profile.save()
self.project.name = "Test Invitation"
self.project.save()
self.user.email = "[email protected]"
self.user.save()
self.invitation = ProjectInvitation.objects.create(
email="[email protected]",
project=self.project,
role="editor",
status=ProjectInvitation.Status.PENDING,
invited_by=self.user,
)
self.email = ProjectInvitationEmail(
self.invitation, "https://example.com/register"
)

@override_settings(DEPLOYMENT_NAME="Misfit")
@patch("onadata.libs.utils.email.send_generic_email")
@patch("onadata.libs.utils.email.send_mail")
def test_send(self, mock_send):
"""Email is sent successfully"""
self.email.send()
email_data = {
"subject": "Invitation to Join a Project on Misfit",
"message_txt": "\nHello,\n\nYou have been added to Test Invitation by"
" a project admin allowing you to begin data collection.\n\nTo begin"
" using Misfit, please create an account first by clicking the link below:"
"\nhttps://example.com/register"
"\n\nThanks,\nThe Team at Misfit\n",
}
expected_subject = "Invitation to Join a Project on Misfit"
expected_message = (
"\nHello,\n"
"You have been added to Test Invitation by a project admin allowing"
" you to begin data collection.\n"
"To begin using Misfit, please create an account first by"
" clicking the link below:\n"
"https://example.com/register\n"
"Thanks,\nThe Team at Misfit\n"
)
expected_html_message = (
"\n<p>Hello,</p>\n<p>You have been added to Test Invitation by a project"
" admin allowing you to begin data collection.</p>\n"
"<p>To begin using Misfit, please create an account first by"
" clicking the link below:</p>\n"
"<p><a href='https://example.com/register'>https://example.com/register</a></p>\n"
"<p>Thanks,</p>\n<p>The Team at Misfit</p>\n"
)
mock_send.assert_called_with(
self.invitation.email,
**email_data,
expected_subject,
expected_message,
"[email protected]",
(self.invitation.email,),
html_message=expected_html_message,
)

@override_settings(DEPLOYMENT_NAME="Misfit")
Expand All @@ -204,11 +221,80 @@ def test_get_template_data(self):
"project_name": "Test Invitation",
"invitation_url": "https://example.com/register",
"organization": "Test User",
"invited_by": "[email protected]",
"username": "[email protected]",
},
}
data = self.email.get_template_data()
self.assertEqual(data, expected_data)

# invitation invited_by is null
self.invitation.invited_by = None
self.invitation.save()
expected_data = {
"subject": {"deployment_name": "Misfit"},
"body": {
"deployment_name": "Misfit",
"project_name": "Test Invitation",
"invitation_url": "https://example.com/register",
"organization": "Test User",
"invited_by": None,
"username": "[email protected]",
},
}
data = self.email.get_template_data()
self.assertEqual(data, expected_data)

@override_settings(
DEPLOYMENT_NAME="Misfit",
PROJECT_INVITATION_SUBJECT="Invitation to join {deployment_name}",
PROJECT_INVITATION_MESSAGE=(
"<p>Hello {username}</p>"
"<p>You have been added to {project_name} in the"
" {organization} account on Misfit.</p>"
"<p>To begin using Misfit, please verify your account"
" first by clicking the link below:</p>"
"<p>{invitation_url}</p>"
"<p>Then, enter you first and last name, desired username,"
" and password and click Join. Once complete, please notify"
" your project admin {invited_by} and we'll activate your account.</p>"
),
)
@patch("onadata.libs.utils.email.send_mail")
def test_send_custom_message(self, mock_send):
"""Custom subject and message works"""
self.email.send()
expected_subject = "Invitation to join Misfit"
expected_message = (
"Hello [email protected]"
"You have been added to Test Invitation in the"
" Test User account on Misfit."
"To begin using Misfit, please verify your account"
" first by clicking the link below:"
"https://example.com/register"
"Then, enter you first and last name, desired username,"
" and password and click Join. Once complete, please notify"
" your project admin [email protected] and we'll activate your account."
)
expected_html_message = (
"<p>Hello [email protected]</p>"
"<p>You have been added to Test Invitation in the"
" Test User account on Misfit.</p>"
"<p>To begin using Misfit, please verify your account"
" first by clicking the link below:</p>"
"<p>https://example.com/register</p>"
"<p>Then, enter you first and last name, desired username,"
" and password and click Join. Once complete, please notify"
" your project admin [email protected] and we'll activate your account.</p>"
)
mock_send.assert_called_with(
expected_subject,
expected_message,
"[email protected]",
(self.invitation.email,),
html_message=expected_html_message,
)


class ProjectInvitationURLTestCase(TestBase):
"""Tests for get_project_invitation_url"""
Expand All @@ -231,8 +317,8 @@ def test_url_configured(self):
}
)
@override_settings(ALLOWED_HOSTS=["*"])
def test_url_configured(self):
"""settings.PROJECT_INVITATION_URL is set"""
def test_url_configured_for_host(self):
"""settings.PROJECT_INVITATION_URL is set for specific host"""
self.custom_request.META["HTTP_HOST"] = "new-domain.com"
url = get_project_invitation_url(self.custom_request)
self.assertEqual(url, "https://new-domain.com/register")
Expand Down
53 changes: 43 additions & 10 deletions onadata/libs/utils/email.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
email utility functions.
"""
from django.conf import settings
from django.core.mail import EmailMultiAlternatives
from django.core.mail import EmailMultiAlternatives, send_mail
from django.http import HttpRequest
from django.utils.html import strip_tags
from django.template.loader import render_to_string
from six.moves.urllib.parse import urlencode
from rest_framework.reverse import reverse
Expand Down Expand Up @@ -123,32 +124,64 @@ def get_template_data(self) -> dict[str, str]:
"""Get context data for the templates"""
deployment_name = getattr(settings, "DEPLOYMENT_NAME", "Ona")
organization = self.invitation.project.organization.profile.name
invited_by = None

if self.invitation.invited_by:
invited_by = self.invitation.invited_by.email

data = {
"subject": {"deployment_name": deployment_name},
"body": {
"deployment_name": deployment_name,
"project_name": self.invitation.project.name,
"invitation_url": self.url,
"organization": organization,
"invited_by": invited_by,
"username": self.invitation.email,
},
}

return data

def get_email_data(self) -> dict[str, str]:
"""Get the email data to be sent"""
message_path = "projects/invitation.txt"
subject_path = "projects/invitation_subject.txt"
template_data = self.get_template_data()
email_data = {
"subject": render_to_string(subject_path, template_data["subject"]),
"message_txt": render_to_string(
custom_subject: str | None = getattr(
settings, "PROJECT_INVITATION_SUBJECT", None
)
custom_message: str | None = getattr(
settings, "PROJECT_INVITATION_MESSAGE", None
)

if custom_subject:
subject = custom_subject.format(**template_data["subject"])

else:
subject_path = "projects/invitation_subject.txt"
subject = render_to_string(subject_path, template_data["subject"])

if custom_message:
message = custom_message.format(**template_data["body"])

else:
message_path = "projects/invitation_message.html"
message = render_to_string(
message_path,
template_data["body"],
),
}
return email_data
)

return (
subject,
message,
)

def send(self) -> None:
"""Send project invitation email"""
send_generic_email(self.invitation.email, **self.get_email_data())
subject, message = self.get_email_data()
send_mail(
subject,
strip_tags(message),
settings.DEFAULT_FROM_EMAIL,
(self.invitation.email,),
html_message=message,
)

0 comments on commit 7fc8444

Please sign in to comment.