mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-08-27 19:43:43 +02:00
Add in checks for if theyre only admin or have any in progress requests
This commit is contained in:
parent
5f6e8968c7
commit
cd517ae885
3 changed files with 128 additions and 49 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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).
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue