diff --git a/docs/operations/data_migration.md b/docs/operations/data_migration.md
index b64b5ea76..cdef3dba7 100644
--- a/docs/operations/data_migration.md
+++ b/docs/operations/data_migration.md
@@ -907,13 +907,14 @@ Example (only requests): `./manage.py create_federal_portfolio --branch "executi
```docker-compose exec app ./manage.py create_federal_portfolio --agency_name "{federal_agency_name}" --both```
##### Parameters
-| | Parameter | Description |
-|:-:|:-------------------------- |:-------------------------------------------------------------------------------------------|
-| 1 | **agency_name** | Name of the FederalAgency record surrounded by quotes. For instance,"AMTRAK". |
-| 2 | **branch** | Creates a portfolio for each federal agency in a branch: executive, legislative, judicial |
-| 3 | **both** | If True, runs parse_requests and parse_domains. |
-| 4 | **parse_requests** | If True, then the created portfolio is added to all related DomainRequests. |
-| 5 | **parse_domains** | If True, then the created portfolio is added to all related Domains. |
+| | Parameter | Description |
+|:-:|:---------------------------- |:-------------------------------------------------------------------------------------------|
+| 1 | **agency_name** | Name of the FederalAgency record surrounded by quotes. For instance,"AMTRAK". |
+| 2 | **branch** | Creates a portfolio for each federal agency in a branch: executive, legislative, judicial |
+| 3 | **both** | If True, runs parse_requests and parse_domains. |
+| 4 | **parse_requests** | If True, then the created portfolio is added to all related DomainRequests. |
+| 5 | **parse_domains** | If True, then the created portfolio is added to all related Domains. |
+| 6 | **skip_existing_portfolios** | If True, then the script will only create suborganizations, modify DomainRequest, and modify DomainInformation records only when creating a new portfolio. Use this flag when you do not want to modify existing records. |
- Parameters #1-#2: Either `--agency_name` or `--branch` must be specified. Not both.
- Parameters #2-#3, you cannot use `--both` while using these. You must specify either `--parse_requests` or `--parse_domains` seperately. While all of these parameters are optional in that you do not need to specify all of them,
diff --git a/src/registrar/admin.py b/src/registrar/admin.py
index ffa5adf6a..8ecf36f52 100644
--- a/src/registrar/admin.py
+++ b/src/registrar/admin.py
@@ -1678,22 +1678,25 @@ class DomainInformationAdmin(ListHeaderAdmin, ImportExportModelAdmin):
parameter_name = "converted_generic_orgs"
def lookups(self, request, model_admin):
- converted_generic_orgs = set()
+ # Annotate the queryset to avoid Python-side iteration
+ queryset = (
+ DomainInformation.objects.annotate(
+ converted_generic_org=Case(
+ When(portfolio__organization_type__isnull=False, then="portfolio__organization_type"),
+ When(portfolio__isnull=True, generic_org_type__isnull=False, then="generic_org_type"),
+ default=Value(""),
+ output_field=CharField(),
+ )
+ )
+ .values_list("converted_generic_org", flat=True)
+ .distinct()
+ )
- # Populate the set with tuples of (value, display value)
- for domain_info in DomainInformation.objects.all():
- converted_generic_org = domain_info.converted_generic_org_type # Actual value
- converted_generic_org_display = domain_info.converted_generic_org_type_display # Display value
+ # Filter out empty results and return sorted list of unique values
+ return sorted([(org, DomainRequest.OrganizationChoices.get_org_label(org)) for org in queryset if org])
- if converted_generic_org:
- converted_generic_orgs.add((converted_generic_org, converted_generic_org_display)) # Value, Display
-
- # Sort the set by display value
- return sorted(converted_generic_orgs, key=lambda x: x[1]) # x[1] is the display value
-
- # Filter queryset
def queryset(self, request, queryset):
- if self.value(): # Check if a generic org is selected in the filter
+ if self.value():
return queryset.filter(
Q(portfolio__organization_type=self.value())
| Q(portfolio__isnull=True, generic_org_type=self.value())
@@ -2031,22 +2034,25 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
parameter_name = "converted_generic_orgs"
def lookups(self, request, model_admin):
- converted_generic_orgs = set()
+ # Annotate the queryset to avoid Python-side iteration
+ queryset = (
+ DomainRequest.objects.annotate(
+ converted_generic_org=Case(
+ When(portfolio__organization_type__isnull=False, then="portfolio__organization_type"),
+ When(portfolio__isnull=True, generic_org_type__isnull=False, then="generic_org_type"),
+ default=Value(""),
+ output_field=CharField(),
+ )
+ )
+ .values_list("converted_generic_org", flat=True)
+ .distinct()
+ )
- # Populate the set with tuples of (value, display value)
- for domain_request in DomainRequest.objects.all():
- converted_generic_org = domain_request.converted_generic_org_type # Actual value
- converted_generic_org_display = domain_request.converted_generic_org_type_display # Display value
+ # Filter out empty results and return sorted list of unique values
+ return sorted([(org, DomainRequest.OrganizationChoices.get_org_label(org)) for org in queryset if org])
- if converted_generic_org:
- converted_generic_orgs.add((converted_generic_org, converted_generic_org_display)) # Value, Display
-
- # Sort the set by display value
- return sorted(converted_generic_orgs, key=lambda x: x[1]) # x[1] is the display value
-
- # Filter queryset
def queryset(self, request, queryset):
- if self.value(): # Check if a generic org is selected in the filter
+ if self.value():
return queryset.filter(
Q(portfolio__organization_type=self.value())
| Q(portfolio__isnull=True, generic_org_type=self.value())
@@ -2062,24 +2068,39 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
parameter_name = "converted_federal_types"
def lookups(self, request, model_admin):
- converted_federal_types = set()
-
- # Populate the set with tuples of (value, display value)
- for domain_request in DomainRequest.objects.all():
- converted_federal_type = domain_request.converted_federal_type # Actual value
- converted_federal_type_display = domain_request.converted_federal_type_display # Display value
-
- if converted_federal_type:
- converted_federal_types.add(
- (converted_federal_type, converted_federal_type_display) # Value, Display
+ # Annotate the queryset for efficient filtering
+ queryset = (
+ DomainRequest.objects.annotate(
+ converted_federal_type=Case(
+ When(
+ portfolio__isnull=False,
+ portfolio__federal_agency__federal_type__isnull=False,
+ then="portfolio__federal_agency__federal_type",
+ ),
+ When(
+ portfolio__isnull=True,
+ federal_agency__federal_type__isnull=False,
+ then="federal_agency__federal_type",
+ ),
+ default=Value(""),
+ output_field=CharField(),
)
+ )
+ .values_list("converted_federal_type", flat=True)
+ .distinct()
+ )
- # Sort the set by display value
- return sorted(converted_federal_types, key=lambda x: x[1]) # x[1] is the display value
+ # Filter out empty values and return sorted unique entries
+ return sorted(
+ [
+ (federal_type, BranchChoices.get_branch_label(federal_type))
+ for federal_type in queryset
+ if federal_type
+ ]
+ )
- # Filter queryset
def queryset(self, request, queryset):
- if self.value(): # Check if a federal type is selected in the filter
+ if self.value():
return queryset.filter(
Q(portfolio__federal_agency__federal_type=self.value())
| Q(portfolio__isnull=True, federal_type=self.value())
@@ -3226,59 +3247,86 @@ class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin):
parameter_name = "converted_generic_orgs"
def lookups(self, request, model_admin):
- converted_generic_orgs = set()
+ # Annotate the queryset to avoid Python-side iteration
+ queryset = (
+ Domain.objects.annotate(
+ converted_generic_org=Case(
+ When(
+ domain_info__isnull=False,
+ domain_info__portfolio__organization_type__isnull=False,
+ then="domain_info__portfolio__organization_type",
+ ),
+ When(
+ domain_info__isnull=False,
+ domain_info__portfolio__isnull=True,
+ domain_info__generic_org_type__isnull=False,
+ then="domain_info__generic_org_type",
+ ),
+ default=Value(""),
+ output_field=CharField(),
+ )
+ )
+ .values_list("converted_generic_org", flat=True)
+ .distinct()
+ )
- # Populate the set with tuples of (value, display value)
- for domain_info in DomainInformation.objects.all():
- converted_generic_org = domain_info.converted_generic_org_type # Actual value
- converted_generic_org_display = domain_info.converted_generic_org_type_display # Display value
+ # Filter out empty results and return sorted list of unique values
+ return sorted([(org, DomainRequest.OrganizationChoices.get_org_label(org)) for org in queryset if org])
- if converted_generic_org:
- converted_generic_orgs.add((converted_generic_org, converted_generic_org_display)) # Value, Display
-
- # Sort the set by display value
- return sorted(converted_generic_orgs, key=lambda x: x[1]) # x[1] is the display value
-
- # Filter queryset
def queryset(self, request, queryset):
- if self.value(): # Check if a generic org is selected in the filter
+ if self.value():
return queryset.filter(
Q(domain_info__portfolio__organization_type=self.value())
| Q(domain_info__portfolio__isnull=True, domain_info__generic_org_type=self.value())
)
-
return queryset
class FederalTypeFilter(admin.SimpleListFilter):
"""Custom Federal Type filter that accomodates portfolio feature.
If we have a portfolio, use the portfolio's federal type. If not, use the
- federal type in the Domain Information object."""
+ organization in the Domain Request object."""
title = "federal type"
parameter_name = "converted_federal_types"
def lookups(self, request, model_admin):
- converted_federal_types = set()
-
- # Populate the set with tuples of (value, display value)
- for domain_info in DomainInformation.objects.all():
- converted_federal_type = domain_info.converted_federal_type # Actual value
- converted_federal_type_display = domain_info.converted_federal_type_display # Display value
-
- if converted_federal_type:
- converted_federal_types.add(
- (converted_federal_type, converted_federal_type_display) # Value, Display
+ # Annotate the queryset for efficient filtering
+ queryset = (
+ Domain.objects.annotate(
+ converted_federal_type=Case(
+ When(
+ domain_info__isnull=False,
+ domain_info__portfolio__isnull=False,
+ then=F("domain_info__portfolio__federal_agency__federal_type"),
+ ),
+ When(
+ domain_info__isnull=False,
+ domain_info__portfolio__isnull=True,
+ domain_info__federal_type__isnull=False,
+ then="domain_info__federal_agency__federal_type",
+ ),
+ default=Value(""),
+ output_field=CharField(),
)
+ )
+ .values_list("converted_federal_type", flat=True)
+ .distinct()
+ )
- # Sort the set by display value
- return sorted(converted_federal_types, key=lambda x: x[1]) # x[1] is the display value
+ # Filter out empty values and return sorted unique entries
+ return sorted(
+ [
+ (federal_type, BranchChoices.get_branch_label(federal_type))
+ for federal_type in queryset
+ if federal_type
+ ]
+ )
- # Filter queryset
def queryset(self, request, queryset):
- if self.value(): # Check if a federal type is selected in the filter
+ if self.value():
return queryset.filter(
- Q(domain_info__portfolio__federal_agency__federal_type=self.value())
- | Q(domain_info__portfolio__isnull=True, domain_info__federal_agency__federal_type=self.value())
+ Q(domain_info__portfolio__federal_type=self.value())
+ | Q(domain_info__portfolio__isnull=True, domain_info__federal_type=self.value())
)
return queryset
diff --git a/src/registrar/assets/src/js/getgov/portfolio-member-page.js b/src/registrar/assets/src/js/getgov/portfolio-member-page.js
index cfb83badc..d3422b722 100644
--- a/src/registrar/assets/src/js/getgov/portfolio-member-page.js
+++ b/src/registrar/assets/src/js/getgov/portfolio-member-page.js
@@ -18,11 +18,11 @@ export function initPortfolioNewMemberPageToggle() {
const unique_id = `${member_type}-${member_id}`;
let cancelInvitationButton = member_type === "invitedmember" ? "Cancel invitation" : "Remove member";
- wrapperDeleteAction.innerHTML = generateKebabHTML('remove-member', unique_id, cancelInvitationButton, `for ${member_name}`);
+ wrapperDeleteAction.innerHTML = generateKebabHTML('remove-member', unique_id, cancelInvitationButton, `More Options for ${member_name}`);
// This easter egg is only for fixtures that dont have names as we are displaying their emails
// All prod users will have emails linked to their account
- MembersTable.addMemberModal(num_domains, member_email || "Samwise Gamgee", member_delete_url, unique_id, wrapperDeleteAction);
+ MembersTable.addMemberDeleteModal(num_domains, member_email || member_name || "Samwise Gamgee", member_delete_url, unique_id, wrapperDeleteAction);
uswdsInitializeModals();
diff --git a/src/registrar/assets/src/js/getgov/table-base.js b/src/registrar/assets/src/js/getgov/table-base.js
index 338d5d98c..0abfee9b6 100644
--- a/src/registrar/assets/src/js/getgov/table-base.js
+++ b/src/registrar/assets/src/js/getgov/table-base.js
@@ -93,7 +93,6 @@ export function generateKebabHTML(action, unique_id, modal_button_text, screen_r
` : ''}
${modal_button_text}
- ${screen_reader_text}
`;
@@ -107,6 +106,7 @@ export function generateKebabHTML(action, unique_id, modal_button_text, screen_r
class="usa-button usa-button--unstyled usa-button--with-icon usa-accordion__button usa-button--more-actions"
aria-expanded="false"
aria-controls="more-actions-${unique_id}"
+ aria-label="${screen_reader_text}"
>