diff --git a/src/registrar/admin.py b/src/registrar/admin.py
index cb198cb6b..b2c41c07f 100644
--- a/src/registrar/admin.py
+++ b/src/registrar/admin.py
@@ -2997,6 +2997,7 @@ class PortfolioAdmin(ListHeaderAdmin):
"domain_requests",
"suborganizations",
"portfolio_type",
+ "creator",
]
def federal_type(self, obj: models.Portfolio):
diff --git a/src/registrar/assets/js/get-gov-admin.js b/src/registrar/assets/js/get-gov-admin.js
index c05ef090c..24f020b75 100644
--- a/src/registrar/assets/js/get-gov-admin.js
+++ b/src/registrar/assets/js/get-gov-admin.js
@@ -908,10 +908,28 @@ function initializeWidgetOnList(list, parentId) {
return;
}
+ // Determine if any changes are necessary to the display of portfolio type or federal type
+ // based on changes to the Federal Agency
+ let federalPortfolioApi = document.getElementById("federal_and_portfolio_types_from_agency_json_url").value;
+ fetch(`${federalPortfolioApi}?organization_type=${organizationType.value}&agency_name=${selectedText}`)
+ .then(response => {
+ const statusCode = response.status;
+ return response.json().then(data => ({ statusCode, data }));
+ })
+ .then(({ statusCode, data }) => {
+ if (data.error) {
+ console.error("Error in AJAX call: " + data.error);
+ return;
+ }
+ updateReadOnly(data.federal_type, '.field-federal_type');
+ updateReadOnly(data.portfolio_type, '.field-portfolio_type');
+ })
+ .catch(error => console.error("Error fetching federal and portfolio types: ", error));
+
// Hide the contactList initially.
// If we can update the contact information, it'll be shown again.
hideElement(contactList.parentElement);
-
+
let seniorOfficialApi = document.getElementById("senior_official_from_agency_json_url").value;
fetch(`${seniorOfficialApi}?agency_name=${selectedText}`)
.then(response => {
@@ -954,6 +972,7 @@ function initializeWidgetOnList(list, parentId) {
}
})
.catch(error => console.error("Error fetching senior official: ", error));
+
}
function handleStateTerritoryChange(stateTerritory, urbanizationField) {
@@ -965,6 +984,26 @@ function initializeWidgetOnList(list, parentId) {
}
}
+ /**
+ * Utility that selects a div from the DOM using selectorString,
+ * and updates a div within that div which has class of 'readonly'
+ * so that the text of the div is updated to updateText
+ * @param {*} updateText
+ * @param {*} selectorString
+ */
+ function updateReadOnly(updateText, selectorString) {
+ // find the div by selectorString
+ const selectedDiv = document.querySelector(selectorString);
+ if (selectedDiv) {
+ // find the nested div with class 'readonly' inside the selectorString div
+ const readonlyDiv = selectedDiv.querySelector('.readonly');
+ if (readonlyDiv) {
+ // Update the text content of the readonly div
+ readonlyDiv.textContent = updateText !== null ? updateText : '-';
+ }
+ }
+ }
+
function updateContactInfo(data) {
if (!contactList) return;
diff --git a/src/registrar/config/urls.py b/src/registrar/config/urls.py
index 413449896..19fa99809 100644
--- a/src/registrar/config/urls.py
+++ b/src/registrar/config/urls.py
@@ -24,7 +24,10 @@ from registrar.views.report_views import (
from registrar.views.domain_request import Step
from registrar.views.domain_requests_json import get_domain_requests_json
-from registrar.views.utility.api_views import get_senior_official_from_federal_agency_json
+from registrar.views.utility.api_views import (
+ get_senior_official_from_federal_agency_json,
+ get_federal_and_portfolio_types_from_federal_agency_json,
+)
from registrar.views.domains_json import get_domains_json
from registrar.views.utility import always_404
from api.views import available, get_current_federal, get_current_full
@@ -139,6 +142,11 @@ urlpatterns = [
get_senior_official_from_federal_agency_json,
name="get-senior-official-from-federal-agency-json",
),
+ path(
+ "admin/api/get-federal-and-portfolio-types-from-federal-agency-json/",
+ get_federal_and_portfolio_types_from_federal_agency_json,
+ name="get-federal-and-portfolio-types-from-federal-agency-json",
+ ),
path("admin/", admin.site.urls),
path(
"reports/export_data_type_user/",
diff --git a/src/registrar/models/portfolio.py b/src/registrar/models/portfolio.py
index 0f9904c31..fadcf8cac 100644
--- a/src/registrar/models/portfolio.py
+++ b/src/registrar/models/portfolio.py
@@ -131,9 +131,13 @@ class Portfolio(TimeStampedModel):
Returns a combination of organization_type / federal_type, seperated by ' - '.
If no federal_type is found, we just return the org type.
"""
- org_type_label = self.OrganizationChoices.get_org_label(self.organization_type)
- agency_type_label = BranchChoices.get_branch_label(self.federal_type)
- if self.organization_type == self.OrganizationChoices.FEDERAL and agency_type_label:
+ return self.get_portfolio_type(self.organization_type, self.federal_type)
+
+ @classmethod
+ def get_portfolio_type(cls, organization_type, federal_type):
+ org_type_label = cls.OrganizationChoices.get_org_label(organization_type)
+ agency_type_label = BranchChoices.get_branch_label(federal_type)
+ if organization_type == cls.OrganizationChoices.FEDERAL and agency_type_label:
return " - ".join([org_type_label, agency_type_label])
else:
return org_type_label
@@ -141,7 +145,11 @@ class Portfolio(TimeStampedModel):
@property
def federal_type(self):
"""Returns the federal_type value on the underlying federal_agency field"""
- return self.federal_agency.federal_type if self.federal_agency else None
+ return self.get_federal_type(self.federal_agency)
+
+ @classmethod
+ def get_federal_type(cls, federal_agency):
+ return federal_agency.federal_type if federal_agency else None
# == Getters for domains == #
def get_domains(self):
diff --git a/src/registrar/templates/django/admin/portfolio_change_form.html b/src/registrar/templates/django/admin/portfolio_change_form.html
index 9d59aae42..4eb941340 100644
--- a/src/registrar/templates/django/admin/portfolio_change_form.html
+++ b/src/registrar/templates/django/admin/portfolio_change_form.html
@@ -5,6 +5,8 @@
{% comment %} Stores the json endpoint in a url for easier access {% endcomment %}
{% url 'get-senior-official-from-federal-agency-json' as url %}
+ {% url 'get-federal-and-portfolio-types-from-federal-agency-json' as url %}
+
{{ block.super }}
{% endblock content %}
diff --git a/src/registrar/tests/test_api.py b/src/registrar/tests/test_api.py
index de52ad3d6..2597e65c2 100644
--- a/src/registrar/tests/test_api.py
+++ b/src/registrar/tests/test_api.py
@@ -5,6 +5,7 @@ from django.contrib.auth import get_user_model
from registrar.tests.common import create_superuser, create_user
from api.tests.common import less_console_noise_decorator
+from registrar.utility.constants import BranchChoices
class GetSeniorOfficialJsonTest(TestCase):
@@ -71,3 +72,40 @@ class GetSeniorOfficialJsonTest(TestCase):
self.assertEqual(response.status_code, 404)
data = response.json()
self.assertEqual(data["error"], "Senior Official not found")
+
+
+class GetFederalPortfolioTypeJsonTest(TestCase):
+ def setUp(self):
+ self.client = Client()
+ p = "password"
+ self.user = get_user_model().objects.create_user(username="testuser", password=p)
+
+ self.superuser = create_superuser()
+ self.analyst_user = create_user()
+
+ self.agency = FederalAgency.objects.create(agency="Test Agency", federal_type=BranchChoices.JUDICIAL)
+
+ self.api_url = reverse("get-federal-and-portfolio-types-from-federal-agency-json")
+
+ def tearDown(self):
+ User.objects.all().delete()
+ FederalAgency.objects.all().delete()
+
+ @less_console_noise_decorator
+ def test_get_federal_and_portfolio_types_json_authenticated_superuser(self):
+ """Test that a superuser can fetch the federal and portfolio types."""
+ p = "adminpass"
+ self.client.login(username="superuser", password=p)
+ response = self.client.get(self.api_url, {"agency_name": "Test Agency", "organization_type": "federal"})
+ self.assertEqual(response.status_code, 200)
+ data = response.json()
+ self.assertEqual(data["federal_type"], "Judicial")
+ self.assertEqual(data["portfolio_type"], "Federal - Judicial")
+
+ @less_console_noise_decorator
+ def test_get_federal_and_portfolio_types_json_authenticated_regularuser(self):
+ """Test that a regular user receives a 403 with an error message."""
+ p = "password"
+ self.client.login(username="testuser", password=p)
+ response = self.client.get(self.api_url, {"agency_name": "Test Agency", "organization_type": "federal"})
+ self.assertEqual(response.status_code, 302)
diff --git a/src/registrar/views/utility/api_views.py b/src/registrar/views/utility/api_views.py
index 2c9414d1d..97eb7e86c 100644
--- a/src/registrar/views/utility/api_views.py
+++ b/src/registrar/views/utility/api_views.py
@@ -5,6 +5,9 @@ from registrar.models import FederalAgency, SeniorOfficial
from django.contrib.admin.views.decorators import staff_member_required
from django.contrib.auth.decorators import login_required
+from registrar.models.portfolio import Portfolio
+from registrar.utility.constants import BranchChoices
+
logger = logging.getLogger(__name__)
@@ -34,3 +37,34 @@ def get_senior_official_from_federal_agency_json(request):
return JsonResponse(so_dict)
else:
return JsonResponse({"error": "Senior Official not found"}, status=404)
+
+
+@login_required
+@staff_member_required
+def get_federal_and_portfolio_types_from_federal_agency_json(request):
+ """Returns specific portfolio information as a JSON. Request must have
+ both agency_name and organization_type."""
+
+ # This API is only accessible to admins and analysts
+ superuser_perm = request.user.has_perm("registrar.full_access_permission")
+ analyst_perm = request.user.has_perm("registrar.analyst_access_permission")
+ if not request.user.is_authenticated or not any([analyst_perm, superuser_perm]):
+ return JsonResponse({"error": "You do not have access to this resource"}, status=403)
+
+ federal_type = None
+ portfolio_type = None
+
+ agency_name = request.GET.get("agency_name")
+ organization_type = request.GET.get("organization_type")
+ agency = FederalAgency.objects.filter(agency=agency_name).first()
+ if agency:
+ federal_type = Portfolio.get_federal_type(agency)
+ portfolio_type = Portfolio.get_portfolio_type(organization_type, federal_type)
+ federal_type = BranchChoices.get_branch_label(federal_type) if federal_type else "-"
+
+ response_data = {
+ "portfolio_type": portfolio_type,
+ "federal_type": federal_type,
+ }
+
+ return JsonResponse(response_data)