This commit is contained in:
David Kennedy 2025-03-09 10:39:49 -04:00
parent b474e0eb6b
commit 6702dca37d
No known key found for this signature in database
GPG key ID: 6528A5386E66B96B
2 changed files with 82 additions and 0 deletions

View file

@ -412,6 +412,17 @@ class DomainInformationAdminForm(forms.ModelForm):
class DomainInformationInlineForm(forms.ModelForm): class DomainInformationInlineForm(forms.ModelForm):
"""This form utilizes the custom widget for its class's ManyToMany UIs.""" """This form utilizes the custom widget for its class's ManyToMany UIs."""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# for OMB analysts, limit portfolio dropdown to FEB portfolios
user = self.request.user if hasattr(self, 'request') else None
if user and user.groups.filter(name="omb_analysts_group").exists():
self.fields["portfolio"].queryset = models.Portfolio.objects.filter(
Q(organization_type=DomainRequest.OrganizationChoices.FEDERAL) &
Q(federal_agency__federal_type=BranchChoices.EXECUTIVE)
)
class Meta: class Meta:
model = models.DomainInformation model = models.DomainInformation
fields = "__all__" fields = "__all__"
@ -2980,6 +2991,7 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportRegistrarModelAdmin):
"rejection_reason_email", "rejection_reason_email",
"action_needed_reason", "action_needed_reason",
"action_needed_reason_email", "action_needed_reason_email",
"portfolio",
] ]
autocomplete_fields = [ autocomplete_fields = [
@ -3754,6 +3766,12 @@ class DomainInformationInline(admin.StackedInline):
return form return form
def get_formset(self, request, obj=None, **kwargs):
"""Attach request to the formset so that it can be available in the form"""
formset = super().get_formset(request, obj, **kwargs)
formset.form.request = request # Attach request to form
return formset
class DomainResource(FsmModelResource): class DomainResource(FsmModelResource):
"""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

View file

@ -17,14 +17,17 @@ from registrar.models import (
Host, Host,
Portfolio, Portfolio,
) )
from registrar.models.federal_agency import FederalAgency
from registrar.models.public_contact import PublicContact from registrar.models.public_contact import PublicContact
from registrar.models.user_domain_role import UserDomainRole from registrar.models.user_domain_role import UserDomainRole
from registrar.utility.constants import BranchChoices
from .common import ( from .common import (
MockSESClient, MockSESClient,
completed_domain_request, completed_domain_request,
less_console_noise, less_console_noise,
create_superuser, create_superuser,
create_user, create_user,
create_omb_analyst_user,
create_ready_domain, create_ready_domain,
MockEppLib, MockEppLib,
GenericTestHelper, GenericTestHelper,
@ -49,6 +52,7 @@ class TestDomainAdminAsStaff(MockEppLib):
def setUpClass(self): def setUpClass(self):
super().setUpClass() super().setUpClass()
self.staffuser = create_user() self.staffuser = create_user()
self.omb_analyst = create_omb_analyst_user()
self.site = AdminSite() self.site = AdminSite()
self.admin = DomainAdmin(model=Domain, admin_site=self.site) self.admin = DomainAdmin(model=Domain, admin_site=self.site)
self.factory = RequestFactory() self.factory = RequestFactory()
@ -56,6 +60,24 @@ class TestDomainAdminAsStaff(MockEppLib):
def setUp(self): def setUp(self):
self.client = Client(HTTP_HOST="localhost:8080") self.client = Client(HTTP_HOST="localhost:8080")
self.client.force_login(self.staffuser) self.client.force_login(self.staffuser)
self.nonfebdomain = Domain.objects.create(name="nonfebexample.com")
self.febdomain = Domain.objects.create(name="febexample.com", state=Domain.State.READY)
self.fed_agency = FederalAgency.objects.create(
agency="New FedExec Agency", federal_type=BranchChoices.EXECUTIVE
)
self.portfolio = Portfolio.objects.create(
organization_name="new portfolio",
organization_type=DomainRequest.OrganizationChoices.FEDERAL,
federal_agency=self.fed_agency,
creator=self.staffuser,
)
self.domain_info = DomainInformation.objects.create(
domain=self.febdomain, portfolio=self.portfolio, creator=self.staffuser
)
self.nonfebportfolio = Portfolio.objects.create(
organization_name="non feb portfolio",
creator=self.staffuser,
)
super().setUp() super().setUp()
def tearDown(self): def tearDown(self):
@ -65,12 +87,54 @@ class TestDomainAdminAsStaff(MockEppLib):
Domain.objects.all().delete() Domain.objects.all().delete()
DomainInformation.objects.all().delete() DomainInformation.objects.all().delete()
DomainRequest.objects.all().delete() DomainRequest.objects.all().delete()
Portfolio.objects.all().delete()
self.fed_agency.delete()
@classmethod @classmethod
def tearDownClass(self): def tearDownClass(self):
User.objects.all().delete() User.objects.all().delete()
super().tearDownClass() super().tearDownClass()
@less_console_noise_decorator
def test_omb_analyst_view(self):
"""Ensure OMB analysts can view domain list."""
self.client.force_login(self.omb_analyst)
response = self.client.get(reverse("admin:registrar_domain_changelist"))
self.assertEqual(response.status_code, 200)
self.assertContains(response, self.febdomain.name)
self.assertNotContains(response, self.nonfebdomain.name)
self.assertNotContains(response, "Import")
self.assertNotContains(response, "Export")
@less_console_noise_decorator
def test_omb_analyst_change(self):
"""Ensure OMB analysts can view/edit federal executive branch domains."""
self.client.force_login(self.omb_analyst)
response = self.client.get(reverse("admin:registrar_domain_change", args=[self.nonfebdomain.id]))
self.assertEqual(response.status_code, 302)
response = self.client.get(reverse("admin:registrar_domain_change", args=[self.febdomain.id]))
self.assertEqual(response.status_code, 200)
self.assertContains(response, self.febdomain.name)
# test portfolio dropdown
self.assertContains(response, self.portfolio.organization_name)
self.assertNotContains(response, self.nonfebportfolio.organization_name)
# test buttons
self.assertNotContains(response, "Manage domain")
self.assertNotContains(response, "Get registry status")
self.assertNotContains(response, "Extend expiration date")
self.assertNotContains(response, "Remove from registry")
self.assertContains(response, "Place hold")
self.assertContains(response, "Save")
self.assertNotContains(response, ">Delete<")
# test whether fields are readonly or editable
self.assertContains(response, "id_domain_info-0-portfolio")
self.assertContains(response, "id_domain_info-0-sub_organization")
self.assertNotContains(response, "id_domain_info-0-creator")
# self.assertNotContains(response, "id_email")
# self.assertContains(response, "closelink")
# self.assertNotContains(response, "Save")
# self.assertNotContains(response, "Delete")
@less_console_noise_decorator @less_console_noise_decorator
def test_staff_can_see_cisa_region_federal(self): def test_staff_can_see_cisa_region_federal(self):
"""Tests if staff can see CISA Region: N/A""" """Tests if staff can see CISA Region: N/A"""