mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-28 13:36:30 +02:00
Replace admins and members with detail table
This commit is contained in:
parent
4414f8f59d
commit
1e082f5fd1
6 changed files with 136 additions and 106 deletions
|
@ -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("<p>No admins found.</p>")
|
||||
|
||||
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'<label class="organization-admin-label padding-top-0" for="{address_id}">'
|
||||
f'Organization admin {i+1}'
|
||||
'</label>'
|
||||
)
|
||||
admin_details += f'<address id="{address_id}" class="margin-bottom-2 dja-address-contact-list">'
|
||||
admin_details += f'<a href="{change_url}">{escape(portfolio_admin.user)}</a><br>'
|
||||
admin_details += f"{escape(portfolio_admin.user.title)}<br>"
|
||||
admin_details += f"{escape(portfolio_admin.user.email)}"
|
||||
admin_details += "<div class='admin-icon-group admin-icon-group__clipboard-link'>"
|
||||
admin_details += (
|
||||
f"<input aria-hidden='true' class='display-none' value='{escape(portfolio_admin.user.email)}'>"
|
||||
)
|
||||
admin_details += (
|
||||
"<button class='usa-button usa-button--unstyled padding-right-1 usa-button--icon padding-left-05"
|
||||
+ "button--clipboard copy-to-clipboard text-no-underline' type='button'>"
|
||||
)
|
||||
admin_details += "<svg class='usa-icon'>"
|
||||
admin_details += "<use aria-hidden='true' xlink:href='/public/img/sprite.svg#content_copy'></use>"
|
||||
admin_details += "</svg>"
|
||||
admin_details += "Copy"
|
||||
admin_details += "</button>"
|
||||
admin_details += "</div><br>"
|
||||
admin_details += f"{escape(portfolio_admin.user.phone)}"
|
||||
admin_details += "</address>"
|
||||
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 = (
|
||||
"<table><thead><tr><th>Name</th><th>Title</th><th>Email</th>"
|
||||
+ "<th>Phone</th><th>Roles</th></tr></thead><tbody>"
|
||||
)
|
||||
for member in members:
|
||||
full_name = member.user.get_formatted_name()
|
||||
member_details += "<tr>"
|
||||
member_details += f"<td>{escape(full_name)}</td>"
|
||||
member_details += f"<td>{escape(member.user.title)}</td>"
|
||||
member_details += f"<td>{escape(member.user.email)}</td>"
|
||||
member_details += f"<td>{escape(member.user.phone)}</td>"
|
||||
member_details += "<td>"
|
||||
for role in member.user.portfolio_role_summary(obj):
|
||||
member_details += f"<span class='usa-tag'>{escape(role)}</span> "
|
||||
member_details += "</td></tr>"
|
||||
member_details += "</tbody></table>"
|
||||
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'<a href="{url}">{admin_count} administrators</a>')
|
||||
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'<a href="{url}">{member_count} members</a>')
|
||||
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):
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
{% load static url_helpers %}
|
||||
|
||||
<details class="margin-top-1 dja-detail-table" aria-role="button" closed>
|
||||
<summary class="padding-1 padding-left-0 dja-details-summary">Details</summary>
|
||||
<div class="grid-container margin-left-0 padding-left-0 padding-right-0 dja-details-contents">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Title</th>
|
||||
<th>Email</th>
|
||||
<th>Phone</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for admin in admins %}
|
||||
{% url 'admin:registrar_userportfoliopermission_change' admin.pk as url %}
|
||||
<tr>
|
||||
<td><a href={{url}}>{{ admin.user.get_formatted_name}}</a></td>
|
||||
<td>{{ admin.user.title }}</td>
|
||||
<td>{{ admin.user.email }}</td>
|
||||
<td>{{ admin.user.phone }}</td>
|
||||
<td class="padding-left-1 text-size-small">
|
||||
<input aria-hidden="true" class="display-none" value="{{ admin.user.email }}" />
|
||||
<button
|
||||
class="usa-button usa-button--unstyled padding-right-1 usa-button--icon button--clipboard copy-to-clipboard usa-button__small-text text-no-underline"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
class="usa-icon"
|
||||
>
|
||||
<use aria-hidden="true" xlink:href="{%static 'img/sprite.svg'%}#content_copy"></use>
|
||||
</svg>
|
||||
<span>Copy email</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</details>
|
|
@ -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" %}
|
||||
<div class="readonly">{{ field.contents|safe }}</div>
|
||||
{% elif field.field.name == "display_members" %}
|
||||
<div class="readonly">
|
||||
{% if display_members_summary %}
|
||||
{{ display_members_summary }}
|
||||
{% else %}
|
||||
<p>No additional members found.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% elif field.field.name == "roles" %}
|
||||
<div class="readonly">
|
||||
{% if get_readable_roles %}
|
||||
|
@ -40,12 +32,9 @@
|
|||
<label aria-label="Senior official contact details"></label>
|
||||
{% include "django/admin/includes/contact_detail_list.html" with user=original_object.senior_official no_title_top_padding=field.is_readonly %}
|
||||
</div>
|
||||
{% elif field.field.name == "display_members" and field.contents %}
|
||||
<details class="margin-top-1 dja-detail-table" aria-role="button" open>
|
||||
<summary class="padding-1 padding-left-0 dja-details-summary">Details</summary>
|
||||
<div class="grid-container margin-left-0 padding-left-0 padding-right-0 dja-details-contents">
|
||||
{{ field.contents|safe }}
|
||||
</div>
|
||||
</details>
|
||||
{% 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 %}
|
|
@ -0,0 +1,49 @@
|
|||
{% load custom_filters %}
|
||||
{% load static url_helpers %}
|
||||
|
||||
<details class="margin-top-1 dja-detail-table" aria-role="button" closed>
|
||||
<summary class="padding-1 padding-left-0 dja-details-summary">Details</summary>
|
||||
<div class="grid-container margin-left-0 padding-left-0 padding-right-0 dja-details-contents">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Title</th>
|
||||
<th>Email</th>
|
||||
<th>Phone</th>
|
||||
<th>Roles</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for member in members %}
|
||||
{% url 'admin:registrar_userportfoliopermission_change' member.pk as url %}
|
||||
<tr>
|
||||
<td><a href={{url}}>{{ member.user.get_formatted_name}}</a></td>
|
||||
<td>{{ member.user.title }}</td>
|
||||
<td>{{ member.user.email }}</td>
|
||||
<td>{{ member.user.phone }}</td>
|
||||
<td>
|
||||
{% for role in member.user|portfolio_role_summary:original %}
|
||||
<span class="usa-tag">{{ role }}</span>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td class="padding-left-1 text-size-small">
|
||||
<input aria-hidden="true" class="display-none" value="{{ member.user.email }}" />
|
||||
<button
|
||||
class="usa-button usa-button--unstyled padding-right-1 usa-button--icon button--clipboard copy-to-clipboard usa-button__small-text text-no-underline"
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
class="usa-icon"
|
||||
>
|
||||
<use aria-hidden="true" xlink:href="{%static 'img/sprite.svg'%}#content_copy"></use>
|
||||
</svg>
|
||||
<span>Copy email</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</details>
|
|
@ -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 []
|
Loading…
Add table
Add a link
Reference in a new issue