diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 439dfd9f9..7fafb730c 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -1,11 +1,14 @@ +import logging from django.contrib import admin from django.contrib.auth.admin import UserAdmin from django.contrib.contenttypes.models import ContentType from django.http.response import HttpResponseRedirect from django.urls import reverse - +from .utility.email import send_templated_email, EmailSendingError from . import models +logger = logging.getLogger(__name__) + class AuditedAdmin(admin.ModelAdmin): @@ -50,13 +53,51 @@ class MyHostAdmin(AuditedAdmin): inlines = [HostIPInline] +class DomainApplicationAdmin(AuditedAdmin): + + """Customize the applications listing view.""" + + # Trigger action when a fieldset is changed + def save_model(self, request, obj, form, change): + if change: # Check if the application is being edited + # Get the original application from the database + original_obj = models.DomainApplication.objects.get(pk=obj.pk) + + if obj.status != original_obj.status and obj.status == "investigating": + if ( + original_obj.submitter is None + or original_obj.submitter.email is None + ): + logger.warning( + "Cannot send status change (in review) email," + "no submitter email address." + ) + return + try: + print( + f"original_obj.submitter.email {original_obj.submitter.email}" + ) + send_templated_email( + "emails/status_change_in_review.txt", + "emails/status_change_in_review_subject.txt", + original_obj.submitter.email, + context={"application": obj}, + ) + except EmailSendingError: + logger.warning( + "Failed to send status change (in review) email", exc_info=True + ) + + super().save_model(request, obj, form, change) + + admin.site.register(models.User, MyUserAdmin) admin.site.register(models.UserDomainRole, AuditedAdmin) admin.site.register(models.Contact, AuditedAdmin) admin.site.register(models.DomainInvitation, AuditedAdmin) -admin.site.register(models.DomainApplication, AuditedAdmin) admin.site.register(models.DomainInformation, AuditedAdmin) admin.site.register(models.Domain, AuditedAdmin) admin.site.register(models.Host, MyHostAdmin) admin.site.register(models.Nameserver, MyHostAdmin) admin.site.register(models.Website, AuditedAdmin) +admin.site.register(models.DomainApplication, DomainApplicationAdmin) diff --git a/src/registrar/templates/emails/status_change_in_review.txt b/src/registrar/templates/emails/status_change_in_review.txt new file mode 100644 index 000000000..9b7928502 --- /dev/null +++ b/src/registrar/templates/emails/status_change_in_review.txt @@ -0,0 +1,81 @@ +{% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #} +Hi {{ application.submitter.first_name }}. + +Your .gov domain request is being reviewed. + +DOMAIN REQUESTED: {{ application.requested_domain.name }} +REQUEST RECEIVED ON: {{ application.updated_at|date }} +REQUEST #: {{ application.id }} +STATUS: In review + + +NEED TO MAKE CHANGES? + +If you need to change your request you have to first withdraw it. Once you +withdraw the request you can edit it and submit it again. Changing your request +might add to the wait time. Learn more about withdrawing your request. +. + + +NEXT STEPS + +- We’re reviewing your request. This usually takes 20 business days. + +- You can check the status of your request at any time. + + +- We’ll email you with questions or when we complete our review. + + +THANK YOU + +.Gov helps the public identify official, trusted information. Thank you for +requesting a .gov domain. + +---------------------------------------------------------------- + +SUMMARY OF YOUR DOMAIN REQUEST + +Type of organization: +{{ application.get_organization_type_display }} + +Organization name and mailing address: +{% spaceless %}{{ application.organization_name }} +{{ application.address_line1 }}{% if application.address_line2 %} +{{ application.address_line2 }}{% endif %} +{{ application.city }}, {{ application.state_territory }} +{{ application.zipcode }}{% if application.urbanization %} +{{ application.urbanization }}{% endif %}{% endspaceless %} +{% if application.type_of_work %}{# if block makes one newline if it's false #} +Type of work: +{% spaceless %}{{ application.type_of_work }}{% endspaceless %} +{% endif %} +Authorizing official: +{% spaceless %}{% include "emails/includes/contact.txt" with contact=application.authorizing_official %}{% endspaceless %} +{% if application.current_websites.exists %}{# if block makes a newline #} +Current website for your organization: {% for site in application.current_websites.all %} +{% spaceless %}{{ site.website }}{% endspaceless %} +{% endfor %}{% endif %} +.gov domain: +{{ application.requested_domain.name }} +{% for site in application.alternative_domains.all %}{% spaceless %}{{ site.website }}{% endspaceless %} +{% endfor %} +Purpose of your domain: +{{ application.purpose }} + +Your contact information: +{% spaceless %}{% include "emails/includes/contact.txt" with contact=application.submitter %}{% endspaceless %} +{% if application.other_contacts.all %} +Other employees from your organization: +{% for other in application.other_contacts.all %} +{% spaceless %}{% include "emails/includes/contact.txt" with contact=other %}{% endspaceless %} +{% endfor %}{% endif %}{% if application.anything_else %} +Anything else we should know? +{{ application.anything_else }} +{% endif %} +---------------------------------------------------------------- + +The .gov team +Contact us: +Visit +{% endautoescape %} diff --git a/src/registrar/templates/emails/status_change_in_review_subject.txt b/src/registrar/templates/emails/status_change_in_review_subject.txt new file mode 100644 index 000000000..e4e43138b --- /dev/null +++ b/src/registrar/templates/emails/status_change_in_review_subject.txt @@ -0,0 +1 @@ +Your .gov domain request is being reviewed \ No newline at end of file diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py new file mode 100644 index 000000000..35684ab00 --- /dev/null +++ b/src/registrar/tests/test_admin.py @@ -0,0 +1,120 @@ +from django.test import TestCase, RequestFactory +from django.contrib.admin.sites import AdminSite +from registrar.admin import DomainApplicationAdmin +from django.contrib.auth import get_user_model +from registrar.models import Contact, DraftDomain, Website, DomainApplication, User + +from django.conf import settings +from unittest.mock import MagicMock, ANY +import boto3_mocking # type: ignore + + +class TestDomainApplicationAdmin(TestCase): + def setUp(self): + self.site = AdminSite() + self.factory = RequestFactory() + + def _completed_application( + self, + has_other_contacts=True, + has_current_website=True, + has_alternative_gov_domain=True, + has_type_of_work=True, + has_anything_else=True, + ): + """A completed domain application.""" + user = get_user_model().objects.create(username="username") + ao, _ = Contact.objects.get_or_create( + first_name="Testy", + last_name="Tester", + title="Chief Tester", + email="testy@town.com", + phone="(555) 555 5555", + ) + domain, _ = DraftDomain.objects.get_or_create(name="city.gov") + alt, _ = Website.objects.get_or_create(website="city1.gov") + current, _ = Website.objects.get_or_create(website="city.com") + you, _ = Contact.objects.get_or_create( + first_name="Testy you", + last_name="Tester you", + title="Admin Tester", + email="mayor@igorville.gov", + phone="(555) 555 5556", + ) + other, _ = Contact.objects.get_or_create( + first_name="Testy2", + last_name="Tester2", + title="Another Tester", + email="testy2@town.com", + phone="(555) 555 5557", + ) + domain_application_kwargs = dict( + organization_type="federal", + federal_type="executive", + purpose="Purpose of the site", + is_policy_acknowledged=True, + organization_name="Testorg", + address_line1="address 1", + address_line2="address 2", + state_territory="NY", + zipcode="10002", + authorizing_official=ao, + requested_domain=domain, + submitter=you, + creator=user, + ) + if has_type_of_work: + domain_application_kwargs["type_of_work"] = "e-Government" + if has_anything_else: + domain_application_kwargs["anything_else"] = "There is more" + + application, _ = DomainApplication.objects.get_or_create( + **domain_application_kwargs + ) + + if has_other_contacts: + application.other_contacts.add(other) + if has_current_website: + application.current_websites.add(current) + if has_alternative_gov_domain: + application.alternative_domains.add(alt) + + return application + + @boto3_mocking.patching + def test_save_model_sends_email_on_property_change(self): + # 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): + # Create a sample application + application = self._completed_application() + + # Create a mock request + request = self.factory.post( + "/admin/registrar/domainapplication/{}/change/".format(application.pk) + ) + + # Create an instance of the model admin + model_admin = DomainApplicationAdmin(DomainApplication, self.site) + + # Modify the application's property + application.status = "investigating" + + # Use the model admin's save_model method + model_admin.save_model(request, application, form=None, change=True) + + # Assert that the email was sent + + mock_client_instance.send_email.assert_called_once_with( + FromEmailAddress=settings.DEFAULT_FROM_EMAIL, + Destination={"ToAddresses": [EMAIL]}, + Content=ANY, + ) + + # Cleanup + application.delete()