mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-05-20 03:19:24 +02:00
Merge branch 'main' into dk/1838-default-domain-requests
This commit is contained in:
commit
166dce15c7
24 changed files with 1038 additions and 97 deletions
|
@ -204,6 +204,16 @@ from .common import less_console_noise
|
||||||
# <test code goes here>
|
# <test code goes here>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Or alternatively, if you prefer using a decorator, just use:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from .common import less_console_noise_decorator
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def some_function():
|
||||||
|
# <test code goes here>
|
||||||
|
```
|
||||||
|
|
||||||
### Accessibility Testing in the browser
|
### Accessibility Testing in the browser
|
||||||
|
|
||||||
We use the [ANDI](https://www.ssa.gov/accessibility/andi/help/install.html) browser extension
|
We use the [ANDI](https://www.ssa.gov/accessibility/andi/help/install.html) browser extension
|
||||||
|
|
|
@ -16,12 +16,14 @@ The following set of rules should be followed while an incident is in progress.
|
||||||
- If downtime occurs outside of working hours, team members who are off for the day may still be pinged and called but are not required to join if unavailable to do so.
|
- If downtime occurs outside of working hours, team members who are off for the day may still be pinged and called but are not required to join if unavailable to do so.
|
||||||
- Uncomment the [banner on get.gov](https://github.com/cisagov/get.gov/blob/0365d3d34b041cc9353497b2b5f81b6ab7fe75a9/_includes/header.html#L9), so it is transparent to users that we know about the issue on manage.get.gov.
|
- Uncomment the [banner on get.gov](https://github.com/cisagov/get.gov/blob/0365d3d34b041cc9353497b2b5f81b6ab7fe75a9/_includes/header.html#L9), so it is transparent to users that we know about the issue on manage.get.gov.
|
||||||
- Designers or Developers should be able to make this change; if designers are online and can help with this task, that will allow developers to focus on fixing the bug.
|
- Designers or Developers should be able to make this change; if designers are online and can help with this task, that will allow developers to focus on fixing the bug.
|
||||||
|
- If the issue persists for three hours or more, follow the [instructions for enabling/disabling a redirect to get.gov](https://docs.google.com/document/d/1PiWXpjBzbiKsSYqEo9Rkl72HMytMp7zTte9CI-vvwYw/edit).
|
||||||
|
|
||||||
## Post Incident
|
## Post Incident
|
||||||
|
|
||||||
The following checklist should be followed after the site is back up and running.
|
The following checklist should be followed after the site is back up and running.
|
||||||
|
|
||||||
- [ ] Message in #dotgov-announce with an @here saying the issue is resolved
|
- [ ] Message in #dotgov-announce with an @here saying the issue is resolved.
|
||||||
|
- [ ] If the redirect was used, refer to the [instructions for enabling/disabling a redirect to get.gov](https://docs.google.com/document/d/1PiWXpjBzbiKsSYqEo9Rkl72HMytMp7zTte9CI-vvwYw/edit) to turn off this redirect. Double-check in the browser that this redirect is no longer occurring (the change may take a few minutes to take full effect).
|
||||||
- [ ] Remove the [banner on get.gov](https://github.com/cisagov/get.gov/blob/0365d3d34b041cc9353497b2b5f81b6ab7fe75a9/_includes/header.html#L9) by commenting it out.
|
- [ ] Remove the [banner on get.gov](https://github.com/cisagov/get.gov/blob/0365d3d34b041cc9353497b2b5f81b6ab7fe75a9/_includes/header.html#L9) by commenting it out.
|
||||||
- [ ] Write up what happened and when; if the cause is already known, write that as well. This is a draft for internal communications and not for any public facing site and can be as simple as using bullet points.
|
- [ ] Write up what happened and when; if the cause is already known, write that as well. This is a draft for internal communications and not for any public facing site and can be as simple as using bullet points.
|
||||||
- [ ] If the cause is not known yet, developers should investigate the issue as the highest priority task.
|
- [ ] If the cause is not known yet, developers should investigate the issue as the highest priority task.
|
||||||
|
|
|
@ -886,13 +886,21 @@ class DomainInformationAdmin(ListHeaderAdmin):
|
||||||
"fields": [
|
"fields": [
|
||||||
"generic_org_type",
|
"generic_org_type",
|
||||||
"is_election_board",
|
"is_election_board",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"More details",
|
||||||
|
{
|
||||||
|
"classes": ["collapse"],
|
||||||
|
"fields": [
|
||||||
"federal_type",
|
"federal_type",
|
||||||
"federal_agency",
|
"federal_agency",
|
||||||
"tribe_name",
|
"tribe_name",
|
||||||
"federally_recognized_tribe",
|
"federally_recognized_tribe",
|
||||||
"state_recognized_tribe",
|
"state_recognized_tribe",
|
||||||
"about_your_organization",
|
"about_your_organization",
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
@ -901,16 +909,27 @@ class DomainInformationAdmin(ListHeaderAdmin):
|
||||||
"fields": [
|
"fields": [
|
||||||
"organization_name",
|
"organization_name",
|
||||||
"state_territory",
|
"state_territory",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"More details",
|
||||||
|
{
|
||||||
|
"classes": ["collapse"],
|
||||||
|
"fields": [
|
||||||
"address_line1",
|
"address_line1",
|
||||||
"address_line2",
|
"address_line2",
|
||||||
"city",
|
"city",
|
||||||
"zipcode",
|
"zipcode",
|
||||||
"urbanization",
|
"urbanization",
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Readonly fields for analysts and superusers
|
||||||
|
readonly_fields = ("other_contacts",)
|
||||||
|
|
||||||
# Read only that we'll leverage for CISA Analysts
|
# Read only that we'll leverage for CISA Analysts
|
||||||
analyst_readonly_fields = [
|
analyst_readonly_fields = [
|
||||||
"creator",
|
"creator",
|
||||||
|
@ -939,6 +958,8 @@ class DomainInformationAdmin(ListHeaderAdmin):
|
||||||
# Table ordering
|
# Table ordering
|
||||||
ordering = ["domain__name"]
|
ordering = ["domain__name"]
|
||||||
|
|
||||||
|
change_form_template = "django/admin/domain_information_change_form.html"
|
||||||
|
|
||||||
def get_readonly_fields(self, request, obj=None):
|
def get_readonly_fields(self, request, obj=None):
|
||||||
"""Set the read-only state on form elements.
|
"""Set the read-only state on form elements.
|
||||||
We have 1 conditions that determine which fields are read-only:
|
We have 1 conditions that determine which fields are read-only:
|
||||||
|
@ -959,6 +980,7 @@ class DomainRequestAdmin(ListHeaderAdmin):
|
||||||
"""Custom domain requests admin class."""
|
"""Custom domain requests admin class."""
|
||||||
|
|
||||||
form = DomainRequestAdminForm
|
form = DomainRequestAdminForm
|
||||||
|
change_form_template = "django/admin/domain_request_change_form.html"
|
||||||
|
|
||||||
class StatusListFilter(MultipleChoiceListFilter):
|
class StatusListFilter(MultipleChoiceListFilter):
|
||||||
"""Custom status filter which is a multiple choice filter"""
|
"""Custom status filter which is a multiple choice filter"""
|
||||||
|
@ -1031,8 +1053,6 @@ 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_request_change_form.html"
|
|
||||||
|
|
||||||
# Columns
|
# Columns
|
||||||
list_display = [
|
list_display = [
|
||||||
"requested_domain",
|
"requested_domain",
|
||||||
|
@ -1104,13 +1124,21 @@ class DomainRequestAdmin(ListHeaderAdmin):
|
||||||
"fields": [
|
"fields": [
|
||||||
"generic_org_type",
|
"generic_org_type",
|
||||||
"is_election_board",
|
"is_election_board",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"More details",
|
||||||
|
{
|
||||||
|
"classes": ["collapse"],
|
||||||
|
"fields": [
|
||||||
"federal_type",
|
"federal_type",
|
||||||
"federal_agency",
|
"federal_agency",
|
||||||
"tribe_name",
|
"tribe_name",
|
||||||
"federally_recognized_tribe",
|
"federally_recognized_tribe",
|
||||||
"state_recognized_tribe",
|
"state_recognized_tribe",
|
||||||
"about_your_organization",
|
"about_your_organization",
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
|
@ -1119,16 +1147,27 @@ class DomainRequestAdmin(ListHeaderAdmin):
|
||||||
"fields": [
|
"fields": [
|
||||||
"organization_name",
|
"organization_name",
|
||||||
"state_territory",
|
"state_territory",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"More details",
|
||||||
|
{
|
||||||
|
"classes": ["collapse"],
|
||||||
|
"fields": [
|
||||||
"address_line1",
|
"address_line1",
|
||||||
"address_line2",
|
"address_line2",
|
||||||
"city",
|
"city",
|
||||||
"zipcode",
|
"zipcode",
|
||||||
"urbanization",
|
"urbanization",
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Readonly fields for analysts and superusers
|
||||||
|
readonly_fields = ("other_contacts", "current_websites", "alternative_domains")
|
||||||
|
|
||||||
# Read only that we'll leverage for CISA Analysts
|
# Read only that we'll leverage for CISA Analysts
|
||||||
analyst_readonly_fields = [
|
analyst_readonly_fields = [
|
||||||
"creator",
|
"creator",
|
||||||
|
@ -1155,6 +1194,8 @@ class DomainRequestAdmin(ListHeaderAdmin):
|
||||||
# Table ordering
|
# Table ordering
|
||||||
ordering = ["requested_domain__name"]
|
ordering = ["requested_domain__name"]
|
||||||
|
|
||||||
|
change_form_template = "django/admin/domain_request_change_form.html"
|
||||||
|
|
||||||
# Trigger action when a fieldset is changed
|
# Trigger action when a fieldset is changed
|
||||||
def save_model(self, request, obj, form, change):
|
def save_model(self, request, obj, form, change):
|
||||||
"""Custom save_model definition that handles edge cases"""
|
"""Custom save_model definition that handles edge cases"""
|
||||||
|
@ -1309,7 +1350,7 @@ class DomainRequestAdmin(ListHeaderAdmin):
|
||||||
readonly_fields.extend([field.name for field in self.model._meta.fields])
|
readonly_fields.extend([field.name for field in self.model._meta.fields])
|
||||||
# Add the multi-select fields to readonly_fields:
|
# Add the multi-select fields to readonly_fields:
|
||||||
# Complex fields like ManyToManyField require special handling
|
# Complex fields like ManyToManyField require special handling
|
||||||
readonly_fields.extend(["current_websites", "other_contacts", "alternative_domains"])
|
readonly_fields.extend(["alternative_domains"])
|
||||||
|
|
||||||
if request.user.has_perm("registrar.full_access_permission"):
|
if request.user.has_perm("registrar.full_access_permission"):
|
||||||
return readonly_fields
|
return readonly_fields
|
||||||
|
@ -1437,7 +1478,6 @@ class DomainAdmin(ListHeaderAdmin):
|
||||||
)
|
)
|
||||||
|
|
||||||
def queryset(self, request, queryset):
|
def queryset(self, request, queryset):
|
||||||
logger.debug(self.value())
|
|
||||||
if self.value() == "1":
|
if self.value() == "1":
|
||||||
return queryset.filter(domain_info__is_election_board=True)
|
return queryset.filter(domain_info__is_election_board=True)
|
||||||
if self.value() == "0":
|
if self.value() == "0":
|
||||||
|
|
|
@ -410,3 +410,60 @@ function enableRelatedWidgetButtons(changeLink, deleteLink, viewLink, elementPk,
|
||||||
});
|
});
|
||||||
observer.observe({ type: "navigation" });
|
observer.observe({ type: "navigation" });
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
/** An IIFE for toggling the submit bar on domain request forms
|
||||||
|
*/
|
||||||
|
(function (){
|
||||||
|
// Get a reference to the button element
|
||||||
|
const toggleButton = document.getElementById('submitRowToggle');
|
||||||
|
const submitRowWrapper = document.querySelector('.submit-row-wrapper');
|
||||||
|
|
||||||
|
if (toggleButton) {
|
||||||
|
// Add event listener to toggle the class and update content on click
|
||||||
|
toggleButton.addEventListener('click', function() {
|
||||||
|
// Toggle the 'collapsed' class on the bar
|
||||||
|
submitRowWrapper.classList.toggle('submit-row-wrapper--collapsed');
|
||||||
|
|
||||||
|
// Get a reference to the span element inside the button
|
||||||
|
const spanElement = this.querySelector('span');
|
||||||
|
|
||||||
|
// Get a reference to the use element inside the button
|
||||||
|
const useElement = this.querySelector('use');
|
||||||
|
|
||||||
|
// Check if the span element text is 'Hide'
|
||||||
|
if (spanElement.textContent.trim() === 'Hide') {
|
||||||
|
// Update the span element text to 'Show'
|
||||||
|
spanElement.textContent = 'Show';
|
||||||
|
|
||||||
|
// Update the xlink:href attribute to expand_more
|
||||||
|
useElement.setAttribute('xlink:href', '/public/img/sprite.svg#expand_less');
|
||||||
|
} else {
|
||||||
|
// Update the span element text to 'Hide'
|
||||||
|
spanElement.textContent = 'Hide';
|
||||||
|
|
||||||
|
// Update the xlink:href attribute to expand_less
|
||||||
|
useElement.setAttribute('xlink:href', '/public/img/sprite.svg#expand_more');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// We have a scroll indicator at the end of the page.
|
||||||
|
// Observe it. Once it gets on screen, test to see if the row is collapsed.
|
||||||
|
// If it is, expand it.
|
||||||
|
const targetElement = document.querySelector(".scroll-indicator");
|
||||||
|
const options = {
|
||||||
|
threshold: 1
|
||||||
|
};
|
||||||
|
// Create a new Intersection Observer
|
||||||
|
const observer = new IntersectionObserver((entries, observer) => {
|
||||||
|
entries.forEach(entry => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
// Refresh reference to submit row wrapper and check if it's collapsed
|
||||||
|
if (document.querySelector('.submit-row-wrapper').classList.contains('submit-row-wrapper--collapsed')) {
|
||||||
|
toggleButton.click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, options);
|
||||||
|
observer.observe(targetElement);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
|
@ -54,17 +54,16 @@
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
|
||||||
createComparativeColumnChart("myChart1", "Managed domains", "Start Date", "End Date");
|
|
||||||
createComparativeColumnChart("myChart2", "Unmanaged domains", "Start Date", "End Date");
|
|
||||||
createComparativeColumnChart("myChart3", "Deleted domains", "Start Date", "End Date");
|
|
||||||
createComparativeColumnChart("myChart4", "Ready domains", "Start Date", "End Date");
|
|
||||||
createComparativeColumnChart("myChart5", "Submitted requests", "Start Date", "End Date");
|
|
||||||
createComparativeColumnChart("myChart6", "All requests", "Start Date", "End Date");
|
|
||||||
});
|
|
||||||
|
|
||||||
function createComparativeColumnChart(canvasId, title, labelOne, labelTwo) {
|
/** An IIFE to initialize the analytics page
|
||||||
|
*/
|
||||||
|
(function () {
|
||||||
|
function createComparativeColumnChart(canvasId, title, labelOne, labelTwo) {
|
||||||
var canvas = document.getElementById(canvasId);
|
var canvas = document.getElementById(canvasId);
|
||||||
|
if (!canvas) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var ctx = canvas.getContext("2d");
|
var ctx = canvas.getContext("2d");
|
||||||
|
|
||||||
var listOne = JSON.parse(canvas.getAttribute('data-list-one'));
|
var listOne = JSON.parse(canvas.getAttribute('data-list-one'));
|
||||||
|
@ -114,4 +113,18 @@ function createComparativeColumnChart(canvasId, title, labelOne, labelTwo) {
|
||||||
data: data,
|
data: data,
|
||||||
options: options,
|
options: options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function initComparativeColumnCharts() {
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
createComparativeColumnChart("myChart1", "Managed domains", "Start Date", "End Date");
|
||||||
|
createComparativeColumnChart("myChart2", "Unmanaged domains", "Start Date", "End Date");
|
||||||
|
createComparativeColumnChart("myChart3", "Deleted domains", "Start Date", "End Date");
|
||||||
|
createComparativeColumnChart("myChart4", "Ready domains", "Start Date", "End Date");
|
||||||
|
createComparativeColumnChart("myChart5", "Submitted requests", "Start Date", "End Date");
|
||||||
|
createComparativeColumnChart("myChart6", "All requests", "Start Date", "End Date");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
initComparativeColumnCharts();
|
||||||
|
})();
|
||||||
|
|
|
@ -349,6 +349,77 @@ input.admin-confirm-button {
|
||||||
color: $dhs-blue-70;
|
color: $dhs-blue-70;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
details.dja-detail-table {
|
||||||
|
display: inline-table;
|
||||||
|
background-color: var(--body-bg);
|
||||||
|
.dja-details-summary {
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--body-quiet-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1024px){
|
||||||
|
.dja-detail-contents {
|
||||||
|
max-width: 400px !important;
|
||||||
|
overflow-x: scroll !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tr {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
td, th {
|
||||||
|
padding-left: 12px;
|
||||||
|
border: none
|
||||||
|
}
|
||||||
|
|
||||||
|
thead > tr > th {
|
||||||
|
border-radius: 4px;
|
||||||
|
border-top: none;
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
address.margin-top-neg-1__detail-list {
|
||||||
|
margin-top: -8px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dja-detail-list {
|
||||||
|
dl {
|
||||||
|
padding-left: 0px !important;
|
||||||
|
margin-top: 5px !important;
|
||||||
|
}
|
||||||
|
// Mimic the normal label size
|
||||||
|
dt {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
color: var(--body-quiet-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
address {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
color: var(--body-quiet-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
address.dja-address-contact-list {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
color: var(--body-quiet-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mimic the normal label size
|
||||||
|
@media (max-width: 1024px){
|
||||||
|
.dja-detail-list dt {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--body-quiet-color);
|
||||||
|
}
|
||||||
|
.dja-detail-list address {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--body-quiet-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.errors span.select2-selection {
|
.errors span.select2-selection {
|
||||||
border: 1px solid var(--error-fg) !important;
|
border: 1px solid var(--error-fg) !important;
|
||||||
}
|
}
|
||||||
|
@ -370,3 +441,95 @@ input.admin-confirm-button {
|
||||||
color: var(--body-loud-color);
|
color: var(--body-loud-color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Let's define this block of code once and use it for analysts over a certain screen size,
|
||||||
|
// superusers over another screen size.
|
||||||
|
@mixin submit-row-wrapper--collapsed-one-line(){
|
||||||
|
&.submit-row-wrapper--collapsed {
|
||||||
|
transform: translate3d(0, 42px, 0);
|
||||||
|
}
|
||||||
|
.submit-row {
|
||||||
|
clear: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sticky submit bar for domain requests on desktop
|
||||||
|
@media screen and (min-width:768px) {
|
||||||
|
.submit-row-wrapper {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
left: 338px;
|
||||||
|
background: var(--darkened-bg);
|
||||||
|
border-top-left-radius: 6px;
|
||||||
|
transition: transform .2s ease-out;
|
||||||
|
.submit-row {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.submit-row-wrapper--collapsed {
|
||||||
|
// translate3d is more performant than translateY
|
||||||
|
// https://stackoverflow.com/questions/22111256/translate3d-vs-translate-performance
|
||||||
|
transform: translate3d(0, 88px, 0);
|
||||||
|
}
|
||||||
|
.submit-row-wrapper--collapsed-one-line {
|
||||||
|
@include submit-row-wrapper--collapsed-one-line();
|
||||||
|
}
|
||||||
|
.submit-row {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
.submit-row-toggle{
|
||||||
|
display: inline-block;
|
||||||
|
position: absolute;
|
||||||
|
top: -30px;
|
||||||
|
right: 0;
|
||||||
|
background: var(--darkened-bg);
|
||||||
|
}
|
||||||
|
#submitRowToggle {
|
||||||
|
color: var(--body-fg);
|
||||||
|
}
|
||||||
|
.requested-domain-sticky {
|
||||||
|
max-width: 325px;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.visible-768 {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width:768px) {
|
||||||
|
.visible-768 {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width:935px) {
|
||||||
|
// Analyst only class
|
||||||
|
.submit-row-wrapper--analyst-view {
|
||||||
|
@include submit-row-wrapper--collapsed-one-line();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width:1256px) {
|
||||||
|
.submit-row-wrapper {
|
||||||
|
@include submit-row-wrapper--collapsed-one-line();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collapse button styles for fieldsets
|
||||||
|
.module.collapse {
|
||||||
|
margin-top: -35px;
|
||||||
|
padding-top: 0;
|
||||||
|
border: none;
|
||||||
|
h2 {
|
||||||
|
background: none;
|
||||||
|
color: var(--body-fg)!important;
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: var(--link-fg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -117,6 +117,10 @@ abbr[title] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.visible-desktop {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
@include at-media(desktop) {
|
@include at-media(desktop) {
|
||||||
.float-right-desktop {
|
.float-right-desktop {
|
||||||
float: right;
|
float: right;
|
||||||
|
@ -124,33 +128,15 @@ abbr[title] {
|
||||||
.float-left-desktop {
|
.float-left-desktop {
|
||||||
float: left;
|
float: left;
|
||||||
}
|
}
|
||||||
|
.visible-desktop {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.flex-end {
|
.flex-end {
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only apply this custom wrapping to desktop
|
.cursor-pointer {
|
||||||
@include at-media(desktop) {
|
cursor: pointer;
|
||||||
.usa-tooltip__body {
|
|
||||||
width: 350px;
|
|
||||||
white-space: normal;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include at-media(tablet) {
|
|
||||||
.usa-tooltip__body {
|
|
||||||
width: 250px !important;
|
|
||||||
white-space: normal !important;
|
|
||||||
text-align: center !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include at-media(mobile) {
|
|
||||||
.usa-tooltip__body {
|
|
||||||
width: 250px !important;
|
|
||||||
white-space: normal !important;
|
|
||||||
text-align: center !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,6 +138,13 @@ a.usa-button--unstyled:visited {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.usa-button--unstyled--white,
|
||||||
|
.usa-button--unstyled--white:hover,
|
||||||
|
.usa-button--unstyled--white:focus,
|
||||||
|
.usa-button--unstyled--white:active {
|
||||||
|
color: color('white');
|
||||||
|
}
|
||||||
|
|
||||||
// Cancel button used on the
|
// Cancel button used on the
|
||||||
// DNSSEC main page
|
// DNSSEC main page
|
||||||
// We want to center this button on mobile
|
// We want to center this button on mobile
|
||||||
|
|
26
src/registrar/assets/sass/_theme/_tooltips.scss
Normal file
26
src/registrar/assets/sass/_theme/_tooltips.scss
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
@use "uswds-core" as *;
|
||||||
|
|
||||||
|
// Only apply this custom wrapping to desktop
|
||||||
|
@include at-media(desktop) {
|
||||||
|
.usa-tooltip__body {
|
||||||
|
width: 350px;
|
||||||
|
white-space: normal;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include at-media(tablet) {
|
||||||
|
.usa-tooltip__body {
|
||||||
|
width: 250px !important;
|
||||||
|
white-space: normal !important;
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include at-media(mobile) {
|
||||||
|
.usa-tooltip__body {
|
||||||
|
width: 250px !important;
|
||||||
|
white-space: normal !important;
|
||||||
|
text-align: center !important;
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,6 +13,7 @@
|
||||||
@forward "lists";
|
@forward "lists";
|
||||||
@forward "buttons";
|
@forward "buttons";
|
||||||
@forward "forms";
|
@forward "forms";
|
||||||
|
@forward "tooltips";
|
||||||
@forward "fieldsets";
|
@forward "fieldsets";
|
||||||
@forward "alerts";
|
@forward "alerts";
|
||||||
@forward "tables";
|
@forward "tables";
|
||||||
|
|
|
@ -93,6 +93,12 @@ class UserFixture:
|
||||||
"last_name": "Chin",
|
"last_name": "Chin",
|
||||||
"email": "szu.chin@associates.cisa.dhs.gov",
|
"email": "szu.chin@associates.cisa.dhs.gov",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"username": "012f844d-8a0f-4225-9d82-cbf87bff1d3e",
|
||||||
|
"first_name": "Riley",
|
||||||
|
"last_name": "Orr",
|
||||||
|
"email": "riley+320@truss.works",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
STAFF = [
|
STAFF = [
|
||||||
|
@ -169,6 +175,12 @@ class UserFixture:
|
||||||
"last_name": "Mcelya-Analyst",
|
"last_name": "Mcelya-Analyst",
|
||||||
"email": "ALEXANDER.MCELYA@cisa.dhs.gov",
|
"email": "ALEXANDER.MCELYA@cisa.dhs.gov",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"username": "082a066f-e0a4-45f6-8672-4343a1208a36",
|
||||||
|
"first_name": "Riley-Analyst",
|
||||||
|
"last_name": "Orr-Analyst",
|
||||||
|
"email": "riley+321@truss.works",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
def load_users(cls, users, group_name):
|
def load_users(cls, users, group_name):
|
||||||
|
|
61
src/registrar/templates/admin/fieldset.html
Normal file
61
src/registrar/templates/admin/fieldset.html
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
{% load i18n static %}
|
||||||
|
|
||||||
|
{% comment %}
|
||||||
|
This is copied from Djangos implementation of this template, with added "blocks"
|
||||||
|
It is not inherently customizable on its own, so we can modify this instead.
|
||||||
|
https://github.com/django/django/blob/main/django/contrib/admin/templates/admin/includes/fieldset.html
|
||||||
|
{% endcomment %}
|
||||||
|
<fieldset class="module aligned {{ fieldset.classes }}">
|
||||||
|
{% if fieldset.name %}<h2>{{ fieldset.name }}</h2>{% endif %}
|
||||||
|
|
||||||
|
{% if fieldset.description %}
|
||||||
|
<div class="description">{{ fieldset.description|safe }}</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% for line in fieldset %}
|
||||||
|
<div class="form-row{% if line.fields|length == 1 and line.errors %} errors{% endif %}{% if not line.has_visible_field %} hidden{% endif %}{% for field in line %}{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% endfor %}">
|
||||||
|
{% if line.fields|length == 1 %}{{ line.errors }}{% else %}<div class="flex-container form-multiline">{% endif %}
|
||||||
|
{% for field in line %}
|
||||||
|
<div>
|
||||||
|
{% if not line.fields|length == 1 and not field.is_readonly %}{{ field.errors }}{% endif %}
|
||||||
|
<div class="flex-container{% if not line.fields|length == 1 %} fieldBox{% if field.field.name %} field-{{ field.field.name }}{% endif %}{% if not field.is_readonly and field.errors %} errors{% endif %}{% if field.field.is_hidden %} hidden{% endif %}{% elif field.is_checkbox %} checkbox-row{% endif %}">
|
||||||
|
{% if field.is_checkbox %}
|
||||||
|
{# .gov override #}
|
||||||
|
{% block field_checkbox %}
|
||||||
|
{{ field.field }}{{ field.label_tag }}
|
||||||
|
{% endblock field_checkbox%}
|
||||||
|
{% else %}
|
||||||
|
{{ field.label_tag }}
|
||||||
|
{% if field.is_readonly %}
|
||||||
|
{# .gov override #}
|
||||||
|
{% block field_readonly %}
|
||||||
|
<div class="readonly">{{ field.contents }}</div>
|
||||||
|
{% endblock field_readonly%}
|
||||||
|
{% else %}
|
||||||
|
{# .gov override #}
|
||||||
|
{% block field_other %}
|
||||||
|
{{ field.field }}
|
||||||
|
{% endblock field_other%}
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if field.field.help_text %}
|
||||||
|
{# .gov override #}
|
||||||
|
{% block help_text %}
|
||||||
|
<div class="help"{% if field.field.id_for_label %} id="{{ field.field.id_for_label }}_helptext"{% endif %}>
|
||||||
|
<div>{{ field.field.help_text|safe }}</div>
|
||||||
|
</div>
|
||||||
|
{% endblock help_text %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# .gov addition #}
|
||||||
|
{% block after_help_text %}
|
||||||
|
{# For templating purposes #}
|
||||||
|
{% endblock after_help_text %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
{% if not line.fields|length == 1 %}</div>{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</fieldset>
|
|
@ -0,0 +1,15 @@
|
||||||
|
{% extends 'admin/change_form.html' %}
|
||||||
|
{% load i18n static %}
|
||||||
|
|
||||||
|
{% block field_sets %}
|
||||||
|
{% for fieldset in adminform %}
|
||||||
|
{% comment %}
|
||||||
|
TODO: this will eventually need to be changed to something like this
|
||||||
|
if we ever want to customize this file:
|
||||||
|
{% include "django/admin/includes/domain_information_fieldset.html" %}
|
||||||
|
|
||||||
|
Use detail_table_fieldset as an example, or just extend it.
|
||||||
|
{% endcomment %}
|
||||||
|
{% include "django/admin/includes/detail_table_fieldset.html" %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endblock %}
|
|
@ -1,10 +1,21 @@
|
||||||
{% extends 'admin/change_form.html' %}
|
{% extends 'admin/change_form.html' %}
|
||||||
|
{% load custom_filters %}
|
||||||
{% load i18n static %}
|
{% load i18n static %}
|
||||||
|
|
||||||
{% block field_sets %}
|
{% block field_sets %}
|
||||||
{# Create an invisible <a> tag so that we can use a click event to toggle the modal. #}
|
{# 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>
|
<a id="invisible-ineligible-modal-toggler" class="display-none" href="#toggle-set-ineligible" aria-controls="toggle-set-ineligible" data-open-modal></a>
|
||||||
{{ block.super }}
|
|
||||||
|
{% for fieldset in adminform %}
|
||||||
|
{% comment %}
|
||||||
|
TODO: this will eventually need to be changed to something like this
|
||||||
|
if we ever want to customize this file:
|
||||||
|
{% include "django/admin/includes/domain_information_fieldset.html" %}
|
||||||
|
|
||||||
|
Use detail_table_fieldset as an example, or just extend it.
|
||||||
|
{% endcomment %}
|
||||||
|
{% include "django/admin/includes/detail_table_fieldset.html" %}
|
||||||
|
{% endfor %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block submit_buttons_bottom %}
|
{% block submit_buttons_bottom %}
|
||||||
|
@ -92,5 +103,25 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{ block.super }}
|
|
||||||
|
{# submit-row-wrapper--analyst-view is a class that manages layout on certain screens for analysts only #}
|
||||||
|
<div class="submit-row-wrapper{% if not request.user|has_permission:'registrar.full_access_permission' %} submit-row-wrapper--analyst-view{% endif %}">
|
||||||
|
|
||||||
|
<span class="submit-row-toggle padding-1 padding-right-2 visible-desktop">
|
||||||
|
<button type="button" class="usa-button usa-button--unstyled" id="submitRowToggle">
|
||||||
|
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24" height="24">
|
||||||
|
<use xlink:href="{%static 'img/sprite.svg'%}#expand_more"></use>
|
||||||
|
</svg>
|
||||||
|
<span>Hide</span>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<p class="text-right margin-top-2 padding-right-2 margin-bottom-0 requested-domain-sticky float-right visible-768">
|
||||||
|
<strong>Requested domain:</strong> {{ original.requested_domain.name }}
|
||||||
|
</p>
|
||||||
|
{{ block.super }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span class="scroll-indicator"></span>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -0,0 +1,51 @@
|
||||||
|
{% load i18n static %}
|
||||||
|
|
||||||
|
<address class="{% if no_title_top_padding %}margin-top-neg-1__detail-list{% endif %} dja-address-contact-list">
|
||||||
|
|
||||||
|
{% if show_formatted_name %}
|
||||||
|
{% if contact.get_formatted_name %}
|
||||||
|
<a href="{% url 'admin:registrar_contact_change' user.id %}">{{ user.get_formatted_name }}</a><br />
|
||||||
|
{% else %}
|
||||||
|
None<br />
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if user.title or user.contact.title or user.email or user.contact.email or user.phone or user.contact.phone %}
|
||||||
|
{# Title #}
|
||||||
|
{% if user.title or user.contact.title %}
|
||||||
|
{% if user.contact.title %}
|
||||||
|
{{ user.contact.title }}
|
||||||
|
{% else %}
|
||||||
|
{{ user.title }}
|
||||||
|
{% endif %}
|
||||||
|
<br>
|
||||||
|
{% else %}
|
||||||
|
None<br>
|
||||||
|
{% endif %}
|
||||||
|
{# Email #}
|
||||||
|
{% if user.email or user.contact.email %}
|
||||||
|
{% if user.contact.email %}
|
||||||
|
{{ user.contact.email }}
|
||||||
|
{% else %}
|
||||||
|
{{ user.email }}
|
||||||
|
{% endif %}
|
||||||
|
<br>
|
||||||
|
{% else %}
|
||||||
|
None<br>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# Phone #}
|
||||||
|
{% if user.phone or user.contact.phone %}
|
||||||
|
{% if user.contact.phone %}
|
||||||
|
{{ user.contact.phone }}
|
||||||
|
{% else %}
|
||||||
|
{{ user.phone }}
|
||||||
|
{% endif %}
|
||||||
|
<br>
|
||||||
|
{% else %}
|
||||||
|
None<br>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
No additional contact information found.
|
||||||
|
{% endif %}
|
||||||
|
</address>
|
|
@ -0,0 +1,106 @@
|
||||||
|
{% extends "admin/fieldset.html" %}
|
||||||
|
{% load static url_helpers %}
|
||||||
|
|
||||||
|
{% comment %}
|
||||||
|
This is using a custom implementation fieldset.html (see admin/fieldset.html)
|
||||||
|
{% endcomment %}
|
||||||
|
{% block field_readonly %}
|
||||||
|
{% with all_contacts=original.other_contacts.all %}
|
||||||
|
{% if field.field.name == "other_contacts" %}
|
||||||
|
{% if all_contacts.count > 2 %}
|
||||||
|
<div class="readonly">
|
||||||
|
{% for contact in all_contacts %}
|
||||||
|
<a href="{% url 'admin:registrar_contact_change' contact.id %}">{{ contact.get_formatted_name }}</a>{% if not forloop.last %}, {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="flex-container dja-detail-list">
|
||||||
|
<dl class="usa-list usa-list--unstyled margin-0">
|
||||||
|
{% for contact in all_contacts %}
|
||||||
|
<dt class="{% if forloop.counter == 1%}margin-top-0{% endif %}">
|
||||||
|
Organization contact {{forloop.counter}}
|
||||||
|
</dt>
|
||||||
|
<dd>
|
||||||
|
{% include "django/admin/includes/contact_detail_list.html" with user=contact show_formatted_name=True %}
|
||||||
|
</dd>
|
||||||
|
{% endfor %}
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% elif field.field.name == "current_websites" %}
|
||||||
|
{% comment %}
|
||||||
|
The "website" model is essentially just a text field.
|
||||||
|
It is not useful to be redirected to the object definition,
|
||||||
|
rather it is more useful in this scenario to be redirected to the
|
||||||
|
actual website (as its just a plaintext string otherwise).
|
||||||
|
|
||||||
|
This ONLY applies to analysts. For superusers, its business as usual.
|
||||||
|
{% endcomment %}
|
||||||
|
<div class="readonly">
|
||||||
|
{% with total_websites=field.contents|split:", " %}
|
||||||
|
{% for website in total_websites %}
|
||||||
|
<a href="{{ website }}" class="padding-top-1 current-website__{{forloop.counter}}">{{ website }}</a>{% if not forloop.last %}, {% endif %}
|
||||||
|
{# Acts as a <br> #}
|
||||||
|
{% if total_websites|length < 5 %}
|
||||||
|
<div class="display-block margin-top-1"></div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endwith %}
|
||||||
|
</div>
|
||||||
|
{% elif field.field.name == "alternative_domains" %}
|
||||||
|
<div class="readonly">
|
||||||
|
{% for alt_domain in original.alternative_domains.all %}
|
||||||
|
<a href="{% url 'admin:registrar_website_change' alt_domain.id %}">{{ alt_domain }}</a>{% if not forloop.last %}, {% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="readonly">{{ field.contents }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endblock field_readonly %}
|
||||||
|
|
||||||
|
{% block after_help_text %}
|
||||||
|
{% if field.field.name == "creator" %}
|
||||||
|
<div class="flex-container">
|
||||||
|
<label aria-label="Creator contact details"></label>
|
||||||
|
{% include "django/admin/includes/contact_detail_list.html" with user=original.creator no_title_top_padding=field.is_readonly %}
|
||||||
|
</div>
|
||||||
|
{% elif field.field.name == "submitter" %}
|
||||||
|
<div class="flex-container">
|
||||||
|
<label aria-label="Submitter contact details"></label>
|
||||||
|
{% include "django/admin/includes/contact_detail_list.html" with user=original.submitter no_title_top_padding=field.is_readonly %}
|
||||||
|
</div>
|
||||||
|
{% elif field.field.name == "authorizing_official" %}
|
||||||
|
<div class="flex-container">
|
||||||
|
<label aria-label="Authorizing official contact details"></label>
|
||||||
|
{% include "django/admin/includes/contact_detail_list.html" with user=original.authorizing_official no_title_top_padding=field.is_readonly %}
|
||||||
|
</div>
|
||||||
|
{% elif field.field.name == "other_contacts" and original.other_contacts.all %}
|
||||||
|
{% with all_contacts=original.other_contacts.all %}
|
||||||
|
{% if all_contacts.count > 2 %}
|
||||||
|
<details class="margin-top-1 dja-detail-table" aria-role="button" open>
|
||||||
|
<summary class="padding-1 padding-left-0 dja-details-summary">Details</summary>
|
||||||
|
<div class="grid-container margin-left-0 padding-left-0 padding-right-0 dja-details-contents">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="4">Other contact information</th>
|
||||||
|
<tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for contact in all_contacts %}
|
||||||
|
<tr>
|
||||||
|
<th class="padding-left-1" scope="row">{{ contact.get_formatted_name }}</th>
|
||||||
|
<td class="padding-left-1">{{ contact.title }}</td>
|
||||||
|
<td class="padding-left-1">{{ contact.email }}</td>
|
||||||
|
<td class="padding-left-1">{{ contact.phone }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock after_help_text %}
|
|
@ -41,8 +41,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<br>
|
<br>
|
||||||
<p> <b class="review__step__name">Last updated:</b> {{DomainRequest.updated_at|date:"F j, Y"}}<br>
|
<p> <b class="review__step__name">Last updated:</b> {{DomainRequest.updated_at|date:"F j, Y"}}<br></p>
|
||||||
<b class="review__step__name">Request #:</b> {{DomainRequest.id}}</p>
|
|
||||||
<p>{% include "includes/domain_request.html" %}</p>
|
<p>{% include "includes/domain_request.html" %}</p>
|
||||||
<p><a href="{% url 'domain-request-withdraw-confirmation' pk=DomainRequest.id %}" class="usa-button usa-button--outline withdraw_outline">
|
<p><a href="{% url 'domain-request-withdraw-confirmation' pk=DomainRequest.id %}" class="usa-button usa-button--outline withdraw_outline">
|
||||||
Withdraw request</a>
|
Withdraw request</a>
|
||||||
|
|
|
@ -135,7 +135,7 @@
|
||||||
<td>
|
<td>
|
||||||
{% if invitation.status == invitation.DomainInvitationStatus.INVITED %}
|
{% if invitation.status == invitation.DomainInvitationStatus.INVITED %}
|
||||||
<form method="POST" action="{% url "invitation-delete" pk=invitation.id %}">
|
<form method="POST" action="{% url "invitation-delete" pk=invitation.id %}">
|
||||||
{% csrf_token %}<input type="submit" class="usa-button--unstyled text-no-underline" value="Cancel">
|
{% csrf_token %}<input type="submit" class="usa-button--unstyled text-no-underline cursor-pointer" value="Cancel">
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
|
|
|
@ -62,3 +62,8 @@ def get_organization_long_name(generic_org_type):
|
||||||
return "Error"
|
return "Error"
|
||||||
|
|
||||||
return long_form_type
|
return long_form_type
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter(name="has_permission")
|
||||||
|
def has_permission(user, permission):
|
||||||
|
return user.has_perm(permission)
|
||||||
|
|
|
@ -26,6 +26,14 @@ def endswith(text, ends):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter("split")
|
||||||
|
def split_string(value, key):
|
||||||
|
"""
|
||||||
|
Splits a given string
|
||||||
|
"""
|
||||||
|
return value.split(key)
|
||||||
|
|
||||||
|
|
||||||
@register.simple_tag
|
@register.simple_tag
|
||||||
def public_site_url(url_path):
|
def public_site_url(url_path):
|
||||||
"""Make a full URL for this path at our public site.
|
"""Make a full URL for this path at our public site.
|
||||||
|
|
|
@ -116,6 +116,31 @@ class GenericTestHelper(TestCase):
|
||||||
self.url = url
|
self.url = url
|
||||||
self.client = client
|
self.client = client
|
||||||
|
|
||||||
|
def assert_response_contains_distinct_values(self, response, expected_values):
|
||||||
|
"""
|
||||||
|
Asserts that each specified value appears exactly once in the response.
|
||||||
|
|
||||||
|
This method iterates over a list of tuples, where each tuple contains a field name
|
||||||
|
and its expected value. It then performs an assertContains check for each value,
|
||||||
|
ensuring that each value appears exactly once in the response.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
- response: The HttpResponse object to inspect.
|
||||||
|
- expected_values: A list of tuples, where each tuple contains:
|
||||||
|
- field: The name of the field (used for subTest identification).
|
||||||
|
- value: The expected value to check for in the response.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
expected_values = [
|
||||||
|
("title", "Treat inspector</td>"),
|
||||||
|
("email", "meoward.jones@igorville.gov</td>"),
|
||||||
|
]
|
||||||
|
self.assert_response_contains_distinct_values(response, expected_values)
|
||||||
|
"""
|
||||||
|
for field, value in expected_values:
|
||||||
|
with self.subTest(field=field, expected_value=value):
|
||||||
|
self.assertContains(response, value, count=1)
|
||||||
|
|
||||||
def assert_table_sorted(self, o_index, sort_fields):
|
def assert_table_sorted(self, o_index, sort_fields):
|
||||||
"""
|
"""
|
||||||
This helper function validates the sorting functionality of a Django Admin table view.
|
This helper function validates the sorting functionality of a Django Admin table view.
|
||||||
|
@ -179,7 +204,6 @@ class GenericTestHelper(TestCase):
|
||||||
{"action": "delete_selected", "select_across": selected_across, "index": index, "_selected_action": "23"},
|
{"action": "delete_selected", "select_across": selected_across, "index": index, "_selected_action": "23"},
|
||||||
follow=True,
|
follow=True,
|
||||||
)
|
)
|
||||||
print(f"what is the response? {response}")
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ from datetime import date
|
||||||
from django.test import TestCase, RequestFactory, Client, override_settings
|
from django.test import TestCase, RequestFactory, Client, override_settings
|
||||||
from django.contrib.admin.sites import AdminSite
|
from django.contrib.admin.sites import AdminSite
|
||||||
from contextlib import ExitStack
|
from contextlib import ExitStack
|
||||||
|
from api.tests.common import less_console_noise_decorator
|
||||||
from django_webtest import WebTest # type: ignore
|
from django_webtest import WebTest # type: ignore
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
@ -1222,6 +1223,195 @@ 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.requested_domain.name, domain_request.approved_domain.name)
|
self.assertEqual(domain_request.requested_domain.name, domain_request.approved_domain.name)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_sticky_submit_row(self):
|
||||||
|
"""Test that the change_form template contains strings indicative of the customization
|
||||||
|
of the sticky submit bar.
|
||||||
|
|
||||||
|
Also test that it does NOT contain a CSS class meant for analysts only when logged in as superuser."""
|
||||||
|
|
||||||
|
# make sure there is no user with this email
|
||||||
|
EMAIL = "mayor@igorville.gov"
|
||||||
|
User.objects.filter(email=EMAIL).delete()
|
||||||
|
self.client.force_login(self.superuser)
|
||||||
|
|
||||||
|
# Create a sample domain request
|
||||||
|
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
|
||||||
|
|
||||||
|
# Create a mock request
|
||||||
|
request = self.client.post("/admin/registrar/domainrequest/{}/change/".format(domain_request.pk))
|
||||||
|
|
||||||
|
# Since we're using client to mock the request, we can only test against
|
||||||
|
# non-interpolated values
|
||||||
|
expected_content = "<strong>Requested domain:</strong>"
|
||||||
|
expected_content2 = '<span class="scroll-indicator"></span>'
|
||||||
|
expected_content3 = '<div class="submit-row-wrapper">'
|
||||||
|
not_expected_content = "submit-row-wrapper--analyst-view>"
|
||||||
|
self.assertContains(request, expected_content)
|
||||||
|
self.assertContains(request, expected_content2)
|
||||||
|
self.assertContains(request, expected_content3)
|
||||||
|
self.assertNotContains(request, not_expected_content)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_sticky_submit_row_has_extra_class_for_analysts(self):
|
||||||
|
"""Test that the change_form template contains strings indicative of the customization
|
||||||
|
of the sticky submit bar.
|
||||||
|
|
||||||
|
Also test that it DOES contain a CSS class meant for analysts only when logged in as analyst."""
|
||||||
|
|
||||||
|
# make sure there is no user with this email
|
||||||
|
EMAIL = "mayor@igorville.gov"
|
||||||
|
User.objects.filter(email=EMAIL).delete()
|
||||||
|
self.client.force_login(self.staffuser)
|
||||||
|
|
||||||
|
# Create a sample domain request
|
||||||
|
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
|
||||||
|
|
||||||
|
# Create a mock request
|
||||||
|
request = self.client.post("/admin/registrar/domainrequest/{}/change/".format(domain_request.pk))
|
||||||
|
|
||||||
|
# Since we're using client to mock the request, we can only test against
|
||||||
|
# non-interpolated values
|
||||||
|
expected_content = "<strong>Requested domain:</strong>"
|
||||||
|
expected_content2 = '<span class="scroll-indicator"></span>'
|
||||||
|
expected_content3 = '<div class="submit-row-wrapper submit-row-wrapper--analyst-view">'
|
||||||
|
self.assertContains(request, expected_content)
|
||||||
|
self.assertContains(request, expected_content2)
|
||||||
|
self.assertContains(request, expected_content3)
|
||||||
|
|
||||||
|
def test_other_contacts_has_readonly_link(self):
|
||||||
|
"""Tests if the readonly other_contacts field has links"""
|
||||||
|
|
||||||
|
# Create a fake domain request
|
||||||
|
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
|
||||||
|
|
||||||
|
# Get the other contact
|
||||||
|
other_contact = domain_request.other_contacts.all().first()
|
||||||
|
|
||||||
|
p = "userpass"
|
||||||
|
self.client.login(username="staffuser", password=p)
|
||||||
|
response = self.client.get(
|
||||||
|
"/admin/registrar/domainrequest/{}/change/".format(domain_request.pk),
|
||||||
|
follow=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Make sure the page loaded, and that we're on the right page
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, domain_request.requested_domain.name)
|
||||||
|
|
||||||
|
# Check that the page contains the url we expect
|
||||||
|
expected_href = reverse("admin:registrar_contact_change", args=[other_contact.id])
|
||||||
|
self.assertContains(response, expected_href)
|
||||||
|
|
||||||
|
# Check that the page contains the link we expect.
|
||||||
|
# Since the url is dynamic (populated by JS), we can test for its existence
|
||||||
|
# by checking for the end tag.
|
||||||
|
expected_url = "Testy Tester</a>"
|
||||||
|
self.assertContains(response, expected_url)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_other_websites_has_readonly_link(self):
|
||||||
|
"""Tests if the readonly other_websites field has links"""
|
||||||
|
|
||||||
|
# Create a fake 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,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Make sure the page loaded, and that we're on the right page
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, domain_request.requested_domain.name)
|
||||||
|
|
||||||
|
# Check that the page contains the link we expect.
|
||||||
|
expected_url = '<a href="city.com" class="padding-top-1 current-website__1">city.com</a>'
|
||||||
|
self.assertContains(response, expected_url)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_contact_fields_have_detail_table(self):
|
||||||
|
"""Tests if the contact fields have the detail table which displays title, email, and phone"""
|
||||||
|
|
||||||
|
# Create fake creator
|
||||||
|
_creator = User.objects.create(
|
||||||
|
username="MrMeoward",
|
||||||
|
first_name="Meoward",
|
||||||
|
last_name="Jones",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Due to the relation between User <==> Contact,
|
||||||
|
# the underlying contact has to be modified this way.
|
||||||
|
_creator.contact.email = "meoward.jones@igorville.gov"
|
||||||
|
_creator.contact.phone = "(555) 123 12345"
|
||||||
|
_creator.contact.title = "Treat inspector"
|
||||||
|
_creator.contact.save()
|
||||||
|
|
||||||
|
# Create a fake domain request
|
||||||
|
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=_creator)
|
||||||
|
|
||||||
|
p = "userpass"
|
||||||
|
self.client.login(username="staffuser", password=p)
|
||||||
|
response = self.client.get(
|
||||||
|
"/admin/registrar/domainrequest/{}/change/".format(domain_request.pk),
|
||||||
|
follow=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Make sure the page loaded, and that we're on the right page
|
||||||
|
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
|
||||||
|
|
||||||
|
# == Check for the creator == #
|
||||||
|
|
||||||
|
# Check for the right title, email, and phone number in the response.
|
||||||
|
expected_creator_fields = [
|
||||||
|
# Field, expected value
|
||||||
|
("title", "Treat inspector"),
|
||||||
|
("email", "meoward.jones@igorville.gov"),
|
||||||
|
("phone", "(555) 123 12345"),
|
||||||
|
]
|
||||||
|
self.test_helper.assert_response_contains_distinct_values(response, expected_creator_fields)
|
||||||
|
|
||||||
|
# Check for the field itself
|
||||||
|
self.assertContains(response, "Meoward Jones")
|
||||||
|
|
||||||
|
# == Check for the submitter == #
|
||||||
|
expected_submitter_fields = [
|
||||||
|
# Field, expected value
|
||||||
|
("title", "Admin Tester"),
|
||||||
|
("email", "mayor@igorville.gov"),
|
||||||
|
("phone", "(555) 555 5556"),
|
||||||
|
]
|
||||||
|
self.test_helper.assert_response_contains_distinct_values(response, expected_submitter_fields)
|
||||||
|
self.assertContains(response, "Testy2 Tester2")
|
||||||
|
|
||||||
|
# == Check for the authorizing_official == #
|
||||||
|
expected_ao_fields = [
|
||||||
|
# Field, expected value
|
||||||
|
("title", "Chief Tester"),
|
||||||
|
("email", "testy@town.com"),
|
||||||
|
("phone", "(555) 555 5555"),
|
||||||
|
]
|
||||||
|
self.test_helper.assert_response_contains_distinct_values(response, expected_ao_fields)
|
||||||
|
|
||||||
|
# count=5 because the underlying domain has two users with this name.
|
||||||
|
# The dropdown has 3 of these.
|
||||||
|
self.assertContains(response, "Testy Tester", count=5)
|
||||||
|
|
||||||
|
# == Test the other_employees field == #
|
||||||
|
expected_other_employees_fields = [
|
||||||
|
# Field, expected value
|
||||||
|
("title", "Another Tester"),
|
||||||
|
("email", "testy2@town.com"),
|
||||||
|
("phone", "(555) 555 5557"),
|
||||||
|
]
|
||||||
|
self.test_helper.assert_response_contains_distinct_values(response, expected_other_employees_fields)
|
||||||
|
|
||||||
def test_save_model_sets_restricted_status_on_user(self):
|
def test_save_model_sets_restricted_status_on_user(self):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
# make sure there is no user with this email
|
# make sure there is no user with this email
|
||||||
|
@ -1305,6 +1495,7 @@ class TestDomainRequestAdmin(MockEppLib):
|
||||||
self.assertContains(response, "Yes, select ineligible status")
|
self.assertContains(response, "Yes, select ineligible status")
|
||||||
|
|
||||||
def test_readonly_when_restricted_creator(self):
|
def test_readonly_when_restricted_creator(self):
|
||||||
|
self.maxDiff = None
|
||||||
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)
|
||||||
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
with boto3_mocking.clients.handler_for("sesv2", self.mock_client):
|
||||||
|
@ -1317,6 +1508,9 @@ class TestDomainRequestAdmin(MockEppLib):
|
||||||
readonly_fields = self.admin.get_readonly_fields(request, domain_request)
|
readonly_fields = self.admin.get_readonly_fields(request, domain_request)
|
||||||
|
|
||||||
expected_fields = [
|
expected_fields = [
|
||||||
|
"other_contacts",
|
||||||
|
"current_websites",
|
||||||
|
"alternative_domains",
|
||||||
"id",
|
"id",
|
||||||
"created_at",
|
"created_at",
|
||||||
"updated_at",
|
"updated_at",
|
||||||
|
@ -1349,8 +1543,6 @@ class TestDomainRequestAdmin(MockEppLib):
|
||||||
"is_policy_acknowledged",
|
"is_policy_acknowledged",
|
||||||
"submission_date",
|
"submission_date",
|
||||||
"notes",
|
"notes",
|
||||||
"current_websites",
|
|
||||||
"other_contacts",
|
|
||||||
"alternative_domains",
|
"alternative_domains",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1364,6 +1556,9 @@ class TestDomainRequestAdmin(MockEppLib):
|
||||||
readonly_fields = self.admin.get_readonly_fields(request)
|
readonly_fields = self.admin.get_readonly_fields(request)
|
||||||
|
|
||||||
expected_fields = [
|
expected_fields = [
|
||||||
|
"other_contacts",
|
||||||
|
"current_websites",
|
||||||
|
"alternative_domains",
|
||||||
"creator",
|
"creator",
|
||||||
"about_your_organization",
|
"about_your_organization",
|
||||||
"requested_domain",
|
"requested_domain",
|
||||||
|
@ -1385,7 +1580,11 @@ class TestDomainRequestAdmin(MockEppLib):
|
||||||
|
|
||||||
readonly_fields = self.admin.get_readonly_fields(request)
|
readonly_fields = self.admin.get_readonly_fields(request)
|
||||||
|
|
||||||
expected_fields = []
|
expected_fields = [
|
||||||
|
"other_contacts",
|
||||||
|
"current_websites",
|
||||||
|
"alternative_domains",
|
||||||
|
]
|
||||||
|
|
||||||
self.assertEqual(readonly_fields, expected_fields)
|
self.assertEqual(readonly_fields, expected_fields)
|
||||||
|
|
||||||
|
@ -1815,6 +2014,125 @@ class TestDomainInformationAdmin(TestCase):
|
||||||
Contact.objects.all().delete()
|
Contact.objects.all().delete()
|
||||||
User.objects.all().delete()
|
User.objects.all().delete()
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_other_contacts_has_readonly_link(self):
|
||||||
|
"""Tests if the readonly other_contacts field has links"""
|
||||||
|
|
||||||
|
# Create a fake domain request and domain
|
||||||
|
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW)
|
||||||
|
domain_request.approve()
|
||||||
|
domain_info = DomainInformation.objects.filter(domain=domain_request.approved_domain).get()
|
||||||
|
|
||||||
|
# Get the other contact
|
||||||
|
other_contact = domain_info.other_contacts.all().first()
|
||||||
|
|
||||||
|
p = "userpass"
|
||||||
|
self.client.login(username="staffuser", password=p)
|
||||||
|
|
||||||
|
response = self.client.get(
|
||||||
|
"/admin/registrar/domaininformation/{}/change/".format(domain_info.pk),
|
||||||
|
follow=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Make sure the page loaded, and that we're on the right page
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, domain_info.domain.name)
|
||||||
|
|
||||||
|
# Check that the page contains the url we expect
|
||||||
|
expected_href = reverse("admin:registrar_contact_change", args=[other_contact.id])
|
||||||
|
self.assertContains(response, expected_href)
|
||||||
|
|
||||||
|
# Check that the page contains the link we expect.
|
||||||
|
# Since the url is dynamic (populated by JS), we can test for its existence
|
||||||
|
# by checking for the end tag.
|
||||||
|
expected_url = "Testy Tester</a>"
|
||||||
|
self.assertContains(response, expected_url)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_contact_fields_have_detail_table(self):
|
||||||
|
"""Tests if the contact fields have the detail table which displays title, email, and phone"""
|
||||||
|
|
||||||
|
# Create fake creator
|
||||||
|
_creator = User.objects.create(
|
||||||
|
username="MrMeoward",
|
||||||
|
first_name="Meoward",
|
||||||
|
last_name="Jones",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Due to the relation between User <==> Contact,
|
||||||
|
# the underlying contact has to be modified this way.
|
||||||
|
_creator.contact.email = "meoward.jones@igorville.gov"
|
||||||
|
_creator.contact.phone = "(555) 123 12345"
|
||||||
|
_creator.contact.title = "Treat inspector"
|
||||||
|
_creator.contact.save()
|
||||||
|
|
||||||
|
# Create a fake domain request
|
||||||
|
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=_creator)
|
||||||
|
domain_request.approve()
|
||||||
|
domain_info = DomainInformation.objects.filter(domain=domain_request.approved_domain).get()
|
||||||
|
|
||||||
|
p = "userpass"
|
||||||
|
self.client.login(username="staffuser", password=p)
|
||||||
|
response = self.client.get(
|
||||||
|
"/admin/registrar/domaininformation/{}/change/".format(domain_info.pk),
|
||||||
|
follow=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Make sure the page loaded, and that we're on the right page
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertContains(response, domain_info.domain.name)
|
||||||
|
|
||||||
|
# Check that the modal has the right content
|
||||||
|
# Check for the header
|
||||||
|
|
||||||
|
# == Check for the creator == #
|
||||||
|
|
||||||
|
# Check for the right title, email, and phone number in the response.
|
||||||
|
# We only need to check for the end tag
|
||||||
|
# (Otherwise this test will fail if we change classes, etc)
|
||||||
|
expected_creator_fields = [
|
||||||
|
# Field, expected value
|
||||||
|
("title", "Treat inspector"),
|
||||||
|
("email", "meoward.jones@igorville.gov"),
|
||||||
|
("phone", "(555) 123 12345"),
|
||||||
|
]
|
||||||
|
self.test_helper.assert_response_contains_distinct_values(response, expected_creator_fields)
|
||||||
|
|
||||||
|
# Check for the field itself
|
||||||
|
self.assertContains(response, "Meoward Jones")
|
||||||
|
|
||||||
|
# == Check for the submitter == #
|
||||||
|
expected_submitter_fields = [
|
||||||
|
# Field, expected value
|
||||||
|
("title", "Admin Tester"),
|
||||||
|
("email", "mayor@igorville.gov"),
|
||||||
|
("phone", "(555) 555 5556"),
|
||||||
|
]
|
||||||
|
self.test_helper.assert_response_contains_distinct_values(response, expected_submitter_fields)
|
||||||
|
self.assertContains(response, "Testy2 Tester2")
|
||||||
|
|
||||||
|
# == Check for the authorizing_official == #
|
||||||
|
expected_ao_fields = [
|
||||||
|
# Field, expected value
|
||||||
|
("title", "Chief Tester"),
|
||||||
|
("email", "testy@town.com"),
|
||||||
|
("phone", "(555) 555 5555"),
|
||||||
|
]
|
||||||
|
self.test_helper.assert_response_contains_distinct_values(response, expected_ao_fields)
|
||||||
|
|
||||||
|
# count=5 because the underlying domain has two users with this name.
|
||||||
|
# The dropdown has 3 of these.
|
||||||
|
self.assertContains(response, "Testy Tester", count=5)
|
||||||
|
|
||||||
|
# == Test the other_employees field == #
|
||||||
|
expected_other_employees_fields = [
|
||||||
|
# Field, expected value
|
||||||
|
("title", "Another Tester"),
|
||||||
|
("email", "testy2@town.com"),
|
||||||
|
("phone", "(555) 555 5557"),
|
||||||
|
]
|
||||||
|
self.test_helper.assert_response_contains_distinct_values(response, expected_other_employees_fields)
|
||||||
|
|
||||||
def test_readonly_fields_for_analyst(self):
|
def test_readonly_fields_for_analyst(self):
|
||||||
"""Ensures that analysts have their permissions setup correctly"""
|
"""Ensures that analysts have their permissions setup correctly"""
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
|
@ -1824,6 +2142,7 @@ class TestDomainInformationAdmin(TestCase):
|
||||||
readonly_fields = self.admin.get_readonly_fields(request)
|
readonly_fields = self.admin.get_readonly_fields(request)
|
||||||
|
|
||||||
expected_fields = [
|
expected_fields = [
|
||||||
|
"other_contacts",
|
||||||
"creator",
|
"creator",
|
||||||
"type_of_work",
|
"type_of_work",
|
||||||
"more_organization_information",
|
"more_organization_information",
|
||||||
|
|
|
@ -307,7 +307,12 @@ class UserDeleteDomainRolePermission(PermissionsLoginMixin):
|
||||||
domain=domain_pk,
|
domain=domain_pk,
|
||||||
domain__permissions__user=self.request.user,
|
domain__permissions__user=self.request.user,
|
||||||
).exists()
|
).exists()
|
||||||
if not has_delete_permission:
|
|
||||||
|
user_is_analyst_or_superuser = self.request.user.has_perm(
|
||||||
|
"registrar.analyst_access_permission"
|
||||||
|
) or self.request.user.has_perm("registrar.full_access_permission")
|
||||||
|
|
||||||
|
if not (has_delete_permission or user_is_analyst_or_superuser):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Check if more than one manager exists on the domain.
|
# Check if more than one manager exists on the domain.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue