Add in checks for if theyre only admin or have any in progress requests

This commit is contained in:
Rebecca Hsieh 2024-10-24 16:22:47 -07:00
parent 5f6e8968c7
commit cd517ae885
No known key found for this signature in database
3 changed files with 128 additions and 49 deletions

View file

@ -2074,9 +2074,14 @@ class MembersTable extends LoadTableBase {
modalHeading = `Are you sure you want to delete ${member_email}?`; modalHeading = `Are you sure you want to delete ${member_email}?`;
modalDescription = `They will no longer be able to access this organization. \n modalDescription = `They will no longer be able to access this organization. \n
This action cannot be undone.`; This action cannot be undone.`;
} else if (num_domains === 1) {
modalHeading = `Are you sure you want to delete ${member_email}?`;
modalDescription = `<b>${member_email}</b> currently manages ${num_domains} domain in the organization. \n
Removing them from the organization will remove all of their domains. They will no longer be able to \n
access this organization. This action cannot be undone.`;
} else if (num_domains >= 1) { } else if (num_domains >= 1) {
modalHeading = `Are you sure you want to delete ${member_email}?`; modalHeading = `Are you sure you want to delete ${member_email}?`;
modalDescription = `${member_email} current manages ${num_domains} domains in the organization \n modalDescription = `<b>${member_email}</b> currently manages ${num_domains} domains in the organization. \n
Removing them from the organization will remove all of their domains. They will no longer be able to \n Removing them from the organization will remove all of their domains. They will no longer be able to \n
access this organization. This action cannot be undone.`; access this organization. This action cannot be undone.`;
} }
@ -2136,7 +2141,6 @@ class MembersTable extends LoadTableBase {
</div> </div>
` `
this.tableWrapper.appendChild(modal); this.tableWrapper.appendChild(modal);
console.log("modal", modal)
} }
generateKebabHTML(member_id, member_name, last_active) { generateKebabHTML(member_id, member_name, last_active) {
@ -2195,10 +2199,45 @@ class MembersTable extends LoadTableBase {
* @param {*} domainRequestPk - the identifier for the request that we're deleting * @param {*} domainRequestPk - the identifier for the request that we're deleting
* @param {*} pageToDisplay - If we're deleting the last item on a page that is not page 1, we'll need to display the previous page * @param {*} pageToDisplay - If we're deleting the last item on a page that is not page 1, we'll need to display the previous page
*/ */
deleteMember(member_delete_url, pageToDisplay) { // This is what we originally have
// Use to debug uswds modal issues // deleteMember(member_delete_url, pageToDisplay) {
console.log(member_delete_url) // // Use to debug uswds modal issues
// console.log(member_delete_url)
// // Get csrf token
// const csrfToken = getCsrfToken();
// // Create FormData object and append the CSRF token
// const formData = `csrfmiddlewaretoken=${encodeURIComponent(csrfToken)}`;
// fetch(`${member_delete_url}`, {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/x-www-form-urlencoded',
// 'X-CSRFToken': csrfToken,
// },
// body: formData
// })
// .then(response => {
// if (!response.ok) {
// throw new Error(`HTTP error! status: ${response.status}`);
// }
// // Update data and UI
// this.loadTable(pageToDisplay, this.currentSortBy, this.currentOrder, this.scrollToTable, this.currentSearchTerm);
// })
// .catch(error => console.error('Error fetching domain requests:', error));
// }
deleteMember(member_delete_url, pageToDisplay) {
// Debugging
console.log(member_delete_url);
const inProgressResponse = "This member has an active domain request and can't \n"
"be removed from this organization. <Contact the .gov team link> to remove them."
const onlyAdminResponse = "There must be at least one admin in your organization. \n"
"Give another member admin permissions, make sure they log into the registrar, \n"
"and then remove this member."
// Get csrf token // Get csrf token
const csrfToken = getCsrfToken(); const csrfToken = getCsrfToken();
// Create FormData object and append the CSRF token // Create FormData object and append the CSRF token
@ -2211,44 +2250,40 @@ class MembersTable extends LoadTableBase {
'X-CSRFToken': csrfToken, 'X-CSRFToken': csrfToken,
}, },
body: formData body: formData
}) })
.then(response => { .then(response => {
if (!response.ok) { if (response.status === 204) {
throw new Error(`HTTP error! status: ${response.status}`); // TODO: Add success alert with "You've removed member.email from the organization." text
console.log('Member successfully deleted');
// Update data and UI
this.loadTable(pageToDisplay, this.currentSortBy, this.currentOrder, this.scrollToTable, this.currentSearchTerm);
} else {
// If the response isn't 204, handle the error response
return response.json().then(data => {
console.log("Member response not 204");
if (data.error) {
// TODO: We maybe don't need the consts above and have those
// responses in the portfolios.py JSON response. Formatting though?
// This should display the error given from backend for
// either only admin OR in progress requests
this.displayErrorMessage(data.error);
} else {
throw new Error(`Unexpected status: ${response.status}`);
}
});
} }
// Update data and UI
this.loadTable(pageToDisplay, this.currentSortBy, this.currentOrder, this.scrollToTable, this.currentSearchTerm);
}) })
.catch(error => console.error('Error fetching domain requests:', error)); .catch(error => {
} console.error('Error deleting member:', error);
this.displayErrorMessage(error.message);
});
}
// deleteDomainRequest(domainRequestPk, pageToDisplay) { displayErrorMessage(errorMessage) {
// // Use to debug uswds modal issues alert(errorMessage); // Just debugging for now
// //console.log('deleteDomainRequest') }
// // Get csrf token
// const csrfToken = getCsrfToken();
// // Create FormData object and append the CSRF token
// const formData = `csrfmiddlewaretoken=${encodeURIComponent(csrfToken)}&delete-domain-request=`;
// fetch(`/domain-request/${domainRequestPk}/delete`, {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/x-www-form-urlencoded',
// 'X-CSRFToken': csrfToken,
// },
// body: formData
// })
// .then(response => {
// if (!response.ok) {
// throw new Error(`HTTP error! status: ${response.status}`);
// }
// // Update data and UI
// this.loadTable(pageToDisplay, this.currentSortBy, this.currentOrder, this.scrollToTable, this.currentSearchTerm);
// })
// .catch(error => console.error('Error fetching domain requests:', error));
// }
/** /**
* Loads rows in the members list, as well as updates pagination around the members list * Loads rows in the members list, as well as updates pagination around the members list

View file

@ -1,5 +1,6 @@
import logging import logging
from django.apps import apps
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.db import models from django.db import models
from django.db.models import Q from django.db.models import Q
@ -472,10 +473,41 @@ class User(AbstractUser):
else: else:
return UserDomainRole.objects.filter(user=self).values_list("id", flat=True) return UserDomainRole.objects.filter(user=self).values_list("id", flat=True)
# def get_user_domain_count(self, request): def get_active_requests_count_in_portfolio(self, request):
# """Returns the count of domains associated with this user on UserDomainRole or Portfolio""" """Return count of active requests for the portfolio associated with the request."""
# portfolio = request.session.get("portfolio") portfolio_id = request.session.get(
# if self.is_org_user(request) and self.has_view_all_domains_portfolio_permission(portfolio): "portfolio_id"
# return DomainInformation.objects.filter(portfolio=portfolio).count() ) # Adjust based on how you store the portfolio ID in the session
# else: if not portfolio_id:
# return UserDomainRole.objects.filter(user=self).count() return 0 # No portfolio ID found
allowed_states = [
DomainRequest.DomainRequestStatus.SUBMITTED,
DomainRequest.DomainRequestStatus.IN_REVIEW,
DomainRequest.DomainRequestStatus.ACTION_NEEDED,
]
# Assuming you have a way to filter domain requests by portfolio
active_requests_count = self.domain_requests_created.filter(
status__in=allowed_states, portfolio__id=portfolio_id # Ensure this field exists on the DomainRequest model
).count()
return active_requests_count
def is_only_admin_of_portfolio(self, portfolio):
"""Check if the user is the only admin of the given portfolio."""
UserPortfolioPermission = apps.get_model("registrar", "UserPortfolioPermission")
# Grab admin permission ability we want
admin_permission = UserPortfolioPermissionChoices.EDIT_PORTFOLIO
# Get all users with admin permission for this portfolio
admins = UserPortfolioPermission.objects.filter(portfolio=portfolio, roles__contains=[admin_permission])
# Check if there is more than one admin
if admins.count() == 1 and admins.first().user == self:
# The user is the only admin
return True
# There are other admins OR the user is not the only one
return False

View file

@ -1,5 +1,5 @@
import logging import logging
from django.http import HttpResponse, Http404 from django.http import HttpResponse, Http404, JsonResponse
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
@ -100,14 +100,26 @@ class PortfolioMemberView(PortfolioMemberPermissionView, View):
}, },
) )
class PortfolioMemberDeleteView(PortfolioMemberPermission, View):
class PortfolioMemberDeleteView(PortfolioMemberPermission, View):
def post(self, request, pk): def post(self, request, pk):
""" """
Find and delete the portfolio member using the provided primary key (pk). Find and delete the portfolio member using the provided primary key (pk).
Redirect to a success page after deletion (or any other appropriate page). Redirect to a success page after deletion (or any other appropriate page).
""" """
portfolio_member_permission = get_object_or_404(UserPortfolioPermission, pk=pk) portfolio_member_permission = get_object_or_404(UserPortfolioPermission, pk=pk)
member = portfolio_member_permission.user
active_requests_count = member.get_active_requests_count_in_portfolio(request)
print(f"Active requests count for member {member.id}: {active_requests_count}")
if active_requests_count > 0:
return JsonResponse({"error": "ERROR: Member has in-progress requests and cannot be removed."}, status=400)
# If they are the last manager of a domain
if member.is_only_admin_of_portfolio(portfolio_member_permission.portfolio):
return JsonResponse({"error": "ERROR: Member is the only admin."}, status=400)
portfolio_member_permission.delete() portfolio_member_permission.delete()
@ -191,8 +203,8 @@ class PortfolioInvitedMemberView(PortfolioInvitedMemberPermissionView, View):
) )
class PortfolioInvitedMemberDeleteView(PortfolioInvitedMemberPermission, View): class PortfolioInvitedMemberDeleteView(PortfolioInvitedMemberPermission, View):
def post(self, request, pk): def post(self, request, pk):
""" """
Find and delete the portfolio invited member using the provided primary key (pk). Find and delete the portfolio invited member using the provided primary key (pk).