diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 9584e3942..4410565f7 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -5,6 +5,7 @@ from django import forms from django.db.models import Value, CharField, Q from django.db.models.functions import Concat, Coalesce from django.http import HttpResponseRedirect +from registrar.utility.admin_helpers import get_action_needed_reason_default_email, get_rejection_reason_default_email from django.conf import settings from django.shortcuts import redirect from django_fsm import get_available_FIELD_transitions, FSMField @@ -1939,6 +1940,15 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): # Get the original domain request from the database. original_obj = models.DomainRequest.objects.get(pk=obj.pk) + # == Handle action needed and rejected emails == # + # Edge case: this logic is handled by javascript, so contexts outside that must be handled + if obj.status == DomainRequest.DomainRequestStatus.ACTION_NEEDED: + if obj.action_needed_reason and not obj.action_needed_reason_email: + obj.action_needed_reason_email = get_action_needed_reason_default_email(obj, obj.action_needed_reason) + elif obj.status == DomainRequest.DomainRequestStatus.REJECTED: + if obj.rejection_reason and not obj.rejection_reason_email: + obj.rejection_reason_email = get_rejection_reason_default_email(obj, obj.rejection_reason) + # == Handle allowed emails == # if obj.status in DomainRequest.get_statuses_that_send_emails() and not settings.IS_PRODUCTION: self._check_for_valid_email(request, obj) diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py index 617143ac7..4877b3756 100644 --- a/src/registrar/models/domain_request.py +++ b/src/registrar/models/domain_request.py @@ -688,7 +688,8 @@ class DomainRequest(TimeStampedModel): "cached_reason": self._cached_rejection_reason, "reason": self.rejection_reason, "email": self.rejection_reason_email, - "excluded_reasons": [DomainRequest.RejectionReasons.OTHER], + "excluded_reasons": [], + # "excluded_reasons": [DomainRequest.RejectionReasons.OTHER], }, } status_info = status_information.get(status) @@ -707,7 +708,7 @@ class DomainRequest(TimeStampedModel): if status_info.get("cached_reason") != status_info.get("reason") or status_info.get("cached_reason") is None: bcc_address = settings.DEFAULT_FROM_EMAIL if settings.IS_PRODUCTION else "" self._send_status_update_email( - new_status=status.label, + new_status=status, email_template=f"emails/includes/custom_email.txt", email_template_subject=f"emails/status_change_subject.txt", bcc_address=bcc_address, diff --git a/src/registrar/templates/emails/status_change_rejected.txt b/src/registrar/templates/emails/status_change_rejected.txt index 62e8d6acb..b1d989bf1 100644 --- a/src/registrar/templates/emails/status_change_rejected.txt +++ b/src/registrar/templates/emails/status_change_rejected.txt @@ -46,7 +46,7 @@ Learn more about eligibility for .gov domains . If you have questions or comments, reply to this email. -{% elif reason == domain_request.RejectionReasons.DOMAIN_PURPOSE.NAMING_NOT_MET %} +{% elif reason == domain_request.RejectionReasons.DOMAIN_PURPOSE.NAMING_REQUIREMENTS %} Your domain request was rejected because it does not meet our naming requirements. Domains should uniquely identify a government organization and be clear to the general public. Learn more about naming requirements for your type of organization diff --git a/src/registrar/tests/test_admin_request.py b/src/registrar/tests/test_admin_request.py index 5104f23fb..55aacd25d 100644 --- a/src/registrar/tests/test_admin_request.py +++ b/src/registrar/tests/test_admin_request.py @@ -599,7 +599,6 @@ class TestDomainRequestAdmin(MockEppLib): domain_request, status, rejection_reason=None, - rejection_reason_email=None, action_needed_reason=None, action_needed_reason_email=None, ): @@ -619,9 +618,6 @@ class TestDomainRequestAdmin(MockEppLib): if rejection_reason: domain_request.rejection_reason = rejection_reason - if rejection_reason_email: - domain_request.rejection_reason_email = rejection_reason_email - if action_needed_reason: domain_request.action_needed_reason = action_needed_reason @@ -697,6 +693,10 @@ class TestDomainRequestAdmin(MockEppLib): self.assert_email_is_accurate("ORGANIZATION ALREADY HAS A .GOV DOMAIN", 0, EMAIL, bcc_email_address=BCC_EMAIL) self.assertEqual(len(self.mock_client.EMAILS_SENT), 1) + # We use javascript to reset the content of this. It is only automatically set + # if the email itself is somehow None. + self._reset_action_needed_email(domain_request) + # Test the email sent out for bad_name bad_name = DomainRequest.ActionNeededReasons.BAD_NAME self.transition_state_and_send_email(domain_request, action_needed, action_needed_reason=bad_name) @@ -704,6 +704,7 @@ class TestDomainRequestAdmin(MockEppLib): "DOMAIN NAME DOES NOT MEET .GOV REQUIREMENTS", 1, EMAIL, bcc_email_address=BCC_EMAIL ) self.assertEqual(len(self.mock_client.EMAILS_SENT), 2) + self._reset_action_needed_email(domain_request) # Test the email sent out for eligibility_unclear eligibility_unclear = DomainRequest.ActionNeededReasons.ELIGIBILITY_UNCLEAR @@ -712,6 +713,7 @@ class TestDomainRequestAdmin(MockEppLib): "ORGANIZATION MAY NOT MEET ELIGIBILITY REQUIREMENTS", 2, EMAIL, bcc_email_address=BCC_EMAIL ) self.assertEqual(len(self.mock_client.EMAILS_SENT), 3) + self._reset_action_needed_email(domain_request) # Test that a custom email is sent out for questionable_so questionable_so = DomainRequest.ActionNeededReasons.QUESTIONABLE_SENIOR_OFFICIAL @@ -720,6 +722,7 @@ class TestDomainRequestAdmin(MockEppLib): "SENIOR OFFICIAL DOES NOT MEET ELIGIBILITY REQUIREMENTS", 3, _creator.email, bcc_email_address=BCC_EMAIL ) self.assertEqual(len(self.mock_client.EMAILS_SENT), 4) + self._reset_action_needed_email(domain_request) # Assert that no other emails are sent on OTHER other = DomainRequest.ActionNeededReasons.OTHER @@ -727,6 +730,7 @@ class TestDomainRequestAdmin(MockEppLib): # Should be unchanged from before self.assertEqual(len(self.mock_client.EMAILS_SENT), 4) + self._reset_action_needed_email(domain_request) # Tests if an analyst can override existing email content questionable_so = DomainRequest.ActionNeededReasons.QUESTIONABLE_SENIOR_OFFICIAL @@ -740,6 +744,7 @@ class TestDomainRequestAdmin(MockEppLib): domain_request.refresh_from_db() self.assert_email_is_accurate("custom email content", 4, _creator.email, bcc_email_address=BCC_EMAIL) self.assertEqual(len(self.mock_client.EMAILS_SENT), 5) + self._reset_action_needed_email(domain_request) # Tests if a new email gets sent when just the email is changed. # An email should NOT be sent out if we just modify the email content. @@ -751,6 +756,7 @@ class TestDomainRequestAdmin(MockEppLib): ) self.assertEqual(len(self.mock_client.EMAILS_SENT), 5) + self._reset_action_needed_email(domain_request) # Set the request back to in review domain_request.in_review() @@ -767,6 +773,12 @@ class TestDomainRequestAdmin(MockEppLib): ) self.assertEqual(len(self.mock_client.EMAILS_SENT), 6) + def _reset_action_needed_email(self, domain_request): + """Sets the given action needed email back to none""" + domain_request.action_needed_reason_email = None + domain_request.save() + domain_request.refresh_from_db() + @override_settings(IS_PRODUCTION=True) @less_console_noise_decorator def test_rejected_sends_reason_email_prod_bcc(self): @@ -794,58 +806,20 @@ class TestDomainRequestAdmin(MockEppLib): expected_emails = { DomainRequest.RejectionReasons.DOMAIN_PURPOSE: "You didn’t provide enough information about how", DomainRequest.RejectionReasons.REQUESTOR_NOT_ELIGIBLE: "You must be a government employee, or be", - DomainRequest.RejectionReasons.ORG_HAS_DOMAIN: "Our practice is to approve one domain", + DomainRequest.RejectionReasons.ORG_HAS_DOMAIN: "practice is to approve one domain", DomainRequest.RejectionReasons.CONTACTS_NOT_VERIFIED: "we could not verify the organizational", DomainRequest.RejectionReasons.ORG_NOT_ELIGIBLE: ".Gov domains are only available to official U.S.-based", DomainRequest.RejectionReasons.NAMING_REQUIREMENTS: "does not meet our naming requirements", - # TODO - add back other? - # DomainRequest.RejectionReasons.OTHER: "", + DomainRequest.RejectionReasons.OTHER: "YOU CAN SUBMIT A NEW REQUEST", } for i, (reason, email_content) in enumerate(expected_emails.items()): with self.subTest(reason=reason): self.transition_state_and_send_email(domain_request, status=rejected, rejection_reason=reason) self.assert_email_is_accurate(email_content, i, EMAIL, bcc_email_address=BCC_EMAIL) self.assertEqual(len(self.mock_client.EMAILS_SENT), i + 1) - - # Tests if an analyst can override existing email content - domain_purpose = DomainRequest.RejectionReasons.DOMAIN_PURPOSE - self.transition_state_and_send_email( - domain_request, - status=rejected, - rejection_reason=domain_purpose, - rejection_reason_email="custom email content", - ) - - logger.info(f"look: {len(self.mock_client.EMAILS_SENT)}") - domain_request.refresh_from_db() - self.assert_email_is_accurate("custom email content", 6, _creator.email, bcc_email_address=BCC_EMAIL) - self.assertEqual(len(self.mock_client.EMAILS_SENT), 7) - - # Tests if a new email gets sent when just the email is changed. - # An email should NOT be sent out if we just modify the email content. - self.transition_state_and_send_email( - domain_request, - status=rejected, - action_needed_reason=domain_purpose, - action_needed_reason_email="dummy email content", - ) - - self.assertEqual(len(self.mock_client.EMAILS_SENT), 7) - - # Set the request back to in review - domain_request.in_review() - - # Try sending another email when changing states AND including content - self.transition_state_and_send_email( - domain_request, - status=rejected, - rejection_reason=domain_purpose, - rejection_reason_email="custom content when starting anew", - ) - self.assert_email_is_accurate( - "custom content when starting anew", 7, _creator.email, bcc_email_address=BCC_EMAIL - ) - self.assertEqual(len(self.mock_client.EMAILS_SENT), 8) + domain_request.rejection_reason_email = None + domain_request.save() + domain_request.refresh_from_db() @less_console_noise_decorator def test_save_model_sends_submitted_email(self): diff --git a/src/registrar/tests/test_api.py b/src/registrar/tests/test_api.py index ef5385d72..d60099570 100644 --- a/src/registrar/tests/test_api.py +++ b/src/registrar/tests/test_api.py @@ -177,3 +177,71 @@ class GetActionNeededEmailForUserJsonTest(TestCase): }, ) self.assertEqual(response.status_code, 302) + + +class GetRejectionEmailForUserJsonTest(TestCase): + def setUp(self): + self.client = Client() + self.superuser = create_superuser() + self.analyst_user = create_user() + self.agency = FederalAgency.objects.create(agency="Test Agency") + self.domain_request = completed_domain_request( + federal_agency=self.agency, + name="test.gov", + status=DomainRequest.DomainRequestStatus.ACTION_NEEDED, + ) + + self.api_url = reverse("get-action-needed-email-for-user-json") + + def tearDown(self): + DomainRequest.objects.all().delete() + User.objects.all().delete() + FederalAgency.objects.all().delete() + + @less_console_noise_decorator + def test_get_action_needed_email_for_user_json_superuser(self): + """Test that a superuser can fetch the action needed email.""" + self.client.force_login(self.superuser) + + response = self.client.get( + self.api_url, + { + "reason": DomainRequest.ActionNeededReasons.ELIGIBILITY_UNCLEAR, + "domain_request_id": self.domain_request.id, + }, + ) + self.assertEqual(response.status_code, 200) + data = response.json() + self.assertIn("action_needed_email", data) + self.assertIn("ORGANIZATION MAY NOT MEET ELIGIBILITY REQUIREMENTS", data["action_needed_email"]) + + @less_console_noise_decorator + def test_get_action_needed_email_for_user_json_analyst(self): + """Test that an analyst can fetch the action needed email.""" + self.client.force_login(self.analyst_user) + + response = self.client.get( + self.api_url, + { + "reason": DomainRequest.ActionNeededReasons.QUESTIONABLE_SENIOR_OFFICIAL, + "domain_request_id": self.domain_request.id, + }, + ) + self.assertEqual(response.status_code, 200) + data = response.json() + self.assertIn("action_needed_email", data) + self.assertIn("SENIOR OFFICIAL DOES NOT MEET ELIGIBILITY REQUIREMENTS", data["action_needed_email"]) + + @less_console_noise_decorator + def test_get_action_needed_email_for_user_json_regular(self): + """Test that a regular user receives a 403 with an error message.""" + p = "password" + self.client.login(username="testuser", password=p) + response = self.client.get( + self.api_url, + { + "reason": DomainRequest.ActionNeededReasons.QUESTIONABLE_SENIOR_OFFICIAL, + "domain_request_id": self.domain_request.id, + }, + ) + self.assertEqual(response.status_code, 302)