From de17d686e3f73234c81915dabab88961e4f37a55 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 28 Feb 2024 15:30:23 -0700 Subject: [PATCH 01/17] Add basic on hold modal --- src/registrar/assets/js/get-gov-admin.js | 2 +- .../django/admin/domain_change_form.html | 84 +++++++++++++++++-- 2 files changed, 77 insertions(+), 9 deletions(-) diff --git a/src/registrar/assets/js/get-gov-admin.js b/src/registrar/assets/js/get-gov-admin.js index ff73acb65..096f7a626 100644 --- a/src/registrar/assets/js/get-gov-admin.js +++ b/src/registrar/assets/js/get-gov-admin.js @@ -29,7 +29,7 @@ function openInNewTab(el, removeAttribute = false){ */ (function (){ function createPhantomModalFormButtons(){ - let submitButtons = document.querySelectorAll('.usa-modal button[type="submit"]'); + let submitButtons = document.querySelectorAll('.usa-modal button[type="submit"].dja-form-placeholder'); form = document.querySelector("form") submitButtons.forEach((button) => { diff --git a/src/registrar/templates/django/admin/domain_change_form.html b/src/registrar/templates/django/admin/domain_change_form.html index 67c5ac291..e38583eb8 100644 --- a/src/registrar/templates/django/admin/domain_change_form.html +++ b/src/registrar/templates/django/admin/domain_change_form.html @@ -11,18 +11,15 @@
{% if original.state != original.State.DELETED %} - + Extend expiration date | {% endif %} {% if original.state == original.State.READY %} - + + Place hold + {% elif original.state == original.State.ON_HOLD %} {% endif %} @@ -52,6 +49,8 @@ In addition, the modal element MUST be placed low in the DOM. The script loads slower on DJA than on other portions of the application, so this means that it will briefly "populate", causing unintended visual effects. {% endcomment %} + + {# Create a modal for the _extend_expiration_date button #}
- +
+ + {# Create a modal for the _on_hold button #} +
+
+
+ +
+

+ When a domain is on hold: +

+
    +
  • The domain (and any subdomains) won’t resolve in DNS. Any infrastructure (like websites) will go offline.
  • +
  • The domain will still appear in the registrar / admin.
  • +
  • Domain managers won’t be able to edit the domain.
  • +
+

+ This action can be reversed, if needed. +

+

+ {# Acts as a
#} +

+ Domain: {{ original.name }} + New status: {{ original.State.ON_HOLD }} +

+
+ + +
+ +
+
+ {# Create a modal for when a domain is marked as ineligible #} {{ block.super }} {% endblock %} \ No newline at end of file From f693557f93eeb0d057d46a23ed80f79c4a3ea7b0 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 29 Feb 2024 08:58:40 -0700 Subject: [PATCH 02/17] Add some modals --- src/registrar/admin.py | 2 + .../admin/domain_application_change_form.html | 87 +++++++++++++++++++ .../django/admin/domain_change_form.html | 78 +++++++++++++++-- 3 files changed, 162 insertions(+), 5 deletions(-) create mode 100644 src/registrar/templates/django/admin/domain_application_change_form.html diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 92e477667..942ae6162 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -931,6 +931,8 @@ class DomainApplicationAdmin(ListHeaderAdmin): if self.value() == "0": return queryset.filter(Q(is_election_board=False) | Q(is_election_board=None)) + change_form_template = "django/admin/domain_application_change_form.html" + # Columns list_display = [ "requested_domain", diff --git a/src/registrar/templates/django/admin/domain_application_change_form.html b/src/registrar/templates/django/admin/domain_application_change_form.html new file mode 100644 index 000000000..26abbec18 --- /dev/null +++ b/src/registrar/templates/django/admin/domain_application_change_form.html @@ -0,0 +1,87 @@ +{% extends 'admin/change_form.html' %} +{% load i18n static %} + +{% block submit_buttons_bottom %} + {% comment %} + Modals behave very weirdly in django admin. + They tend to "strip out" any injected form elements, leaving only the main form. + In addition, USWDS handles modals by first destroying the element, then repopulating it toward the end of the page. + In effect, this means that the modal is not, and cannot, be surrounded by any form element at compile time. + + The current workaround for this is to use javascript to inject a hidden input, and bind submit of that + element to the click of the confirmation button within this modal. + + This is controlled by the class `dja-form-placeholder` on the button. + + In addition, the modal element MUST be placed low in the DOM. The script loads slower on DJA than on other portions + of the application, so this means that it will briefly "populate", causing unintended visual effects. + {% endcomment %} +{# Create a modal for when a domain is marked as ineligible #} +
+
+
+ +
+

+ When a domain request is in ineligible status, the registrant's permissions within the registrar are restricted as follows: +

+
    +
  • They cannot edit the ineligible request or any other pending requests.
  • +
  • They cannot manage any of their approved domains.
  • +
  • They cannot initiate a new domain request.
  • +
+

+ This action can be reversed, if needed. +

+

+ {# Acts as a
#} +

+ Domain: {{ original.name }} + New status: {{ original.State }} +

+
+ + +
+ +
+
+{{ block.super }} +{% endblock %} \ No newline at end of file diff --git a/src/registrar/templates/django/admin/domain_change_form.html b/src/registrar/templates/django/admin/domain_change_form.html index e38583eb8..9d7b1d5de 100644 --- a/src/registrar/templates/django/admin/domain_change_form.html +++ b/src/registrar/templates/django/admin/domain_change_form.html @@ -27,7 +27,9 @@ | {% endif %} {% if original.state != original.State.DELETED %} - + + Remove from registry + {% endif %} @@ -118,8 +120,8 @@
@@ -131,7 +133,7 @@ When a domain is on hold:

    -
  • The domain (and any subdomains) won’t resolve in DNS. Any infrastructure (like websites) will go offline.
  • +
  • The domain and its subdomains won’t resolve in DNS. Any infrastructure (like websites) will go offline.
  • The domain will still appear in the registrar / admin.
  • Domain managers won’t be able to edit the domain.
@@ -181,6 +183,72 @@
- {# Create a modal for when a domain is marked as ineligible #} + {# Create a modal for the _remove_domain button #} +
+
+
+ +
+

+ When a domain is removed from the registry: +

+
    +
  • The domain and its subdomains won’t resolve in DNS. Any infrastructure (like websites) will go offline.
  • +
  • The domain will still appear in the registrar / admin.
  • +
  • Domain managers won’t be able to edit the domain.
  • +
+

+ This action cannot be undone. +

+

+ {# Acts as a
#} +

+ Domain: {{ original.name }} + New status: {{ original.State.DELETED }} +

+
+ + +
+ +
+
{{ block.super }} {% endblock %} \ No newline at end of file From eeff5586d2b0de040ca2ed1614f94db0a9b3d618 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 29 Feb 2024 09:20:33 -0700 Subject: [PATCH 03/17] Update styling --- src/registrar/assets/sass/_theme/_admin.scss | 9 +++++++++ .../django/admin/domain_change_form.html | 18 +++++++++--------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_admin.scss b/src/registrar/assets/sass/_theme/_admin.scss index b57c6a015..46e4a10d4 100644 --- a/src/registrar/assets/sass/_theme/_admin.scss +++ b/src/registrar/assets/sass/_theme/_admin.scss @@ -302,3 +302,12 @@ input.admin-confirm-button { display: contents !important; } } + +.django-admin-modal .usa-prose ul > li { + list-style-type: inherit; + // Styling based off of the

styling in django admin + line-height: 1.5; + margin-bottom: 0; + margin-top: 0; + max-width: 68ex; +} diff --git a/src/registrar/templates/django/admin/domain_change_form.html b/src/registrar/templates/django/admin/domain_change_form.html index 9d7b1d5de..61007c3d4 100644 --- a/src/registrar/templates/django/admin/domain_change_form.html +++ b/src/registrar/templates/django/admin/domain_change_form.html @@ -54,7 +54,7 @@ {# Create a modal for the _extend_expiration_date button #}

+ Domain: {{ original.name }} {# Acts as a
#}

- Domain: {{ original.name }} New status: {{ original.State.ON_HOLD }}

@@ -185,7 +185,7 @@
{# Create a modal for the _remove_domain button #}

This action cannot be undone.

+ Domain: {{ original.name }} {# Acts as a
#}

- Domain: {{ original.name }} New status: {{ original.State.DELETED }}

@@ -251,4 +251,4 @@ {{ block.super }} -{% endblock %} \ No newline at end of file +{% endblock %} From 7d65b2cf5cfea3a816e5fc44902f67a7d5993a4d Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 29 Feb 2024 10:15:21 -0700 Subject: [PATCH 04/17] JS changes --- src/registrar/assets/js/get-gov-admin.js | 66 ++++++++- .../admin/domain_application_change_form.html | 135 ++++++++++-------- 2 files changed, 135 insertions(+), 66 deletions(-) diff --git a/src/registrar/assets/js/get-gov-admin.js b/src/registrar/assets/js/get-gov-admin.js index 096f7a626..47351a608 100644 --- a/src/registrar/assets/js/get-gov-admin.js +++ b/src/registrar/assets/js/get-gov-admin.js @@ -35,14 +35,20 @@ function openInNewTab(el, removeAttribute = false){ let input = document.createElement("input"); input.type = "submit"; - input.name = button.name; - input.value = button.value; + + if(button.name){ + input.name = button.name; + } + + if(button.value){ + input.value = button.value; + } + input.style.display = "none" // Add the hidden input to the form form.appendChild(input); button.addEventListener("click", () => { - console.log("clicking") input.click(); }) }) @@ -50,6 +56,60 @@ function openInNewTab(el, removeAttribute = false){ createPhantomModalFormButtons(); })(); + +/** An IIFE for DomainApplication to hook a modal to a dropdown option. + * This intentionally does not interact with createPhantomModalFormButtons() +*/ +(function (){ + function displayModalOnDropdownClick(){ + // Grab the invisible element that will hook to the modal. + // This doesn't technically need to be done with one, but this is simpler to manage. + let linkClickedDisplaysModal = document.getElementById("invisible-ineligible-modal-toggler") + let statusDropdown = document.getElementById("id_status") + + // If these exist all at the same time, we're on the right page + if (linkClickedDisplaysModal && statusDropdown && statusDropdown.value){ + // Store the previous value in the event the user cancels. + // We only need to do this if we're on the correct page. + let previousValue = statusDropdown.value; + // Because the modal button does not have the class "dja-form-placeholder", + // it will not be affected by the createPhantomModalFormButtons() function. + let cancelButton = document.querySelector('button[name="_cancel_application_ineligible"]'); + if (cancelButton){ + console.log(`This is the previous val: ${previousValue}`) + cancelButton.addEventListener('click', function() { + // Revert the dropdown to its previous value + statusDropdown.value = previousValue; + }); + + // Add a change event listener to the dropdown. + statusDropdown.addEventListener('change', function() { + // Check if "Ineligible" is selected + if (this.value && this.value.toLowerCase() === "ineligible") { + // Display the modal. + linkClickedDisplaysModal.click() + } + + // Update previousValue if another option is selected and confirmed + previousValue = this.value; + console.log(`This is the previous val NOW: ${previousValue}`) + }); + + } else{ + console.error("displayModalOnDropdownClick() -> No cancel button defined.") + } + + } + } + + // Adds event listeners on the confirm and cancel modal buttons + function handleModalButtons(){ + + } + + displayModalOnDropdownClick(); +})(); + /** An IIFE for pages in DjangoAdmin which may need custom JS implementation. * Currently only appends target="_blank" to the domain_form object, * but this can be expanded. diff --git a/src/registrar/templates/django/admin/domain_application_change_form.html b/src/registrar/templates/django/admin/domain_application_change_form.html index 26abbec18..f6380fb82 100644 --- a/src/registrar/templates/django/admin/domain_application_change_form.html +++ b/src/registrar/templates/django/admin/domain_application_change_form.html @@ -1,6 +1,12 @@ {% extends 'admin/change_form.html' %} {% load i18n static %} +{% block field_sets %} + {# Create an invisible tag so that we can use a click event to toggle the modal. #} + + {{ block.super }} +{% endblock %} + {% block submit_buttons_bottom %} {% comment %} Modals behave very weirdly in django admin. @@ -16,72 +22,75 @@ In addition, the modal element MUST be placed low in the DOM. The script loads slower on DJA than on other portions of the application, so this means that it will briefly "populate", causing unintended visual effects. {% endcomment %} -{# Create a modal for when a domain is marked as ineligible #} -
-
-
- -
-

- When a domain request is in ineligible status, the registrant's permissions within the registrar are restricted as follows: -

-
    -
  • They cannot edit the ineligible request or any other pending requests.
  • -
  • They cannot manage any of their approved domains.
  • -
  • They cannot initiate a new domain request.
  • -
-

- This action can be reversed, if needed. -

-

- {# Acts as a
#} -

- Domain: {{ original.name }} - New status: {{ original.State }} -

-
+ {# Create a modal for when a domain is marked as ineligible #} +
+
+
+ +
+

+ When a domain request is in ineligible status, the registrant's permissions within the registrar are restricted as follows: +

+
    +
  • They cannot edit the ineligible request or any other pending requests.
  • +
  • They cannot manage any of their approved domains.
  • +
  • They cannot initiate a new domain request.
  • +
+

+ The restrictions will not take effect until you “save” the changes for this domain request. + This action can be reversed, if needed. +

+

+ {# Acts as a
#} +

+ Domain: {{ original.name }} + New status: {{ original.State }} +

+
- +
- -
{{ block.super }} {% endblock %} \ No newline at end of file From 7a7000cf9d358930280585cdb74c8f366ba815bd Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 29 Feb 2024 10:31:08 -0700 Subject: [PATCH 05/17] Fix bug --- src/registrar/assets/js/get-gov-admin.js | 6 ------ .../django/admin/domain_application_change_form.html | 6 +++--- .../templates/django/admin/domain_change_form.html | 6 +++--- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/registrar/assets/js/get-gov-admin.js b/src/registrar/assets/js/get-gov-admin.js index 47351a608..ebcca16d7 100644 --- a/src/registrar/assets/js/get-gov-admin.js +++ b/src/registrar/assets/js/get-gov-admin.js @@ -91,7 +91,6 @@ function openInNewTab(el, removeAttribute = false){ } // Update previousValue if another option is selected and confirmed - previousValue = this.value; console.log(`This is the previous val NOW: ${previousValue}`) }); @@ -102,11 +101,6 @@ function openInNewTab(el, removeAttribute = false){ } } - // Adds event listeners on the confirm and cancel modal buttons - function handleModalButtons(){ - - } - displayModalOnDropdownClick(); })(); diff --git a/src/registrar/templates/django/admin/domain_application_change_form.html b/src/registrar/templates/django/admin/domain_application_change_form.html index f6380fb82..6e6ab3723 100644 --- a/src/registrar/templates/django/admin/domain_application_change_form.html +++ b/src/registrar/templates/django/admin/domain_application_change_form.html @@ -39,9 +39,9 @@ When a domain request is in ineligible status, the registrant's permissions within the registrar are restricted as follows:

    -
  • They cannot edit the ineligible request or any other pending requests.
  • -
  • They cannot manage any of their approved domains.
  • -
  • They cannot initiate a new domain request.
  • +
  • They cannot edit the ineligible request or any other pending requests.
  • +
  • They cannot manage any of their approved domains.
  • +
  • They cannot initiate a new domain request.

The restrictions will not take effect until you “save” the changes for this domain request. diff --git a/src/registrar/templates/django/admin/domain_change_form.html b/src/registrar/templates/django/admin/domain_change_form.html index 61007c3d4..393983e32 100644 --- a/src/registrar/templates/django/admin/domain_change_form.html +++ b/src/registrar/templates/django/admin/domain_change_form.html @@ -133,9 +133,9 @@ When a domain is on hold:

    -
  • The domain and its subdomains won’t resolve in DNS. Any infrastructure (like websites) will go offline.
  • -
  • The domain will still appear in the registrar / admin.
  • -
  • Domain managers won’t be able to edit the domain.
  • +
  • The domain and its subdomains won’t resolve in DNS. Any infrastructure (like websites) will go offline.
  • +
  • The domain will still appear in the registrar / admin.
  • +
  • Domain managers won’t be able to edit the domain.

This action can be reversed, if needed. From 0ef72ad016a975f73922286723e0c5f0ebd3bd29 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 29 Feb 2024 12:05:19 -0700 Subject: [PATCH 06/17] Add custom desc on delete button --- src/registrar/admin.py | 12 +++++++++++ src/registrar/assets/js/get-gov-admin.js | 1 - .../admin/domain_application_change_form.html | 4 ++-- .../django/admin/domain_change_form.html | 4 ++-- .../admin/domain_delete_confirmation.html | 21 +++++++++++++++++++ 5 files changed, 37 insertions(+), 5 deletions(-) create mode 100644 src/registrar/templates/django/admin/domain_delete_confirmation.html diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 942ae6162..fd6ce45a7 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -1339,6 +1339,18 @@ class DomainAdmin(ListHeaderAdmin): # Table ordering ordering = ["name"] + def delete_view(self, request, object_id, extra_context=None): + """ + Custom delete_view to perform additional actions or customize the template. + """ + + # Set the delete template to a custom one + self.delete_confirmation_template = "django/admin/domain_delete_confirmation.html" + + response = super().delete_view(request, object_id, extra_context=extra_context) + + return response + def changeform_view(self, request, object_id=None, form_url="", extra_context=None): """Custom changeform implementation to pass in context information""" if extra_context is None: diff --git a/src/registrar/assets/js/get-gov-admin.js b/src/registrar/assets/js/get-gov-admin.js index ebcca16d7..8ecf2cbee 100644 --- a/src/registrar/assets/js/get-gov-admin.js +++ b/src/registrar/assets/js/get-gov-admin.js @@ -76,7 +76,6 @@ function openInNewTab(el, removeAttribute = false){ // it will not be affected by the createPhantomModalFormButtons() function. let cancelButton = document.querySelector('button[name="_cancel_application_ineligible"]'); if (cancelButton){ - console.log(`This is the previous val: ${previousValue}`) cancelButton.addEventListener('click', function() { // Revert the dropdown to its previous value statusDropdown.value = previousValue; diff --git a/src/registrar/templates/django/admin/domain_application_change_form.html b/src/registrar/templates/django/admin/domain_application_change_form.html index 6e6ab3723..f0e4cfe4f 100644 --- a/src/registrar/templates/django/admin/domain_application_change_form.html +++ b/src/registrar/templates/django/admin/domain_application_change_form.html @@ -48,10 +48,10 @@ This action can be reversed, if needed.

+ Domain: {{ original.requested_domain.name }} {# Acts as a
#}

- Domain: {{ original.name }} - New status: {{ original.State }} + New status: {{ original.ApplicationStatus.INELIGIBLE|capfirst }}

diff --git a/src/registrar/templates/django/admin/domain_change_form.html b/src/registrar/templates/django/admin/domain_change_form.html index 393983e32..818522c8d 100644 --- a/src/registrar/templates/django/admin/domain_change_form.html +++ b/src/registrar/templates/django/admin/domain_change_form.html @@ -144,7 +144,7 @@ Domain: {{ original.name }} {# Acts as a
#}
- New status: {{ original.State.ON_HOLD }} + New status: {{ original.State.ON_HOLD|capfirst }}

@@ -211,7 +211,7 @@ Domain: {{ original.name }} {# Acts as a
#}
- New status: {{ original.State.DELETED }} + New status: {{ original.State.DELETED|capfirst }}

diff --git a/src/registrar/templates/django/admin/domain_delete_confirmation.html b/src/registrar/templates/django/admin/domain_delete_confirmation.html new file mode 100644 index 000000000..793a28c4c --- /dev/null +++ b/src/registrar/templates/django/admin/domain_delete_confirmation.html @@ -0,0 +1,21 @@ +{% extends 'admin/delete_confirmation.html' %} +{% load i18n static %} + +{% block content %} +{# TODO modify the "Are you sure?" to the text content below.. #} +{% comment %} +

Are you sure you want to remove this domain from the registry?

+{% endcomment %} +

Description

+

When a domain is removed from the registry:

+ + + +

This action cannot be undone.

+ +{{ block.super }} +{% endblock %} From 8408ea0f68452a7c4840991ca6195c39bf5945f1 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 1 Mar 2024 14:37:20 -0700 Subject: [PATCH 07/17] Add some test cases --- src/registrar/tests/test_admin.py | 143 +++++++++++++++++++++++++++++- 1 file changed, 142 insertions(+), 1 deletion(-) diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index d76f12f35..3c5861ee1 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -230,6 +230,20 @@ class TestDomainAdmin(MockEppLib, WebTest): ) mock_add_message.assert_has_calls([expected_call], 1) + def test_custom_delete_confirmation_page(self): + """Tests if we override the delete confirmation page for custom content""" + # Create a ready domain with a preset expiration date + domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY) + + domain_change_page = self.app.get(reverse("admin:registrar_domain_change", args=[domain.pk])) + + self.assertContains(domain_change_page, "fake.gov") + # click the "Manage" link + confirmation_page = domain_change_page.click("Delete", index=0) + + content_slice = "

When a domain is removed from the registry:

" + self.assertContains(confirmation_page, content_slice) + def test_short_org_name_in_domains_list(self): """ Make sure the short name is displaying in admin on the list page @@ -309,6 +323,17 @@ class TestDomainAdmin(MockEppLib, WebTest): self.assertEqual(response.status_code, 200) self.assertContains(response, domain.name) self.assertContains(response, "Remove from registry") + + # The contents of the modal should exist before and after the post. + # Check for the header + self.assertContains(response, "Are you sure you want to remove this domain from the registry?") + + # Check for some of its body + self.assertContains(response, "When a domain is removed from the registry:") + + # Check for some of the button content + self.assertContains(response, "Yes, remove from registry") + # Test the info dialog request = self.factory.post( "/admin/registrar/domain/{}/change/".format(domain.pk), @@ -325,8 +350,60 @@ class TestDomainAdmin(MockEppLib, WebTest): extra_tags="", fail_silently=False, ) + + # The modal should still exist + self.assertContains(response, "Are you sure you want to remove this domain from the registry?") + self.assertContains(response, "When a domain is removed from the registry:") + self.assertContains(response, "Yes, remove from registry") + self.assertEqual(domain.state, Domain.State.DELETED) + def test_on_hold_is_successful_web_test(self): + """ + Scenario: Domain on_hold is successful through webtest + """ + with less_console_noise(): + domain = create_ready_domain() + + response = self.app.get(reverse("admin:registrar_domain_change", args=[domain.pk])) + + # Check the contents of the modal + # Check for the header + self.assertContains(response, "Are you sure you want to place this domain on hold?") + + # Check for some of its body + self.assertContains(response, "When a domain is on hold:") + + # Check for some of the button content + self.assertContains(response, "Yes, place hold") + + # Grab the form to submit + form = response.forms["domain_form"] + + # Submit the form + response = form.submit("_place_client_hold") + + # Follow the response + response = response.follow() + + self.assertEqual(response.status_code, 200) + self.assertContains(response, domain.name) + self.assertContains(response, "Remove hold") + + # The modal should still exist + # Check for the header + self.assertContains(response, "Are you sure you want to place this domain on hold?") + + # Check for some of its body + self.assertContains(response, "When a domain is on hold:") + + # Check for some of the button content + self.assertContains(response, "Yes, place hold") + + # Web test has issues grabbing up to date data from the db, so we can test + # the returned view instead + self.assertContains(response, '
On hold
') + def test_deletion_ready_fsm_failure(self): """ Scenario: Domain deletion is unsuccessful @@ -440,6 +517,7 @@ class TestDomainAdmin(MockEppLib, WebTest): class TestDomainApplicationAdminForm(TestCase): + def setUp(self): # Create a test application with an initial state of started self.application = completed_application() @@ -1101,7 +1179,10 @@ class TestDomainApplicationAdmin(MockEppLib): application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW) # Create a mock request - request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk)) + request = self.factory.post( + "/admin/registrar/domainapplication/{}/change/".format(application.pk), + follow=True + ) with boto3_mocking.clients.handler_for("sesv2", self.mock_client): # Modify the application's property @@ -1113,6 +1194,66 @@ class TestDomainApplicationAdmin(MockEppLib): # Test that approved domain exists and equals requested domain self.assertEqual(application.creator.status, "restricted") + def test_user_sets_restricted_status_modal(self): + """Tests the modal for when a user sets the status to restricted""" + with less_console_noise(): + # make sure there is no user with this email + EMAIL = "mayor@igorville.gov" + User.objects.filter(email=EMAIL).delete() + + # Create a sample application + application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW) + + p = "userpass" + self.client.login(username="staffuser", password=p) + response = self.client.get( + "/admin/registrar/domainapplication/{}/change/".format(application.pk), + follow=True, + ) + + self.assertEqual(response.status_code, 200) + self.assertContains(response, application.requested_domain.name) + + + # Check that the modal has the right content + # Check for the header + self.assertContains(response, "Are you sure you want to select ineligible status?") + + # Check for some of its body + self.assertContains(response, "When a domain request is in ineligible status") + + # Check for some of the button content + self.assertContains(response, "Yes, select ineligible status") + + # Create a mock request + request = self.factory.post( + "/admin/registrar/domainapplication/{}/change/".format(application.pk), + follow=True + ) + with boto3_mocking.clients.handler_for("sesv2", self.mock_client): + # Modify the application's property + application.status = DomainApplication.ApplicationStatus.INELIGIBLE + + # Use the model admin's save_model method + self.admin.save_model(request, application, form=None, change=True) + + # Test that approved domain exists and equals requested domain + self.assertEqual(application.creator.status, "restricted") + + # 'Get' to the application again + response = self.client.get( + "/admin/registrar/domainapplication/{}/change/".format(application.pk), + follow=True, + ) + + self.assertEqual(response.status_code, 200) + self.assertContains(response, application.requested_domain.name) + + # The modal should be unchanged + self.assertContains(response, "Are you sure you want to select ineligible status?") + self.assertContains(response, "When a domain request is in ineligible status") + self.assertContains(response, "Yes, select ineligible status") + def test_readonly_when_restricted_creator(self): with less_console_noise(): application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW) From 73673abae1484b84cdf1bd3e3f34ad88bc8c678a Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 4 Mar 2024 08:37:52 -0700 Subject: [PATCH 08/17] Minor JS refactor --- src/registrar/assets/js/get-gov-admin.js | 61 +++++++++++++----------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/src/registrar/assets/js/get-gov-admin.js b/src/registrar/assets/js/get-gov-admin.js index 8ecf2cbee..e632903bb 100644 --- a/src/registrar/assets/js/get-gov-admin.js +++ b/src/registrar/assets/js/get-gov-admin.js @@ -61,46 +61,51 @@ function openInNewTab(el, removeAttribute = false){ * This intentionally does not interact with createPhantomModalFormButtons() */ (function (){ - function displayModalOnDropdownClick(){ - // Grab the invisible element that will hook to the modal. - // This doesn't technically need to be done with one, but this is simpler to manage. - let linkClickedDisplaysModal = document.getElementById("invisible-ineligible-modal-toggler") - let statusDropdown = document.getElementById("id_status") + function displayModalOnDropdownClick(linkClickedDisplaysModal, statusDropdown, cancelButton, valueToCheck){ // If these exist all at the same time, we're on the right page if (linkClickedDisplaysModal && statusDropdown && statusDropdown.value){ - // Store the previous value in the event the user cancels. - // We only need to do this if we're on the correct page. - let previousValue = statusDropdown.value; - // Because the modal button does not have the class "dja-form-placeholder", - // it will not be affected by the createPhantomModalFormButtons() function. - let cancelButton = document.querySelector('button[name="_cancel_application_ineligible"]'); + if (cancelButton){ + // Store the previous value in the event the user cancels. + // We only need to do this if cancel button is specified. + let previousValue = statusDropdown.value; cancelButton.addEventListener('click', function() { // Revert the dropdown to its previous value statusDropdown.value = previousValue; }); - - // Add a change event listener to the dropdown. - statusDropdown.addEventListener('change', function() { - // Check if "Ineligible" is selected - if (this.value && this.value.toLowerCase() === "ineligible") { - // Display the modal. - linkClickedDisplaysModal.click() - } - - // Update previousValue if another option is selected and confirmed - console.log(`This is the previous val NOW: ${previousValue}`) - }); - - } else{ - console.error("displayModalOnDropdownClick() -> No cancel button defined.") + }else { + console.log("displayModalOnDropdownClick() -> Cancel button was null") } - + + // Add a change event listener to the dropdown. + statusDropdown.addEventListener('change', function() { + // Check if "Ineligible" is selected + if (this.value && this.value.toLowerCase() === valueToCheck) { + // Display the modal. + linkClickedDisplaysModal.click() + } + }); + }else{ + console.error("displayModalOnDropdownClick() -> Some inputs are null") } } - displayModalOnDropdownClick(); + // When the status dropdown is clicked and is set to "ineligible", toggle a confirmation dropdown. + function hookModalToIneligibleStatus(){ + // Grab the invisible element that will hook to the modal. + // This doesn't technically need to be done with one, but this is simpler to manage. + let modalButton = document.getElementById("invisible-ineligible-modal-toggler") + let statusDropdown = document.getElementById("id_status") + + // Because the modal button does not have the class "dja-form-placeholder", + // it will not be affected by the createPhantomModalFormButtons() function. + let cancelButton = document.querySelector('button[name="_cancel_application_ineligible"]'); + let valueToCheck = "ineligible" + displayModalOnDropdownClick(modalButton, statusDropdown, cancelButton, valueToCheck); + } + + hookModalToIneligibleStatus() })(); /** An IIFE for pages in DjangoAdmin which may need custom JS implementation. From ac1886a2b5db7e3469c142c6ba7e545440f7246f Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 4 Mar 2024 10:28:44 -0700 Subject: [PATCH 09/17] Override for table view --- src/registrar/admin.py | 4 +++- src/registrar/assets/js/get-gov-admin.js | 2 -- .../domain_delete_selected_confirmation.html | 21 +++++++++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 src/registrar/templates/django/admin/domain_delete_selected_confirmation.html diff --git a/src/registrar/admin.py b/src/registrar/admin.py index fd6ce45a7..2ffc29cda 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -1339,6 +1339,9 @@ class DomainAdmin(ListHeaderAdmin): # Table ordering ordering = ["name"] + # Override for the delete confirmation page on the domain table (bulk delete action) + delete_selected_confirmation_template = "django/admin/domain_delete_selected_confirmation.html" + def delete_view(self, request, object_id, extra_context=None): """ Custom delete_view to perform additional actions or customize the template. @@ -1346,7 +1349,6 @@ class DomainAdmin(ListHeaderAdmin): # Set the delete template to a custom one self.delete_confirmation_template = "django/admin/domain_delete_confirmation.html" - response = super().delete_view(request, object_id, extra_context=extra_context) return response diff --git a/src/registrar/assets/js/get-gov-admin.js b/src/registrar/assets/js/get-gov-admin.js index e632903bb..4ed1a0d28 100644 --- a/src/registrar/assets/js/get-gov-admin.js +++ b/src/registrar/assets/js/get-gov-admin.js @@ -86,8 +86,6 @@ function openInNewTab(el, removeAttribute = false){ linkClickedDisplaysModal.click() } }); - }else{ - console.error("displayModalOnDropdownClick() -> Some inputs are null") } } diff --git a/src/registrar/templates/django/admin/domain_delete_selected_confirmation.html b/src/registrar/templates/django/admin/domain_delete_selected_confirmation.html new file mode 100644 index 000000000..b8d9eb25c --- /dev/null +++ b/src/registrar/templates/django/admin/domain_delete_selected_confirmation.html @@ -0,0 +1,21 @@ +{% extends 'admin/delete_selected_confirmation.html' %} +{% load i18n static %} + +{% block content %} +{# TODO modify the "Are you sure?" to the text content below.. #} +{% comment %} +

Are you sure you want to remove this domain from the registry?

+{% endcomment %} +

Description

+

When a domain is removed from the registry:

+ + + +

This action cannot be undone.

+ +{{ block.super }} +{% endblock %} \ No newline at end of file From 27cd468bcafb303590546909316272bd2dc718fb Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 4 Mar 2024 11:33:25 -0700 Subject: [PATCH 10/17] Add better confirmation messages --- src/registrar/assets/sass/_theme/_admin.scss | 9 +++++ .../admin/domain_delete_confirmation.html | 38 ++++++++++-------- .../domain_delete_selected_confirmation.html | 39 +++++++++++-------- 3 files changed, 54 insertions(+), 32 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_admin.scss b/src/registrar/assets/sass/_theme/_admin.scss index 46e4a10d4..9f6db0c46 100644 --- a/src/registrar/assets/sass/_theme/_admin.scss +++ b/src/registrar/assets/sass/_theme/_admin.scss @@ -311,3 +311,12 @@ input.admin-confirm-button { margin-top: 0; max-width: 68ex; } + +.django-admin-custom-bullets { + // Set list-style-type to inherit without modifying text size + list-style-type: inherit; +} + +.usa-summary-box__dhs-color { + color: $dhs-blue-70; +} diff --git a/src/registrar/templates/django/admin/domain_delete_confirmation.html b/src/registrar/templates/django/admin/domain_delete_confirmation.html index 793a28c4c..5a9bef5b0 100644 --- a/src/registrar/templates/django/admin/domain_delete_confirmation.html +++ b/src/registrar/templates/django/admin/domain_delete_confirmation.html @@ -2,20 +2,26 @@ {% load i18n static %} {% block content %} -{# TODO modify the "Are you sure?" to the text content below.. #} -{% comment %} -

Are you sure you want to remove this domain from the registry?

-{% endcomment %} -

Description

-

When a domain is removed from the registry:

- - - -

This action cannot be undone.

- -{{ block.super }} +
+
+

+ When a domain is deleted: +

+
+
    +
  • The domain will no longer appear in the registrar / admin.
  • +
  • It will be removed from the registry.
  • +
  • The domain and its subdomains won’t resolve in DNS.
  • +
  • Any infrastructure (like websites) will go offline.
  • +
+

You should probably remove this domain from the registry instead of deleting it.

+

This action cannot be undone.

+
+
+
+ {{ block.super }} {% endblock %} diff --git a/src/registrar/templates/django/admin/domain_delete_selected_confirmation.html b/src/registrar/templates/django/admin/domain_delete_selected_confirmation.html index b8d9eb25c..61eb20f85 100644 --- a/src/registrar/templates/django/admin/domain_delete_selected_confirmation.html +++ b/src/registrar/templates/django/admin/domain_delete_selected_confirmation.html @@ -1,21 +1,28 @@ {% extends 'admin/delete_selected_confirmation.html' %} {% load i18n static %} + {% block content %} -{# TODO modify the "Are you sure?" to the text content below.. #} -{% comment %} -

Are you sure you want to remove this domain from the registry?

-{% endcomment %} -

Description

-

When a domain is removed from the registry:

- - - -

This action cannot be undone.

- -{{ block.super }} +
+
+

+ When a domain is deleted: +

+
+
    +
  • The domain will no longer appear in the registrar / admin.
  • +
  • It will be removed from the registry.
  • +
  • The domain and its subdomains won’t resolve in DNS.
  • +
  • Any infrastructure (like websites) will go offline.
  • +
+

You should probably remove these domains from the registry instead of deleting them.

+

This action cannot be undone.

+
+
+
+ {{ block.super }} {% endblock %} \ No newline at end of file From d8bb86fe641e87af2eaf92209cfd2b6975f2c8c8 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 4 Mar 2024 11:48:57 -0700 Subject: [PATCH 11/17] Update domain_delete_selected_confirmation.html --- .../django/admin/domain_delete_selected_confirmation.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/templates/django/admin/domain_delete_selected_confirmation.html b/src/registrar/templates/django/admin/domain_delete_selected_confirmation.html index 61eb20f85..3e0a32a4d 100644 --- a/src/registrar/templates/django/admin/domain_delete_selected_confirmation.html +++ b/src/registrar/templates/django/admin/domain_delete_selected_confirmation.html @@ -19,7 +19,7 @@
  • The domain and its subdomains won’t resolve in DNS.
  • Any infrastructure (like websites) will go offline.
  • -

    You should probably remove these domains from the registry instead of deleting them.

    +

    You should probably remove these domains from the registry instead.

    This action cannot be undone.

    From 7e4dc38b40e7ce92cf9ed426f49a7e4c6ebc345d Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 4 Mar 2024 11:50:03 -0700 Subject: [PATCH 12/17] Change bullet list style --- src/registrar/assets/sass/_theme/_admin.scss | 2 +- .../templates/django/admin/domain_delete_confirmation.html | 2 +- .../django/admin/domain_delete_selected_confirmation.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_admin.scss b/src/registrar/assets/sass/_theme/_admin.scss index 9f6db0c46..88675cb32 100644 --- a/src/registrar/assets/sass/_theme/_admin.scss +++ b/src/registrar/assets/sass/_theme/_admin.scss @@ -312,7 +312,7 @@ input.admin-confirm-button { max-width: 68ex; } -.django-admin-custom-bullets { +.django-admin-custom-bullets ul > li { // Set list-style-type to inherit without modifying text size list-style-type: inherit; } diff --git a/src/registrar/templates/django/admin/domain_delete_confirmation.html b/src/registrar/templates/django/admin/domain_delete_confirmation.html index 5a9bef5b0..2836c32f7 100644 --- a/src/registrar/templates/django/admin/domain_delete_confirmation.html +++ b/src/registrar/templates/django/admin/domain_delete_confirmation.html @@ -11,7 +11,7 @@

    When a domain is deleted:

    -
    +