mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-24 03:30:50 +02:00
merge main
This commit is contained in:
commit
a76779b2c2
29 changed files with 280 additions and 241 deletions
|
@ -163,6 +163,18 @@ class MyUserAdminForm(UserChangeForm):
|
||||||
"user_permissions": NoAutocompleteFilteredSelectMultiple("user_permissions", False),
|
"user_permissions": NoAutocompleteFilteredSelectMultiple("user_permissions", False),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Loads "tabtitle" for this admin page so that on render the <title>
|
||||||
|
# element will only have the model name instead of
|
||||||
|
# the default string loaded by native Django admin code.
|
||||||
|
# (Eg. instead of "Select contact to change", display "Contacts")
|
||||||
|
# see "base_site.html" for the <title> code.
|
||||||
|
def changelist_view(self, request, extra_context=None):
|
||||||
|
if extra_context is None:
|
||||||
|
extra_context = {}
|
||||||
|
extra_context["tabtitle"] = str(self.opts.verbose_name_plural).title()
|
||||||
|
# Get the filtered values
|
||||||
|
return super().changelist_view(request, extra_context=extra_context)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
"""Custom init to modify the user form"""
|
"""Custom init to modify the user form"""
|
||||||
super(MyUserAdminForm, self).__init__(*args, **kwargs)
|
super(MyUserAdminForm, self).__init__(*args, **kwargs)
|
||||||
|
@ -662,6 +674,18 @@ class CustomLogEntryAdmin(LogEntryAdmin):
|
||||||
"user_url",
|
"user_url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Loads "tabtitle" for this admin page so that on render the <title>
|
||||||
|
# element will only have the model name instead of
|
||||||
|
# the default string loaded by native Django admin code.
|
||||||
|
# (Eg. instead of "Select contact to change", display "Contacts")
|
||||||
|
# see "base_site.html" for the <title> code.
|
||||||
|
def changelist_view(self, request, extra_context=None):
|
||||||
|
if extra_context is None:
|
||||||
|
extra_context = {}
|
||||||
|
extra_context["tabtitle"] = str(self.opts.verbose_name_plural).title()
|
||||||
|
# Get the filtered values
|
||||||
|
return super().changelist_view(request, extra_context=extra_context)
|
||||||
|
|
||||||
# We name the custom prop 'resource' because linter
|
# We name the custom prop 'resource' because linter
|
||||||
# is not allowing a short_description attr on it
|
# is not allowing a short_description attr on it
|
||||||
# This gets around the linter limitation, for now.
|
# This gets around the linter limitation, for now.
|
||||||
|
@ -681,13 +705,6 @@ class CustomLogEntryAdmin(LogEntryAdmin):
|
||||||
change_form_template = "admin/change_form_no_submit.html"
|
change_form_template = "admin/change_form_no_submit.html"
|
||||||
add_form_template = "admin/change_form_no_submit.html"
|
add_form_template = "admin/change_form_no_submit.html"
|
||||||
|
|
||||||
# Select log entry to change -> Log entries
|
|
||||||
def changelist_view(self, request, extra_context=None):
|
|
||||||
if extra_context is None:
|
|
||||||
extra_context = {}
|
|
||||||
extra_context["tabtitle"] = "Log entries"
|
|
||||||
return super().changelist_view(request, extra_context=extra_context)
|
|
||||||
|
|
||||||
# #786: Skipping on updating audit log tab titles for now
|
# #786: Skipping on updating audit log tab titles for now
|
||||||
# def change_view(self, request, object_id, form_url="", extra_context=None):
|
# def change_view(self, request, object_id, form_url="", extra_context=None):
|
||||||
# if extra_context is None:
|
# if extra_context is None:
|
||||||
|
@ -768,6 +785,18 @@ class AdminSortFields:
|
||||||
class AuditedAdmin(admin.ModelAdmin):
|
class AuditedAdmin(admin.ModelAdmin):
|
||||||
"""Custom admin to make auditing easier."""
|
"""Custom admin to make auditing easier."""
|
||||||
|
|
||||||
|
# Loads "tabtitle" for this admin page so that on render the <title>
|
||||||
|
# element will only have the model name instead of
|
||||||
|
# the default string loaded by native Django admin code.
|
||||||
|
# (Eg. instead of "Select contact to change", display "Contacts")
|
||||||
|
# see "base_site.html" for the <title> code.
|
||||||
|
def changelist_view(self, request, extra_context=None):
|
||||||
|
if extra_context is None:
|
||||||
|
extra_context = {}
|
||||||
|
extra_context["tabtitle"] = str(self.opts.verbose_name_plural).title()
|
||||||
|
# Get the filtered values
|
||||||
|
return super().changelist_view(request, extra_context=extra_context)
|
||||||
|
|
||||||
def history_view(self, request, object_id, extra_context=None):
|
def history_view(self, request, object_id, extra_context=None):
|
||||||
"""On clicking 'History', take admin to the auditlog view for an object."""
|
"""On clicking 'History', take admin to the auditlog view for an object."""
|
||||||
return HttpResponseRedirect(
|
return HttpResponseRedirect(
|
||||||
|
@ -1168,6 +1197,18 @@ class MyUserAdmin(BaseUserAdmin, ImportExportModelAdmin):
|
||||||
extra_context = {"domain_requests": domain_requests, "domains": domains, "portfolios": portfolios}
|
extra_context = {"domain_requests": domain_requests, "domains": domains, "portfolios": portfolios}
|
||||||
return super().change_view(request, object_id, form_url, extra_context)
|
return super().change_view(request, object_id, form_url, extra_context)
|
||||||
|
|
||||||
|
# Loads "tabtitle" for this admin page so that on render the <title>
|
||||||
|
# element will only have the model name instead of
|
||||||
|
# the default string loaded by native Django admin code.
|
||||||
|
# (Eg. instead of "Select contact to change", display "Contacts")
|
||||||
|
# see "base_site.html" for the <title> code.
|
||||||
|
def changelist_view(self, request, extra_context=None):
|
||||||
|
if extra_context is None:
|
||||||
|
extra_context = {}
|
||||||
|
extra_context["tabtitle"] = str(self.opts.verbose_name_plural).title()
|
||||||
|
# Get the filtered values
|
||||||
|
return super().changelist_view(request, extra_context=extra_context)
|
||||||
|
|
||||||
|
|
||||||
class HostIPInline(admin.StackedInline):
|
class HostIPInline(admin.StackedInline):
|
||||||
"""Edit an ip address on the host page."""
|
"""Edit an ip address on the host page."""
|
||||||
|
@ -1192,14 +1233,6 @@ class MyHostAdmin(AuditedAdmin, ImportExportModelAdmin):
|
||||||
search_help_text = "Search by domain or host name."
|
search_help_text = "Search by domain or host name."
|
||||||
inlines = [HostIPInline]
|
inlines = [HostIPInline]
|
||||||
|
|
||||||
# Select host to change -> Host
|
|
||||||
def changelist_view(self, request, extra_context=None):
|
|
||||||
if extra_context is None:
|
|
||||||
extra_context = {}
|
|
||||||
extra_context["tabtitle"] = "Host"
|
|
||||||
# Get the filtered values
|
|
||||||
return super().changelist_view(request, extra_context=extra_context)
|
|
||||||
|
|
||||||
|
|
||||||
class HostIpResource(resources.ModelResource):
|
class HostIpResource(resources.ModelResource):
|
||||||
"""defines how each field in the referenced model should be mapped to the corresponding fields in the
|
"""defines how each field in the referenced model should be mapped to the corresponding fields in the
|
||||||
|
@ -1215,14 +1248,6 @@ class HostIpAdmin(AuditedAdmin, ImportExportModelAdmin):
|
||||||
resource_classes = [HostIpResource]
|
resource_classes = [HostIpResource]
|
||||||
model = models.HostIP
|
model = models.HostIP
|
||||||
|
|
||||||
# Select host ip to change -> Host ip
|
|
||||||
def changelist_view(self, request, extra_context=None):
|
|
||||||
if extra_context is None:
|
|
||||||
extra_context = {}
|
|
||||||
extra_context["tabtitle"] = "Host IP"
|
|
||||||
# Get the filtered values
|
|
||||||
return super().changelist_view(request, extra_context=extra_context)
|
|
||||||
|
|
||||||
|
|
||||||
class ContactResource(resources.ModelResource):
|
class ContactResource(resources.ModelResource):
|
||||||
"""defines how each field in the referenced model should be mapped to the corresponding fields in the
|
"""defines how each field in the referenced model should be mapped to the corresponding fields in the
|
||||||
|
@ -1344,14 +1369,6 @@ class ContactAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||||
|
|
||||||
return super().change_view(request, object_id, form_url, extra_context=extra_context)
|
return super().change_view(request, object_id, form_url, extra_context=extra_context)
|
||||||
|
|
||||||
# Select contact to change -> Contacts
|
|
||||||
def changelist_view(self, request, extra_context=None):
|
|
||||||
if extra_context is None:
|
|
||||||
extra_context = {}
|
|
||||||
extra_context["tabtitle"] = "Contacts"
|
|
||||||
# Get the filtered values
|
|
||||||
return super().changelist_view(request, extra_context=extra_context)
|
|
||||||
|
|
||||||
def save_model(self, request, obj, form, change):
|
def save_model(self, request, obj, form, change):
|
||||||
# Clear warning messages before saving
|
# Clear warning messages before saving
|
||||||
storage = messages.get_messages(request)
|
storage = messages.get_messages(request)
|
||||||
|
@ -1667,14 +1684,6 @@ class DomainInvitationAdmin(BaseInvitationAdmin):
|
||||||
# Override for the delete confirmation page on the domain table (bulk delete action)
|
# Override for the delete confirmation page on the domain table (bulk delete action)
|
||||||
delete_selected_confirmation_template = "django/admin/domain_invitation_delete_selected_confirmation.html"
|
delete_selected_confirmation_template = "django/admin/domain_invitation_delete_selected_confirmation.html"
|
||||||
|
|
||||||
# Select domain invitations to change -> Domain invitations
|
|
||||||
def changelist_view(self, request, extra_context=None):
|
|
||||||
if extra_context is None:
|
|
||||||
extra_context = {}
|
|
||||||
extra_context["tabtitle"] = "Domain invitations"
|
|
||||||
# Get the filtered values
|
|
||||||
return super().changelist_view(request, extra_context=extra_context)
|
|
||||||
|
|
||||||
def change_view(self, request, object_id, form_url="", extra_context=None):
|
def change_view(self, request, object_id, form_url="", extra_context=None):
|
||||||
"""Override the change_view to add the invitation obj for the change_form_object_tools template"""
|
"""Override the change_view to add the invitation obj for the change_form_object_tools template"""
|
||||||
|
|
||||||
|
@ -1819,14 +1828,6 @@ class PortfolioInvitationAdmin(BaseInvitationAdmin):
|
||||||
|
|
||||||
get_roles.short_description = "Member access" # type: ignore
|
get_roles.short_description = "Member access" # type: ignore
|
||||||
|
|
||||||
# Select portfolio invitations to change -> Portfolio invitations
|
|
||||||
def changelist_view(self, request, extra_context=None):
|
|
||||||
if extra_context is None:
|
|
||||||
extra_context = {}
|
|
||||||
extra_context["tabtitle"] = "Portfolio invitations"
|
|
||||||
# Get the filtered values
|
|
||||||
return super().changelist_view(request, extra_context=extra_context)
|
|
||||||
|
|
||||||
def save_model(self, request, obj, form, change):
|
def save_model(self, request, obj, form, change):
|
||||||
"""
|
"""
|
||||||
Override the save_model method.
|
Override the save_model method.
|
||||||
|
@ -2216,14 +2217,6 @@ class DomainInformationAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||||
readonly_fields.extend([field for field in self.analyst_readonly_fields])
|
readonly_fields.extend([field for field in self.analyst_readonly_fields])
|
||||||
return readonly_fields # Read-only fields for analysts
|
return readonly_fields # Read-only fields for analysts
|
||||||
|
|
||||||
# Select domain information to change -> Domain information
|
|
||||||
def changelist_view(self, request, extra_context=None):
|
|
||||||
if extra_context is None:
|
|
||||||
extra_context = {}
|
|
||||||
extra_context["tabtitle"] = "Domain information"
|
|
||||||
# Get the filtered values
|
|
||||||
return super().changelist_view(request, extra_context=extra_context)
|
|
||||||
|
|
||||||
def formfield_for_foreignkey(self, db_field, request, **kwargs):
|
def formfield_for_foreignkey(self, db_field, request, **kwargs):
|
||||||
"""Customize the behavior of formfields with foreign key relationships. This will customize
|
"""Customize the behavior of formfields with foreign key relationships. This will customize
|
||||||
the behavior of selects. Customized behavior includes sorting of objects in list."""
|
the behavior of selects. Customized behavior includes sorting of objects in list."""
|
||||||
|
@ -3044,11 +3037,6 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||||
if next_char.isdigit():
|
if next_char.isdigit():
|
||||||
should_apply_default_filter = True
|
should_apply_default_filter = True
|
||||||
|
|
||||||
# Select domain request to change -> Domain requests
|
|
||||||
if extra_context is None:
|
|
||||||
extra_context = {}
|
|
||||||
extra_context["tabtitle"] = "Domain requests"
|
|
||||||
|
|
||||||
if should_apply_default_filter:
|
if should_apply_default_filter:
|
||||||
# modify the GET of the request to set the selected filter
|
# modify the GET of the request to set the selected filter
|
||||||
modified_get = copy.deepcopy(request.GET)
|
modified_get = copy.deepcopy(request.GET)
|
||||||
|
@ -4105,14 +4093,6 @@ class DraftDomainAdmin(ListHeaderAdmin, ImportExportModelAdmin):
|
||||||
# If no redirection is needed, return the original response
|
# If no redirection is needed, return the original response
|
||||||
return response
|
return response
|
||||||
|
|
||||||
# Select draft domain to change -> Draft domains
|
|
||||||
def changelist_view(self, request, extra_context=None):
|
|
||||||
if extra_context is None:
|
|
||||||
extra_context = {}
|
|
||||||
extra_context["tabtitle"] = "Draft domains"
|
|
||||||
# Get the filtered values
|
|
||||||
return super().changelist_view(request, extra_context=extra_context)
|
|
||||||
|
|
||||||
|
|
||||||
class PublicContactResource(resources.ModelResource):
|
class PublicContactResource(resources.ModelResource):
|
||||||
"""defines how each field in the referenced model should be mapped to the corresponding fields in the
|
"""defines how each field in the referenced model should be mapped to the corresponding fields in the
|
||||||
|
@ -4534,14 +4514,6 @@ class UserGroupAdmin(AuditedAdmin):
|
||||||
def user_group(self, obj):
|
def user_group(self, obj):
|
||||||
return obj.name
|
return obj.name
|
||||||
|
|
||||||
# Select user groups to change -> User groups
|
|
||||||
def changelist_view(self, request, extra_context=None):
|
|
||||||
if extra_context is None:
|
|
||||||
extra_context = {}
|
|
||||||
extra_context["tabtitle"] = "User groups"
|
|
||||||
# Get the filtered values
|
|
||||||
return super().changelist_view(request, extra_context=extra_context)
|
|
||||||
|
|
||||||
|
|
||||||
class WaffleFlagAdmin(FlagAdmin):
|
class WaffleFlagAdmin(FlagAdmin):
|
||||||
"""Custom admin implementation of django-waffle's Flag class"""
|
"""Custom admin implementation of django-waffle's Flag class"""
|
||||||
|
@ -4558,6 +4530,13 @@ class WaffleFlagAdmin(FlagAdmin):
|
||||||
if extra_context is None:
|
if extra_context is None:
|
||||||
extra_context = {}
|
extra_context = {}
|
||||||
extra_context["dns_prototype_flag"] = flag_is_active_for_user(request.user, "dns_prototype_flag")
|
extra_context["dns_prototype_flag"] = flag_is_active_for_user(request.user, "dns_prototype_flag")
|
||||||
|
|
||||||
|
# Loads "tabtitle" for this admin page so that on render the <title>
|
||||||
|
# element will only have the model name instead of
|
||||||
|
# the default string loaded by native Django admin code.
|
||||||
|
# (Eg. instead of "Select waffle flags to change", display "Waffle Flags")
|
||||||
|
# see "base_site.html" for the <title> code.
|
||||||
|
extra_context["tabtitle"] = str(self.opts.verbose_name_plural).title()
|
||||||
return super().changelist_view(request, extra_context=extra_context)
|
return super().changelist_view(request, extra_context=extra_context)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5284,7 +5284,10 @@ const setUpModal = baseComponent => {
|
||||||
overlayDiv.classList.add(OVERLAY_CLASSNAME);
|
overlayDiv.classList.add(OVERLAY_CLASSNAME);
|
||||||
|
|
||||||
// Set attributes
|
// Set attributes
|
||||||
modalWrapper.setAttribute("role", "dialog");
|
// DOTGOV
|
||||||
|
// Removes the dialog role as this causes a double readout bug with screenreaders
|
||||||
|
// modalWrapper.setAttribute("role", "dialog");
|
||||||
|
// END DOTGOV
|
||||||
modalWrapper.setAttribute("id", modalID);
|
modalWrapper.setAttribute("id", modalID);
|
||||||
if (ariaLabelledBy) {
|
if (ariaLabelledBy) {
|
||||||
modalWrapper.setAttribute("aria-labelledby", ariaLabelledBy);
|
modalWrapper.setAttribute("aria-labelledby", ariaLabelledBy);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { hideElement, showElement, addOrRemoveSessionBoolean } from './helpers-admin.js';
|
import { hideElement, showElement, addOrRemoveSessionBoolean, announceForScreenReaders } from './helpers-admin.js';
|
||||||
import { handlePortfolioSelection } from './helpers-portfolio-dynamic-fields.js';
|
import { handlePortfolioSelection } from './helpers-portfolio-dynamic-fields.js';
|
||||||
|
|
||||||
function displayModalOnDropdownClick(linkClickedDisplaysModal, statusDropdown, actionButton, valueToCheck){
|
function displayModalOnDropdownClick(linkClickedDisplaysModal, statusDropdown, actionButton, valueToCheck){
|
||||||
|
@ -684,3 +684,33 @@ export function initDynamicDomainRequestFields(){
|
||||||
handleSuborgFieldsAndButtons();
|
handleSuborgFieldsAndButtons();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function initFilterFocusListeners() {
|
||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
let filters = document.querySelectorAll("#changelist-filter li a"); // Get list of all filter links
|
||||||
|
let clickedFilter = false; // Used to determine if we are truly navigating away or not
|
||||||
|
|
||||||
|
// Restore focus from localStorage
|
||||||
|
let lastClickedFilterId = localStorage.getItem("admin_filter_focus_id");
|
||||||
|
if (lastClickedFilterId) {
|
||||||
|
let focusedElement = document.getElementById(lastClickedFilterId);
|
||||||
|
if (focusedElement) {
|
||||||
|
//Focus the element
|
||||||
|
focusedElement.setAttribute("tabindex", "0");
|
||||||
|
focusedElement.focus({ preventScroll: true });
|
||||||
|
|
||||||
|
// Announce focus change for screen readers
|
||||||
|
announceForScreenReaders("Filter refocused on " + focusedElement.textContent);
|
||||||
|
localStorage.removeItem("admin_filter_focus_id");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Capture clicked filter and store its ID
|
||||||
|
filters.forEach(filter => {
|
||||||
|
filter.addEventListener("click", function() {
|
||||||
|
localStorage.setItem("admin_filter_focus_id", this.id);
|
||||||
|
clickedFilter = true; // Mark that a filter was clicked
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -32,3 +32,22 @@ export function getParameterByName(name, url) {
|
||||||
if (!results[2]) return '';
|
if (!results[2]) return '';
|
||||||
return decodeURIComponent(results[2].replace(/\+/g, ' '));
|
return decodeURIComponent(results[2].replace(/\+/g, ' '));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a temporary live region to announce messages for screen readers.
|
||||||
|
*/
|
||||||
|
export function announceForScreenReaders(message) {
|
||||||
|
let liveRegion = document.createElement("div");
|
||||||
|
liveRegion.setAttribute("aria-live", "assertive");
|
||||||
|
liveRegion.setAttribute("role", "alert");
|
||||||
|
liveRegion.setAttribute("class", "usa-sr-only");
|
||||||
|
document.body.appendChild(liveRegion);
|
||||||
|
|
||||||
|
// Delay the update slightly to ensure it's recognized
|
||||||
|
setTimeout(() => {
|
||||||
|
liveRegion.textContent = message;
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.removeChild(liveRegion);
|
||||||
|
}, 1000);
|
||||||
|
}, 100);
|
||||||
|
}
|
|
@ -10,7 +10,8 @@ import {
|
||||||
initRejectedEmail,
|
initRejectedEmail,
|
||||||
initApprovedDomain,
|
initApprovedDomain,
|
||||||
initCopyRequestSummary,
|
initCopyRequestSummary,
|
||||||
initDynamicDomainRequestFields } from './domain-request-form.js';
|
initDynamicDomainRequestFields,
|
||||||
|
initFilterFocusListeners } from './domain-request-form.js';
|
||||||
import { initDomainFormTargetBlankButtons } from './domain-form.js';
|
import { initDomainFormTargetBlankButtons } from './domain-form.js';
|
||||||
import { initDynamicPortfolioFields } from './portfolio-form.js';
|
import { initDynamicPortfolioFields } from './portfolio-form.js';
|
||||||
import { initDynamicPortfolioPermissionFields } from './portfolio-permissions-form.js'
|
import { initDynamicPortfolioPermissionFields } from './portfolio-permissions-form.js'
|
||||||
|
@ -35,6 +36,7 @@ initRejectedEmail();
|
||||||
initApprovedDomain();
|
initApprovedDomain();
|
||||||
initCopyRequestSummary();
|
initCopyRequestSummary();
|
||||||
initDynamicDomainRequestFields();
|
initDynamicDomainRequestFields();
|
||||||
|
initFilterFocusListeners();
|
||||||
|
|
||||||
// Domain
|
// Domain
|
||||||
initDomainFormTargetBlankButtons();
|
initDomainFormTargetBlankButtons();
|
||||||
|
|
|
@ -292,7 +292,18 @@ export function initFormsetsForms() {
|
||||||
// For the other contacts form, we need to update the fieldset headers based on what's visible vs hidden,
|
// For the other contacts form, we need to update the fieldset headers based on what's visible vs hidden,
|
||||||
// since the form on the backend employs Django's DELETE widget.
|
// since the form on the backend employs Django's DELETE widget.
|
||||||
let totalShownForms = document.querySelectorAll(`.repeatable-form:not([style*="display: none"])`).length;
|
let totalShownForms = document.querySelectorAll(`.repeatable-form:not([style*="display: none"])`).length;
|
||||||
newForm.innerHTML = newForm.innerHTML.replace(formLabelRegex, `${formLabel} ${totalShownForms + 1}`);
|
let newFormCount = totalShownForms + 1;
|
||||||
|
// update the header
|
||||||
|
let header = newForm.querySelector('legend h3');
|
||||||
|
header.textContent = `${formLabel} ${newFormCount}`;
|
||||||
|
header.id = `org-contact-${newFormCount}`;
|
||||||
|
// update accessibility elements on the delete buttons
|
||||||
|
let deleteDescription = newForm.querySelector('.delete-button-description');
|
||||||
|
deleteDescription.textContent = 'Delete new contact';
|
||||||
|
deleteDescription.id = `org-contact-${newFormCount}__name`;
|
||||||
|
let deleteButton = newForm.querySelector('button');
|
||||||
|
deleteButton.setAttribute("aria-labelledby", header.id);
|
||||||
|
deleteButton.setAttribute("aria-describedby", deleteDescription.id);
|
||||||
} else {
|
} else {
|
||||||
// Nameservers form is cloned from index 2 which has the word optional on init, does not have the word optional
|
// Nameservers form is cloned from index 2 which has the word optional on init, does not have the word optional
|
||||||
// if indices 0 or 1 have been deleted
|
// if indices 0 or 1 have been deleted
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { initDomainManagersPage } from './domain-managers.js';
|
||||||
import { initDomainDSData } from './domain-dsdata.js';
|
import { initDomainDSData } from './domain-dsdata.js';
|
||||||
import { initDomainDNSSEC } from './domain-dnssec.js';
|
import { initDomainDNSSEC } from './domain-dnssec.js';
|
||||||
import { initFormErrorHandling } from './form-errors.js';
|
import { initFormErrorHandling } from './form-errors.js';
|
||||||
|
import { initButtonLinks } from '../getgov-admin/button-utils.js';
|
||||||
|
|
||||||
initDomainValidators();
|
initDomainValidators();
|
||||||
|
|
||||||
|
@ -49,3 +50,5 @@ initFormErrorHandling();
|
||||||
initPortfolioMemberPageRadio();
|
initPortfolioMemberPageRadio();
|
||||||
initPortfolioNewMemberPageToggle();
|
initPortfolioNewMemberPageToggle();
|
||||||
initAddNewMemberPageListeners();
|
initAddNewMemberPageListeners();
|
||||||
|
|
||||||
|
initButtonLinks();
|
||||||
|
|
|
@ -25,7 +25,6 @@
|
||||||
// Note, width is determined by a custom width class on one of the children
|
// Note, width is determined by a custom width class on one of the children
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
left: 0;
|
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: solid 1px color('base-lighter');
|
border: solid 1px color('base-lighter');
|
||||||
padding: units(2) units(2) units(3) units(2);
|
padding: units(2) units(2) units(3) units(2);
|
||||||
|
@ -42,6 +41,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This will work in responsive tables if we overwrite the overflow value on the table container
|
||||||
|
// Works with styles in _tables
|
||||||
|
@include at-media(desktop) {
|
||||||
|
.usa-accordion--more-actions .usa-accordion__content {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.usa-accordion--select .usa-accordion__content {
|
.usa-accordion--select .usa-accordion__content {
|
||||||
top: 33.88px;
|
top: 33.88px;
|
||||||
}
|
}
|
||||||
|
@ -59,10 +66,12 @@
|
||||||
// This won't work on the Members table rows because that table has show-more rows
|
// This won't work on the Members table rows because that table has show-more rows
|
||||||
// Currently, that's not an issue since that Members table is not wrapped in the
|
// Currently, that's not an issue since that Members table is not wrapped in the
|
||||||
// reponsive wrapper.
|
// reponsive wrapper.
|
||||||
tr:last-of-type .usa-accordion--more-actions .usa-accordion__content {
|
@include at-media-max("desktop") {
|
||||||
top: auto;
|
tr:last-of-type .usa-accordion--more-actions .usa-accordion__content {
|
||||||
bottom: -10px;
|
top: auto;
|
||||||
right: 30px;
|
bottom: -10px;
|
||||||
|
right: 30px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A CSS only show-more/show-less based on usa-accordion
|
// A CSS only show-more/show-less based on usa-accordion
|
||||||
|
|
|
@ -226,11 +226,6 @@ abbr[title] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Boost this USWDS utility class for the accordions in the portfolio requests table
|
|
||||||
.left-auto {
|
|
||||||
left: auto!important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.usa-banner__inner--widescreen {
|
.usa-banner__inner--widescreen {
|
||||||
max-width: $widescreen-max-width;
|
max-width: $widescreen-max-width;
|
||||||
}
|
}
|
||||||
|
|
|
@ -152,3 +152,12 @@ th {
|
||||||
.usa-table--full-borderless th {
|
.usa-table--full-borderless th {
|
||||||
border: none !important;
|
border: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is an override to overflow on certain tables (note the custom class)
|
||||||
|
// so that a popup menu can appear and starddle the edge of the table on large
|
||||||
|
// screen sizes. Works with styles in _accordions
|
||||||
|
@include at-media(desktop) {
|
||||||
|
.usa-table-container--scrollable.usa-table-container--override-overflow {
|
||||||
|
overflow-y: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -315,7 +315,7 @@ class DomainRequestFixture:
|
||||||
cls._create_domain_requests(users)
|
cls._create_domain_requests(users)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _create_domain_requests(cls, users): # noqa: C901
|
def _create_domain_requests(cls, users, total_requests=None): # noqa: C901
|
||||||
"""Creates DomainRequests given a list of users."""
|
"""Creates DomainRequests given a list of users."""
|
||||||
total_domain_requests_to_make = len(users) # 100000
|
total_domain_requests_to_make = len(users) # 100000
|
||||||
|
|
||||||
|
@ -323,27 +323,33 @@ class DomainRequestFixture:
|
||||||
# number of entries.
|
# number of entries.
|
||||||
# (Prevents re-adding more entries to an already populated database,
|
# (Prevents re-adding more entries to an already populated database,
|
||||||
# which happens when restarting Docker src)
|
# which happens when restarting Docker src)
|
||||||
domain_requests_already_made = DomainRequest.objects.count()
|
total_existing_requests = DomainRequest.objects.count()
|
||||||
|
|
||||||
domain_requests_to_create = []
|
domain_requests_to_create = []
|
||||||
if domain_requests_already_made < total_domain_requests_to_make:
|
if total_requests and total_requests <= total_existing_requests:
|
||||||
for user in users:
|
total_domain_requests_to_make = total_requests - total_existing_requests
|
||||||
for request_data in cls.DOMAINREQUESTS:
|
if total_domain_requests_to_make >= 0:
|
||||||
# Prepare DomainRequest objects
|
DomainRequest.objects.filter(
|
||||||
try:
|
id__in=list(DomainRequest.objects.values_list("pk", flat=True)[:total_domain_requests_to_make])
|
||||||
domain_request = DomainRequest(
|
).delete()
|
||||||
creator=user,
|
if total_domain_requests_to_make == 0:
|
||||||
organization_name=request_data["organization_name"],
|
return
|
||||||
)
|
|
||||||
cls._set_non_foreign_key_fields(domain_request, request_data)
|
|
||||||
cls._set_foreign_key_fields(domain_request, request_data, user)
|
|
||||||
domain_requests_to_create.append(domain_request)
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(e)
|
|
||||||
|
|
||||||
num_additional_requests_to_make = (
|
for user in users:
|
||||||
total_domain_requests_to_make - domain_requests_already_made - len(domain_requests_to_create)
|
for request_data in cls.DOMAINREQUESTS:
|
||||||
)
|
# Prepare DomainRequest objects
|
||||||
|
try:
|
||||||
|
domain_request = DomainRequest(
|
||||||
|
creator=user,
|
||||||
|
organization_name=request_data["organization_name"],
|
||||||
|
)
|
||||||
|
cls._set_non_foreign_key_fields(domain_request, request_data)
|
||||||
|
cls._set_foreign_key_fields(domain_request, request_data, user)
|
||||||
|
domain_requests_to_create.append(domain_request)
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(e)
|
||||||
|
|
||||||
|
num_additional_requests_to_make = total_domain_requests_to_make - len(domain_requests_to_create)
|
||||||
if num_additional_requests_to_make > 0:
|
if num_additional_requests_to_make > 0:
|
||||||
for _ in range(num_additional_requests_to_make):
|
for _ in range(num_additional_requests_to_make):
|
||||||
random_user = random.choice(users) # nosec
|
random_user = random.choice(users) # nosec
|
||||||
|
|
|
@ -2,6 +2,10 @@
|
||||||
{% load static %}
|
{% load static %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Registrar Analytics | Django admin
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
{% block content_title %}<h1>Registrar Analytics</h1>{% endblock %}
|
{% block content_title %}<h1>Registrar Analytics</h1>{% endblock %}
|
||||||
|
|
||||||
{% block breadcrumbs %}
|
{% block breadcrumbs %}
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ title }} |
|
{{ title }} |
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{{ site_title|default:_('Django site admin') }}
|
Django admin
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extrastyle %}{{ block.super }}
|
{% block extrastyle %}{{ block.super }}
|
||||||
|
|
13
src/registrar/templates/admin/filter.html
Normal file
13
src/registrar/templates/admin/filter.html
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{% comment %} Override of this file: https://github.com/django/django/blob/main/django/contrib/admin/templates/admin/filter.html {% endcomment %}
|
||||||
|
{% load i18n %}
|
||||||
|
<details data-filter-title="{{ title }}" open>
|
||||||
|
<summary>
|
||||||
|
{% blocktranslate with filter_title=title %} By {{ filter_title }} {% endblocktranslate %}
|
||||||
|
</summary>
|
||||||
|
<ul>
|
||||||
|
{% for choice in choices %}
|
||||||
|
<li {% if choice.selected %} class="selected"{% endif %}>
|
||||||
|
<a id="{{ title|lower|cut:' ' }}-filter-{{ choice.display|slugify }}" href="{{ choice.query_string|iriencode }}">{{ choice.display }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</details>
|
|
@ -9,16 +9,12 @@
|
||||||
{% 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 id="{{ title|lower|cut:' ' }}-filter-{{ choice.display|slugify }}" href="{{ choice.query_string|iriencode }}" title="{{ choice.display }}">{{ choice.display }}</a>
|
||||||
</li>
|
</li>
|
||||||
{% endif %}
|
{% else %}
|
||||||
{% endfor %}
|
<li{% if choice.selected %} class="selected"{% endif %}>
|
||||||
|
|
||||||
{% for choice in choices %}
|
|
||||||
{% if not choice.reset %}
|
|
||||||
<li{% if choice.selected %} class="selected"{% endif %}">
|
|
||||||
{% if choice.selected and choice.exclude_query_string %}
|
{% if choice.selected and choice.exclude_query_string %}
|
||||||
<a role="menuitemcheckbox" class="choice-filter choice-filter--checked" href="{{ choice.exclude_query_string|iriencode }}">{{ choice.display }}
|
<a id="{{ title|lower|cut:' ' }}-filter-{{ choice.display|slugify }}" role="menuitemcheckbox" 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">
|
||||||
<use xlink:href="{%static 'img/sprite.svg'%}#check_box_outline_blank"></use>
|
<use xlink:href="{%static 'img/sprite.svg'%}#check_box_outline_blank"></use>
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -26,9 +22,8 @@
|
||||||
<use xlink:href="{%static 'img/sprite.svg'%}#check"></use>
|
<use xlink:href="{%static 'img/sprite.svg'%}#check"></use>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% elif not choice.selected and choice.include_query_string %}
|
||||||
{% if not choice.selected and choice.include_query_string %}
|
<a id="{{ title|lower|cut:' ' }}-filter-{{ choice.display|slugify }}" role="menuitemcheckbox" class="choice-filter" href="{{ choice.include_query_string|iriencode }}">{{ choice.display }}
|
||||||
<a role="menuitemcheckbox" class="choice-filter" href="{{ choice.include_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">
|
||||||
<use xlink:href="{%static 'img/sprite.svg'%}#check_box_outline_blank"></use>
|
<use xlink:href="{%static 'img/sprite.svg'%}#check_box_outline_blank"></use>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
|
@ -29,7 +29,10 @@
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
{% if domain.domain_info.generic_org_type == 'federal' %}
|
{% if domain.domain_info.generic_org_type == 'federal' %}
|
||||||
{% input_with_errors form.federal_agency %}
|
<h4 class="margin-bottom-05">Federal Agency</h4>
|
||||||
|
<p class="margin-top-0">
|
||||||
|
{{ domain.domain_info.federal_agency }}
|
||||||
|
</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% input_with_errors form.organization_name %}
|
{% input_with_errors form.organization_name %}
|
||||||
|
|
|
@ -61,7 +61,7 @@
|
||||||
|
|
||||||
<fieldset class="usa-fieldset margin-top-1 dotgov-domain-form" id="form-container">
|
<fieldset class="usa-fieldset margin-top-1 dotgov-domain-form" id="form-container">
|
||||||
<legend>
|
<legend>
|
||||||
<h2>Alternative domains (optional)</h2>
|
<h2 id="alternative-domains-title">Alternative domains (optional)</h2>
|
||||||
</legend>
|
</legend>
|
||||||
|
|
||||||
<p id="alt_domain_instructions" class="margin-top-05">Are there other domains you’d like if we can’t give
|
<p id="alt_domain_instructions" class="margin-top-05">Are there other domains you’d like if we can’t give
|
||||||
|
@ -80,18 +80,22 @@
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
<button type="button" value="save" class="usa-button usa-button--unstyled usa-button--with-icon" id="add-form">
|
<div class="usa-sr-only" id="alternative-domains__add-another-alternative">Add another alternative domain</div>
|
||||||
|
<button aria-labelledby="alternative-domains-title" aria-describedby="alternative-domains__add-another-alternative" type="button" value="save" class="usa-button usa-button--unstyled usa-button--with-icon" id="add-form">
|
||||||
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24" height="24">
|
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24" height="24">
|
||||||
<use xlink:href="{%static 'img/sprite.svg'%}#add_circle"></use>
|
<use xlink:href="{%static 'img/sprite.svg'%}#add_circle"></use>
|
||||||
</svg><span class="margin-left-05">Add another alternative</span>
|
</svg><span class="margin-left-05">Add another alternative</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div class="margin-bottom-3">
|
<div class="margin-bottom-3">
|
||||||
|
<div class="usa-sr-only" id="alternative-domains__check-availability">Check domain availability</div>
|
||||||
<button
|
<button
|
||||||
id="validate-alt-domains-availability"
|
id="validate-alt-domains-availability"
|
||||||
type="button"
|
type="button"
|
||||||
class="usa-button usa-button--outline"
|
class="usa-button usa-button--outline"
|
||||||
validate-for="{{ forms.1.requested_domain.auto_id }}"
|
validate-for="{{ forms.1.requested_domain.auto_id }}"
|
||||||
|
aria-labelledby="alternative-domains-title"
|
||||||
|
aria-describedby="alternative-domains__check-availability"
|
||||||
>Check availability</button>
|
>Check availability</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -31,10 +31,14 @@
|
||||||
<fieldset class="usa-fieldset repeatable-form padding-y-1">
|
<fieldset class="usa-fieldset repeatable-form padding-y-1">
|
||||||
|
|
||||||
<legend class="float-left-tablet">
|
<legend class="float-left-tablet">
|
||||||
<h3 class="margin-top-05">Organization contact {{ forloop.counter }}</h2>
|
<h3 class="margin-top-05" id="org-contact-{{ forloop.counter }}">Organization contact {{ forloop.counter }}</h2>
|
||||||
</legend>
|
</legend>
|
||||||
|
{% if form.first_name or form.last_name %}
|
||||||
<button type="button" class="usa-button usa-button--unstyled display-block float-right-tablet delete-record margin-top-1 text-secondary line-height-sans-5 usa-button--with-icon">
|
<span class="usa-sr-only delete-button-description" id="org-contact-{{ forloop.counter }}__name">Delete {{form.first_name.value }} {{ form.last_name.value }}</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="usa-sr-only" id="org-contact-{{ forloop.counter }}__name">Delete new contact</span>
|
||||||
|
{% endif %}
|
||||||
|
<button aria-labelledby="org-contact-{{ forloop.counter }}" aria-describedby="org-contact-{{ forloop.counter }}__name" type="button" class="usa-button usa-button--unstyled display-block float-right-tablet delete-record margin-top-1 text-secondary line-height-sans-5 usa-button--with-icon">
|
||||||
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24" height="24">
|
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24" height="24">
|
||||||
<use xlink:href="{%static 'img/sprite.svg'%}#delete"></use>
|
<use xlink:href="{%static 'img/sprite.svg'%}#delete"></use>
|
||||||
</svg>Delete
|
</svg>Delete
|
||||||
|
|
|
@ -18,10 +18,10 @@
|
||||||
<h1>Manage your domains</h1>
|
<h1>Manage your domains</h1>
|
||||||
|
|
||||||
<p class="margin-top-4">
|
<p class="margin-top-4">
|
||||||
<a href="{% url 'domain-request:start' %}" class="usa-button"
|
<button data-href="{% url 'domain-request:start' %}" class="usa-button use-button-as-link"
|
||||||
>
|
>
|
||||||
Start a new domain request
|
Start a new domain request
|
||||||
</a>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{% include "includes/domains_table.html" with user_domain_count=user_domain_count %}
|
{% include "includes/domains_table.html" with user_domain_count=user_domain_count %}
|
||||||
|
|
|
@ -14,22 +14,15 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="section-outlined__search section-outlined__search--widescreen {% if portfolio %}mobile:grid-col-12 desktop:grid-col-6{% 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" id="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 %}
|
||||||
<button class="usa-button usa-button--unstyled margin-right-3 display-none" id="domain-requests__reset-search" type="button">
|
<button class="usa-button usa-button--unstyled margin-right-3 display-none" id="domain-requests__reset-search" type="button" aria-labelledby="domain-requests-search-component">
|
||||||
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24">
|
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24">
|
||||||
<use xlink:href="{%static 'img/sprite.svg'%}#close"></use>
|
<use xlink:href="{%static 'img/sprite.svg'%}#close"></use>
|
||||||
</svg>
|
</svg>
|
||||||
Reset
|
Reset
|
||||||
</button>
|
</button>
|
||||||
<label id="domain-requests__search-label" class="usa-sr-only" for="domain-requests__search-field">
|
|
||||||
{% if portfolio %}
|
|
||||||
Search by domain name or creator
|
|
||||||
{% else %}
|
|
||||||
Search by domain name
|
|
||||||
{% endif %}
|
|
||||||
</label>
|
|
||||||
<input
|
<input
|
||||||
class="usa-input"
|
class="usa-input"
|
||||||
id="domain-requests__search-field"
|
id="domain-requests__search-field"
|
||||||
|
@ -40,8 +33,10 @@
|
||||||
{% else %}
|
{% else %}
|
||||||
placeholder="Search by domain name"
|
placeholder="Search by domain name"
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
aria-labelledby="domain-requests-search-component"
|
||||||
/>
|
/>
|
||||||
<button class="usa-button" type="submit" id="domain-requests__search-field-submit" aria-labelledby="domain-requests__search-label">
|
<div class="usa-sr-only" id="domain-requests-search-button__description">Click to search</div>
|
||||||
|
<button class="usa-button" type="submit" id="domain-requests__search-field-submit" aria-labelledby="domain-requests-search-component" aria-describedby="domain-requests-search-button__description">
|
||||||
<img
|
<img
|
||||||
src="{% static 'img/usa-icons-bg/search--white.svg' %}"
|
src="{% static 'img/usa-icons-bg/search--white.svg' %}"
|
||||||
class="usa-search__submit-icon"
|
class="usa-search__submit-icon"
|
||||||
|
@ -163,7 +158,7 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="display-none usa-table-container--scrollable margin-top-0" tabindex="0" id="domain-requests__table-wrapper">
|
<div class="display-none usa-table-container--scrollable usa-table-container--override-overflow margin-top-0" tabindex="0" id="domain-requests__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 domain requests</caption>
|
<caption class="sr-only">Your domain requests</caption>
|
||||||
<thead>
|
<thead>
|
||||||
|
|
|
@ -34,24 +34,25 @@
|
||||||
<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 section-outlined__search--widescreen {% if portfolio %}mobile:grid-col-12 desktop:grid-col-6{% 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" id="domains-search-component">
|
||||||
<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 %}
|
||||||
<button class="usa-button usa-button--unstyled margin-right-3 display-none" id="domains__reset-search" type="button">
|
<button class="usa-button usa-button--unstyled margin-right-3 display-none" id="domains__reset-search" type="button" aria-labelledby="domains-search-component">
|
||||||
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24">
|
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24">
|
||||||
<use xlink:href="{%static 'img/sprite.svg'%}#close"></use>
|
<use xlink:href="{%static 'img/sprite.svg'%}#close"></use>
|
||||||
</svg>
|
</svg>
|
||||||
Reset
|
Reset
|
||||||
</button>
|
</button>
|
||||||
<label id="domains__search-label" class="usa-sr-only" for="domains__search-field">Search by domain name</label>
|
|
||||||
<input
|
<input
|
||||||
class="usa-input"
|
class="usa-input"
|
||||||
id="domains__search-field"
|
id="domains__search-field"
|
||||||
type="search"
|
type="search"
|
||||||
name="domains-search"
|
name="domains-search"
|
||||||
placeholder="Search by domain name"
|
placeholder="Search by domain name"
|
||||||
|
aria-labelledby="domains-search-component"
|
||||||
/>
|
/>
|
||||||
<button class="usa-button" type="submit" id="domains__search-field-submit" aria-labelledby="domains__search-label">
|
<div class="usa-sr-only" id="domains-search-button__description">Click to search</div>
|
||||||
|
<button class="usa-button" type="submit" id="domains__search-field-submit" aria-labelledby="domains-search-component" aria-describedby="domains-search-button__description">
|
||||||
<img
|
<img
|
||||||
src="{% static 'img/usa-icons-bg/search--white.svg' %}"
|
src="{% static 'img/usa-icons-bg/search--white.svg' %}"
|
||||||
class="usa-search__submit-icon"
|
class="usa-search__submit-icon"
|
||||||
|
@ -63,12 +64,13 @@
|
||||||
</div>
|
</div>
|
||||||
{% if user_domain_count and user_domain_count > 0 %}
|
{% if user_domain_count and user_domain_count > 0 %}
|
||||||
<div class="section-outlined__utility-button mobile-lg:padding-right-105 {% if portfolio %} mobile:grid-col-12 desktop:grid-col-6 desktop:padding-left-3{% endif %}">
|
<div class="section-outlined__utility-button mobile-lg:padding-right-105 {% if portfolio %} mobile:grid-col-12 desktop:grid-col-6 desktop:padding-left-3{% endif %}">
|
||||||
<section aria-label="Domains report component" class="margin-top-205">
|
<section aria-label="Domains report component" class="margin-top-205" id="domains-report-component">
|
||||||
<a href="{% url 'export_data_type_user' %}" class="usa-button usa-button--unstyled usa-button--with-icon usa-button--justify-right">
|
<div class="usa-sr-only" id="domains-export-button__description">Click to export as csv</div>
|
||||||
|
<button data-href="{% url 'export_data_type_user' %}" class="use-button-as-link usa-button usa-button--unstyled usa-button--with-icon usa-button--justify-right" aria-labelledby="domains-report-component" aria-describedby="domains-export-button__description">
|
||||||
<svg class="usa-icon usa-icon--large" aria-hidden="true" focusable="false" role="img" width="24" height="24">
|
<svg class="usa-icon usa-icon--large" aria-hidden="true" focusable="false" role="img" width="24" height="24">
|
||||||
<use xlink:href="{%static 'img/sprite.svg'%}#file_download"></use>
|
<use xlink:href="{%static 'img/sprite.svg'%}#file_download"></use>
|
||||||
</svg>Export as CSV
|
</svg>Export as CSV
|
||||||
</a>
|
</button>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -198,7 +200,7 @@
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<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 usa-table-container--override-overflow 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>
|
||||||
<thead>
|
<thead>
|
||||||
|
|
|
@ -1,16 +1,18 @@
|
||||||
{% if form.errors %}
|
{% if form.errors %}
|
||||||
<div id="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" role="alert">
|
<div class="usa-alert usa-alert--error usa-alert--slim margin-bottom-2" role="alert" tabindex="0">
|
||||||
<div class="usa-alert__body">
|
<div class="usa-alert__body">
|
||||||
{{ error|escape }}
|
<span class="usa-sr-only">Error:</span>
|
||||||
|
{{ error|escape }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% for field in form %}
|
{% for field in form %}
|
||||||
{% for error in field.errors %}
|
{% for error in 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" tabindex="0">
|
||||||
<div class="usa-alert__body">
|
<div class="usa-alert__body">
|
||||||
|
<span class="usa-sr-only">Error:</span>
|
||||||
{{ error|escape }}
|
{{ error|escape }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -9,24 +9,25 @@
|
||||||
<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 section-outlined__search--widescreen">
|
<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" id="members-search-component">
|
||||||
<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 %}
|
||||||
<button class="usa-button usa-button--unstyled margin-right-3 display-none" id="members__reset-search" type="button">
|
<button class="usa-button usa-button--unstyled margin-right-3 display-none" id="members__reset-search" type="button" aria-labelledby="members-search-component">
|
||||||
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24">
|
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24">
|
||||||
<use xlink:href="{%static 'img/sprite.svg'%}#close"></use>
|
<use xlink:href="{%static 'img/sprite.svg'%}#close"></use>
|
||||||
</svg>
|
</svg>
|
||||||
Reset
|
Reset
|
||||||
</button>
|
</button>
|
||||||
<label class="usa-sr-only" for="members__search-field">Search by member name</label>
|
|
||||||
<input
|
<input
|
||||||
class="usa-input"
|
class="usa-input"
|
||||||
id="members__search-field"
|
id="members__search-field"
|
||||||
type="search"
|
type="search"
|
||||||
name="members-search"
|
name="members-search"
|
||||||
placeholder="Search by member name"
|
placeholder="Search by member name"
|
||||||
|
aria-labelledby="members-search-component"
|
||||||
/>
|
/>
|
||||||
<button class="usa-button" type="submit" id="members__search-field-submit">
|
<div class="usa-sr-only" id="members-search-button__description">Click to search</div>
|
||||||
|
<button class="usa-button" type="submit" id="members__search-field-submit" aria-labelledby="members-search-component" aria-describedby="members-search-button__description">
|
||||||
<img
|
<img
|
||||||
src="{% static 'img/usa-icons-bg/search--white.svg' %}"
|
src="{% static 'img/usa-icons-bg/search--white.svg' %}"
|
||||||
class="usa-search__submit-icon"
|
class="usa-search__submit-icon"
|
||||||
|
@ -37,12 +38,13 @@
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
<div class="section-outlined__utility-button mobile-lg:padding-right-105 {% if portfolio %} mobile:grid-col-12 desktop:grid-col-6 desktop:padding-left-3{% endif %}">
|
<div class="section-outlined__utility-button mobile-lg:padding-right-105 {% if portfolio %} mobile:grid-col-12 desktop:grid-col-6 desktop:padding-left-3{% endif %}">
|
||||||
<section aria-label="Domains report component" class="margin-top-205">
|
<section aria-label="Members report component" class="margin-top-205" id="members-report-component">
|
||||||
<a href="{% url 'export_members_portfolio' %}" class="usa-button usa-button--unstyled usa-button--with-icon usa-button--justify-right">
|
<div class="usa-sr-only" id="members-export-button__description">Click to export as csv</div>
|
||||||
|
<button href="{% url 'export_members_portfolio' %}" class="use-button-as-link usa-button usa-button--unstyled usa-button--with-icon usa-button--justify-right" aria-labelledby="members-report-component" aria-describedby="members-export-button__description">
|
||||||
<svg class="usa-icon usa-icon--large" aria-hidden="true" focusable="false" role="img" width="24" height="24">
|
<svg class="usa-icon usa-icon--large" aria-hidden="true" focusable="false" role="img" width="24" height="24">
|
||||||
<use xlink:href="{%static 'img/sprite.svg'%}#file_download"></use>
|
<use xlink:href="{%static 'img/sprite.svg'%}#file_download"></use>
|
||||||
</svg>Export as CSV
|
</svg>Export as CSV
|
||||||
</a>
|
</button>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -26,10 +26,10 @@
|
||||||
<div class="mobile:grid-col-12 tablet:grid-col-6">
|
<div class="mobile:grid-col-12 tablet:grid-col-6">
|
||||||
|
|
||||||
<p class="float-right-tablet tablet:margin-y-0">
|
<p class="float-right-tablet tablet:margin-y-0">
|
||||||
<a href="{% url 'domain-request:start' %}" class="usa-button"
|
<button data-href="{% url 'domain-request:start' %}" class="usa-button use-button-as-link"
|
||||||
>
|
>
|
||||||
Start a new domain request
|
Start a new domain request
|
||||||
</a>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
|
@ -219,12 +219,12 @@ class TestDomainInvitationAdmin(WebTest):
|
||||||
# Assert that the filters are added
|
# Assert that the filters are added
|
||||||
self.assertContains(response, "invited", count=4)
|
self.assertContains(response, "invited", count=4)
|
||||||
self.assertContains(response, "Invited", count=2)
|
self.assertContains(response, "Invited", count=2)
|
||||||
self.assertContains(response, "retrieved", count=2)
|
self.assertContains(response, "retrieved", count=3)
|
||||||
self.assertContains(response, "Retrieved", count=2)
|
self.assertContains(response, "Retrieved", count=2)
|
||||||
|
|
||||||
# Check for the HTML context specificially
|
# Check for the HTML context specificially
|
||||||
invited_html = '<a href="?status__exact=invited">Invited</a>'
|
invited_html = '<a id="status-filter-invited" href="?status__exact=invited">Invited</a>'
|
||||||
retrieved_html = '<a href="?status__exact=retrieved">Retrieved</a>'
|
retrieved_html = '<a id="status-filter-retrieved" href="?status__exact=retrieved">Retrieved</a>'
|
||||||
|
|
||||||
self.assertContains(response, invited_html, count=1)
|
self.assertContains(response, invited_html, count=1)
|
||||||
self.assertContains(response, retrieved_html, count=1)
|
self.assertContains(response, retrieved_html, count=1)
|
||||||
|
@ -1271,14 +1271,14 @@ class TestPortfolioInvitationAdmin(TestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Assert that the filters are added
|
# Assert that the filters are added
|
||||||
self.assertContains(response, "invited", count=4)
|
self.assertContains(response, "invited", count=5)
|
||||||
self.assertContains(response, "Invited", count=2)
|
self.assertContains(response, "Invited", count=2)
|
||||||
self.assertContains(response, "retrieved", count=2)
|
self.assertContains(response, "retrieved", count=3)
|
||||||
self.assertContains(response, "Retrieved", count=2)
|
self.assertContains(response, "Retrieved", count=2)
|
||||||
|
|
||||||
# Check for the HTML context specificially
|
# Check for the HTML context specificially
|
||||||
invited_html = '<a href="?status__exact=invited">Invited</a>'
|
invited_html = '<a id="status-filter-invited" href="?status__exact=invited">Invited</a>'
|
||||||
retrieved_html = '<a href="?status__exact=retrieved">Retrieved</a>'
|
retrieved_html = '<a id="status-filter-retrieved" href="?status__exact=retrieved">Retrieved</a>'
|
||||||
|
|
||||||
self.assertContains(response, invited_html, count=1)
|
self.assertContains(response, invited_html, count=1)
|
||||||
self.assertContains(response, retrieved_html, count=1)
|
self.assertContains(response, retrieved_html, count=1)
|
||||||
|
|
|
@ -888,8 +888,8 @@ class MemberExportTest(MockDbForIndividualTests, MockEppLib):
|
||||||
csv_content = csv_file.read()
|
csv_content = csv_file.read()
|
||||||
expected_content = (
|
expected_content = (
|
||||||
# Header
|
# Header
|
||||||
"Email,Organization admin,Invited by,Joined date,Last active,Domain requests,"
|
"Email,Member access,Invited by,Joined date,Last active,Domain requests,"
|
||||||
"Member management,Domain management,Number of domains,Domains\n"
|
"Members,Domains,Number domains assigned,Domain assignments\n"
|
||||||
# Content
|
# Content
|
||||||
"big_lebowski@dude.co,False,help@get.gov,2022-04-01,Invalid date,None,"
|
"big_lebowski@dude.co,False,help@get.gov,2022-04-01,Invalid date,None,"
|
||||||
"Viewer,True,1,cdomain1.gov\n"
|
"Viewer,True,1,cdomain1.gov\n"
|
||||||
|
|
|
@ -712,7 +712,7 @@ class TestDomainDetailDomainRenewal(TestDomainOverview):
|
||||||
self.assertRedirects(response, reverse("domain", kwargs={"domain_pk": self.domain_with_ip.id}))
|
self.assertRedirects(response, reverse("domain", kwargs={"domain_pk": self.domain_with_ip.id}))
|
||||||
|
|
||||||
# Check for the updated expiration
|
# Check for the updated expiration
|
||||||
formatted_new_expiration_date = self.expiration_date_one_year_out().strftime("%b. %-d, %Y")
|
formatted_new_expiration_date = self.expiration_date_one_year_out().strftime("%B %-d, %Y")
|
||||||
redirect_response = self.client.get(
|
redirect_response = self.client.get(
|
||||||
reverse("domain", kwargs={"domain_pk": self.domain_with_ip.id}), follow=True
|
reverse("domain", kwargs={"domain_pk": self.domain_with_ip.id}), follow=True
|
||||||
)
|
)
|
||||||
|
@ -2088,62 +2088,6 @@ class TestDomainOrganization(TestDomainOverview):
|
||||||
# Check for the value we want to update
|
# Check for the value we want to update
|
||||||
self.assertContains(success_result_page, "Faketown")
|
self.assertContains(success_result_page, "Faketown")
|
||||||
|
|
||||||
@less_console_noise_decorator
|
|
||||||
def test_domain_org_name_address_form_federal(self):
|
|
||||||
"""
|
|
||||||
Submitting a change to federal_agency is blocked for federal domains
|
|
||||||
"""
|
|
||||||
|
|
||||||
fed_org_type = DomainInformation.OrganizationChoices.FEDERAL
|
|
||||||
self.domain_information.generic_org_type = fed_org_type
|
|
||||||
self.domain_information.save()
|
|
||||||
try:
|
|
||||||
federal_agency, _ = FederalAgency.objects.get_or_create(agency="AMTRAK")
|
|
||||||
self.domain_information.federal_agency = federal_agency
|
|
||||||
self.domain_information.save()
|
|
||||||
except ValueError as err:
|
|
||||||
self.fail(f"A ValueError was caught during the test: {err}")
|
|
||||||
|
|
||||||
self.assertEqual(self.domain_information.generic_org_type, fed_org_type)
|
|
||||||
|
|
||||||
org_name_page = self.app.get(reverse("domain-org-name-address", kwargs={"domain_pk": self.domain.id}))
|
|
||||||
|
|
||||||
form = org_name_page.forms[0]
|
|
||||||
# Check the value of the input field
|
|
||||||
agency_input = form.fields["federal_agency"][0]
|
|
||||||
self.assertEqual(agency_input.value, str(federal_agency.id))
|
|
||||||
|
|
||||||
# Check if the input field is disabled
|
|
||||||
self.assertTrue("disabled" in agency_input.attrs)
|
|
||||||
self.assertEqual(agency_input.attrs.get("disabled"), "")
|
|
||||||
|
|
||||||
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
|
||||||
|
|
||||||
org_name_page.form["federal_agency"] = FederalAgency.objects.filter(agency="Department of State").get().id
|
|
||||||
org_name_page.form["city"] = "Faketown"
|
|
||||||
|
|
||||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
|
||||||
|
|
||||||
# Make the change. The agency should be unchanged, but city should be modifiable.
|
|
||||||
success_result_page = org_name_page.form.submit()
|
|
||||||
self.assertEqual(success_result_page.status_code, 200)
|
|
||||||
|
|
||||||
# Check that the agency has not changed
|
|
||||||
self.assertEqual(self.domain_information.federal_agency.agency, "AMTRAK")
|
|
||||||
|
|
||||||
# Do another check on the form itself
|
|
||||||
form = success_result_page.forms[0]
|
|
||||||
# Check the value of the input field
|
|
||||||
organization_name_input = form.fields["federal_agency"][0]
|
|
||||||
self.assertEqual(organization_name_input.value, str(federal_agency.id))
|
|
||||||
|
|
||||||
# Check if the input field is disabled
|
|
||||||
self.assertTrue("disabled" in organization_name_input.attrs)
|
|
||||||
self.assertEqual(organization_name_input.attrs.get("disabled"), "")
|
|
||||||
|
|
||||||
# Check for the value we want to update
|
|
||||||
self.assertContains(success_result_page, "Faketown")
|
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_federal_agency_submit_blocked(self):
|
def test_federal_agency_submit_blocked(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -38,10 +38,15 @@ from django.contrib.admin.models import LogEntry, ADDITION
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from registrar.models.utility.generic_helper import convert_queryset_to_dict
|
from registrar.models.utility.generic_helper import convert_queryset_to_dict
|
||||||
from registrar.models.utility.orm_helper import ArrayRemoveNull
|
from registrar.models.utility.orm_helper import ArrayRemoveNull
|
||||||
from registrar.models.utility.portfolio_helper import UserPortfolioRoleChoices
|
|
||||||
from registrar.templatetags.custom_filters import get_region
|
from registrar.templatetags.custom_filters import get_region
|
||||||
from registrar.utility.constants import BranchChoices
|
from registrar.utility.constants import BranchChoices
|
||||||
from registrar.utility.enums import DefaultEmail, DefaultUserValues
|
from registrar.utility.enums import DefaultEmail, DefaultUserValues
|
||||||
|
from registrar.models.utility.portfolio_helper import (
|
||||||
|
get_role_display,
|
||||||
|
get_domain_requests_display,
|
||||||
|
get_domains_display,
|
||||||
|
get_members_display,
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -479,15 +484,15 @@ class MemberExport(BaseExport):
|
||||||
"""
|
"""
|
||||||
return [
|
return [
|
||||||
"Email",
|
"Email",
|
||||||
"Organization admin",
|
"Member access",
|
||||||
"Invited by",
|
"Invited by",
|
||||||
"Joined date",
|
"Joined date",
|
||||||
"Last active",
|
"Last active",
|
||||||
"Domain requests",
|
"Domain requests",
|
||||||
"Member management",
|
"Members",
|
||||||
"Domain management",
|
|
||||||
"Number of domains",
|
|
||||||
"Domains",
|
"Domains",
|
||||||
|
"Number domains assigned",
|
||||||
|
"Domain assignments",
|
||||||
]
|
]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -503,15 +508,15 @@ class MemberExport(BaseExport):
|
||||||
length_user_managed_domains = len(user_managed_domains)
|
length_user_managed_domains = len(user_managed_domains)
|
||||||
FIELDS = {
|
FIELDS = {
|
||||||
"Email": model.get("email_display"),
|
"Email": model.get("email_display"),
|
||||||
"Organization admin": bool(UserPortfolioRoleChoices.ORGANIZATION_ADMIN in roles),
|
"Member access": get_role_display(roles),
|
||||||
"Invited by": model.get("invited_by"),
|
"Invited by": model.get("invited_by"),
|
||||||
"Joined date": model.get("joined_date"),
|
"Joined date": model.get("joined_date"),
|
||||||
"Last active": model.get("last_active"),
|
"Last active": model.get("last_active"),
|
||||||
"Domain requests": UserPortfolioPermission.get_domain_request_permission_display(roles, permissions),
|
"Domain requests": f"{get_domain_requests_display(roles, permissions)}",
|
||||||
"Member management": UserPortfolioPermission.get_member_permission_display(roles, permissions),
|
"Members": f"{get_members_display(roles, permissions)}",
|
||||||
"Domain management": bool(length_user_managed_domains > 0),
|
"Domains": f"{get_domains_display(roles, permissions)}",
|
||||||
"Number of domains": length_user_managed_domains,
|
"Number domains assigned": length_user_managed_domains,
|
||||||
"Domains": ",".join(user_managed_domains),
|
"Domain assignments": ", ".join(user_managed_domains),
|
||||||
}
|
}
|
||||||
return [FIELDS.get(column, "") for column in columns]
|
return [FIELDS.get(column, "") for column in columns]
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue