checking in updates

This commit is contained in:
David Kennedy 2024-09-17 10:54:29 -04:00
parent 6584eb1547
commit bec50b4e08
No known key found for this signature in database
GPG key ID: 6528A5386E66B96B
4 changed files with 195 additions and 6 deletions

View file

@ -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,10 +2354,72 @@ 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 = "<table><thead><tr><th>UID</th><th>Name</th><th>Email</th>" + "</tr></thead><tbody>"
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 += "<tr>"
domain_manager_details += f'<td><a href="{change_url}">{escape(domain_manager.username)}</a>'
domain_manager_details += f"<td>{escape(full_name)}</td>"
domain_manager_details += f"<td>{escape(domain_manager.email)}</td>"
domain_manager_details += "</tr>"
domain_manager_details += "</tbody></table>"
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 = "<table><thead><tr><th>Email</th><th>Status</th>" + "</tr></thead><tbody>"
for domain_invitation in domain_invitations:
domain_invitation_details += "<tr>"
domain_invitation_details += f"<td>{escape(domain_invitation.email)}</td>"
domain_invitation_details += f"<td>{escape(domain_invitation.status.capitalize())}</td>"
domain_invitation_details += "</tr>"
domain_invitation_details += "</tbody></table>"
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
analysts can edit this through this inline, but not through the model normally"""
@ -2466,7 +2530,7 @@ class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin):
fieldsets = (
(
"Domain Information",
None,
{"fields": ["state", "expiration_date", "first_ready", "deleted", "dnssecdata", "nameservers"]},
),
)

View file

@ -137,7 +137,7 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html)
{% endfor %}
{% endwith %}
</div>
{% 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" %}
<div class="readonly">{{ field.contents|safe }}</div>
{% elif field.field.name == "display_members" %}
<div class="readonly">

View file

@ -1,4 +1,4 @@
{% extends 'admin/stacked_no_heading.html' %}
{% extends 'admin/stacked.html' %}
{% load i18n static %}
{% block fieldset %}

View file

@ -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'<a href="/admin/registrar/user/{admin_user_1.pk}/change/">testuser1</a>',
domain_managers,
)
self.assertIn("Gerald Meoward", domain_managers)
self.assertIn("meoward@gov.gov", domain_managers)
self.assertIn(f'<a href="/admin/registrar/user/{admin_user_2.pk}/change/">testuser2</a>', 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'<a href="/admin/registrar/user/{admin_user_1.pk}/change/">testuser1</a>',
domain_managers,
)
self.assertIn("Gerald Meoward", domain_managers)
self.assertIn("meoward@gov.gov", domain_managers)
self.assertIn(f'<a href="/admin/registrar/user/{admin_user_2.pk}/change/">testuser2</a>', domain_managers)
self.assertIn("Arnold Poopy", domain_managers)
self.assertIn("poopy@gov.gov", domain_managers)
class TestDomainAdminWithClient(TestCase):
"""Test DomainAdmin class as super user.