From 4ce9efffcdfc1783bab23f90a16ef39248d10f45 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Thu, 30 Jan 2025 15:06:00 -0500 Subject: [PATCH] updates to complete initial implementation of invitation emails to admins --- src/registrar/admin.py | 15 ++++++++-- src/registrar/tests/test_email_invitations.py | 10 +++---- src/registrar/utility/email_invitations.py | 28 +++++++++++++------ src/registrar/views/domain.py | 4 ++- src/registrar/views/portfolios.py | 12 ++++++-- 5 files changed, 50 insertions(+), 19 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 7dbe7abb0..a9944c4c0 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -1543,7 +1543,9 @@ class DomainInvitationAdmin(BaseInvitationAdmin): and not member_of_this_org and not member_of_a_different_org ): - send_portfolio_invitation_email(email=requested_email, requestor=requestor, portfolio=domain_org) + send_portfolio_invitation_email( + email=requested_email, requestor=requestor, portfolio=domain_org, is_admin_invitation=False + ) portfolio_invitation, _ = PortfolioInvitation.objects.get_or_create( email=requested_email, portfolio=domain_org, @@ -1638,6 +1640,7 @@ class PortfolioInvitationAdmin(BaseInvitationAdmin): portfolio = obj.portfolio requested_email = obj.email requestor = request.user + is_admin_invitation = UserPortfolioRoleChoices.ORGANIZATION_ADMIN in obj.roles # Look up a user with that email requested_user = get_requested_user(requested_email) @@ -1647,7 +1650,15 @@ class PortfolioInvitationAdmin(BaseInvitationAdmin): try: if not permission_exists: # if permission does not exist for a user with requested_email, send email - send_portfolio_invitation_email(email=requested_email, requestor=requestor, portfolio=portfolio) + if not send_portfolio_invitation_email( + email=requested_email, + requestor=requestor, + portfolio=portfolio, + is_admin_invitation=is_admin_invitation, + ): + messages.warning( + self.request, "Could not send email notification to existing organization admins." + ) # if user exists for email, immediately retrieve portfolio invitation upon creation if requested_user is not None: obj.retrieve() diff --git a/src/registrar/tests/test_email_invitations.py b/src/registrar/tests/test_email_invitations.py index 1377dec42..1914e73bd 100644 --- a/src/registrar/tests/test_email_invitations.py +++ b/src/registrar/tests/test_email_invitations.py @@ -16,7 +16,7 @@ class DomainInvitationEmail(unittest.TestCase): @patch("registrar.utility.email_invitations.send_templated_email") @patch("registrar.utility.email_invitations.UserDomainRole.objects.filter") @patch("registrar.utility.email_invitations._validate_invitation") - @patch("registrar.utility.email_invitations.get_requestor_email") + @patch("registrar.utility.email_invitations._get_requestor_email") @patch("registrar.utility.email_invitations.send_invitation_email") @patch("registrar.utility.email_invitations.normalize_domains") def test_send_domain_invitation_email( @@ -81,7 +81,7 @@ class DomainInvitationEmail(unittest.TestCase): @patch("registrar.utility.email_invitations.send_templated_email") @patch("registrar.utility.email_invitations.UserDomainRole.objects.filter") @patch("registrar.utility.email_invitations._validate_invitation") - @patch("registrar.utility.email_invitations.get_requestor_email") + @patch("registrar.utility.email_invitations._get_requestor_email") @patch("registrar.utility.email_invitations.send_invitation_email") @patch("registrar.utility.email_invitations.normalize_domains") def test_send_domain_invitation_email_multiple_domains( @@ -197,7 +197,7 @@ class DomainInvitationEmail(unittest.TestCase): mock_validate_invitation.assert_called_once() @less_console_noise_decorator - @patch("registrar.utility.email_invitations.get_requestor_email") + @patch("registrar.utility.email_invitations._get_requestor_email") def test_send_domain_invitation_email_raises_get_requestor_email_exception(self, mock_get_requestor_email): """Test sending domain invitation email for one domain and assert exception when get_requestor_email fails. @@ -217,7 +217,7 @@ class DomainInvitationEmail(unittest.TestCase): @less_console_noise_decorator @patch("registrar.utility.email_invitations._validate_invitation") - @patch("registrar.utility.email_invitations.get_requestor_email") + @patch("registrar.utility.email_invitations._get_requestor_email") @patch("registrar.utility.email_invitations.send_invitation_email") @patch("registrar.utility.email_invitations.normalize_domains") def test_send_domain_invitation_email_raises_sending_email_exception( @@ -267,7 +267,7 @@ class DomainInvitationEmail(unittest.TestCase): @less_console_noise_decorator @patch("registrar.utility.email_invitations.send_emails_to_domain_managers") @patch("registrar.utility.email_invitations._validate_invitation") - @patch("registrar.utility.email_invitations.get_requestor_email") + @patch("registrar.utility.email_invitations._get_requestor_email") @patch("registrar.utility.email_invitations.send_invitation_email") @patch("registrar.utility.email_invitations.normalize_domains") def test_send_domain_invitation_email_manager_emails_send_mail_exception( diff --git a/src/registrar/utility/email_invitations.py b/src/registrar/utility/email_invitations.py index 4e315cdda..209c8b392 100644 --- a/src/registrar/utility/email_invitations.py +++ b/src/registrar/utility/email_invitations.py @@ -1,6 +1,7 @@ from datetime import date from django.conf import settings from registrar.models import Domain, DomainInvitation, UserDomainRole +from registrar.models.portfolio import Portfolio from registrar.models.user_portfolio_permission import UserPortfolioPermission from registrar.models.utility.portfolio_helper import UserPortfolioRoleChoices from registrar.utility.errors import ( @@ -40,7 +41,7 @@ def send_domain_invitation_email( EmailSendingError: If there is an error while sending the email. """ domains = normalize_domains(domains) - requestor_email = get_requestor_email(requestor, domains=domains) + requestor_email = _get_requestor_email(requestor, domains=domains) _validate_invitation(email, requested_user, domains, requestor, is_member_of_different_org) @@ -99,7 +100,7 @@ def normalize_domains(domains: Domain | list[Domain]) -> list[Domain]: return [domains] if isinstance(domains, Domain) else domains -def get_requestor_email(requestor, domains=None, portfolio=None): +def _get_requestor_email(requestor, domains=None, portfolio=None): """Get the requestor's email or raise an error if it's missing. If the requestor is staff, default email is returned. @@ -196,7 +197,7 @@ def send_portfolio_invitation_email(email: str, requestor, portfolio, is_admin_i EmailSendingError: If there is an error while sending the email. """ - requestor_email = get_requestor_email(requestor, portfolio=portfolio) + requestor_email = _get_requestor_email(requestor, portfolio=portfolio) try: send_templated_email( @@ -217,18 +218,29 @@ def send_portfolio_invitation_email(email: str, requestor, portfolio, is_admin_i all_admin_emails_sent = True # send emails to portfolio admins if is_admin_invitation: - all_admin_emails_sent = send_portfolio_admin_addition_emails_to_portfolio_admins( + all_admin_emails_sent = _send_portfolio_admin_addition_emails_to_portfolio_admins( email=email, requestor_email=requestor_email, portfolio=portfolio, - requested_user=None, ) return all_admin_emails_sent -def send_portfolio_admin_addition_emails_to_portfolio_admins( - email: str, requestor_email, portfolio: Domain, requested_user=None -): +def send_portfolio_admin_addition_emails(email: str, requestor, portfolio: Portfolio): + """ + Notifies all portfolio admins of the provided portfolio of a newly invited portfolio admin + + Returns: + Boolean indicating if all messages were sent successfully. + + Raises: + MissingEmailError + """ + requestor_email = _get_requestor_email(requestor, portfolio=portfolio) + return _send_portfolio_admin_addition_emails_to_portfolio_admins(email, requestor_email, portfolio) + + +def _send_portfolio_admin_addition_emails_to_portfolio_admins(email: str, requestor_email, portfolio: Portfolio): """ Notifies all portfolio admins of the provided portfolio of a newly invited portfolio admin diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index a1d2b8081..464e0d2a1 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -1206,7 +1206,9 @@ class DomainAddUserView(DomainFormBaseView): and requestor_can_update_portfolio and not member_of_this_org ): - send_portfolio_invitation_email(email=requested_email, requestor=requestor, portfolio=domain_org) + send_portfolio_invitation_email( + email=requested_email, requestor=requestor, portfolio=domain_org, is_admin_invitation=False + ) portfolio_invitation, _ = PortfolioInvitation.objects.get_or_create( email=requested_email, portfolio=domain_org, roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER] ) diff --git a/src/registrar/views/portfolios.py b/src/registrar/views/portfolios.py index 28933ec65..b8003b622 100644 --- a/src/registrar/views/portfolios.py +++ b/src/registrar/views/portfolios.py @@ -15,7 +15,11 @@ from registrar.models.user_domain_role import UserDomainRole from registrar.models.user_portfolio_permission import UserPortfolioPermission from registrar.models.utility.portfolio_helper import UserPortfolioPermissionChoices, UserPortfolioRoleChoices from registrar.utility.email import EmailSendingError -from registrar.utility.email_invitations import send_domain_invitation_email, send_portfolio_admin_addition_emails, send_portfolio_invitation_email +from registrar.utility.email_invitations import ( + send_domain_invitation_email, + send_portfolio_admin_addition_emails, + send_portfolio_invitation_email, +) from registrar.utility.errors import MissingEmailError from registrar.utility.enums import DefaultUserValues from registrar.views.utility.mixins import PortfolioMemberPermission @@ -418,9 +422,11 @@ class PortfolioInvitedMemberEditView(PortfolioMemberEditPermissionView, View): if not send_portfolio_admin_addition_emails( email=portfolio_invitation.email, requestor=request.user, - portfolio=portfolio_invitation.portfolio + portfolio=portfolio_invitation.portfolio, ): - messages.warning(self.request, "Could not send email notification to existing organization admins.") + messages.warning( + self.request, "Could not send email notification to existing organization admins." + ) elif form.is_change_from_admin_to_member(): # NOTE: need to add portfolio_admin_removal_emails when ready pass