mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-23 11:16:07 +02:00
wip
This commit is contained in:
parent
2ecbab459b
commit
d933da326c
6 changed files with 165 additions and 73 deletions
|
@ -13,4 +13,5 @@ from .domain import (
|
|||
)
|
||||
from .portfolio import (
|
||||
PortfolioOrgAddressForm,
|
||||
PortfolioMemberForm,
|
||||
)
|
||||
|
|
|
@ -4,6 +4,9 @@ import logging
|
|||
from django import forms
|
||||
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
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -95,3 +98,31 @@ class PortfolioSeniorOfficialForm(forms.ModelForm):
|
|||
cleaned_data = super().clean()
|
||||
cleaned_data.pop("full_name", None)
|
||||
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",
|
||||
]
|
||||
|
||||
|
|
|
@ -91,12 +91,14 @@
|
|||
</li>
|
||||
{% endif %}
|
||||
|
||||
{% if has_organization_members_flag and has_view_members_portfolio_permission %}
|
||||
<li class="usa-nav__primary-item">
|
||||
<a href="/members/" class="usa-nav-link {% if path|is_members_subpage %} usa-current{% endif %}">
|
||||
Members
|
||||
</a>
|
||||
</li>
|
||||
{% if has_organization_members_flag %}
|
||||
{% if has_view_members_portfolio_permission or has_edit_members_portfolio_permission %}
|
||||
<li class="usa-nav__primary-item">
|
||||
<a href="/members/" class="usa-nav-link {% if path|is_members_subpage %} usa-current{% endif %}">
|
||||
Members
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<li class="usa-nav__primary-item">
|
||||
|
|
|
@ -7,14 +7,6 @@
|
|||
|
||||
{% block portfolio_content %}
|
||||
<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">
|
||||
|
||||
{% block messages %}
|
||||
|
@ -23,7 +15,22 @@
|
|||
|
||||
<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>
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ from registrar.models.portfolio_invitation import PortfolioInvitation
|
|||
from registrar.models.user import User
|
||||
from registrar.models.user_portfolio_permission import UserPortfolioPermission
|
||||
from registrar.models.utility.portfolio_helper import UserPortfolioRoleChoices
|
||||
from operator import itemgetter
|
||||
|
||||
|
||||
@login_required
|
||||
|
@ -15,31 +16,60 @@ def get_portfolio_members_json(request):
|
|||
"""Given the current request,
|
||||
get all members that are associated with the given portfolio"""
|
||||
portfolio = request.GET.get("portfolio")
|
||||
member_ids = get_member_ids_from_request(request, portfolio)
|
||||
objects = User.objects.filter(id__in=member_ids)
|
||||
# member_ids = get_member_ids_from_request(request, portfolio)
|
||||
# members = User.objects.filter(id__in=member_ids)
|
||||
|
||||
admin_ids = UserPortfolioPermission.objects.filter(
|
||||
portfolio=portfolio,
|
||||
roles__overlap=[
|
||||
UserPortfolioRoleChoices.ORGANIZATION_ADMIN,
|
||||
],
|
||||
).values_list("user__id", flat=True)
|
||||
portfolio_invitation_emails = PortfolioInvitation.objects.filter(portfolio=portfolio).values_list(
|
||||
"email", flat=True
|
||||
permissions = UserPortfolioPermission.objects.filter(portfolio=portfolio).select_related("user").values_list("pk", "user__first_name", "user__last_name", "user__email", "user__last_login", "roles")
|
||||
invitations = PortfolioInvitation.objects.filter(portfolio=portfolio).values_list(
|
||||
'pk', 'email', 'portfolio_roles', 'portfolio_additional_permissions', 'status'
|
||||
)
|
||||
|
||||
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)
|
||||
# objects = apply_status_filter(objects, request)
|
||||
objects = apply_sorting(objects, request)
|
||||
# Convert the invitations queryset into a list of dictionaries
|
||||
invitation_list = [
|
||||
{
|
||||
'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_obj = paginator.get_page(page_number)
|
||||
|
||||
|
||||
members = [
|
||||
serialize_members(request, portfolio, member, request.user, admin_ids, portfolio_invitation_emails)
|
||||
for member in page_obj.object_list
|
||||
serialize_members(request, portfolio, item, request.user)
|
||||
for item in page_obj.object_list
|
||||
]
|
||||
|
||||
return JsonResponse(
|
||||
|
@ -55,44 +85,43 @@ def get_portfolio_members_json(request):
|
|||
)
|
||||
|
||||
|
||||
def get_member_ids_from_request(request, portfolio):
|
||||
"""Given the current request,
|
||||
get all members that are associated with the given portfolio"""
|
||||
member_ids = []
|
||||
if portfolio:
|
||||
member_ids = UserPortfolioPermission.objects.filter(portfolio=portfolio).values_list("user__id", flat=True)
|
||||
return member_ids
|
||||
# def get_member_ids_from_request(request, portfolio):
|
||||
# """Given the current request,
|
||||
# get all members that are associated with the given portfolio"""
|
||||
# member_ids = []
|
||||
# if portfolio:
|
||||
# member_ids = UserPortfolioPermission.objects.filter(portfolio=portfolio).values_list("user__id", flat=True)
|
||||
# return member_ids
|
||||
|
||||
|
||||
def apply_search(queryset, request):
|
||||
search_term = request.GET.get("search_term")
|
||||
def apply_search(data_list, request):
|
||||
search_term = request.GET.get("search_term", "").lower()
|
||||
|
||||
if search_term:
|
||||
queryset = queryset.filter(
|
||||
Q(username__icontains=search_term)
|
||||
| Q(first_name__icontains=search_term)
|
||||
| Q(last_name__icontains=search_term)
|
||||
| Q(email__icontains=search_term)
|
||||
)
|
||||
return queryset
|
||||
# Filter the list based on the search term (case-insensitive)
|
||||
data_list = [
|
||||
item for item in data_list
|
||||
if search_term in (item.get('first_name', '') or '').lower()
|
||||
or search_term in (item.get('last_name', '') or '').lower()
|
||||
or search_term in (item.get('email', '') or '').lower()
|
||||
]
|
||||
|
||||
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'
|
||||
order = request.GET.get("order", "asc") # Default to 'asc'
|
||||
|
||||
if sort_by == "member":
|
||||
sort_by = ["email", "first_name", "middle_name", "last_name"]
|
||||
else:
|
||||
sort_by = [sort_by]
|
||||
sort_by = "email"
|
||||
|
||||
if order == "desc":
|
||||
sort_by = [f"-{field}" for field in sort_by]
|
||||
# Sort the list
|
||||
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
|
||||
# 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).
|
||||
|
@ -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
|
||||
|
||||
# ------- USER STATUSES
|
||||
is_invited = member.email in portfolio_invitation_emails
|
||||
last_active = "Invited" if is_invited else "Unknown"
|
||||
if member.last_login:
|
||||
last_active = member.last_login.strftime("%b. %d, %Y")
|
||||
is_admin = member.id in admin_ids
|
||||
is_admin = UserPortfolioRoleChoices.ORGANIZATION_ADMIN in item['roles']
|
||||
|
||||
action_url = '#'
|
||||
if item['source'] == 'permission':
|
||||
action_url = reverse("member", kwargs={"pk": item['id']})
|
||||
|
||||
# ------- SERIALIZE
|
||||
member_json = {
|
||||
"id": member.id,
|
||||
"name": member.get_formatted_name(),
|
||||
"email": member.email,
|
||||
"id": item['id'],
|
||||
"name": (item['first_name'] or '') + ' ' + (item['last_name'] or ''),
|
||||
"email": item['email'],
|
||||
"is_admin": is_admin,
|
||||
"last_active": last_active,
|
||||
"action_url": reverse("member", kwargs={"pk": member.id}), # TODO: Future ticket?
|
||||
"last_active": item['last_active'],
|
||||
"action_url": action_url,
|
||||
"action_label": ("View" if view_only else "Manage"),
|
||||
"svg_icon": ("visibility" if view_only else "settings"),
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ from django.http import Http404
|
|||
from django.shortcuts import render
|
||||
from django.urls import reverse
|
||||
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.user_portfolio_permission import UserPortfolioPermission
|
||||
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.edit import FormMixin
|
||||
|
||||
from django.shortcuts import get_object_or_404, redirect
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -55,11 +55,33 @@ class PortfolioMembersView(PortfolioMembersPermissionView, View):
|
|||
class PortfolioMemberView(PortfolioMemberPermissionView, View):
|
||||
|
||||
template_name = "portfolio_member.html"
|
||||
model = User
|
||||
form_class = PortfolioMemberForm
|
||||
|
||||
# def get(self, request):
|
||||
# """Add additional context data to the template."""
|
||||
# return render(request, self.template_name, context=self.get_context_data())
|
||||
def get(self, request, pk):
|
||||
portfolio_permission = get_object_or_404(UserPortfolioPermission, pk=pk)
|
||||
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
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue