mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-27 21:16:28 +02:00
Merge remote-tracking branch 'origin/main' into ms/1768-status-change-notifications
This commit is contained in:
commit
c657b9e666
76 changed files with 1388 additions and 798 deletions
|
@ -1995,7 +1995,7 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
|||
@admin.display(description=_("Requested Domain"))
|
||||
def custom_requested_domain(self, obj):
|
||||
# Example: Show different icons based on `status`
|
||||
url = reverse("admin:registrar_domainrequest_changelist") + f"?portfolio={obj.id}"
|
||||
url = reverse("admin:registrar_domainrequest_changelist") + f"{obj.id}"
|
||||
text = obj.requested_domain
|
||||
if obj.portfolio:
|
||||
return format_html('<a href="{}"><img src="/public/admin/img/icon-yes.svg"> {}</a>', url, text)
|
||||
|
|
15
src/registrar/assets/src/js/getgov/domain-dnssec.js
Normal file
15
src/registrar/assets/src/js/getgov/domain-dnssec.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { submitForm } from './helpers.js';
|
||||
|
||||
export function initDomainDNSSEC() {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
let domain_dnssec_page = document.getElementById("domain-dnssec");
|
||||
if (domain_dnssec_page) {
|
||||
const button = document.getElementById("disable-dnssec-button");
|
||||
if (button) {
|
||||
button.addEventListener("click", function () {
|
||||
submitForm("disable-dnssec-form");
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
27
src/registrar/assets/src/js/getgov/domain-dsdata.js
Normal file
27
src/registrar/assets/src/js/getgov/domain-dsdata.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { submitForm } from './helpers.js';
|
||||
|
||||
export function initDomainDSData() {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
let domain_dsdata_page = document.getElementById("domain-dsdata");
|
||||
if (domain_dsdata_page) {
|
||||
const override_button = document.getElementById("disable-override-click-button");
|
||||
const cancel_button = document.getElementById("btn-cancel-click-button");
|
||||
const cancel_close_button = document.getElementById("btn-cancel-click-close-button");
|
||||
if (override_button) {
|
||||
override_button.addEventListener("click", function () {
|
||||
submitForm("disable-override-click-form");
|
||||
});
|
||||
}
|
||||
if (cancel_button) {
|
||||
cancel_button.addEventListener("click", function () {
|
||||
submitForm("btn-cancel-click-form");
|
||||
});
|
||||
}
|
||||
if (cancel_close_button) {
|
||||
cancel_close_button.addEventListener("click", function () {
|
||||
submitForm("btn-cancel-click-form");
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
20
src/registrar/assets/src/js/getgov/domain-managers.js
Normal file
20
src/registrar/assets/src/js/getgov/domain-managers.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
import { submitForm } from './helpers.js';
|
||||
|
||||
export function initDomainManagersPage() {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
let domain_managers_page = document.getElementById("domain-managers");
|
||||
if (domain_managers_page) {
|
||||
// Add event listeners for all buttons matching user-delete-button-{NUMBER}
|
||||
const deleteButtons = document.querySelectorAll('[id^="user-delete-button-"]'); // Select buttons with ID starting with "user-delete-button-"
|
||||
deleteButtons.forEach((button) => {
|
||||
const buttonId = button.id; // e.g., "user-delete-button-1"
|
||||
const number = buttonId.split('-').pop(); // Extract the NUMBER part
|
||||
const formId = `user-delete-form-${number}`; // Generate the corresponding form ID
|
||||
|
||||
button.addEventListener("click", function () {
|
||||
submitForm(formId); // Pass the form ID to submitForm
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
12
src/registrar/assets/src/js/getgov/domain-request-form.js
Normal file
12
src/registrar/assets/src/js/getgov/domain-request-form.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { submitForm } from './helpers.js';
|
||||
|
||||
export function initDomainRequestForm() {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const button = document.getElementById("domain-request-form-submit-button");
|
||||
if (button) {
|
||||
button.addEventListener("click", function () {
|
||||
submitForm("submit-domain-request-form");
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
19
src/registrar/assets/src/js/getgov/form-errors.js
Normal file
19
src/registrar/assets/src/js/getgov/form-errors.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
export function initFormErrorHandling() {
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const errorSummary = document.getElementById('form-errors');
|
||||
const firstErrorField = document.querySelector('.usa-input--error');
|
||||
if (firstErrorField) {
|
||||
// Scroll to the first field in error
|
||||
firstErrorField.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
|
||||
// Add focus to the first field in error
|
||||
setTimeout(() => {
|
||||
firstErrorField.focus();
|
||||
}, 50);
|
||||
} else if (errorSummary) {
|
||||
// Scroll to the error summary
|
||||
errorSummary.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
|
||||
});
|
||||
}
|
|
@ -75,3 +75,16 @@ export function debounce(handler, cooldown=600) {
|
|||
export function getCsrfToken() {
|
||||
return document.querySelector('input[name="csrfmiddlewaretoken"]').value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to submit a form
|
||||
* @param {} form_id - the id of the form to be submitted
|
||||
*/
|
||||
export function submitForm(form_id) {
|
||||
let form = document.getElementById(form_id);
|
||||
if (form) {
|
||||
form.submit();
|
||||
} else {
|
||||
console.error("Form '" + form_id + "' not found.");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,11 @@ import { initMembersTable } from './table-members.js';
|
|||
import { initMemberDomainsTable } from './table-member-domains.js';
|
||||
import { initEditMemberDomainsTable } from './table-edit-member-domains.js';
|
||||
import { initPortfolioNewMemberPageToggle, initAddNewMemberPageListeners, initPortfolioMemberPageRadio } from './portfolio-member-page.js';
|
||||
import { initDomainRequestForm } from './domain-request-form.js';
|
||||
import { initDomainManagersPage } from './domain-managers.js';
|
||||
import { initDomainDSData } from './domain-dsdata.js';
|
||||
import { initDomainDNSSEC } from './domain-dnssec.js';
|
||||
import { initFormErrorHandling } from './form-errors.js';
|
||||
|
||||
initDomainValidators();
|
||||
|
||||
|
@ -36,6 +41,13 @@ initMembersTable();
|
|||
initMemberDomainsTable();
|
||||
initEditMemberDomainsTable();
|
||||
|
||||
initDomainRequestForm();
|
||||
initDomainManagersPage();
|
||||
initDomainDSData();
|
||||
initDomainDNSSEC();
|
||||
|
||||
initFormErrorHandling();
|
||||
|
||||
// Init the portfolio new member page
|
||||
initPortfolioMemberPageRadio();
|
||||
initPortfolioNewMemberPageToggle();
|
||||
|
|
|
@ -495,7 +495,8 @@ export class BaseTable {
|
|||
// Add event listeners to table headers for sorting
|
||||
initializeTableHeaders() {
|
||||
this.tableHeaders.forEach(header => {
|
||||
header.addEventListener('click', () => {
|
||||
header.addEventListener('click', event => {
|
||||
let button = header.querySelector('.usa-table__header__button')
|
||||
const sortBy = header.getAttribute('data-sortable');
|
||||
let order = 'asc';
|
||||
// sort order will be ascending, unless the currently sorted column is ascending, and the user
|
||||
|
@ -505,6 +506,13 @@ export class BaseTable {
|
|||
}
|
||||
// load the results with the updated sort
|
||||
this.loadTable(1, sortBy, order);
|
||||
// If the click occurs outside of the button, need to simulate a button click in order
|
||||
// for USWDS listener on the button to execute.
|
||||
// Check first to see if click occurs outside of the button
|
||||
if (!button.contains(event.target)) {
|
||||
// Simulate a button click
|
||||
button.click();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -31,6 +31,9 @@ export class DomainsTable extends BaseTable {
|
|||
</td>
|
||||
`
|
||||
}
|
||||
const isExpiring = domain.state_display === "Expiring soon"
|
||||
const iconType = isExpiring ? "error_outline" : "info_outline";
|
||||
const iconColor = isExpiring ? "text-secondary-vivid" : "text-accent-cool"
|
||||
row.innerHTML = `
|
||||
<th scope="row" role="rowheader" data-label="Domain name">
|
||||
${domain.name}
|
||||
|
@ -41,14 +44,14 @@ export class DomainsTable extends BaseTable {
|
|||
<td data-label="Status">
|
||||
${domain.state_display}
|
||||
<svg
|
||||
class="usa-icon usa-tooltip usa-tooltip--registrar text-middle margin-bottom-05 text-accent-cool no-click-outline-and-cursor-help"
|
||||
class="usa-icon usa-tooltip usa-tooltip--registrar text-middle margin-bottom-05 ${iconColor} no-click-outline-and-cursor-help"
|
||||
data-position="top"
|
||||
title="${domain.get_state_help_text}"
|
||||
focusable="true"
|
||||
aria-label="${domain.get_state_help_text}"
|
||||
role="tooltip"
|
||||
>
|
||||
<use aria-hidden="true" xlink:href="/public/img/sprite.svg#info_outline"></use>
|
||||
<use aria-hidden="true" xlink:href="/public/img/sprite.svg#${iconType}"></use>
|
||||
</svg>
|
||||
</td>
|
||||
${markupForSuborganizationRow}
|
||||
|
@ -77,3 +80,30 @@ export function initDomainsTable() {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
// For clicking the "Expiring" checkbox
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const expiringLink = document.getElementById('link-expiring-domains');
|
||||
|
||||
if (expiringLink) {
|
||||
// Grab the selection for the status filter by
|
||||
const statusCheckboxes = document.querySelectorAll('input[name="filter-status"]');
|
||||
|
||||
expiringLink.addEventListener('click', (event) => {
|
||||
event.preventDefault();
|
||||
// Loop through all statuses
|
||||
statusCheckboxes.forEach(checkbox => {
|
||||
// To find the for checkbox for "Expiring soon"
|
||||
if (checkbox.value === "expiring") {
|
||||
// If the checkbox is not already checked, check it
|
||||
if (!checkbox.checked) {
|
||||
checkbox.checked = true;
|
||||
// Do the checkbox action
|
||||
let event = new Event('change');
|
||||
checkbox.dispatchEvent(event)
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
|
@ -176,7 +176,16 @@ html[data-theme="dark"] {
|
|||
color: var(--primary-fg);
|
||||
}
|
||||
|
||||
// Reset the USWDS styles for alerts
|
||||
@include at-media(desktop) {
|
||||
.dashboard .usa-alert__body--widescreen {
|
||||
padding-left: 4rem !important;
|
||||
}
|
||||
|
||||
.dashboard .usa-alert__body--widescreen::before {
|
||||
left: 1.5rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
#branding h1,
|
||||
h1, h2, h3,
|
||||
|
|
|
@ -1,21 +1,18 @@
|
|||
@use "uswds-core" as *;
|
||||
@use "base" as *;
|
||||
|
||||
// Fixes some font size disparities with the Figma
|
||||
// for usa-alert alert elements
|
||||
.usa-alert {
|
||||
.usa-alert__heading.larger-font-sizing {
|
||||
font-size: units(3);
|
||||
}
|
||||
}
|
||||
|
||||
.usa-alert__text.measure-none {
|
||||
max-width: measure(none);
|
||||
}
|
||||
/*----------------
|
||||
Alert Layout
|
||||
-----------------*/
|
||||
|
||||
// The icon was off center for some reason
|
||||
// Fixes that issue
|
||||
@media (min-width: 64em){
|
||||
@include at-media(desktop) {
|
||||
// NOTE: !important is used because _font.scss overrides this
|
||||
.usa-alert__body--widescreen {
|
||||
max-width: $widescreen-max-width !important;
|
||||
}
|
||||
.usa-alert--warning{
|
||||
.usa-alert__body::before {
|
||||
left: 1rem !important;
|
||||
|
@ -24,13 +21,29 @@
|
|||
.usa-alert__body.margin-left-1 {
|
||||
margin-left: 0.5rem!important;
|
||||
}
|
||||
|
||||
.usa-alert__body--widescreen::before {
|
||||
left: 4rem !important;
|
||||
}
|
||||
.usa-alert__body--widescreen {
|
||||
padding-left: 7rem!important;
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: !important is used because _font.scss overrides this
|
||||
.usa-alert__body--widescreen {
|
||||
max-width: $widescreen-max-width !important;
|
||||
/*----------------
|
||||
Alert Fonts
|
||||
-----------------*/
|
||||
// Fixes some font size disparities with the Figma
|
||||
// for usa-alert alert elements
|
||||
.usa-alert {
|
||||
.usa-alert__heading.larger-font-sizing {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
/*----------------
|
||||
Alert Coloring
|
||||
-----------------*/
|
||||
.usa-site-alert--hot-pink {
|
||||
.usa-alert {
|
||||
background-color: $hot-pink;
|
||||
|
@ -47,3 +60,4 @@
|
|||
background-color: color('base-darkest');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
@use "cisa_colors" as *;
|
||||
|
||||
$widescreen-max-width: 1920px;
|
||||
$widescreen-x-padding: 4.5rem;
|
||||
|
||||
$hot-pink: #FFC3F9;
|
||||
|
||||
/* Styles for making visible to screen reader / AT users only. */
|
||||
|
@ -252,6 +254,15 @@ abbr[title] {
|
|||
max-width: $widescreen-max-width;
|
||||
}
|
||||
|
||||
// This is used in cases where we want to align content to widescreen margins
|
||||
// but we don't want the content itself to have widescreen widths
|
||||
@include at-media(desktop) {
|
||||
.padding-x--widescreen {
|
||||
padding-left: $widescreen-x-padding !important;
|
||||
padding-right: $widescreen-x-padding !important;
|
||||
}
|
||||
}
|
||||
|
||||
.margin-right-neg-4px {
|
||||
margin-right: -4px;
|
||||
}
|
||||
|
@ -266,3 +277,7 @@ abbr[title] {
|
|||
height: 1.5em;
|
||||
width: 1.5em;
|
||||
}
|
||||
|
||||
.maxw-fit-content {
|
||||
max-width: fit-content;
|
||||
}
|
|
@ -6,3 +6,21 @@
|
|||
.usa-identifier__container--widescreen {
|
||||
max-width: $widescreen-max-width !important;
|
||||
}
|
||||
|
||||
|
||||
// NOTE: !important is used because we are overriding default
|
||||
// USWDS paddings in a few locations
|
||||
@include at-media(desktop) {
|
||||
.grid-container--widescreen {
|
||||
padding-left: $widescreen-x-padding !important;
|
||||
padding-right: $widescreen-x-padding !important;
|
||||
}
|
||||
}
|
||||
|
||||
// matches max-width to equal the max-width of .grid-container
|
||||
// used to trick the eye into thinking we have left-aligned a
|
||||
// regular grid-container within a widescreen (see instances
|
||||
// where is_widescreen_centered is used in the html).
|
||||
.max-width--grid-container {
|
||||
max-width: 960px;
|
||||
}
|
|
@ -110,8 +110,8 @@
|
|||
}
|
||||
}
|
||||
.usa-nav__secondary {
|
||||
// I don't know why USWDS has this at 2 rem, which puts it out of alignment
|
||||
right: 3rem;
|
||||
right: 1rem;
|
||||
padding-right: $widescreen-x-padding;
|
||||
color: color('white');
|
||||
bottom: 4.3rem;
|
||||
.usa-nav-link,
|
||||
|
|
|
@ -88,8 +88,14 @@ th {
|
|||
}
|
||||
|
||||
@include at-media(tablet-lg) {
|
||||
th[data-sortable]:not([aria-sort]) .usa-table__header__button {
|
||||
th[data-sortable] .usa-table__header__button {
|
||||
right: auto;
|
||||
|
||||
&[aria-sort=ascending],
|
||||
&[aria-sort=descending],
|
||||
&:not([aria-sort]) {
|
||||
right: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -251,7 +251,7 @@ TEMPLATES = [
|
|||
"registrar.context_processors.org_user_status",
|
||||
"registrar.context_processors.add_path_to_context",
|
||||
"registrar.context_processors.portfolio_permissions",
|
||||
"registrar.context_processors.is_widescreen_mode",
|
||||
"registrar.context_processors.is_widescreen_centered",
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -69,9 +69,19 @@ def portfolio_permissions(request):
|
|||
"has_organization_requests_flag": False,
|
||||
"has_organization_members_flag": False,
|
||||
"is_portfolio_admin": False,
|
||||
"has_domain_renewal_flag": False,
|
||||
}
|
||||
try:
|
||||
portfolio = request.session.get("portfolio")
|
||||
|
||||
# These feature flags will display and doesn't depend on portfolio
|
||||
portfolio_context.update(
|
||||
{
|
||||
"has_organization_feature_flag": True,
|
||||
"has_domain_renewal_flag": request.user.has_domain_renewal_flag(),
|
||||
}
|
||||
)
|
||||
|
||||
# Linting: line too long
|
||||
view_suborg = request.user.has_view_suborganization_portfolio_permission(portfolio)
|
||||
edit_suborg = request.user.has_edit_suborganization_portfolio_permission(portfolio)
|
||||
|
@ -90,6 +100,7 @@ def portfolio_permissions(request):
|
|||
"has_organization_requests_flag": request.user.has_organization_requests_flag(),
|
||||
"has_organization_members_flag": request.user.has_organization_members_flag(),
|
||||
"is_portfolio_admin": request.user.is_portfolio_admin(portfolio),
|
||||
"has_domain_renewal_flag": request.user.has_domain_renewal_flag(),
|
||||
}
|
||||
return portfolio_context
|
||||
|
||||
|
@ -98,31 +109,21 @@ def portfolio_permissions(request):
|
|||
return portfolio_context
|
||||
|
||||
|
||||
def is_widescreen_mode(request):
|
||||
widescreen_paths = [] # If this list is meant to include specific paths, populate it.
|
||||
portfolio_widescreen_paths = [
|
||||
def is_widescreen_centered(request):
|
||||
include_paths = [
|
||||
"/domains/",
|
||||
"/requests/",
|
||||
"/request/",
|
||||
"/no-organization-requests/",
|
||||
"/no-organization-domains/",
|
||||
"/domain-request/",
|
||||
"/members/",
|
||||
]
|
||||
# widescreen_paths can be a bear as it trickles down sub-urls. exclude_paths gives us a way out.
|
||||
exclude_paths = [
|
||||
"/domains/edit",
|
||||
"members/new-member/",
|
||||
]
|
||||
|
||||
# Check if the current path matches a widescreen path or the root path.
|
||||
is_widescreen = any(path in request.path for path in widescreen_paths) or request.path == "/"
|
||||
is_excluded = any(exclude_path in request.path for exclude_path in exclude_paths)
|
||||
|
||||
# Check if the user is an organization user and the path matches portfolio paths.
|
||||
is_portfolio_widescreen = (
|
||||
hasattr(request.user, "is_org_user")
|
||||
and request.user.is_org_user(request)
|
||||
and any(path in request.path for path in portfolio_widescreen_paths)
|
||||
and not any(exclude_path in request.path for exclude_path in exclude_paths)
|
||||
)
|
||||
# Check if the current path matches a path in included_paths or the root path.
|
||||
is_widescreen_centered = any(path in request.path for path in include_paths) or request.path == "/"
|
||||
|
||||
# Return a dictionary with the widescreen mode status.
|
||||
return {"is_widescreen_mode": is_widescreen or is_portfolio_widescreen}
|
||||
return {"is_widescreen_centered": is_widescreen_centered and not is_excluded}
|
||||
|
|
|
@ -2,7 +2,7 @@ from itertools import zip_longest
|
|||
import logging
|
||||
import ipaddress
|
||||
import re
|
||||
from datetime import date
|
||||
from datetime import date, timedelta
|
||||
from typing import Optional
|
||||
from django_fsm import FSMField, transition, TransitionNotAllowed # type: ignore
|
||||
|
||||
|
@ -40,6 +40,7 @@ from .utility.time_stamped_model import TimeStampedModel
|
|||
from .public_contact import PublicContact
|
||||
|
||||
from .user_domain_role import UserDomainRole
|
||||
from waffle.decorators import flag_is_active
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -1152,13 +1153,28 @@ class Domain(TimeStampedModel, DomainHelper):
|
|||
now = timezone.now().date()
|
||||
return self.expiration_date < now
|
||||
|
||||
def state_display(self):
|
||||
def is_expiring(self):
|
||||
"""
|
||||
Check if the domain's expiration date is within 60 days.
|
||||
Return True if domain expiration date exists and within 60 days
|
||||
and otherwise False bc there's no expiration date meaning so not expiring
|
||||
"""
|
||||
if self.expiration_date is None:
|
||||
return False
|
||||
|
||||
now = timezone.now().date()
|
||||
|
||||
threshold_date = now + timedelta(days=60)
|
||||
return now < self.expiration_date <= threshold_date
|
||||
|
||||
def state_display(self, request=None):
|
||||
"""Return the display status of the domain."""
|
||||
if self.is_expired() and self.state != self.State.UNKNOWN:
|
||||
if self.is_expired() and (self.state != self.State.UNKNOWN):
|
||||
return "Expired"
|
||||
elif flag_is_active(request, "domain_renewal") and self.is_expiring():
|
||||
return "Expiring soon"
|
||||
elif self.state == self.State.UNKNOWN or self.state == self.State.DNS_NEEDED:
|
||||
return "DNS needed"
|
||||
else:
|
||||
return self.state.capitalize()
|
||||
|
||||
def map_epp_contact_to_public_contact(self, contact: eppInfo.InfoContactResultData, contact_id, contact_type):
|
||||
|
|
|
@ -14,6 +14,8 @@ from .domain import Domain
|
|||
from .domain_request import DomainRequest
|
||||
from registrar.utility.waffle import flag_is_active_for_user
|
||||
from waffle.decorators import flag_is_active
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
|
||||
from phonenumber_field.modelfields import PhoneNumberField # type: ignore
|
||||
|
||||
|
@ -163,6 +165,20 @@ class User(AbstractUser):
|
|||
active_requests_count = self.domain_requests_created.filter(status__in=allowed_states).count()
|
||||
return active_requests_count
|
||||
|
||||
def get_num_expiring_domains(self, request):
|
||||
"""Return number of expiring domains"""
|
||||
domain_ids = self.get_user_domain_ids(request)
|
||||
now = timezone.now().date()
|
||||
expiration_window = 60
|
||||
threshold_date = now + timedelta(days=expiration_window)
|
||||
num_of_expiring_domains = Domain.objects.filter(
|
||||
id__in=domain_ids,
|
||||
expiration_date__isnull=False,
|
||||
expiration_date__lte=threshold_date,
|
||||
expiration_date__gt=now,
|
||||
).count()
|
||||
return num_of_expiring_domains
|
||||
|
||||
def get_rejected_requests_count(self):
|
||||
"""Return count of rejected requests"""
|
||||
return self.domain_requests_created.filter(status=DomainRequest.DomainRequestStatus.REJECTED).count()
|
||||
|
@ -259,6 +275,9 @@ class User(AbstractUser):
|
|||
def is_portfolio_admin(self, portfolio):
|
||||
return "Admin" in self.portfolio_role_summary(portfolio)
|
||||
|
||||
def has_domain_renewal_flag(self):
|
||||
return flag_is_active_for_user(self, "domain_renewal")
|
||||
|
||||
def get_first_portfolio(self):
|
||||
permission = self.portfolio_permissions.first()
|
||||
if permission:
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
{% block title %}{% translate "Unauthorized | " %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<main id="main-content" class="grid-container {% if is_widescreen_mode %} grid-container--widescreen{% endif %}">
|
||||
<div class="grid-row grow-gap">
|
||||
<main id="main-content" class="grid-container grid-container--widescreen">
|
||||
<div class="grid-row grow-gap {% if not is_widescreen_centered %}max-width--grid-container{% endif %}">
|
||||
<div class="tablet:grid-col-6 usa-prose margin-bottom-3">
|
||||
<h1>
|
||||
{% translate "You are not authorized to view this page" %}
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
{% block title %}{% translate "Forbidden | " %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<main id="main-content" class="grid-container {% if is_widescreen_mode %} grid-container--widescreen{% endif %}">
|
||||
<div class="grid-row grow-gap">
|
||||
<main id="main-content" class="grid-container grid-container--widescreen">
|
||||
<div class="grid-row grow-gap {% if not is_widescreen_centered %}max-width--grid-container{% endif %}">
|
||||
<div class="tablet:grid-col-6 usa-prose margin-bottom-3">
|
||||
<h1>
|
||||
{% translate "You're not authorized to view this page." %}
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
{% block title %}{% translate "Page not found | " %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<main id="main-content" class="grid-container {% if is_widescreen_mode %} grid-container--widescreen{% endif %}">
|
||||
<div class="grid-row grid-gap">
|
||||
<main id="main-content" class="grid-container grid-container--widescreen">
|
||||
<div class="grid-row grid-gap {% if not is_widescreen_centered %}max-width--grid-container{% endif %}">
|
||||
<div class="tablet:grid-col-6 usa-prose margin-bottom-3">
|
||||
<h1>
|
||||
{% translate "We couldn’t find that page" %}
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
{% block title %}{% translate "Server error | " %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<main id="main-content" class="grid-container {% if is_widescreen_mode %} grid-container--widescreen{% endif %}">
|
||||
<div class="grid-row grid-gap">
|
||||
<main id="main-content" class="grid-container grid-container--widescreen">
|
||||
<div class="grid-row grid-gap {% if not is_widescreen_centered %}max-width--grid-container{% endif %}">
|
||||
<div class="tablet:grid-col-6 usa-prose margin-bottom-3">
|
||||
<h1>
|
||||
{% translate "We're having some trouble." %}
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
{% for model in app.models %}
|
||||
<tr class="model-{{ model.object_name|lower }}{% if model.admin_url in request.path|urlencode %} current-model{% endif %}">
|
||||
{% if model.admin_url %}
|
||||
<th scope="row"><a href="{{ model.admin_url }}"{% if model.admin_url in request.path|urlencode %} aria-current="page"{% endif %}>{{ model.name }}</a></th>
|
||||
<th scope="row"><a href="{{ model.admin_url }}"{% if model.admin_url in request.path|urlencode %} aria-current="page"{% endif %}">{{ model.name }}</a></th>
|
||||
{% else %}
|
||||
<th scope="row">{{ model.name }}</th>
|
||||
{% endif %}
|
||||
|
|
|
@ -61,7 +61,7 @@ https://github.com/django/django/blob/main/django/contrib/admin/templates/admin/
|
|||
{% 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 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 %}
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
<select name="selected_user" id="selected_user" class="admin-combobox margin-top-0" onchange="this.form.submit()">
|
||||
<option value="">Select a user</option>
|
||||
{% for user in other_users %}
|
||||
<option value="{{ user.pk }}" {% if selected_user and user.pk == selected_user.pk %}selected{% endif %}>
|
||||
<option value="{{ user.pk }}" {% if selected_user and user.pk == selected_user.pk %}selected{% endif %}">
|
||||
{{ user.first_name }} {{ user.last_name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
|
|
|
@ -97,7 +97,7 @@
|
|||
<section class="usa-banner" aria-label="Official website of the United States government">
|
||||
<div class="usa-accordion">
|
||||
<header class="usa-banner__header">
|
||||
<div class="usa-banner__inner {% if is_widescreen_mode %} usa-banner__inner--widescreen {% endif %}">
|
||||
<div class="usa-banner__inner usa-banner__inner--widescreen padding-x--widescreen">
|
||||
<div class="grid-col-auto">
|
||||
<img class="usa-banner__header-flag" src="{% static 'img/us_flag_small.png' %}" alt="U.S. flag" />
|
||||
</div>
|
||||
|
@ -113,7 +113,7 @@
|
|||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<div class="usa-banner__content usa-accordion__content" id="gov-banner-default">
|
||||
<div class="usa-banner__content usa-accordion__content padding-x--widescreen margin-x-0" id="gov-banner-default">
|
||||
<div class="grid-row grid-gap-lg">
|
||||
<div class="usa-banner__guidance tablet:grid-col-6">
|
||||
<img class="usa-banner__icon usa-media-block__img" src="{% static 'img/icon-dot-gov.svg' %}" role="img"
|
||||
|
@ -159,14 +159,14 @@
|
|||
|
||||
{% block wrapper %}
|
||||
{% block wrapperdiv %}
|
||||
<div id="wrapper">
|
||||
<div id="wrapper" class="wrapper--padding-top-6">
|
||||
{% endblock wrapperdiv %}
|
||||
{% block messages %}
|
||||
{% if messages %}
|
||||
<ul class="messages">
|
||||
{% for message in messages %}
|
||||
{% if 'base' in message.extra_tags %}
|
||||
<li{% if message.tags %} class="{{ message.tags }}" {% endif %}>
|
||||
<li{% if message.tags %} class="{{ message.tags }}" {% endif %}">
|
||||
{{ message }}
|
||||
</li>
|
||||
{% endif %}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
{% if messages %}
|
||||
<ul class="messages">
|
||||
{% for message in messages %}
|
||||
<li {% if message.tags %} class="{{ message.tags }}" {% endif %}>
|
||||
<li {% if message.tags %} class="{{ message.tags }}" {% endif %}">
|
||||
{{ message }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
{% comment %} This view provides a detail button that can be used to show/hide content {% endcomment %}
|
||||
<details class="margin-top-1 dja-detail-table" aria-role="button" {% if start_open %}open{% else %}closed{% endif %}>
|
||||
<details class="margin-top-1 dja-detail-table" aria-role="button" {% if start_open %}open{% else %}closed{% endif %}">
|
||||
<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">
|
||||
{% block detail_content %}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block help_text %}
|
||||
<div class="help margin-bottom-1" {% if field.field.id_for_label %} id="{{ field.field.id_for_label }}_helptext"{% endif %}>
|
||||
<div class="help margin-bottom-1" {% if field.field.id_for_label %} id="{{ field.field.id_for_label }}_helptext"{% endif %}">
|
||||
{% if field.field.name == "state" %}
|
||||
<div>{{ state_help_message }}</div>
|
||||
{% else %}
|
||||
|
|
|
@ -6,14 +6,14 @@
|
|||
<ul class="mulitple-choice">
|
||||
{% for choice in choices %}
|
||||
{% if choice.reset %}
|
||||
<li{% if choice.selected %} class="selected"{% endif %}>
|
||||
<li{% if choice.selected %} class="selected"{% endif %}">
|
||||
<a href="{{ choice.query_string|iriencode }}" title="{{ choice.display }}">{{ choice.display }}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% for choice in choices %}
|
||||
{% if not choice.reset %}
|
||||
<li{% if choice.selected %} class="selected"{% endif %}>
|
||||
<li{% if choice.selected %} class="selected"{% endif %}">
|
||||
{% if choice.selected and choice.exclude_query_string %}
|
||||
<a class="choice-filter choice-filter--checked" href="{{ choice.exclude_query_string|iriencode }}">{{ choice.display }}
|
||||
<svg class="usa-icon position-absolute z-0 left-0" aria-hidden="true" focusable="false" role="img" width="24" height="24">
|
||||
|
|
|
@ -37,6 +37,9 @@
|
|||
</nav>
|
||||
{% endif %}
|
||||
{% endblock breadcrumb %}
|
||||
|
||||
{% include "includes/form_errors.html" with form=form %}
|
||||
|
||||
<h1>Add a domain manager</h1>
|
||||
{% if has_organization_feature_flag %}
|
||||
<p>
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
{% block title %}{{ domain.name }} | {% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="grid-container">
|
||||
<div class="grid-container grid-container--widescreen">
|
||||
|
||||
<div class="grid-row grid-gap">
|
||||
<div class="tablet:grid-col-3">
|
||||
<div class="grid-row grid-gap {% if not is_widescreen_centered %}max-width--grid-container{% endif %}">
|
||||
<div class="tablet:grid-col-3 ">
|
||||
<p class="font-body-md margin-top-0 margin-bottom-2
|
||||
text-primary-darker text-semibold domain-name-wrap"
|
||||
>
|
||||
|
|
|
@ -35,9 +35,12 @@
|
|||
Status:
|
||||
</span>
|
||||
<span class="text-primary-darker">
|
||||
|
||||
{# UNKNOWN domains would not have an expiration date and thus would show 'Expired' #}
|
||||
{% if domain.is_expired and domain.state != domain.State.UNKNOWN %}
|
||||
Expired
|
||||
{% elif has_domain_renewal_flag and domain.is_expiring %}
|
||||
Expiring soon
|
||||
{% elif domain.state == domain.State.UNKNOWN or domain.state == domain.State.DNS_NEEDED %}
|
||||
DNS needed
|
||||
{% else %}
|
||||
|
@ -46,7 +49,13 @@
|
|||
</span>
|
||||
{% if domain.get_state_help_text %}
|
||||
<div class="padding-top-1 text-primary-darker">
|
||||
{% if has_domain_renewal_flag and domain.is_expiring and is_domain_manager %}
|
||||
This domain will expire soon. <a href="/not-available-yet">Renew to maintain access.</a>
|
||||
{% elif has_domain_renewal_flag and domain.is_expiring and is_portfolio_user %}
|
||||
This domain will expire soon. Contact one of the listed domain managers to renew the domain.
|
||||
{% else %}
|
||||
{{ domain.get_state_help_text }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</p>
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
{% endif %}
|
||||
{% endblock breadcrumb %}
|
||||
|
||||
<h1>DNSSEC</h1>
|
||||
<h1 id="domain-dnssec">DNSSEC</h1>
|
||||
|
||||
<p>DNSSEC, or DNS Security Extensions, is an additional security layer to protect your website. Enabling DNSSEC ensures that when someone visits your domain, they can be certain that it’s connecting to the correct server, preventing potential hijacking or tampering with your domain's records.</p>
|
||||
|
||||
|
@ -78,7 +78,11 @@
|
|||
aria-labelledby="Are you sure you want to continue?"
|
||||
aria-describedby="Your DNSSEC records will be deleted from the registry."
|
||||
>
|
||||
{% include 'includes/modal.html' with modal_heading="Are you sure you want to disable DNSSEC?" modal_button=modal_button|safe %}
|
||||
{% include 'includes/modal.html' with modal_heading="Are you sure you want to disable DNSSEC?" modal_button_id="disable-dnssec-button" modal_button_text="Confirm" modal_button_class="usa-button--secondary" %}
|
||||
</div>
|
||||
<form method="post" id="disable-dnssec-form">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="disable_dnssec" value="1">
|
||||
</form>
|
||||
|
||||
{% endblock %} {# domain_content #}
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
{% include "includes/form_errors.html" with form=form %}
|
||||
{% endfor %}
|
||||
|
||||
<h1>DS data</h1>
|
||||
<h1 id="domain-dsdata">DS data</h1>
|
||||
|
||||
<p>In order to enable DNSSEC, you must first configure it with your DNS hosting service.</p>
|
||||
|
||||
|
@ -141,7 +141,15 @@
|
|||
aria-describedby="Your DNSSEC records will be deleted from the registry."
|
||||
data-force-action
|
||||
>
|
||||
{% include 'includes/modal.html' with cancel_button_resets_ds_form=True modal_heading="Warning: You are about to remove all DS records on your domain." modal_description="To fully disable DNSSEC: In addition to removing your DS records here, you’ll need to delete the DS records at your DNS host. To avoid causing your domain to appear offline, you should wait to delete your DS records at your DNS host until the Time to Live (TTL) expires. This is often less than 24 hours, but confirm with your provider." modal_button=modal_button|safe %}
|
||||
{% include 'includes/modal.html' with cancel_button_resets_ds_form=True modal_heading="Warning: You are about to remove all DS records on your domain." modal_description="To fully disable DNSSEC: In addition to removing your DS records here, you’ll need to delete the DS records at your DNS host. To avoid causing your domain to appear offline, you should wait to delete your DS records at your DNS host until the Time to Live (TTL) expires. This is often less than 24 hours, but confirm with your provider." modal_button_id="disable-override-click-button" modal_button_text="Remove all DS data" modal_button_class="usa-button--secondary" %}
|
||||
</div>
|
||||
<form method="post" id="disable-override-click-form">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="disable-override-click" value="1">
|
||||
</form>
|
||||
<form method="post" id="btn-cancel-click-form">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="btn-cancel-click" value="1">
|
||||
</form>
|
||||
|
||||
{% endblock %} {# domain_content #}
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
|
||||
{% block title %}{{form_titles|get_item:steps.current}} | Request a .gov | {% endblock %}
|
||||
{% block content %}
|
||||
<div class="grid-container">
|
||||
<div class="grid-row grid-gap">
|
||||
<div class="grid-container grid-container--widescreen">
|
||||
<div class="grid-row grid-gap {% if not is_widescreen_centered %}max-width--grid-container{% endif %}">
|
||||
<div class="tablet:grid-col-3">
|
||||
{% include 'domain_request_sidebar.html' %}
|
||||
</div>
|
||||
<div class="tablet:grid-col-9">
|
||||
<main id="main-content" class="grid-container register-form-step">
|
||||
<main id="main-content" class="register-form-step">
|
||||
<input type="hidden" class="display-none" id="wizard-domain-request-id" value="{{domain_request_id}}"/>
|
||||
{% if steps.current == steps.first %}
|
||||
{% if portfolio %}
|
||||
|
@ -130,9 +130,17 @@
|
|||
aria-describedby="Are you sure you want to submit a domain request?"
|
||||
data-force-action
|
||||
>
|
||||
{% include 'includes/modal.html' with is_domain_request_form=True review_form_is_complete=review_form_is_complete modal_heading=modal_heading|safe modal_description=modal_description|safe modal_button=modal_button|safe %}
|
||||
{% if review_form_is_complete %}
|
||||
{% include 'includes/modal.html' with modal_heading="You are about to submit a domain request for " domain_name_modal=requested_domain__name modal_description="Once you submit this request, you won’t be able to edit it until we review it. You’ll only be able to withdraw your request." modal_button_id="domain-request-form-submit-button" modal_button_text="Submit request" %}
|
||||
{% else %}
|
||||
{% include 'includes/modal.html' with modal_heading="Your request form is incomplete" modal_description='This request cannot be submitted yet. Return to the request and visit the steps that are marked as "incomplete."' modal_button_text="Return to request" cancel_button_only=True %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<form method="post" id="submit-domain-request-form">
|
||||
{% csrf_token %}
|
||||
</form>
|
||||
|
||||
{% block after_form_content %}{% endblock %}
|
||||
|
||||
</main>
|
||||
|
|
|
@ -4,8 +4,9 @@
|
|||
{% block title %} Start a request | {% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<main id="main-content" class="grid-container">
|
||||
<div class="grid-col desktop:grid-offset-2 desktop:grid-col-8">
|
||||
<main id="main-content" class="grid-container grid-container--widescreen">
|
||||
<div class="grid-row {% if not is_widescreen_centered %}max-width--grid-container{% endif %}">
|
||||
<div class="grid-col {% if is_widescreen_centered %}desktop:grid-offset-2 {% endif %} desktop:grid-col-8">
|
||||
|
||||
<form class="usa-form usa-form--extra-large" method="post" novalidate>
|
||||
{% csrf_token %}
|
||||
|
@ -25,7 +26,7 @@
|
|||
{% include "includes/profile_information.html" with user=user%}
|
||||
|
||||
|
||||
{% block form_buttons %}
|
||||
{% block form_buttons %}
|
||||
<div class="stepnav">
|
||||
<button
|
||||
type="submit"
|
||||
|
@ -34,7 +35,7 @@
|
|||
class="usa-button"
|
||||
>Continue</button>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
</form>
|
||||
|
||||
|
|
|
@ -8,18 +8,20 @@
|
|||
{% endblock wrapperdiv %}
|
||||
|
||||
{% block content %}
|
||||
<div class="grid-container">
|
||||
<div class="grid-col desktop:grid-offset-2 desktop:grid-col-8">
|
||||
<div class="grid-container grid-container--widescreen">
|
||||
<div class="grid-row {% if not is_widescreen_centered %}max-width--grid-container{% endif %}">
|
||||
<div class="grid-col {% if is_widescreen_centered %}desktop:grid-offset-2 {% endif %} desktop:grid-col-8">
|
||||
|
||||
|
||||
<h1>Withdraw request for {{ DomainRequest.requested_domain.name }}?</h1>
|
||||
<h1>Withdraw request for {{ DomainRequest.requested_domain.name }}?</h1>
|
||||
|
||||
<p>If you withdraw your request, we won't review it. Once you withdraw your request, you can edit it and submit it again. </p>
|
||||
<p>If you withdraw your request, we won't review it. Once you withdraw your request, you can edit it and submit it again. </p>
|
||||
|
||||
<p><a href="{% url 'domain-request-withdrawn' DomainRequest.id %}" class="usa-button withdraw">Withdraw request</a>
|
||||
<p><a href="{% url 'domain-request-withdrawn' DomainRequest.id %}" class="usa-button withdraw">Withdraw request</a>
|
||||
<a href="{% url 'domain-request-status' DomainRequest.id %}">Cancel</a></p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
<li class="usa-sidenav__item">
|
||||
{% url 'domain-dns' pk=domain.id as url %}
|
||||
<a href="{{ url }}" {% if request.path|startswith:url %}class="usa-current"{% endif %}>
|
||||
<a href="{{ url }}" {% if request.path|startswith:url %}class="usa-current"{% endif %}">
|
||||
DNS
|
||||
</a>
|
||||
{% if request.path|startswith:url %}
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
</ul>
|
||||
|
||||
{% if domain_manager_roles %}
|
||||
<section class="section-outlined">
|
||||
<section class="section-outlined" id="domain-managers">
|
||||
<table class="usa-table usa-table--borderless usa-table--stacked dotgov-table--stacked dotgov-table">
|
||||
<h2 class> Domain managers </h2>
|
||||
<caption class="sr-only">Domain managers</caption>
|
||||
|
@ -89,12 +89,13 @@
|
|||
aria-describedby="You will be removed from this domain"
|
||||
data-force-action
|
||||
>
|
||||
<form method="POST" action="{% url "domain-user-delete" pk=domain.id user_pk=item.permission.user.id %}">
|
||||
{% with domain_name=domain.name|force_escape %}
|
||||
{% include 'includes/modal.html' with modal_heading="Are you sure you want to remove yourself as a domain manager?" modal_description="You will no longer be able to manage the domain <strong>"|add:domain_name|add:"</strong>."|safe modal_button=modal_button_self|safe %}
|
||||
{% with domain_name=domain.name|force_escape counter_str=forloop.counter|stringformat:"s" %}
|
||||
{% include 'includes/modal.html' with modal_heading="Are you sure you want to remove yourself as a domain manager?" modal_description="You will no longer be able to manage the domain <strong>"|add:domain_name|add:"</strong>."|safe modal_button_id="user-delete-button-"|add:counter_str|safe modal_button_text="Yes, remove myself" modal_button_class="usa-button--secondary" %}
|
||||
{% endwith %}
|
||||
</form>
|
||||
</div>
|
||||
<form method="POST" id="user-delete-form-{{ forloop.counter }}" action="{% url "domain-user-delete" pk=domain.id user_pk=item.permission.user.id %}" >
|
||||
{% csrf_token %}
|
||||
</form>
|
||||
{% else %}
|
||||
<div
|
||||
class="usa-modal"
|
||||
|
@ -103,12 +104,13 @@
|
|||
aria-describedby="{{ item.permission.user.email }} will be removed"
|
||||
data-force-action
|
||||
>
|
||||
<form method="POST" action="{% url "domain-user-delete" pk=domain.id user_pk=item.permission.user.id %}">
|
||||
{% with email=item.permission.user.email|default:item.permission.user|force_escape domain_name=domain.name|force_escape %}
|
||||
{% include 'includes/modal.html' with modal_heading="Are you sure you want to remove " heading_value=email|add:"?" modal_description="<strong>"|add:email|add:"</strong> will no longer be able to manage the domain <strong>"|add:domain_name|add:"</strong>."|safe modal_button=modal_button|safe %}
|
||||
{% with email=item.permission.user.email|default:item.permission.user|force_escape domain_name=domain.name|force_escape counter_str=forloop.counter|stringformat:"s" %}
|
||||
{% include 'includes/modal.html' with modal_heading="Are you sure you want to remove " heading_value=email|add:"?" modal_description="<strong>"|add:email|add:"</strong> will no longer be able to manage the domain <strong>"|add:domain_name|add:"</strong>."|safe modal_button_id="user-delete-button-"|add:counter_str|safe modal_button_text="Yes, remove domain manager" modal_button_class="usa-button--secondary" %}
|
||||
{% endwith %}
|
||||
</form>
|
||||
</div>
|
||||
<form method="POST" id="user-delete-form-{{ forloop.counter }}" action="{% url "domain-user-delete" pk=domain.id user_pk=item.permission.user.id %}">
|
||||
{% csrf_token %}
|
||||
</form>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<input
|
||||
|
|
|
@ -5,12 +5,12 @@
|
|||
{% block title %} Home | {% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<main id="main-content" class="grid-container {% if is_widescreen_mode %} grid-container--widescreen{% endif %}">
|
||||
<main id="main-content" class="grid-container grid-container--widescreen">
|
||||
{% if user.is_authenticated %}
|
||||
{# the entire logged in page goes here #}
|
||||
|
||||
{% block homepage_content %}
|
||||
<div class="tablet:grid-col-11 desktop:grid-col-10 tablet:grid-offset-1">
|
||||
<div class="tablet:grid-col-11 desktop:grid-col-10 {% if is_widescreen_centered %}tablet:grid-offset-1{% endif %}">
|
||||
{% block messages %}
|
||||
{% include "includes/form_messages.html" %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div class="margin-y-0 {% if add_class %}{{ add_class }}{% endif %}" aria-label="Site alert">
|
||||
<div class="usa-alert usa-alert--error">
|
||||
<div class="usa-alert__body {% if is_widescreen_mode %}usa-alert__body--widescreen{% endif %}">
|
||||
<div class="usa-alert__body usa-alert__body--widescreen">
|
||||
<h4 class="usa-alert__heading">
|
||||
Header
|
||||
</h4>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<section class="usa-site-alert usa-site-alert--info margin-y-0 {% if add_class %}{{ add_class }}{% endif %}" aria-label="Site alert">
|
||||
<div class="usa-alert">
|
||||
<div class="usa-alert__body {% if is_widescreen_mode %}usa-alert__body--widescreen{% endif %}">
|
||||
<div class="usa-alert__body usa-alert__body--widescreen">
|
||||
<h4 class="usa-alert__heading">
|
||||
Header
|
||||
</h4>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<section class="usa-site-alert usa-site-alert--emergency usa-site-alert--hot-pink margin-y-0 {% if add_class %}{{ add_class }}{% endif %}" aria-label="Site alert">
|
||||
<div class="usa-alert">
|
||||
<div class="usa-alert__body {% if add_body_class %}{{ add_body_class }}{% endif %} {% if is_widescreen_mode %}usa-alert__body--widescreen{% endif %}">
|
||||
<div class="usa-alert__body {% if add_body_class %}{{ add_body_class }}{% endif %} usa-alert__body--widescreen">
|
||||
<p class="usa-alert__text maxw-none">
|
||||
<strong>Attention:</strong> You are on a test site.
|
||||
</p>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<section class="usa-site-alert usa-site-alert--emergency margin-y-0 {% if add_class %}{{ add_class }}{% endif %}" aria-label="Site alert">
|
||||
<div class="usa-alert">
|
||||
<div class="usa-alert__body {% if is_widescreen_mode %}usa-alert__body--widescreen{% endif %}">
|
||||
<div class="usa-alert__body usa-alert__body--widescreen">
|
||||
<h3 class="usa-alert__heading">
|
||||
Service disruption
|
||||
</h3>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<section class="usa-site-alert usa-site-alert--emergency margin-y-0 {% if add_class %}{{ add_class }}{% endif %}" aria-label="Site alert">
|
||||
<div class="usa-alert">
|
||||
<div class="usa-alert__body {% if is_widescreen_mode %}usa-alert__body--widescreen{% endif %}">
|
||||
<div class="usa-alert__body usa-alert__body--widescreen">
|
||||
<h3 class="usa-alert__heading">
|
||||
Header here
|
||||
</h3>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<section class="usa-site-alert usa-site-alert--emergency margin-y-0 {% if add_class %}{{ add_class }}{% endif %}" aria-label="Site alert">
|
||||
<div class="usa-alert">
|
||||
<div class="usa-alert__body {% if is_widescreen_mode %}usa-alert__body--widescreen{% endif %}">
|
||||
<div class="usa-alert__body usa-alert__body--widescreen">
|
||||
<h3 class="usa-alert__heading">
|
||||
System outage
|
||||
</h3>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div class="margin-y-0 {% if add_class %}{{ add_class }}{% endif %}" aria-label="Site alert">
|
||||
<div class="usa-alert usa-alert--warning">
|
||||
<div class="usa-alert__body {% if is_widescreen_mode %}usa-alert__body--widescreen{% endif %}">
|
||||
<div class="usa-alert__body usa-alert__body--widescreen">
|
||||
<h4 class="usa-alert__heading">
|
||||
Header
|
||||
</h4>
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
{% load custom_filters %}
|
||||
{% load static url_helpers %}
|
||||
<main id="main-content" class="grid-container">
|
||||
<div class="grid-col desktop:grid-offset-2 desktop:grid-col-8">
|
||||
<main id="main-content" class="grid-container grid-container--widescreen">
|
||||
<div class="grid-row {% if not is_widescreen_centered %}max-width--grid-container{% endif %}">
|
||||
<div class="grid-col {% if is_widescreen_centered %}desktop:grid-offset-2 {% endif %} desktop:grid-col-8">
|
||||
{% block breadcrumb %}
|
||||
{% if portfolio %}
|
||||
{% url 'domain-requests' as url %}
|
||||
|
@ -139,7 +140,7 @@
|
|||
{% endblock modify_request %}
|
||||
</div>
|
||||
|
||||
<div class="grid-col desktop:grid-offset-2 maxw-tablet">
|
||||
<div class="grid-col {% if is_widescreen_centered %}desktop:grid-offset-2 maxw-tablet{% endif %}">
|
||||
{% block request_summary_header %}
|
||||
<h2 class="text-primary-darker"> Summary of your domain request </h2>
|
||||
{% endblock request_summary_header%}
|
||||
|
@ -233,4 +234,5 @@
|
|||
{% endwith %}
|
||||
{% endblock request_summary%}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
|
@ -4,8 +4,8 @@
|
|||
{% url 'get_domain_requests_json' as url %}
|
||||
<span id="get_domain_requests_json_url" class="display-none">{{url}}</span>
|
||||
|
||||
<section class="section-outlined domain-requests{% if portfolio %} section-outlined--border-base-light{% endif %}" id="domain-requests">
|
||||
<div class="section-outlined__header margin-bottom-3 {% if not portfolio %} section-outlined__header--no-portfolio justify-content-space-between{% else %} grid-row{% endif %}">
|
||||
<section class="section-outlined domain-requests {% if portfolio %}section-outlined--border-base-light{% endif %}" id="domain-requests">
|
||||
<div class="section-outlined__header margin-bottom-3 {% if not portfolio %}section-outlined__header--no-portfolio justify-content-space-between{% else %} grid-row{% endif %}">
|
||||
{% if not portfolio %}
|
||||
<h2 id="domain-requests-header" class="display-inline-block">Domain requests</h2>
|
||||
{% else %}
|
||||
|
@ -13,7 +13,7 @@
|
|||
<span id="portfolio-js-value" data-portfolio="{{ portfolio.id }}"></span>
|
||||
{% endif %}
|
||||
|
||||
<div class="section-outlined__search {% if portfolio %} mobile:grid-col-12 desktop:grid-col-6{% endif %} {% if is_widescreen_mode %} section-outlined__search--widescreen {% endif %}">
|
||||
<div class="section-outlined__search section-outlined__search--widescreen {% if portfolio %}mobile:grid-col-12 desktop:grid-col-6{% endif %}">
|
||||
<section aria-label="Domain requests search component" class="margin-top-2">
|
||||
<form class="usa-search usa-search--small" method="POST" role="search">
|
||||
{% csrf_token %}
|
||||
|
|
|
@ -1,10 +1,30 @@
|
|||
{% load static %}
|
||||
|
||||
|
||||
|
||||
{% comment %} Stores the json endpoint in a url for easier access {% endcomment %}
|
||||
{% url 'get_domains_json' as url %}
|
||||
|
||||
|
||||
|
||||
<span id="get_domains_json_url" class="display-none">{{url}}</span>
|
||||
|
||||
<!-- Org model banner (org manager can view, domain manager can edit) -->
|
||||
{% if has_domain_renewal_flag and num_expiring_domains > 0 and has_any_domains_portfolio_permission %}
|
||||
<section class="usa-site-alert usa-site-alert--info margin-bottom-2 {% if add_class %}{{ add_class }}{% endif %}" aria-label="Site alert">
|
||||
<div class="usa-alert">
|
||||
<div class="usa-alert__body usa-alert__body--widescreen">
|
||||
<p class="usa-alert__text maxw-none">
|
||||
{% if num_expiring_domains == 1%}
|
||||
One domain will expire soon. Go to "Manage" to renew the domain. <a href="#" id="link-expiring-domains" class="usa-link">Show expiring domain.</a>
|
||||
{% else%}
|
||||
Multiple domains will expire soon. Go to "Manage" to renew the domains. <a href="#" id="link-expiring-domains" class="usa-link">Show expiring domains.</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
<section class="section-outlined domains margin-top-0{% if portfolio %} section-outlined--border-base-light{% endif %}" id="domains">
|
||||
<div class="section-outlined__header margin-bottom-3 {% if not portfolio %} section-outlined__header--no-portfolio justify-content-space-between{% else %} grid-row{% endif %}">
|
||||
{% if not portfolio %}
|
||||
|
@ -13,7 +33,7 @@
|
|||
<!-- Embedding the portfolio value in a data attribute -->
|
||||
<span id="portfolio-js-value" data-portfolio="{{ portfolio.id }}"></span>
|
||||
{% endif %}
|
||||
<div class="section-outlined__search {% if portfolio %} mobile:grid-col-12 desktop:grid-col-6{% endif %} {% if is_widescreen_mode %} section-outlined__search--widescreen {% endif %}">
|
||||
<div class="section-outlined__search section-outlined__search--widescreen {% if portfolio %}mobile:grid-col-12 desktop:grid-col-6{% endif %}">
|
||||
<section aria-label="Domains search component" class="margin-top-2">
|
||||
<form class="usa-search usa-search--small" method="POST" role="search">
|
||||
{% csrf_token %}
|
||||
|
@ -53,7 +73,24 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if portfolio %}
|
||||
|
||||
<!-- Non org model banner -->
|
||||
{% if has_domain_renewal_flag and num_expiring_domains > 0 and not portfolio %}
|
||||
<section class="usa-site-alert usa-site-alert--info margin-bottom-2 {% if add_class %}{{ add_class }}{% endif %}" aria-label="Site alert">
|
||||
<div class="usa-alert">
|
||||
<div class="usa-alert__body usa-alert__body--widescreen">
|
||||
<p class="usa-alert__text maxw-none">
|
||||
{% if num_expiring_domains == 1%}
|
||||
One domain will expire soon. Go to "Manage" to renew the domain. <a href="#" id="link-expiring-domains" class="usa-link">Show expiring domain.</a>
|
||||
{% else%}
|
||||
Multiple domains will expire soon. Go to "Manage" to renew the domains. <a href="#" id="link-expiring-domains" class="usa-link">Show expiring domains.</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
<div class="display-flex flex-align-center">
|
||||
<span class="margin-right-2 margin-top-neg-1 usa-prose text-base-darker">Filter by</span>
|
||||
<div class="usa-accordion usa-accordion--select margin-right-2">
|
||||
|
@ -135,6 +172,19 @@
|
|||
>Deleted</label
|
||||
>
|
||||
</div>
|
||||
{% if has_domain_renewal_flag and num_expiring_domains > 0 %}
|
||||
<div class="usa-checkbox">
|
||||
<input
|
||||
class="usa-checkbox__input"
|
||||
id="filter-status-expiring"
|
||||
type="checkbox"
|
||||
name="filter-status"
|
||||
value="expiring"
|
||||
/>
|
||||
<label class="usa-checkbox__label" for="filter-status-expiring"
|
||||
>Expiring soon</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -149,7 +199,6 @@
|
|||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="display-none usa-table-container--scrollable margin-top-0" tabindex="0" id="domains__table-wrapper">
|
||||
<table class="usa-table usa-table--borderless usa-table--stacked dotgov-table dotgov-table--stacked">
|
||||
<caption class="sr-only">Your registered domains</caption>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
<footer class="usa-footer">
|
||||
<div class="usa-footer__secondary-section">
|
||||
<div class="grid-container {% if is_widescreen_mode %} grid-container--widescreen{% endif %}">
|
||||
<div class="grid-container grid-container--widescreen">
|
||||
<div class="grid-row grid-gap">
|
||||
<div
|
||||
class="
|
||||
|
@ -24,7 +24,7 @@
|
|||
<div class="usa-footer__contact-links
|
||||
mobile-lg:grid-col-6 flex-align-self-center"
|
||||
>
|
||||
<address class="usa-footer__address">
|
||||
<address class="usa-footer__address maxw-none">
|
||||
<div class="usa-footer__contact-info grid-row grid-gap-md">
|
||||
{% if show_manage_your_domains %}
|
||||
<div class="grid-col-auto">
|
||||
|
@ -51,7 +51,7 @@
|
|||
class="usa-identifier__section usa-identifier__section--masthead"
|
||||
aria-label="Agency identifier"
|
||||
>
|
||||
<div class="usa-identifier__container {% if is_widescreen_mode %} usa-identifier__container--widescreen {% endif %}">
|
||||
<div class="usa-identifier__container usa-identifier__container--widescreen padding-x--widescreen">
|
||||
<div class="usa-identifier__logos">
|
||||
<a rel="noopener noreferrer" target="_blank" href="https://www.cisa.gov" class="usa-identifier__logo"
|
||||
><img
|
||||
|
@ -77,7 +77,7 @@
|
|||
class="usa-identifier__section usa-identifier__section--required-links"
|
||||
aria-label="Important links"
|
||||
>
|
||||
<div class="usa-identifier__container {% if is_widescreen_mode %} usa-identifier__container--widescreen {% endif %}">
|
||||
<div class="usa-identifier__container usa-identifier__container--widescreen padding-x--widescreen">
|
||||
<ul class="usa-identifier__required-links-list">
|
||||
<li class="usa-identifier__required-links-item">
|
||||
<a rel="noopener noreferrer" target="_blank" href="{% public_site_url 'about/' %}"
|
||||
|
@ -119,7 +119,7 @@
|
|||
class="usa-identifier__section usa-identifier__section--usagov"
|
||||
aria-label="U.S. government information and services"
|
||||
>
|
||||
<div class="usa-identifier__container {% if is_widescreen_mode %} usa-identifier__container--widescreen {% endif %}">
|
||||
<div class="usa-identifier__container usa-identifier__container--widescreen padding-x--widescreen">
|
||||
<div class="usa-identifier__usagov-description">
|
||||
Looking for U.S. government information and services?
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{% if form.errors %}
|
||||
<div id="form-errors">
|
||||
{% for error in form.non_field_errors %}
|
||||
<div class="usa-alert usa-alert--error usa-alert--slim margin-bottom-2">
|
||||
<div class="usa-alert usa-alert--error usa-alert--slim margin-bottom-2" role="alert">
|
||||
<div class="usa-alert__body">
|
||||
{{ error|escape }}
|
||||
</div>
|
||||
|
@ -14,5 +15,6 @@
|
|||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{% load static %}
|
||||
|
||||
<header class="usa-header usa-header--basic">
|
||||
<div class="usa-nav-container {% if is_widescreen_mode %} usa-nav-container--widescreen {% endif %}">
|
||||
<div class="usa-nav-container usa-nav-container--widescreen padding-x--widescreen">
|
||||
<div class="usa-navbar">
|
||||
{% include "includes/gov_extended_logo.html" with logo_clickable=logo_clickable %}
|
||||
<button type="button" class="usa-menu-btn">Menu</button>
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
{% load custom_filters %}
|
||||
|
||||
<header class="usa-header usa-header--extended">
|
||||
<div class="usa-navbar {% if is_widescreen_mode %} usa-navbar--widescreen {% endif %}">
|
||||
<div class="usa-navbar usa-navbar--widescreen padding-x--widescreen">
|
||||
{% include "includes/gov_extended_logo.html" with logo_clickable=logo_clickable %}
|
||||
<button type="button" class="usa-menu-btn">Menu</button>
|
||||
</div>
|
||||
{% block usa_nav %}
|
||||
<nav class="usa-nav" aria-label="Primary navigation">
|
||||
<div class="usa-nav__inner {% if is_widescreen_mode %} usa-nav__inner--widescreen {% endif %}">
|
||||
<div class="usa-nav__inner usa-nav__inner--widescreen padding-x--widescreen">
|
||||
<button type="button" class="usa-nav__close">
|
||||
<img src="{%static 'img/usa-icons/close.svg'%}" role="img" alt="Close" />
|
||||
</button>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<section class="section-outlined members margin-top-0 section-outlined--border-base-light" id="members">
|
||||
<div class="section-outlined__header margin-bottom-3 grid-row">
|
||||
<!-- ---------- SEARCH ---------- -->
|
||||
<div class="section-outlined__search mobile:grid-col-12 desktop:grid-col-6 {% if is_widescreen_mode %} section-outlined__search--widescreen {% endif %}">
|
||||
<div class="section-outlined__search mobile:grid-col-12 desktop:grid-col-6 section-outlined__search--widescreen">
|
||||
<section aria-label="Members search component" class="margin-top-2">
|
||||
<form class="usa-search usa-search--small" method="POST" role="search">
|
||||
{% csrf_token %}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{% load static form_helpers url_helpers %}
|
||||
{% load custom_filters %}
|
||||
|
||||
<div class="usa-modal__content">
|
||||
<div class="usa-modal__main">
|
||||
|
@ -24,39 +25,51 @@
|
|||
<div class="usa-modal__footer">
|
||||
<ul class="usa-button-group">
|
||||
<li class="usa-button-group__item">
|
||||
{% if not_form and modal_button %}
|
||||
{{ modal_button }}
|
||||
{% if cancel_button_only %}
|
||||
<button
|
||||
type="button"
|
||||
class="{{ modal_button_class|button_class }}"
|
||||
data-close-modal
|
||||
>
|
||||
{% if modal_button_text %}
|
||||
{{ modal_button_text }}
|
||||
{% else %}
|
||||
Cancel
|
||||
{% endif %}
|
||||
</button>
|
||||
{% elif modal_button_id and modal_button_text %}
|
||||
{% comment %} Adding button id allows for onclick listeners on button by id,
|
||||
which execute form submission on form elsewhere on a page outside modal.{% endcomment %}
|
||||
<button
|
||||
id="{{ modal_button_id }}"
|
||||
type="button"
|
||||
class="{{ modal_button_class|button_class }}"
|
||||
>
|
||||
{{ modal_button_text }}
|
||||
</button>
|
||||
{% elif modal_button_url and modal_button_text %}
|
||||
<a
|
||||
href="{{ modal_button_url }}"
|
||||
type="button"
|
||||
class="usa-button"
|
||||
class="{{ modal_button_class|button_class }}"
|
||||
>
|
||||
{{ modal_button_text }}
|
||||
</a>
|
||||
{% else %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ modal_button }}
|
||||
</form>
|
||||
{% endif %}
|
||||
</li>
|
||||
<li class="usa-button-group__item">
|
||||
{% comment %} The cancel button the DS form actually triggers a context change in the view,
|
||||
in addition to being a close modal hook {% endcomment %}
|
||||
{% if cancel_button_resets_ds_form %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<button
|
||||
type="submit"
|
||||
class="usa-button usa-button--unstyled padding-105 text-center"
|
||||
name="btn-cancel-click"
|
||||
id="btn-cancel-click-button"
|
||||
data-close-modal
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</form>
|
||||
{% elif not is_domain_request_form or review_form_is_complete %}
|
||||
{% elif not cancel_button_only %}
|
||||
<button
|
||||
type="button"
|
||||
class="usa-button usa-button--unstyled padding-105 text-center"
|
||||
|
@ -72,20 +85,17 @@
|
|||
{% comment %} The cancel button the DS form actually triggers a context change in the view,
|
||||
in addition to being a close modal hook {% endcomment %}
|
||||
{% if cancel_button_resets_ds_form %}
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<button
|
||||
type="submit"
|
||||
class="usa-button usa-modal__close"
|
||||
aria-label="Close this window"
|
||||
name="btn-cancel-click"
|
||||
id="btn-cancel-click-close-button"
|
||||
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>
|
||||
</form>
|
||||
{% else %}
|
||||
<button
|
||||
type="button"
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
{% load custom_filters %}
|
||||
{% load static url_helpers %}
|
||||
<main id="main-content" class="grid-container">
|
||||
<div class="grid-col desktop:grid-offset-2 desktop:grid-col-8">
|
||||
<main id="main-content" class="grid-container grid-container--widescreen">
|
||||
<div class="grid-row {% if not is_widescreen_centered %}max-width--grid-container{% endif %}">
|
||||
<div class="grid-col desktop:grid-col-8 {% if is_widescreen_centered %}desktop:grid-offset-2{% endif %}">
|
||||
{% block breadcrumb %}
|
||||
{% if portfolio %}
|
||||
{% url 'domain-requests' as url %}
|
||||
|
@ -139,7 +140,7 @@
|
|||
{% endblock modify_request %}
|
||||
</div>
|
||||
|
||||
<div class="grid-col desktop:grid-offset-2 maxw-tablet">
|
||||
<div class="grid-col {% if is_widescreen_centered %}desktop:grid-offset-2 maxw-tablet{% endif %}">
|
||||
{% block request_summary_header %}
|
||||
<h2 class="text-primary-darker"> Summary of your domain request </h2>
|
||||
{% endblock request_summary_header%}
|
||||
|
@ -237,4 +238,5 @@
|
|||
{% endif %}
|
||||
{% endblock request_summary%}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
|
@ -1,3 +1,3 @@
|
|||
<p class="{% if not remove_margin_top %}margin-top-3 {% endif %}">
|
||||
<p class="{% if not remove_margin_top %}margin-top-3{% endif %}">
|
||||
<em>Required fields are marked with an asterisk (<abbr class="usa-hint usa-hint--required" title="required">*</abbr>).</em>
|
||||
</p>
|
||||
|
|
|
@ -26,7 +26,6 @@
|
|||
</form>
|
||||
{% elif not form.full_name.value and not form.title.value and not form.email.value %}
|
||||
<p>
|
||||
Your senior official is a person within your organization who can authorize domain requests.
|
||||
We don't have information about your organization's senior official. To suggest an update, email <a href="mailto:help@get.gov" class="usa-link">help@get.gov</a>.
|
||||
</p>
|
||||
{% else %}
|
||||
|
|
|
@ -4,11 +4,12 @@
|
|||
<div id="wrapper" class="{% block wrapper_class %}wrapper--padding-top-6{% endblock %}">
|
||||
{% block content %}
|
||||
|
||||
<main class="grid-container {% if is_widescreen_mode %} grid-container--widescreen{% endif %}">
|
||||
<main class="grid-container grid-container--widescreen">
|
||||
{% if user.is_authenticated %}
|
||||
{# the entire logged in page goes here #}
|
||||
|
||||
<div class="tablet:grid-col-11 desktop:grid-col-10 tablet:grid-offset-1">
|
||||
<div class="grid-row {% if not is_widescreen_centered %}max-width--grid-container{% endif %}">
|
||||
<div class="tablet:grid-col-11 desktop:grid-col-10 {% if is_widescreen_centered %}tablet:grid-offset-1{% endif %}">
|
||||
{% block messages %}
|
||||
{% include "includes/form_messages.html" %}
|
||||
{% endblock %}
|
||||
|
@ -24,6 +25,8 @@
|
|||
</a></p>
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
{% endblock content%}
|
||||
|
|
|
@ -15,6 +15,6 @@
|
|||
|
||||
<div id="main-content">
|
||||
<h1 id="domains-header">Domains</h1>
|
||||
{% include "includes/domains_table.html" with portfolio=portfolio user_domain_count=user_domain_count %}
|
||||
{% include "includes/domains_table.html" with portfolio=portfolio user_domain_count=user_domain_count num_expiring_domains=num_expiring_domains%}
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -23,6 +23,8 @@
|
|||
{% include "includes/form_messages.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% include "includes/form_errors.html" with form=form %}
|
||||
|
||||
<h1>Organization</h1>
|
||||
|
||||
<p>The name of your organization will be publicly listed as the domain registrant.</p>
|
||||
|
@ -33,7 +35,6 @@
|
|||
To suggest an update, email <a href="mailto:help@get.gov" class="usa-link">help@get.gov</a>.
|
||||
</p>
|
||||
|
||||
{% include "includes/form_errors.html" with form=form %}
|
||||
{% include "includes/required_fields.html" %}
|
||||
<form class="usa-form usa-form--large desktop:margin-top-4" method="post" novalidate>
|
||||
{% csrf_token %}
|
||||
|
|
|
@ -11,8 +11,9 @@ Edit your User Profile |
|
|||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<main id="main-content" class="grid-container">
|
||||
<div class="grid-col desktop:grid-offset-2 desktop:grid-col-8">
|
||||
<main id="main-content" class="grid-container grid-container--widescreen">
|
||||
<div class="grid-row {% if not is_widescreen_centered %}max-width--grid-container{% endif %}">
|
||||
<div class="desktop:grid-col-8">
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="usa-alert usa-alert--{{ message.tags }} usa-alert--slim margin-bottom-3">
|
||||
|
@ -90,11 +91,12 @@ Edit your User Profile |
|
|||
|
||||
|
||||
|
||||
{% endblock content %}
|
||||
{% endblock content %}
|
||||
|
||||
{% block content_bottom %}
|
||||
{% block content_bottom %}
|
||||
{% include "includes/profile_form.html" with form=form %}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
{% endblock content_bottom %}
|
||||
|
||||
|
|
|
@ -290,3 +290,9 @@ def get_dict_value(dictionary, key):
|
|||
if isinstance(dictionary, dict):
|
||||
return dictionary.get(key, "")
|
||||
return ""
|
||||
|
||||
|
||||
@register.filter
|
||||
def button_class(custom_class):
|
||||
default_class = "usa-button"
|
||||
return f"{default_class} {custom_class}" if custom_class else default_class
|
||||
|
|
|
@ -7,7 +7,7 @@ This file tests the various ways in which the registrar interacts with the regis
|
|||
from django.test import TestCase
|
||||
from django.db.utils import IntegrityError
|
||||
from unittest.mock import MagicMock, patch, call
|
||||
import datetime
|
||||
from datetime import datetime, date, timedelta
|
||||
from django.utils.timezone import make_aware
|
||||
from api.tests.common import less_console_noise_decorator
|
||||
from registrar.models import Domain, Host, HostIP
|
||||
|
@ -2267,13 +2267,13 @@ class TestExpirationDate(MockEppLib):
|
|||
"""assert that the setter for expiration date is not implemented and will raise error"""
|
||||
with less_console_noise():
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.domain.registry_expiration_date = datetime.date.today()
|
||||
self.domain.registry_expiration_date = date.today()
|
||||
|
||||
def test_renew_domain(self):
|
||||
"""assert that the renew_domain sets new expiration date in cache and saves to registrar"""
|
||||
with less_console_noise():
|
||||
self.domain.renew_domain()
|
||||
test_date = datetime.date(2023, 5, 25)
|
||||
test_date = date(2023, 5, 25)
|
||||
self.assertEquals(self.domain._cache["ex_date"], test_date)
|
||||
self.assertEquals(self.domain.expiration_date, test_date)
|
||||
|
||||
|
@ -2295,18 +2295,42 @@ class TestExpirationDate(MockEppLib):
|
|||
with less_console_noise():
|
||||
# to do this, need to mock value returned from timezone.now
|
||||
# set now to 2023-01-01
|
||||
mocked_datetime = datetime.datetime(2023, 1, 1, 12, 0, 0)
|
||||
mocked_datetime = datetime(2023, 1, 1, 12, 0, 0)
|
||||
# force fetch_cache which sets the expiration date to 2023-05-25
|
||||
self.domain.statuses
|
||||
with patch("registrar.models.domain.timezone.now", return_value=mocked_datetime):
|
||||
self.assertFalse(self.domain.is_expired())
|
||||
|
||||
def test_is_expiring_within_threshold(self):
|
||||
"""assert that is_expiring returns true when expiration date is within 60 days"""
|
||||
with less_console_noise():
|
||||
mocked_datetime = datetime(2023, 1, 1, 12, 0, 0)
|
||||
expiration_date = mocked_datetime.date() + timedelta(days=30)
|
||||
|
||||
# set domain's expiration date
|
||||
self.domain.expiration_date = expiration_date
|
||||
|
||||
with patch("registrar.models.domain.timezone.now", return_value=mocked_datetime):
|
||||
self.assertTrue(self.domain.is_expiring())
|
||||
|
||||
def test_is_not_expiring_outside_threshold(self):
|
||||
"""assert that is_expiring returns false when expiration date is outside 60 days"""
|
||||
with less_console_noise():
|
||||
mocked_datetime = datetime(2023, 1, 1, 12, 0, 0)
|
||||
expiration_date = mocked_datetime.date() + timedelta(days=61)
|
||||
|
||||
# set domain's expiration date
|
||||
self.domain.expiration_date = expiration_date
|
||||
|
||||
with patch("registrar.models.domain.timezone.now", return_value=mocked_datetime):
|
||||
self.assertFalse(self.domain.is_expiring())
|
||||
|
||||
def test_expiration_date_updated_on_info_domain_call(self):
|
||||
"""assert that expiration date in db is updated on info domain call"""
|
||||
with less_console_noise():
|
||||
# force fetch_cache to be called
|
||||
self.domain.statuses
|
||||
test_date = datetime.date(2023, 5, 25)
|
||||
test_date = date(2023, 5, 25)
|
||||
self.assertEquals(self.domain.expiration_date, test_date)
|
||||
|
||||
|
||||
|
@ -2322,7 +2346,7 @@ class TestCreationDate(MockEppLib):
|
|||
self.domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY)
|
||||
# creation_date returned from mockDataInfoDomain with creation date:
|
||||
# cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35)
|
||||
self.creation_date = make_aware(datetime.datetime(2023, 5, 25, 19, 45, 35))
|
||||
self.creation_date = make_aware(datetime(2023, 5, 25, 19, 45, 35))
|
||||
|
||||
def tearDown(self):
|
||||
Domain.objects.all().delete()
|
||||
|
@ -2331,7 +2355,7 @@ class TestCreationDate(MockEppLib):
|
|||
def test_creation_date_setter_not_implemented(self):
|
||||
"""assert that the setter for creation date is not implemented and will raise error"""
|
||||
with self.assertRaises(NotImplementedError):
|
||||
self.domain.creation_date = datetime.date.today()
|
||||
self.domain.creation_date = date.today()
|
||||
|
||||
def test_creation_date_updated_on_info_domain_call(self):
|
||||
"""assert that creation date in db is updated on info domain call"""
|
||||
|
|
|
@ -424,6 +424,112 @@ class TestDomainDetail(TestDomainOverview):
|
|||
self.assertContains(detail_page, "invited@example.com")
|
||||
|
||||
|
||||
class TestDomainDetailDomainRenewal(TestDomainOverview):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
self.user = get_user_model().objects.create(
|
||||
first_name="User",
|
||||
last_name="Test",
|
||||
email="bogus@example.gov",
|
||||
phone="8003111234",
|
||||
title="test title",
|
||||
username="usertest",
|
||||
)
|
||||
|
||||
self.expiringdomain, _ = Domain.objects.get_or_create(
|
||||
name="expiringdomain.gov",
|
||||
)
|
||||
|
||||
UserDomainRole.objects.get_or_create(
|
||||
user=self.user, domain=self.expiringdomain, role=UserDomainRole.Roles.MANAGER
|
||||
)
|
||||
|
||||
DomainInformation.objects.get_or_create(creator=self.user, domain=self.expiringdomain)
|
||||
|
||||
self.portfolio, _ = Portfolio.objects.get_or_create(organization_name="Test org", creator=self.user)
|
||||
|
||||
self.user.save()
|
||||
|
||||
def custom_is_expired(self):
|
||||
return False
|
||||
|
||||
def custom_is_expiring(self):
|
||||
return True
|
||||
|
||||
@override_flag("domain_renewal", active=True)
|
||||
def test_expiring_domain_on_detail_page_as_domain_manager(self):
|
||||
self.client.force_login(self.user)
|
||||
with patch.object(Domain, "is_expiring", self.custom_is_expiring), patch.object(
|
||||
Domain, "is_expired", self.custom_is_expired
|
||||
):
|
||||
self.assertEquals(self.expiringdomain.state, Domain.State.UNKNOWN)
|
||||
detail_page = self.client.get(
|
||||
reverse("domain", kwargs={"pk": self.expiringdomain.id}),
|
||||
)
|
||||
self.assertContains(detail_page, "Expiring soon")
|
||||
|
||||
self.assertContains(detail_page, "Renew to maintain access")
|
||||
|
||||
self.assertNotContains(detail_page, "DNS needed")
|
||||
self.assertNotContains(detail_page, "Expired")
|
||||
|
||||
@override_flag("domain_renewal", active=True)
|
||||
@override_flag("organization_feature", active=True)
|
||||
def test_expiring_domain_on_detail_page_in_org_model_as_a_non_domain_manager(self):
|
||||
portfolio, _ = Portfolio.objects.get_or_create(organization_name="Test org", creator=self.user)
|
||||
non_dom_manage_user = get_user_model().objects.create(
|
||||
first_name="Non Domain",
|
||||
last_name="Manager",
|
||||
email="verybogus@example.gov",
|
||||
phone="8003111234",
|
||||
title="test title again",
|
||||
username="nondomain",
|
||||
)
|
||||
|
||||
non_dom_manage_user.save()
|
||||
UserPortfolioPermission.objects.get_or_create(
|
||||
user=non_dom_manage_user,
|
||||
portfolio=portfolio,
|
||||
roles=[UserPortfolioRoleChoices.ORGANIZATION_MEMBER],
|
||||
additional_permissions=[
|
||||
UserPortfolioPermissionChoices.VIEW_ALL_DOMAINS,
|
||||
],
|
||||
)
|
||||
expiringdomain2, _ = Domain.objects.get_or_create(name="bogusdomain2.gov")
|
||||
DomainInformation.objects.get_or_create(
|
||||
creator=non_dom_manage_user, domain=expiringdomain2, portfolio=self.portfolio
|
||||
)
|
||||
non_dom_manage_user.refresh_from_db()
|
||||
self.client.force_login(non_dom_manage_user)
|
||||
with patch.object(Domain, "is_expiring", self.custom_is_expiring), patch.object(
|
||||
Domain, "is_expired", self.custom_is_expired
|
||||
):
|
||||
detail_page = self.client.get(
|
||||
reverse("domain", kwargs={"pk": expiringdomain2.id}),
|
||||
)
|
||||
self.assertContains(detail_page, "Contact one of the listed domain managers to renew the domain.")
|
||||
|
||||
@override_flag("domain_renewal", active=True)
|
||||
@override_flag("organization_feature", active=True)
|
||||
def test_expiring_domain_on_detail_page_in_org_model_as_a_domain_manager(self):
|
||||
portfolio, _ = Portfolio.objects.get_or_create(organization_name="Test org2", creator=self.user)
|
||||
|
||||
expiringdomain3, _ = Domain.objects.get_or_create(name="bogusdomain3.gov")
|
||||
|
||||
UserDomainRole.objects.get_or_create(user=self.user, domain=expiringdomain3, role=UserDomainRole.Roles.MANAGER)
|
||||
DomainInformation.objects.get_or_create(creator=self.user, domain=expiringdomain3, portfolio=portfolio)
|
||||
self.user.refresh_from_db()
|
||||
self.client.force_login(self.user)
|
||||
with patch.object(Domain, "is_expiring", self.custom_is_expiring), patch.object(
|
||||
Domain, "is_expired", self.custom_is_expired
|
||||
):
|
||||
detail_page = self.client.get(
|
||||
reverse("domain", kwargs={"pk": expiringdomain3.id}),
|
||||
)
|
||||
self.assertContains(detail_page, "Renew to maintain access")
|
||||
|
||||
|
||||
class TestDomainManagers(TestDomainOverview):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
|
@ -2348,3 +2454,125 @@ class TestDomainChangeNotifications(TestDomainOverview):
|
|||
|
||||
# Check that an email was not sent
|
||||
self.assertFalse(self.mock_client.send_email.called)
|
||||
|
||||
|
||||
class TestDomainRenewal(TestWithUser):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
today = datetime.now()
|
||||
expiring_date = (today + timedelta(days=30)).strftime("%Y-%m-%d")
|
||||
expiring_date_current = (today + timedelta(days=70)).strftime("%Y-%m-%d")
|
||||
expired_date = (today - timedelta(days=30)).strftime("%Y-%m-%d")
|
||||
|
||||
self.domain_with_expiring_soon_date, _ = Domain.objects.get_or_create(
|
||||
name="igorville.gov", expiration_date=expiring_date
|
||||
)
|
||||
self.domain_with_expired_date, _ = Domain.objects.get_or_create(
|
||||
name="domainwithexpireddate.com", expiration_date=expired_date
|
||||
)
|
||||
|
||||
self.domain_with_current_date, _ = Domain.objects.get_or_create(
|
||||
name="domainwithfarexpireddate.com", expiration_date=expiring_date_current
|
||||
)
|
||||
|
||||
UserDomainRole.objects.get_or_create(
|
||||
user=self.user, domain=self.domain_with_current_date, role=UserDomainRole.Roles.MANAGER
|
||||
)
|
||||
|
||||
UserDomainRole.objects.get_or_create(
|
||||
user=self.user, domain=self.domain_with_expired_date, role=UserDomainRole.Roles.MANAGER
|
||||
)
|
||||
|
||||
UserDomainRole.objects.get_or_create(
|
||||
user=self.user, domain=self.domain_with_expiring_soon_date, role=UserDomainRole.Roles.MANAGER
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
try:
|
||||
UserDomainRole.objects.all().delete()
|
||||
Domain.objects.all().delete()
|
||||
except ValueError:
|
||||
pass
|
||||
super().tearDown()
|
||||
|
||||
# Remove test_without_domain_renewal_flag when domain renewal is released as a feature
|
||||
@less_console_noise_decorator
|
||||
@override_flag("domain_renewal", active=False)
|
||||
def test_without_domain_renewal_flag(self):
|
||||
self.client.force_login(self.user)
|
||||
domains_page = self.client.get("/")
|
||||
self.assertNotContains(domains_page, "will expire soon")
|
||||
self.assertNotContains(domains_page, "Expiring soon")
|
||||
|
||||
@less_console_noise_decorator
|
||||
@override_flag("domain_renewal", active=True)
|
||||
def test_domain_renewal_flag_single_domain(self):
|
||||
self.client.force_login(self.user)
|
||||
domains_page = self.client.get("/")
|
||||
self.assertContains(domains_page, "One domain will expire soon")
|
||||
self.assertContains(domains_page, "Expiring soon")
|
||||
|
||||
@less_console_noise_decorator
|
||||
@override_flag("domain_renewal", active=True)
|
||||
def test_with_domain_renewal_flag_mulitple_domains(self):
|
||||
today = datetime.now()
|
||||
expiring_date = (today + timedelta(days=30)).strftime("%Y-%m-%d")
|
||||
self.domain_with_another_expiring, _ = Domain.objects.get_or_create(
|
||||
name="domainwithanotherexpiringdate.com", expiration_date=expiring_date
|
||||
)
|
||||
|
||||
UserDomainRole.objects.get_or_create(
|
||||
user=self.user, domain=self.domain_with_another_expiring, role=UserDomainRole.Roles.MANAGER
|
||||
)
|
||||
self.client.force_login(self.user)
|
||||
domains_page = self.client.get("/")
|
||||
self.assertContains(domains_page, "Multiple domains will expire soon")
|
||||
self.assertContains(domains_page, "Expiring soon")
|
||||
|
||||
@less_console_noise_decorator
|
||||
@override_flag("domain_renewal", active=True)
|
||||
def test_with_domain_renewal_flag_no_expiring_domains(self):
|
||||
UserDomainRole.objects.filter(user=self.user, domain=self.domain_with_expired_date).delete()
|
||||
UserDomainRole.objects.filter(user=self.user, domain=self.domain_with_expiring_soon_date).delete()
|
||||
self.client.force_login(self.user)
|
||||
domains_page = self.client.get("/")
|
||||
self.assertNotContains(domains_page, "Expiring soon")
|
||||
self.assertNotContains(domains_page, "will expire soon")
|
||||
|
||||
@less_console_noise_decorator
|
||||
@override_flag("domain_renewal", active=True)
|
||||
@override_flag("organization_feature", active=True)
|
||||
def test_domain_renewal_flag_single_domain_w_org_feature_flag(self):
|
||||
self.client.force_login(self.user)
|
||||
domains_page = self.client.get("/")
|
||||
self.assertContains(domains_page, "One domain will expire soon")
|
||||
self.assertContains(domains_page, "Expiring soon")
|
||||
|
||||
@less_console_noise_decorator
|
||||
@override_flag("domain_renewal", active=True)
|
||||
@override_flag("organization_feature", active=True)
|
||||
def test_with_domain_renewal_flag_mulitple_domains_w_org_feature_flag(self):
|
||||
today = datetime.now()
|
||||
expiring_date = (today + timedelta(days=31)).strftime("%Y-%m-%d")
|
||||
self.domain_with_another_expiring_org_model, _ = Domain.objects.get_or_create(
|
||||
name="domainwithanotherexpiringdate_orgmodel.com", expiration_date=expiring_date
|
||||
)
|
||||
|
||||
UserDomainRole.objects.get_or_create(
|
||||
user=self.user, domain=self.domain_with_another_expiring_org_model, role=UserDomainRole.Roles.MANAGER
|
||||
)
|
||||
self.client.force_login(self.user)
|
||||
domains_page = self.client.get("/")
|
||||
self.assertContains(domains_page, "Multiple domains will expire soon")
|
||||
self.assertContains(domains_page, "Expiring soon")
|
||||
|
||||
@less_console_noise_decorator
|
||||
@override_flag("domain_renewal", active=True)
|
||||
@override_flag("organization_feature", active=True)
|
||||
def test_with_domain_renewal_flag_no_expiring_domains_w_org_feature_flag(self):
|
||||
UserDomainRole.objects.filter(user=self.user, domain=self.domain_with_expired_date).delete()
|
||||
UserDomainRole.objects.filter(user=self.user, domain=self.domain_with_expiring_soon_date).delete()
|
||||
self.client.force_login(self.user)
|
||||
domains_page = self.client.get("/")
|
||||
self.assertNotContains(domains_page, "Expiring soon")
|
||||
self.assertNotContains(domains_page, "will expire soon")
|
||||
|
|
|
@ -8,24 +8,34 @@ from django_webtest import WebTest # type: ignore
|
|||
from django.utils.dateparse import parse_date
|
||||
from api.tests.common import less_console_noise_decorator
|
||||
from waffle.testutils import override_flag
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
|
||||
class GetDomainsJsonTest(TestWithUser, WebTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.app.set_user(self.user.username)
|
||||
today = datetime.now()
|
||||
expiring_date = (today + timedelta(days=30)).strftime("%Y-%m-%d")
|
||||
expiring_date_2 = (today + timedelta(days=31)).strftime("%Y-%m-%d")
|
||||
|
||||
# Create test domains
|
||||
self.domain1 = Domain.objects.create(name="example1.com", expiration_date="2024-01-01", state="unknown")
|
||||
self.domain2 = Domain.objects.create(name="example2.com", expiration_date="2024-02-01", state="dns needed")
|
||||
self.domain3 = Domain.objects.create(name="example3.com", expiration_date="2024-03-01", state="ready")
|
||||
self.domain4 = Domain.objects.create(name="example4.com", expiration_date="2024-03-01", state="ready")
|
||||
|
||||
self.domain5 = Domain.objects.create(name="example5.com", expiration_date=expiring_date, state="expiring soon")
|
||||
self.domain6 = Domain.objects.create(
|
||||
name="example6.com", expiration_date=expiring_date_2, state="expiring soon"
|
||||
)
|
||||
# Create UserDomainRoles
|
||||
UserDomainRole.objects.create(user=self.user, domain=self.domain1)
|
||||
UserDomainRole.objects.create(user=self.user, domain=self.domain2)
|
||||
UserDomainRole.objects.create(user=self.user, domain=self.domain3)
|
||||
|
||||
UserDomainRole.objects.create(user=self.user, domain=self.domain5)
|
||||
UserDomainRole.objects.create(user=self.user, domain=self.domain6)
|
||||
|
||||
# Create Portfolio
|
||||
self.portfolio = Portfolio.objects.create(creator=self.user, organization_name="Example org")
|
||||
|
||||
|
@ -63,7 +73,7 @@ class GetDomainsJsonTest(TestWithUser, WebTest):
|
|||
self.assertEqual(data["num_pages"], 1)
|
||||
|
||||
# Check the number of domains
|
||||
self.assertEqual(len(data["domains"]), 3)
|
||||
self.assertEqual(len(data["domains"]), 5)
|
||||
|
||||
# Expected domains
|
||||
expected_domains = [self.domain1, self.domain2, self.domain3]
|
||||
|
@ -310,7 +320,7 @@ class GetDomainsJsonTest(TestWithUser, WebTest):
|
|||
self.assertFalse(data["has_previous"])
|
||||
self.assertEqual(data["num_pages"], 1)
|
||||
self.assertEqual(data["total"], 1)
|
||||
self.assertEqual(data["unfiltered_total"], 3)
|
||||
self.assertEqual(data["unfiltered_total"], 5)
|
||||
|
||||
# Check the number of domain requests
|
||||
self.assertEqual(len(data["domains"]), 1)
|
||||
|
@ -377,14 +387,15 @@ class GetDomainsJsonTest(TestWithUser, WebTest):
|
|||
@less_console_noise_decorator
|
||||
def test_state_filtering(self):
|
||||
"""Test that different states in request get expected responses."""
|
||||
|
||||
expected_values = [
|
||||
("unknown", 1),
|
||||
("ready", 0),
|
||||
("expired", 2),
|
||||
("ready,expired", 2),
|
||||
("unknown,expired", 3),
|
||||
("expiring", 2),
|
||||
]
|
||||
|
||||
for state, num_domains in expected_values:
|
||||
with self.subTest(state=state, num_domains=num_domains):
|
||||
response = self.app.get(reverse("get_domains_json"), {"status": state})
|
||||
|
|
|
@ -805,15 +805,6 @@ class DomainDNSSECView(DomainFormBaseView):
|
|||
context = super().get_context_data(**kwargs)
|
||||
|
||||
has_dnssec_records = self.object.dnssecdata is not None
|
||||
|
||||
# Create HTML for the modal button
|
||||
modal_button = (
|
||||
'<button type="submit" '
|
||||
'class="usa-button usa-button--secondary" '
|
||||
'name="disable_dnssec">Confirm</button>'
|
||||
)
|
||||
|
||||
context["modal_button"] = modal_button
|
||||
context["has_dnssec_records"] = has_dnssec_records
|
||||
context["dnssec_enabled"] = self.request.session.pop("dnssec_enabled", False)
|
||||
|
||||
|
@ -906,15 +897,6 @@ class DomainDsDataView(DomainFormBaseView):
|
|||
# to preserve the context["form"]
|
||||
context = super().get_context_data(form=formset)
|
||||
context["trigger_modal"] = True
|
||||
# Create HTML for the modal button
|
||||
modal_button = (
|
||||
'<button type="submit" '
|
||||
'class="usa-button usa-button--secondary" '
|
||||
'name="disable-override-click">Remove all DS data</button>'
|
||||
)
|
||||
|
||||
# context to back out of a broken form on all fields delete
|
||||
context["modal_button"] = modal_button
|
||||
return self.render_to_response(context)
|
||||
|
||||
if formset.is_valid() or override:
|
||||
|
@ -1050,9 +1032,6 @@ class DomainUsersView(DomainBaseView):
|
|||
# Add conditionals to the context (such as "can_delete_users")
|
||||
context = self._add_booleans_to_context(context)
|
||||
|
||||
# Add modal buttons to the context (such as for delete)
|
||||
context = self._add_modal_buttons_to_context(context)
|
||||
|
||||
# Get portfolio from session (if set)
|
||||
portfolio = self.request.session.get("portfolio")
|
||||
|
||||
|
@ -1149,26 +1128,6 @@ class DomainUsersView(DomainBaseView):
|
|||
context["can_delete_users"] = can_delete_users
|
||||
return context
|
||||
|
||||
def _add_modal_buttons_to_context(self, context):
|
||||
"""Adds modal buttons (and their HTML) to the context"""
|
||||
# Create HTML for the modal button
|
||||
modal_button = (
|
||||
'<button type="submit" '
|
||||
'class="usa-button usa-button--secondary" '
|
||||
'name="delete_domain_manager">Yes, remove domain manager</button>'
|
||||
)
|
||||
context["modal_button"] = modal_button
|
||||
|
||||
# Create HTML for the modal button when deleting yourself
|
||||
modal_button_self = (
|
||||
'<button type="submit" '
|
||||
'class="usa-button usa-button--secondary" '
|
||||
'name="delete_domain_manager_self">Yes, remove myself</button>'
|
||||
)
|
||||
context["modal_button_self"] = modal_button_self
|
||||
|
||||
return context
|
||||
|
||||
|
||||
class DomainAddUserView(DomainFormBaseView):
|
||||
"""Inside of a domain's user management, a form for adding users.
|
||||
|
|
|
@ -448,34 +448,21 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
|
|||
non_org_steps_complete = DomainRequest._form_complete(self.domain_request, self.request)
|
||||
org_steps_complete = len(self.db_check_for_unlocking_steps()) == len(self.steps)
|
||||
if (not self.is_portfolio and non_org_steps_complete) or (self.is_portfolio and org_steps_complete):
|
||||
modal_button = '<button type="submit" ' 'class="usa-button" ' ">Submit request</button>"
|
||||
context = {
|
||||
"not_form": False,
|
||||
"form_titles": self.titles,
|
||||
"steps": self.steps,
|
||||
"visited": self.storage.get("step_history", []),
|
||||
"is_federal": self.domain_request.is_federal(),
|
||||
"modal_button": modal_button,
|
||||
"modal_heading": "You are about to submit a domain request for ",
|
||||
"domain_name_modal": str(self.domain_request.requested_domain),
|
||||
"modal_description": "Once you submit this request, you won’t be able to edit it until we review it.\
|
||||
You’ll only be able to withdraw your request.",
|
||||
"review_form_is_complete": True,
|
||||
"user": self.request.user,
|
||||
"requested_domain__name": requested_domain_name,
|
||||
}
|
||||
else: # form is not complete
|
||||
modal_button = '<button type="button" class="usa-button" data-close-modal>Return to request</button>'
|
||||
context = {
|
||||
"not_form": True,
|
||||
"form_titles": self.titles,
|
||||
"steps": self.steps,
|
||||
"visited": self.storage.get("step_history", []),
|
||||
"is_federal": self.domain_request.is_federal(),
|
||||
"modal_button": modal_button,
|
||||
"modal_heading": "Your request form is incomplete",
|
||||
"modal_description": 'This request cannot be submitted yet.\
|
||||
Return to the request and visit the steps that are marked as "incomplete."',
|
||||
"review_form_is_complete": False,
|
||||
"user": self.request.user,
|
||||
"requested_domain__name": requested_domain_name,
|
||||
|
|
|
@ -109,6 +109,10 @@ def apply_sorting(queryset, request):
|
|||
sort_by = request.GET.get("sort_by", "id") # Default to 'id'
|
||||
order = request.GET.get("order", "asc") # Default to 'asc'
|
||||
|
||||
# Handle special case for 'creator'
|
||||
if sort_by == "creator":
|
||||
sort_by = "creator__email"
|
||||
|
||||
if order == "desc":
|
||||
sort_by = f"-{sort_by}"
|
||||
return queryset.order_by(sort_by)
|
||||
|
|
|
@ -27,7 +27,7 @@ def get_domains_json(request):
|
|||
page_number = request.GET.get("page")
|
||||
page_obj = paginator.get_page(page_number)
|
||||
|
||||
domains = [serialize_domain(domain, request.user) for domain in page_obj.object_list]
|
||||
domains = [serialize_domain(domain, request) for domain in page_obj.object_list]
|
||||
|
||||
return JsonResponse(
|
||||
{
|
||||
|
@ -80,21 +80,27 @@ def apply_state_filter(queryset, request):
|
|||
status_list.append("dns needed")
|
||||
# Split the status list into normal states and custom states
|
||||
normal_states = [state for state in status_list if state in Domain.State.values]
|
||||
custom_states = [state for state in status_list if state == "expired"]
|
||||
custom_states = [state for state in status_list if (state == "expired" or state == "expiring")]
|
||||
# Construct Q objects for normal states that can be queried through ORM
|
||||
state_query = Q()
|
||||
if normal_states:
|
||||
state_query |= Q(state__in=normal_states)
|
||||
# Handle custom states in Python, as expired can not be queried through ORM
|
||||
if "expired" in custom_states:
|
||||
expired_domain_ids = [domain.id for domain in queryset if domain.state_display() == "Expired"]
|
||||
expired_domain_ids = [domain.id for domain in queryset if domain.state_display(request) == "Expired"]
|
||||
state_query |= Q(id__in=expired_domain_ids)
|
||||
if "expiring" in custom_states:
|
||||
expiring_domain_ids = [domain.id for domain in queryset if domain.state_display(request) == "Expiring soon"]
|
||||
state_query |= Q(id__in=expiring_domain_ids)
|
||||
# Apply the combined query
|
||||
queryset = queryset.filter(state_query)
|
||||
# If there are filtered states, and expired is not one of them, domains with
|
||||
# state_display of 'Expired' must be removed
|
||||
if "expired" not in custom_states:
|
||||
expired_domain_ids = [domain.id for domain in queryset if domain.state_display() == "Expired"]
|
||||
expired_domain_ids = [domain.id for domain in queryset if domain.state_display(request) == "Expired"]
|
||||
queryset = queryset.exclude(id__in=expired_domain_ids)
|
||||
if "expiring" not in custom_states:
|
||||
expired_domain_ids = [domain.id for domain in queryset if domain.state_display(request) == "Expiring soon"]
|
||||
queryset = queryset.exclude(id__in=expired_domain_ids)
|
||||
|
||||
return queryset
|
||||
|
@ -105,7 +111,7 @@ def apply_sorting(queryset, request):
|
|||
order = request.GET.get("order", "asc")
|
||||
if sort_by == "state_display":
|
||||
objects = list(queryset)
|
||||
objects.sort(key=lambda domain: domain.state_display(), reverse=(order == "desc"))
|
||||
objects.sort(key=lambda domain: domain.state_display(request), reverse=(order == "desc"))
|
||||
return objects
|
||||
else:
|
||||
if order == "desc":
|
||||
|
@ -113,7 +119,8 @@ def apply_sorting(queryset, request):
|
|||
return queryset.order_by(sort_by)
|
||||
|
||||
|
||||
def serialize_domain(domain, user):
|
||||
def serialize_domain(domain, request):
|
||||
user = request.user
|
||||
suborganization_name = None
|
||||
try:
|
||||
domain_info = domain.domain_info
|
||||
|
@ -133,7 +140,7 @@ def serialize_domain(domain, user):
|
|||
"name": domain.name,
|
||||
"expiration_date": domain.expiration_date,
|
||||
"state": domain.state,
|
||||
"state_display": domain.state_display(),
|
||||
"state_display": domain.state_display(request),
|
||||
"get_state_help_text": domain.get_state_help_text(),
|
||||
"action_url": reverse("domain", kwargs={"pk": domain.id}),
|
||||
"action_label": ("View" if view_only else "Manage"),
|
||||
|
|
|
@ -8,5 +8,6 @@ def index(request):
|
|||
if request and request.user and request.user.is_authenticated:
|
||||
# This controls the creation of a new domain request in the wizard
|
||||
context["user_domain_count"] = request.user.get_user_domain_ids(request).count()
|
||||
context["num_expiring_domains"] = request.user.get_num_expiring_domains(request)
|
||||
|
||||
return render(request, "home.html", context)
|
||||
|
|
|
@ -39,6 +39,8 @@ class PortfolioDomainsView(PortfolioDomainsPermissionView, View):
|
|||
context = {}
|
||||
if self.request and self.request.user and self.request.user.is_authenticated:
|
||||
context["user_domain_count"] = self.request.user.get_user_domain_ids(request).count()
|
||||
context["num_expiring_domains"] = request.user.get_num_expiring_domains(request)
|
||||
|
||||
return render(request, "portfolio_domains.html", context)
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue