modified userportfoliopermissions and portfolioinvitation admin forms

This commit is contained in:
David Kennedy 2025-02-21 22:57:12 -05:00
parent 260d2e587f
commit 3434519a16
No known key found for this signature in database
GPG key ID: 6528A5386E66B96B
7 changed files with 167 additions and 65 deletions

View file

@ -192,7 +192,16 @@ class MyUserAdminForm(UserChangeForm):
) )
class UserPortfolioPermissionsForm(forms.ModelForm): class PortfolioPermissionsForm(forms.ModelForm):
"""
Form for managing portfolio permissions in Django admin. This form class is used
for both UserPortfolioPermission and PortfolioInvitation models.
Allows selecting a portfolio, assigning a role, and managing specific permissions
related to requests, domains, and members.
"""
# Define available permissions for requests, domains, and members
REQUEST_PERMISSIONS = [ REQUEST_PERMISSIONS = [
UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS, UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS,
UserPortfolioPermissionChoices.EDIT_REQUESTS, UserPortfolioPermissionChoices.EDIT_REQUESTS,
@ -207,111 +216,134 @@ class UserPortfolioPermissionsForm(forms.ModelForm):
UserPortfolioPermissionChoices.VIEW_MEMBERS, UserPortfolioPermissionChoices.VIEW_MEMBERS,
] ]
user = forms.ModelChoiceField( # Dropdown to select a portfolio
queryset=models.User.objects.all(), portfolio = forms.ModelChoiceField(queryset=models.Portfolio.objects.all(), label="Portfolio")
label="User"
)
portfolio = forms.ModelChoiceField(
queryset=models.Portfolio.objects.all(),
label="Portfolio"
)
# Dropdown for selecting the user role (e.g., Admin or Basic)
role = forms.ChoiceField( role = forms.ChoiceField(
choices=UserPortfolioRoleChoices.choices, choices=UserPortfolioRoleChoices.choices,
required=True, required=True,
widget=forms.Select(attrs={"class": "admin-dropdown"}), widget=forms.Select(attrs={"class": "admin-dropdown"}),
label="Member access" label="Member access",
) )
# Dropdown for selecting request permissions, with a default "No access" option
request_permissions = forms.ChoiceField( request_permissions = forms.ChoiceField(
choices=[(perm.value, perm.label) for perm in REQUEST_PERMISSIONS], choices=[(None, "No access")] + [(perm.value, perm.label) for perm in REQUEST_PERMISSIONS],
required=False, required=False,
widget=forms.Select(attrs={"class": "admin-dropdown"}), widget=forms.Select(attrs={"class": "admin-dropdown"}),
label="Domain requests" label="Domain requests",
) )
# Dropdown for selecting domain permissions
domain_permissions = forms.ChoiceField( domain_permissions = forms.ChoiceField(
choices=[(perm.value, perm.label) for perm in DOMAIN_PERMISSIONS], choices=[(perm.value, perm.label) for perm in DOMAIN_PERMISSIONS],
required=False, required=False,
widget=forms.Select(attrs={"class": "admin-dropdown"}), widget=forms.Select(attrs={"class": "admin-dropdown"}),
label="Domains" label="Domains",
) )
# Dropdown for selecting member permissions, with a default "No access" option
member_permissions = forms.ChoiceField( member_permissions = forms.ChoiceField(
choices=[(perm.value, perm.label) for perm in MEMBER_PERMISSIONS], choices=[(None, "No access")] + [(perm.value, perm.label) for perm in MEMBER_PERMISSIONS],
required=False, required=False,
widget=forms.Select(attrs={"class": "admin-dropdown"}), widget=forms.Select(attrs={"class": "admin-dropdown"}),
label="Members" label="Members",
) )
class Meta:
model = models.UserPortfolioPermission
fields = ["user", "portfolio", "role", "domain_permissions", "request_permissions", "member_permissions"]
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
"""
Initialize the form and set default values based on the existing instance.
"""
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
logger.debug("Initializing form") # If an instance exists, populate the form fields with existing data
# Populate roles
if self.instance and self.instance.pk: if self.instance and self.instance.pk:
# Set the initial value for the role field
if self.instance.roles: if self.instance.roles:
logger.debug(f"Setting role: {self.instance.roles[0]}") self.fields["role"].initial = self.instance.roles[0] # Assuming a single role per user
self.fields["role"].initial = self.instance.roles[0] # Assuming single role per user
# Set the initial values for permissions based on the instance data
if self.instance.additional_permissions: if self.instance.additional_permissions:
logger.debug(f"Existing permissions: {self.instance.additional_permissions}")
for perm in self.instance.additional_permissions: for perm in self.instance.additional_permissions:
logger.debug(f"Processing permission: {perm}")
if perm in self.REQUEST_PERMISSIONS: if perm in self.REQUEST_PERMISSIONS:
logger.debug("Assigning request permission")
self.fields["request_permissions"].initial = perm self.fields["request_permissions"].initial = perm
elif perm in self.DOMAIN_PERMISSIONS: elif perm in self.DOMAIN_PERMISSIONS:
logger.debug("Assigning domain permission")
self.fields["domain_permissions"].initial = perm self.fields["domain_permissions"].initial = perm
elif perm in self.MEMBER_PERMISSIONS: elif perm in self.MEMBER_PERMISSIONS:
logger.debug("Assigning member permission")
self.fields["member_permissions"].initial = perm self.fields["member_permissions"].initial = perm
def clean(self): def clean(self):
"""
Custom validation and processing of form data before saving.
"""
cleaned_data = super().clean() cleaned_data = super().clean()
# Store the selected role as a list (assuming single role assignment)
self.instance.roles = [cleaned_data.get("role")] if cleaned_data.get("role") else [] self.instance.roles = [cleaned_data.get("role")] if cleaned_data.get("role") else []
logger.debug(f"Cleaned roles: {self.instance.roles}")
# If the selected role is "organization_member," store additional permissions
if self.instance.roles == [UserPortfolioRoleChoices.ORGANIZATION_MEMBER]: if self.instance.roles == [UserPortfolioRoleChoices.ORGANIZATION_MEMBER]:
self.instance.additional_permissions = list( self.instance.additional_permissions = list(
filter(None, [ filter(
None,
[
cleaned_data.get("request_permissions"), cleaned_data.get("request_permissions"),
cleaned_data.get("domain_permissions"), cleaned_data.get("domain_permissions"),
cleaned_data.get("member_permissions"), cleaned_data.get("member_permissions"),
]) ],
)
) )
else: else:
# If the user is an admin, clear any additional permissions
self.instance.additional_permissions = [] self.instance.additional_permissions = []
logger.debug(f"Final saved permissions: {self.instance.additional_permissions}")
return cleaned_data return cleaned_data
class PortfolioInvitationAdminForm(UserChangeForm): class UserPortfolioPermissionsForm(PortfolioPermissionsForm):
"""This form utilizes the custom widget for its class's ManyToMany UIs.""" """
Form for managing user portfolio permissions in Django admin.
Extends PortfolioPermissionsForm to include a user field, allowing administrators
to assign roles and permissions to specific users within a portfolio.
"""
# Dropdown to select a user from the database
user = forms.ModelChoiceField(queryset=models.User.objects.all(), label="User")
class Meta: class Meta:
model = models.PortfolioInvitation """
fields = "__all__" Meta class defining the model and fields to be used in the form.
widgets = { """
"roles": FilteredSelectMultipleArrayWidget(
"roles", is_stacked=False, choices=UserPortfolioRoleChoices.choices model = models.UserPortfolioPermission # Uses the UserPortfolioPermission model
), fields = ["user", "portfolio", "role", "domain_permissions", "request_permissions", "member_permissions"]
"additional_permissions": FilteredSelectMultipleArrayWidget(
"additional_permissions",
is_stacked=False, class PortfolioInvitationForm(PortfolioPermissionsForm):
choices=UserPortfolioPermissionChoices.choices, """
), Form for sending portfolio invitations in Django admin.
}
Extends PortfolioPermissionsForm to include an email field for inviting users,
allowing them to be assigned a role and permissions within a portfolio before they join.
"""
class Meta:
"""
Meta class defining the model and fields to be used in the form.
"""
model = models.PortfolioInvitation # Uses the PortfolioInvitation model
fields = [
"email",
"portfolio",
"role",
"domain_permissions",
"request_permissions",
"member_permissions",
"status",
]
class DomainInformationAdminForm(forms.ModelForm): class DomainInformationAdminForm(forms.ModelForm):
@ -1721,7 +1753,7 @@ class DomainInvitationAdmin(BaseInvitationAdmin):
class PortfolioInvitationAdmin(BaseInvitationAdmin): class PortfolioInvitationAdmin(BaseInvitationAdmin):
"""Custom portfolio invitation admin class.""" """Custom portfolio invitation admin class."""
form = PortfolioInvitationAdminForm form = PortfolioInvitationForm
class Meta: class Meta:
model = models.PortfolioInvitation model = models.PortfolioInvitation

View file

@ -13,6 +13,7 @@ import {
initDynamicDomainRequestFields } from './domain-request-form.js'; initDynamicDomainRequestFields } from './domain-request-form.js';
import { initDomainFormTargetBlankButtons } from './domain-form.js'; import { initDomainFormTargetBlankButtons } from './domain-form.js';
import { initDynamicPortfolioFields } from './portfolio-form.js'; import { initDynamicPortfolioFields } from './portfolio-form.js';
import { initDynamicPortfolioPermissionFields } from './portfolio-permissions-form.js'
import { initDynamicDomainInformationFields } from './domain-information-form.js'; import { initDynamicDomainInformationFields } from './domain-information-form.js';
import { initDynamicDomainFields } from './domain-form.js'; import { initDynamicDomainFields } from './domain-form.js';
import { initAnalyticsDashboard } from './analytics.js'; import { initAnalyticsDashboard } from './analytics.js';
@ -40,6 +41,9 @@ initDynamicDomainFields();
// Portfolio // Portfolio
initDynamicPortfolioFields(); initDynamicPortfolioFields();
// Portfolio permissions
initDynamicPortfolioPermissionFields();
// Domain information // Domain information
initDynamicDomainInformationFields(); initDynamicDomainInformationFields();

View file

@ -0,0 +1,67 @@
import { hideElement, showElement } from './helpers-admin.js';
/**
* A function for dynamically changing fields on the UserPortfolioPermissions
* and PortfolioInvitation admin forms
*/
function handlePortfolioPermissionFields(){
const roleDropdown = document.getElementById("id_role");
const domainPermissionsField = document.querySelector(".field-domain_permissions");
const domainRequestPermissionsField = document.querySelector(".field-request_permissions");
const memberPermissionsField = document.querySelector(".field-member_permissions");
/**
* Updates the visibility of portfolio permissions fields based on the selected role.
*
* This function checks the value of the role dropdown (`roleDropdown`):
* - If the selected role is "organization_admin":
* - Hides the domain permissions field (`domainPermissionsField`).
* - Hides the domain request permissions field (`domainRequestPermissionsField`).
* - Hides the member permissions field (`memberPermissionsField`).
* - Otherwise:
* - Shows all the above fields.
*
* The function ensures that the appropriate fields are dynamically displayed
* or hidden depending on the role selection in the form.
*/
function updatePortfolioPermissionsFormVisibility() {
if (roleDropdown && domainPermissionsField && domainRequestPermissionsField && memberPermissionsField) {
if (roleDropdown.value === "organization_admin") {
hideElement(domainPermissionsField);
hideElement(domainRequestPermissionsField);
hideElement(memberPermissionsField);
} else {
showElement(domainPermissionsField);
showElement(domainRequestPermissionsField);
showElement(memberPermissionsField);
}
}
}
/**
* Sets event listeners for key UI elements.
*/
function setEventListeners() {
if (roleDropdown) {
roleDropdown.addEventListener("change", function() {
updatePortfolioPermissionsFormVisibility();
})
}
}
// Run initial setup functions
updatePortfolioPermissionsFormVisibility();
setEventListeners();
}
export function initDynamicPortfolioPermissionFields() {
document.addEventListener('DOMContentLoaded', function() {
let isPortfolioPermissionPage = document.getElementById("userportfoliopermission_form");
let isPortfolioInvitationPage = document.getElementById("portfolioinvitation_form")
if (isPortfolioPermissionPage || isPortfolioInvitationPage) {
handlePortfolioPermissionFields();
}
});
}

View file

@ -1,4 +1,4 @@
"""" """
Converts all ready and DNS needed domains with a non-default public contact Converts all ready and DNS needed domains with a non-default public contact
to disclose their public contact. Created for Issue#1535 to resolve to disclose their public contact. Created for Issue#1535 to resolve
disclose issue of domains with missing security emails. disclose issue of domains with missing security emails.

View file

@ -1,8 +1,8 @@
"""Data migration: """Data migration:
1 - generates a report of data integrity across all 1 - generates a report of data integrity across all
transition domain related tables transition domain related tables
2 - allows users to run all migration scripts for 2 - allows users to run all migration scripts for
transition domain data transition domain data
""" """
import logging import logging

View file

@ -1,4 +1,4 @@
"""" """
Data migration: Renaming deprecated Federal Agencies to Data migration: Renaming deprecated Federal Agencies to
their new updated names ie (U.S. Peace Corps to Peace Corps) their new updated names ie (U.S. Peace Corps to Peace Corps)
within Domain Information and Domain Requests within Domain Information and Domain Requests

View file

@ -1,5 +1,4 @@
"""Views for a User Profile. """Views for a User Profile."""
"""
import logging import logging