From bec50b4e08c8629eac47d83485483818f66c20b5 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Tue, 17 Sep 2024 10:54:29 -0400 Subject: [PATCH] checking in updates --- src/registrar/admin.py | 72 +++++++++- .../admin/includes/detail_table_fieldset.html | 2 +- .../includes/domain_info_inline_stacked.html | 2 +- src/registrar/tests/test_admin_domain.py | 125 ++++++++++++++++++ 4 files changed, 195 insertions(+), 6 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index c1b90083e..84649913d 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -11,6 +11,7 @@ from django.conf import settings from django.shortcuts import redirect from django_fsm import get_available_FIELD_transitions, FSMField from registrar.models.domain_information import DomainInformation +from registrar.models.domain_invitation import DomainInvitation from registrar.models.user_portfolio_permission import UserPortfolioPermission from registrar.models.utility.portfolio_helper import UserPortfolioPermissionChoices, UserPortfolioRoleChoices from waffle.decorators import flag_is_active @@ -2340,11 +2341,12 @@ class DomainInformationInline(admin.StackedInline): template = "django/admin/includes/domain_info_inline_stacked.html" model = models.DomainInformation - fieldsets = DomainInformationAdmin.fieldsets - readonly_fields = DomainInformationAdmin.readonly_fields + fieldsets = list(DomainInformationAdmin.fieldsets) + readonly_fields = list(DomainInformationAdmin.readonly_fields) analyst_readonly_fields = DomainInformationAdmin.analyst_readonly_fields autocomplete_fields = DomainInformationAdmin.autocomplete_fields + readonly_fields.extend(["domain_managers", "invited_domain_managers"]) # type: ignore # Removing specific fields from the first fieldset dynamically fieldsets[0][1]["fields"] = [ field for field in fieldsets[0][1]["fields"] if field not in ["creator", "submitter", "domain_request", "notes"] @@ -2352,9 +2354,71 @@ class DomainInformationInline(admin.StackedInline): fieldsets[2][1]["fields"] = [ field for field in fieldsets[2][1]["fields"] if field not in ["other_contacts", "no_other_contacts_rationale"] ] - fieldsets[3][1]["fields"].extend(["other_contacts", "no_other_contacts_rationale"]) # type: ignore + fieldsets[2][1]["fields"].extend(["domain_managers", "invited_domain_managers"]) # type: ignore fieldsets_to_move = fieldsets.pop(3) fieldsets.append(fieldsets_to_move) + + + def get_domain_managers(self, obj): + user_domain_roles = UserDomainRole.objects.filter(domain=obj.domain) + user_ids = user_domain_roles.values_list("user_id", flat=True) + domain_managers = User.objects.filter(id__in=user_ids) + return domain_managers + + def get_domain_invitations(self, obj): + domain_invitations = DomainInvitation.objects.filter( + domain=obj.domain, status=DomainInvitation.DomainInvitationStatus.INVITED + ) + return domain_invitations + + def domain_managers(self, obj): + """Get joined users who have roles/perms that are not Admin, unpack and return an HTML block. + + DJA readonly can't handle querysets, so we need to unpack and return html here. + Alternatively, we could return querysets in context but that would limit where this + data would display in a custom change form without extensive template customization. + + Will be used in the after_help_text block.""" + domain_managers = self.get_domain_managers(obj) + if not domain_managers: + return "No domain managers found." + + domain_manager_details = "" + "" + for domain_manager in domain_managers: + full_name = domain_manager.get_formatted_name() + change_url = reverse("admin:registrar_user_change", args=[domain_manager.pk]) + domain_manager_details += "" + domain_manager_details += f'" + domain_manager_details += f"" + domain_manager_details += "" + domain_manager_details += "
UIDNameEmail
{escape(domain_manager.username)}' + domain_manager_details += f"{escape(full_name)}{escape(domain_manager.email)}
" + return format_html(domain_manager_details) + + domain_managers.short_description = "Domain Managers" # type: ignore + + def invited_domain_managers(self, obj): + """Get emails which have been invited to the domain, unpack and return an HTML block. + + DJA readonly can't handle querysets, so we need to unpack and return html here. + Alternatively, we could return querysets in context but that would limit where this + data would display in a custom change form without extensive template customization. + + Will be used in the after_help_text block.""" + domain_invitations = self.get_domain_invitations(obj) + if not domain_invitations: + return "No invited domain managers found." + + domain_invitation_details = "" + "" + for domain_invitation in domain_invitations: + domain_invitation_details += "" + domain_invitation_details += f"" + domain_invitation_details += f"" + domain_invitation_details += "" + domain_invitation_details += "
EmailStatus
{escape(domain_invitation.email)}{escape(domain_invitation.status.capitalize())}
" + return format_html(domain_invitation_details) + + invited_domain_managers.short_description = "Invited Domain Managers" # type: ignore def has_change_permission(self, request, obj=None): """Custom has_change_permission override so that we can specify that @@ -2466,7 +2530,7 @@ class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin): fieldsets = ( ( - "Domain Information", + None, {"fields": ["state", "expiration_date", "first_ready", "deleted", "dnssecdata", "nameservers"]}, ), ) diff --git a/src/registrar/templates/django/admin/includes/detail_table_fieldset.html b/src/registrar/templates/django/admin/includes/detail_table_fieldset.html index e22bcb571..a02162167 100644 --- a/src/registrar/templates/django/admin/includes/detail_table_fieldset.html +++ b/src/registrar/templates/django/admin/includes/detail_table_fieldset.html @@ -137,7 +137,7 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html) {% endfor %} {% endwith %} - {% elif field.field.name == "display_admins" %} + {% elif field.field.name == "display_admins" or field.field.name == "domain_managers" or field.field.namd == "invited_domain_managers" %}
{{ field.contents|safe }}
{% elif field.field.name == "display_members" %}
diff --git a/src/registrar/templates/django/admin/includes/domain_info_inline_stacked.html b/src/registrar/templates/django/admin/includes/domain_info_inline_stacked.html index 9638bb7cb..414c485e5 100644 --- a/src/registrar/templates/django/admin/includes/domain_info_inline_stacked.html +++ b/src/registrar/templates/django/admin/includes/domain_info_inline_stacked.html @@ -1,4 +1,4 @@ -{% extends 'admin/stacked_no_heading.html' %} +{% extends 'admin/stacked.html' %} {% load i18n static %} {% block fieldset %} diff --git a/src/registrar/tests/test_admin_domain.py b/src/registrar/tests/test_admin_domain.py index 49f095a25..bfbdad81d 100644 --- a/src/registrar/tests/test_admin_domain.py +++ b/src/registrar/tests/test_admin_domain.py @@ -334,6 +334,131 @@ class TestDomainAdminAsStaff(MockEppLib): domain.delete() +class TestDomainInformationInline(MockEppLib): + """Test DomainAdmin class, specifically the DomainInformationInline class, as staff user. + + Notes: + all tests share staffuser; do not change staffuser model in tests + tests have available staffuser, client, and admin + """ + + @classmethod + def setUpClass(cls): + super().setUpClass() + cls.staffuser = create_user() + cls.site = AdminSite() + cls.admin = DomainAdmin(model=Domain, admin_site=cls.site) + cls.factory = RequestFactory() + + def setUp(self): + self.client = Client(HTTP_HOST="localhost:8080") + self.client.force_login(self.staffuser) + super().setUp() + + def tearDown(self): + super().tearDown() + Host.objects.all().delete() + UserDomainRole.objects.all().delete() + Domain.objects.all().delete() + DomainInformation.objects.all().delete() + DomainRequest.objects.all().delete() + + @classmethod + def tearDownClass(cls): + User.objects.all().delete() + super().tearDownClass() + + @less_console_noise_decorator + def test_domain_managers_display(self): + """Tests the custom domain managers field""" + admin_user_1 = User.objects.create( + username="testuser1", + first_name="Gerald", + last_name="Meoward", + email="meoward@gov.gov", + ) + + domain_request = completed_domain_request( + status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=self.staffuser, name="fake.gov" + ) + domain_request.approve() + _domain_info = DomainInformation.objects.filter(domain=domain_request.approved_domain).get() + domain = Domain.objects.filter(domain_info=_domain_info).get() + + UserDomainRole.objects.get_or_create(user=admin_user_1, domain=domain, role=UserDomainRole.Roles.MANAGER) + + admin_user_2 = User.objects.create( + username="testuser2", + first_name="Arnold", + last_name="Poopy", + email="poopy@gov.gov", + ) + + UserDomainRole.objects.get_or_create(user=admin_user_2, domain=domain, role=UserDomainRole.Roles.MANAGER) + + # Get the first inline (DomainInformationInline) + inline_instance = self.admin.inlines[0](self.admin.model, self.admin.admin_site) + + # Call the domain_managers method + domain_managers = inline_instance.domain_managers(domain.domain_info) + + self.assertIn( + f'testuser1', + domain_managers, + ) + self.assertIn("Gerald Meoward", domain_managers) + self.assertIn("meoward@gov.gov", domain_managers) + self.assertIn(f'testuser2', domain_managers) + self.assertIn("Arnold Poopy", domain_managers) + self.assertIn("poopy@gov.gov", domain_managers) + + @less_console_noise_decorator + def test_invited_domain_managers_display(self): + """Tests the custom invited domain managers field""" + admin_user_1 = User.objects.create( + username="testuser1", + first_name="Gerald", + last_name="Meoward", + email="meoward@gov.gov", + ) + + domain_request = completed_domain_request( + status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=self.staffuser, name="fake.gov" + ) + domain_request.approve() + _domain_info = DomainInformation.objects.filter(domain=domain_request.approved_domain).get() + domain = Domain.objects.filter(domain_info=_domain_info).get() + + # domain, _ = Domain.objects.get_or_create(name="fake.gov", state=Domain.State.READY) + UserDomainRole.objects.get_or_create(user=admin_user_1, domain=domain, role=UserDomainRole.Roles.MANAGER) + + admin_user_2 = User.objects.create( + username="testuser2", + first_name="Arnold", + last_name="Poopy", + email="poopy@gov.gov", + ) + + UserDomainRole.objects.get_or_create(user=admin_user_2, domain=domain, role=UserDomainRole.Roles.MANAGER) + + # Get the first inline (DomainInformationInline) + inline_instance = self.admin.inlines[0](self.admin.model, self.admin.admin_site) + + # Call the domain_managers method + domain_managers = inline_instance.domain_managers(domain.domain_info) + # domain_managers = self.admin.get_inlinesdomain_managers(self.domain) + + self.assertIn( + f'testuser1', + domain_managers, + ) + self.assertIn("Gerald Meoward", domain_managers) + self.assertIn("meoward@gov.gov", domain_managers) + self.assertIn(f'testuser2', domain_managers) + self.assertIn("Arnold Poopy", domain_managers) + self.assertIn("poopy@gov.gov", domain_managers) + + class TestDomainAdminWithClient(TestCase): """Test DomainAdmin class as super user.