diff --git a/src/registrar/admin.py b/src/registrar/admin.py
index 412ba4449..f43261885 100644
--- a/src/registrar/admin.py
+++ b/src/registrar/admin.py
@@ -1830,10 +1830,12 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
form = DomainRequestAdminForm
change_form_template = "django/admin/domain_request_change_form.html"
+ # ------ Filters ------
+ # Define custom filters
class StatusListFilter(MultipleChoiceListFilter):
"""Custom status filter which is a multiple choice filter"""
- title = "Status"
+ title = "status"
parameter_name = "status__in"
template = "django/admin/multiple_choice_list_filter.html"
@@ -1877,7 +1879,7 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
If we have a portfolio, use the portfolio's federal type. If not, use the
organization in the Domain Request object."""
- title = "federal Type"
+ title = "federal type"
parameter_name = "converted_federal_types"
def lookups(self, request, model_admin):
@@ -1965,6 +1967,45 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
if self.value() == "0":
return queryset.filter(Q(is_election_board=False) | Q(is_election_board=None))
+ class PortfolioFilter(admin.SimpleListFilter):
+ """Define a custom filter for portfolio"""
+
+ title = _("portfolio")
+ parameter_name = "portfolio__isnull"
+
+ def lookups(self, request, model_admin):
+ return (
+ ("1", _("Yes")),
+ ("0", _("No")),
+ )
+
+ def queryset(self, request, queryset):
+ if self.value() == "1":
+ return queryset.filter(Q(portfolio__isnull=False))
+ if self.value() == "0":
+ return queryset.filter(Q(portfolio__isnull=True))
+
+ # ------ Custom fields ------
+ def custom_election_board(self, obj):
+ return "Yes" if obj.is_election_board else "No"
+
+ custom_election_board.admin_order_field = "is_election_board" # type: ignore
+ custom_election_board.short_description = "Election office" # type: ignore
+
+ @admin.display(description=_("Requested Domain"))
+ def custom_requested_domain(self, obj):
+ # Example: Show different icons based on `status`
+ url = reverse("admin:registrar_domainrequest_changelist") + f"?portfolio={obj.id}"
+ text = obj.requested_domain
+ if obj.portfolio:
+ return format_html('
{}', url, text)
+ return format_html('{}', url, text)
+
+ custom_requested_domain.admin_order_field = "requested_domain__name" # type: ignore
+
+ # ------ Converted fields ------
+ # These fields map to @Property methods and
+ # require these custom definitions to work properly
@admin.display(description="Suborganization options")
def reject_suborganization_button(self, obj):
"""Custom field to display the reject suborganization button"""
@@ -1976,7 +2017,13 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
@admin.display(description=_("Organization Name"))
def converted_organization_name(self, obj):
- return obj.converted_organization_name
+ # Example: Show different icons based on `status`
+ if obj.portfolio:
+ url = reverse("admin:registrar_portfolio_change", args=[obj.portfolio.id])
+ text = obj.converted_organization_name
+ return format_html('{}', url, text)
+ else:
+ return obj.converted_organization_name
@admin.display(description=_("Federal Agency"))
def converted_federal_agency(self, obj):
@@ -1994,34 +2041,7 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
def converted_state_territory(self, obj):
return obj.converted_state_territory
- # Columns
- list_display = [
- "requested_domain",
- "first_submitted_date",
- "last_submitted_date",
- "last_status_update",
- "status",
- "custom_election_board",
- "converted_generic_org_type",
- "converted_organization_name",
- "converted_federal_agency",
- "converted_federal_type",
- "converted_city",
- "converted_state_territory",
- "investigator",
- ]
-
- orderable_fk_fields = [
- ("requested_domain", "name"),
- ("investigator", ["first_name", "last_name"]),
- ]
-
- def custom_election_board(self, obj):
- return "Yes" if obj.is_election_board else "No"
-
- custom_election_board.admin_order_field = "is_election_board" # type: ignore
- custom_election_board.short_description = "Election office" # type: ignore
-
+ # ------ Portfolio fields ------
# Define methods to display fields from the related portfolio
def portfolio_senior_official(self, obj) -> Optional[SeniorOfficial]:
return obj.portfolio.senior_official if obj.portfolio and obj.portfolio.senior_official else None
@@ -2091,10 +2111,33 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
def status_history(self, obj):
return "No changelog to display."
- status_history.short_description = "Status History" # type: ignore
+ status_history.short_description = "Status history" # type: ignore
+
+ # Columns
+ list_display = [
+ "custom_requested_domain",
+ "first_submitted_date",
+ "last_submitted_date",
+ "last_status_update",
+ "status",
+ "custom_election_board",
+ "converted_generic_org_type",
+ "converted_organization_name",
+ "converted_federal_agency",
+ "converted_federal_type",
+ "converted_city",
+ "converted_state_territory",
+ "investigator",
+ ]
+
+ orderable_fk_fields = [
+ ("requested_domain", "name"),
+ ("investigator", ["first_name", "last_name"]),
+ ]
# Filters
list_filter = (
+ PortfolioFilter,
StatusListFilter,
GenericOrgFilter,
FederalTypeFilter,
@@ -2104,13 +2147,14 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
)
# Search
+ # NOTE: converted fields are included in the override for get_search_results
search_fields = [
"requested_domain__name",
"creator__email",
"creator__first_name",
"creator__last_name",
]
- search_help_text = "Search by domain or creator."
+ search_help_text = "Search by domain, creator, or organization name."
fieldsets = [
(
@@ -2278,9 +2322,6 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
"cisa_representative_first_name",
"cisa_representative_last_name",
"cisa_representative_email",
- "requested_suborganization",
- "suborganization_city",
- "suborganization_state_territory",
]
autocomplete_fields = [
@@ -2699,6 +2740,25 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
qs = qs.filter(portfolio=portfolio_id)
return qs
+ def get_search_results(self, request, queryset, search_term):
+ # Call the parent's method to apply default search logic
+ base_queryset, use_distinct = super().get_search_results(request, queryset, search_term)
+
+ # Add custom search logic for the annotated field
+ if search_term:
+ annotated_queryset = queryset.filter(
+ # converted_organization_name
+ Q(portfolio__organization_name__icontains=search_term)
+ | Q(portfolio__isnull=True, organization_name__icontains=search_term)
+ )
+
+ # Combine the two querysets using union
+ combined_queryset = base_queryset | annotated_queryset
+ else:
+ combined_queryset = base_queryset
+
+ return combined_queryset, use_distinct
+
class TransitionDomainAdmin(ListHeaderAdmin):
"""Custom transition domain admin class."""
@@ -3753,9 +3813,9 @@ class PortfolioAdmin(ListHeaderAdmin):
"senior_official",
]
- analyst_readonly_fields = [
- "organization_name",
- ]
+ # Even though this is empty, I will leave it as a stub for easy changes in the future
+ # rather than strip it out of our logic.
+ analyst_readonly_fields = [] # type: ignore
def get_admin_users(self, obj):
# Filter UserPortfolioPermission objects related to the portfolio
diff --git a/src/registrar/tests/test_admin_request.py b/src/registrar/tests/test_admin_request.py
index 9bdc1fbb4..b464eb538 100644
--- a/src/registrar/tests/test_admin_request.py
+++ b/src/registrar/tests/test_admin_request.py
@@ -1733,9 +1733,6 @@ class TestDomainRequestAdmin(MockEppLib):
"cisa_representative_first_name",
"cisa_representative_last_name",
"cisa_representative_email",
- "requested_suborganization",
- "suborganization_city",
- "suborganization_state_territory",
]
self.assertEqual(readonly_fields, expected_fields)
@@ -1970,6 +1967,7 @@ class TestDomainRequestAdmin(MockEppLib):
# Grab the current list of table filters
readonly_fields = self.admin.get_list_filter(request)
expected_fields = (
+ DomainRequestAdmin.PortfolioFilter,
DomainRequestAdmin.StatusListFilter,
DomainRequestAdmin.GenericOrgFilter,
DomainRequestAdmin.FederalTypeFilter,