Revert "resolved merge conflict"

This reverts commit 85be976ee4, reversing
changes made to bd018e14ab.
This commit is contained in:
CocoByte 2024-10-09 14:08:05 -06:00
parent 85be976ee4
commit d1f3f0efb1
No known key found for this signature in database
GPG key ID: BBFAA2526384C97F
13 changed files with 129 additions and 1192 deletions

View file

@ -1976,30 +1976,18 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
# If the status is not mapped properly, saving could cause # If the status is not mapped properly, saving could cause
# weird issues down the line. Instead, we should block this. # weird issues down the line. Instead, we should block this.
# NEEDS A UNIT TEST
should_proceed = False should_proceed = False
return (obj, should_proceed) return should_proceed
obj_is_not_approved = obj.status != models.DomainRequest.DomainRequestStatus.APPROVED request_is_not_approved = obj.status != models.DomainRequest.DomainRequestStatus.APPROVED
if obj_is_not_approved and not obj.domain_is_not_active(): if request_is_not_approved and not obj.domain_is_not_active():
# REDUNDANT CHECK / ERROR SCREEN AVOIDANCE: # If an admin tried to set an approved domain request to
# This action (moving a request from approved to # another status and the related domain is already
# another status) when the domain is already active (READY), # active, shortcut the action and throw a friendly
# would still not go through even without this check as the rules are # error message. This action would still not go through
# duplicated in the model and the error is raised from the model. # shortcut or not as the rules are duplicated on the model,
# This avoids an ugly Django error screen. # but the error would be an ugly Django error screen.
error_message = "This action is not permitted. The domain is already active." error_message = "This action is not permitted. The domain is already active."
elif (
original_obj.status != models.DomainRequest.DomainRequestStatus.APPROVED
and obj.status == models.DomainRequest.DomainRequestStatus.APPROVED
and original_obj.requested_domain is not None
and Domain.objects.filter(name=original_obj.requested_domain.name).exists()
):
# REDUNDANT CHECK:
# This action (approving a request when the domain exists)
# would still not go through even without this check as the rules are
# duplicated in the model and the error is raised from the model.
error_message = FSMDomainRequestError.get_error_message(FSMErrorCodes.APPROVE_DOMAIN_IN_USE)
elif obj.status == models.DomainRequest.DomainRequestStatus.REJECTED and not obj.rejection_reason: elif obj.status == models.DomainRequest.DomainRequestStatus.REJECTED and not obj.rejection_reason:
# This condition should never be triggered. # This condition should never be triggered.
# The opposite of this condition is acceptable (rejected -> other status and rejection_reason) # The opposite of this condition is acceptable (rejected -> other status and rejection_reason)

View file

@ -515,14 +515,10 @@ document.addEventListener('DOMContentLoaded', function() {
const formLabel = document.querySelector('label[for="id_action_needed_reason_email"]'); const formLabel = document.querySelector('label[for="id_action_needed_reason_email"]');
let lastSentEmailContent = document.getElementById("last-sent-email-content"); let lastSentEmailContent = document.getElementById("last-sent-email-content");
const initialDropdownValue = dropdown ? dropdown.value : null; const initialDropdownValue = dropdown ? dropdown.value : null;
let initialEmailValue; const initialEmailValue = textarea.value;
if (textarea)
initialEmailValue = textarea.value
// We will use the const to control the modal // We will use the const to control the modal
let isEmailAlreadySentConst; let isEmailAlreadySentConst = lastSentEmailContent.value.replace(/\s+/g, '') === textarea.value.replace(/\s+/g, '');
if (lastSentEmailContent)
isEmailAlreadySentConst = lastSentEmailContent.value.replace(/\s+/g, '') === textarea.value.replace(/\s+/g, '');
// We will use the function to control the label and help // We will use the function to control the label and help
function isEmailAlreadySent() { function isEmailAlreadySent() {
return lastSentEmailContent.value.replace(/\s+/g, '') === textarea.value.replace(/\s+/g, ''); return lastSentEmailContent.value.replace(/\s+/g, '') === textarea.value.replace(/\s+/g, '');
@ -710,6 +706,18 @@ document.addEventListener('DOMContentLoaded', function() {
} }
return ''; return '';
} }
// Extract the submitter name, title, email, and phone number
const submitterDiv = document.querySelector('.form-row.field-submitter');
const submitterNameElement = document.getElementById('id_submitter');
// We have to account for different superuser and analyst markups
const submitterName = submitterNameElement
? submitterNameElement.options[submitterNameElement.selectedIndex].text
: submitterDiv.querySelector('a').text;
const submitterTitle = extractTextById('contact_info_title', submitterDiv);
const submitterEmail = extractTextById('contact_info_email', submitterDiv);
const submitterPhone = extractTextById('contact_info_phone', submitterDiv);
let submitterInfo = `${submitterName}${submitterTitle}${submitterEmail}${submitterPhone}`;
//------ Senior Official //------ Senior Official
const seniorOfficialDiv = document.querySelector('.form-row.field-senior_official'); const seniorOfficialDiv = document.querySelector('.form-row.field-senior_official');
@ -726,6 +734,7 @@ document.addEventListener('DOMContentLoaded', function() {
`<strong>Current Websites:</strong> ${existingWebsites.join(', ')}</br>` + `<strong>Current Websites:</strong> ${existingWebsites.join(', ')}</br>` +
`<strong>Rationale:</strong></br>` + `<strong>Rationale:</strong></br>` +
`<strong>Alternative Domains:</strong> ${alternativeDomains.join(', ')}</br>` + `<strong>Alternative Domains:</strong> ${alternativeDomains.join(', ')}</br>` +
`<strong>Submitter:</strong> ${submitterInfo}</br>` +
`<strong>Senior Official:</strong> ${seniorOfficialInfo}</br>` + `<strong>Senior Official:</strong> ${seniorOfficialInfo}</br>` +
`<strong>Other Employees:</strong> ${otherContactsSummary}</br>`; `<strong>Other Employees:</strong> ${otherContactsSummary}</br>`;

View file

@ -385,7 +385,6 @@ a.button,
font-kerning: auto; font-kerning: auto;
font-family: inherit; font-family: inherit;
font-weight: normal; font-weight: normal;
text-decoration: none !important;
} }
.button svg, .button svg,
.button span, .button span,
@ -393,9 +392,6 @@ a.button,
.usa-button--dja span { .usa-button--dja span {
vertical-align: middle; vertical-align: middle;
} }
.usa-button--dja.usa-button--unstyled {
color: var(--link-fg);
}
.usa-button--dja:not(.usa-button--unstyled, .usa-button--outline, .usa-modal__close, .usa-button--secondary) { .usa-button--dja:not(.usa-button--unstyled, .usa-button--outline, .usa-modal__close, .usa-button--secondary) {
background: var(--button-bg); background: var(--button-bg);
} }
@ -425,34 +421,11 @@ input[type=submit].button--dja-toolbar {
input[type=submit].button--dja-toolbar:focus, input[type=submit].button--dja-toolbar:hover { input[type=submit].button--dja-toolbar:focus, input[type=submit].button--dja-toolbar:hover {
border-color: var(--body-quiet-color); border-color: var(--body-quiet-color);
} }
.admin-icon-group { // Targets the DJA buttom with a nested icon
position: relative; button .usa-icon,
display: inline; .button .usa-icon,
align-items: center; .button--clipboard .usa-icon {
vertical-align: middle;
input {
// Allow for padding around the copy button
padding-right: 35px !important;
}
button {
width: max-content;
}
@media (max-width: 1000px) {
button {
display: block;
}
}
span {
padding-left: 0.05rem;
}
}
.usa-button__small-text,
.usa-button__small-text span {
font-size: 13px;
} }
.module--custom { .module--custom {
@ -700,10 +673,71 @@ address.dja-address-contact-list {
} }
} }
// Make the clipboard button "float" inside of the input box
.admin-icon-group {
position: relative;
display: inline;
align-items: center;
input {
// Allow for padding around the copy button
padding-right: 35px !important;
// Match the height of other inputs
min-height: 2.25rem !important;
}
button {
line-height: 14px;
width: max-content;
font-size: unset;
text-decoration: none !important;
}
@media (max-width: 1000px) {
button {
display: block;
padding-top: 8px;
}
}
span {
padding-left: 0.1rem;
}
}
.admin-icon-group.admin-icon-group__clipboard-link {
position: relative;
display: inline;
align-items: center;
.usa-button--icon {
position: absolute;
right: auto;
left: 4px;
height: 100%;
top: -1px;
}
button {
font-size: unset !important;
display: inline-flex;
padding-top: 4px;
line-height: 14px;
width: max-content;
font-size: unset;
text-decoration: none !important;
}
}
.no-outline-on-click:focus { .no-outline-on-click:focus {
outline: none !important; outline: none !important;
} }
.usa-button__small-text {
font-size: small;
}
// Get rid of padding on all help texts // Get rid of padding on all help texts
form .aligned p.help, form .aligned div.help { form .aligned p.help, form .aligned div.help {
padding-left: 0px !important; padding-left: 0px !important;
@ -853,9 +887,6 @@ div.dja__model-description{
padding-top: 0 !important; padding-top: 0 !important;
} }
.padding-bottom-0 {
padding-bottom: 0 !important;
}
.flex-container { .flex-container {
@media screen and (min-width: 700px) and (max-width: 1150px) { @media screen and (min-width: 700px) and (max-width: 1150px) {

View file

@ -20,11 +20,10 @@
</li> </li>
{% if opts.model_name == 'domainrequest' %} {% if opts.model_name == 'domainrequest' %}
<li> <li>
<a id="id-copy-to-clipboard-summary" class="usa-button--dja" type="button" href="#"> <a id="id-copy-to-clipboard-summary" class="button--clipboard" type="button" href="#">
<svg class="usa-icon" > <svg class="usa-icon" >
<use xlink:href="{%static 'img/sprite.svg'%}#content_copy"></use> <use xlink:href="{%static 'img/sprite.svg'%}#content_copy"></use>
</svg> </svg>
<!-- the span is targeted in JS, do not remove -->
<span>{% translate "Copy request summary" %}</span> <span>{% translate "Copy request summary" %}</span>
</a> </a>
</li> </li>

View file

@ -8,7 +8,7 @@ Template for an input field with a clipboard
<div class="admin-icon-group"> <div class="admin-icon-group">
{{ field }} {{ field }}
<button <button
class="usa-button--dja usa-button usa-button__small-text usa-button--unstyled padding-left-1 usa-button--icon copy-to-clipboard" class="usa-button usa-button--unstyled padding-left-1 usa-button--icon button--clipboard copy-to-clipboard"
type="button" type="button"
> >
<div class="no-outline-on-click"> <div class="no-outline-on-click">
@ -17,27 +17,23 @@ Template for an input field with a clipboard
> >
<use aria-hidden="true" xlink:href="{%static 'img/sprite.svg'%}#content_copy"></use> <use aria-hidden="true" xlink:href="{%static 'img/sprite.svg'%}#content_copy"></use>
</svg> </svg>
<!-- the span is targeted in JS, do not remove --> Copy
<span>Copy</span>
</div> </div>
</button> </button>
</div> </div>
{% else %} {% else %}
<div class="admin-icon-group"> <div class="admin-icon-group admin-icon-group__clipboard-link">
<input aria-hidden="true" class="display-none" value="{{ field.email }}" /> <input aria-hidden="true" class="display-none" value="{{ field.email }}" />
{% if field.email is not None %} <button
<button class="usa-button usa-button--unstyled padding-right-1 usa-button--icon button--clipboard copy-to-clipboard text-no-underline padding-left-05"
class="usa-button--dja usa-button usa-button__small-text usa-button--unstyled padding-right-1 usa-button--icon copy-to-clipboard text-no-underline padding-left-05" type="button"
type="button" >
<svg
class="usa-icon"
> >
<svg <use aria-hidden="true" xlink:href="{%static 'img/sprite.svg'%}#content_copy"></use>
class="usa-icon" </svg>
> Copy
<use aria-hidden="true" xlink:href="{%static 'img/sprite.svg'%}#content_copy"></use> </button>
</svg>
<!-- the span is targeted in JS, do not remove -->
<span>Copy</span>
</button>
{% endif %}
</div> </div>
{% endif %} {% endif %}

View file

@ -26,7 +26,7 @@
{% if user.email %} {% if user.email %}
<span id="contact_info_email">{{ user.email }}</span> <span id="contact_info_email">{{ user.email }}</span>
{% include "admin/input_with_clipboard.html" with field=user invisible_input_field=True %} {% include "admin/input_with_clipboard.html" with field=user invisible_input_field=True %}
<br> <br class="admin-icon-group__br">
{% else %} {% else %}
None<br> None<br>
{% endif %} {% endif %}

View file

@ -254,7 +254,7 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html)
<table> <table>
<thead> <thead>
<tr> <tr>
<th colspan="5">Other contact information</th> <th colspan="4">Other contact information</th>
<tr> <tr>
</thead> </thead>
<tbody> <tbody>
@ -267,31 +267,18 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html)
</td> </td>
<td class="padding-left-1">{{ contact.phone }}</td> <td class="padding-left-1">{{ contact.phone }}</td>
<td class="padding-left-1 text-size-small"> <td class="padding-left-1 text-size-small">
{% if contact.email %} <input aria-hidden="true" class="display-none" value="{{ contact.email }}" />
<input aria-hidden="true" class="display-none" value="{{ contact.email }}" /> <button
<button class="usa-button usa-button--unstyled padding-right-1 usa-button--icon button--clipboard copy-to-clipboard usa-button__small-text text-no-underline"
class=" type="button"
usa-button--dja >
usa-button <svg
usa-button__small-text class="usa-icon"
usa-button--unstyled
padding-right-1
padding-top-0
padding-bottom-0
usa-button--icon
copy-to-clipboard
text-no-underline"
type="button"
> >
<svg <use aria-hidden="true" xlink:href="{%static 'img/sprite.svg'%}#content_copy"></use>
class="usa-icon" </svg>
> <span>Copy email</span>
<use aria-hidden="true" xlink:href="{%static 'img/sprite.svg'%}#content_copy"></use> </button>
</svg>
<!-- the span is targeted in JS, do not remove -->
<span>Copy email</span>
</button>
{% endif %}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}

View file

@ -654,7 +654,7 @@ class TestDomainInformationAdmin(TestCase):
self.test_helper.assert_response_contains_distinct_values(response, expected_other_employees_fields) self.test_helper.assert_response_contains_distinct_values(response, expected_other_employees_fields)
# Test for the copy link # Test for the copy link
self.assertContains(response, "copy-to-clipboard", count=3) self.assertContains(response, "button--clipboard", count=3)
# cleanup this test # cleanup this test
domain_info.delete() domain_info.delete()

View file

@ -535,7 +535,7 @@ class TestDomainAdminWithClient(TestCase):
self.assertContains(response, "Testy Tester") self.assertContains(response, "Testy Tester")
# Test for the copy link # Test for the copy link
self.assertContains(response, "copy-to-clipboard") self.assertContains(response, "button--clipboard")
# cleanup from this test # cleanup from this test
domain.delete() domain.delete()

View file

@ -1511,7 +1511,7 @@ class TestDomainRequestAdmin(MockEppLib):
self.test_helper.assert_response_contains_distinct_values(response, expected_other_employees_fields) self.test_helper.assert_response_contains_distinct_values(response, expected_other_employees_fields)
# Test for the copy link # Test for the copy link
self.assertContains(response, "copy-to-clipboard", count=4) self.assertContains(response, "button--clipboard", count=4)
# Test that Creator counts display properly # Test that Creator counts display properly
self.assertNotContains(response, "Approved domains") self.assertNotContains(response, "Approved domains")
@ -1846,58 +1846,6 @@ class TestDomainRequestAdmin(MockEppLib):
def test_side_effects_when_saving_approved_to_ineligible(self): def test_side_effects_when_saving_approved_to_ineligible(self):
self.trigger_saving_approved_to_another_state(False, DomainRequest.DomainRequestStatus.INELIGIBLE) self.trigger_saving_approved_to_another_state(False, DomainRequest.DomainRequestStatus.INELIGIBLE)
@less_console_noise
def test_error_when_saving_to_approved_and_domain_exists(self):
"""Redundant admin check on model transition not allowed."""
Domain.objects.create(name="wabbitseason.gov")
new_request = completed_domain_request(
status=DomainRequest.DomainRequestStatus.SUBMITTED, name="wabbitseason.gov"
)
# Create a request object with a superuser
request = self.factory.post("/admin/registrar/domainrequest/{}/change/".format(new_request.pk))
request.user = self.superuser
request.session = {}
# Use ExitStack to combine patch contexts
with ExitStack() as stack:
# Patch django.contrib.messages.error
stack.enter_context(patch.object(messages, "error"))
new_request.status = DomainRequest.DomainRequestStatus.APPROVED
self.admin.save_model(request, new_request, None, True)
messages.error.assert_called_once_with(
request,
"Cannot approve. Requested domain is already in use.",
)
@less_console_noise
def test_no_error_when_saving_to_approved_and_domain_exists(self):
"""The negative of the redundant admin check on model transition not allowed."""
new_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.SUBMITTED)
# Create a request object with a superuser
request = self.factory.post("/admin/registrar/domainrequest/{}/change/".format(new_request.pk))
request.user = self.superuser
request.session = {}
# Use ExitStack to combine patch contexts
with ExitStack() as stack:
# Patch Domain.is_active and django.contrib.messages.error simultaneously
stack.enter_context(patch.object(messages, "error"))
new_request.status = DomainRequest.DomainRequestStatus.APPROVED
self.admin.save_model(request, new_request, None, True)
# Assert that the error message was never called
messages.error.assert_not_called()
def test_has_correct_filters(self): def test_has_correct_filters(self):
""" """
This test verifies that DomainRequestAdmin has the correct filters set up. This test verifies that DomainRequestAdmin has the correct filters set up.

View file

@ -1,5 +1,7 @@
from django.forms import ValidationError from django.forms import ValidationError
from django.test import TestCase from django.test import TestCase
from django.db.utils import IntegrityError
from django.db import transaction
from unittest.mock import patch from unittest.mock import patch
from django.test import RequestFactory from django.test import RequestFactory
@ -18,18 +20,23 @@ from registrar.models import (
UserPortfolioPermission, UserPortfolioPermission,
AllowedEmail, AllowedEmail,
) )
import boto3_mocking import boto3_mocking
from registrar.models.portfolio import Portfolio from registrar.models.portfolio import Portfolio
from registrar.models.portfolio_invitation import PortfolioInvitation from registrar.models.portfolio_invitation import PortfolioInvitation
from registrar.models.transition_domain import TransitionDomain from registrar.models.transition_domain import TransitionDomain
from registrar.models.utility.portfolio_helper import UserPortfolioPermissionChoices, UserPortfolioRoleChoices from registrar.models.utility.portfolio_helper import UserPortfolioPermissionChoices, UserPortfolioRoleChoices
from registrar.models.verified_by_staff import VerifiedByStaff # type: ignore from registrar.models.verified_by_staff import VerifiedByStaff # type: ignore
from registrar.utility.constants import BranchChoices
from .common import ( from .common import (
MockSESClient, MockSESClient,
less_console_noise,
completed_domain_request, completed_domain_request,
set_domain_request_investigators,
create_test_user, create_test_user,
) )
from django_fsm import TransitionNotAllowed
from waffle.testutils import override_flag from waffle.testutils import override_flag
from api.tests.common import less_console_noise_decorator from api.tests.common import less_console_noise_decorator

File diff suppressed because it is too large Load diff

View file

@ -82,6 +82,7 @@ class DomainRequestTests(TestWithUser, WebTest):
response = self.app.get(f"/domain-request/{domain_request.id}") response = self.app.get(f"/domain-request/{domain_request.id}")
# Ensure that the date is still set to None # Ensure that the date is still set to None
self.assertIsNone(domain_request.last_status_update) self.assertIsNone(domain_request.last_status_update)
print(response)
# We should still grab a date for this field in this event - but it should come from the audit log instead # We should still grab a date for this field in this event - but it should come from the audit log instead
self.assertContains(response, "Started on:") self.assertContains(response, "Started on:")
self.assertContains(response, fixed_date.strftime("%B %-d, %Y")) self.assertContains(response, fixed_date.strftime("%B %-d, %Y"))