Merge remote-tracking branch 'origin/main' into ms/1768-status-change-notifications

This commit is contained in:
matthewswspence 2024-12-31 14:48:40 -06:00
commit c657b9e666
No known key found for this signature in database
GPG key ID: FB458202A7852BA4
76 changed files with 1388 additions and 798 deletions

View file

@ -1995,7 +1995,7 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
@admin.display(description=_("Requested Domain")) @admin.display(description=_("Requested Domain"))
def custom_requested_domain(self, obj): def custom_requested_domain(self, obj):
# Example: Show different icons based on `status` # 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 text = obj.requested_domain
if obj.portfolio: if obj.portfolio:
return format_html('<a href="{}"><img src="/public/admin/img/icon-yes.svg"> {}</a>', url, text) return format_html('<a href="{}"><img src="/public/admin/img/icon-yes.svg"> {}</a>', url, text)

View 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");
});
}
}
});
}

View 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");
});
}
}
});
}

View 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
});
});
}
});
}

View 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");
});
}
});
}

View 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' });
}
});
}

View file

@ -75,3 +75,16 @@ export function debounce(handler, cooldown=600) {
export function getCsrfToken() { export function getCsrfToken() {
return document.querySelector('input[name="csrfmiddlewaretoken"]').value; 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.");
}
}

View file

@ -11,6 +11,11 @@ import { initMembersTable } from './table-members.js';
import { initMemberDomainsTable } from './table-member-domains.js'; import { initMemberDomainsTable } from './table-member-domains.js';
import { initEditMemberDomainsTable } from './table-edit-member-domains.js'; import { initEditMemberDomainsTable } from './table-edit-member-domains.js';
import { initPortfolioNewMemberPageToggle, initAddNewMemberPageListeners, initPortfolioMemberPageRadio } from './portfolio-member-page.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(); initDomainValidators();
@ -36,6 +41,13 @@ initMembersTable();
initMemberDomainsTable(); initMemberDomainsTable();
initEditMemberDomainsTable(); initEditMemberDomainsTable();
initDomainRequestForm();
initDomainManagersPage();
initDomainDSData();
initDomainDNSSEC();
initFormErrorHandling();
// Init the portfolio new member page // Init the portfolio new member page
initPortfolioMemberPageRadio(); initPortfolioMemberPageRadio();
initPortfolioNewMemberPageToggle(); initPortfolioNewMemberPageToggle();

View file

@ -495,7 +495,8 @@ export class BaseTable {
// Add event listeners to table headers for sorting // Add event listeners to table headers for sorting
initializeTableHeaders() { initializeTableHeaders() {
this.tableHeaders.forEach(header => { 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'); const sortBy = header.getAttribute('data-sortable');
let order = 'asc'; let order = 'asc';
// sort order will be ascending, unless the currently sorted column is ascending, and the user // 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 // load the results with the updated sort
this.loadTable(1, sortBy, order); 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();
}
}); });
}); });
} }

View file

@ -31,6 +31,9 @@ export class DomainsTable extends BaseTable {
</td> </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 = ` row.innerHTML = `
<th scope="row" role="rowheader" data-label="Domain name"> <th scope="row" role="rowheader" data-label="Domain name">
${domain.name} ${domain.name}
@ -41,14 +44,14 @@ export class DomainsTable extends BaseTable {
<td data-label="Status"> <td data-label="Status">
${domain.state_display} ${domain.state_display}
<svg <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" data-position="top"
title="${domain.get_state_help_text}" title="${domain.get_state_help_text}"
focusable="true" focusable="true"
aria-label="${domain.get_state_help_text}" aria-label="${domain.get_state_help_text}"
role="tooltip" 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> </svg>
</td> </td>
${markupForSuborganizationRow} ${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)
}
}
});
});
}
});

View file

@ -176,7 +176,16 @@ html[data-theme="dark"] {
color: var(--primary-fg); 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, #branding h1,
h1, h2, h3, h1, h2, h3,

View file

@ -1,21 +1,18 @@
@use "uswds-core" as *; @use "uswds-core" as *;
@use "base" 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 // The icon was off center for some reason
// Fixes that issue // 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--warning{
.usa-alert__body::before { .usa-alert__body::before {
left: 1rem !important; left: 1rem !important;
@ -24,13 +21,29 @@
.usa-alert__body.margin-left-1 { .usa-alert__body.margin-left-1 {
margin-left: 0.5rem!important; 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 { Alert Fonts
max-width: $widescreen-max-width !important; -----------------*/
// 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-site-alert--hot-pink {
.usa-alert { .usa-alert {
background-color: $hot-pink; background-color: $hot-pink;
@ -47,3 +60,4 @@
background-color: color('base-darkest'); background-color: color('base-darkest');
} }
} }

View file

@ -2,6 +2,8 @@
@use "cisa_colors" as *; @use "cisa_colors" as *;
$widescreen-max-width: 1920px; $widescreen-max-width: 1920px;
$widescreen-x-padding: 4.5rem;
$hot-pink: #FFC3F9; $hot-pink: #FFC3F9;
/* Styles for making visible to screen reader / AT users only. */ /* Styles for making visible to screen reader / AT users only. */
@ -252,6 +254,15 @@ abbr[title] {
max-width: $widescreen-max-width; 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-neg-4px {
margin-right: -4px; margin-right: -4px;
} }
@ -265,4 +276,8 @@ abbr[title] {
margin: 0; margin: 0;
height: 1.5em; height: 1.5em;
width: 1.5em; width: 1.5em;
}
.maxw-fit-content {
max-width: fit-content;
} }

View file

@ -6,3 +6,21 @@
.usa-identifier__container--widescreen { .usa-identifier__container--widescreen {
max-width: $widescreen-max-width !important; 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;
}

View file

@ -110,8 +110,8 @@
} }
} }
.usa-nav__secondary { .usa-nav__secondary {
// I don't know why USWDS has this at 2 rem, which puts it out of alignment right: 1rem;
right: 3rem; padding-right: $widescreen-x-padding;
color: color('white'); color: color('white');
bottom: 4.3rem; bottom: 4.3rem;
.usa-nav-link, .usa-nav-link,

View file

@ -88,8 +88,14 @@ th {
} }
@include at-media(tablet-lg) { @include at-media(tablet-lg) {
th[data-sortable]:not([aria-sort]) .usa-table__header__button { th[data-sortable] .usa-table__header__button {
right: auto; right: auto;
&[aria-sort=ascending],
&[aria-sort=descending],
&:not([aria-sort]) {
right: auto;
}
} }
} }
} }

View file

@ -251,7 +251,7 @@ TEMPLATES = [
"registrar.context_processors.org_user_status", "registrar.context_processors.org_user_status",
"registrar.context_processors.add_path_to_context", "registrar.context_processors.add_path_to_context",
"registrar.context_processors.portfolio_permissions", "registrar.context_processors.portfolio_permissions",
"registrar.context_processors.is_widescreen_mode", "registrar.context_processors.is_widescreen_centered",
], ],
}, },
}, },

View file

@ -69,9 +69,19 @@ def portfolio_permissions(request):
"has_organization_requests_flag": False, "has_organization_requests_flag": False,
"has_organization_members_flag": False, "has_organization_members_flag": False,
"is_portfolio_admin": False, "is_portfolio_admin": False,
"has_domain_renewal_flag": False,
} }
try: try:
portfolio = request.session.get("portfolio") 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 # Linting: line too long
view_suborg = request.user.has_view_suborganization_portfolio_permission(portfolio) view_suborg = request.user.has_view_suborganization_portfolio_permission(portfolio)
edit_suborg = request.user.has_edit_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_requests_flag": request.user.has_organization_requests_flag(),
"has_organization_members_flag": request.user.has_organization_members_flag(), "has_organization_members_flag": request.user.has_organization_members_flag(),
"is_portfolio_admin": request.user.is_portfolio_admin(portfolio), "is_portfolio_admin": request.user.is_portfolio_admin(portfolio),
"has_domain_renewal_flag": request.user.has_domain_renewal_flag(),
} }
return portfolio_context return portfolio_context
@ -98,31 +109,21 @@ def portfolio_permissions(request):
return portfolio_context return portfolio_context
def is_widescreen_mode(request): def is_widescreen_centered(request):
widescreen_paths = [] # If this list is meant to include specific paths, populate it. include_paths = [
portfolio_widescreen_paths = [
"/domains/", "/domains/",
"/requests/", "/requests/",
"/request/", "/members/",
"/no-organization-requests/",
"/no-organization-domains/",
"/domain-request/",
] ]
# widescreen_paths can be a bear as it trickles down sub-urls. exclude_paths gives us a way out.
exclude_paths = [ exclude_paths = [
"/domains/edit", "/domains/edit",
"members/new-member/",
] ]
# Check if the current path matches a widescreen path or the root path. is_excluded = any(exclude_path in request.path for exclude_path in exclude_paths)
is_widescreen = any(path in request.path for path in widescreen_paths) or request.path == "/"
# Check if the user is an organization user and the path matches portfolio paths. # Check if the current path matches a path in included_paths or the root path.
is_portfolio_widescreen = ( is_widescreen_centered = any(path in request.path for path in include_paths) or request.path == "/"
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)
)
# Return a dictionary with the widescreen mode status. # 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}

View file

@ -2,7 +2,7 @@ from itertools import zip_longest
import logging import logging
import ipaddress import ipaddress
import re import re
from datetime import date from datetime import date, timedelta
from typing import Optional from typing import Optional
from django_fsm import FSMField, transition, TransitionNotAllowed # type: ignore 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 .public_contact import PublicContact
from .user_domain_role import UserDomainRole from .user_domain_role import UserDomainRole
from waffle.decorators import flag_is_active
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -1152,14 +1153,29 @@ class Domain(TimeStampedModel, DomainHelper):
now = timezone.now().date() now = timezone.now().date()
return self.expiration_date < now 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.""" """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" 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: elif self.state == self.State.UNKNOWN or self.state == self.State.DNS_NEEDED:
return "DNS needed" return "DNS needed"
else: return self.state.capitalize()
return self.state.capitalize()
def map_epp_contact_to_public_contact(self, contact: eppInfo.InfoContactResultData, contact_id, contact_type): def map_epp_contact_to_public_contact(self, contact: eppInfo.InfoContactResultData, contact_id, contact_type):
"""Maps the Epp contact representation to a PublicContact object. """Maps the Epp contact representation to a PublicContact object.

View file

@ -14,6 +14,8 @@ from .domain import Domain
from .domain_request import DomainRequest from .domain_request import DomainRequest
from registrar.utility.waffle import flag_is_active_for_user from registrar.utility.waffle import flag_is_active_for_user
from waffle.decorators import flag_is_active from waffle.decorators import flag_is_active
from django.utils import timezone
from datetime import timedelta
from phonenumber_field.modelfields import PhoneNumberField # type: ignore 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() active_requests_count = self.domain_requests_created.filter(status__in=allowed_states).count()
return active_requests_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): def get_rejected_requests_count(self):
"""Return count of rejected requests""" """Return count of rejected requests"""
return self.domain_requests_created.filter(status=DomainRequest.DomainRequestStatus.REJECTED).count() return self.domain_requests_created.filter(status=DomainRequest.DomainRequestStatus.REJECTED).count()
@ -259,6 +275,9 @@ class User(AbstractUser):
def is_portfolio_admin(self, portfolio): def is_portfolio_admin(self, portfolio):
return "Admin" in self.portfolio_role_summary(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): def get_first_portfolio(self):
permission = self.portfolio_permissions.first() permission = self.portfolio_permissions.first()
if permission: if permission:

View file

@ -5,8 +5,8 @@
{% block title %}{% translate "Unauthorized | " %}{% endblock %} {% block title %}{% translate "Unauthorized | " %}{% endblock %}
{% block content %} {% 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">
<div class="grid-row grow-gap"> <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"> <div class="tablet:grid-col-6 usa-prose margin-bottom-3">
<h1> <h1>
{% translate "You are not authorized to view this page" %} {% translate "You are not authorized to view this page" %}

View file

@ -5,8 +5,8 @@
{% block title %}{% translate "Forbidden | " %}{% endblock %} {% block title %}{% translate "Forbidden | " %}{% endblock %}
{% block content %} {% 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">
<div class="grid-row grow-gap"> <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"> <div class="tablet:grid-col-6 usa-prose margin-bottom-3">
<h1> <h1>
{% translate "You're not authorized to view this page." %} {% translate "You're not authorized to view this page." %}

View file

@ -5,8 +5,8 @@
{% block title %}{% translate "Page not found | " %}{% endblock %} {% block title %}{% translate "Page not found | " %}{% endblock %}
{% block content %} {% 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">
<div class="grid-row grid-gap"> <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"> <div class="tablet:grid-col-6 usa-prose margin-bottom-3">
<h1> <h1>
{% translate "We couldnt find that page" %} {% translate "We couldnt find that page" %}

View file

@ -5,8 +5,8 @@
{% block title %}{% translate "Server error | " %}{% endblock %} {% block title %}{% translate "Server error | " %}{% endblock %}
{% block content %} {% 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">
<div class="grid-row grid-gap"> <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"> <div class="tablet:grid-col-6 usa-prose margin-bottom-3">
<h1> <h1>
{% translate "We're having some trouble." %} {% translate "We're having some trouble." %}

View file

@ -39,7 +39,7 @@
{% for model in app.models %} {% for model in app.models %}
<tr class="model-{{ model.object_name|lower }}{% if model.admin_url in request.path|urlencode %} current-model{% endif %}"> <tr class="model-{{ model.object_name|lower }}{% if model.admin_url in request.path|urlencode %} current-model{% endif %}">
{% if model.admin_url %} {% 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 %} {% else %}
<th scope="row">{{ model.name }}</th> <th scope="row">{{ model.name }}</th>
{% endif %} {% endif %}

View file

@ -61,7 +61,7 @@ https://github.com/django/django/blob/main/django/contrib/admin/templates/admin/
{% if field.field.help_text %} {% if field.field.help_text %}
{# .gov override #} {# .gov override #}
{% block help_text %} {% 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>{{ field.field.help_text|safe }}</div>
</div> </div>
{% endblock help_text %} {% endblock help_text %}

View file

@ -43,7 +43,7 @@
<select name="selected_user" id="selected_user" class="admin-combobox margin-top-0" onchange="this.form.submit()"> <select name="selected_user" id="selected_user" class="admin-combobox margin-top-0" onchange="this.form.submit()">
<option value="">Select a user</option> <option value="">Select a user</option>
{% for user in other_users %} {% 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 }} {{ user.first_name }} {{ user.last_name }}
</option> </option>
{% endfor %} {% endfor %}

View file

@ -97,7 +97,7 @@
<section class="usa-banner" aria-label="Official website of the United States government"> <section class="usa-banner" aria-label="Official website of the United States government">
<div class="usa-accordion"> <div class="usa-accordion">
<header class="usa-banner__header"> <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"> <div class="grid-col-auto">
<img class="usa-banner__header-flag" src="{% static 'img/us_flag_small.png' %}" alt="U.S. flag" /> <img class="usa-banner__header-flag" src="{% static 'img/us_flag_small.png' %}" alt="U.S. flag" />
</div> </div>
@ -113,7 +113,7 @@
</button> </button>
</div> </div>
</header> </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="grid-row grid-gap-lg">
<div class="usa-banner__guidance tablet:grid-col-6"> <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" <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 wrapper %}
{% block wrapperdiv %} {% block wrapperdiv %}
<div id="wrapper"> <div id="wrapper" class="wrapper--padding-top-6">
{% endblock wrapperdiv %} {% endblock wrapperdiv %}
{% block messages %} {% block messages %}
{% if messages %} {% if messages %}
<ul class="messages"> <ul class="messages">
{% for message in messages %} {% for message in messages %}
{% if 'base' in message.extra_tags %} {% if 'base' in message.extra_tags %}
<li{% if message.tags %} class="{{ message.tags }}" {% endif %}> <li{% if message.tags %} class="{{ message.tags }}" {% endif %}">
{{ message }} {{ message }}
</li> </li>
{% endif %} {% endif %}

View file

@ -11,7 +11,7 @@
{% if messages %} {% if messages %}
<ul class="messages"> <ul class="messages">
{% for message in messages %} {% for message in messages %}
<li {% if message.tags %} class="{{ message.tags }}" {% endif %}> <li {% if message.tags %} class="{{ message.tags }}" {% endif %}">
{{ message }} {{ message }}
</li> </li>
{% endfor %} {% endfor %}

View file

@ -1,6 +1,6 @@
{% comment %} This view provides a detail button that can be used to show/hide content {% endcomment %} {% 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> <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"> <div class="grid-container margin-left-0 padding-left-0 padding-right-0 dja-details-contents">
{% block detail_content %} {% block detail_content %}

View file

@ -11,7 +11,7 @@
{% endblock %} {% endblock %}
{% block help_text %} {% 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" %} {% if field.field.name == "state" %}
<div>{{ state_help_message }}</div> <div>{{ state_help_message }}</div>
{% else %} {% else %}

View file

@ -6,14 +6,14 @@
<ul class="mulitple-choice"> <ul class="mulitple-choice">
{% for choice in choices %} {% for choice in choices %}
{% if choice.reset %} {% 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> <a href="{{ choice.query_string|iriencode }}" title="{{ choice.display }}">{{ choice.display }}</a>
</li> </li>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% for choice in choices %} {% for choice in choices %}
{% if not choice.reset %} {% 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 %} {% if choice.selected and choice.exclude_query_string %}
<a class="choice-filter choice-filter--checked" href="{{ choice.exclude_query_string|iriencode }}">{{ choice.display }} <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"> <svg class="usa-icon position-absolute z-0 left-0" aria-hidden="true" focusable="false" role="img" width="24" height="24">

View file

@ -37,6 +37,9 @@
</nav> </nav>
{% endif %} {% endif %}
{% endblock breadcrumb %} {% endblock breadcrumb %}
{% include "includes/form_errors.html" with form=form %}
<h1>Add a domain manager</h1> <h1>Add a domain manager</h1>
{% if has_organization_feature_flag %} {% if has_organization_feature_flag %}
<p> <p>

View file

@ -4,10 +4,10 @@
{% block title %}{{ domain.name }} | {% endblock %} {% block title %}{{ domain.name }} | {% endblock %}
{% block content %} {% block content %}
<div class="grid-container"> <div class="grid-container grid-container--widescreen">
<div class="grid-row grid-gap"> <div class="grid-row grid-gap {% if not is_widescreen_centered %}max-width--grid-container{% endif %}">
<div class="tablet:grid-col-3"> <div class="tablet:grid-col-3 ">
<p class="font-body-md margin-top-0 margin-bottom-2 <p class="font-body-md margin-top-0 margin-bottom-2
text-primary-darker text-semibold domain-name-wrap" text-primary-darker text-semibold domain-name-wrap"
> >

View file

@ -35,18 +35,27 @@
Status: Status:
</span> </span>
<span class="text-primary-darker"> <span class="text-primary-darker">
{# UNKNOWN domains would not have an expiration date and thus would show 'Expired' #} {# UNKNOWN domains would not have an expiration date and thus would show 'Expired' #}
{% if domain.is_expired and domain.state != domain.State.UNKNOWN %} {% if domain.is_expired and domain.state != domain.State.UNKNOWN %}
Expired 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 %} {% elif domain.state == domain.State.UNKNOWN or domain.state == domain.State.DNS_NEEDED %}
DNS needed DNS needed
{% else %} {% else %}
{{ domain.state|title }} {{ domain.state|title }}
{% endif %} {% endif %}
</span> </span>
{% if domain.get_state_help_text %} {% if domain.get_state_help_text %}
<div class="padding-top-1 text-primary-darker"> <div class="padding-top-1 text-primary-darker">
{{ domain.get_state_help_text }} {% 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> </div>
{% endif %} {% endif %}
</p> </p>
@ -119,4 +128,4 @@
{% endif %} {% endif %}
</div> </div>
{% endblock %} {# domain_content #} {% endblock %} {# domain_content #}

View file

@ -27,7 +27,7 @@
{% endif %} {% endif %}
{% endblock breadcrumb %} {% 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 its connecting to the correct server, preventing potential hijacking or tampering with your domain's records.</p> <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 its 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-labelledby="Are you sure you want to continue?"
aria-describedby="Your DNSSEC records will be deleted from the registry." 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> </div>
<form method="post" id="disable-dnssec-form">
{% csrf_token %}
<input type="hidden" name="disable_dnssec" value="1">
</form>
{% endblock %} {# domain_content #} {% endblock %} {# domain_content #}

View file

@ -42,7 +42,7 @@
{% include "includes/form_errors.html" with form=form %} {% include "includes/form_errors.html" with form=form %}
{% endfor %} {% 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> <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." aria-describedby="Your DNSSEC records will be deleted from the registry."
data-force-action 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, youll 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, youll 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> </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 #} {% endblock %} {# domain_content #}

View file

@ -3,13 +3,13 @@
{% block title %}{{form_titles|get_item:steps.current}} | Request a .gov | {% endblock %} {% block title %}{{form_titles|get_item:steps.current}} | Request a .gov | {% endblock %}
{% block content %} {% block content %}
<div class="grid-container"> <div class="grid-container grid-container--widescreen">
<div class="grid-row grid-gap"> <div class="grid-row grid-gap {% if not is_widescreen_centered %}max-width--grid-container{% endif %}">
<div class="tablet:grid-col-3"> <div class="tablet:grid-col-3">
{% include 'domain_request_sidebar.html' %} {% include 'domain_request_sidebar.html' %}
</div> </div>
<div class="tablet:grid-col-9"> <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}}"/> <input type="hidden" class="display-none" id="wizard-domain-request-id" value="{{domain_request_id}}"/>
{% if steps.current == steps.first %} {% if steps.current == steps.first %}
{% if portfolio %} {% if portfolio %}
@ -130,9 +130,17 @@
aria-describedby="Are you sure you want to submit a domain request?" aria-describedby="Are you sure you want to submit a domain request?"
data-force-action 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 wont be able to edit it until we review it. Youll 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> </div>
<form method="post" id="submit-domain-request-form">
{% csrf_token %}
</form>
{% block after_form_content %}{% endblock %} {% block after_form_content %}{% endblock %}
</main> </main>

View file

@ -4,41 +4,42 @@
{% block title %} Start a request | {% endblock %} {% block title %} Start a request | {% endblock %}
{% block content %} {% block content %}
<main id="main-content" class="grid-container"> <main id="main-content" class="grid-container grid-container--widescreen">
<div class="grid-col desktop:grid-offset-2 desktop:grid-col-8"> <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> <form class="usa-form usa-form--extra-large" method="post" novalidate>
{% csrf_token %} {% csrf_token %}
<h1>Youre about to start your .gov domain request.</h1> <h1>Youre about to start your .gov domain request.</h1>
<p>You dont have to complete the process in one session. You can save what you enter and come back to it when youre ready.</p> <p>You dont have to complete the process in one session. You can save what you enter and come back to it when youre ready.</p>
{% if portfolio %} {% if portfolio %}
<p>Well use the information you provide to verify your domain request meets our guidelines.</p> <p>Well use the information you provide to verify your domain request meets our guidelines.</p>
{% else %} {% else %}
<p>Well use the information you provide to verify your organizations eligibility for a .gov domain. Well also verify that the domain you request meets our guidelines.</p> <p>Well use the information you provide to verify your organizations eligibility for a .gov domain. Well also verify that the domain you request meets our guidelines.</p>
{% endif %} {% endif %}
<h2>Time to complete the form</h2> <h2>Time to complete the form</h2>
<p>If you have <a href="{% public_site_url 'domains/before/#information-you%E2%80%99ll-need-to-complete-the-domain-request-form' %}" target="_blank" class="usa-link">all the information you need</a>, <p>If you have <a href="{% public_site_url 'domains/before/#information-you%E2%80%99ll-need-to-complete-the-domain-request-form' %}" target="_blank" class="usa-link">all the information you need</a>,
completing your domain request might take around 15 minutes.</p> completing your domain request might take around 15 minutes.</p>
<h2>How well reach you</h2> <h2>How well reach you</h2>
<p>While reviewing your domain request, we may need to reach out with questions. Well also email you when we complete our review. If the contact information below is not correct, visit <a href="{% url 'user-profile' %}?redirect=domain-request:start" class="usa-link">your profile</a> to make updates.</p> <p>While reviewing your domain request, we may need to reach out with questions. Well also email you when we complete our review. If the contact information below is not correct, visit <a href="{% url 'user-profile' %}?redirect=domain-request:start" class="usa-link">your profile</a> to make updates.</p>
{% include "includes/profile_information.html" with user=user%} {% include "includes/profile_information.html" with user=user%}
{% block form_buttons %} {% block form_buttons %}
<div class="stepnav"> <div class="stepnav">
<button <button
type="submit" type="submit"
name="submit_button" name="submit_button"
value="intro_acknowledge" value="intro_acknowledge"
class="usa-button" class="usa-button"
>Continue</button> >Continue</button>
</div> </div>
{% endblock %} {% endblock %}
</form> </form>
<div class="caption margin-top-5"><a href="{% public_site_url 'privacy-policy#pra' %}" target="_blank" rel="noopener noreferrer" class="usa-link">Paperwork Reduction Act statement</a> (OMB control number: 1670-0049; expiration date: 10/31/2026)</div> <div class="caption margin-top-5"><a href="{% public_site_url 'privacy-policy#pra' %}" target="_blank" rel="noopener noreferrer" class="usa-link">Paperwork Reduction Act statement</a> (OMB control number: 1670-0049; expiration date: 10/31/2026)</div>
</div> </div>
</main> </main>
{% endblock %} {% endblock %}

View file

@ -8,18 +8,20 @@
{% endblock wrapperdiv %} {% endblock wrapperdiv %}
{% block content %} {% block content %}
<div class="grid-container"> <div class="grid-container grid-container--widescreen">
<div class="grid-col desktop:grid-offset-2 desktop:grid-col-8"> <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> <a href="{% url 'domain-request-status' DomainRequest.id %}">Cancel</a></p>
</div> </div>
</div>
</div> </div>
{% endblock %} {% endblock %}

View file

@ -18,7 +18,7 @@
<li class="usa-sidenav__item"> <li class="usa-sidenav__item">
{% url 'domain-dns' pk=domain.id as url %} {% 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 DNS
</a> </a>
{% if request.path|startswith:url %} {% if request.path|startswith:url %}

View file

@ -49,7 +49,7 @@
</ul> </ul>
{% if domain_manager_roles %} {% 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"> <table class="usa-table usa-table--borderless usa-table--stacked dotgov-table--stacked dotgov-table">
<h2 class> Domain managers </h2> <h2 class> Domain managers </h2>
<caption class="sr-only">Domain managers</caption> <caption class="sr-only">Domain managers</caption>
@ -89,12 +89,13 @@
aria-describedby="You will be removed from this domain" aria-describedby="You will be removed from this domain"
data-force-action 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 counter_str=forloop.counter|stringformat:"s" %}
{% 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_id="user-delete-button-"|add:counter_str|safe modal_button_text="Yes, remove myself" modal_button_class="usa-button--secondary" %}
{% 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 %} {% endwith %}
{% endwith %}
</form>
</div> </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 %} {% else %}
<div <div
class="usa-modal" class="usa-modal"
@ -103,12 +104,13 @@
aria-describedby="{{ item.permission.user.email }} will be removed" aria-describedby="{{ item.permission.user.email }} will be removed"
data-force-action 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 counter_str=forloop.counter|stringformat:"s" %}
{% 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_id="user-delete-button-"|add:counter_str|safe modal_button_text="Yes, remove domain manager" modal_button_class="usa-button--secondary" %}
{% 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 %} {% endwith %}
{% endwith %}
</form>
</div> </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 %} {% endif %}
{% else %} {% else %}
<input <input

View file

@ -5,12 +5,12 @@
{% block title %} Home | {% endblock %} {% block title %} Home | {% endblock %}
{% block content %} {% 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 %} {% if user.is_authenticated %}
{# the entire logged in page goes here #} {# the entire logged in page goes here #}
{% block homepage_content %} {% 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 %} {% block messages %}
{% include "includes/form_messages.html" %} {% include "includes/form_messages.html" %}
{% endblock %} {% endblock %}

View file

@ -1,6 +1,6 @@
<div class="margin-y-0 {% if add_class %}{{ add_class }}{% endif %}" aria-label="Site alert"> <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 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"> <h4 class="usa-alert__heading">
Header Header
</h4> </h4>

View file

@ -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"> <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">
<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"> <h4 class="usa-alert__heading">
Header Header
</h4> </h4>

View file

@ -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"> <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">
<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"> <p class="usa-alert__text maxw-none">
<strong>Attention:</strong> You are on a test site. <strong>Attention:</strong> You are on a test site.
</p> </p>

View file

@ -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"> <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">
<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"> <h3 class="usa-alert__heading">
Service disruption Service disruption
</h3> </h3>

View file

@ -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"> <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">
<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"> <h3 class="usa-alert__heading">
Header here Header here
</h3> </h3>

View file

@ -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"> <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">
<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"> <h3 class="usa-alert__heading">
System outage System outage
</h3> </h3>

View file

@ -1,6 +1,6 @@
<div class="margin-y-0 {% if add_class %}{{ add_class }}{% endif %}" aria-label="Site alert"> <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 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"> <h4 class="usa-alert__heading">
Header Header
</h4> </h4>

View file

@ -1,236 +1,238 @@
{% load custom_filters %} {% load custom_filters %}
{% load static url_helpers %} {% load static url_helpers %}
<main id="main-content" class="grid-container"> <main id="main-content" class="grid-container grid-container--widescreen">
<div class="grid-col desktop:grid-offset-2 desktop:grid-col-8"> <div class="grid-row {% if not is_widescreen_centered %}max-width--grid-container{% endif %}">
{% block breadcrumb %} <div class="grid-col {% if is_widescreen_centered %}desktop:grid-offset-2 {% endif %} desktop:grid-col-8">
{% if portfolio %} {% block breadcrumb %}
{% url 'domain-requests' as url %}
{% else %}
{% url 'home' as url %}
{% endif %}
<nav class="usa-breadcrumb padding-top-0" aria-label="Domain request breadcrumb">
<ol class="usa-breadcrumb__list">
<li class="usa-breadcrumb__list-item">
{% if portfolio %} {% if portfolio %}
<a href="{{ url }}" class="usa-breadcrumb__link"><span>Domain requests</span></a> {% url 'domain-requests' as url %}
{% else %} {% else %}
<a href="{{ url }}" class="usa-breadcrumb__link"><span>Manage your domains</span></a> {% url 'home' as url %}
{% endif %} {% endif %}
</li> <nav class="usa-breadcrumb padding-top-0" aria-label="Domain request breadcrumb">
<li class="usa-breadcrumb__list-item usa-current" aria-current="page"> <ol class="usa-breadcrumb__list">
{% if not DomainRequest.requested_domain and DomainRequest.status == DomainRequest.DomainRequestStatus.STARTED %} <li class="usa-breadcrumb__list-item">
<span>New domain request</span> {% if portfolio %}
{% else %} <a href="{{ url }}" class="usa-breadcrumb__link"><span>Domain requests</span></a>
<span>{{ DomainRequest.requested_domain.name }}</span> {% else %}
<a href="{{ url }}" class="usa-breadcrumb__link"><span>Manage your domains</span></a>
{% endif %}
</li>
<li class="usa-breadcrumb__list-item usa-current" aria-current="page">
{% if not DomainRequest.requested_domain and DomainRequest.status == DomainRequest.DomainRequestStatus.STARTED %}
<span>New domain request</span>
{% else %}
<span>{{ DomainRequest.requested_domain.name }}</span>
{% endif %}
</li>
</ol>
</nav>
{% endblock breadcrumb %}
{% block header %}
{% if not DomainRequest.requested_domain and DomainRequest.status == DomainRequest.DomainRequestStatus.STARTED %}
<h1>New domain request</h1>
{% else %}
<h1>Domain request for {{ DomainRequest.requested_domain.name }}</h1>
{% endif %}
{% endblock header %}
{% block status_summary %}
<div
class="usa-summary-box dotgov-status-box margin-top-3 padding-left-2"
role="region"
aria-labelledby="summary-box-key-information"
>
<div class="usa-summary-box__body">
<p class="usa-summary-box__heading font-sans-md margin-bottom-0"
id="summary-box-key-information"
>
<span class="text-bold text-primary-darker">
Status:
</span>
{{ DomainRequest.get_status_display|default:"ERROR Please contact technical support/dev" }}
</p>
</div>
</div>
<br>
{% endblock status_summary %}
{% block status_metadata %}
{% if portfolio %}
{% if DomainRequest.creator %}
<p class="margin-top-1 margin-bottom-1">
<b class="review__step__name">Created by:</b> {{DomainRequest.creator.email|default:DomainRequest.creator.get_formatted_name }}
</p>
{% else %}
<p class="margin-top-1 margin-bottom-1">
<b class="review__step__name">No creator found:</b> this is an error, please email <a href="mailto:help@get.gov" class="usa-link">help@get.gov</a>.
</p>
{% endif %}
{% endif %} {% endif %}
</li>
</ol>
</nav>
{% endblock breadcrumb %}
{% block header %} {% with statuses=DomainRequest.DomainRequestStatus last_submitted=DomainRequest.last_submitted_date|date:"F j, Y" first_submitted=DomainRequest.first_submitted_date|date:"F j, Y" last_status_update=DomainRequest.last_status_update|date:"F j, Y" %}
{% if not DomainRequest.requested_domain and DomainRequest.status == DomainRequest.DomainRequestStatus.STARTED %}
<h1>New domain request</h1>
{% else %}
<h1>Domain request for {{ DomainRequest.requested_domain.name }}</h1>
{% endif %}
{% endblock header %}
{% block status_summary %}
<div
class="usa-summary-box dotgov-status-box margin-top-3 padding-left-2"
role="region"
aria-labelledby="summary-box-key-information"
>
<div class="usa-summary-box__body">
<p class="usa-summary-box__heading font-sans-md margin-bottom-0"
id="summary-box-key-information"
>
<span class="text-bold text-primary-darker">
Status:
</span>
{{ DomainRequest.get_status_display|default:"ERROR Please contact technical support/dev" }}
</p>
</div>
</div>
<br>
{% endblock status_summary %}
{% block status_metadata %}
{% if portfolio %}
{% if DomainRequest.creator %}
<p class="margin-top-1 margin-bottom-1">
<b class="review__step__name">Created by:</b> {{DomainRequest.creator.email|default:DomainRequest.creator.get_formatted_name }}
</p>
{% else %}
<p class="margin-top-1 margin-bottom-1">
<b class="review__step__name">No creator found:</b> this is an error, please email <a href="mailto:help@get.gov" class="usa-link">help@get.gov</a>.
</p>
{% endif %}
{% endif %}
{% with statuses=DomainRequest.DomainRequestStatus last_submitted=DomainRequest.last_submitted_date|date:"F j, Y" first_submitted=DomainRequest.first_submitted_date|date:"F j, Y" last_status_update=DomainRequest.last_status_update|date:"F j, Y" %}
{% comment %}
These are intentionally seperated this way.
There is some code repetition, but it gives us more flexibility rather than a dense reduction.
Leave it this way until we've solidified our requirements.
{% endcomment %}
{% if DomainRequest.status == statuses.STARTED %}
{% with first_started_date=DomainRequest.get_first_status_started_date|date:"F j, Y" %}
<p class="margin-top-1">
{% comment %} {% comment %}
A newly created domain request will not have a value for last_status update. These are intentionally seperated this way.
This is because the status never really updated. There is some code repetition, but it gives us more flexibility rather than a dense reduction.
However, if this somehow goes back to started we can default to displaying that new date. Leave it this way until we've solidified our requirements.
{% endcomment %} {% endcomment %}
<b class="review__step__name">Started on:</b> {{last_status_update|default:first_started_date}} {% if DomainRequest.status == statuses.STARTED %}
</p> {% with first_started_date=DomainRequest.get_first_status_started_date|date:"F j, Y" %}
{% endwith %} <p class="margin-top-1">
{% elif DomainRequest.status == statuses.SUBMITTED %} {% comment %}
<p class="margin-top-1 margin-bottom-1"> A newly created domain request will not have a value for last_status update.
<b class="review__step__name">Submitted on:</b> {{last_submitted|default:first_submitted }} This is because the status never really updated.
</p> However, if this somehow goes back to started we can default to displaying that new date.
<p class="margin-top-1"> {% endcomment %}
<b class="review__step__name">Last updated on:</b> {{DomainRequest.updated_at|date:"F j, Y"}} <b class="review__step__name">Started on:</b> {{last_status_update|default:first_started_date}}
</p>
{% elif DomainRequest.status == statuses.ACTION_NEEDED %}
<p class="margin-top-1 margin-bottom-1">
<b class="review__step__name">Submitted on:</b> {{last_submitted|default:first_submitted }}
</p>
<p class="margin-top-1">
<b class="review__step__name">Last updated on:</b> {{DomainRequest.updated_at|date:"F j, Y"}}
</p>
{% elif DomainRequest.status == statuses.REJECTED %}
<p class="margin-top-1 margin-bottom-1">
<b class="review__step__name">Submitted on:</b> {{last_submitted|default:first_submitted }}
</p>
<p class="margin-top-1">
<b class="review__step__name">Rejected on:</b> {{last_status_update}}
</p>
{% elif DomainRequest.status == statuses.WITHDRAWN %}
<p class="margin-top-1 margin-bottom-1">
<b class="review__step__name">Submitted on:</b> {{last_submitted|default:first_submitted }}
</p>
<p class="margin-top-1">
<b class="review__step__name">Withdrawn on:</b> {{last_status_update}}
</p>
{% else %}
{% comment %} Shown for in_review, approved, ineligible {% endcomment %}
<p class="margin-top-1">
<b class="review__step__name">Last updated on:</b> {{DomainRequest.updated_at|date:"F j, Y"}}
</p>
{% endif %}
{% endwith %}
{% endblock status_metadata %}
{% block status_blurb %}
{% if DomainRequest.is_awaiting_review %}
<p>{% include "includes/domain_request_awaiting_review.html" with show_withdraw_text=DomainRequest.is_withdrawable %}</p>
{% endif %}
{% endblock status_blurb %}
{% block modify_request %}
{% if DomainRequest.is_withdrawable %}
<p><a href="{% url 'domain-request-withdraw-confirmation' pk=DomainRequest.id %}" class="usa-button usa-button--outline withdraw_outline">
Withdraw request</a>
</p> </p>
{% endif %}
{% endblock modify_request %}
</div>
<div class="grid-col desktop:grid-offset-2 maxw-tablet">
{% block request_summary_header %}
<h2 class="text-primary-darker"> Summary of your domain request </h2>
{% endblock request_summary_header%}
{% block request_summary %}
{% with heading_level='h3' %}
{% with org_type=DomainRequest.get_generic_org_type_display %}
{% include "includes/summary_item.html" with title='Type of organization' value=org_type heading_level=heading_level %}
{% endwith %}
{% if DomainRequest.tribe_name %}
{% include "includes/summary_item.html" with title='Tribal government' value=DomainRequest.tribe_name heading_level=heading_level %}
{% if DomainRequest.federally_recognized_tribe %}
<p>Federally-recognized tribe</p>
{% endif %}
{% if DomainRequest.state_recognized_tribe %}
<p>State-recognized tribe</p>
{% endif %}
{% endif %}
{% if DomainRequest.get_federal_type_display %}
{% include "includes/summary_item.html" with title='Federal government branch' value=DomainRequest.get_federal_type_display heading_level=heading_level %}
{% endif %}
{% if DomainRequest.is_election_board %}
{% with value=DomainRequest.is_election_board|yesno:"Yes,No,Incomplete" %}
{% include "includes/summary_item.html" with title='Election office' value=value heading_level=heading_level %}
{% endwith %} {% endwith %}
{% endif %} {% elif DomainRequest.status == statuses.SUBMITTED %}
<p class="margin-top-1 margin-bottom-1">
<b class="review__step__name">Submitted on:</b> {{last_submitted|default:first_submitted }}
</p>
<p class="margin-top-1">
<b class="review__step__name">Last updated on:</b> {{DomainRequest.updated_at|date:"F j, Y"}}
</p>
{% elif DomainRequest.status == statuses.ACTION_NEEDED %}
<p class="margin-top-1 margin-bottom-1">
<b class="review__step__name">Submitted on:</b> {{last_submitted|default:first_submitted }}
</p>
<p class="margin-top-1">
<b class="review__step__name">Last updated on:</b> {{DomainRequest.updated_at|date:"F j, Y"}}
</p>
{% elif DomainRequest.status == statuses.REJECTED %}
<p class="margin-top-1 margin-bottom-1">
<b class="review__step__name">Submitted on:</b> {{last_submitted|default:first_submitted }}
</p>
<p class="margin-top-1">
<b class="review__step__name">Rejected on:</b> {{last_status_update}}
</p>
{% elif DomainRequest.status == statuses.WITHDRAWN %}
<p class="margin-top-1 margin-bottom-1">
<b class="review__step__name">Submitted on:</b> {{last_submitted|default:first_submitted }}
</p>
<p class="margin-top-1">
<b class="review__step__name">Withdrawn on:</b> {{last_status_update}}
</p>
{% else %}
{% comment %} Shown for in_review, approved, ineligible {% endcomment %}
<p class="margin-top-1">
<b class="review__step__name">Last updated on:</b> {{DomainRequest.updated_at|date:"F j, Y"}}
</p>
{% endif %}
{% endwith %}
{% endblock status_metadata %}
{% if DomainRequest.organization_name %} {% block status_blurb %}
{% include "includes/summary_item.html" with title='Organization' value=DomainRequest address='true' heading_level=heading_level %} {% if DomainRequest.is_awaiting_review %}
{% endif %} <p>{% include "includes/domain_request_awaiting_review.html" with show_withdraw_text=DomainRequest.is_withdrawable %}</p>
{% endif %}
{% endblock status_blurb %}
{% if DomainRequest.about_your_organization %} {% block modify_request %}
{% include "includes/summary_item.html" with title='About your organization' value=DomainRequest.about_your_organization heading_level=heading_level %} {% if DomainRequest.is_withdrawable %}
{% endif %} <p><a href="{% url 'domain-request-withdraw-confirmation' pk=DomainRequest.id %}" class="usa-button usa-button--outline withdraw_outline">
Withdraw request</a>
</p>
{% endif %}
{% endblock modify_request %}
</div>
{% if DomainRequest.senior_official %} <div class="grid-col {% if is_widescreen_centered %}desktop:grid-offset-2 maxw-tablet{% endif %}">
{% include "includes/summary_item.html" with title='Senior official' value=DomainRequest.senior_official contact='true' heading_level=heading_level %} {% block request_summary_header %}
{% endif %} <h2 class="text-primary-darker"> Summary of your domain request </h2>
{% endblock request_summary_header%}
{% if DomainRequest.current_websites.all %} {% block request_summary %}
{% include "includes/summary_item.html" with title='Current websites' value=DomainRequest.current_websites.all list='true' heading_level=heading_level %} {% with heading_level='h3' %}
{% endif %} {% with org_type=DomainRequest.get_generic_org_type_display %}
{% include "includes/summary_item.html" with title='Type of organization' value=org_type heading_level=heading_level %}
{% endwith %}
{% if DomainRequest.requested_domain %} {% if DomainRequest.tribe_name %}
{% include "includes/summary_item.html" with title='.gov domain' value=DomainRequest.requested_domain heading_level=heading_level %} {% include "includes/summary_item.html" with title='Tribal government' value=DomainRequest.tribe_name heading_level=heading_level %}
{% endif %}
{% if DomainRequest.alternative_domains.all %} {% if DomainRequest.federally_recognized_tribe %}
{% include "includes/summary_item.html" with title='Alternative domains' value=DomainRequest.alternative_domains.all list='true' heading_level=heading_level %} <p>Federally-recognized tribe</p>
{% endif %}
{% if DomainRequest.purpose %}
{% include "includes/summary_item.html" with title='Purpose of your domain' value=DomainRequest.purpose heading_level=heading_level %}
{% endif %}
{% if DomainRequest.creator %}
{% include "includes/summary_item.html" with title='Your contact information' value=DomainRequest.creator contact='true' heading_level=heading_level %}
{% endif %}
{% if DomainRequest.other_contacts.all %}
{% include "includes/summary_item.html" with title='Other employees from your organization' value=DomainRequest.other_contacts.all contact='true' list='true' heading_level=heading_level %}
{% else %}
{% include "includes/summary_item.html" with title='Other employees from your organization' value=DomainRequest.no_other_contacts_rationale heading_level=heading_level %}
{% endif %}
{# We always show this field even if None #}
{% if DomainRequest %}
<h3 class="register-form-review-header">CISA Regional Representative</h3>
<ul class="usa-list usa-list--unstyled margin-top-0">
{% if DomainRequest.cisa_representative_first_name %}
{{ DomainRequest.get_formatted_cisa_rep_name }}
{% else %}
No
{% endif %} {% endif %}
</ul>
<h3 class="register-form-review-header">Anything else</h3> {% if DomainRequest.state_recognized_tribe %}
<ul class="usa-list usa-list--unstyled margin-top-0"> <p>State-recognized tribe</p>
{% if DomainRequest.anything_else %}
{{DomainRequest.anything_else}}
{% else %}
No
{% endif %} {% endif %}
</ul>
{% endif %} {% endif %}
{% endwith %}
{% endblock request_summary%} {% if DomainRequest.get_federal_type_display %}
{% include "includes/summary_item.html" with title='Federal government branch' value=DomainRequest.get_federal_type_display heading_level=heading_level %}
{% endif %}
{% if DomainRequest.is_election_board %}
{% with value=DomainRequest.is_election_board|yesno:"Yes,No,Incomplete" %}
{% include "includes/summary_item.html" with title='Election office' value=value heading_level=heading_level %}
{% endwith %}
{% endif %}
{% if DomainRequest.organization_name %}
{% include "includes/summary_item.html" with title='Organization' value=DomainRequest address='true' heading_level=heading_level %}
{% endif %}
{% if DomainRequest.about_your_organization %}
{% include "includes/summary_item.html" with title='About your organization' value=DomainRequest.about_your_organization heading_level=heading_level %}
{% endif %}
{% if DomainRequest.senior_official %}
{% include "includes/summary_item.html" with title='Senior official' value=DomainRequest.senior_official contact='true' heading_level=heading_level %}
{% endif %}
{% if DomainRequest.current_websites.all %}
{% include "includes/summary_item.html" with title='Current websites' value=DomainRequest.current_websites.all list='true' heading_level=heading_level %}
{% endif %}
{% if DomainRequest.requested_domain %}
{% include "includes/summary_item.html" with title='.gov domain' value=DomainRequest.requested_domain heading_level=heading_level %}
{% endif %}
{% if DomainRequest.alternative_domains.all %}
{% include "includes/summary_item.html" with title='Alternative domains' value=DomainRequest.alternative_domains.all list='true' heading_level=heading_level %}
{% endif %}
{% if DomainRequest.purpose %}
{% include "includes/summary_item.html" with title='Purpose of your domain' value=DomainRequest.purpose heading_level=heading_level %}
{% endif %}
{% if DomainRequest.creator %}
{% include "includes/summary_item.html" with title='Your contact information' value=DomainRequest.creator contact='true' heading_level=heading_level %}
{% endif %}
{% if DomainRequest.other_contacts.all %}
{% include "includes/summary_item.html" with title='Other employees from your organization' value=DomainRequest.other_contacts.all contact='true' list='true' heading_level=heading_level %}
{% else %}
{% include "includes/summary_item.html" with title='Other employees from your organization' value=DomainRequest.no_other_contacts_rationale heading_level=heading_level %}
{% endif %}
{# We always show this field even if None #}
{% if DomainRequest %}
<h3 class="register-form-review-header">CISA Regional Representative</h3>
<ul class="usa-list usa-list--unstyled margin-top-0">
{% if DomainRequest.cisa_representative_first_name %}
{{ DomainRequest.get_formatted_cisa_rep_name }}
{% else %}
No
{% endif %}
</ul>
<h3 class="register-form-review-header">Anything else</h3>
<ul class="usa-list usa-list--unstyled margin-top-0">
{% if DomainRequest.anything_else %}
{{DomainRequest.anything_else}}
{% else %}
No
{% endif %}
</ul>
{% endif %}
{% endwith %}
{% endblock request_summary%}
</div>
</div> </div>
</main> </main>

View file

@ -4,8 +4,8 @@
{% url 'get_domain_requests_json' as url %} {% url 'get_domain_requests_json' as url %}
<span id="get_domain_requests_json_url" class="display-none">{{url}}</span> <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"> <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 %}"> <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 %} {% if not portfolio %}
<h2 id="domain-requests-header" class="display-inline-block">Domain requests</h2> <h2 id="domain-requests-header" class="display-inline-block">Domain requests</h2>
{% else %} {% else %}
@ -13,7 +13,7 @@
<span id="portfolio-js-value" data-portfolio="{{ portfolio.id }}"></span> <span id="portfolio-js-value" data-portfolio="{{ portfolio.id }}"></span>
{% endif %} {% 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"> <section aria-label="Domain requests search component" class="margin-top-2">
<form class="usa-search usa-search--small" method="POST" role="search"> <form class="usa-search usa-search--small" method="POST" role="search">
{% csrf_token %} {% csrf_token %}

View file

@ -1,10 +1,30 @@
{% load static %} {% load static %}
{% comment %} Stores the json endpoint in a url for easier access {% endcomment %} {% comment %} Stores the json endpoint in a url for easier access {% endcomment %}
{% url 'get_domains_json' as url %} {% url 'get_domains_json' as url %}
<span id="get_domains_json_url" class="display-none">{{url}}</span> <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"> <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 %}"> <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 %} {% if not portfolio %}
@ -13,7 +33,7 @@
<!-- Embedding the portfolio value in a data attribute --> <!-- Embedding the portfolio value in a data attribute -->
<span id="portfolio-js-value" data-portfolio="{{ portfolio.id }}"></span> <span id="portfolio-js-value" data-portfolio="{{ portfolio.id }}"></span>
{% endif %} {% 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"> <section aria-label="Domains search component" class="margin-top-2">
<form class="usa-search usa-search--small" method="POST" role="search"> <form class="usa-search usa-search--small" method="POST" role="search">
{% csrf_token %} {% csrf_token %}
@ -53,7 +73,24 @@
</div> </div>
{% endif %} {% endif %}
</div> </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"> <div class="display-flex flex-align-center">
<span class="margin-right-2 margin-top-neg-1 usa-prose text-base-darker">Filter by</span> <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"> <div class="usa-accordion usa-accordion--select margin-right-2">
@ -135,6 +172,19 @@
>Deleted</label >Deleted</label
> >
</div> </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> </fieldset>
</div> </div>
</div> </div>
@ -149,7 +199,6 @@
</svg> </svg>
</button> </button>
</div> </div>
{% endif %}
<div class="display-none usa-table-container--scrollable margin-top-0" tabindex="0" id="domains__table-wrapper"> <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"> <table class="usa-table usa-table--borderless usa-table--stacked dotgov-table dotgov-table--stacked">
<caption class="sr-only">Your registered domains</caption> <caption class="sr-only">Your registered domains</caption>
@ -200,4 +249,4 @@
<ul class="usa-pagination__list"> <ul class="usa-pagination__list">
<!-- Pagination links will be dynamically populated by JS --> <!-- Pagination links will be dynamically populated by JS -->
</ul> </ul>
</nav> </nav>

View file

@ -3,7 +3,7 @@
<footer class="usa-footer"> <footer class="usa-footer">
<div class="usa-footer__secondary-section"> <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="grid-row grid-gap">
<div <div
class=" class="
@ -24,7 +24,7 @@
<div class="usa-footer__contact-links <div class="usa-footer__contact-links
mobile-lg:grid-col-6 flex-align-self-center" 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"> <div class="usa-footer__contact-info grid-row grid-gap-md">
{% if show_manage_your_domains %} {% if show_manage_your_domains %}
<div class="grid-col-auto"> <div class="grid-col-auto">
@ -51,7 +51,7 @@
class="usa-identifier__section usa-identifier__section--masthead" class="usa-identifier__section usa-identifier__section--masthead"
aria-label="Agency identifier" 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"> <div class="usa-identifier__logos">
<a rel="noopener noreferrer" target="_blank" href="https://www.cisa.gov" class="usa-identifier__logo" <a rel="noopener noreferrer" target="_blank" href="https://www.cisa.gov" class="usa-identifier__logo"
><img ><img
@ -77,7 +77,7 @@
class="usa-identifier__section usa-identifier__section--required-links" class="usa-identifier__section usa-identifier__section--required-links"
aria-label="Important 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"> <ul class="usa-identifier__required-links-list">
<li class="usa-identifier__required-links-item"> <li class="usa-identifier__required-links-item">
<a rel="noopener noreferrer" target="_blank" href="{% public_site_url 'about/' %}" <a rel="noopener noreferrer" target="_blank" href="{% public_site_url 'about/' %}"
@ -119,7 +119,7 @@
class="usa-identifier__section usa-identifier__section--usagov" class="usa-identifier__section usa-identifier__section--usagov"
aria-label="U.S. government information and services" 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"> <div class="usa-identifier__usagov-description">
Looking for U.S. government information and services? Looking for U.S. government information and services?
</div> </div>

View file

@ -1,6 +1,7 @@
{% if form.errors %} {% if form.errors %}
<div id="form-errors">
{% for error in form.non_field_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"> <div class="usa-alert__body">
{{ error|escape }} {{ error|escape }}
</div> </div>
@ -14,5 +15,6 @@
</div> </div>
</div> </div>
{% endfor %} {% endfor %}
{% endfor %} {% endfor %}
</div>
{% endif %} {% endif %}

View file

@ -1,7 +1,7 @@
{% load static %} {% load static %}
<header class="usa-header usa-header--basic"> <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"> <div class="usa-navbar">
{% include "includes/gov_extended_logo.html" with logo_clickable=logo_clickable %} {% include "includes/gov_extended_logo.html" with logo_clickable=logo_clickable %}
<button type="button" class="usa-menu-btn">Menu</button> <button type="button" class="usa-menu-btn">Menu</button>

View file

@ -2,13 +2,13 @@
{% load custom_filters %} {% load custom_filters %}
<header class="usa-header usa-header--extended"> <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 %} {% include "includes/gov_extended_logo.html" with logo_clickable=logo_clickable %}
<button type="button" class="usa-menu-btn">Menu</button> <button type="button" class="usa-menu-btn">Menu</button>
</div> </div>
{% block usa_nav %} {% block usa_nav %}
<nav class="usa-nav" aria-label="Primary navigation"> <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"> <button type="button" class="usa-nav__close">
<img src="{%static 'img/usa-icons/close.svg'%}" role="img" alt="Close" /> <img src="{%static 'img/usa-icons/close.svg'%}" role="img" alt="Close" />
</button> </button>

View file

@ -8,7 +8,7 @@
<section class="section-outlined members margin-top-0 section-outlined--border-base-light" id="members"> <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"> <div class="section-outlined__header margin-bottom-3 grid-row">
<!-- ---------- SEARCH ---------- --> <!-- ---------- 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"> <section aria-label="Members search component" class="margin-top-2">
<form class="usa-search usa-search--small" method="POST" role="search"> <form class="usa-search usa-search--small" method="POST" role="search">
{% csrf_token %} {% csrf_token %}

View file

@ -1,4 +1,5 @@
{% load static form_helpers url_helpers %} {% load static form_helpers url_helpers %}
{% load custom_filters %}
<div class="usa-modal__content"> <div class="usa-modal__content">
<div class="usa-modal__main"> <div class="usa-modal__main">
@ -24,39 +25,51 @@
<div class="usa-modal__footer"> <div class="usa-modal__footer">
<ul class="usa-button-group"> <ul class="usa-button-group">
<li class="usa-button-group__item"> <li class="usa-button-group__item">
{% if not_form and modal_button %} {% if cancel_button_only %}
{{ modal_button }} <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 %} {% elif modal_button_url and modal_button_text %}
<a <a
href="{{ modal_button_url }}" href="{{ modal_button_url }}"
type="button" type="button"
class="usa-button" class="{{ modal_button_class|button_class }}"
> >
{{ modal_button_text }} {{ modal_button_text }}
</a> </a>
{% else %}
<form method="post">
{% csrf_token %}
{{ modal_button }}
</form>
{% endif %} {% endif %}
</li> </li>
<li class="usa-button-group__item"> <li class="usa-button-group__item">
{% comment %} The cancel button the DS form actually triggers a context change in the view, {% comment %} The cancel button the DS form actually triggers a context change in the view,
in addition to being a close modal hook {% endcomment %} in addition to being a close modal hook {% endcomment %}
{% if cancel_button_resets_ds_form %} {% if cancel_button_resets_ds_form %}
<form method="post"> <button
{% csrf_token %} type="submit"
<button class="usa-button usa-button--unstyled padding-105 text-center"
type="submit" id="btn-cancel-click-button"
class="usa-button usa-button--unstyled padding-105 text-center" data-close-modal
name="btn-cancel-click" >
data-close-modal Cancel
> </button>
Cancel {% elif not cancel_button_only %}
</button>
</form>
{% elif not is_domain_request_form or review_form_is_complete %}
<button <button
type="button" type="button"
class="usa-button usa-button--unstyled padding-105 text-center" 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, {% comment %} The cancel button the DS form actually triggers a context change in the view,
in addition to being a close modal hook {% endcomment %} in addition to being a close modal hook {% endcomment %}
{% if cancel_button_resets_ds_form %} {% if cancel_button_resets_ds_form %}
<form method="post"> <button
{% csrf_token %} type="submit"
<button class="usa-button usa-modal__close"
type="submit" aria-label="Close this window"
class="usa-button usa-modal__close" id="btn-cancel-click-close-button"
aria-label="Close this window" data-close-modal
name="btn-cancel-click" >
data-close-modal <svg class="usa-icon" aria-hidden="true" focusable="false" role="img">
> <use xlink:href="{%static 'img/sprite.svg'%}#close"></use>
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img"> </svg>
<use xlink:href="{%static 'img/sprite.svg'%}#close"></use> </button>
</svg>
</button>
</form>
{% else %} {% else %}
<button <button
type="button" type="button"

View file

@ -1,240 +1,242 @@
{% load custom_filters %} {% load custom_filters %}
{% load static url_helpers %} {% load static url_helpers %}
<main id="main-content" class="grid-container"> <main id="main-content" class="grid-container grid-container--widescreen">
<div class="grid-col desktop:grid-offset-2 desktop:grid-col-8"> <div class="grid-row {% if not is_widescreen_centered %}max-width--grid-container{% endif %}">
{% block breadcrumb %} <div class="grid-col desktop:grid-col-8 {% if is_widescreen_centered %}desktop:grid-offset-2{% endif %}">
{% if portfolio %} {% block breadcrumb %}
{% url 'domain-requests' as url %}
{% else %}
{% url 'home' as url %}
{% endif %}
<nav class="usa-breadcrumb padding-top-0" aria-label="Domain request breadcrumb">
<ol class="usa-breadcrumb__list">
<li class="usa-breadcrumb__list-item">
{% if portfolio %} {% if portfolio %}
<a href="{{ url }}" class="usa-breadcrumb__link"><span>Domain requests</span></a> {% url 'domain-requests' as url %}
{% else %} {% else %}
<a href="{{ url }}" class="usa-breadcrumb__link"><span>Manage your domains</span></a> {% url 'home' as url %}
{% endif %} {% endif %}
</li> <nav class="usa-breadcrumb padding-top-0" aria-label="Domain request breadcrumb">
<li class="usa-breadcrumb__list-item usa-current" aria-current="page"> <ol class="usa-breadcrumb__list">
{% if not DomainRequest.requested_domain and DomainRequest.status == DomainRequest.DomainRequestStatus.STARTED %} <li class="usa-breadcrumb__list-item">
<span>New domain request</span> {% if portfolio %}
{% else %} <a href="{{ url }}" class="usa-breadcrumb__link"><span>Domain requests</span></a>
<span>{{ DomainRequest.requested_domain.name }}</span> {% else %}
<a href="{{ url }}" class="usa-breadcrumb__link"><span>Manage your domains</span></a>
{% endif %}
</li>
<li class="usa-breadcrumb__list-item usa-current" aria-current="page">
{% if not DomainRequest.requested_domain and DomainRequest.status == DomainRequest.DomainRequestStatus.STARTED %}
<span>New domain request</span>
{% else %}
<span>{{ DomainRequest.requested_domain.name }}</span>
{% endif %}
</li>
</ol>
</nav>
{% endblock breadcrumb %}
{% block header %}
{% if not DomainRequest.requested_domain and DomainRequest.status == DomainRequest.DomainRequestStatus.STARTED %}
<h1>New domain request</h1>
{% else %}
<h1>Domain request for {{ DomainRequest.requested_domain.name }}</h1>
{% endif %}
{% endblock header %}
{% block status_summary %}
<div
class="usa-summary-box dotgov-status-box margin-top-3 padding-left-2"
role="region"
aria-labelledby="summary-box-key-information"
>
<div class="usa-summary-box__body">
<p class="usa-summary-box__heading font-sans-md margin-bottom-0"
id="summary-box-key-information"
>
<span class="text-bold text-primary-darker">
Status:
</span>
{{ DomainRequest.get_status_display|default:"ERROR Please contact technical support/dev" }}
</p>
</div>
</div>
<br>
{% endblock status_summary %}
{% block status_metadata %}
{% if portfolio %}
{% if DomainRequest.creator %}
<p class="margin-top-1 margin-bottom-1">
<b class="review__step__name">Created by:</b> {{DomainRequest.creator.email|default:DomainRequest.creator.get_formatted_name }}
</p>
{% else %}
<p class="margin-top-1 margin-bottom-1">
<b class="review__step__name">No creator found:</b> this is an error, please email <a href="mailto:help@get.gov" class="usa-link">help@get.gov</a>.
</p>
{% endif %}
{% endif %} {% endif %}
</li>
</ol>
</nav>
{% endblock breadcrumb %}
{% block header %} {% with statuses=DomainRequest.DomainRequestStatus last_submitted=DomainRequest.last_submitted_date|date:"F j, Y" first_submitted=DomainRequest.first_submitted_date|date:"F j, Y" last_status_update=DomainRequest.last_status_update|date:"F j, Y" %}
{% if not DomainRequest.requested_domain and DomainRequest.status == DomainRequest.DomainRequestStatus.STARTED %}
<h1>New domain request</h1>
{% else %}
<h1>Domain request for {{ DomainRequest.requested_domain.name }}</h1>
{% endif %}
{% endblock header %}
{% block status_summary %}
<div
class="usa-summary-box dotgov-status-box margin-top-3 padding-left-2"
role="region"
aria-labelledby="summary-box-key-information"
>
<div class="usa-summary-box__body">
<p class="usa-summary-box__heading font-sans-md margin-bottom-0"
id="summary-box-key-information"
>
<span class="text-bold text-primary-darker">
Status:
</span>
{{ DomainRequest.get_status_display|default:"ERROR Please contact technical support/dev" }}
</p>
</div>
</div>
<br>
{% endblock status_summary %}
{% block status_metadata %}
{% if portfolio %}
{% if DomainRequest.creator %}
<p class="margin-top-1 margin-bottom-1">
<b class="review__step__name">Created by:</b> {{DomainRequest.creator.email|default:DomainRequest.creator.get_formatted_name }}
</p>
{% else %}
<p class="margin-top-1 margin-bottom-1">
<b class="review__step__name">No creator found:</b> this is an error, please email <a href="mailto:help@get.gov" class="usa-link">help@get.gov</a>.
</p>
{% endif %}
{% endif %}
{% with statuses=DomainRequest.DomainRequestStatus last_submitted=DomainRequest.last_submitted_date|date:"F j, Y" first_submitted=DomainRequest.first_submitted_date|date:"F j, Y" last_status_update=DomainRequest.last_status_update|date:"F j, Y" %}
{% comment %}
These are intentionally seperated this way.
There is some code repetition, but it gives us more flexibility rather than a dense reduction.
Leave it this way until we've solidified our requirements.
{% endcomment %}
{% if DomainRequest.status == statuses.STARTED %}
{% with first_started_date=DomainRequest.get_first_status_started_date|date:"F j, Y" %}
<p class="margin-top-1">
{% comment %} {% comment %}
A newly created domain request will not have a value for last_status update. These are intentionally seperated this way.
This is because the status never really updated. There is some code repetition, but it gives us more flexibility rather than a dense reduction.
However, if this somehow goes back to started we can default to displaying that new date. Leave it this way until we've solidified our requirements.
{% endcomment %} {% endcomment %}
<b class="review__step__name">Started on:</b> {{last_status_update|default:first_started_date}} {% if DomainRequest.status == statuses.STARTED %}
</p> {% with first_started_date=DomainRequest.get_first_status_started_date|date:"F j, Y" %}
{% endwith %} <p class="margin-top-1">
{% elif DomainRequest.status == statuses.SUBMITTED %} {% comment %}
<p class="margin-top-1 margin-bottom-1"> A newly created domain request will not have a value for last_status update.
<b class="review__step__name">Submitted on:</b> {{last_submitted|default:first_submitted }} This is because the status never really updated.
</p> However, if this somehow goes back to started we can default to displaying that new date.
<p class="margin-top-1"> {% endcomment %}
<b class="review__step__name">Last updated on:</b> {{DomainRequest.updated_at|date:"F j, Y"}} <b class="review__step__name">Started on:</b> {{last_status_update|default:first_started_date}}
</p>
{% elif DomainRequest.status == statuses.ACTION_NEEDED %}
<p class="margin-top-1 margin-bottom-1">
<b class="review__step__name">Submitted on:</b> {{last_submitted|default:first_submitted }}
</p>
<p class="margin-top-1">
<b class="review__step__name">Last updated on:</b> {{DomainRequest.updated_at|date:"F j, Y"}}
</p>
{% elif DomainRequest.status == statuses.REJECTED %}
<p class="margin-top-1 margin-bottom-1">
<b class="review__step__name">Submitted on:</b> {{last_submitted|default:first_submitted }}
</p>
<p class="margin-top-1">
<b class="review__step__name">Rejected on:</b> {{last_status_update}}
</p>
{% elif DomainRequest.status == statuses.WITHDRAWN %}
<p class="margin-top-1 margin-bottom-1">
<b class="review__step__name">Submitted on:</b> {{last_submitted|default:first_submitted }}
</p>
<p class="margin-top-1">
<b class="review__step__name">Withdrawn on:</b> {{last_status_update}}
</p>
{% else %}
{% comment %} Shown for in_review, approved, ineligible {% endcomment %}
<p class="margin-top-1">
<b class="review__step__name">Last updated on:</b> {{DomainRequest.updated_at|date:"F j, Y"}}
</p>
{% endif %}
{% endwith %}
{% endblock status_metadata %}
{% block status_blurb %}
{% if DomainRequest.is_awaiting_review %}
<p>{% include "includes/domain_request_awaiting_review.html" with show_withdraw_text=DomainRequest.is_withdrawable %}</p>
{% endif %}
{% endblock status_blurb %}
{% block modify_request %}
{% if DomainRequest.is_withdrawable %}
<p><a href="{% url 'domain-request-withdraw-confirmation' pk=DomainRequest.id %}" class="usa-button usa-button--outline withdraw_outline">
Withdraw request</a>
</p> </p>
{% endif %}
{% endblock modify_request %}
</div>
<div class="grid-col desktop:grid-offset-2 maxw-tablet">
{% block request_summary_header %}
<h2 class="text-primary-darker"> Summary of your domain request </h2>
{% endblock request_summary_header%}
{% block request_summary %}
{% if portfolio %}
{% include "includes/portfolio_request_review_steps.html" with is_editable=False domain_request=DomainRequest %}
{% else %}
{% with heading_level='h3' %}
{% with org_type=DomainRequest.get_generic_org_type_display %}
{% include "includes/summary_item.html" with title='Type of organization' value=org_type heading_level=heading_level %}
{% endwith %} {% endwith %}
{% elif DomainRequest.status == statuses.SUBMITTED %}
{% if DomainRequest.tribe_name %} <p class="margin-top-1 margin-bottom-1">
{% include "includes/summary_item.html" with title='Tribal government' value=DomainRequest.tribe_name heading_level=heading_level %} <b class="review__step__name">Submitted on:</b> {{last_submitted|default:first_submitted }}
</p>
{% if DomainRequest.federally_recognized_tribe %} <p class="margin-top-1">
<p>Federally-recognized tribe</p> <b class="review__step__name">Last updated on:</b> {{DomainRequest.updated_at|date:"F j, Y"}}
{% endif %} </p>
{% elif DomainRequest.status == statuses.ACTION_NEEDED %}
{% if DomainRequest.state_recognized_tribe %} <p class="margin-top-1 margin-bottom-1">
<p>State-recognized tribe</p> <b class="review__step__name">Submitted on:</b> {{last_submitted|default:first_submitted }}
{% endif %} </p>
<p class="margin-top-1">
{% endif %} <b class="review__step__name">Last updated on:</b> {{DomainRequest.updated_at|date:"F j, Y"}}
</p>
{% if DomainRequest.get_federal_type_display %} {% elif DomainRequest.status == statuses.REJECTED %}
{% include "includes/summary_item.html" with title='Federal government branch' value=DomainRequest.get_federal_type_display heading_level=heading_level %} <p class="margin-top-1 margin-bottom-1">
{% endif %} <b class="review__step__name">Submitted on:</b> {{last_submitted|default:first_submitted }}
</p>
{% if DomainRequest.is_election_board %} <p class="margin-top-1">
{% with value=DomainRequest.is_election_board|yesno:"Yes,No,Incomplete" %} <b class="review__step__name">Rejected on:</b> {{last_status_update}}
{% include "includes/summary_item.html" with title='Election office' value=value heading_level=heading_level %} </p>
{% endwith %} {% elif DomainRequest.status == statuses.WITHDRAWN %}
{% endif %} <p class="margin-top-1 margin-bottom-1">
<b class="review__step__name">Submitted on:</b> {{last_submitted|default:first_submitted }}
{% if DomainRequest.organization_name %} </p>
{% include "includes/summary_item.html" with title='Organization' value=DomainRequest address='true' heading_level=heading_level %} <p class="margin-top-1">
{% endif %} <b class="review__step__name">Withdrawn on:</b> {{last_status_update}}
</p>
{% if DomainRequest.about_your_organization %}
{% include "includes/summary_item.html" with title='About your organization' value=DomainRequest.about_your_organization heading_level=heading_level %}
{% endif %}
{% if DomainRequest.senior_official %}
{% include "includes/summary_item.html" with title='Senior official' value=DomainRequest.senior_official contact='true' heading_level=heading_level %}
{% endif %}
{% if DomainRequest.current_websites.all %}
{% include "includes/summary_item.html" with title='Current websites' value=DomainRequest.current_websites.all list='true' heading_level=heading_level %}
{% endif %}
{% if DomainRequest.requested_domain %}
{% include "includes/summary_item.html" with title='.gov domain' value=DomainRequest.requested_domain heading_level=heading_level %}
{% endif %}
{% if DomainRequest.alternative_domains.all %}
{% include "includes/summary_item.html" with title='Alternative domains' value=DomainRequest.alternative_domains.all list='true' heading_level=heading_level %}
{% endif %}
{% if DomainRequest.purpose %}
{% include "includes/summary_item.html" with title='Purpose of your domain' value=DomainRequest.purpose heading_level=heading_level %}
{% endif %}
{% if DomainRequest.creator %}
{% include "includes/summary_item.html" with title='Your contact information' value=DomainRequest.creator contact='true' heading_level=heading_level %}
{% endif %}
{% if DomainRequest.other_contacts.all %}
{% include "includes/summary_item.html" with title='Other employees from your organization' value=DomainRequest.other_contacts.all contact='true' list='true' heading_level=heading_level %}
{% else %} {% else %}
{% include "includes/summary_item.html" with title='Other employees from your organization' value=DomainRequest.no_other_contacts_rationale heading_level=heading_level %} {% comment %} Shown for in_review, approved, ineligible {% endcomment %}
<p class="margin-top-1">
<b class="review__step__name">Last updated on:</b> {{DomainRequest.updated_at|date:"F j, Y"}}
</p>
{% endif %} {% endif %}
{% endwith %}
{% endblock status_metadata %}
{# We always show this field even if None #} {% block status_blurb %}
{% if DomainRequest %} {% if DomainRequest.is_awaiting_review %}
<h3 class="register-form-review-header">CISA Regional Representative</h3> <p>{% include "includes/domain_request_awaiting_review.html" with show_withdraw_text=DomainRequest.is_withdrawable %}</p>
<ul class="usa-list usa-list--unstyled margin-top-0">
{% if DomainRequest.cisa_representative_first_name %}
{{ DomainRequest.get_formatted_cisa_rep_name }}
{% else %}
No
{% endif %}
</ul>
<h3 class="register-form-review-header">Anything else</h3>
<ul class="usa-list usa-list--unstyled margin-top-0">
{% if DomainRequest.anything_else %}
{{DomainRequest.anything_else}}
{% else %}
No
{% endif %}
</ul>
{% endif %} {% endif %}
{% endwith %} {% endblock status_blurb %}
{% endif %}
{% endblock request_summary%} {% block modify_request %}
{% if DomainRequest.is_withdrawable %}
<p><a href="{% url 'domain-request-withdraw-confirmation' pk=DomainRequest.id %}" class="usa-button usa-button--outline withdraw_outline">
Withdraw request</a>
</p>
{% endif %}
{% endblock modify_request %}
</div>
<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%}
{% block request_summary %}
{% if portfolio %}
{% include "includes/portfolio_request_review_steps.html" with is_editable=False domain_request=DomainRequest %}
{% else %}
{% with heading_level='h3' %}
{% with org_type=DomainRequest.get_generic_org_type_display %}
{% include "includes/summary_item.html" with title='Type of organization' value=org_type heading_level=heading_level %}
{% endwith %}
{% if DomainRequest.tribe_name %}
{% include "includes/summary_item.html" with title='Tribal government' value=DomainRequest.tribe_name heading_level=heading_level %}
{% if DomainRequest.federally_recognized_tribe %}
<p>Federally-recognized tribe</p>
{% endif %}
{% if DomainRequest.state_recognized_tribe %}
<p>State-recognized tribe</p>
{% endif %}
{% endif %}
{% if DomainRequest.get_federal_type_display %}
{% include "includes/summary_item.html" with title='Federal government branch' value=DomainRequest.get_federal_type_display heading_level=heading_level %}
{% endif %}
{% if DomainRequest.is_election_board %}
{% with value=DomainRequest.is_election_board|yesno:"Yes,No,Incomplete" %}
{% include "includes/summary_item.html" with title='Election office' value=value heading_level=heading_level %}
{% endwith %}
{% endif %}
{% if DomainRequest.organization_name %}
{% include "includes/summary_item.html" with title='Organization' value=DomainRequest address='true' heading_level=heading_level %}
{% endif %}
{% if DomainRequest.about_your_organization %}
{% include "includes/summary_item.html" with title='About your organization' value=DomainRequest.about_your_organization heading_level=heading_level %}
{% endif %}
{% if DomainRequest.senior_official %}
{% include "includes/summary_item.html" with title='Senior official' value=DomainRequest.senior_official contact='true' heading_level=heading_level %}
{% endif %}
{% if DomainRequest.current_websites.all %}
{% include "includes/summary_item.html" with title='Current websites' value=DomainRequest.current_websites.all list='true' heading_level=heading_level %}
{% endif %}
{% if DomainRequest.requested_domain %}
{% include "includes/summary_item.html" with title='.gov domain' value=DomainRequest.requested_domain heading_level=heading_level %}
{% endif %}
{% if DomainRequest.alternative_domains.all %}
{% include "includes/summary_item.html" with title='Alternative domains' value=DomainRequest.alternative_domains.all list='true' heading_level=heading_level %}
{% endif %}
{% if DomainRequest.purpose %}
{% include "includes/summary_item.html" with title='Purpose of your domain' value=DomainRequest.purpose heading_level=heading_level %}
{% endif %}
{% if DomainRequest.creator %}
{% include "includes/summary_item.html" with title='Your contact information' value=DomainRequest.creator contact='true' heading_level=heading_level %}
{% endif %}
{% if DomainRequest.other_contacts.all %}
{% include "includes/summary_item.html" with title='Other employees from your organization' value=DomainRequest.other_contacts.all contact='true' list='true' heading_level=heading_level %}
{% else %}
{% include "includes/summary_item.html" with title='Other employees from your organization' value=DomainRequest.no_other_contacts_rationale heading_level=heading_level %}
{% endif %}
{# We always show this field even if None #}
{% if DomainRequest %}
<h3 class="register-form-review-header">CISA Regional Representative</h3>
<ul class="usa-list usa-list--unstyled margin-top-0">
{% if DomainRequest.cisa_representative_first_name %}
{{ DomainRequest.get_formatted_cisa_rep_name }}
{% else %}
No
{% endif %}
</ul>
<h3 class="register-form-review-header">Anything else</h3>
<ul class="usa-list usa-list--unstyled margin-top-0">
{% if DomainRequest.anything_else %}
{{DomainRequest.anything_else}}
{% else %}
No
{% endif %}
</ul>
{% endif %}
{% endwith %}
{% endif %}
{% endblock request_summary%}
</div>
</div> </div>
</main> </main>

View file

@ -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> <em>Required fields are marked with an asterisk (<abbr class="usa-hint usa-hint--required" title="required">*</abbr>).</em>
</p> </p>

View file

@ -26,7 +26,6 @@
</form> </form>
{% elif not form.full_name.value and not form.title.value and not form.email.value %} {% elif not form.full_name.value and not form.title.value and not form.email.value %}
<p> <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>. 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> </p>
{% else %} {% else %}

View file

@ -4,26 +4,29 @@
<div id="wrapper" class="{% block wrapper_class %}wrapper--padding-top-6{% endblock %}"> <div id="wrapper" class="{% block wrapper_class %}wrapper--padding-top-6{% endblock %}">
{% block content %} {% 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 %} {% if user.is_authenticated %}
{# the entire logged in page goes here #} {# 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 %} {% block messages %}
{% include "includes/form_messages.html" %} {% include "includes/form_messages.html" %}
{% endblock %} {% endblock %}
{% block portfolio_content %}{% endblock %} {% block portfolio_content %}{% endblock %}
</div> </div>
{% else %} {# not user.is_authenticated #} {% else %} {# not user.is_authenticated #}
{# the entire logged out page goes here #} {# the entire logged out page goes here #}
<p><a class="usa-button" href="{% url 'login' %}"> <p><a class="usa-button" href="{% url 'login' %}">
Sign in Sign in
</a></p> </a></p>
{% endif %} {% endif %}
</div>
</main> </main>
{% endblock content%} {% endblock content%}

View file

@ -15,6 +15,6 @@
<div id="main-content"> <div id="main-content">
<h1 id="domains-header">Domains</h1> <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> </div>
{% endblock %} {% endblock %}

View file

@ -23,6 +23,8 @@
{% include "includes/form_messages.html" %} {% include "includes/form_messages.html" %}
{% endblock %} {% endblock %}
{% include "includes/form_errors.html" with form=form %}
<h1>Organization</h1> <h1>Organization</h1>
<p>The name of your organization will be publicly listed as the domain registrant.</p> <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>. To suggest an update, email <a href="mailto:help@get.gov" class="usa-link">help@get.gov</a>.
</p> </p>
{% include "includes/form_errors.html" with form=form %}
{% include "includes/required_fields.html" %} {% include "includes/required_fields.html" %}
<form class="usa-form usa-form--large desktop:margin-top-4" method="post" novalidate> <form class="usa-form usa-form--large desktop:margin-top-4" method="post" novalidate>
{% csrf_token %} {% csrf_token %}

View file

@ -11,90 +11,92 @@ Edit your User Profile |
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<main id="main-content" class="grid-container"> <main id="main-content" class="grid-container grid-container--widescreen">
<div class="grid-col desktop:grid-offset-2 desktop:grid-col-8"> <div class="grid-row {% if not is_widescreen_centered %}max-width--grid-container{% endif %}">
{% if messages %} <div class="desktop:grid-col-8">
{% for message in messages %} {% if messages %}
<div class="usa-alert usa-alert--{{ message.tags }} usa-alert--slim margin-bottom-3"> {% for message in messages %}
<div class="usa-alert__body"> <div class="usa-alert usa-alert--{{ message.tags }} usa-alert--slim margin-bottom-3">
{{ message }} <div class="usa-alert__body">
</div> {{ message }}
</div> </div>
{% endfor %} </div>
{% endif %} {% endfor %}
{% include "includes/form_errors.html" with form=form %} {% endif %}
{% include "includes/form_errors.html" with form=form %}
{% if show_back_button %} {% if show_back_button %}
<a href="{% url form.initial.redirect %}" class="breadcrumb__back"> <a href="{% url form.initial.redirect %}" class="breadcrumb__back">
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img"> <svg class="usa-icon" aria-hidden="true" focusable="false" role="img">
<use xlink:href="{% static 'img/sprite.svg' %}#arrow_back"></use> <use xlink:href="{% static 'img/sprite.svg' %}#arrow_back"></use>
</svg> </svg>
<p class="margin-left-05 margin-top-0 margin-bottom-0 line-height-sans-1"> <p class="margin-left-05 margin-top-0 margin-bottom-0 line-height-sans-1">
{{ profile_back_button_text }} {{ profile_back_button_text }}
</p> </p>
</a> </a>
{% endif %} {% endif %}
{% if show_confirmation_modal %} {% if show_confirmation_modal %}
<a <a
href="#toggle-confirmation-modal" href="#toggle-confirmation-modal"
class="usa-button display-none show-confirmation-modal" class="usa-button display-none show-confirmation-modal"
aria-controls="toggle-confirmation-modal" aria-controls="toggle-confirmation-modal"
data-open-modal data-open-modal
>Open confirmation modal</a> >Open confirmation modal</a>
<div <div
class="usa-modal usa-modal--lg is-visible" class="usa-modal usa-modal--lg is-visible"
id="toggle-confirmation-modal" id="toggle-confirmation-modal"
aria-labelledby="Add contact information" aria-labelledby="Add contact information"
aria-describedby="Add contact information" aria-describedby="Add contact information"
data-force-action data-force-action
> >
<div class="usa-modal__content"> <div class="usa-modal__content">
<div class="usa-modal__main"> <div class="usa-modal__main">
<h2 class="usa-modal__heading"> <h2 class="usa-modal__heading">
Add contact information
</h2>
<div class="usa-prose">
<p>
.Gov domain registrants must maintain accurate contact information in the .gov registrar.
Before you can manage your domain, we need you to add your contact information.
</p>
</div>
<div class="usa-modal__footer">
<ul class="usa-button-group">
<li class="usa-button-group__item">
<button
type="button"
class="usa-button padding-105 text-center"
data-close-modal
>
Add contact information Add contact information
</button> </h2>
</li> <div class="usa-prose">
</ul> <p>
.Gov domain registrants must maintain accurate contact information in the .gov registrar.
Before you can manage your domain, we need you to add your contact information.
</p>
</div>
<div class="usa-modal__footer">
<ul class="usa-button-group">
<li class="usa-button-group__item">
<button
type="button"
class="usa-button padding-105 text-center"
data-close-modal
>
Add contact information
</button>
</li>
</ul>
</div>
</div>
<button
type="button"
class="usa-button usa-modal__close"
aria-label="Close this window"
data-close-modal
>
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img">
<use xlink:href="{%static 'img/sprite.svg'%}#close"></use>
</svg>
</button>
</div> </div>
</div> </div>
<button {% endif %}
type="button"
class="usa-button usa-modal__close"
aria-label="Close this window"
data-close-modal {% endblock content %}
>
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img"> {% block content_bottom %}
<use xlink:href="{%static 'img/sprite.svg'%}#close"></use> {% include "includes/profile_form.html" with form=form %}
</svg>
</button>
</div>
</div> </div>
{% endif %} </div>
{% endblock content %}
{% block content_bottom %}
{% include "includes/profile_form.html" with form=form %}
</div>
</main> </main>
{% endblock content_bottom %} {% endblock content_bottom %}

View file

@ -290,3 +290,9 @@ def get_dict_value(dictionary, key):
if isinstance(dictionary, dict): if isinstance(dictionary, dict):
return dictionary.get(key, "") return dictionary.get(key, "")
return "" return ""
@register.filter
def button_class(custom_class):
default_class = "usa-button"
return f"{default_class} {custom_class}" if custom_class else default_class

View file

@ -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.test import TestCase
from django.db.utils import IntegrityError from django.db.utils import IntegrityError
from unittest.mock import MagicMock, patch, call from unittest.mock import MagicMock, patch, call
import datetime from datetime import datetime, date, timedelta
from django.utils.timezone import make_aware from django.utils.timezone import make_aware
from api.tests.common import less_console_noise_decorator from api.tests.common import less_console_noise_decorator
from registrar.models import Domain, Host, HostIP 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""" """assert that the setter for expiration date is not implemented and will raise error"""
with less_console_noise(): with less_console_noise():
with self.assertRaises(NotImplementedError): with self.assertRaises(NotImplementedError):
self.domain.registry_expiration_date = datetime.date.today() self.domain.registry_expiration_date = date.today()
def test_renew_domain(self): def test_renew_domain(self):
"""assert that the renew_domain sets new expiration date in cache and saves to registrar""" """assert that the renew_domain sets new expiration date in cache and saves to registrar"""
with less_console_noise(): with less_console_noise():
self.domain.renew_domain() 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._cache["ex_date"], test_date)
self.assertEquals(self.domain.expiration_date, test_date) self.assertEquals(self.domain.expiration_date, test_date)
@ -2295,18 +2295,42 @@ class TestExpirationDate(MockEppLib):
with less_console_noise(): with less_console_noise():
# to do this, need to mock value returned from timezone.now # to do this, need to mock value returned from timezone.now
# set now to 2023-01-01 # 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 # force fetch_cache which sets the expiration date to 2023-05-25
self.domain.statuses self.domain.statuses
with patch("registrar.models.domain.timezone.now", return_value=mocked_datetime): with patch("registrar.models.domain.timezone.now", return_value=mocked_datetime):
self.assertFalse(self.domain.is_expired()) 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): def test_expiration_date_updated_on_info_domain_call(self):
"""assert that expiration date in db is updated on info domain call""" """assert that expiration date in db is updated on info domain call"""
with less_console_noise(): with less_console_noise():
# force fetch_cache to be called # force fetch_cache to be called
self.domain.statuses self.domain.statuses
test_date = datetime.date(2023, 5, 25) test_date = date(2023, 5, 25)
self.assertEquals(self.domain.expiration_date, test_date) 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) self.domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY)
# creation_date returned from mockDataInfoDomain with creation date: # creation_date returned from mockDataInfoDomain with creation date:
# cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35) # 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): def tearDown(self):
Domain.objects.all().delete() Domain.objects.all().delete()
@ -2331,7 +2355,7 @@ class TestCreationDate(MockEppLib):
def test_creation_date_setter_not_implemented(self): def test_creation_date_setter_not_implemented(self):
"""assert that the setter for creation date is not implemented and will raise error""" """assert that the setter for creation date is not implemented and will raise error"""
with self.assertRaises(NotImplementedError): 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): def test_creation_date_updated_on_info_domain_call(self):
"""assert that creation date in db is updated on info domain call""" """assert that creation date in db is updated on info domain call"""

View file

@ -424,6 +424,112 @@ class TestDomainDetail(TestDomainOverview):
self.assertContains(detail_page, "invited@example.com") 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): class TestDomainManagers(TestDomainOverview):
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
@ -2348,3 +2454,125 @@ class TestDomainChangeNotifications(TestDomainOverview):
# Check that an email was not sent # Check that an email was not sent
self.assertFalse(self.mock_client.send_email.called) 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")

View file

@ -8,24 +8,34 @@ from django_webtest import WebTest # type: ignore
from django.utils.dateparse import parse_date from django.utils.dateparse import parse_date
from api.tests.common import less_console_noise_decorator from api.tests.common import less_console_noise_decorator
from waffle.testutils import override_flag from waffle.testutils import override_flag
from datetime import datetime, timedelta
class GetDomainsJsonTest(TestWithUser, WebTest): class GetDomainsJsonTest(TestWithUser, WebTest):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.app.set_user(self.user.username) 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 # Create test domains
self.domain1 = Domain.objects.create(name="example1.com", expiration_date="2024-01-01", state="unknown") 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.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.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.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 # Create UserDomainRoles
UserDomainRole.objects.create(user=self.user, domain=self.domain1) 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.domain2)
UserDomainRole.objects.create(user=self.user, domain=self.domain3) 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 # Create Portfolio
self.portfolio = Portfolio.objects.create(creator=self.user, organization_name="Example org") 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) self.assertEqual(data["num_pages"], 1)
# Check the number of domains # Check the number of domains
self.assertEqual(len(data["domains"]), 3) self.assertEqual(len(data["domains"]), 5)
# Expected domains # Expected domains
expected_domains = [self.domain1, self.domain2, self.domain3] expected_domains = [self.domain1, self.domain2, self.domain3]
@ -310,7 +320,7 @@ class GetDomainsJsonTest(TestWithUser, WebTest):
self.assertFalse(data["has_previous"]) self.assertFalse(data["has_previous"])
self.assertEqual(data["num_pages"], 1) self.assertEqual(data["num_pages"], 1)
self.assertEqual(data["total"], 1) self.assertEqual(data["total"], 1)
self.assertEqual(data["unfiltered_total"], 3) self.assertEqual(data["unfiltered_total"], 5)
# Check the number of domain requests # Check the number of domain requests
self.assertEqual(len(data["domains"]), 1) self.assertEqual(len(data["domains"]), 1)
@ -377,14 +387,15 @@ class GetDomainsJsonTest(TestWithUser, WebTest):
@less_console_noise_decorator @less_console_noise_decorator
def test_state_filtering(self): def test_state_filtering(self):
"""Test that different states in request get expected responses.""" """Test that different states in request get expected responses."""
expected_values = [ expected_values = [
("unknown", 1), ("unknown", 1),
("ready", 0), ("ready", 0),
("expired", 2), ("expired", 2),
("ready,expired", 2), ("ready,expired", 2),
("unknown,expired", 3), ("unknown,expired", 3),
("expiring", 2),
] ]
for state, num_domains in expected_values: for state, num_domains in expected_values:
with self.subTest(state=state, num_domains=num_domains): with self.subTest(state=state, num_domains=num_domains):
response = self.app.get(reverse("get_domains_json"), {"status": state}) response = self.app.get(reverse("get_domains_json"), {"status": state})

View file

@ -805,15 +805,6 @@ class DomainDNSSECView(DomainFormBaseView):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
has_dnssec_records = self.object.dnssecdata is not None 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["has_dnssec_records"] = has_dnssec_records
context["dnssec_enabled"] = self.request.session.pop("dnssec_enabled", False) context["dnssec_enabled"] = self.request.session.pop("dnssec_enabled", False)
@ -906,15 +897,6 @@ class DomainDsDataView(DomainFormBaseView):
# to preserve the context["form"] # to preserve the context["form"]
context = super().get_context_data(form=formset) context = super().get_context_data(form=formset)
context["trigger_modal"] = True 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) return self.render_to_response(context)
if formset.is_valid() or override: if formset.is_valid() or override:
@ -1050,9 +1032,6 @@ class DomainUsersView(DomainBaseView):
# Add conditionals to the context (such as "can_delete_users") # Add conditionals to the context (such as "can_delete_users")
context = self._add_booleans_to_context(context) 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) # Get portfolio from session (if set)
portfolio = self.request.session.get("portfolio") portfolio = self.request.session.get("portfolio")
@ -1149,26 +1128,6 @@ class DomainUsersView(DomainBaseView):
context["can_delete_users"] = can_delete_users context["can_delete_users"] = can_delete_users
return context 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): class DomainAddUserView(DomainFormBaseView):
"""Inside of a domain's user management, a form for adding users. """Inside of a domain's user management, a form for adding users.

View file

@ -448,34 +448,21 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
non_org_steps_complete = DomainRequest._form_complete(self.domain_request, self.request) 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) 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): 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 = { context = {
"not_form": False,
"form_titles": self.titles, "form_titles": self.titles,
"steps": self.steps, "steps": self.steps,
"visited": self.storage.get("step_history", []), "visited": self.storage.get("step_history", []),
"is_federal": self.domain_request.is_federal(), "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 wont be able to edit it until we review it.\
Youll only be able to withdraw your request.",
"review_form_is_complete": True, "review_form_is_complete": True,
"user": self.request.user, "user": self.request.user,
"requested_domain__name": requested_domain_name, "requested_domain__name": requested_domain_name,
} }
else: # form is not complete else: # form is not complete
modal_button = '<button type="button" class="usa-button" data-close-modal>Return to request</button>'
context = { context = {
"not_form": True,
"form_titles": self.titles, "form_titles": self.titles,
"steps": self.steps, "steps": self.steps,
"visited": self.storage.get("step_history", []), "visited": self.storage.get("step_history", []),
"is_federal": self.domain_request.is_federal(), "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, "review_form_is_complete": False,
"user": self.request.user, "user": self.request.user,
"requested_domain__name": requested_domain_name, "requested_domain__name": requested_domain_name,

View file

@ -109,6 +109,10 @@ def apply_sorting(queryset, request):
sort_by = request.GET.get("sort_by", "id") # Default to 'id' sort_by = request.GET.get("sort_by", "id") # Default to 'id'
order = request.GET.get("order", "asc") # Default to 'asc' 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": if order == "desc":
sort_by = f"-{sort_by}" sort_by = f"-{sort_by}"
return queryset.order_by(sort_by) return queryset.order_by(sort_by)

View file

@ -27,7 +27,7 @@ def get_domains_json(request):
page_number = request.GET.get("page") page_number = request.GET.get("page")
page_obj = paginator.get_page(page_number) 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( return JsonResponse(
{ {
@ -80,21 +80,27 @@ def apply_state_filter(queryset, request):
status_list.append("dns needed") status_list.append("dns needed")
# Split the status list into normal states and custom states # Split the status list into normal states and custom states
normal_states = [state for state in status_list if state in Domain.State.values] 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 # Construct Q objects for normal states that can be queried through ORM
state_query = Q() state_query = Q()
if normal_states: if normal_states:
state_query |= Q(state__in=normal_states) state_query |= Q(state__in=normal_states)
# Handle custom states in Python, as expired can not be queried through ORM # Handle custom states in Python, as expired can not be queried through ORM
if "expired" in custom_states: 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) 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 # Apply the combined query
queryset = queryset.filter(state_query) queryset = queryset.filter(state_query)
# If there are filtered states, and expired is not one of them, domains with # If there are filtered states, and expired is not one of them, domains with
# state_display of 'Expired' must be removed # state_display of 'Expired' must be removed
if "expired" not in custom_states: 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) queryset = queryset.exclude(id__in=expired_domain_ids)
return queryset return queryset
@ -105,7 +111,7 @@ def apply_sorting(queryset, request):
order = request.GET.get("order", "asc") order = request.GET.get("order", "asc")
if sort_by == "state_display": if sort_by == "state_display":
objects = list(queryset) 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 return objects
else: else:
if order == "desc": if order == "desc":
@ -113,7 +119,8 @@ def apply_sorting(queryset, request):
return queryset.order_by(sort_by) return queryset.order_by(sort_by)
def serialize_domain(domain, user): def serialize_domain(domain, request):
user = request.user
suborganization_name = None suborganization_name = None
try: try:
domain_info = domain.domain_info domain_info = domain.domain_info
@ -133,7 +140,7 @@ def serialize_domain(domain, user):
"name": domain.name, "name": domain.name,
"expiration_date": domain.expiration_date, "expiration_date": domain.expiration_date,
"state": domain.state, "state": domain.state,
"state_display": domain.state_display(), "state_display": domain.state_display(request),
"get_state_help_text": domain.get_state_help_text(), "get_state_help_text": domain.get_state_help_text(),
"action_url": reverse("domain", kwargs={"pk": domain.id}), "action_url": reverse("domain", kwargs={"pk": domain.id}),
"action_label": ("View" if view_only else "Manage"), "action_label": ("View" if view_only else "Manage"),

View file

@ -8,5 +8,6 @@ def index(request):
if request and request.user and request.user.is_authenticated: if request and request.user and request.user.is_authenticated:
# This controls the creation of a new domain request in the wizard # 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["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) return render(request, "home.html", context)

View file

@ -39,6 +39,8 @@ class PortfolioDomainsView(PortfolioDomainsPermissionView, View):
context = {} context = {}
if self.request and self.request.user and self.request.user.is_authenticated: 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["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) return render(request, "portfolio_domains.html", context)