Send invitation email when invite is created

This commit is contained in:
Neil Martinsen-Burrell 2023-03-27 15:20:08 -05:00
parent 1e77bd9bf6
commit 8338704315
No known key found for this signature in database
GPG key ID: 6A3C818CC10D0184
4 changed files with 64 additions and 7 deletions

View file

@ -469,7 +469,9 @@ class DomainApplication(TimeStampedModel):
nothing. nothing.
""" """
if self.submitter is None or self.submitter.email is None: if self.submitter is None or self.submitter.email is None:
logger.warning("Cannot send confirmation email, no submitter email address.") logger.warning(
"Cannot send confirmation email, no submitter email address."
)
return return
try: try:
send_templated_email( send_templated_email(

View file

@ -0,0 +1,6 @@
You have been invited to manage a domain on get.gov, the registrar for
.gov domain names.
To accept your invitation, go to <{{ domain_url }}>.
You will need to log in with a Login.gov account using this email address.

View file

@ -1,4 +1,5 @@
from unittest import skip from unittest import skip
from unittest.mock import MagicMock, ANY
from django.conf import settings from django.conf import settings
from django.test import Client, TestCase from django.test import Client, TestCase
@ -557,9 +558,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
# the post request should return a redirect to the contact # the post request should return a redirect to the contact
# question # question
self.assertEqual(election_result.status_code, 302) self.assertEqual(election_result.status_code, 302)
self.assertEqual( self.assertEqual(election_result["Location"], "/register/organization_contact/")
election_result["Location"], "/register/organization_contact/"
)
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
contact_page = election_result.follow() contact_page = election_result.follow()
self.assertNotContains(contact_page, "Federal agency") self.assertNotContains(contact_page, "Federal agency")
@ -1139,8 +1138,13 @@ class TestDomainDetail(TestWithDomainPermissions, WebTest):
success_page = success_result.follow() success_page = success_result.follow()
self.assertContains(success_page, "mayor@igorville.gov") self.assertContains(success_page, "mayor@igorville.gov")
@boto3_mocking.patching
def test_domain_invitation_created(self): def test_domain_invitation_created(self):
"""Add user on a nonexistent email creates an invitation.""" """Add user on a nonexistent email creates an invitation.
Adding a non-existent user sends an email as a side-effect, so mock
out the boto3 SES email sending here.
"""
# make sure there is no user with this email # make sure there is no user with this email
EMAIL = "mayor@igorville.gov" EMAIL = "mayor@igorville.gov"
User.objects.filter(email=EMAIL).delete() User.objects.filter(email=EMAIL).delete()
@ -1159,10 +1163,36 @@ class TestDomainDetail(TestWithDomainPermissions, WebTest):
self.assertContains(success_page, "Cancel") # link to cancel invitation self.assertContains(success_page, "Cancel") # link to cancel invitation
self.assertTrue(DomainInvitation.objects.filter(email=EMAIL).exists()) self.assertTrue(DomainInvitation.objects.filter(email=EMAIL).exists())
@boto3_mocking.patching
def test_domain_invitation_email_sent(self):
"""Inviting a non-existent user sends them an email."""
# make sure there is no user with this email
EMAIL = "mayor@igorville.gov"
User.objects.filter(email=EMAIL).delete()
mock_client = MagicMock()
mock_client_instance = mock_client.return_value
with boto3_mocking.clients.handler_for("sesv2", mock_client):
add_page = self.app.get(
reverse("domain-users-add", kwargs={"pk": self.domain.id})
)
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
add_page.form["email"] = EMAIL
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
add_page.form.submit()
# check the mock instance to see if `send_email` was called right
mock_client_instance.send_email.assert_called_once_with(
FromEmailAddress=settings.DEFAULT_FROM_EMAIL,
Destination={"ToAddresses": [EMAIL]},
Content=ANY,
)
def test_domain_invitation_cancel(self): def test_domain_invitation_cancel(self):
"""Posting to the delete view deletes an invitation.""" """Posting to the delete view deletes an invitation."""
EMAIL = "mayor@igorville.gov" EMAIL = "mayor@igorville.gov"
invitation, _ = DomainInvitation.objects.get_or_create(domain=self.domain, email=EMAIL) invitation, _ = DomainInvitation.objects.get_or_create(
domain=self.domain, email=EMAIL
)
self.client.post(reverse("invitation-delete", kwargs={"pk": invitation.id})) self.client.post(reverse("invitation-delete", kwargs={"pk": invitation.id}))
with self.assertRaises(DomainInvitation.DoesNotExist): with self.assertRaises(DomainInvitation.DoesNotExist):
DomainInvitation.objects.get(id=invitation.id) DomainInvitation.objects.get(id=invitation.id)

View file

@ -11,6 +11,7 @@ from django.views.generic.edit import DeleteView, FormMixin
from registrar.models import Domain, DomainInvitation, User, UserDomainRole from registrar.models import Domain, DomainInvitation, User, UserDomainRole
from ..forms import DomainAddUserForm from ..forms import DomainAddUserForm
from ..utility.email import send_templated_email, EmailSendingError
from .utility import DomainPermission from .utility import DomainPermission
@ -56,6 +57,12 @@ class DomainAddUserView(DomainPermission, FormMixin, DetailView):
else: else:
return self.form_invalid(form) return self.form_invalid(form)
def _domain_abs_url(self):
"""Get an absolute URL for this domain."""
return self.request.build_absolute_uri(
reverse("domain", kwargs={"pk": self.object.id})
)
def _make_invitation(self, email_address): def _make_invitation(self, email_address):
"""Make a Domain invitation for this email and redirect with a message.""" """Make a Domain invitation for this email and redirect with a message."""
invitation, created = DomainInvitation.objects.get_or_create( invitation, created = DomainInvitation.objects.get_or_create(
@ -68,7 +75,19 @@ class DomainAddUserView(DomainPermission, FormMixin, DetailView):
f"{email_address} has already been invited to this domain.", f"{email_address} has already been invited to this domain.",
) )
else: else:
messages.success(self.request, f"Invited {email_address} to this domain.") # created a new invitation in the database, so send an email
try:
send_templated_email(
"emails/domain_invitation.txt",
to_address=email_address,
context={"domain_url": self._domain_abs_url()},
)
except EmailSendingError:
messages.warning(self.request, "Could not send email invitation.")
else:
messages.success(
self.request, f"Invited {email_address} to this domain."
)
return redirect(self.get_success_url()) return redirect(self.get_success_url())
def form_valid(self, form): def form_valid(self, form):