mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-06-11 23:14:49 +02:00
merge main
This commit is contained in:
commit
4336f85254
9 changed files with 577 additions and 23 deletions
|
@ -994,6 +994,8 @@ class DomainRequestAdmin(ListHeaderAdmin):
|
||||||
if self.value() == "0":
|
if self.value() == "0":
|
||||||
return queryset.filter(Q(is_election_board=False) | Q(is_election_board=None))
|
return queryset.filter(Q(is_election_board=False) | Q(is_election_board=None))
|
||||||
|
|
||||||
|
change_form_template = "django/admin/domain_application_change_form.html"
|
||||||
|
|
||||||
# Columns
|
# Columns
|
||||||
list_display = [
|
list_display = [
|
||||||
"requested_domain",
|
"requested_domain",
|
||||||
|
@ -1467,6 +1469,20 @@ class DomainAdmin(ListHeaderAdmin):
|
||||||
# Table ordering
|
# Table ordering
|
||||||
ordering = ["name"]
|
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.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 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):
|
def changeform_view(self, request, object_id=None, form_url="", extra_context=None):
|
||||||
"""Custom changeform implementation to pass in context information"""
|
"""Custom changeform implementation to pass in context information"""
|
||||||
if extra_context is None:
|
if extra_context is None:
|
||||||
|
@ -1757,9 +1773,6 @@ class VerifiedByStaffAdmin(ListHeaderAdmin):
|
||||||
list_display = ("email", "requestor", "truncated_notes", "created_at")
|
list_display = ("email", "requestor", "truncated_notes", "created_at")
|
||||||
search_fields = ["email"]
|
search_fields = ["email"]
|
||||||
search_help_text = "Search by email."
|
search_help_text = "Search by email."
|
||||||
list_filter = [
|
|
||||||
"requestor",
|
|
||||||
]
|
|
||||||
readonly_fields = [
|
readonly_fields = [
|
||||||
"requestor",
|
"requestor",
|
||||||
]
|
]
|
||||||
|
|
|
@ -29,20 +29,26 @@ function openInNewTab(el, removeAttribute = false){
|
||||||
*/
|
*/
|
||||||
(function (){
|
(function (){
|
||||||
function createPhantomModalFormButtons(){
|
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")
|
form = document.querySelector("form")
|
||||||
submitButtons.forEach((button) => {
|
submitButtons.forEach((button) => {
|
||||||
|
|
||||||
let input = document.createElement("input");
|
let input = document.createElement("input");
|
||||||
input.type = "submit";
|
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"
|
input.style.display = "none"
|
||||||
|
|
||||||
// Add the hidden input to the form
|
// Add the hidden input to the form
|
||||||
form.appendChild(input);
|
form.appendChild(input);
|
||||||
button.addEventListener("click", () => {
|
button.addEventListener("click", () => {
|
||||||
console.log("clicking")
|
|
||||||
input.click();
|
input.click();
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -50,6 +56,61 @@ function openInNewTab(el, removeAttribute = false){
|
||||||
|
|
||||||
createPhantomModalFormButtons();
|
createPhantomModalFormButtons();
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
/** An IIFE for DomainRequest to hook a modal to a dropdown option.
|
||||||
|
* This intentionally does not interact with createPhantomModalFormButtons()
|
||||||
|
*/
|
||||||
|
(function (){
|
||||||
|
function displayModalOnDropdownClick(linkClickedDisplaysModal, statusDropdown, actionButton, valueToCheck){
|
||||||
|
|
||||||
|
// If these exist all at the same time, we're on the right page
|
||||||
|
if (linkClickedDisplaysModal && statusDropdown && statusDropdown.value){
|
||||||
|
|
||||||
|
// Set the previous value in the event the user cancels.
|
||||||
|
let previousValue = statusDropdown.value;
|
||||||
|
if (actionButton){
|
||||||
|
|
||||||
|
// Otherwise, if the confirmation buttion is pressed, set it to that
|
||||||
|
actionButton.addEventListener('click', function() {
|
||||||
|
// Revert the dropdown to its previous value
|
||||||
|
statusDropdown.value = valueToCheck;
|
||||||
|
});
|
||||||
|
}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) {
|
||||||
|
// Set the old value in the event the user cancels,
|
||||||
|
// or otherwise exists the dropdown.
|
||||||
|
statusDropdown.value = previousValue
|
||||||
|
|
||||||
|
// Display the modal.
|
||||||
|
linkClickedDisplaysModal.click()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 actionButton = document.querySelector('button[name="_set_domain_request_ineligible"]');
|
||||||
|
let valueToCheck = "ineligible"
|
||||||
|
displayModalOnDropdownClick(modalButton, statusDropdown, actionButton, valueToCheck);
|
||||||
|
}
|
||||||
|
|
||||||
|
hookModalToIneligibleStatus()
|
||||||
|
})();
|
||||||
|
|
||||||
/** An IIFE for pages in DjangoAdmin which may need custom JS implementation.
|
/** An IIFE for pages in DjangoAdmin which may need custom JS implementation.
|
||||||
* Currently only appends target="_blank" to the domain_form object,
|
* Currently only appends target="_blank" to the domain_form object,
|
||||||
* but this can be expanded.
|
* but this can be expanded.
|
||||||
|
|
|
@ -332,4 +332,15 @@ input.admin-confirm-button {
|
||||||
border: solid 1px var(--darkened-bg);
|
border: solid 1px var(--darkened-bg);
|
||||||
background: var(--darkened-bg);
|
background: var(--darkened-bg);
|
||||||
}
|
}
|
||||||
|
.django-admin-modal .usa-prose ul > li {
|
||||||
|
list-style-type: inherit;
|
||||||
|
// Styling based off of the <p> styling in django admin
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-bottom: 0;
|
||||||
|
margin-top: 0;
|
||||||
|
max-width: 68ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usa-summary-box__dhs-color {
|
||||||
|
color: $dhs-blue-70;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
{% extends 'admin/change_form.html' %}
|
||||||
|
{% load i18n static %}
|
||||||
|
|
||||||
|
{% block field_sets %}
|
||||||
|
{# Create an invisible <a> tag so that we can use a click event to toggle the modal. #}
|
||||||
|
<a id="invisible-ineligible-modal-toggler" class="display-none" href="#toggle-set-ineligible" aria-controls="toggle-set-ineligible" data-open-modal></a>
|
||||||
|
{{ block.super }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% 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 #}
|
||||||
|
<div
|
||||||
|
class="usa-modal"
|
||||||
|
id="toggle-set-ineligible"
|
||||||
|
aria-labelledby="Are you sure you want to select ineligible status?"
|
||||||
|
aria-describedby="This request will be marked as ineligible."
|
||||||
|
>
|
||||||
|
<div class="usa-modal__content">
|
||||||
|
<div class="usa-modal__main">
|
||||||
|
<h2 class="usa-modal__heading" id="modal-1-heading">
|
||||||
|
Are you sure you want to select ineligible status?
|
||||||
|
</h2>
|
||||||
|
<div class="usa-prose">
|
||||||
|
<p>
|
||||||
|
When a domain request is in ineligible status, the registrant's permissions within the registrar are restricted as follows:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li class="font-body-sm">They cannot edit the ineligible request or any other pending requests.</li>
|
||||||
|
<li class="font-body-sm">They cannot manage any of their approved domains.</li>
|
||||||
|
<li class="font-body-sm">They cannot initiate a new domain request.</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
The restrictions will not take effect until you “save” the changes for this domain request.
|
||||||
|
This action can be reversed, if needed.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Domain: <b>{{ original.requested_domain.name }}</b>
|
||||||
|
{# Acts as a <br> #}
|
||||||
|
<div class="display-inline"></div>
|
||||||
|
New status: <b>{{ original.DomainRequestStatus.INELIGIBLE|capfirst }}</b>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="usa-modal__footer">
|
||||||
|
<ul class="usa-button-group">
|
||||||
|
<li class="usa-button-group__item">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="usa-button"
|
||||||
|
name="_set_domain_request_ineligible"
|
||||||
|
data-close-modal
|
||||||
|
>
|
||||||
|
Yes, select ineligible status
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="usa-button-group__item">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="usa-button usa-button--unstyled padding-105 text-center"
|
||||||
|
name="_cancel_domain_request_ineligible"
|
||||||
|
data-close-modal
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="usa-button usa-modal__close"
|
||||||
|
aria-label="Close this window"
|
||||||
|
data-close-modal
|
||||||
|
>
|
||||||
|
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img">
|
||||||
|
<use xlink:href="{%static 'img/sprite.svg'%}#close"></use>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ block.super }}
|
||||||
|
{% endblock %}
|
|
@ -11,18 +11,15 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="desktop:flex-align-self-end">
|
<div class="desktop:flex-align-self-end">
|
||||||
{% if original.state != original.State.DELETED %}
|
{% if original.state != original.State.DELETED %}
|
||||||
<a
|
<a class="text-middle" href="#toggle-extend-expiration-alert" aria-controls="toggle-extend-expiration-alert" data-open-modal>
|
||||||
class="text-middle"
|
|
||||||
href="#toggle-extend-expiration-alert"
|
|
||||||
aria-controls="toggle-extend-expiration-alert"
|
|
||||||
data-open-modal
|
|
||||||
>
|
|
||||||
Extend expiration date
|
Extend expiration date
|
||||||
</a>
|
</a>
|
||||||
<span class="margin-left-05 margin-right-05 text-middle"> | </span>
|
<span class="margin-left-05 margin-right-05 text-middle"> | </span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if original.state == original.State.READY %}
|
{% if original.state == original.State.READY %}
|
||||||
<input type="submit" value="Place hold" name="_place_client_hold" class="custom-link-button">
|
<a class="text-middle" href="#toggle-place-on-hold" aria-controls="toggle-place-on-hold" data-open-modal>
|
||||||
|
Place hold
|
||||||
|
</a>
|
||||||
{% elif original.state == original.State.ON_HOLD %}
|
{% elif original.state == original.State.ON_HOLD %}
|
||||||
<input type="submit" value="Remove hold" name="_remove_client_hold" class="custom-link-button">
|
<input type="submit" value="Remove hold" name="_remove_client_hold" class="custom-link-button">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -30,7 +27,9 @@
|
||||||
<span class="margin-left-05 margin-right-05 text-middle"> | </span>
|
<span class="margin-left-05 margin-right-05 text-middle"> | </span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if original.state != original.State.DELETED %}
|
{% if original.state != original.State.DELETED %}
|
||||||
<input type="submit" value="Remove from registry" name="_delete_domain" class="custom-link-button">
|
<a class="text-middle" href="#toggle-remove-from-registry" aria-controls="toggle-remove-from-registry" data-open-modal>
|
||||||
|
Remove from registry
|
||||||
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -52,8 +51,10 @@
|
||||||
In addition, the modal element MUST be placed low in the DOM. The script loads slower on DJA than on other portions
|
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.
|
of the application, so this means that it will briefly "populate", causing unintended visual effects.
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
|
|
||||||
|
{# Create a modal for the _extend_expiration_date button #}
|
||||||
<div
|
<div
|
||||||
class="usa-modal"
|
class="usa-modal django-admin-modal"
|
||||||
id="toggle-extend-expiration-alert"
|
id="toggle-extend-expiration-alert"
|
||||||
aria-labelledby="Are you sure you want to extend the expiration date?"
|
aria-labelledby="Are you sure you want to extend the expiration date?"
|
||||||
aria-describedby="This expiration date will be extended."
|
aria-describedby="This expiration date will be extended."
|
||||||
|
@ -78,7 +79,7 @@
|
||||||
{{test}}
|
{{test}}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="usa-modal__footer">
|
<div class="usa-modal__footer">
|
||||||
<ul class="usa-button-group">
|
<ul class="usa-button-group">
|
||||||
<li class="usa-button-group__item">
|
<li class="usa-button-group__item">
|
||||||
|
@ -114,5 +115,140 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{# Create a modal for the _on_hold button #}
|
||||||
|
<div
|
||||||
|
class="usa-modal django-admin-modal"
|
||||||
|
id="toggle-place-on-hold"
|
||||||
|
aria-labelledby="Are you sure you want to place this domain on hold?"
|
||||||
|
aria-describedby="This domain will be put on hold"
|
||||||
|
>
|
||||||
|
<div class="usa-modal__content">
|
||||||
|
<div class="usa-modal__main">
|
||||||
|
<h2 class="usa-modal__heading" id="modal-1-heading">
|
||||||
|
Are you sure you want to place this domain on hold?
|
||||||
|
</h2>
|
||||||
|
<div class="usa-prose">
|
||||||
|
<p>
|
||||||
|
When a domain is on hold:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li class="font-body-sm">The domain and its subdomains won’t resolve in DNS. Any infrastructure (like websites) will go offline.</li>
|
||||||
|
<li class="font-body-sm">The domain will still appear in the registrar / admin.</li>
|
||||||
|
<li class="font-body-sm">Domain managers won’t be able to edit the domain.</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
This action can be reversed, if needed.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Domain: <b>{{ original.name }}</b>
|
||||||
|
{# Acts as a <br> #}
|
||||||
|
<div class="display-inline"></div>
|
||||||
|
New status: <b>{{ original.State.ON_HOLD|capfirst }}</b>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="usa-modal__footer">
|
||||||
|
<ul class="usa-button-group">
|
||||||
|
<li class="usa-button-group__item">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="usa-button dja-form-placeholder"
|
||||||
|
name="_place_client_hold"
|
||||||
|
>
|
||||||
|
Yes, place hold
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="usa-button-group__item">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="usa-button usa-button--unstyled padding-105 text-center"
|
||||||
|
data-close-modal
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="usa-button usa-modal__close"
|
||||||
|
aria-label="Close this window"
|
||||||
|
data-close-modal
|
||||||
|
>
|
||||||
|
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img">
|
||||||
|
<use xlink:href="{%static 'img/sprite.svg'%}#close"></use>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{# Create a modal for the _remove_domain button #}
|
||||||
|
<div
|
||||||
|
class="usa-modal django-admin-modal"
|
||||||
|
id="toggle-remove-from-registry"
|
||||||
|
aria-labelledby="Are you sure you want to remove this domain from the registry?"
|
||||||
|
aria-describedby="This domain will be removed."
|
||||||
|
>
|
||||||
|
<div class="usa-modal__content">
|
||||||
|
<div class="usa-modal__main">
|
||||||
|
<h2 class="usa-modal__heading" id="modal-1-heading">
|
||||||
|
Are you sure you want to remove this domain from the registry?
|
||||||
|
</h2>
|
||||||
|
<div class="usa-prose">
|
||||||
|
<p>
|
||||||
|
When a domain is removed from the registry:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li class="font-body-sm">The domain and its subdomains won’t resolve in DNS. Any infrastructure (like websites) will go offline.</li>
|
||||||
|
<li class="font-body-sm">The domain will still appear in the registrar / admin.</li>
|
||||||
|
<li class="font-body-sm">Domain managers won’t be able to edit the domain.</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
This action cannot be undone.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Domain: <b>{{ original.name }}</b>
|
||||||
|
{# Acts as a <br> #}
|
||||||
|
<div class="display-inline"></div>
|
||||||
|
New status: <b>{{ original.State.DELETED|capfirst }}</b>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="usa-modal__footer">
|
||||||
|
<ul class="usa-button-group">
|
||||||
|
<li class="usa-button-group__item">
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
class="usa-button dja-form-placeholder"
|
||||||
|
name="_delete_domain"
|
||||||
|
>
|
||||||
|
Yes, remove from registry
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
<li class="usa-button-group__item">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="usa-button usa-button--unstyled padding-105 text-center"
|
||||||
|
data-close-modal
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="usa-button usa-modal__close"
|
||||||
|
aria-label="Close this window"
|
||||||
|
data-close-modal
|
||||||
|
>
|
||||||
|
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img">
|
||||||
|
<use xlink:href="{%static 'img/sprite.svg'%}#close"></use>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
{% extends 'admin/delete_confirmation.html' %}
|
||||||
|
{% load i18n static %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div
|
||||||
|
class="usa-summary-box width-tablet"
|
||||||
|
role="region"
|
||||||
|
aria-labelledby="summary-box-description"
|
||||||
|
>
|
||||||
|
<div class="usa-summary-box__body">
|
||||||
|
<h3 class="usa-summary-box__heading usa-summary-box__dhs-color" id="summary-box-description">
|
||||||
|
When a domain is deleted:
|
||||||
|
</h3>
|
||||||
|
<div class="usa-summary-box__text">
|
||||||
|
<ul class="usa-list">
|
||||||
|
<li>The domain will no longer appear in the registrar / admin.</li>
|
||||||
|
<li>It will be removed from the registry. </li>
|
||||||
|
<li>The domain and its subdomains won’t resolve in DNS.</li>
|
||||||
|
<li>Any infrastructure (like websites) will go offline.</li>
|
||||||
|
</ul>
|
||||||
|
<p>You should probably remove this domain from the registry instead of deleting it.</p>
|
||||||
|
<p><strong>This action cannot be undone.</strong></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ block.super }}
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,28 @@
|
||||||
|
{% extends 'admin/delete_selected_confirmation.html' %}
|
||||||
|
{% load i18n static %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div
|
||||||
|
class="usa-summary-box width-tablet"
|
||||||
|
role="region"
|
||||||
|
aria-labelledby="summary-box-description"
|
||||||
|
>
|
||||||
|
<div class="usa-summary-box__body">
|
||||||
|
<h3 class="usa-summary-box__heading usa-summary-box__dhs-color" id="summary-box-description">
|
||||||
|
When a domain is deleted:
|
||||||
|
</h3>
|
||||||
|
<div class="usa-summary-box__text">
|
||||||
|
<ul class="usa-list">
|
||||||
|
<li>The domain will no longer appear in the registrar / admin.</li>
|
||||||
|
<li>It will be removed from the registry. </li>
|
||||||
|
<li>The domain and its subdomains won’t resolve in DNS.</li>
|
||||||
|
<li>Any infrastructure (like websites) will go offline.</li>
|
||||||
|
</ul>
|
||||||
|
<p>You should probably remove these domains from the registry instead.</p>
|
||||||
|
<p><strong>This action cannot be undone.</strong></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{ block.super }}
|
||||||
|
{% endblock %}
|
|
@ -99,7 +99,7 @@ def less_console_noise(output_stream=None):
|
||||||
class GenericTestHelper(TestCase):
|
class GenericTestHelper(TestCase):
|
||||||
"""A helper class that contains various helper functions for TestCases"""
|
"""A helper class that contains various helper functions for TestCases"""
|
||||||
|
|
||||||
def __init__(self, admin, model=None, url=None, user=None, factory=None, **kwargs):
|
def __init__(self, admin, model=None, url=None, user=None, factory=None, client=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Parameters:
|
Parameters:
|
||||||
admin (ModelAdmin): The Django ModelAdmin instance associated with the model.
|
admin (ModelAdmin): The Django ModelAdmin instance associated with the model.
|
||||||
|
@ -114,6 +114,7 @@ class GenericTestHelper(TestCase):
|
||||||
self.admin = admin
|
self.admin = admin
|
||||||
self.model = model
|
self.model = model
|
||||||
self.url = url
|
self.url = url
|
||||||
|
self.client = client
|
||||||
|
|
||||||
def assert_table_sorted(self, o_index, sort_fields):
|
def assert_table_sorted(self, o_index, sort_fields):
|
||||||
"""
|
"""
|
||||||
|
@ -149,9 +150,7 @@ class GenericTestHelper(TestCase):
|
||||||
dummy_request.user = self.user
|
dummy_request.user = self.user
|
||||||
|
|
||||||
# Mock a user request
|
# Mock a user request
|
||||||
middleware = SessionMiddleware(lambda req: req)
|
dummy_request = self._mock_user_request_for_factory(dummy_request)
|
||||||
middleware.process_request(dummy_request)
|
|
||||||
dummy_request.session.save()
|
|
||||||
|
|
||||||
expected_sort_order = list(self.model.objects.order_by(*sort_fields))
|
expected_sort_order = list(self.model.objects.order_by(*sort_fields))
|
||||||
|
|
||||||
|
@ -162,6 +161,27 @@ class GenericTestHelper(TestCase):
|
||||||
|
|
||||||
self.assertEqual(expected_sort_order, returned_sort_order)
|
self.assertEqual(expected_sort_order, returned_sort_order)
|
||||||
|
|
||||||
|
def _mock_user_request_for_factory(self, request):
|
||||||
|
"""Adds sessionmiddleware when using factory to associate session information"""
|
||||||
|
middleware = SessionMiddleware(lambda req: req)
|
||||||
|
middleware.process_request(request)
|
||||||
|
request.session.save()
|
||||||
|
return request
|
||||||
|
|
||||||
|
def get_table_delete_confirmation_page(self, selected_across: str, index: str):
|
||||||
|
"""
|
||||||
|
Grabs the response for the delete confirmation page (generated from the actions toolbar).
|
||||||
|
selected_across and index must both be numbers encoded as str, e.g. "0" rather than 0
|
||||||
|
"""
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
self.url,
|
||||||
|
{"action": "delete_selected", "select_across": selected_across, "index": index, "_selected_action": "23"},
|
||||||
|
follow=True,
|
||||||
|
)
|
||||||
|
print(f"what is the response? {response}")
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
class MockUserLogin:
|
class MockUserLogin:
|
||||||
def __init__(self, get_response):
|
def __init__(self, get_response):
|
||||||
|
|
|
@ -61,6 +61,16 @@ class TestDomainAdmin(MockEppLib, WebTest):
|
||||||
self.factory = RequestFactory()
|
self.factory = RequestFactory()
|
||||||
self.app.set_user(self.superuser.username)
|
self.app.set_user(self.superuser.username)
|
||||||
self.client.force_login(self.superuser)
|
self.client.force_login(self.superuser)
|
||||||
|
|
||||||
|
# Contains some test tools
|
||||||
|
self.test_helper = GenericTestHelper(
|
||||||
|
factory=self.factory,
|
||||||
|
user=self.superuser,
|
||||||
|
admin=self.admin,
|
||||||
|
url=reverse("admin:registrar_domain_changelist"),
|
||||||
|
model=Domain,
|
||||||
|
client=self.client,
|
||||||
|
)
|
||||||
super().setUp()
|
super().setUp()
|
||||||
|
|
||||||
@skip("TODO for another ticket. This test case is grabbing old db data.")
|
@skip("TODO for another ticket. This test case is grabbing old db data.")
|
||||||
|
@ -230,6 +240,35 @@ class TestDomainAdmin(MockEppLib, WebTest):
|
||||||
)
|
)
|
||||||
mock_add_message.assert_has_calls([expected_call], 1)
|
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 deleted:"
|
||||||
|
self.assertContains(confirmation_page, content_slice)
|
||||||
|
|
||||||
|
def test_custom_delete_confirmation_page_table(self):
|
||||||
|
"""Tests if we override the delete confirmation page for custom content on the table"""
|
||||||
|
# Create a ready domain
|
||||||
|
domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY)
|
||||||
|
|
||||||
|
# Get the index. The post expects the index to be encoded as a string
|
||||||
|
index = f"{domain.id}"
|
||||||
|
|
||||||
|
# Simulate selecting a single record, then clicking "Delete selected domains"
|
||||||
|
response = self.test_helper.get_table_delete_confirmation_page("0", index)
|
||||||
|
|
||||||
|
# Check that our content exists
|
||||||
|
content_slice = "When a domain is deleted:"
|
||||||
|
self.assertContains(response, content_slice)
|
||||||
|
|
||||||
def test_short_org_name_in_domains_list(self):
|
def test_short_org_name_in_domains_list(self):
|
||||||
"""
|
"""
|
||||||
Make sure the short name is displaying in admin on the list page
|
Make sure the short name is displaying in admin on the list page
|
||||||
|
@ -309,6 +348,17 @@ class TestDomainAdmin(MockEppLib, WebTest):
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertContains(response, domain.name)
|
self.assertContains(response, domain.name)
|
||||||
self.assertContains(response, "Remove from registry")
|
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
|
# Test the info dialog
|
||||||
request = self.factory.post(
|
request = self.factory.post(
|
||||||
"/admin/registrar/domain/{}/change/".format(domain.pk),
|
"/admin/registrar/domain/{}/change/".format(domain.pk),
|
||||||
|
@ -325,8 +375,60 @@ class TestDomainAdmin(MockEppLib, WebTest):
|
||||||
extra_tags="",
|
extra_tags="",
|
||||||
fail_silently=False,
|
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)
|
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, '<div class="readonly">On hold</div>')
|
||||||
|
|
||||||
def test_deletion_ready_fsm_failure(self):
|
def test_deletion_ready_fsm_failure(self):
|
||||||
"""
|
"""
|
||||||
Scenario: Domain deletion is unsuccessful
|
Scenario: Domain deletion is unsuccessful
|
||||||
|
@ -1101,7 +1203,9 @@ class TestDomainRequestAdmin(MockEppLib):
|
||||||
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
|
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
|
||||||
|
|
||||||
# Create a mock request
|
# Create a mock request
|
||||||
request = self.factory.post("/admin/registrar/domainrequest/{}/change/".format(domain_request.pk))
|
request = self.factory.post(
|
||||||
|
"/admin/registrar/domainrequest/{}/change/".format(domain_request.pk), follow=True
|
||||||
|
)
|
||||||
|
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
# Modify the domain request's property
|
# Modify the domain request's property
|
||||||
|
@ -1113,6 +1217,64 @@ class TestDomainRequestAdmin(MockEppLib):
|
||||||
# Test that approved domain exists and equals requested domain
|
# Test that approved domain exists and equals requested domain
|
||||||
self.assertEqual(domain_request.creator.status, "restricted")
|
self.assertEqual(domain_request.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 domain request
|
||||||
|
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
|
||||||
|
|
||||||
|
p = "userpass"
|
||||||
|
self.client.login(username="staffuser", password=p)
|
||||||
|
response = self.client.get(
|
||||||
|
"/admin/registrar/domainrequest/{}/change/".format(domain_request.pk),
|
||||||
|
follow=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, domain_request.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/domainrequest{}/change/".format(domain_request.pk), follow=True
|
||||||
|
)
|
||||||
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
|
# Modify the domain request's property
|
||||||
|
domain_request.status = DomainRequest.DomainRequestStatus.INELIGIBLE
|
||||||
|
|
||||||
|
# Use the model admin's save_model method
|
||||||
|
self.admin.save_model(request, domain_request, form=None, change=True)
|
||||||
|
|
||||||
|
# Test that approved domain exists and equals requested domain
|
||||||
|
self.assertEqual(domain_request.creator.status, "restricted")
|
||||||
|
|
||||||
|
# 'Get' to the domain request again
|
||||||
|
response = self.client.get(
|
||||||
|
"/admin/registrar/domainrequest/{}/change/".format(domain_request.pk),
|
||||||
|
follow=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, domain_request.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):
|
def test_readonly_when_restricted_creator(self):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
|
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue