mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-24 03:30:50 +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 (
|
from .portfolio import (
|
||||||
PortfolioOrgAddressForm,
|
PortfolioOrgAddressForm,
|
||||||
|
PortfolioMemberForm,
|
||||||
)
|
)
|
||||||
|
|
|
@ -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",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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"),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue