Finish work on view, template and unit tests

This commit is contained in:
Rachid Mrad 2024-06-03 17:39:12 -04:00
commit e5aeefe451
No known key found for this signature in database
23 changed files with 8387 additions and 403 deletions

View file

@ -291,13 +291,13 @@ We use the [CSS Block Element Modifier (BEM)](https://getbem.com/naming/) naming
### Upgrading USWDS and other JavaScript packages
Version numbers can be manually controlled in `package.json`. Edit that, if desired.
Now run `docker-compose run node npm update`.
Then run `docker-compose up` to recompile and recopy the assets.
Examine the results in the running application (remember to empty your cache!) and commit `package.json` and `package-lock.json` if all is well.
1. Version numbers can be manually controlled in `package.json`. Edit that, if desired.
2. Now run `docker-compose run node npm update`.
3. Then run `docker-compose up` to recompile and recopy the assets, or run `docker-compose updateUswds` if your docker is already up.
4. Make note of the dotgov changes in uswds-edited.js.
5. Copy over the newly compiled code from uswds.js into uswds-edited.js.
6. Put back the dotgov changes you made note of into uswds-edited.js.
7. Examine the results in the running application (remember to empty your cache!) and commit `package.json` and `package-lock.json` if all is well.
## Finite State Machines

View file

@ -15,6 +15,7 @@ from django.contrib.contenttypes.models import ContentType
from django.urls import reverse
from dateutil.relativedelta import relativedelta # type: ignore
from epplibwrapper.errors import ErrorCode, RegistryError
from registrar.models.user_domain_role import UserDomainRole
from waffle.admin import FlagAdmin
from waffle.models import Sample, Switch
from registrar.models import Contact, Domain, DomainRequest, DraftDomain, User, Website
@ -753,6 +754,23 @@ class MyUserAdmin(BaseUserAdmin, ImportExportModelAdmin):
# users who might not belong to groups
return self.analyst_readonly_fields
def change_view(self, request, object_id, form_url="", extra_context=None):
"""Add user's related domains and requests to context"""
obj = self.get_object(request, object_id)
domain_requests = DomainRequest.objects.filter(creator=obj).exclude(
Q(status=DomainRequest.DomainRequestStatus.STARTED) | Q(status=DomainRequest.DomainRequestStatus.WITHDRAWN)
)
sort_by = request.GET.get("sort_by", "requested_domain__name")
domain_requests = domain_requests.order_by(sort_by)
user_domain_roles = UserDomainRole.objects.filter(user=obj)
domain_ids = user_domain_roles.values_list("domain_id", flat=True)
domains = Domain.objects.filter(id__in=domain_ids).exclude(state=Domain.State.DELETED)
extra_context = {"domain_requests": domain_requests, "domains": domains}
return super().change_view(request, object_id, form_url, extra_context)
class HostIPInline(admin.StackedInline):
"""Edit an ip address on the host page."""

View file

@ -834,3 +834,417 @@ function hideDeletedForms() {
(function cisaRepresentativesFormListener() {
HookupYesNoListener("additional_details-has_cisa_representative",'cisa-representative', null)
})();
/**
* Initialize USWDS tooltips by calling initialization method. Requires that uswds-edited.js
* be loaded before get-gov.js. uswds-edited.js adds the tooltip module to the window to be
* accessible directly in get-gov.js
*
*/
function initializeTooltips() {
function checkTooltip() {
// Check that the tooltip library is loaded, and if not, wait and retry
if (window.tooltip && typeof window.tooltip.init === 'function') {
window.tooltip.init();
} else {
// Retry after a short delay
setTimeout(checkTooltip, 100);
}
}
checkTooltip();
}
/**
* Initialize USWDS modals by calling on method. Requires that uswds-edited.js be loaded
* before get-gov.js. uswds-edited.js adds the modal module to the window to be accessible
* directly in get-gov.js.
* initializeModals adds modal-related DOM elements, based on other DOM elements existing in
* the page. It needs to be called only once for any particular DOM element; otherwise, it
* will initialize improperly. Therefore, if DOM elements change dynamically and include
* DOM elements with modal classes, unloadModals needs to be called before initializeModals.
*
*/
function initializeModals() {
window.modal.on();
}
/**
* Unload existing USWDS modals by calling off method. Requires that uswds-edited.js be
* loaded before get-gov.js. uswds-edited.js adds the modal module to the window to be
* accessible directly in get-gov.js.
* See note above with regards to calling this method relative to initializeModals.
*
*/
function unloadModals() {
window.modal.off();
}
/**
* An IIFE that listens for DOM Content to be loaded, then executes. This function
* initializes the domains list and associated functionality on the home page of the app.
*
*/
document.addEventListener('DOMContentLoaded', function() {
let domainsWrapper = document.querySelector('.domains-wrapper');
if (domainsWrapper) {
let currentSortBy = 'id';
let currentOrder = 'asc';
let noDomainsWrapper = document.querySelector('.no-domains-wrapper');
/**
* Loads rows in the domains list, as well as updates pagination around the domains list
* based on the supplied attributes.
* @param {*} page - the page number of the results (starts with 1)
* @param {*} sortBy - the sort column option
* @param {*} order - the sort order {asc, desc}
*/
function loadDomains(page, sortBy = currentSortBy, order = currentOrder) {
//fetch json of page of domains, given page # and sort
fetch(`/get-domains-json/?page=${page}&sort_by=${sortBy}&order=${order}`)
.then(response => response.json())
.then(data => {
if (data.error) {
console.log('Error in AJAX call: ' + data.error);
return;
}
// handle the display of proper messaging in the event that no domains exist in the list
if (data.domains.length) {
domainsWrapper.classList.remove('display-none');
noDomainsWrapper.classList.add('display-none');
} else {
domainsWrapper.classList.add('display-none');
noDomainsWrapper.classList.remove('display-none');
}
// identify the DOM element where the domain list will be inserted into the DOM
const domainList = document.querySelector('.dotgov-table__registered-domains tbody');
domainList.innerHTML = '';
data.domains.forEach(domain => {
const expirationDate = domain.expiration_date ? new Date(domain.expiration_date) : null;
const expirationDateSortValue = expirationDate ? expirationDate.getTime() : '';
const actionUrl = domain.action_url;
const row = document.createElement('tr');
row.innerHTML = `
<th scope="row" role="rowheader" data-label="Domain name">
${domain.name}
</th>
<td data-sort-value="${expirationDateSortValue}" data-label="Expires">
${expirationDate ? expirationDate.toLocaleDateString() : ''}
</td>
<td data-label="Status">
${domain.state_display}
<svg
class="usa-icon usa-tooltip usa-tooltip--registrar text-middle margin-bottom-05 text-accent-cool no-click-outline-and-cursor-help"
data-position="top"
title="${domain.get_state_help_text}"
focusable="true"
aria-label="Status Information"
role="tooltip"
>
<use aria-hidden="true" xlink:href="/public/img/sprite.svg#info_outline"></use>
</svg>
</td>
<td>
<a href="${actionUrl}">
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24">
<use xlink:href="/public/img/sprite.svg#${domain.svg_icon}"></use>
</svg>
${domain.action_label} <span class="usa-sr-only">${domain.name}</span>
</a>
</td>
`;
domainList.appendChild(row);
});
// initialize tool tips immediately after the associated DOM elements are added
initializeTooltips();
hasLoaded = true;
// update pagination
updateDomainsPagination(data.page, data.num_pages, data.has_previous, data.has_next, data.total);
currentSortBy = sortBy;
currentOrder = order;
})
.catch(error => console.error('Error fetching domains:', error));
}
/**
* Update the pagination below the domains list.
* @param {*} currentPage - the current page number (starting with 1)
* @param {*} numPages - the number of pages indicated by the domains list response
* @param {*} hasPrevious - if there is a page of results prior to the current page
* @param {*} hasNext - if there is a page of results after the current page
*/
function updateDomainsPagination(currentPage, numPages, hasPrevious, hasNext, totalItems) {
// identify the DOM element where the pagination will be inserted
const paginationContainer = document.querySelector('#domains-pagination');
const paginationCounter = document.querySelector('#domains-pagination .usa-pagination__counter');
const paginationButtons = document.querySelector('#domains-pagination .usa-pagination__list');
paginationCounter.innerHTML = '';
paginationButtons.innerHTML = '';
// Buttons should only be displayed if there are more than one pages of results
paginationButtons.classList.toggle('display-none', numPages <= 1);
// Counter should only be displayed if there is more than 1 item
paginationContainer.classList.toggle('display-none', totalItems < 1);
paginationCounter.innerHTML = `${totalItems} domain${totalItems > 1 ? 's' : ''}`;
if (hasPrevious) {
const prevPageItem = document.createElement('li');
prevPageItem.className = 'usa-pagination__item usa-pagination__arrow';
prevPageItem.innerHTML = `
<a href="#domains-header" class="usa-pagination__link usa-pagination__previous-page" aria-label="Domains previous page">
<svg class="usa-icon" aria-hidden="true" role="img">
<use xlink:href="/public/img/sprite.svg#navigate_before"></use>
</svg>
<span class="usa-pagination__link-text">Previous</span>
</a>
`;
prevPageItem.querySelector('a').addEventListener('click', () => loadDomains(currentPage - 1));
paginationButtons.appendChild(prevPageItem);
}
for (let i = 1; i <= numPages; i++) {
const pageItem = document.createElement('li');
pageItem.className = 'usa-pagination__item usa-pagination__page-no';
pageItem.innerHTML = `
<a href="#domains-header" class="usa-pagination__button" aria-label="Domains page ${i}">${i}</a>
`;
if (i === currentPage) {
pageItem.querySelector('a').classList.add('usa-current');
pageItem.querySelector('a').setAttribute('aria-current', 'page');
}
pageItem.querySelector('a').addEventListener('click', () => loadDomains(i));
paginationButtons.appendChild(pageItem);
}
if (hasNext) {
const nextPageItem = document.createElement('li');
nextPageItem.className = 'usa-pagination__item usa-pagination__arrow';
nextPageItem.innerHTML = `
<a href="#domains-header" class="usa-pagination__link usa-pagination__next-page" aria-label="Domains next page">
<span class="usa-pagination__link-text">Next</span>
<svg class="usa-icon" aria-hidden="true" role="img">
<use xlink:href="/public/img/sprite.svg#navigate_next"></use>
</svg>
</a>
`;
nextPageItem.querySelector('a').addEventListener('click', () => loadDomains(currentPage + 1));
paginationButtons.appendChild(nextPageItem);
}
}
// Add event listeners to table headers for sorting
document.querySelectorAll('.dotgov-table__registered-domains th[data-sortable]').forEach(header => {
header.addEventListener('click', function() {
const sortBy = this.getAttribute('data-sortable');
let order = 'asc';
// sort order will be ascending, unless the currently sorted column is ascending, and the user
// is selecting the same column to sort in descending order
if (sortBy === currentSortBy) {
order = currentOrder === 'asc' ? 'desc' : 'asc';
}
// load the results with the updated sort
loadDomains(1, sortBy, order);
});
});
// Load the first page initially
loadDomains(1);
}
});
/**
* An IIFE that listens for DOM Content to be loaded, then executes. This function
* initializes the domain requests list and associated functionality on the home page of the app.
*
*/
document.addEventListener('DOMContentLoaded', function() {
let domainRequestsWrapper = document.querySelector('.domain-requests-wrapper');
if (domainRequestsWrapper) {
let currentSortBy = 'id';
let currentOrder = 'asc';
let noDomainRequestsWrapper = document.querySelector('.no-domain-requests-wrapper');
/**
* Loads rows in the domain requests list, as well as updates pagination around the domain requests list
* based on the supplied attributes.
* @param {*} page - the page number of the results (starts with 1)
* @param {*} sortBy - the sort column option
* @param {*} order - the sort order {asc, desc}
*/
function loadDomainRequests(page, sortBy = currentSortBy, order = currentOrder) {
//fetch json of page of domain requests, given page # and sort
fetch(`/get-domain-requests-json/?page=${page}&sort_by=${sortBy}&order=${order}`)
.then(response => response.json())
.then(data => {
if (data.error) {
console.log('Error in AJAX call: ' + data.error);
return;
}
// handle the display of proper messaging in the event that no domain requests exist in the list
if (data.domain_requests.length) {
domainRequestsWrapper.classList.remove('display-none');
noDomainRequestsWrapper.classList.add('display-none');
} else {
domainRequestsWrapper.classList.add('display-none');
noDomainRequestsWrapper.classList.remove('display-none');
}
// identify the DOM element where the domain request list will be inserted into the DOM
const tbody = document.querySelector('.dotgov-table__domain-requests tbody');
tbody.innerHTML = '';
// remove any existing modal elements from the DOM so they can be properly re-initialized
// after the DOM content changes and there are new delete modal buttons added
unloadModals();
data.domain_requests.forEach(request => {
const domainName = request.requested_domain ? request.requested_domain : `New domain request <span class="text-base font-body-xs">(${new Date(request.created_at).toLocaleString()} UTC)</span>`;
const actionUrl = request.action_url;
const actionLabel = request.action_label;
const options = { year: 'numeric', month: 'short', day: 'numeric' };
const submissionDate = request.submission_date ? new Date(request.submission_date).toLocaleDateString('en-US', options) : `<span class="text-base">Not submitted</span>`;
const deleteButton = request.is_deletable ? `
<a
role="button"
id="button-toggle-delete-domain-alert-${request.id}"
href="#toggle-delete-domain-alert-${request.id}"
class="usa-button--unstyled text-no-underline late-loading-modal-trigger"
aria-controls="toggle-delete-domain-alert-${request.id}"
data-open-modal
>
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24">
<use xlink:href="/public/img/sprite.svg#delete"></use>
</svg> Delete <span class="usa-sr-only">${domainName}</span>
</a>` : '';
const row = document.createElement('tr');
row.innerHTML = `
<th scope="row" role="rowheader" data-label="Domain name">
${domainName}
</th>
<td data-sort-value="${new Date(request.submission_date).getTime()}" data-label="Date submitted">
${submissionDate}
</td>
<td data-label="Status">
${request.status}
</td>
<td>
<a href="${actionUrl}">
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24">
<use xlink:href="/public/img/sprite.svg#${request.svg_icon}"></use>
</svg>
${actionLabel} <span class="usa-sr-only">${request.requested_domain ? request.requested_domain : 'New domain request'}</span>
</a>
</td>
<td>${deleteButton}</td>
`;
tbody.appendChild(row);
});
// initialize modals immediately after the DOM content is updated
initializeModals();
hasLoaded = true;
// update the pagination after the domain requests list is updated
updateDomainRequestsPagination(data.page, data.num_pages, data.has_previous, data.has_next, data.total);
currentSortBy = sortBy;
currentOrder = order;
})
.catch(error => console.error('Error fetching domain requests:', error));
}
/**
* Update the pagination below the domain requests list.
* @param {*} currentPage - the current page number (starting with 1)
* @param {*} numPages - the number of pages indicated by the domain request list response
* @param {*} hasPrevious - if there is a page of results prior to the current page
* @param {*} hasNext - if there is a page of results after the current page
*/
function updateDomainRequestsPagination(currentPage, numPages, hasPrevious, hasNext, totalItems) {
// identify the DOM element where pagination is contained
const paginationContainer = document.querySelector('#domain-requests-pagination');
const paginationCounter = document.querySelector('#domain-requests-pagination .usa-pagination__counter');
const paginationButtons = document.querySelector('#domain-requests-pagination .usa-pagination__list');
paginationCounter.innerHTML = '';
paginationButtons.innerHTML = '';
// Buttons should only be displayed if there are more than one pages of results
paginationButtons.classList.toggle('display-none', numPages <= 1);
// Counter should only be displayed if there is more than 1 item
paginationContainer.classList.toggle('display-none', totalItems < 1);
paginationCounter.innerHTML = `${totalItems} domain request${totalItems > 1 ? 's' : ''}`;
if (hasPrevious) {
const prevPageItem = document.createElement('li');
prevPageItem.className = 'usa-pagination__item usa-pagination__arrow';
prevPageItem.innerHTML = `
<a href="#domain-requests-header" class="usa-pagination__link usa-pagination__previous-page" aria-label="Domain requests previous page">
<svg class="usa-icon" aria-hidden="true" role="img">
<use xlink:href="/public/img/sprite.svg#navigate_before"></use>
</svg>
<span class="usa-pagination__link-text">Previous</span>
</a>
`;
prevPageItem.querySelector('a').addEventListener('click', () => loadDomainRequests(currentPage - 1));
paginationButtons.appendChild(prevPageItem);
}
for (let i = 1; i <= numPages; i++) {
const pageItem = document.createElement('li');
pageItem.className = 'usa-pagination__item usa-pagination__page-no';
pageItem.innerHTML = `
<a href="#domain-requests-header" class="usa-pagination__button" aria-label="Domain requests page ${i}">${i}</a>
`;
if (i === currentPage) {
pageItem.querySelector('a').classList.add('usa-current');
pageItem.querySelector('a').setAttribute('aria-current', 'page');
}
pageItem.querySelector('a').addEventListener('click', () => loadDomainRequests(i));
paginationButtons.appendChild(pageItem);
}
if (hasNext) {
const nextPageItem = document.createElement('li');
nextPageItem.className = 'usa-pagination__item usa-pagination__arrow';
nextPageItem.innerHTML = `
<a href="#domain-requests-header" class="usa-pagination__link usa-pagination__next-page" aria-label="Domain requests next page">
<span class="usa-pagination__link-text">Next</span>
<svg class="usa-icon" aria-hidden="true" role="img">
<use xlink:href="/public/img/sprite.svg#navigate_next"></use>
</svg>
</a>
`;
nextPageItem.querySelector('a').addEventListener('click', () => loadDomainRequests(currentPage + 1));
paginationButtons.appendChild(nextPageItem);
}
}
// Add event listeners to table headers for sorting
document.querySelectorAll('.dotgov-table__domain-requests th[data-sortable]').forEach(header => {
header.addEventListener('click', function() {
const sortBy = this.getAttribute('data-sortable');
let order = 'asc';
// sort order will be ascending, unless the currently sorted column is ascending, and the user
// is selecting the same column to sort in descending order
if (sortBy === currentSortBy) {
order = currentOrder === 'asc' ? 'desc' : 'asc';
}
loadDomainRequests(1, sortBy, order);
});
});
// Load the first page initially
loadDomainRequests(1);
}
});

File diff suppressed because one or more lines are too long

View file

@ -126,6 +126,12 @@ html[data-theme="light"] {
.usa-table td {
background-color: transparent;
}
// Sets darker color on delete page links.
// Remove when dark mode successfully applies to Django delete page.
.delete-confirmation .content a:not(.button) {
color: color('primary');
}
}
// Firefox needs this to be specifically set
@ -149,6 +155,12 @@ html[data-theme="dark"] {
.usa-table td {
background-color: transparent;
}
// Sets darker color on delete page links.
// Remove when dark mode successfully applies to Django delete page.
.delete-confirmation .content a:not(.button) {
color: color('primary');
}
}
#branding h1 a:link, #branding h1 a:visited {
@ -173,6 +185,14 @@ div#content > h2 {
margin: units(2) 0 units(1) 0;
}
.module ul.padding-0 {
padding: 0 !important;
}
.module ul.margin-0 {
margin: 0 !important;
}
.change-list {
.usa-table--striped tbody tr:nth-child(odd) td,
.usa-table--striped tbody tr:nth-child(odd) th,
@ -707,7 +727,7 @@ div.dja__model-description{
a, a:link, a:visited {
font-size: medium;
color: #005288 !important;
color: color('primary') !important;
}
&.dja__model-description--no-overflow {
@ -724,3 +744,15 @@ div.dja__model-description{
.text-underline {
text-decoration: underline !important;
}
//-- Override some styling for the USWDS summary box (per design quidance for ticket #2055
.usa-summary-box {
background: #{$dhs-blue-10};
border-color: #{$dhs-blue-30};
max-width: 72ex;
word-wrap: break-word;
}
.usa-summary-box h3 {
color: #{$dhs-blue-60};
}

View file

@ -21,6 +21,8 @@ from registrar.views.admin_views import (
)
from registrar.views.domain_request import Step
from registrar.views.domain_requests_json import get_domain_requests_json
from registrar.views.domains_json import get_domains_json
from registrar.views.utility import always_404
from api.views import available, get_current_federal, get_current_full
@ -198,6 +200,8 @@ urlpatterns = [
views.DomainDeleteUserView.as_view(http_method_names=["post"]),
name="domain-user-delete",
),
path("get-domains-json/", get_domains_json, name="get_domains_json"),
path("get-domain-requests-json/", get_domain_requests_json, name="get_domain_requests_json"),
]
# Djangooidc strips out context data from that context, so we define a custom error

View file

@ -1062,6 +1062,15 @@ class Domain(TimeStampedModel, DomainHelper):
now = timezone.now().date()
return self.expiration_date < now
def state_display(self):
"""Return the display status of the domain."""
if self.is_expired() and self.state != self.State.UNKNOWN:
return "Expired"
elif self.state == self.State.UNKNOWN or self.state == self.State.DNS_NEEDED:
return "DNS needed"
else:
return self.state.capitalize()
def map_epp_contact_to_public_contact(self, contact: eppInfo.InfoContactResultData, contact_id, contact_type):
"""Maps the Epp contact representation to a PublicContact object.

View file

@ -45,6 +45,8 @@
{% block css %}
<link rel="stylesheet" href="{% static 'css/styles.css' %}">
<script src="{% static 'js/uswds-init.min.js' %}" defer></script>
<!-- We override with our own copy to make some classes accessible in our JS -->
<script src="{% static 'js/uswds-edited.js' %}" defer></script>
<script src="{% static 'js/get-gov.js' %}" defer></script>
{% endblock %}
@ -67,7 +69,6 @@
</head>
<body id="{% block body_id %}default{% endblock %}" class="{% block body_class %}section_front{% endblock %}">
<script src="{% static 'js/uswds.min.js' %}" defer></script>
<a class="usa-skipnav" href="#main-content">Skip to main content</a>
{% if not IS_PRODUCTION %}

View file

@ -2,28 +2,35 @@
{% load i18n static %}
{% block after_related_objects %}
<div class="module aligned padding-3">
<h2>Associated requests and domains</h2>
<div class="grid-row grid-gap">
<div class="grid-row grid-gap mobile:padding-x-1 desktop:padding-x-4">
<div class="mobile:grid-col-12 tablet:grid-col-6 desktop:grid-col-4">
<h3>Domain requests</h3>
<p>asfasdsd</p>
<ul class="margin-0 padding-0">
{% for domain_request in domain_requests %}
<li>
<a href="{% url 'admin:registrar_domainrequest_change' domain_request.pk %}">
{{ domain_request.requested_domain }}
</a>
({{ domain_request.status }})
</li>
{% endfor %}
</ul>
</div>
<div class="mobile:grid-col-12 tablet:grid-col-6 desktop:grid-col-4">
<h3>Domains</h3>
<p>asfasdsd</p>
<ul class="margin-0 padding-0">
{% for domain in domains %}
<li>
<a href="{% url 'admin:registrar_domain_change' domain.pk %}">
{{ domain.name }}
</a>
({{ domain.state }})
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
{% endblock %}

View file

@ -14,6 +14,11 @@
<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>,
completing your domain request might take around 15 minutes.</p>
{% if has_profile_feature_flag %}
<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="" target="_blank" class="usa-link">your profile</a> to make updates.</p>
{% include "includes/profile_information.html" with user=user%}
{% endif %}
{% block form_buttons %}

View file

@ -24,15 +24,15 @@
</p>
<section class="section--outlined">
<h2>Domains</h2>
{% if domains %}
<h2 id="domains-header">Domains</h2>
<div class="domains-wrapper display-none">
<table class="usa-table usa-table--borderless usa-table--stacked dotgov-table dotgov-table--stacked dotgov-table__registered-domains">
<caption class="sr-only">Your registered domains</caption>
<thead>
<tr>
<th data-sortable scope="col" role="columnheader">Domain name</th>
<th data-sortable scope="col" role="columnheader">Expires</th>
<th data-sortable scope="col" role="columnheader">Status</th>
<th data-sortable="name" scope="col" role="columnheader">Domain name</th>
<th data-sortable="expiration_date" scope="col" role="columnheader">Expires</th>
<th data-sortable="state_display" scope="col" role="columnheader">Status</th>
<th
scope="col"
role="columnheader"
@ -42,61 +42,15 @@
</tr>
</thead>
<tbody>
{% for domain in domains %}
<tr>
<th th scope="row" role="rowheader" data-label="Domain name">
{{ domain.name }}
</th>
<td data-sort-value="{{ domain.expiration_date|date:"U" }}" data-label="Expires">{{ domain.expiration_date|date }}</td>
<td data-label="Status">
{# UNKNOWN domains would not have an expiration date and thus would show 'Expired' #}
{% if domain.is_expired and domain.state != domain.State.UNKNOWN %}
Expired
{% elif domain.state == domain.State.UNKNOWN or domain.state == domain.State.DNS_NEEDED %}
DNS needed
{% else %}
{{ domain.state|capfirst }}
{% endif %}
<svg
class="usa-icon usa-tooltip usa-tooltip--registrar text-middle margin-bottom-05 text-accent-cool no-click-outline-and-cursor-help"
data-position="top"
title="{{domain.get_state_help_text}}"
focusable="true"
aria-label="Status Information"
role="tooltip"
>
<use aria-hidden="true" xlink:href="{%static 'img/sprite.svg'%}#info_outline"></use>
</svg>
</td>
<td>
<a href="{% url "domain" pk=domain.pk %}">
<svg
class="usa-icon"
aria-hidden="true"
focusable="false"
role="img"
width="24"
>
{% if domain.state == "deleted" or domain.state == "on hold" %}
<use xlink:href="{%static 'img/sprite.svg'%}#visibility"></use>
</svg>
View <span class="usa-sr-only">{{ domain.name }}</span>
{% else %}
<use xlink:href="{%static 'img/sprite.svg'%}#settings"></use>
</svg>
Manage <span class="usa-sr-only">{{ domain.name }}</span>
{% endif %}
</a>
</td>
</tr>
{% endfor %}
<!-- AJAX will populate this tbody -->
</tbody>
</table>
<div
class="usa-sr-only usa-table__announcement-region"
aria-live="polite"
></div>
{% else %}
</div>
<div class="no-domains-wrapper display-none">
<p>You don't have any registered domains.</p>
<p class="maxw-none clearfix">
<a href="https://get.gov/help/faq/#do-not-see-my-domain" class="float-right-tablet display-flex flex-align-start usa-link" target="_blank">
@ -106,102 +60,46 @@
Why don't I see my domain when I sign in to the registrar?
</a>
</p>
{% endif %}
</div>
</section>
<nav aria-label="Pagination" class="usa-pagination flex-justify" id="domains-pagination">
<span class="usa-pagination__counter text-base-dark padding-left-2">
<!-- Count will be dynamically populated by JS -->
</span>
<ul class="usa-pagination__list">
<!-- Pagination links will be dynamically populated by JS -->
</ul>
</nav>
<section class="section--outlined">
<h2>Domain requests</h2>
{% if domain_requests %}
<h2 id="domain-requests-header">Domain requests</h2>
<div class="domain-requests-wrapper display-none">
<table class="usa-table usa-table--borderless usa-table--stacked dotgov-table dotgov-table--stacked dotgov-table__domain-requests">
<caption class="sr-only">Your domain requests</caption>
<thead>
<tr>
<th data-sortable scope="col" role="columnheader">Domain name</th>
<th data-sortable scope="col" role="columnheader">Date submitted</th>
<th data-sortable scope="col" role="columnheader">Status</th>
<th data-sortable="requested_domain__name" scope="col" role="columnheader">Domain name</th>
<th data-sortable="submission_date" scope="col" role="columnheader">Date submitted</th>
<th data-sortable="status" scope="col" role="columnheader">Status</th>
<th scope="col" role="columnheader"><span class="usa-sr-only">Action</span></th>
{% if has_deletable_domain_requests %}
<th scope="col" role="columnheader"><span class="usa-sr-only">Delete Action</span></th>
{% endif %}
</tr>
</thead>
<tbody>
{% for domain_request in domain_requests %}
<tr>
<th th scope="row" role="rowheader" data-label="Domain name">
{% if domain_request.requested_domain is None %}
New domain request
{# Add a breakpoint #}
<div aria-hidden="true"></div>
<span class="text-base font-body-xs">({{ domain_request.created_at }} UTC)</span>
{% else %}
{{ domain_request.requested_domain.name }}
{% endif %}
</th>
<td data-sort-value="{{ domain_request.submission_date|date:"U" }}" data-label="Date submitted">
{% if domain_request.submission_date %}
{{ domain_request.submission_date|date }}
{% else %}
<span class="text-base">Not submitted</span>
{% endif %}
</td>
<td data-label="Status">{{ domain_request.get_status_display }}</td>
<td>
{% with prefix="New domain request ("%}
{% with date=domain_request.created_at|date:"DATETIME_FORMAT"%}
{% with name_default=prefix|add:date|add:" UTC)"%}
{% if domain_request.status == domain_request.DomainRequestStatus.STARTED or domain_request.status == domain_request.DomainRequestStatus.ACTION_NEEDED or domain_request.status == domain_request.DomainRequestStatus.WITHDRAWN %}
<a href="{% url 'edit-domain-request' domain_request.pk %}">
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24">
<use xlink:href="{%static 'img/sprite.svg'%}#edit"></use>
</svg>
{% if domain_request.requested_domain is not None%}
Edit <span class="usa-sr-only">{{ domain_request.requested_domain.name }}</span>
{% else %}
Edit <span class="usa-sr-only">{{ name_default }}</span>
{% endif %}
{% else %}
<a href="{% url 'domain-request-status' domain_request.pk %}">
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24">
<use xlink:href="{%static 'img/sprite.svg'%}#settings"></use>
</svg>
Manage <span class="usa-sr-only">{{ domain_request.requested_domain.name|default:name_default }}</span>
{% endif %}
{% endwith %}
{% endwith %}
{% endwith %}
</a>
</td>
{% if has_deletable_domain_requests %}
<td>
{% if domain_request.status == "started" or domain_request.status == "withdrawn" %}
<a
role="button"
id="button-toggle-delete-domain-alert-{{ forloop.counter }}"
href="#toggle-delete-domain-alert-{{ forloop.counter }}"
class="usa-button--unstyled text-no-underline"
aria-controls="toggle-delete-domain-alert-{{ forloop.counter }}"
data-open-modal
>
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24">
<use xlink:href="{%static 'img/sprite.svg'%}#delete"></use>
</svg>
{% with prefix="New domain request ("%}
{% with date=domain_request.created_at|date:"DATETIME_FORMAT"%}
{% with name_default=prefix|add:date|add:" UTC)"%}
{% if domain_request.requested_domain is not None %}
Delete <span class="usa-sr-only">{{ domain_request.requested_domain.name }}</span>
{% else %}
Delete <span class="usa-sr-only">{{ name_default }}</span>
{% endif %}
{% endwith %}
{% endwith %}
{% endwith %}
</a>
<tbody id="domain-requests-tbody">
<!-- AJAX will populate this tbody -->
</tbody>
</table>
<div
class="usa-sr-only usa-table__announcement-region"
aria-live="polite"
></div>
{% for domain_request in domain_requests %}
{% if has_deletable_domain_requests %}
{% if domain_request.status == domain_request.DomainRequestStatus.STARTED or domain_request.status == domain_request.DomainRequestStatus.WITHDRAWN %}
<div
class="usa-modal"
id="toggle-delete-domain-alert-{{ forloop.counter }}"
id="toggle-delete-domain-alert-{{ domain_request.id }}"
aria-labelledby="Are you sure you want to continue?"
aria-describedby="Domain will be removed"
data-force-action
@ -227,21 +125,22 @@
</form>
</div>
{% endif %}
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
<div
class="usa-sr-only usa-table__announcement-region"
aria-live="polite"
></div>
{% else %}
</div>
<div class="no-domain-requests-wrapper display-none">
<p>You haven't requested any domains.</p>
<!-- <p><a href="{% url 'domain-request:' %}" class="usa-button">Start a new domain request</a></p> -->
{% endif %}
</div>
</section>
<nav aria-label="Pagination" class="usa-pagination flex-justify" id="domain-requests-pagination">
<span class="usa-pagination__counter text-base-dark padding-left-2">
<!-- Count will be dynamically populated by JS -->
</span>
<ul class="usa-pagination__list">
<!-- Pagination links will be dynamically populated by JS -->
</ul>
</nav>
{# Note: Reimplement this after MVP #}
<!--

View file

@ -0,0 +1,25 @@
{% load static field_helpers %}
{% block domain_content %}
<div
class="usa-summary-box"
role="region"
aria-labelledby="summary-box-key-information"
>
<div class="usa-summary-box__body">
<h3 class="usa-summary-box__heading" id="summary-box-key-information">
Your contact information
</h3>
<div class="usa-summary-box__text">
<ul>
<li>Full name: <b>{{ user.contact.get_formatted_name }}</b></li>
<li>Organization email: <b>{{ user.email }}</b></li>
<li>Title or role in your organization: <b>{{ user.contact.title }}</b></li>
<li>Phone: <b>{{ user.contact.phone.as_national }}</b></li>
</ul>
</div>
</div>
</div>
{% endblock %}

View file

@ -667,7 +667,7 @@ class MockDb(TestCase):
is_election_board=False,
)
meoward_user = get_user_model().objects.create(
self.meoward_user = get_user_model().objects.create(
username="meoward_username", first_name="first_meoward", last_name="last_meoward", email="meoward@rocks.com"
)
@ -676,7 +676,7 @@ class MockDb(TestCase):
)
_, created = UserDomainRole.objects.get_or_create(
user=meoward_user, domain=self.domain_1, role=UserDomainRole.Roles.MANAGER
user=self.meoward_user, domain=self.domain_1, role=UserDomainRole.Roles.MANAGER
)
_, created = UserDomainRole.objects.get_or_create(
@ -688,19 +688,21 @@ class MockDb(TestCase):
)
_, created = UserDomainRole.objects.get_or_create(
user=meoward_user, domain=self.domain_2, role=UserDomainRole.Roles.MANAGER
user=self.meoward_user, domain=self.domain_2, role=UserDomainRole.Roles.MANAGER
)
_, created = UserDomainRole.objects.get_or_create(
user=meoward_user, domain=self.domain_11, role=UserDomainRole.Roles.MANAGER
user=self.meoward_user, domain=self.domain_11, role=UserDomainRole.Roles.MANAGER
)
_, created = UserDomainRole.objects.get_or_create(
user=meoward_user, domain=self.domain_12, role=UserDomainRole.Roles.MANAGER
user=self.meoward_user, domain=self.domain_12, role=UserDomainRole.Roles.MANAGER
)
_, created = DomainInvitation.objects.get_or_create(
email=meoward_user.email, domain=self.domain_1, status=DomainInvitation.DomainInvitationStatus.RETRIEVED
email=self.meoward_user.email,
domain=self.domain_1,
status=DomainInvitation.DomainInvitationStatus.RETRIEVED,
)
_, created = DomainInvitation.objects.get_or_create(

View file

@ -47,6 +47,7 @@ from registrar.models import (
from registrar.models.user_domain_role import UserDomainRole
from registrar.models.verified_by_staff import VerifiedByStaff
from .common import (
MockDb,
MockSESClient,
AuditedAdminMockData,
completed_domain_request,
@ -3438,16 +3439,19 @@ class TestListHeaderAdmin(TestCase):
User.objects.all().delete()
class TestMyUserAdmin(TestCase):
class TestMyUserAdmin(MockDb):
def setUp(self):
super().setUp()
admin_site = AdminSite()
self.admin = MyUserAdmin(model=get_user_model(), admin_site=admin_site)
self.client = Client(HTTP_HOST="localhost:8080")
self.superuser = create_superuser()
self.staffuser = create_user()
self.test_helper = GenericTestHelper(admin=self.admin)
def tearDown(self):
super().tearDown()
DomainRequest.objects.all().delete()
User.objects.all().delete()
@less_console_noise_decorator
@ -3472,7 +3476,7 @@ class TestMyUserAdmin(TestCase):
"""
Tests for the correct helper text on this page
"""
user = create_user()
user = self.staffuser
p = "adminpass"
self.client.login(username="superuser", password=p)
@ -3493,10 +3497,11 @@ class TestMyUserAdmin(TestCase):
]
self.test_helper.assert_response_contains_distinct_values(response, expected_values)
@less_console_noise_decorator
def test_list_display_without_username(self):
with less_console_noise():
request = self.client.request().wsgi_request
request.user = create_user()
request.user = self.staffuser
list_display = self.admin.get_list_display(request)
expected_list_display = [
@ -3522,7 +3527,7 @@ class TestMyUserAdmin(TestCase):
def test_get_fieldsets_cisa_analyst(self):
with less_console_noise():
request = self.client.request().wsgi_request
request.user = create_user()
request.user = self.staffuser
fieldsets = self.admin.get_fieldsets(request)
expected_fieldsets = (
(
@ -3540,6 +3545,97 @@ class TestMyUserAdmin(TestCase):
)
self.assertEqual(fieldsets, expected_fieldsets)
def test_analyst_can_see_related_domains_and_requests_in_user_form(self):
"""Tests if an analyst can see the related domains and domain requests for a user in that user's form"""
# From MockDb, we have self.meoward_user which we'll use as creator
# Create fake domain requests
domain_request_started = completed_domain_request(
status=DomainRequest.DomainRequestStatus.STARTED, user=self.meoward_user, name="started.gov"
)
domain_request_submitted = completed_domain_request(
status=DomainRequest.DomainRequestStatus.SUBMITTED, user=self.meoward_user, name="submitted.gov"
)
domain_request_in_review = completed_domain_request(
status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=self.meoward_user, name="in-review.gov"
)
domain_request_withdrawn = completed_domain_request(
status=DomainRequest.DomainRequestStatus.WITHDRAWN, user=self.meoward_user, name="withdrawn.gov"
)
domain_request_approved = completed_domain_request(
status=DomainRequest.DomainRequestStatus.APPROVED, user=self.meoward_user, name="approved.gov"
)
domain_request_rejected = completed_domain_request(
status=DomainRequest.DomainRequestStatus.REJECTED, user=self.meoward_user, name="rejected.gov"
)
domain_request_ineligible = completed_domain_request(
status=DomainRequest.DomainRequestStatus.INELIGIBLE, user=self.meoward_user, name="ineligible.gov"
)
# From MockDb, we have sel.meoward_user who's admin on
# self.domain_1 - READY
# self.domain_2 - DNS_NEEDED
# self.domain_11 - READY
# self.domain_12 - READY
# DELETED:
domain_deleted, _ = Domain.objects.get_or_create(
name="domain_deleted.gov", state=Domain.State.DELETED, deleted=timezone.make_aware(datetime(2024, 4, 2))
)
_, created = UserDomainRole.objects.get_or_create(
user=self.meoward_user, domain=domain_deleted, role=UserDomainRole.Roles.MANAGER
)
p = "userpass"
self.client.login(username="staffuser", password=p)
response = self.client.get(
"/admin/registrar/user/{}/change/".format(self.meoward_user.id),
follow=True,
)
# Make sure the page loaded and contains the expected domain request names and links to the domain requests
self.assertEqual(response.status_code, 200)
self.assertContains(response, domain_request_submitted.requested_domain.name)
expected_href = reverse("admin:registrar_domainrequest_change", args=[domain_request_submitted.pk])
self.assertContains(response, expected_href)
self.assertContains(response, domain_request_in_review.requested_domain.name)
expected_href = reverse("admin:registrar_domainrequest_change", args=[domain_request_in_review.pk])
self.assertContains(response, expected_href)
self.assertContains(response, domain_request_approved.requested_domain.name)
expected_href = reverse("admin:registrar_domainrequest_change", args=[domain_request_approved.pk])
self.assertContains(response, expected_href)
self.assertContains(response, domain_request_rejected.requested_domain.name)
expected_href = reverse("admin:registrar_domainrequest_change", args=[domain_request_rejected.pk])
self.assertContains(response, expected_href)
self.assertContains(response, domain_request_ineligible.requested_domain.name)
expected_href = reverse("admin:registrar_domainrequest_change", args=[domain_request_ineligible.pk])
self.assertContains(response, expected_href)
# We filter out those requests
# STARTED
self.assertNotContains(response, domain_request_started.requested_domain.name)
expected_href = reverse("admin:registrar_domainrequest_change", args=[domain_request_started.pk])
self.assertNotContains(response, expected_href)
# WITHDRAWN
self.assertNotContains(response, domain_request_withdrawn.requested_domain.name)
expected_href = reverse("admin:registrar_domainrequest_change", args=[domain_request_withdrawn.pk])
self.assertNotContains(response, expected_href)
# Make sure the page contains the expected domain names and links to the domains
self.assertContains(response, self.domain_1.name)
expected_href = reverse("admin:registrar_domain_change", args=[self.domain_1.pk])
self.assertContains(response, expected_href)
# We filter out DELETED
self.assertNotContains(response, domain_deleted.name)
expected_href = reverse("admin:registrar_domain_change", args=[domain_deleted.pk])
self.assertNotContains(response, expected_href)
class AuditedAdminTest(TestCase):
def setUp(self):

View file

@ -161,31 +161,14 @@ class HomeTests(TestWithUser):
self.assertContains(response, "You don't have any registered domains.")
self.assertContains(response, "Why don't I see my domain when I sign in to the registrar?")
def test_home_lists_domain_requests(self):
response = self.client.get("/")
self.assertNotContains(response, "igorville.gov")
site = DraftDomain.objects.create(name="igorville.gov")
domain_request = DomainRequest.objects.create(creator=self.user, requested_domain=site)
response = self.client.get("/")
# count = 7 because of screenreader content
self.assertContains(response, "igorville.gov", count=7)
# clean up
domain_request.delete()
def test_state_help_text(self):
"""Tests if each domain state has help text"""
# Get the expected text content of each state
deleted_text = "This domain has been removed and " "is no longer registered to your organization."
dns_needed_text = "Before this domain can be used, " "youll need to add name server addresses."
dns_needed_text = "Before this domain can be used, "
ready_text = "This domain has name servers and is ready for use."
on_hold_text = (
"This domain is administratively paused, "
"so it cant be edited and wont resolve in DNS. "
"Contact help@get.gov for details."
)
on_hold_text = "This domain is administratively paused, "
deleted_text = "This domain has been removed and " "is no longer registered to your organization."
# Generate a mapping of domain names, the state, and expected messages for the subtest
test_cases = [
@ -206,12 +189,11 @@ class HomeTests(TestWithUser):
user=self.user, domain=test_domain, role=UserDomainRole.Roles.MANAGER
)
# Grab the home page
response = self.client.get("/")
# Grab the json response for domain list
response = self.client.get("/get-domains-json/")
# Make sure the user can actually see the domain.
# We expect two instances because of SR content.
self.assertContains(response, domain_name, count=2)
# Make sure the domain is in the list.
self.assertContains(response, domain_name, count=1)
# Check that we have the right text content.
self.assertContains(response, expected_message, count=1)
@ -222,19 +204,18 @@ class HomeTests(TestWithUser):
def test_state_help_text_expired(self):
"""Tests if each domain state has help text when expired"""
expired_text = "This domain has expired, but it is still online. " "To renew this domain, contact help@get.gov."
expired_text = "This domain has expired, but it is still online. "
test_domain, _ = Domain.objects.get_or_create(name="expired.gov", state=Domain.State.READY)
test_domain.expiration_date = date(2011, 10, 10)
test_domain.save()
UserDomainRole.objects.get_or_create(user=self.user, domain=test_domain, role=UserDomainRole.Roles.MANAGER)
# Grab the home page
response = self.client.get("/")
# Grab the json response of the domains list
response = self.client.get("/get-domains-json/")
# Make sure the user can actually see the domain.
# We expect two instances because of SR content.
self.assertContains(response, "expired.gov", count=2)
# Make sure the domain is in the response
self.assertContains(response, "expired.gov", count=1)
# Check that we have the right text content.
self.assertContains(response, expired_text, count=1)
@ -243,19 +224,18 @@ class HomeTests(TestWithUser):
"""Tests if each domain state has help text when expiration date is None"""
# == Test a expiration of None for state ready. This should be expired. == #
expired_text = "This domain has expired, but it is still online. " "To renew this domain, contact help@get.gov."
expired_text = "This domain has expired, but it is still online. "
test_domain, _ = Domain.objects.get_or_create(name="imexpired.gov", state=Domain.State.READY)
test_domain.expiration_date = None
test_domain.save()
UserDomainRole.objects.get_or_create(user=self.user, domain=test_domain, role=UserDomainRole.Roles.MANAGER)
# Grab the home page
response = self.client.get("/")
# Grab the json response of the domains list
response = self.client.get("/get-domains-json/")
# Make sure the user can actually see the domain.
# We expect two instances because of SR content.
self.assertContains(response, "imexpired.gov", count=2)
# Make sure domain is in the response
self.assertContains(response, "imexpired.gov", count=1)
# Make sure the expiration date is None
self.assertEqual(test_domain.expiration_date, None)
@ -264,19 +244,18 @@ class HomeTests(TestWithUser):
self.assertContains(response, expired_text, count=1)
# == Test a expiration of None for state unknown. This should not display expired text. == #
unknown_text = "Before this domain can be used, " "youll need to add name server addresses."
unknown_text = "Before this domain can be used, "
test_domain_2, _ = Domain.objects.get_or_create(name="notexpired.gov", state=Domain.State.UNKNOWN)
test_domain_2.expiration_date = None
test_domain_2.save()
UserDomainRole.objects.get_or_create(user=self.user, domain=test_domain_2, role=UserDomainRole.Roles.MANAGER)
# Grab the home page
response = self.client.get("/")
# Grab the json response of the domains list
response = self.client.get("/get-domains-json/")
# Make sure the user can actually see the domain.
# We expect two instances because of SR content.
self.assertContains(response, "notexpired.gov", count=2)
# Make sure the response contains the domain
self.assertContains(response, "notexpired.gov", count=1)
# Make sure the expiration date is None
self.assertEqual(test_domain_2.expiration_date, None)
@ -292,14 +271,6 @@ class HomeTests(TestWithUser):
creator=self.user, requested_domain=site, status=DomainRequest.DomainRequestStatus.WITHDRAWN
)
# Ensure that igorville.gov exists on the page
home_page = self.client.get("/")
self.assertContains(home_page, "igorville.gov")
# Check if the delete button exists. We can do this by checking for its id and text content.
self.assertContains(home_page, "Delete")
self.assertContains(home_page, "button-toggle-delete-domain-alert-1")
# Trigger the delete logic
response = self.client.post(reverse("domain-request-delete", kwargs={"pk": domain_request.pk}), follow=True)
@ -316,14 +287,6 @@ class HomeTests(TestWithUser):
creator=self.user, requested_domain=site, status=DomainRequest.DomainRequestStatus.STARTED
)
# Ensure that igorville.gov exists on the page
home_page = self.client.get("/")
self.assertContains(home_page, "igorville.gov")
# Check if the delete button exists. We can do this by checking for its id and text content.
self.assertContains(home_page, "Delete")
self.assertContains(home_page, "button-toggle-delete-domain-alert-1")
# Trigger the delete logic
response = self.client.post(reverse("domain-request-delete", kwargs={"pk": domain_request.pk}), follow=True)

View file

@ -222,27 +222,6 @@ class TestDomainDetail(TestDomainOverview):
self.assertContains(detail_page, "igorville.gov")
self.assertContains(detail_page, "Status")
def test_unknown_domain_does_not_show_as_expired_on_homepage(self):
"""An UNKNOWN domain does not show as expired on the homepage.
It shows as 'DNS needed'"""
# At the time of this test's writing, there are 6 UNKNOWN domains inherited
# from constructors. Let's reset.
with less_console_noise():
Domain.objects.all().delete()
UserDomainRole.objects.all().delete()
self.domain, _ = Domain.objects.get_or_create(name="igorville.gov")
home_page = self.app.get("/")
self.assertNotContains(home_page, "igorville.gov")
self.role, _ = UserDomainRole.objects.get_or_create(
user=self.user, domain=self.domain, role=UserDomainRole.Roles.MANAGER
)
home_page = self.app.get("/")
self.assertContains(home_page, "igorville.gov")
igorville = Domain.objects.get(name="igorville.gov")
self.assertEquals(igorville.state, Domain.State.UNKNOWN)
self.assertNotContains(home_page, "Expired")
self.assertContains(home_page, "DNS needed")
def test_unknown_domain_does_not_show_as_expired_on_detail_page(self):
"""An UNKNOWN domain should not exist on the detail_page anymore.
It shows as 'DNS needed'"""
@ -258,11 +237,9 @@ class TestDomainDetail(TestDomainOverview):
user=self.user, domain=self.domain, role=UserDomainRole.Roles.MANAGER
)
home_page = self.app.get("/")
self.assertContains(home_page, "igorville.gov")
igorville = Domain.objects.get(name="igorville.gov")
self.assertEquals(igorville.state, Domain.State.UNKNOWN)
detail_page = home_page.click("Manage", index=0)
detail_page = self.app.get(f"/domain/{igorville.id}")
self.assertContains(detail_page, "Expired")
self.assertNotContains(detail_page, "DNS needed")
@ -274,26 +251,18 @@ class TestDomainDetail(TestDomainOverview):
with less_console_noise():
self.user.status = User.RESTRICTED
self.user.save()
home_page = self.app.get("/")
self.assertContains(home_page, "igorville.gov")
response = self.client.get(reverse("domain", kwargs={"pk": self.domain.id}))
self.assertEqual(response.status_code, 403)
def test_domain_detail_allowed_for_on_hold(self):
"""Test that the domain overview page displays for on hold domain"""
with less_console_noise():
home_page = self.app.get("/")
self.assertContains(home_page, "on-hold.gov")
# View domain overview page
detail_page = self.client.get(reverse("domain", kwargs={"pk": self.domain_on_hold.id}))
self.assertNotContains(detail_page, "Edit")
def test_domain_detail_see_just_nameserver(self):
with less_console_noise():
home_page = self.app.get("/")
self.assertContains(home_page, "justnameserver.com")
# View nameserver on Domain Overview page
detail_page = self.app.get(reverse("domain", kwargs={"pk": self.domain_just_nameserver.id}))
@ -303,9 +272,6 @@ class TestDomainDetail(TestDomainOverview):
def test_domain_detail_see_nameserver_and_ip(self):
with less_console_noise():
home_page = self.app.get("/")
self.assertContains(home_page, "nameserverwithip.gov")
# View nameserver on Domain Overview page
detail_page = self.app.get(reverse("domain", kwargs={"pk": self.domain_with_ip.id}))
@ -1643,8 +1609,6 @@ class TestDomainSecurityEmail(TestDomainOverview):
management pages share the same permissions class"""
self.user.status = User.RESTRICTED
self.user.save()
home_page = self.app.get("/")
self.assertContains(home_page, "igorville.gov")
with less_console_noise():
response = self.client.get(reverse("domain", kwargs={"pk": self.domain.id}))
self.assertEqual(response.status_code, 403)

View file

@ -0,0 +1,151 @@
from registrar.models import UserDomainRole, Domain
from django.urls import reverse
from .test_views import TestWithUser
from django_webtest import WebTest # type: ignore
from django.utils.dateparse import parse_date
class GetDomainsJsonTest(TestWithUser, WebTest):
def setUp(self):
super().setUp()
self.app.set_user(self.user.username)
# Create test domains
self.domain1 = Domain.objects.create(name="example1.com", expiration_date="2024-01-01", state="active")
self.domain2 = Domain.objects.create(name="example2.com", expiration_date="2024-02-01", state="inactive")
self.domain3 = Domain.objects.create(name="example3.com", expiration_date="2024-03-01", state="active")
# Create UserDomainRoles
UserDomainRole.objects.create(user=self.user, domain=self.domain1)
UserDomainRole.objects.create(user=self.user, domain=self.domain2)
UserDomainRole.objects.create(user=self.user, domain=self.domain3)
def tearDown(self):
super().tearDown()
UserDomainRole.objects.all().delete()
UserDomainRole.objects.all().delete()
def test_get_domains_json_unauthenticated(self):
"""for an unauthenticated user, test that the user is redirected for auth"""
self.app.reset()
response = self.client.get(reverse("get_domains_json"))
self.assertEqual(response.status_code, 302)
def test_get_domains_json_authenticated(self):
"""Test that an authenticated user gets the list of 3 domains."""
response = self.app.get(reverse("get_domains_json"))
self.assertEqual(response.status_code, 200)
data = response.json
# Check pagination info
self.assertEqual(data["page"], 1)
self.assertFalse(data["has_next"])
self.assertFalse(data["has_previous"])
self.assertEqual(data["num_pages"], 1)
# Check the number of domains
self.assertEqual(len(data["domains"]), 3)
# Expected domains
expected_domains = [self.domain1, self.domain2, self.domain3]
# Extract fields from response
domain_ids = [domain["id"] for domain in data["domains"]]
names = [domain["name"] for domain in data["domains"]]
expiration_dates = [domain["expiration_date"] for domain in data["domains"]]
states = [domain["state"] for domain in data["domains"]]
state_displays = [domain["state_display"] for domain in data["domains"]]
get_state_help_texts = [domain["get_state_help_text"] for domain in data["domains"]]
action_urls = [domain["action_url"] for domain in data["domains"]]
action_labels = [domain["action_label"] for domain in data["domains"]]
svg_icons = [domain["svg_icon"] for domain in data["domains"]]
# Check fields for each domain
for i, expected_domain in enumerate(expected_domains):
self.assertEqual(expected_domain.id, domain_ids[i])
self.assertEqual(expected_domain.name, names[i])
self.assertEqual(expected_domain.expiration_date, expiration_dates[i])
self.assertEqual(expected_domain.state, states[i])
# Parsing the expiration date from string to date
parsed_expiration_date = parse_date(expiration_dates[i])
expected_domain.expiration_date = parsed_expiration_date
# Check state_display and get_state_help_text
self.assertEqual(expected_domain.state_display(), state_displays[i])
self.assertEqual(expected_domain.get_state_help_text(), get_state_help_texts[i])
self.assertEqual(reverse("domain", kwargs={"pk": expected_domain.id}), action_urls[i])
# Check action_label
action_label_expected = (
"View"
if expected_domains[i].state
in [
Domain.State.DELETED,
Domain.State.ON_HOLD,
]
else "Manage"
)
self.assertEqual(action_label_expected, action_labels[i])
# Check svg_icon
svg_icon_expected = (
"visibility"
if expected_domains[i].state
in [
Domain.State.DELETED,
Domain.State.ON_HOLD,
]
else "settings"
)
self.assertEqual(svg_icon_expected, svg_icons[i])
def test_pagination(self):
"""Test that pagination is correct in the response"""
response = self.app.get(reverse("get_domains_json"), {"page": 1})
self.assertEqual(response.status_code, 200)
data = response.json
# Check pagination info
self.assertEqual(data["page"], 1)
self.assertFalse(data["has_next"])
self.assertFalse(data["has_previous"])
self.assertEqual(data["num_pages"], 1)
def test_sorting(self):
"""test that sorting works properly in the response"""
response = self.app.get(reverse("get_domains_json"), {"sort_by": "expiration_date", "order": "desc"})
self.assertEqual(response.status_code, 200)
data = response.json
# Check if sorted by expiration_date in descending order
expiration_dates = [domain["expiration_date"] for domain in data["domains"]]
self.assertEqual(expiration_dates, sorted(expiration_dates, reverse=True))
response = self.app.get(reverse("get_domains_json"), {"sort_by": "expiration_date", "order": "asc"})
self.assertEqual(response.status_code, 200)
data = response.json
# Check if sorted by expiration_date in ascending order
expiration_dates = [domain["expiration_date"] for domain in data["domains"]]
self.assertEqual(expiration_dates, sorted(expiration_dates))
def test_sorting_by_state_display(self):
"""test that the state_display sorting works properly"""
response = self.app.get(reverse("get_domains_json"), {"sort_by": "state_display", "order": "asc"})
self.assertEqual(response.status_code, 200)
data = response.json
# Check if sorted by state_display in ascending order
states = [domain["state_display"] for domain in data["domains"]]
self.assertEqual(states, sorted(states))
response = self.app.get(reverse("get_domains_json"), {"sort_by": "state_display", "order": "desc"})
self.assertEqual(response.status_code, 200)
data = response.json
# Check if sorted by state_display in descending order
states = [domain["state_display"] for domain in data["domains"]]
self.assertEqual(states, sorted(states, reverse=True))

View file

@ -47,11 +47,8 @@ class DomainRequestTests(TestWithUser, WebTest):
def test_domain_request_form_intro_is_skipped_when_edit_access(self):
"""Tests that user is NOT presented with intro acknowledgement page when accessed through 'edit'"""
completed_domain_request(status=DomainRequest.DomainRequestStatus.STARTED, user=self.user)
home_page = self.app.get("/")
self.assertContains(home_page, "city.gov")
# click the "Edit" link
detail_page = home_page.click("Edit", index=0)
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.STARTED, user=self.user)
detail_page = self.app.get(f"/domain-request/{domain_request.id}/edit/")
# Check that the response is a redirect
self.assertEqual(detail_page.status_code, 302)
# You can access the 'Location' header to get the redirect URL
@ -2402,10 +2399,7 @@ class DomainRequestTestDifferentStatuses(TestWithUser, WebTest):
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.SUBMITTED, user=self.user)
domain_request.save()
home_page = self.app.get("/")
self.assertContains(home_page, "city.gov")
# click the "Manage" link
detail_page = home_page.click("Manage", index=0)
detail_page = self.app.get(f"/domain-request/{domain_request.id}")
self.assertContains(detail_page, "city.gov")
self.assertContains(detail_page, "city1.gov")
self.assertContains(detail_page, "Chief Tester")
@ -2422,10 +2416,7 @@ class DomainRequestTestDifferentStatuses(TestWithUser, WebTest):
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.SUBMITTED, user=self.user)
domain_request.save()
home_page = self.app.get("/")
self.assertContains(home_page, "city.gov")
# click the "Manage" link
detail_page = home_page.click("Manage", index=0)
detail_page = self.app.get(f"/domain-request/{domain_request.id}")
self.assertContains(detail_page, "city.gov")
self.assertContains(detail_page, "Chief Tester")
self.assertContains(detail_page, "testy@town.com")
@ -2437,10 +2428,7 @@ class DomainRequestTestDifferentStatuses(TestWithUser, WebTest):
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.SUBMITTED, user=self.user)
domain_request.save()
home_page = self.app.get("/")
self.assertContains(home_page, "city.gov")
# click the "Manage" link
detail_page = home_page.click("Manage", index=0)
detail_page = self.app.get(f"/domain-request/{domain_request.id}")
self.assertContains(detail_page, "city.gov")
self.assertContains(detail_page, "city1.gov")
self.assertContains(detail_page, "Chief Tester")
@ -2462,8 +2450,8 @@ class DomainRequestTestDifferentStatuses(TestWithUser, WebTest):
target_status_code=200,
fetch_redirect_response=True,
)
home_page = self.app.get("/")
self.assertContains(home_page, "Withdrawn")
response = self.client.get("/get-domain-requests-json/")
self.assertContains(response, "Withdrawn")
def test_domain_request_withdraw_no_permissions(self):
"""Can't withdraw domain requests as a restricted user."""
@ -2472,10 +2460,7 @@ class DomainRequestTestDifferentStatuses(TestWithUser, WebTest):
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.SUBMITTED, user=self.user)
domain_request.save()
home_page = self.app.get("/")
self.assertContains(home_page, "city.gov")
# click the "Manage" link
detail_page = home_page.click("Manage", index=0)
detail_page = self.app.get(f"/domain-request/{domain_request.id}")
self.assertContains(detail_page, "city.gov")
self.assertContains(detail_page, "city1.gov")
self.assertContains(detail_page, "Chief Tester")
@ -2545,9 +2530,9 @@ class TestWizardUnlockingSteps(TestWithUser, WebTest):
def test_unlocked_steps_full_domain_request(self):
"""Test when all fields in the domain request are filled."""
completed_domain_request(status=DomainRequest.DomainRequestStatus.STARTED, user=self.user)
# Make a request to the home page
home_page = self.app.get("/")
domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.STARTED, user=self.user)
response = self.app.get(f"/domain-request/{domain_request.id}/edit/")
# django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here
@ -2555,13 +2540,6 @@ class TestWizardUnlockingSteps(TestWithUser, WebTest):
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
# Assert that the response contains "city.gov"
self.assertContains(home_page, "city.gov")
# Click the "Edit" link
response = home_page.click("Edit", index=0)
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
# Check if the response is a redirect
if response.status_code == 302:
# Follow the redirect manually
@ -2612,8 +2590,7 @@ class TestWizardUnlockingSteps(TestWithUser, WebTest):
)
domain_request.other_contacts.set([contact_2])
# Make a request to the home page
home_page = self.app.get("/")
response = self.app.get(f"/domain-request/{domain_request.id}/edit/")
# django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here
@ -2621,13 +2598,6 @@ class TestWizardUnlockingSteps(TestWithUser, WebTest):
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
# Assert that the response contains "city.gov"
self.assertContains(home_page, "igorville.gov")
# Click the "Edit" link
response = home_page.click("Edit", index=0)
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
# Check if the response is a redirect
if response.status_code == 302:
# Follow the redirect manually

View file

@ -0,0 +1,248 @@
from registrar.models import DomainRequest
from django.urls import reverse
from .test_views import TestWithUser
from django_webtest import WebTest # type: ignore
from django.utils.dateparse import parse_datetime
class GetRequestsJsonTest(TestWithUser, WebTest):
def setUp(self):
super().setUp()
self.app.set_user(self.user.username)
# Create domain requests for the user
self.domain_requests = [
DomainRequest.objects.create(
creator=self.user,
requested_domain=None,
submission_date="2024-01-01",
status=DomainRequest.DomainRequestStatus.STARTED,
created_at="2024-01-01",
),
DomainRequest.objects.create(
creator=self.user,
requested_domain=None,
submission_date="2024-02-01",
status=DomainRequest.DomainRequestStatus.WITHDRAWN,
created_at="2024-02-01",
),
DomainRequest.objects.create(
creator=self.user,
requested_domain=None,
submission_date="2024-03-01",
status=DomainRequest.DomainRequestStatus.REJECTED,
created_at="2024-03-01",
),
DomainRequest.objects.create(
creator=self.user,
requested_domain=None,
submission_date="2024-04-01",
status=DomainRequest.DomainRequestStatus.STARTED,
created_at="2024-04-01",
),
DomainRequest.objects.create(
creator=self.user,
requested_domain=None,
submission_date="2024-05-01",
status=DomainRequest.DomainRequestStatus.STARTED,
created_at="2024-05-01",
),
DomainRequest.objects.create(
creator=self.user,
requested_domain=None,
submission_date="2024-06-01",
status=DomainRequest.DomainRequestStatus.WITHDRAWN,
created_at="2024-06-01",
),
DomainRequest.objects.create(
creator=self.user,
requested_domain=None,
submission_date="2024-07-01",
status=DomainRequest.DomainRequestStatus.REJECTED,
created_at="2024-07-01",
),
DomainRequest.objects.create(
creator=self.user,
requested_domain=None,
submission_date="2024-08-01",
status=DomainRequest.DomainRequestStatus.STARTED,
created_at="2024-08-01",
),
DomainRequest.objects.create(
creator=self.user,
requested_domain=None,
submission_date="2024-09-01",
status=DomainRequest.DomainRequestStatus.STARTED,
created_at="2024-09-01",
),
DomainRequest.objects.create(
creator=self.user,
requested_domain=None,
submission_date="2024-10-01",
status=DomainRequest.DomainRequestStatus.WITHDRAWN,
created_at="2024-10-01",
),
DomainRequest.objects.create(
creator=self.user,
requested_domain=None,
submission_date="2024-11-01",
status=DomainRequest.DomainRequestStatus.REJECTED,
created_at="2024-11-01",
),
DomainRequest.objects.create(
creator=self.user,
requested_domain=None,
submission_date="2024-11-02",
status=DomainRequest.DomainRequestStatus.WITHDRAWN,
created_at="2024-11-02",
),
DomainRequest.objects.create(
creator=self.user,
requested_domain=None,
submission_date="2024-12-01",
status=DomainRequest.DomainRequestStatus.APPROVED,
created_at="2024-12-01",
),
]
def tearDown(self):
super().tearDown()
DomainRequest.objects.all().delete()
def test_get_domain_requests_json_authenticated(self):
"""Test that domain requests are returned properly for an authenticated user."""
response = self.app.get(reverse("get_domain_requests_json"))
self.assertEqual(response.status_code, 200)
data = response.json
# Check pagination info
self.assertEqual(data["page"], 1)
self.assertTrue(data["has_next"])
self.assertFalse(data["has_previous"])
self.assertEqual(data["num_pages"], 2)
# Check the number of domain requests
self.assertEqual(len(data["domain_requests"]), 10)
# Extract fields from response
requested_domains = [request["requested_domain"] for request in data["domain_requests"]]
submission_dates = [request["submission_date"] for request in data["domain_requests"]]
statuses = [request["status"] for request in data["domain_requests"]]
created_ats = [request["created_at"] for request in data["domain_requests"]]
ids = [request["id"] for request in data["domain_requests"]]
is_deletables = [request["is_deletable"] for request in data["domain_requests"]]
action_urls = [request["action_url"] for request in data["domain_requests"]]
action_labels = [request["action_label"] for request in data["domain_requests"]]
svg_icons = [request["svg_icon"] for request in data["domain_requests"]]
# Check fields for each domain request
for i in range(10):
self.assertNotEqual(data["domain_requests"][i]["status"], "Approved")
self.assertEqual(
self.domain_requests[i].requested_domain.name if self.domain_requests[i].requested_domain else None,
requested_domains[i],
)
self.assertEqual(self.domain_requests[i].submission_date, submission_dates[i])
self.assertEqual(self.domain_requests[i].get_status_display(), statuses[i])
self.assertEqual(
parse_datetime(self.domain_requests[i].created_at.isoformat()), parse_datetime(created_ats[i])
)
self.assertEqual(self.domain_requests[i].id, ids[i])
# Check is_deletable
is_deletable_expected = self.domain_requests[i].status in [
DomainRequest.DomainRequestStatus.STARTED,
DomainRequest.DomainRequestStatus.WITHDRAWN,
]
self.assertEqual(is_deletable_expected, is_deletables[i])
# Check action_url
action_url_expected = (
reverse("edit-domain-request", kwargs={"id": self.domain_requests[i].id})
if self.domain_requests[i].status
in [
DomainRequest.DomainRequestStatus.STARTED,
DomainRequest.DomainRequestStatus.ACTION_NEEDED,
DomainRequest.DomainRequestStatus.WITHDRAWN,
]
else reverse("domain-request-status", kwargs={"pk": self.domain_requests[i].id})
)
self.assertEqual(action_url_expected, action_urls[i])
# Check action_label
action_label_expected = (
"Edit"
if self.domain_requests[i].status
in [
DomainRequest.DomainRequestStatus.STARTED,
DomainRequest.DomainRequestStatus.ACTION_NEEDED,
DomainRequest.DomainRequestStatus.WITHDRAWN,
]
else "Manage"
)
self.assertEqual(action_label_expected, action_labels[i])
# Check svg_icon
svg_icon_expected = (
"edit"
if self.domain_requests[i].status
in [
DomainRequest.DomainRequestStatus.STARTED,
DomainRequest.DomainRequestStatus.ACTION_NEEDED,
DomainRequest.DomainRequestStatus.WITHDRAWN,
]
else "settings"
)
self.assertEqual(svg_icon_expected, svg_icons[i])
def test_pagination(self):
"""Test that pagination works properly. There are 11 total non-approved requests and
a page size of 10"""
response = self.app.get(reverse("get_domain_requests_json"), {"page": 1})
self.assertEqual(response.status_code, 200)
data = response.json
# Check pagination info
self.assertEqual(data["page"], 1)
self.assertTrue(data["has_next"])
self.assertFalse(data["has_previous"])
self.assertEqual(data["num_pages"], 2)
response = self.app.get(reverse("get_domain_requests_json"), {"page": 2})
self.assertEqual(response.status_code, 200)
data = response.json
# Check pagination info
self.assertEqual(data["page"], 2)
self.assertFalse(data["has_next"])
self.assertTrue(data["has_previous"])
self.assertEqual(data["num_pages"], 2)
def test_sorting(self):
"""test that sorting works properly on the result set"""
response = self.app.get(reverse("get_domain_requests_json"), {"sort_by": "submission_date", "order": "desc"})
self.assertEqual(response.status_code, 200)
data = response.json
# Check if sorted by submission_date in descending order
submission_dates = [req["submission_date"] for req in data["domain_requests"]]
self.assertEqual(submission_dates, sorted(submission_dates, reverse=True))
response = self.app.get(reverse("get_domain_requests_json"), {"sort_by": "submission_date", "order": "asc"})
self.assertEqual(response.status_code, 200)
data = response.json
# Check if sorted by submission_date in ascending order
submission_dates = [req["submission_date"] for req in data["domain_requests"]]
self.assertEqual(submission_dates, sorted(submission_dates))
def test_filter_approved_excluded(self):
"""test that approved requests are excluded from result set."""
# sort in reverse chronological order of submission date, since most recent request is approved
response = self.app.get(reverse("get_domain_requests_json"), {"sort_by": "submission_date", "order": "desc"})
self.assertEqual(response.status_code, 200)
data = response.json
# Ensure no approved requests are included
for domain_request in data["domain_requests"]:
self.assertNotEqual(domain_request["status"], DomainRequest.DomainRequestStatus.APPROVED)

View file

@ -230,7 +230,8 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
context = self.get_context_data()
if self.__class__ == DomainRequestWizard:
if request.path_info == self.NEW_URL_NAME:
return render(request, "domain_request_intro.html", context)
context = self.get_context_data()
return render(request, "domain_request_intro.html", context=context)
else:
return self.goto(self.steps.first)
@ -386,7 +387,10 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
else:
modal_heading = "You are about to submit an incomplete request"
return {
has_profile_flag = flag_is_active(self.request, "profile_feature")
logger.debug("PROFILE FLAG is %s" % has_profile_flag)
context = {
"form_titles": self.TITLES,
"steps": self.steps,
# Add information about which steps should be unlocked
@ -394,8 +398,11 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
"is_federal": self.domain_request.is_federal(),
"modal_button": modal_button,
"modal_heading": modal_heading,
"has_profile_feature_flag": flag_is_active(self.request, "profile_feature"),
# Use the profile waffle feature flag to toggle profile features throughout domain requests
"has_profile_feature_flag": has_profile_flag,
"user": self.request.user,
}
return context
def get_step_list(self) -> list:
"""Dynamically generated list of steps in the form wizard."""

View file

@ -0,0 +1,79 @@
from django.http import JsonResponse
from django.core.paginator import Paginator
from registrar.models import DomainRequest
from django.utils.dateformat import format
from django.contrib.auth.decorators import login_required
from django.urls import reverse
@login_required
def get_domain_requests_json(request):
"""Given the current request,
get all domain requests that are associated with the request user and exclude the APPROVED ones"""
domain_requests = DomainRequest.objects.filter(creator=request.user).exclude(
status=DomainRequest.DomainRequestStatus.APPROVED
)
# Handle sorting
sort_by = request.GET.get("sort_by", "id") # Default to 'id'
order = request.GET.get("order", "asc") # Default to 'asc'
if order == "desc":
sort_by = f"-{sort_by}"
domain_requests = domain_requests.order_by(sort_by)
page_number = request.GET.get("page", 1)
paginator = Paginator(domain_requests, 10)
page_obj = paginator.get_page(page_number)
domain_requests_data = [
{
"requested_domain": domain_request.requested_domain.name if domain_request.requested_domain else None,
"submission_date": domain_request.submission_date,
"status": domain_request.get_status_display(),
"created_at": format(domain_request.created_at, "c"), # Serialize to ISO 8601
"id": domain_request.id,
"is_deletable": domain_request.status
in [DomainRequest.DomainRequestStatus.STARTED, DomainRequest.DomainRequestStatus.WITHDRAWN],
"action_url": (
reverse("edit-domain-request", kwargs={"id": domain_request.id})
if domain_request.status
in [
DomainRequest.DomainRequestStatus.STARTED,
DomainRequest.DomainRequestStatus.ACTION_NEEDED,
DomainRequest.DomainRequestStatus.WITHDRAWN,
]
else reverse("domain-request-status", kwargs={"pk": domain_request.id})
),
"action_label": (
"Edit"
if domain_request.status
in [
DomainRequest.DomainRequestStatus.STARTED,
DomainRequest.DomainRequestStatus.ACTION_NEEDED,
DomainRequest.DomainRequestStatus.WITHDRAWN,
]
else "Manage"
),
"svg_icon": (
"edit"
if domain_request.status
in [
DomainRequest.DomainRequestStatus.STARTED,
DomainRequest.DomainRequestStatus.ACTION_NEEDED,
DomainRequest.DomainRequestStatus.WITHDRAWN,
]
else "settings"
),
}
for domain_request in page_obj
]
return JsonResponse(
{
"domain_requests": domain_requests_data,
"has_next": page_obj.has_next(),
"has_previous": page_obj.has_previous(),
"page": page_obj.number,
"num_pages": paginator.num_pages,
"total": paginator.count,
}
)

View file

@ -0,0 +1,60 @@
from django.http import JsonResponse
from django.core.paginator import Paginator
from registrar.models import UserDomainRole, Domain
from django.contrib.auth.decorators import login_required
from django.urls import reverse
@login_required
def get_domains_json(request):
"""Given the current request,
get all domains that are associated with the UserDomainRole object"""
user_domain_roles = UserDomainRole.objects.filter(user=request.user)
domain_ids = user_domain_roles.values_list("domain_id", flat=True)
objects = Domain.objects.filter(id__in=domain_ids)
# Handle sorting
sort_by = request.GET.get("sort_by", "id") # Default to 'id'
order = request.GET.get("order", "asc") # Default to 'asc'
if sort_by == "state_display":
# Fetch the objects and sort them in Python
objects = list(objects) # Evaluate queryset to a list
objects.sort(key=lambda domain: domain.state_display(), reverse=(order == "desc"))
else:
if order == "desc":
sort_by = f"-{sort_by}"
objects = objects.order_by(sort_by)
paginator = Paginator(objects, 10)
page_number = request.GET.get("page")
page_obj = paginator.get_page(page_number)
# Convert objects to JSON-serializable format
domains = [
{
"id": domain.id,
"name": domain.name,
"expiration_date": domain.expiration_date,
"state": domain.state,
"state_display": domain.state_display(),
"get_state_help_text": domain.get_state_help_text(),
"action_url": reverse("domain", kwargs={"pk": domain.id}),
"action_label": ("View" if domain.state in [Domain.State.DELETED, Domain.State.ON_HOLD] else "Manage"),
"svg_icon": ("visibility" if domain.state in [Domain.State.DELETED, Domain.State.ON_HOLD] else "settings"),
}
for domain in page_obj.object_list
]
return JsonResponse(
{
"domains": domains,
"page": page_obj.number,
"num_pages": paginator.num_pages,
"has_previous": page_obj.has_previous(),
"has_next": page_obj.has_next(),
"total": paginator.count,
}
)

View file

@ -1,22 +1,18 @@
from django.shortcuts import render
from registrar.models import DomainRequest, Domain, UserDomainRole
from registrar.models import DomainRequest
from waffle.decorators import flag_is_active
def index(request):
"""This page is available to anyone without logging in."""
context = {}
if request.user.is_authenticated:
# Get all domain requests the user has access to
domain_requests, deletable_domain_requests = _get_domain_requests(request)
context["domain_requests"] = domain_requests
# Get all domains the user has access to
domains = _get_domains(request)
context["domains"] = domains
# Determine if the user will see domain requests that they can delete
has_deletable_domain_requests = deletable_domain_requests.exists()
context["has_deletable_domain_requests"] = has_deletable_domain_requests
@ -55,11 +51,3 @@ def _get_domain_requests(request):
deletable_domain_requests = domain_requests.filter(status__in=valid_statuses)
return (domain_requests, deletable_domain_requests)
def _get_domains(request):
"""Given the current request,
get all domains that are associated with the UserDomainRole object"""
user_domain_roles = UserDomainRole.objects.filter(user=request.user)
domain_ids = user_domain_roles.values_list("domain_id", flat=True)
return Domain.objects.filter(id__in=domain_ids)