Skip to content

Commit

Permalink
Merge branch 'main' into rb/3281-members-design-review
Browse files Browse the repository at this point in the history
  • Loading branch information
rachidatecs authored Feb 21, 2025
2 parents 3795e14 + a70eee4 commit 6525a91
Show file tree
Hide file tree
Showing 24 changed files with 871 additions and 266 deletions.
4 changes: 4 additions & 0 deletions src/registrar/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -1327,6 +1327,7 @@ class Meta:
search_help_text = "Search by first name, last name, email, or portfolio."

change_form_template = "django/admin/user_portfolio_permission_change_form.html"
delete_confirmation_template = "django/admin/user_portfolio_permission_delete_confirmation.html"

def get_roles(self, obj):
readable_roles = obj.get_readable_roles()
Expand Down Expand Up @@ -1670,6 +1671,7 @@ class Meta:
autocomplete_fields = ["portfolio"]

change_form_template = "django/admin/portfolio_invitation_change_form.html"
delete_confirmation_template = "django/admin/portfolio_invitation_delete_confirmation.html"

# Select portfolio invitations to change -> Portfolio invitations
def changelist_view(self, request, extra_context=None):
Expand Down Expand Up @@ -3738,11 +3740,13 @@ def do_delete_domain(self, request, obj):
# Using variables to get past the linter
message1 = f"Cannot delete Domain when in state {obj.state}"
message2 = f"This subdomain is being used as a hostname on another domain: {err.note}"
message3 = f"Command failed with note: {err.note}"
# Human-readable mappings of ErrorCodes. Can be expanded.
error_messages = {
# noqa on these items as black wants to reformat to an invalid length
ErrorCode.OBJECT_STATUS_PROHIBITS_OPERATION: message1,
ErrorCode.OBJECT_ASSOCIATION_PROHIBITS_OPERATION: message2,
ErrorCode.COMMAND_FAILED: message3,
}

message = "Cannot connect to the registry"
Expand Down
20 changes: 18 additions & 2 deletions src/registrar/assets/js/uswds-edited.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 11 additions & 9 deletions src/registrar/assets/src/sass/_theme/_admin.scss
Original file line number Diff line number Diff line change
Expand Up @@ -520,15 +520,6 @@ input[type=submit].button--dja-toolbar:focus, input[type=submit].button--dja-too
}
}

.module--custom {
a {
font-size: 13px;
font-weight: 600;
border: solid 1px var(--darkened-bg);
background: var(--darkened-bg);
}
}

.usa-modal--django-admin .usa-prose ul > li {
list-style-type: inherit;
// Styling based off of the <p> styling in django admin
Expand Down Expand Up @@ -839,6 +830,17 @@ div.dja__model-description{
text-transform: capitalize;
}

.module caption {
// Match the old <h2> size for django admin
font-size: 0.8125rem;
}

// text-bold doesn't work here due to style overrides, unfortunately.
// This is a workaround.
caption.text-bold {
font-weight: font-weight('bold');
}

.wrapped-button-group {
// This button group has too many items
flex-wrap: wrap;
Expand Down
55 changes: 40 additions & 15 deletions src/registrar/assets/src/sass/_theme/_tables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,8 @@ th {
}
}

// The member table has an extra "expand" row, which looks like a single row.
// But the DOM disagrees - so we basically need to hide the border on both rows.
#members__table-wrapper .dotgov-table tr:nth-last-child(2) td,
#members__table-wrapper .dotgov-table tr:nth-last-child(2) th {
border-bottom: none;
}

// .dotgov-table allows us to customize .usa-table on the user-facing pages,
// while leaving the default styles for use on the admin pages
.dotgov-table {
width: 100%;

Expand All @@ -68,7 +63,8 @@ th {
border-bottom: 1px solid color('base-lighter');
}

thead th {
thead th,
thead th[aria-sort] {
color: color('primary-darker');
border-bottom: 2px solid color('base-light');
}
Expand All @@ -93,17 +89,46 @@ th {
}
}

// Sortable headers
th[data-sortable][aria-sort=ascending],
th[data-sortable][aria-sort=descending] {
background-color: transparent;
.usa-table__header__button {
background-color: color('accent-cool-lightest');
border-radius: units(.5);
color: color('primary-darker');
&:hover {
background-color: color('accent-cool-lightest');
}
}
}
@include at-media(tablet-lg) {
th[data-sortable] .usa-table__header__button {
th[data-sortable]:not(.left-align-sort-button) .usa-table__header__button {
// position next to the copy
right: auto;

&[aria-sort=ascending],
&[aria-sort=descending],
&:not([aria-sort]) {
right: auto;
}
// slide left to mock a margin between the copy and the icon
transform: translateX(units(1));
// fix vertical alignment
top: units(1.5);
}
th[data-sortable].left-align-sort-button .usa-table__header__button {
left: 0;
}
}

// Currently the 'flash' when sort is clicked,
// this will become persistent if the double-sort bug is fixed
td[data-sort-active],
th[data-sort-active] {
background-color: color('primary-lightest');
}
}

// The member table has an extra "expand" row, which looks like a single row.
// But the DOM disagrees - so we basically need to hide the border on both rows.
#members__table-wrapper .dotgov-table tr:nth-last-child(2) td,
#members__table-wrapper .dotgov-table tr:nth-last-child(2) th {
border-bottom: none;
}

.dotgov-table--cell-padding-2 {
Expand Down
1 change: 1 addition & 0 deletions src/registrar/assets/src/sass/_theme/_uswds-theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ in the form $setting: value,
----------------------------*/
$theme-font-weight-medium: 400,
$theme-font-weight-semibold: 600,
$theme-font-weight-bold: 700,

/*---------------------------
## Font roles
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
""""
""" "
Converts all ready and DNS needed domains with a non-default public contact
to disclose their public contact. Created for Issue#1535 to resolve
disclose issue of domains with missing security emails.
Expand Down
8 changes: 4 additions & 4 deletions src/registrar/management/commands/master_domain_migrations.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"""Data migration:
1 - generates a report of data integrity across all
transition domain related tables
2 - allows users to run all migration scripts for
transition domain data
1 - generates a report of data integrity across all
transition domain related tables
2 - allows users to run all migration scripts for
transition domain data
"""

import logging
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
""""
""" "
Data migration: Renaming deprecated Federal Agencies to
their new updated names ie (U.S. Peace Corps to Peace Corps)
within Domain Information and Domain Requests
Expand Down
132 changes: 104 additions & 28 deletions src/registrar/models/domain.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import logging
import ipaddress
import re
import time
from datetime import date, timedelta
from typing import Optional
from django.db import transaction
Expand Down Expand Up @@ -750,11 +751,7 @@ def nameservers(self, hosts: list[tuple[str, list]]): # noqa

successTotalNameservers = len(oldNameservers) - deleteCount + addToDomainCount

try:
self._delete_hosts_if_not_used(hostsToDelete=deleted_values)
except Exception as e:
# we don't need this part to succeed in order to continue.
logger.error("Failed to delete nameserver hosts: %s", e)
self._delete_hosts_if_not_used(hostsToDelete=deleted_values)

if successTotalNameservers < 2:
try:
Expand Down Expand Up @@ -1038,52 +1035,133 @@ def _remove_client_hold(self):
logger.error(f"registry error removing client hold: {err}")
raise (err)

def _delete_domain(self):
def _delete_domain(self): # noqa
"""This domain should be deleted from the registry
may raises RegistryError, should be caught or handled correctly by caller"""

logger.info("Deleting subdomains for %s", self.name)
# check if any subdomains are in use by another domain
hosts = Host.objects.filter(name__regex=r".+{}".format(self.name))
hosts = Host.objects.filter(name__regex=r".+\.{}".format(self.name))
for host in hosts:
if host.domain != self:
logger.error("Unable to delete host: %s is in use by another domain: %s", host.name, host.domain)
raise RegistryError(
code=ErrorCode.OBJECT_ASSOCIATION_PROHIBITS_OPERATION,
note=f"Host {host.name} is in use by {host.domain}",
)

(
deleted_values,
updated_values,
new_values,
oldNameservers,
) = self.getNameserverChanges(hosts=[])

_ = self._update_host_values(updated_values, oldNameservers) # returns nothing, just need to be run and errors
addToDomainList, _ = self.createNewHostList(new_values)
deleteHostList, _ = self.createDeleteHostList(deleted_values)
responseCode = self.addAndRemoveHostsFromDomain(hostsToAdd=addToDomainList, hostsToDelete=deleteHostList)

try:
# set hosts to empty list so nameservers are deleted
(
deleted_values,
updated_values,
new_values,
oldNameservers,
) = self.getNameserverChanges(hosts=[])

# update the hosts
_ = self._update_host_values(
updated_values, oldNameservers
) # returns nothing, just need to be run and errors
addToDomainList, _ = self.createNewHostList(new_values)
deleteHostList, _ = self.createDeleteHostList(deleted_values)
responseCode = self.addAndRemoveHostsFromDomain(hostsToAdd=addToDomainList, hostsToDelete=deleteHostList)
except RegistryError as e:
logger.error(f"Error trying to delete hosts from domain {self}: {e}")
raise e
# if unable to update domain raise error and stop
if responseCode != ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY:
raise NameserverError(code=nsErrorCodes.BAD_DATA)

logger.info("Finished removing nameservers from domain")

# addAndRemoveHostsFromDomain removes the hosts from the domain object,
# but we still need to delete the object themselves
self._delete_hosts_if_not_used(hostsToDelete=deleted_values)
logger.info("Finished _delete_hosts_if_not_used inside _delete_domain()")

# delete the non-registrant contacts
logger.debug("Deleting non-registrant contacts for %s", self.name)
contacts = PublicContact.objects.filter(domain=self)
logger.info(f"retrieved contacts for domain: {contacts}")

for contact in contacts:
if contact.contact_type != PublicContact.ContactTypeChoices.REGISTRANT:
self._update_domain_with_contact(contact, rem=True)
request = commands.DeleteContact(contact.registry_id)
registry.send(request, cleaned=True)
try:
if contact.contact_type != PublicContact.ContactTypeChoices.REGISTRANT:
logger.info(f"Deleting contact: {contact}")
try:
self._update_domain_with_contact(contact, rem=True)
except Exception as e:
logger.error(f"Error while updating domain with contact: {contact}, e: {e}", exc_info=True)
request = commands.DeleteContact(contact.registry_id)
registry.send(request, cleaned=True)
logger.info(f"sent DeleteContact for {contact}")
except RegistryError as e:
logger.error(f"Error deleting contact: {contact}, {e}", exc_info=True)

logger.info(f"Finished deleting contacts for {self.name}")

logger.info("Deleting domain %s", self.name)
# delete ds data if it exists
if self.dnssecdata:
logger.debug("Deleting ds data for %s", self.name)
try:
# set and unset client hold to be able to change ds data
logger.info("removing client hold")
self._remove_client_hold()
self.dnssecdata = None
logger.info("placing client hold")
self._place_client_hold()
except RegistryError as e:
logger.error("Error deleting ds data for %s: %s", self.name, e)
e.note = "Error deleting ds data for %s" % self.name
raise e

# check if the domain can be deleted
if not self._domain_can_be_deleted():
note = "Domain has associated objects that prevent deletion."
raise RegistryError(code=ErrorCode.COMMAND_FAILED, note=note)

# delete the domain
request = commands.DeleteDomain(name=self.name)
registry.send(request, cleaned=True)
try:
registry.send(request, cleaned=True)
logger.info("Domain %s deleted successfully.", self.name)
except RegistryError as e:
logger.error("Error deleting domain %s: %s", self.name, e)
raise e

def _domain_can_be_deleted(self, max_attempts=5, wait_interval=2) -> bool:
"""
Polls the registry using InfoDomain calls to confirm that the domain can be deleted.
Returns True if the domain can be deleted, False otherwise. Includes a retry mechanism
using wait_interval and max_attempts, which may be necessary if subdomains and other
associated objects were only recently deleted as the registry may not be immediately updated.
"""
logger.info("Polling registry to confirm deletion pre-conditions for %s", self.name)
last_info_error = None
for attempt in range(max_attempts):
try:
info_response = registry.send(commands.InfoDomain(name=self.name), cleaned=True)
domain_info = info_response.res_data[0]
hosts_associated = getattr(domain_info, "hosts", None)
if hosts_associated is None or len(hosts_associated) == 0:
logger.info("InfoDomain reports no associated hosts for %s. Proceeding with deletion.", self.name)
return True
else:
logger.info("Attempt %d: Domain %s still has hosts: %s", attempt + 1, self.name, hosts_associated)
except RegistryError as info_e:
# If the domain is already gone, we can assume deletion already occurred.
if info_e.code == ErrorCode.OBJECT_DOES_NOT_EXIST:
logger.info("InfoDomain check indicates domain %s no longer exists.", self.name)
raise info_e
logger.warning("Attempt %d: Error during InfoDomain check: %s", attempt + 1, info_e)
time.sleep(wait_interval)
else:
logger.error(
"Exceeded max attempts waiting for domain %s to clear associated objects; last error: %s",
self.name,
last_info_error,
)
return False

def __str__(self) -> str:
return self.name
Expand Down Expand Up @@ -1840,8 +1918,6 @@ def _delete_hosts_if_not_used(self, hostsToDelete: list[str]):
else:
logger.error("Error _delete_hosts_if_not_used, code was %s error was %s" % (e.code, e))

raise e

def _fix_unknown_state(self, cleaned):
"""
_fix_unknown_state: Calls _add_missing_contacts_if_unknown
Expand Down
Loading

0 comments on commit 6525a91

Please sign in to comment.