This commit is contained in:
Rachid Mrad 2024-10-03 18:44:34 -04:00
parent 2ecbab459b
commit d933da326c
No known key found for this signature in database
6 changed files with 165 additions and 73 deletions

View file

@ -13,4 +13,5 @@ from .domain import (
) )
from .portfolio import ( from .portfolio import (
PortfolioOrgAddressForm, PortfolioOrgAddressForm,
PortfolioMemberForm,
) )

View file

@ -4,6 +4,9 @@ import logging
from django import forms from django import forms
from django.core.validators import RegexValidator from django.core.validators import RegexValidator
from registrar.models.user_portfolio_permission import UserPortfolioPermission
from registrar.models.utility.portfolio_helper import UserPortfolioPermissionChoices, UserPortfolioRoleChoices
from ..models import DomainInformation, Portfolio, SeniorOfficial from ..models import DomainInformation, Portfolio, SeniorOfficial
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -95,3 +98,31 @@ class PortfolioSeniorOfficialForm(forms.ModelForm):
cleaned_data = super().clean() cleaned_data = super().clean()
cleaned_data.pop("full_name", None) cleaned_data.pop("full_name", None)
return cleaned_data return cleaned_data
class PortfolioMemberForm(forms.ModelForm):
"""
Form for updating a portfolio member.
"""
roles = forms.MultipleChoiceField(
choices=UserPortfolioRoleChoices.choices,
widget=forms.SelectMultiple(attrs={'class': 'usa-select'}),
required=False,
label="Roles",
)
additional_permissions = forms.MultipleChoiceField(
choices=UserPortfolioPermissionChoices.choices,
widget=forms.SelectMultiple(attrs={'class': 'usa-select'}),
required=False,
label="Additional Permissions",
)
class Meta:
model = UserPortfolioPermission
fields = [
"roles",
"additional_permissions",
]

View file

@ -91,12 +91,14 @@
</li> </li>
{% endif %} {% endif %}
{% if has_organization_members_flag and has_view_members_portfolio_permission %} {% if has_organization_members_flag %}
<li class="usa-nav__primary-item"> {% if has_view_members_portfolio_permission or has_edit_members_portfolio_permission %}
<a href="/members/" class="usa-nav-link {% if path|is_members_subpage %} usa-current{% endif %}"> <li class="usa-nav__primary-item">
Members <a href="/members/" class="usa-nav-link {% if path|is_members_subpage %} usa-current{% endif %}">
</a> Members
</li> </a>
</li>
{% endif %}
{% endif %} {% endif %}
<li class="usa-nav__primary-item"> <li class="usa-nav__primary-item">

View file

@ -7,14 +7,6 @@
{% block portfolio_content %} {% block portfolio_content %}
<div class="grid-row grid-gap"> <div class="grid-row grid-gap">
<div class="tablet:grid-col-3">
<p class="font-body-md margin-top-0 margin-bottom-2
text-primary-darker text-semibold"
>
<span class="usa-sr-only"> Portfolio name:</span> {{ portfolio }}
</p>
</div>
<div class="tablet:grid-col-9" id="main-content"> <div class="tablet:grid-col-9" id="main-content">
{% block messages %} {% block messages %}
@ -23,7 +15,22 @@
<h1>Member</h1> <h1>Member</h1>
<p>The name of your organization will be publicly listed as the domain registrant.</p> <p>{{ user.first_name }}</p>
<hr>
<form class="usa-form usa-form--large" method="post" novalidate>
{% csrf_token %}
{% input_with_errors form.roles %}
{% input_with_errors form.additional_permissions %}
<button
type="submit"
class="usa-button"
>Submit</button>
</form>

View file

@ -8,6 +8,7 @@ from registrar.models.portfolio_invitation import PortfolioInvitation
from registrar.models.user import User from registrar.models.user import User
from registrar.models.user_portfolio_permission import UserPortfolioPermission from registrar.models.user_portfolio_permission import UserPortfolioPermission
from registrar.models.utility.portfolio_helper import UserPortfolioRoleChoices from registrar.models.utility.portfolio_helper import UserPortfolioRoleChoices
from operator import itemgetter
@login_required @login_required
@ -15,31 +16,60 @@ def get_portfolio_members_json(request):
"""Given the current request, """Given the current request,
get all members that are associated with the given portfolio""" get all members that are associated with the given portfolio"""
portfolio = request.GET.get("portfolio") portfolio = request.GET.get("portfolio")
member_ids = get_member_ids_from_request(request, portfolio) # member_ids = get_member_ids_from_request(request, portfolio)
objects = User.objects.filter(id__in=member_ids) # members = User.objects.filter(id__in=member_ids)
admin_ids = UserPortfolioPermission.objects.filter( permissions = UserPortfolioPermission.objects.filter(portfolio=portfolio).select_related("user").values_list("pk", "user__first_name", "user__last_name", "user__email", "user__last_login", "roles")
portfolio=portfolio, invitations = PortfolioInvitation.objects.filter(portfolio=portfolio).values_list(
roles__overlap=[ 'pk', 'email', 'portfolio_roles', 'portfolio_additional_permissions', 'status'
UserPortfolioRoleChoices.ORGANIZATION_ADMIN,
],
).values_list("user__id", flat=True)
portfolio_invitation_emails = PortfolioInvitation.objects.filter(portfolio=portfolio).values_list(
"email", flat=True
) )
unfiltered_total = objects.count() # Convert the permissions queryset into a list of dictionaries
permission_list = [
{
'id': perm[0],
'first_name': perm[1],
'last_name': perm[2],
'email': perm[3],
'last_active': perm[4],
'roles': perm[5],
'source': 'permission' # Mark the source as permissions
}
for perm in permissions
]
objects = apply_search(objects, request) # Convert the invitations queryset into a list of dictionaries
# objects = apply_status_filter(objects, request) invitation_list = [
objects = apply_sorting(objects, request) {
'id': invite[0],
'first_name': None, # No first name in invitations
'last_name': None, # No last name in invitations
'email': invite[1],
'roles': invite[2],
'additional_permissions': invite[3],
'status': invite[4],
'last_active': 'Invited',
'source': 'invitation' # Mark the source as invitations
}
for invite in invitations
]
paginator = Paginator(objects, 10) # Combine both lists into one unified list
combined_list = permission_list + invitation_list
unfiltered_total = len(combined_list)
combined_list = apply_search(combined_list, request)
combined_list = apply_sorting(combined_list, request)
paginator = Paginator(combined_list, 10)
page_number = request.GET.get("page", 1) page_number = request.GET.get("page", 1)
page_obj = paginator.get_page(page_number) page_obj = paginator.get_page(page_number)
members = [ members = [
serialize_members(request, portfolio, member, request.user, admin_ids, portfolio_invitation_emails) serialize_members(request, portfolio, item, request.user)
for member in page_obj.object_list for item in page_obj.object_list
] ]
return JsonResponse( return JsonResponse(
@ -55,44 +85,43 @@ def get_portfolio_members_json(request):
) )
def get_member_ids_from_request(request, portfolio): # def get_member_ids_from_request(request, portfolio):
"""Given the current request, # """Given the current request,
get all members that are associated with the given portfolio""" # get all members that are associated with the given portfolio"""
member_ids = [] # member_ids = []
if portfolio: # if portfolio:
member_ids = UserPortfolioPermission.objects.filter(portfolio=portfolio).values_list("user__id", flat=True) # member_ids = UserPortfolioPermission.objects.filter(portfolio=portfolio).values_list("user__id", flat=True)
return member_ids # return member_ids
def apply_search(data_list, request):
def apply_search(queryset, request): search_term = request.GET.get("search_term", "").lower()
search_term = request.GET.get("search_term")
if search_term: if search_term:
queryset = queryset.filter( # Filter the list based on the search term (case-insensitive)
Q(username__icontains=search_term) data_list = [
| Q(first_name__icontains=search_term) item for item in data_list
| Q(last_name__icontains=search_term) if search_term in (item.get('first_name', '') or '').lower()
| Q(email__icontains=search_term) or search_term in (item.get('last_name', '') or '').lower()
) or search_term in (item.get('email', '') or '').lower()
return queryset ]
return data_list
def apply_sorting(queryset, request): def apply_sorting(data_list, request):
sort_by = request.GET.get("sort_by", "id") # Default to 'id' sort_by = request.GET.get("sort_by", "id") # Default to 'id'
order = request.GET.get("order", "asc") # Default to 'asc' order = request.GET.get("order", "asc") # Default to 'asc'
if sort_by == "member": if sort_by == "member":
sort_by = ["email", "first_name", "middle_name", "last_name"] sort_by = "email"
else:
sort_by = [sort_by]
if order == "desc": # Sort the list
sort_by = [f"-{field}" for field in sort_by] data_list = sorted(data_list, key=itemgetter(sort_by), reverse=(order == "desc"))
return queryset.order_by(*sort_by) return data_list
def serialize_members(request, portfolio, member, user, admin_ids, portfolio_invitation_emails): def serialize_members(request, portfolio, item, user):
# ------- VIEW ONLY # ------- VIEW ONLY
# If not view_only (the user has permissions to edit/manage users), show the gear icon with "Manage" link. # If not view_only (the user has permissions to edit/manage users), show the gear icon with "Manage" link.
# If view_only (the user only has view user permissions), show the "View" link (no gear icon). # If view_only (the user only has view user permissions), show the "View" link (no gear icon).
@ -106,20 +135,20 @@ def serialize_members(request, portfolio, member, user, admin_ids, portfolio_inv
view_only = not user.has_edit_members_portfolio_permission(portfolio) or not user_can_edit_other_users view_only = not user.has_edit_members_portfolio_permission(portfolio) or not user_can_edit_other_users
# ------- USER STATUSES # ------- USER STATUSES
is_invited = member.email in portfolio_invitation_emails is_admin = UserPortfolioRoleChoices.ORGANIZATION_ADMIN in item['roles']
last_active = "Invited" if is_invited else "Unknown"
if member.last_login: action_url = '#'
last_active = member.last_login.strftime("%b. %d, %Y") if item['source'] == 'permission':
is_admin = member.id in admin_ids action_url = reverse("member", kwargs={"pk": item['id']})
# ------- SERIALIZE # ------- SERIALIZE
member_json = { member_json = {
"id": member.id, "id": item['id'],
"name": member.get_formatted_name(), "name": (item['first_name'] or '') + ' ' + (item['last_name'] or ''),
"email": member.email, "email": item['email'],
"is_admin": is_admin, "is_admin": is_admin,
"last_active": last_active, "last_active": item['last_active'],
"action_url": reverse("member", kwargs={"pk": member.id}), # TODO: Future ticket? "action_url": action_url,
"action_label": ("View" if view_only else "Manage"), "action_label": ("View" if view_only else "Manage"),
"svg_icon": ("visibility" if view_only else "settings"), "svg_icon": ("visibility" if view_only else "settings"),
} }

View file

@ -3,7 +3,7 @@ from django.http import Http404
from django.shortcuts import render from django.shortcuts import render
from django.urls import reverse from django.urls import reverse
from django.contrib import messages from django.contrib import messages
from registrar.forms.portfolio import PortfolioOrgAddressForm, PortfolioSeniorOfficialForm from registrar.forms.portfolio import PortfolioMemberForm, PortfolioOrgAddressForm, PortfolioSeniorOfficialForm
from registrar.models import Portfolio, User from registrar.models import Portfolio, User
from registrar.models.user_portfolio_permission import UserPortfolioPermission from registrar.models.user_portfolio_permission import UserPortfolioPermission
from registrar.models.utility.portfolio_helper import UserPortfolioRoleChoices from registrar.models.utility.portfolio_helper import UserPortfolioRoleChoices
@ -17,7 +17,7 @@ from registrar.views.utility.permission_views import (
) )
from django.views.generic import View from django.views.generic import View
from django.views.generic.edit import FormMixin from django.views.generic.edit import FormMixin
from django.shortcuts import get_object_or_404, redirect
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -55,11 +55,33 @@ class PortfolioMembersView(PortfolioMembersPermissionView, View):
class PortfolioMemberView(PortfolioMemberPermissionView, View): class PortfolioMemberView(PortfolioMemberPermissionView, View):
template_name = "portfolio_member.html" template_name = "portfolio_member.html"
model = User form_class = PortfolioMemberForm
# def get(self, request): def get(self, request, pk):
# """Add additional context data to the template.""" portfolio_permission = get_object_or_404(UserPortfolioPermission, pk=pk)
# return render(request, self.template_name, context=self.get_context_data()) user = portfolio_permission.user
form = self.form_class(instance=portfolio_permission)
return render(request, self.template_name, {
'form': form,
'user': user,
})
def post(self, request, pk):
portfolio_permission = get_object_or_404(UserPortfolioPermission, pk=pk)
user = portfolio_permission.user
form = self.form_class(request.POST, instance=portfolio_permission)
if form.is_valid():
form.save()
return redirect('home')
return render(request, self.template_name, {
'form': form,
'user': user, # Pass the user object again to the template
})