diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 051dc7a9a..04730371c 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -9,6 +9,7 @@ from django.db.models.functions import Concat, Coalesce from django.http import HttpResponseRedirect from django.shortcuts import redirect from django_fsm import get_available_FIELD_transitions, FSMField +from waffle.decorators import flag_is_active from django.contrib import admin, messages from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from django.contrib.auth.models import Group @@ -166,6 +167,9 @@ class DomainRequestAdminForm(forms.ModelForm): "alternative_domains": NoAutocompleteFilteredSelectMultiple("alternative_domains", False), "other_contacts": NoAutocompleteFilteredSelectMultiple("other_contacts", False), } + labels = { + "action_needed_reason_email": "Auto-generated email", + } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -1509,6 +1513,13 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): custom_election_board.admin_order_field = "is_election_board" # type: ignore custom_election_board.short_description = "Election office" # type: ignore + # This is just a placeholder. This field will be populated in the detail_table_fieldset view. + # This is not a field that exists on the model. + def status_history(self, obj): + return "No changelog to display." + + status_history.short_description = "Status History" # type: ignore + # Filters list_filter = ( StatusListFilter, @@ -1535,9 +1546,11 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): "fields": [ "portfolio", "sub_organization", + "status_history", "status", "rejection_reason", "action_needed_reason", + "action_needed_reason_email", "investigator", "creator", "submitter", @@ -1617,6 +1630,8 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): "alternative_domains", "is_election_board", "federal_agency", + "status_history", + "action_needed_reason_email", ) # Read only that we'll leverage for CISA Analysts @@ -1935,6 +1950,7 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): extra_context = extra_context or {} extra_context["filtered_audit_log_entries"] = filtered_audit_log_entries extra_context["action_needed_reason_emails"] = self.get_all_action_needed_reason_emails_as_json(obj) + extra_context["has_profile_feature_flag"] = flag_is_active(request, "profile_feature") # Call the superclass method with updated extra_context return super().change_view(request, object_id, form_url, extra_context) @@ -1965,9 +1981,13 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): template_subject_path = f"emails/action_needed_reasons/{action_needed_reason}_subject.txt" subject_template = get_template(template_subject_path) - # Return the content of the rendered views - context = {"domain_request": domain_request} + if flag_is_active(None, "profile_feature"): # type: ignore + recipient = domain_request.creator + else: + recipient = domain_request.submitter + # Return the content of the rendered views + context = {"domain_request": domain_request, "recipient": recipient} return { "subject_text": subject_template.render(context=context), "email_body_text": template.render(context=context), diff --git a/src/registrar/assets/js/get-gov-admin.js b/src/registrar/assets/js/get-gov-admin.js index 94fdd8b07..ad759f445 100644 --- a/src/registrar/assets/js/get-gov-admin.js +++ b/src/registrar/assets/js/get-gov-admin.js @@ -361,9 +361,12 @@ function initializeWidgetOnList(list, parentId) { */ (function (){ let rejectionReasonFormGroup = document.querySelector('.field-rejection_reason') + // This is the "action needed reason" field let actionNeededReasonFormGroup = document.querySelector('.field-action_needed_reason'); + // This is the "auto-generated email" field + let actionNeededReasonEmailFormGroup = document.querySelector('.field-action_needed_reason_email') - if (rejectionReasonFormGroup && actionNeededReasonFormGroup) { + if (rejectionReasonFormGroup && actionNeededReasonFormGroup && actionNeededReasonEmailFormGroup) { let statusSelect = document.getElementById('id_status') let isRejected = statusSelect.value == "rejected" let isActionNeeded = statusSelect.value == "action needed" @@ -371,6 +374,7 @@ function initializeWidgetOnList(list, parentId) { // Initial handling of rejectionReasonFormGroup display showOrHideObject(rejectionReasonFormGroup, show=isRejected) showOrHideObject(actionNeededReasonFormGroup, show=isActionNeeded) + showOrHideObject(actionNeededReasonEmailFormGroup, show=isActionNeeded) // Listen to change events and handle rejectionReasonFormGroup display, then save status to session storage statusSelect.addEventListener('change', function() { @@ -382,6 +386,7 @@ function initializeWidgetOnList(list, parentId) { isActionNeeded = statusSelect.value == "action needed" showOrHideObject(actionNeededReasonFormGroup, show=isActionNeeded) + showOrHideObject(actionNeededReasonEmailFormGroup, show=isActionNeeded) addOrRemoveSessionBoolean("showActionNeededReason", add=isActionNeeded) }); @@ -398,6 +403,7 @@ function initializeWidgetOnList(list, parentId) { let showActionNeededReason = sessionStorage.getItem("showActionNeededReason") !== null showOrHideObject(actionNeededReasonFormGroup, show=showActionNeededReason) + showOrHideObject(actionNeededReasonEmailFormGroup, show=isActionNeeded) } }); }); @@ -421,42 +427,6 @@ function initializeWidgetOnList(list, parentId) { sessionStorage.removeItem(name); } } - - document.addEventListener('DOMContentLoaded', function() { - let statusSelect = document.getElementById('id_status'); - - function moveStatusChangelog(actionNeededReasonFormGroup, statusSelect) { - if (!actionNeededReasonFormGroup || !statusSelect) { - return; - } - - let flexContainer = actionNeededReasonFormGroup.querySelector('.flex-container'); - let statusChangelog = document.getElementById('dja-status-changelog'); - - // On action needed, show the email that will be sent out - let showReasonEmailContainer = document.querySelector("#action_needed_reason_email_readonly") - - // Prepopulate values on page load. - if (statusSelect.value === "action needed") { - flexContainer.parentNode.insertBefore(statusChangelog, flexContainer.nextSibling); - showElement(showReasonEmailContainer); - } else { - // Move the changelog back to its original location - let statusFlexContainer = statusSelect.closest('.flex-container'); - statusFlexContainer.parentNode.insertBefore(statusChangelog, statusFlexContainer.nextSibling); - hideElement(showReasonEmailContainer); - } - - } - - // Call the function on page load - moveStatusChangelog(actionNeededReasonFormGroup, statusSelect); - - // Add event listener to handle changes to the selector itself - statusSelect.addEventListener('change', function() { - moveStatusChangelog(actionNeededReasonFormGroup, statusSelect); - }) - }); })(); /** An IIFE for toggling the submit bar on domain request forms @@ -552,13 +522,13 @@ function initializeWidgetOnList(list, parentId) { })(); -/** An IIFE that hooks up to the "show email" button. - * which shows the auto generated email on action needed reason. +/** An IIFE that hooks to the show/hide button underneath action needed reason. + * This shows the auto generated email on action needed reason. */ (function () { let actionNeededReasonDropdown = document.querySelector("#id_action_needed_reason"); let actionNeededEmail = document.querySelector("#action_needed_reason_email_view_more"); - if(actionNeededReasonDropdown && actionNeededEmail && container) { + if(actionNeededReasonDropdown && actionNeededEmail) { // Add a change listener to the action needed reason dropdown handleChangeActionNeededEmail(actionNeededReasonDropdown, actionNeededEmail); } diff --git a/src/registrar/assets/sass/_theme/_admin.scss b/src/registrar/assets/sass/_theme/_admin.scss index aa8020ede..d7d116046 100644 --- a/src/registrar/assets/sass/_theme/_admin.scss +++ b/src/registrar/assets/sass/_theme/_admin.scss @@ -787,10 +787,16 @@ div.dja__model-description{ color: var(--link-fg); } +.textarea-wrapper { + width: 100%; + max-width: 610px; +} + .dja-readonly-textarea-container { + width: 100%; textarea { width: 100%; - min-width: 610px; + max-width: 610px; resize: none; cursor: auto; @@ -827,3 +833,20 @@ div.dja__model-description{ // Many elements in django admin try to override this, so we need !important. display: none !important; } + +.margin-top-0 { + margin-top: 0 !important; +} + +.padding-top-0 { + padding-top: 0 !important; +} + + +.flex-container { + @media screen and (min-width: 700px) and (max-width: 1150px) { + &.flex-container--mobile-inline { + display: inline !important; + } + } +} diff --git a/src/registrar/templates/admin/fieldset.html b/src/registrar/templates/admin/fieldset.html index 89537b098..40cd98ca8 100644 --- a/src/registrar/templates/admin/fieldset.html +++ b/src/registrar/templates/admin/fieldset.html @@ -32,7 +32,9 @@ https://github.com/django/django/blob/main/django/contrib/admin/templates/admin/ {% for field in line %}
{% if not line.fields|length == 1 and not field.is_readonly %}{{ field.errors }}{% endif %} + {% block flex_container_start %}
+ {% endblock flex_container_start %} {% if field.is_checkbox %} {# .gov override #} {% block field_checkbox %} @@ -52,7 +54,9 @@ https://github.com/django/django/blob/main/django/contrib/admin/templates/admin/ {% endblock field_other%} {% endif %} {% endif %} + {% block flex_container_end %}
+ {% endblock flex_container_end %} {% if field.field.help_text %} {# .gov override #} diff --git a/src/registrar/templates/django/admin/includes/detail_table_fieldset.html b/src/registrar/templates/django/admin/includes/detail_table_fieldset.html index e3e7ef42c..8b8748f80 100644 --- a/src/registrar/templates/django/admin/includes/detail_table_fieldset.html +++ b/src/registrar/templates/django/admin/includes/detail_table_fieldset.html @@ -6,9 +6,85 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html) {% endcomment %} +{% block flex_container_start %} + {% if field.field.name == "status_history" %} +
+ {% else %} + {% comment %} Default flex container element {% endcomment %} +
+ {% endif %} +{% endblock flex_container_start %} + {% block field_readonly %} {% with all_contacts=original_object.other_contacts.all %} - {% if field.field.name == "other_contacts" %} + {% if field.field.name == "status_history" %} + {% if filtered_audit_log_entries %} +
+ + +
+ {% else %} +
+ No changelog to display. +
+ {% endif %} + {% elif field.field.name == "action_needed_reason_email" %} +
+ + +
+ {% elif field.field.name == "other_contacts" %} {% if all_contacts.count > 2 %}
{% for contact in all_contacts %} @@ -68,80 +144,19 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html) {% endblock field_readonly %} {% block after_help_text %} - {% if field.field.name == "status" %} -
- -
- - - - -
-
+ Given that we have a limited number of emails, doing it this way makes sense. + {% endcomment %} + {% if action_needed_reason_emails %} + + {% endif %} {% elif field.field.name == "creator" %}
diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index 51832e4ed..3ffb14905 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -2240,6 +2240,8 @@ class TestDomainRequestAdmin(MockEppLib): "alternative_domains", "is_election_board", "federal_agency", + "status_history", + "action_needed_reason_email", "id", "created_at", "updated_at", @@ -2300,6 +2302,8 @@ class TestDomainRequestAdmin(MockEppLib): "alternative_domains", "is_election_board", "federal_agency", + "status_history", + "action_needed_reason_email", "creator", "about_your_organization", "requested_domain", @@ -2330,6 +2334,8 @@ class TestDomainRequestAdmin(MockEppLib): "alternative_domains", "is_election_board", "federal_agency", + "status_history", + "action_needed_reason_email", ] self.assertEqual(readonly_fields, expected_fields)