diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 15f1ccb79..b524f9d0a 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -21,7 +21,12 @@ from epplibwrapper.errors import ErrorCode, RegistryError from registrar.models.user_domain_role import UserDomainRole from waffle.admin import FlagAdmin from waffle.models import Sample, Switch -from registrar.utility.admin_helpers import get_all_action_needed_reason_emails, get_action_needed_reason_default_email +from registrar.utility.admin_helpers import ( + get_all_action_needed_reason_emails, + get_action_needed_reason_default_email, + get_all_rejection_reason_emails, + get_rejection_reason_default_email, +) from registrar.models import Contact, Domain, DomainRequest, DraftDomain, User, Website, SeniorOfficial from registrar.utility.constants import BranchChoices from registrar.utility.errors import FSMDomainRequestError, FSMErrorCodes @@ -234,6 +239,7 @@ class DomainRequestAdminForm(forms.ModelForm): } labels = { "action_needed_reason_email": "Email", + "rejection_reason_email": "Email", } def __init__(self, *args, **kwargs): @@ -1755,6 +1761,7 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): "status_history", "status", "rejection_reason", + "rejection_reason_email", "action_needed_reason", "action_needed_reason_email", "investigator", @@ -1938,24 +1945,16 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): original_obj = models.DomainRequest.objects.get(pk=obj.pk) # == Handle action_needed_reason == # + action_needed_reason_changed = obj.action_needed_reason != original_obj.action_needed_reason + if action_needed_reason_changed: + obj = self._handle_action_needed_reason(request, obj, original_obj) - reason_changed = obj.action_needed_reason != original_obj.action_needed_reason - if reason_changed: - # Track the fact that we sent out an email - request.session["action_needed_email_sent"] = True - - # Set the action_needed_reason_email to the default if nothing exists. - # Since this check occurs after save, if the user enters a value then we won't update. - - default_email = get_action_needed_reason_default_email(request, obj, obj.action_needed_reason) - if obj.action_needed_reason_email: - emails = get_all_action_needed_reason_emails(request, obj) - is_custom_email = obj.action_needed_reason_email not in emails.values() - if not is_custom_email: - obj.action_needed_reason_email = default_email - else: - obj.action_needed_reason_email = default_email + # == Handle rejection_reason == # + rejection_reason_changed = obj.rejection_reason != original_obj.rejection_reason + if rejection_reason_changed: + obj = self._handle_rejection_reason(request, obj, original_obj) + # == 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) @@ -1971,6 +1970,40 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): if should_save: return super().save_model(request, obj, form, change) + def _handle_action_needed_reason(self, request, obj, original_obj): + # Track the fact that we sent out an email + request.session["action_needed_email_sent"] = True + + # Set the action_needed_reason_email to the default if nothing exists. + # Since this check occurs after save, if the user enters a value then we won't update. + + default_email = get_action_needed_reason_default_email(obj, obj.action_needed_reason) + if obj.action_needed_reason_email: + emails = get_all_action_needed_reason_emails(obj) + is_custom_email = obj.action_needed_reason_email not in emails.values() + if not is_custom_email: + obj.action_needed_reason_email = default_email + else: + obj.action_needed_reason_email = default_email + return obj + + def _handle_rejection_reason(self, request, obj, original_obj): + # Track the fact that we sent out an email + request.session["rejection_reason_email_sent"] = True + + # Set the rejection_reason_email to the default if nothing exists. + # Since this check occurs after save, if the user enters a value then we won't update. + + default_email = get_rejection_reason_default_email(obj, obj.action_needed_reason) + if obj.rejection_reason_email: + emails = get_all_rejection_reason_emails(obj) + is_custom_email = obj.rejection_reason_email not in emails.values() + if not is_custom_email: + obj.rejection_reason_email = default_email + else: + obj.rejection_reason_email = default_email + return obj + def _check_for_valid_email(self, request, obj): """Certain emails are whitelisted in non-production environments, so we should display that information using this function. diff --git a/src/registrar/assets/js/get-gov-admin.js b/src/registrar/assets/js/get-gov-admin.js index 7e3c086c4..7a7b63ba4 100644 --- a/src/registrar/assets/js/get-gov-admin.js +++ b/src/registrar/assets/js/get-gov-admin.js @@ -500,83 +500,41 @@ function initializeWidgetOnList(list, parentId) { } })(); +class CustomizableEmailBase { + constructor(dropdown, textarea, textareaPlaceholder, directEditButton, modalTrigger, modalConfirm, formLabel, lastSentEmailContent, apiUrl) { + this.dropdown = dropdown; + this.textarea = textarea; + this.textareaPlaceholder = textareaPlaceholder; + this.directEditButton = directEditButton; + this.modalTrigger = modalTrigger; + this.modalConfirm = modalConfirm; + this.formLabel = formLabel; + this.lastSentEmailContent = lastSentEmailContent; + this.apiUrl = apiUrl; -/** An IIFE that hooks to the show/hide button underneath action needed reason. - * This shows the auto generated email on action needed reason. -*/ -document.addEventListener('DOMContentLoaded', function() { - const dropdown = document.getElementById("id_action_needed_reason"); - const textarea = document.getElementById("id_action_needed_reason_email") - const domainRequestId = dropdown ? document.getElementById("domain_request_id").value : null - const textareaPlaceholder = document.querySelector(".field-action_needed_reason_email__placeholder"); - const directEditButton = document.querySelector('.field-action_needed_reason_email__edit'); - const modalTrigger = document.querySelector('.field-action_needed_reason_email__modal-trigger'); - const modalConfirm = document.getElementById('confirm-edit-email'); - const formLabel = document.querySelector('label[for="id_action_needed_reason_email"]'); - let lastSentEmailContent = document.getElementById("last-sent-email-content"); - const initialDropdownValue = dropdown ? dropdown.value : null; - const initialEmailValue = textarea.value; + this.domainRequestId = this.dropdown ? document.getElementById("domain_request_id").value : null + this.initialDropdownValue = this.dropdown ? this.dropdown.value : null; + this.initialEmailValue = this.textarea ? this.textarea.value : null; - // We will use the const to control the modal - let isEmailAlreadySentConst = lastSentEmailContent.value.replace(/\s+/g, '') === textarea.value.replace(/\s+/g, ''); - // We will use the function to control the label and help - function isEmailAlreadySent() { - return lastSentEmailContent.value.replace(/\s+/g, '') === textarea.value.replace(/\s+/g, ''); - } - - if (!dropdown || !textarea || !domainRequestId || !formLabel || !modalConfirm) return; - const apiUrl = document.getElementById("get-action-needed-email-for-user-json").value; - - function updateUserInterface(reason) { - if (!reason) { - // No reason selected, we will set the label to "Email", show the "Make a selection" placeholder, hide the trigger, textarea, hide the help text - formLabel.innerHTML = "Email:"; - textareaPlaceholder.innerHTML = "Select an action needed reason to see email"; - showElement(textareaPlaceholder); - hideElement(directEditButton); - hideElement(modalTrigger); - hideElement(textarea); - } else if (reason === 'other') { - // 'Other' selected, we will set the label to "Email", show the "No email will be sent" placeholder, hide the trigger, textarea, hide the help text - formLabel.innerHTML = "Email:"; - textareaPlaceholder.innerHTML = "No email will be sent"; - showElement(textareaPlaceholder); - hideElement(directEditButton); - hideElement(modalTrigger); - hideElement(textarea); - } else { - // A triggering selection is selected, all hands on board: - textarea.setAttribute('readonly', true); - showElement(textarea); - hideElement(textareaPlaceholder); - - if (isEmailAlreadySentConst) { - hideElement(directEditButton); - showElement(modalTrigger); - } else { - showElement(directEditButton); - hideElement(modalTrigger); - } - if (isEmailAlreadySent()) { - formLabel.innerHTML = "Email sent to creator:"; - } else { - formLabel.innerHTML = "Email:"; - } + this.isEmailAlreadySentConst; + if (lastSentEmailContent && textarea) { + this.isEmailAlreadySentConst = lastSentEmailContent.value.replace(/\s+/g, '') === textarea.value.replace(/\s+/g, ''); } } - // Initialize UI - updateUserInterface(dropdown.value); - - dropdown.addEventListener("change", function() { - const reason = dropdown.value; - // Update the UI - updateUserInterface(reason); - if (reason && reason !== "other") { - // If it's not the initial value - if (initialDropdownValue !== dropdown.value || initialEmailValue !== textarea.value) { + initializeDropdown(errorMessage) { + this.dropdown.addEventListener("change", () => { + console.log(this.dropdown) + let reason = this.dropdown.value; + if (this.initialDropdownValue !== this.dropdown.value || this.initialEmailValue !== this.textarea.value) { + let searchParams = new URLSearchParams( + { + "reason": reason, + "domain_request_id": this.domainRequestId, + } + ); // Replace the email content - fetch(`${apiUrl}?reason=${reason}&domain_request_id=${domainRequestId}`) + fetch(`${this.apiUrl}?${searchParams.toString()}`) .then(response => { return response.json().then(data => data); }) @@ -584,30 +542,174 @@ document.addEventListener('DOMContentLoaded', function() { if (data.error) { console.error("Error in AJAX call: " + data.error); }else { - textarea.value = data.action_needed_email; + this.textarea.value = data.action_needed_email; } - updateUserInterface(reason); + this.updateUserInterface(reason); }) .catch(error => { - console.error("Error action needed email: ", error) + console.error(errorMessage, error) }); } + }); + } + + initializeModalConfirm() { + this.modalConfirm.addEventListener("click", () => { + this.textarea.removeAttribute('readonly'); + this.textarea.focus(); + hideElement(this.directEditButton); + hideElement(this.modalTrigger); + }); + } + + initializeDirectEditButton() { + this.directEditButton.addEventListener("click", () => { + this.textarea.removeAttribute('readonly'); + this.textarea.focus(); + hideElement(this.directEditButton); + hideElement(this.modalTrigger); + }); + } + + isEmailAlreadySent() { + return this.lastSentEmailContent.value.replace(/\s+/g, '') === this.textarea.value.replace(/\s+/g, ''); + } + + updateUserInterface(reason) { + if (!reason) { + // No reason selected, we will set the label to "Email", show the "Make a selection" placeholder, hide the trigger, textarea, hide the help text + this.showPlaceholder("Email:", "Select an action needed reason to see email"); + } else if (reason === 'other') { + // 'Other' selected, we will set the label to "Email", show the "No email will be sent" placeholder, hide the trigger, textarea, hide the help text + this.showPlaceholder("Email:", "No email will be sent"); + } else { + // A triggering selection is selected, all hands on board: + this.textarea.setAttribute('readonly', true); + showElement(this.textarea); + hideElement(this.textareaPlaceholder); + + if (this.isEmailAlreadySentConst) { + hideElement(this.directEditButton); + showElement(this.modalTrigger); + } else { + showElement(this.directEditButton); + hideElement(this.modalTrigger); + } + + if (this.isEmailAlreadySent()) { + this.formLabel.innerHTML = "Email sent to creator:"; + } else { + this.formLabel.innerHTML = "Email:"; + } } + } - }); + showPlaceholder(formLabelText, placeholderText) { + this.formLabel.innerHTML = formLabelText; + this.textareaPlaceholder.innerHTML = placeholderText; + showElement(this.textareaPlaceholder); + hideElement(this.directEditButton); + hideElement(this.modalTrigger); + hideElement(this.textarea); + } +} - modalConfirm.addEventListener("click", () => { - textarea.removeAttribute('readonly'); - textarea.focus(); - hideElement(directEditButton); - hideElement(modalTrigger); - }); - directEditButton.addEventListener("click", () => { - textarea.removeAttribute('readonly'); - textarea.focus(); - hideElement(directEditButton); - hideElement(modalTrigger); - }); + + +class customActionNeededEmail extends CustomizableEmailBase { + constructor() { + const dropdown = document.getElementById("id_action_needed_reason"); + const textarea = document.getElementById("id_action_needed_reason_email") + const textareaPlaceholder = document.querySelector(".field-action_needed_reason_email__placeholder"); + const directEditButton = document.querySelector('.field-action_needed_reason_email__edit'); + const modalTrigger = document.querySelector('.field-action_needed_reason_email__modal-trigger'); + const modalConfirm = document.getElementById('confirm-edit-email'); + const formLabel = document.querySelector('label[for="id_action_needed_reason_email"]'); + const lastSentEmailContent = document.getElementById("last-sent-email-content"); + + let apiContainer = document.getElementById("get-action-needed-email-for-user-json") + const apiUrl = apiContainer ? apiContainer.value : null; + super( + dropdown, + textarea, + textareaPlaceholder, + directEditButton, + modalTrigger, + modalConfirm, + formLabel, + lastSentEmailContent, + apiUrl + ); + } + + loadActionNeededEmail() { + this.updateUserInterface(this.dropdown.value); + this.initializeDropdown("Error when attempting to grab action needed email: ") + this.initializeModalConfirm() + this.initializeDirectEditButton() + } +} + +/** An IIFE that hooks to the show/hide button underneath action needed reason. + * This shows the auto generated email on action needed reason. +*/ +document.addEventListener('DOMContentLoaded', function() { + const customEmail = new customActionNeededEmail(); + if (!customEmail.dropdown || !customEmail.textarea || !customEmail.domainRequestId || !customEmail.formLabel || !customEmail.modalConfirm){ + return; + } + + // Initialize UI + customEmail.loadActionNeededEmail() +}); + + +class customRejectedEmail extends CustomizableEmailBase { + constructor() { + const dropdown = document.getElementById("id_rejection_reason"); + const textarea = document.getElementById("id_rejection_reason_email") + const textareaPlaceholder = document.querySelector(".field-rejection_reason_email__placeholder"); + const directEditButton = document.querySelector('.field-rejection_reason_email__edit'); + const modalTrigger = document.querySelector('.field-rejection_reason_email__modal-trigger'); + const modalConfirm = document.getElementById('confirm-edit-email'); + const formLabel = document.querySelector('label[for="id_rejection_reason_email"]'); + const lastSentEmailContent = document.getElementById("last-sent-email-content"); + + let apiContainer = document.getElementById("get-rejection-reason-email-for-user-json") + const apiUrl = apiContainer ? apiContainer.value : null; + super( + dropdown, + textarea, + textareaPlaceholder, + directEditButton, + modalTrigger, + modalConfirm, + formLabel, + lastSentEmailContent, + apiUrl + ); + } + + loadRejectedEmail() { + this.updateUserInterface(this.dropdown.value); + this.initializeDropdown("Error when attempting to grab rejected email: ") + this.initializeModalConfirm() + this.initializeDirectEditButton() + } +} + + +/** An IIFE that hooks to the show/hide button underneath rejected reason. + * This shows the auto generated email on action needed reason. +*/ +document.addEventListener('DOMContentLoaded', function() { + const customEmail = new customRejectedEmail(); + if (!customEmail.dropdown || !customEmail.textarea || !customEmail.domainRequestId || !customEmail.formLabel || !customEmail.modalConfirm){ + return; + } + + // Initialize UI + customEmail.loadRejectedEmail() }); diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py index bb8693ac1..b1cc00bde 100644 --- a/src/registrar/models/domain_request.py +++ b/src/registrar/models/domain_request.py @@ -300,6 +300,11 @@ class DomainRequest(TimeStampedModel): blank=True, ) + rejection_reason_email = models.TextField( + null=True, + blank=True, + ) + action_needed_reason = models.TextField( choices=ActionNeededReasons.choices, null=True, @@ -798,6 +803,21 @@ class DomainRequest(TimeStampedModel): except EmailSendingError: logger.warning("Failed to send confirmation email", exc_info=True) + def _send_custom_status_update_email(self, email_content): + """Wrapper for `_send_status_update_email` that bcc's help@get.gov + and sends an email equivalent to the 'email_content' variable.""" + if settings.IS_PRODUCTION: + bcc_address = settings.DEFAULT_FROM_EMAIL + + self._send_status_update_email( + new_status="action needed", + email_template=f"emails/includes/custom_email.txt", + email_template_subject=f"emails/status_change_subject.txt", + bcc_address=bcc_address, + custom_email_content=email_content, + wrap_email=True, + ) + def investigator_exists_and_is_staff(self): """Checks if the current investigator is in a valid state for a state transition""" is_valid = True @@ -901,7 +921,7 @@ class DomainRequest(TimeStampedModel): target=DomainRequestStatus.ACTION_NEEDED, conditions=[domain_is_not_active, investigator_exists_and_is_staff], ) - def action_needed(self, send_email=True): + def action_needed(self): """Send back an domain request that is under investigation or rejected. This action is logged. @@ -924,27 +944,7 @@ class DomainRequest(TimeStampedModel): # Send out an email if an action needed reason exists if self.action_needed_reason and self.action_needed_reason != self.ActionNeededReasons.OTHER: email_content = self.action_needed_reason_email - self._send_action_needed_reason_email(send_email, email_content) - - def _send_action_needed_reason_email(self, send_email=True, email_content=None): - """Sends out an automatic email for each valid action needed reason provided""" - - email_template_name = "custom_email.txt" - email_template_subject_name = f"{self.action_needed_reason}_subject.txt" - - bcc_address = "" - if settings.IS_PRODUCTION: - bcc_address = settings.DEFAULT_FROM_EMAIL - - self._send_status_update_email( - new_status="action needed", - email_template=f"emails/action_needed_reasons/{email_template_name}", - email_template_subject=f"emails/action_needed_reasons/{email_template_subject_name}", - send_email=send_email, - bcc_address=bcc_address, - custom_email_content=email_content, - wrap_email=True, - ) + self._send_custom_status_update_email(email_content) @transition( field="status", @@ -1051,6 +1051,11 @@ class DomainRequest(TimeStampedModel): "emails/status_change_rejected_subject.txt", ) + # Send out an email if a rejection reason exists + if self.rejection_reason: + email_content = self.rejection_reason_email + self._send_custom_status_update_email(email_content) + @transition( field="status", source=[ diff --git a/src/registrar/templates/emails/action_needed_reasons/custom_email.txt b/src/registrar/templates/emails/includes/custom_email.txt similarity index 100% rename from src/registrar/templates/emails/action_needed_reasons/custom_email.txt rename to src/registrar/templates/emails/includes/custom_email.txt diff --git a/src/registrar/templates/emails/includes/email_footer.txt b/src/registrar/templates/emails/includes/email_footer.txt new file mode 100644 index 000000000..f10d82a91 --- /dev/null +++ b/src/registrar/templates/emails/includes/email_footer.txt @@ -0,0 +1,10 @@ +THANK YOU +.Gov helps the public identify official, trusted information. Thank you for requesting a .gov domain. + +---------------------------------------------------------------- + +The .gov team +Contact us: +Learn about .gov + +The .gov registry is a part of the Cybersecurity and Infrastructure Security Agency (CISA) \ No newline at end of file diff --git a/src/registrar/templates/emails/includes/status_change_rejected_header.txt b/src/registrar/templates/emails/includes/status_change_rejected_header.txt new file mode 100644 index 000000000..16b7c73a9 --- /dev/null +++ b/src/registrar/templates/emails/includes/status_change_rejected_header.txt @@ -0,0 +1,8 @@ +Hi, {{ recipient.first_name }}. + +Your .gov domain request has been rejected. + +DOMAIN REQUESTED: {{ domain_request.requested_domain.name }} +REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }} +STATUS: Rejected +---------------------------------------------------------------- \ No newline at end of file diff --git a/src/registrar/templates/emails/rejection_reasons/contacts_not_verified.txt b/src/registrar/templates/emails/rejection_reasons/contacts_not_verified.txt new file mode 100644 index 000000000..c35c82c2b --- /dev/null +++ b/src/registrar/templates/emails/rejection_reasons/contacts_not_verified.txt @@ -0,0 +1,8 @@ +{% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #} +{% include "emails/includes/status_change_rejected_header.html" %} +REJECTION REASON +Your domain request was rejected because we could not verify the organizational +contacts you provided. If you have questions or comments, reply to this email. + +{% include "emails/includes/email_footer.html" %} +{% endautoescape %} \ No newline at end of file diff --git a/src/registrar/templates/emails/rejection_reasons/naming_not_met.txt b/src/registrar/templates/emails/rejection_reasons/naming_not_met.txt new file mode 100644 index 000000000..3e57d579d --- /dev/null +++ b/src/registrar/templates/emails/rejection_reasons/naming_not_met.txt @@ -0,0 +1,15 @@ +{% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #} +{% include "emails/includes/status_change_rejected_header.html" %} +REJECTION REASON +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 +. + + +YOU CAN SUBMIT A NEW REQUEST +We encourage you to request a domain that meets our requirements. If you have +questions or want to discuss potential domain names, reply to this email. + +{% include "emails/includes/email_footer.html" %} +{% endautoescape %} \ No newline at end of file diff --git a/src/registrar/templates/emails/rejection_reasons/org_has_domain.txt b/src/registrar/templates/emails/rejection_reasons/org_has_domain.txt new file mode 100644 index 000000000..26757efd6 --- /dev/null +++ b/src/registrar/templates/emails/rejection_reasons/org_has_domain.txt @@ -0,0 +1,15 @@ +{% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #} +{% include "emails/includes/status_change_rejected_header.html" %} +REJECTION REASON +Your domain request was rejected because {{ domain_request.organization_name }} has a .gov domain. Our +practice is to approve one domain per online service per government organization. We +evaluate additional requests on a case-by-case basis. You did not provide sufficient +justification for an additional domain. + +Read more about our practice of approving one domain per online service +. + +If you have questions or comments, reply to this email. + +{% include "emails/includes/email_footer.html" %} +{% endautoescape %} \ No newline at end of file diff --git a/src/registrar/templates/emails/rejection_reasons/org_not_eligible.txt b/src/registrar/templates/emails/rejection_reasons/org_not_eligible.txt new file mode 100644 index 000000000..3c7de3f42 --- /dev/null +++ b/src/registrar/templates/emails/rejection_reasons/org_not_eligible.txt @@ -0,0 +1,14 @@ +{% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #} +{% include "emails/includes/status_change_rejected_header.html" %} +REJECTION REASON +Your domain request was rejected because we determined that {{ domain_request.organization_name }} is not +eligible for a .gov domain. .Gov domains are only available to official U.S.-based +government organizations. + +Learn more about eligibility for .gov domains +. + +If you have questions or comments, reply to this email. + +{% include "emails/includes/email_footer.html" %} +{% endautoescape %} \ No newline at end of file diff --git a/src/registrar/templates/emails/rejection_reasons/other.txt b/src/registrar/templates/emails/rejection_reasons/other.txt new file mode 100644 index 000000000..6835a45e0 --- /dev/null +++ b/src/registrar/templates/emails/rejection_reasons/other.txt @@ -0,0 +1,15 @@ +{% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #} +{% include "emails/includes/status_change_rejected_header.html" %} +YOU CAN SUBMIT A NEW REQUEST +If your organization is eligible for a .gov domain and you meet our other requirements, you can submit a new request. + +Learn more about: +- Eligibility for a .gov domain +- Choosing a .gov domain name + + +NEED ASSISTANCE? +If you have questions about this domain request or need help choosing a new domain name, reply to this email. + +{% include "emails/includes/email_footer.html" %} +{% endautoescape %} \ No newline at end of file diff --git a/src/registrar/templates/emails/rejection_reasons/purpose_not_met.txt b/src/registrar/templates/emails/rejection_reasons/purpose_not_met.txt new file mode 100644 index 000000000..57bce78f0 --- /dev/null +++ b/src/registrar/templates/emails/rejection_reasons/purpose_not_met.txt @@ -0,0 +1,15 @@ +{% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #} +{% include "emails/includes/status_change_rejected_header.html" %} +REJECTION REASON +Your domain request was rejected because the purpose you provided did not meet our +requirements. You didn’t provide enough information about how you intend to use the +domain. + +Learn more about: +- Eligibility for a .gov domain +- What you can and can’t do with .gov domains + +If you have questions or comments, reply to this email. + +{% include "emails/includes/email_footer.html" %} +{% endautoescape %} \ No newline at end of file diff --git a/src/registrar/templates/emails/rejection_reasons/requestor_not_eligible.txt b/src/registrar/templates/emails/rejection_reasons/requestor_not_eligible.txt new file mode 100644 index 000000000..7974c1690 --- /dev/null +++ b/src/registrar/templates/emails/rejection_reasons/requestor_not_eligible.txt @@ -0,0 +1,14 @@ +{% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #} +{% include "emails/includes/status_change_rejected_header.html" %} +REJECTION REASON +Your domain request was rejected because we don’t believe you’re eligible to request a +.gov domain on behalf of {{ domain_request.organization_name }}. You must be a government employee, or be +working on behalf of a government organization, to request a .gov domain. + + +DEMONSTRATE ELIGIBILITY +If you can provide more information that demonstrates your eligibility, or you want to +discuss further, reply to this email. + +{% include "emails/includes/email_footer.html" %} +{% endautoescape %} \ No newline at end of file diff --git a/src/registrar/templates/emails/status_change_rejected_subject.txt b/src/registrar/templates/emails/status_change_subject.txt similarity index 100% rename from src/registrar/templates/emails/status_change_rejected_subject.txt rename to src/registrar/templates/emails/status_change_subject.txt diff --git a/src/registrar/utility/admin_helpers.py b/src/registrar/utility/admin_helpers.py index 0b99bba13..87edc2106 100644 --- a/src/registrar/utility/admin_helpers.py +++ b/src/registrar/utility/admin_helpers.py @@ -2,23 +2,66 @@ from registrar.models.domain_request import DomainRequest from django.template.loader import get_template -def get_all_action_needed_reason_emails(request, domain_request): +def get_all_action_needed_reason_emails(domain_request): """Returns a dictionary of every action needed reason and its associated email for this particular domain request.""" + return _get_all_default_emails( + reasons=DomainRequest.ActionNeededReasons, + # Where the emails are stored. This assumes that it contains a list of .txt files with the reason. + path_root="emails/action_needed_reasons", + # What reasons don't send out emails (none is handled automagically) + excluded_reasons=[DomainRequest.ActionNeededReasons.OTHER], + # Who to send it to, and from what domain request to reference + domain_request=domain_request + ) + +def get_action_needed_reason_default_email(domain_request, action_needed_reason): + """Returns the default email associated with the given action needed reason""" + return _get_default_email( + domain_request, + path_root="emails/rejection_reasons", + reason=action_needed_reason, + excluded_reasons=[DomainRequest.ActionNeededReasons.OTHER] + ) + + +def get_all_rejection_reason_emails(domain_request): + """Returns a dictionary of every rejection reason and its associated email + for this particular domain request.""" + return _get_all_default_emails( + reasons=DomainRequest.RejectionReasons, + # Where the emails are stored. This assumes that it contains a list of .txt files with the reason. + path_root="emails/rejection_reasons", + # What reasons don't send out emails (none is handled automagically) + excluded_reasons=[DomainRequest.RejectionReasons.OTHER], + # Who to send it to, and from what domain request to reference + domain_request=domain_request + ) + + +def get_rejection_reason_default_email(domain_request, action_needed_reason): + """Returns the default email associated with the given rejection reason""" + return _get_default_email( + domain_request, + path_root="emails/rejection_reasons", + reason=action_needed_reason, + excluded_reasons=[DomainRequest.RejectionReasons.OTHER] + ) + +def _get_all_default_emails(reasons, path_root, excluded_reasons, domain_request): emails = {} - for action_needed_reason in domain_request.ActionNeededReasons: - # Map the action_needed_reason to its default email - emails[action_needed_reason.value] = get_action_needed_reason_default_email( - request, domain_request, action_needed_reason.value + for reason in reasons: + # Map the reason to its default email + emails[reason.value] = _get_default_email( + domain_request, path_root, reason, excluded_reasons ) - return emails +def _get_default_email(domain_request, path_root, reason, excluded_reasons=None): + if not reason: + return None - -def get_action_needed_reason_default_email(request, domain_request, action_needed_reason): - """Returns the default email associated with the given action needed reason""" - if not action_needed_reason or action_needed_reason == DomainRequest.ActionNeededReasons.OTHER: + if excluded_reasons and reason in excluded_reasons: return None recipient = domain_request.creator @@ -26,7 +69,7 @@ def get_action_needed_reason_default_email(request, domain_request, action_neede context = {"domain_request": domain_request, "recipient": recipient} # Get the email body - template_path = f"emails/action_needed_reasons/{action_needed_reason}.txt" + template_path = f"{path_root}/{reason}.txt" email_body_text = get_template(template_path).render(context=context) email_body_text_cleaned = None diff --git a/src/registrar/views/utility/api_views.py b/src/registrar/views/utility/api_views.py index 6a6269baa..973f85855 100644 --- a/src/registrar/views/utility/api_views.py +++ b/src/registrar/views/utility/api_views.py @@ -90,5 +90,5 @@ def get_action_needed_email_for_user_json(request): return JsonResponse({"error": "No domain_request_id specified"}, status=404) domain_request = DomainRequest.objects.filter(id=domain_request_id).first() - emails = get_all_action_needed_reason_emails(request, domain_request) + emails = get_all_action_needed_reason_emails(domain_request) return JsonResponse({"action_needed_email": emails.get(reason)}, status=200)