diff --git a/src/registrar/admin.py b/src/registrar/admin.py index bc26a00e1..e75ffb65f 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -5,6 +5,7 @@ import json from django.template.loader import get_template from django import forms from django.db.models import Value, CharField, Q +from django.template.loader import render_to_string from django.db.models.functions import Concat, Coalesce from django.http import HttpResponseRedirect from django.conf import settings @@ -2945,12 +2946,13 @@ class PortfolioAdmin(ListHeaderAdmin): # This is the fieldset display when adding a new model add_fieldsets = [ - (None, {"fields": ["organization_name", "creator", "notes"]}), + (None, {"fields": ["creator", "notes"]}), ("Type of organization", {"fields": ["organization_type"]}), ( "Organization name and mailing address", { "fields": [ + "organization_name", "federal_agency", "state_territory", "address_line1", @@ -3043,93 +3045,6 @@ class PortfolioAdmin(ListHeaderAdmin): else: return [] - def display_admins(self, obj): - """Get joined users who are 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 field_readonly block""" - admins = self.get_user_portfolio_permission_admins(obj) - if not admins: - return format_html("

No admins found.

") - - admin_details = "" - for i, portfolio_admin in enumerate(admins): - change_url = reverse("admin:registrar_userportfoliopermission_change", args=[portfolio_admin.pk]) - - address_id = f"portfolio-administrator-{portfolio_admin.pk}" - if len(admins) > 1: - admin_details += ( - f'' - ) - admin_details += f'
' - admin_details += f'{escape(portfolio_admin.user)}
' - admin_details += f"{escape(portfolio_admin.user.title)}
" - admin_details += f"{escape(portfolio_admin.user.email)}" - admin_details += "
" - admin_details += f"{escape(portfolio_admin.user.phone)}" - admin_details += "
" - return format_html(admin_details) - - display_admins.short_description = "Administrators" # type: ignore - - def display_members(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.""" - members = self.get_user_portfolio_permission_non_admins(obj) - if not members: - return "" - - member_details = ( - "" - + "" - ) - for member in members: - full_name = member.user.get_formatted_name() - member_details += "" - member_details += f"" - member_details += f"" - member_details += f"" - member_details += f"" - member_details += "" - member_details += "
NameTitleEmailPhoneRoles
{escape(full_name)}{escape(member.user.title)}{escape(member.user.email)}{escape(member.user.phone)}" - for role in member.user.portfolio_role_summary(obj): - member_details += f"{escape(role)} " - member_details += "
" - return format_html(member_details) - - display_members.short_description = "Members" # type: ignore - - def display_members_summary(self, obj): - """Will be passed as context and used in the field_readonly block.""" - members = self.get_user_portfolio_permission_non_admins(obj) - if not members: - return {} - - return self.get_field_links_as_list(members, "userportfoliopermission", attribute_name="user", separator=", ") - def federal_type(self, obj: models.Portfolio): """Returns the federal_type field""" return BranchChoices.get_branch_label(obj.federal_type) if obj.federal_type else "-" @@ -3181,6 +3096,28 @@ class PortfolioAdmin(ListHeaderAdmin): domain_requests.short_description = "Domain requests" # type: ignore + def display_admins(self, obj): + """Returns the number of administrators for this portfolio""" + admin_count = len(self.get_user_portfolio_permission_admins(obj)) + if admin_count > 0: + url = reverse("admin:registrar_userportfoliopermission_changelist") + f"?portfolio={obj.id}" + # Create a clickable link with the count + return format_html(f'{admin_count} administrators') + return "No administrators found." + + display_admins.short_description = "Administrators" # type: ignore + + def display_members(self, obj): + """Returns the number of members for this portfolio""" + member_count = len(self.get_user_portfolio_permission_non_admins(obj)) + if member_count > 0: + url = reverse("admin:registrar_userportfoliopermission_changelist") + f"?portfolio={obj.id}" + # Create a clickable link with the count + return format_html(f'{member_count} members') + return "No additional members found." + + display_members.short_description = "Members" # type: ignore + # Creates select2 fields (with search bars) autocomplete_fields = [ "creator", @@ -3254,7 +3191,8 @@ class PortfolioAdmin(ListHeaderAdmin): obj = self.get_object(request, object_id) extra_context = extra_context or {} extra_context["skip_additional_contact_info"] = True - extra_context["display_members_summary"] = self.display_members_summary(obj) + extra_context["members"] = self.get_user_portfolio_permission_non_admins(obj) + extra_context["admins"] = self.get_user_portfolio_permission_admins(obj) return super().change_view(request, object_id, form_url, extra_context) def save_model(self, request, obj, form, change): diff --git a/src/registrar/assets/js/get-gov-admin.js b/src/registrar/assets/js/get-gov-admin.js index 032d9f84f..117be405d 100644 --- a/src/registrar/assets/js/get-gov-admin.js +++ b/src/registrar/assets/js/get-gov-admin.js @@ -882,7 +882,10 @@ function initializeWidgetOnList(list, parentId) { // Handle hiding the organization name field when the organization_type is federal. // Run this first one page load, then secondly on a change event. - let organizationNameContainer = document.querySelector(".field-organization_name") + let organizationNameContainer = document.querySelector(".field-organization_name"); + if (!organizationNameContainer) { + organizationNameContainer = document.querySelector("#id_organization_name"); + } handleOrganizationTypeChange(organizationType, organizationNameContainer); organizationType.addEventListener("change", function() { handleOrganizationTypeChange(organizationType, organizationNameContainer); diff --git a/src/registrar/templates/django/admin/includes/portfolio_admins_table.html b/src/registrar/templates/django/admin/includes/portfolio_admins_table.html new file mode 100644 index 000000000..78bc53a38 --- /dev/null +++ b/src/registrar/templates/django/admin/includes/portfolio_admins_table.html @@ -0,0 +1,42 @@ +{% load static url_helpers %} + +
+ Details +
+ + + + + + + + + + + {% for admin in admins %} + {% url 'admin:registrar_userportfoliopermission_change' admin.pk as url %} + + + + + + + + {% endfor %} + +
NameTitleEmailPhone
{{ admin.user.get_formatted_name}}{{ admin.user.title }}{{ admin.user.email }}{{ admin.user.phone }} + + +
+
+
\ No newline at end of file diff --git a/src/registrar/templates/django/admin/includes/portfolio_fieldset.html b/src/registrar/templates/django/admin/includes/portfolio_fieldset.html index aae096a2e..16f637a4e 100644 --- a/src/registrar/templates/django/admin/includes/portfolio_fieldset.html +++ b/src/registrar/templates/django/admin/includes/portfolio_fieldset.html @@ -3,16 +3,8 @@ {% load static url_helpers %} {% block field_readonly %} - {% if field.field.name == "display_admins" %} + {% if field.field.name == "display_admins" or field.field.name == "display_members" %}
{{ field.contents|safe }}
- {% elif field.field.name == "display_members" %} -
- {% if display_members_summary %} - {{ display_members_summary }} - {% else %} -

No additional members found.

- {% endif %} -
{% elif field.field.name == "roles" %}
{% if get_readable_roles %} @@ -40,12 +32,9 @@ {% include "django/admin/includes/contact_detail_list.html" with user=original_object.senior_official no_title_top_padding=field.is_readonly %}
- {% elif field.field.name == "display_members" and field.contents %} -
- Details -
- {{ field.contents|safe }} -
-
+ {% elif field.field.name == "display_admins" %} + {% include "django/admin/includes/portfolio_admins_table.html" with admins=admins %} + {% elif field.field.name == "display_members" %} + {% include "django/admin/includes/portfolio_members_table.html" with members=members %} {% endif %} {% endblock after_help_text %} \ No newline at end of file diff --git a/src/registrar/templates/django/admin/includes/portfolio_members_table.html b/src/registrar/templates/django/admin/includes/portfolio_members_table.html new file mode 100644 index 000000000..44b954077 --- /dev/null +++ b/src/registrar/templates/django/admin/includes/portfolio_members_table.html @@ -0,0 +1,49 @@ +{% load custom_filters %} +{% load static url_helpers %} + +
+ Details +
+ + + + + + + + + + + + {% for member in members %} + {% url 'admin:registrar_userportfoliopermission_change' member.pk as url %} + + + + + + + + + {% endfor %} + +
NameTitleEmailPhoneRoles
{{ member.user.get_formatted_name}}{{ member.user.title }}{{ member.user.email }}{{ member.user.phone }} + {% for role in member.user|portfolio_role_summary:original %} + {{ role }} + {% endfor %} + + + +
+
+
\ No newline at end of file diff --git a/src/registrar/templatetags/custom_filters.py b/src/registrar/templatetags/custom_filters.py index c6c7c97d1..de9b7bfa1 100644 --- a/src/registrar/templatetags/custom_filters.py +++ b/src/registrar/templatetags/custom_filters.py @@ -239,3 +239,12 @@ def is_portfolio_subpage(path): "senior-official", ] return get_url_name(path) in url_names + + +@register.filter(name="portfolio_role_summary") +def portfolio_role_summary(user, portfolio): + """Returns the value of user.portfolio_role_summary""" + if user and portfolio: + return user.portfolio_role_summary(portfolio) + else: + return [] \ No newline at end of file