mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-08-14 21:44:08 +02:00
Remove changes to newmember
This commit is contained in:
parent
c5184e5b29
commit
437981ff30
6 changed files with 449 additions and 274 deletions
|
@ -46,22 +46,20 @@ export function initPortfolioNewMemberPageToggle() {
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hooks up specialized listeners for handling form validation and modals
|
* Hooks up specialized listeners for handling form validation and modals
|
||||||
* on the Add New Member page.
|
* on the Add New Member page.
|
||||||
*/
|
*/
|
||||||
export function initAddNewMemberPageListeners() {
|
export function initAddNewMemberPageListeners() {
|
||||||
let add_member_form = document.getElementById("add_member_form");
|
let add_member_form = document.getElementById("add_member_form");
|
||||||
if (!add_member_form){
|
if (!add_member_form){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
document.getElementById("confirm_new_member_submit").addEventListener("click", function() {
|
||||||
// Hookup the submission buttons
|
|
||||||
document.getElementById("confirm_new_member_submit").addEventListener("click", function() {
|
|
||||||
// Upon confirmation, submit the form
|
// Upon confirmation, submit the form
|
||||||
document.getElementById("add_member_form").submit();
|
document.getElementById("add_member_form").submit();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById("add_member_form").addEventListener("submit", function(event) {
|
document.getElementById("add_member_form").addEventListener("submit", function(event) {
|
||||||
event.preventDefault(); // Prevents the form from submitting
|
event.preventDefault(); // Prevents the form from submitting
|
||||||
const form = document.getElementById("add_member_form")
|
const form = document.getElementById("add_member_form")
|
||||||
const formData = new FormData(form);
|
const formData = new FormData(form);
|
||||||
|
@ -87,26 +85,37 @@ export function initAddNewMemberPageListeners() {
|
||||||
form.submit();
|
form.submit();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
Helper function to capitalize the first letter in a string (for display purposes)
|
||||||
|
*/
|
||||||
|
function capitalizeFirstLetter(text) {
|
||||||
|
if (!text) return ''; // Return empty string if input is falsy
|
||||||
|
return text.charAt(0).toUpperCase() + text.slice(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
Populates contents of the "Add Member" confirmation modal
|
Populates contents of the "Add Member" confirmation modal
|
||||||
*/
|
*/
|
||||||
function populatePermissionDetails(permission_details_div_id) {
|
function populatePermissionDetails(permission_details_div_id) {
|
||||||
const permissionDetailsContainer = document.getElementById("permission_details");
|
const permissionDetailsContainer = document.getElementById("permission_details");
|
||||||
permissionDetailsContainer.innerHTML = ""; // Clear previous content
|
permissionDetailsContainer.innerHTML = ""; // Clear previous content
|
||||||
|
|
||||||
// Get all permission sections (divs with h3 and radio inputs)
|
// Get all permission sections (divs with h3 and radio inputs)
|
||||||
const permissionSections = document.querySelectorAll(`#${permission_details_div_id} > h3`);
|
const permissionSections = document.querySelectorAll(`#${permission_details_div_id} > h3`);
|
||||||
|
|
||||||
permissionSections.forEach(section => {
|
permissionSections.forEach(section => {
|
||||||
// Find the <h3> element text
|
// Find the <h3> element text
|
||||||
const sectionTitle = section.textContent;
|
const sectionTitle = section.textContent;
|
||||||
|
|
||||||
// Find the associated radio buttons container (next fieldset)
|
// Find the associated radio buttons container (next fieldset)
|
||||||
const fieldset = section.nextElementSibling;
|
const fieldset = section.nextElementSibling;
|
||||||
|
|
||||||
if (fieldset && fieldset.tagName.toLowerCase() === 'fieldset') {
|
if (fieldset && fieldset.tagName.toLowerCase() === 'fieldset') {
|
||||||
// Get the selected radio button within this fieldset
|
// Get the selected radio button within this fieldset
|
||||||
const selectedRadio = fieldset.querySelector('input[type="radio"]:checked');
|
const selectedRadio = fieldset.querySelector('input[type="radio"]:checked');
|
||||||
|
|
||||||
// If a radio button is selected, get its label text
|
// If a radio button is selected, get its label text
|
||||||
let selectedPermission = "No permission selected";
|
let selectedPermission = "No permission selected";
|
||||||
if (selectedRadio) {
|
if (selectedRadio) {
|
||||||
|
@ -129,57 +138,62 @@ export function initAddNewMemberPageListeners() {
|
||||||
permissionDetailsContainer.appendChild(permissionElement);
|
permissionDetailsContainer.appendChild(permissionElement);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Updates and opens the "Add Member" confirmation modal.
|
Updates and opens the "Add Member" confirmation modal.
|
||||||
*/
|
*/
|
||||||
function openAddMemberConfirmationModal() {
|
function openAddMemberConfirmationModal() {
|
||||||
//------- Populate modal details
|
//------- Populate modal details
|
||||||
// Get email value
|
// Get email value
|
||||||
let emailValue = document.getElementById('id_email').value;
|
let emailValue = document.getElementById('id_email').value;
|
||||||
document.getElementById('modalEmail').textContent = emailValue;
|
document.getElementById('modalEmail').textContent = emailValue;
|
||||||
|
|
||||||
// Get selected radio button for access level
|
// Get selected radio button for access level
|
||||||
let selectedAccess = document.querySelector('input[name="role"]:checked');
|
let selectedAccess = document.querySelector('input[name="member_access_level"]:checked');
|
||||||
|
|
||||||
// Set the selected permission text to 'Basic' or 'Admin' (the value of the selected radio button)
|
// Set the selected permission text to 'Basic' or 'Admin' (the value of the selected radio button)
|
||||||
// This value does not have the first letter capitalized so let's capitalize it
|
// This value does not have the first letter capitalized so let's capitalize it
|
||||||
let accessText = "No access level selected";
|
let accessText = selectedAccess ? capitalizeFirstLetter(selectedAccess.value) : "No access level selected";
|
||||||
|
document.getElementById('modalAccessLevel').textContent = accessText;
|
||||||
|
|
||||||
// Populate permission details based on access level
|
// Populate permission details based on access level
|
||||||
if (selectedAccess && selectedAccess.value === 'organization_admin') {
|
if (selectedAccess && selectedAccess.value === 'admin') {
|
||||||
populatePermissionDetails('member-admin-permissions');
|
populatePermissionDetails('new-member-admin-permissions');
|
||||||
accessText = "Admin"
|
} else {
|
||||||
} else if (selectedAccess && selectedAccess.value === 'organization_member') {
|
populatePermissionDetails('new-member-basic-permissions');
|
||||||
populatePermissionDetails('member-basic-permissions');
|
|
||||||
accessText = "Member"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('modalAccessLevel').textContent = accessText;
|
|
||||||
|
|
||||||
//------- Show the modal
|
//------- Show the modal
|
||||||
let modalTrigger = document.querySelector("#invite_member_trigger");
|
let modalTrigger = document.querySelector("#invite_member_trigger");
|
||||||
if (modalTrigger) {
|
if (modalTrigger) {
|
||||||
modalTrigger.click();
|
modalTrigger.click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initalize the radio for the member pages
|
// Initalize the radio for the member pages
|
||||||
export function initPortfolioMemberPageRadio() {
|
export function initPortfolioMemberPageRadio() {
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
console.log("new content 2")
|
||||||
let memberForm = document.getElementById("member_form");
|
let memberForm = document.getElementById("member_form");
|
||||||
let newMemberForm = document.getElementById("add_member_form")
|
let newMemberForm = document.getElementById("add_member_form")
|
||||||
if (!memberForm && !newMemberForm) {
|
if (memberForm) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
hookupRadioTogglerListener(
|
hookupRadioTogglerListener(
|
||||||
'role',
|
'role',
|
||||||
{
|
{
|
||||||
'organization_admin': 'member-admin-permissions',
|
'organization_admin': 'member-admin-permissions',
|
||||||
'organization_member': 'member-basic-permissions'
|
'organization_member': 'member-basic-permissions'
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
|
}else if (newMemberForm){
|
||||||
|
hookupRadioTogglerListener(
|
||||||
|
'member_access_level',
|
||||||
|
{
|
||||||
|
'admin': 'new-member-admin-permissions',
|
||||||
|
'basic': 'new-member-basic-permissions'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ from django.core.validators import RegexValidator
|
||||||
from django.core.validators import MaxLengthValidator
|
from django.core.validators import MaxLengthValidator
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from registrar.models import (
|
from registrar.models import (
|
||||||
PortfolioInvitation,
|
User,
|
||||||
UserPortfolioPermission,
|
UserPortfolioPermission,
|
||||||
DomainInformation,
|
DomainInformation,
|
||||||
Portfolio,
|
Portfolio,
|
||||||
|
@ -218,6 +218,10 @@ class BasePortfolioMemberForm(forms.Form):
|
||||||
if not cleaned_data.get(field_name):
|
if not cleaned_data.get(field_name):
|
||||||
self.add_error(field_name, self.fields.get(field_name).error_messages.get("required"))
|
self.add_error(field_name, self.fields.get(field_name).error_messages.get("required"))
|
||||||
|
|
||||||
|
# Edgecase: Member uses a special form value for None called "no_access".
|
||||||
|
if cleaned_data.get("domain_request_permission_member") == "no_access":
|
||||||
|
cleaned_data["domain_request_permission_member"] = None
|
||||||
|
|
||||||
return cleaned_data
|
return cleaned_data
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
|
@ -248,27 +252,32 @@ class BasePortfolioMemberForm(forms.Form):
|
||||||
|
|
||||||
# Function variables
|
# Function variables
|
||||||
form_data = {}
|
form_data = {}
|
||||||
is_admin = UserPortfolioRoleChoices.ORGANIZATION_ADMIN in instance.roles if instance.roles else False
|
perms = UserPortfolioPermission.get_portfolio_permissions(
|
||||||
perms = UserPortfolioPermission.get_portfolio_permissions(instance.roles, instance.additional_permissions)
|
instance.roles, instance.additional_permissions, get_list=False
|
||||||
|
)
|
||||||
|
|
||||||
|
# Explanation of this logic pattern: we can only display one item in the list at a time.
|
||||||
|
# But how do we determine what is most important to display in a list? Order-based hierarchy.
|
||||||
|
# Example: print(instance.roles) => (output) ["organization_admin", "organization_member"]
|
||||||
|
# If we can only pick one item in this list, we should pick organization_admin.
|
||||||
|
|
||||||
# Get role
|
# Get role
|
||||||
role = UserPortfolioRoleChoices.ORGANIZATION_MEMBER
|
roles = [UserPortfolioRoleChoices.ORGANIZATION_ADMIN, UserPortfolioRoleChoices.ORGANIZATION_MEMBER]
|
||||||
if is_admin:
|
role = next((role for role in roles if role in instance.roles), None)
|
||||||
role = UserPortfolioRoleChoices.ORGANIZATION_ADMIN
|
is_admin = role == UserPortfolioRoleChoices.ORGANIZATION_ADMIN
|
||||||
|
|
||||||
# Get domain request permission level
|
# Get domain request permission level
|
||||||
domain_request_permission = None
|
# First we get permissions we expect to display (ordered hierarchically).
|
||||||
if UserPortfolioPermissionChoices.EDIT_REQUESTS in perms:
|
# Then we check if this item exists in the list and return the first instance of it.
|
||||||
domain_request_permission = UserPortfolioPermissionChoices.EDIT_REQUESTS
|
domain_permissions = [
|
||||||
elif UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS in perms:
|
UserPortfolioPermissionChoices.EDIT_REQUESTS,
|
||||||
domain_request_permission = UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS
|
UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS,
|
||||||
|
]
|
||||||
|
domain_request_permission = next((perm for perm in domain_permissions if perm in perms), None)
|
||||||
|
|
||||||
# Get member permission level
|
# Get member permission level.
|
||||||
member_permission = None
|
member_permissions = [UserPortfolioPermissionChoices.EDIT_MEMBERS, UserPortfolioPermissionChoices.VIEW_MEMBERS]
|
||||||
if UserPortfolioPermissionChoices.EDIT_MEMBERS in perms:
|
member_permission = next((perm for perm in member_permissions if perm in perms), None)
|
||||||
member_permission = UserPortfolioPermissionChoices.EDIT_MEMBERS
|
|
||||||
elif UserPortfolioPermissionChoices.VIEW_MEMBERS in perms:
|
|
||||||
member_permission = UserPortfolioPermissionChoices.VIEW_MEMBERS
|
|
||||||
|
|
||||||
# Build form data based on role.
|
# Build form data based on role.
|
||||||
form_data = {
|
form_data = {
|
||||||
|
@ -304,24 +313,13 @@ class BasePortfolioMemberForm(forms.Form):
|
||||||
instance: Updated instance
|
instance: Updated instance
|
||||||
"""
|
"""
|
||||||
role = cleaned_data.get("role")
|
role = cleaned_data.get("role")
|
||||||
member_permission_admin = cleaned_data.get("member_permission_admin")
|
|
||||||
domain_request_permission_admin = cleaned_data.get("domain_request_permission_admin")
|
|
||||||
domain_request_permission_member = cleaned_data.get("domain_request_permission_member")
|
|
||||||
|
|
||||||
# Handle roles
|
# Handle roles
|
||||||
instance.roles = [role]
|
instance.roles = [role]
|
||||||
|
|
||||||
# Handle additional_permissions
|
# Handle additional_permissions
|
||||||
additional_permissions = set()
|
valid_fields = self.ROLE_REQUIRED_FIELDS.get(role, [])
|
||||||
if role == UserPortfolioRoleChoices.ORGANIZATION_ADMIN:
|
additional_permissions = {cleaned_data.get(field) for field in valid_fields if cleaned_data.get(field)}
|
||||||
if domain_request_permission_admin:
|
|
||||||
additional_permissions.add(domain_request_permission_admin)
|
|
||||||
|
|
||||||
if member_permission_admin:
|
|
||||||
additional_permissions.add(member_permission_admin)
|
|
||||||
else:
|
|
||||||
if domain_request_permission_member and domain_request_permission_member != "no_access":
|
|
||||||
additional_permissions.add(domain_request_permission_member)
|
|
||||||
|
|
||||||
# Handle EDIT permissions (should be accompanied with a view permission)
|
# Handle EDIT permissions (should be accompanied with a view permission)
|
||||||
if UserPortfolioPermissionChoices.EDIT_MEMBERS in additional_permissions:
|
if UserPortfolioPermissionChoices.EDIT_MEMBERS in additional_permissions:
|
||||||
|
@ -336,7 +334,48 @@ class BasePortfolioMemberForm(forms.Form):
|
||||||
return instance
|
return instance
|
||||||
|
|
||||||
|
|
||||||
class NewMemberForm(BasePortfolioMemberForm):
|
class NewMemberForm(forms.ModelForm):
|
||||||
|
member_access_level = forms.ChoiceField(
|
||||||
|
label="Select permission",
|
||||||
|
choices=[("admin", "Admin Access"), ("basic", "Basic Access")],
|
||||||
|
widget=forms.RadioSelect(attrs={"class": "usa-radio__input usa-radio__input--tile"}),
|
||||||
|
required=True,
|
||||||
|
error_messages={
|
||||||
|
"required": "Member access level is required",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
admin_org_domain_request_permissions = forms.ChoiceField(
|
||||||
|
label="Select permission",
|
||||||
|
choices=[("view_only", "View all requests"), ("view_and_create", "View all requests plus create requests")],
|
||||||
|
widget=forms.RadioSelect,
|
||||||
|
required=True,
|
||||||
|
error_messages={
|
||||||
|
"required": "Admin domain request permission is required",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
admin_org_members_permissions = forms.ChoiceField(
|
||||||
|
label="Select permission",
|
||||||
|
choices=[("view_only", "View all members"), ("view_and_create", "View all members plus manage members")],
|
||||||
|
widget=forms.RadioSelect,
|
||||||
|
required=True,
|
||||||
|
error_messages={
|
||||||
|
"required": "Admin member permission is required",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
basic_org_domain_request_permissions = forms.ChoiceField(
|
||||||
|
label="Select permission",
|
||||||
|
choices=[
|
||||||
|
("view_only", "View all requests"),
|
||||||
|
("view_and_create", "View all requests plus create requests"),
|
||||||
|
("no_access", "No access"),
|
||||||
|
],
|
||||||
|
widget=forms.RadioSelect,
|
||||||
|
required=True,
|
||||||
|
error_messages={
|
||||||
|
"required": "Basic member permission is required",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
email = forms.EmailField(
|
email = forms.EmailField(
|
||||||
label="Enter the email of the member you'd like to invite",
|
label="Enter the email of the member you'd like to invite",
|
||||||
max_length=None,
|
max_length=None,
|
||||||
|
@ -353,23 +392,20 @@ class NewMemberForm(BasePortfolioMemberForm):
|
||||||
required=True,
|
required=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
class Meta:
|
||||||
self.portfolio = kwargs.pop("portfolio", None)
|
model = User
|
||||||
super().__init__(*args, **kwargs)
|
fields = ["email"]
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
cleaned_data = super().clean()
|
cleaned_data = super().clean()
|
||||||
|
|
||||||
# Lowercase the value of the 'email' field
|
# Lowercase the value of the 'email' field
|
||||||
email_value = cleaned_data.get("email")
|
email_value = cleaned_data.get("email")
|
||||||
if email_value:
|
if email_value:
|
||||||
# Check if user is already a member
|
cleaned_data["email"] = email_value.lower()
|
||||||
if UserPortfolioPermission.objects.filter(user__email=email_value, portfolio=self.portfolio).exists():
|
|
||||||
self.add_error("email", "User is already a member of this portfolio.")
|
|
||||||
|
|
||||||
if PortfolioInvitation.objects.filter(email=email_value, portfolio=self.portfolio).exists():
|
|
||||||
self.add_error("email", "An invitation already exists for this user.")
|
|
||||||
##########################################
|
##########################################
|
||||||
# TODO: #3019
|
# TODO: future ticket
|
||||||
# (invite new member)
|
# (invite new member)
|
||||||
##########################################
|
##########################################
|
||||||
# Check for an existing user (if there isn't any, send an invite)
|
# Check for an existing user (if there isn't any, send an invite)
|
||||||
|
@ -378,14 +414,30 @@ class NewMemberForm(BasePortfolioMemberForm):
|
||||||
# existingUser = User.objects.get(email=email_value)
|
# existingUser = User.objects.get(email=email_value)
|
||||||
# except User.DoesNotExist:
|
# except User.DoesNotExist:
|
||||||
# raise forms.ValidationError("User with this email does not exist.")
|
# raise forms.ValidationError("User with this email does not exist.")
|
||||||
|
|
||||||
|
member_access_level = cleaned_data.get("member_access_level")
|
||||||
|
|
||||||
|
# Intercept the error messages so that we don't validate hidden inputs
|
||||||
|
if not member_access_level:
|
||||||
|
# If no member access level has been selected, delete error messages
|
||||||
|
# for all hidden inputs (which is everything except the e-mail input
|
||||||
|
# and member access selection)
|
||||||
|
for field in self.fields:
|
||||||
|
if field in self.errors and field != "email" and field != "member_access_level":
|
||||||
|
del self.errors[field]
|
||||||
return cleaned_data
|
return cleaned_data
|
||||||
|
|
||||||
def map_cleaned_data_to_instance(self, cleaned_data, instance):
|
basic_dom_req_error = "basic_org_domain_request_permissions"
|
||||||
"""Override of the base class to add portfolio and email."""
|
admin_dom_req_error = "admin_org_domain_request_permissions"
|
||||||
instance = super().map_cleaned_data_to_instance(cleaned_data, instance)
|
admin_member_error = "admin_org_members_permissions"
|
||||||
email = cleaned_data.get("email")
|
|
||||||
if email and isinstance(email, str):
|
if member_access_level == "admin" and basic_dom_req_error in self.errors:
|
||||||
email = email.lower()
|
# remove the error messages pertaining to basic permission inputs
|
||||||
instance.email = email
|
del self.errors[basic_dom_req_error]
|
||||||
instance.portfolio = self.portfolio
|
elif member_access_level == "basic":
|
||||||
return instance
|
# remove the error messages pertaining to admin permission inputs
|
||||||
|
if admin_dom_req_error in self.errors:
|
||||||
|
del self.errors[admin_dom_req_error]
|
||||||
|
if admin_member_error in self.errors:
|
||||||
|
del self.errors[admin_member_error]
|
||||||
|
return cleaned_data
|
||||||
|
|
|
@ -56,14 +56,28 @@
|
||||||
|
|
||||||
<em>Select the level of access for this member. <abbr class="usa-hint usa-hint--required" title="required">*</abbr></em>
|
<em>Select the level of access for this member. <abbr class="usa-hint usa-hint--required" title="required">*</abbr></em>
|
||||||
|
|
||||||
{% with add_class="usa-radio__input--tile" add_legend_class="usa-sr-only" %}
|
{% with group_classes="usa-form-editable usa-form-editable--no-border padding-top-0" %}
|
||||||
{% input_with_errors form.role %}
|
<div class="usa-radio">
|
||||||
|
{% for radio in form.member_access_level %}
|
||||||
|
{{ radio.tag }}
|
||||||
|
<label class="usa-radio__label usa-legend" for="{{ radio.id_for_label }}">
|
||||||
|
{{ radio.choice_label }}
|
||||||
|
<p class="margin-0 margin-top-2">
|
||||||
|
{% if radio.choice_label == "Admin Access" %}
|
||||||
|
Grants this member access to the organization-wide information on domains, domain requests, and members. Domain management can be assigned separately.
|
||||||
|
{% else %}
|
||||||
|
Grants this member access to the organization. They can be given extra permissions to view all organization domain requests and submit domain requests on behalf of the organization. Basic access members can’t view all members of an organization or manage them. Domain management can be assigned separately.
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</label>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<!-- Admin access form -->
|
<!-- Admin access form -->
|
||||||
<div id="member-admin-permissions" class="margin-top-2">
|
<div id="new-member-admin-permissions" class="margin-top-2">
|
||||||
<h2>Admin access permissions</h2>
|
<h2>Admin access permissions</h2>
|
||||||
<p>Member permissions available for admin-level acccess.</p>
|
<p>Member permissions available for admin-level acccess.</p>
|
||||||
|
|
||||||
|
@ -71,7 +85,7 @@
|
||||||
text-primary-dark
|
text-primary-dark
|
||||||
margin-bottom-0">Organization domain requests</h3>
|
margin-bottom-0">Organization domain requests</h3>
|
||||||
{% with group_classes="usa-form-editable usa-form-editable--no-border padding-top-0" %}
|
{% with group_classes="usa-form-editable usa-form-editable--no-border padding-top-0" %}
|
||||||
{% input_with_errors form.domain_request_permission_admin %}
|
{% input_with_errors form.admin_org_domain_request_permissions %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
<h3 class="summary-item__title
|
<h3 class="summary-item__title
|
||||||
|
@ -79,20 +93,20 @@
|
||||||
margin-bottom-0
|
margin-bottom-0
|
||||||
margin-top-3">Organization members</h3>
|
margin-top-3">Organization members</h3>
|
||||||
{% with group_classes="usa-form-editable usa-form-editable--no-border padding-top-0" %}
|
{% with group_classes="usa-form-editable usa-form-editable--no-border padding-top-0" %}
|
||||||
{% input_with_errors form.member_permission_admin %}
|
{% input_with_errors form.admin_org_members_permissions %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Basic access form -->
|
<!-- Basic access form -->
|
||||||
<div id="member-basic-permissions" class="margin-top-2">
|
<div id="new-member-basic-permissions" class="margin-top-2">
|
||||||
<h2>Basic member permissions</h2>
|
<h2>Basic member permissions</h2>
|
||||||
<p>Member permissions available for basic-level acccess.</p>
|
<p>Member permissions available for basic-level acccess.</p>
|
||||||
|
|
||||||
<h3 class="margin-bottom-0 summary-item__title text-primary-dark">Organization domain requests</h3>
|
<h3 class="margin-bottom-0">Organization domain requests</h3>
|
||||||
{% with group_classes="usa-form-editable usa-form-editable--no-border padding-top-0" %}
|
{% with group_classes="usa-form-editable usa-form-editable--no-border padding-top-0" %}
|
||||||
{% input_with_errors form.domain_request_permission_member %}
|
{% input_with_errors form.basic_org_domain_request_permissions %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Submit/cancel buttons -->
|
<!-- Submit/cancel buttons -->
|
||||||
<div class="margin-top-3">
|
<div class="margin-top-3">
|
||||||
|
@ -176,4 +190,3 @@
|
||||||
|
|
||||||
{% endblock portfolio_content%}
|
{% endblock portfolio_content%}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ from django.shortcuts import get_object_or_404, redirect, render
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
|
from django.conf import settings
|
||||||
from registrar.forms import portfolio as portfolioForms
|
from registrar.forms import portfolio as portfolioForms
|
||||||
from registrar.models import Portfolio, User
|
from registrar.models import Portfolio, User
|
||||||
from registrar.models.portfolio_invitation import PortfolioInvitation
|
from registrar.models.portfolio_invitation import PortfolioInvitation
|
||||||
|
@ -16,7 +16,6 @@ from registrar.views.utility.permission_views import (
|
||||||
PortfolioDomainsPermissionView,
|
PortfolioDomainsPermissionView,
|
||||||
PortfolioBasePermissionView,
|
PortfolioBasePermissionView,
|
||||||
NoPortfolioDomainsPermissionView,
|
NoPortfolioDomainsPermissionView,
|
||||||
PortfolioInvitationCreatePermissionView,
|
|
||||||
PortfolioMemberDomainsPermissionView,
|
PortfolioMemberDomainsPermissionView,
|
||||||
PortfolioMemberDomainsEditPermissionView,
|
PortfolioMemberDomainsEditPermissionView,
|
||||||
PortfolioMemberEditPermissionView,
|
PortfolioMemberEditPermissionView,
|
||||||
|
@ -506,45 +505,163 @@ class PortfolioMembersView(PortfolioMembersPermissionView, View):
|
||||||
return render(request, "portfolio_members.html")
|
return render(request, "portfolio_members.html")
|
||||||
|
|
||||||
|
|
||||||
class NewMemberView(PortfolioInvitationCreatePermissionView):
|
|
||||||
|
class NewMemberView(PortfolioMembersPermissionView, FormMixin):
|
||||||
|
|
||||||
template_name = "portfolio_members_add_new.html"
|
template_name = "portfolio_members_add_new.html"
|
||||||
form_class = portfolioForms.NewMemberForm
|
form_class = portfolioForms.NewMemberForm
|
||||||
|
|
||||||
|
def get_object(self, queryset=None):
|
||||||
|
"""Get the portfolio object based on the session."""
|
||||||
|
portfolio = self.request.session.get("portfolio")
|
||||||
|
if portfolio is None:
|
||||||
|
raise Http404("No organization found for this user")
|
||||||
|
return portfolio
|
||||||
|
|
||||||
def get_form_kwargs(self):
|
def get_form_kwargs(self):
|
||||||
"""Pass request and portfolio to form."""
|
"""Include the instance in the form kwargs."""
|
||||||
kwargs = super().get_form_kwargs()
|
kwargs = super().get_form_kwargs()
|
||||||
kwargs["portfolio"] = self.request.session.get("portfolio")
|
kwargs["instance"] = self.get_object()
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def get_success_url(self):
|
def get(self, request, *args, **kwargs):
|
||||||
return reverse("members")
|
"""Handle GET requests to display the form."""
|
||||||
|
self.object = self.get_object()
|
||||||
|
form = self.get_form()
|
||||||
|
return self.render_to_response(self.get_context_data(form=form))
|
||||||
|
|
||||||
def form_valid(self, form):
|
def post(self, request, *args, **kwargs):
|
||||||
"""Create portfolio invitation from form data."""
|
"""Handle POST requests to process form submission."""
|
||||||
if self.is_ajax():
|
self.object = self.get_object()
|
||||||
return JsonResponse({"is_valid": True})
|
form = self.get_form()
|
||||||
|
|
||||||
# TODO: #3019 - this will probably have to be a small try/catch. Stub for posterity.
|
if form.is_valid():
|
||||||
# requested_email = form.cleaned_data.get("email")
|
return self.form_valid(form)
|
||||||
# send_success = self.send_portfolio_invitation_email(requested_email)
|
else:
|
||||||
# if not send_success:
|
return self.form_invalid(form)
|
||||||
# return
|
|
||||||
|
|
||||||
# Create instance using form's mapping method.
|
|
||||||
# Pass in a new object since we are adding a new record.
|
|
||||||
self.object = form.map_cleaned_data_to_instance(form.cleaned_data, PortfolioInvitation())
|
|
||||||
self.object.save()
|
|
||||||
messages.success(self.request, f"{self.object.email} has been invited.")
|
|
||||||
return redirect(self.get_success_url())
|
|
||||||
|
|
||||||
# TODO: #3019
|
|
||||||
# def send_portfolio_invitation_email(self, email):
|
|
||||||
# pass
|
|
||||||
|
|
||||||
def form_invalid(self, form):
|
|
||||||
if self.is_ajax():
|
|
||||||
return JsonResponse({"is_valid": False})
|
|
||||||
return super().form_invalid(form)
|
|
||||||
|
|
||||||
def is_ajax(self):
|
def is_ajax(self):
|
||||||
return self.request.headers.get("X-Requested-With") == "XMLHttpRequest"
|
return self.request.headers.get("X-Requested-With") == "XMLHttpRequest"
|
||||||
|
|
||||||
|
def form_invalid(self, form):
|
||||||
|
if self.is_ajax():
|
||||||
|
return JsonResponse({"is_valid": False}) # Return a JSON response
|
||||||
|
else:
|
||||||
|
return super().form_invalid(form) # Handle non-AJAX requests normally
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
|
||||||
|
if self.is_ajax():
|
||||||
|
return JsonResponse({"is_valid": True}) # Return a JSON response
|
||||||
|
else:
|
||||||
|
return self.submit_new_member(form)
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
"""Redirect to members table."""
|
||||||
|
return reverse("members")
|
||||||
|
|
||||||
|
def _send_portfolio_invitation_email(self, email: str, requestor: User, add_success=True):
|
||||||
|
"""Performs the sending of the member invitation email
|
||||||
|
email: string- email to send to
|
||||||
|
add_success: bool- default True indicates:
|
||||||
|
adding a success message to the view if the email sending succeeds
|
||||||
|
|
||||||
|
raises EmailSendingError
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Set a default email address to send to for staff
|
||||||
|
requestor_email = settings.DEFAULT_FROM_EMAIL
|
||||||
|
|
||||||
|
# Check if the email requestor has a valid email address
|
||||||
|
if not requestor.is_staff and requestor.email is not None and requestor.email.strip() != "":
|
||||||
|
requestor_email = requestor.email
|
||||||
|
elif not requestor.is_staff:
|
||||||
|
messages.error(self.request, "Can't send invitation email. No email is associated with your account.")
|
||||||
|
logger.error(
|
||||||
|
f"Can't send email to '{email}' on domain '{self.object}'."
|
||||||
|
f"No email exists for the requestor '{requestor.username}'.",
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Check to see if an invite has already been sent
|
||||||
|
try:
|
||||||
|
invite = PortfolioInvitation.objects.get(email=email, portfolio=self.object)
|
||||||
|
if invite: # We have an existin invite
|
||||||
|
# check if the invite has already been accepted
|
||||||
|
if invite.status == PortfolioInvitation.PortfolioInvitationStatus.RETRIEVED:
|
||||||
|
add_success = False
|
||||||
|
messages.warning(
|
||||||
|
self.request,
|
||||||
|
f"{email} is already a manager for this portfolio.",
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
add_success = False
|
||||||
|
# it has been sent but not accepted
|
||||||
|
messages.warning(self.request, f"{email} has already been invited to this portfolio")
|
||||||
|
return
|
||||||
|
except Exception as err:
|
||||||
|
logger.error(f"_send_portfolio_invitation_email() => An error occured: {err}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
logger.debug("requestor email: " + requestor_email)
|
||||||
|
|
||||||
|
# send_templated_email(
|
||||||
|
# "emails/portfolio_invitation.txt",
|
||||||
|
# "emails/portfolio_invitation_subject.txt",
|
||||||
|
# to_address=email,
|
||||||
|
# context={
|
||||||
|
# "portfolio": self.object,
|
||||||
|
# "requestor_email": requestor_email,
|
||||||
|
# },
|
||||||
|
# )
|
||||||
|
except EmailSendingError as exc:
|
||||||
|
logger.warn(
|
||||||
|
"Could not sent email invitation to %s for domain %s",
|
||||||
|
email,
|
||||||
|
self.object,
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
raise EmailSendingError("Could not send email invitation.") from exc
|
||||||
|
else:
|
||||||
|
if add_success:
|
||||||
|
messages.success(self.request, f"{email} has been invited.")
|
||||||
|
|
||||||
|
def _make_invitation(self, email_address: str, requestor: User, add_success=True):
|
||||||
|
"""Make a Member invitation for this email and redirect with a message."""
|
||||||
|
try:
|
||||||
|
self._send_portfolio_invitation_email(email=email_address, requestor=requestor, add_success=add_success)
|
||||||
|
except EmailSendingError:
|
||||||
|
logger.warn(
|
||||||
|
"Could not send email invitation (EmailSendingError)",
|
||||||
|
self.object,
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
messages.warning(self.request, "Could not send email invitation.")
|
||||||
|
except Exception:
|
||||||
|
logger.warn(
|
||||||
|
"Could not send email invitation (Other Exception)",
|
||||||
|
self.object,
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
messages.warning(self.request, "Could not send email invitation.")
|
||||||
|
else:
|
||||||
|
# (NOTE: only create a MemberInvitation if the e-mail sends correctly)
|
||||||
|
PortfolioInvitation.objects.get_or_create(email=email_address, portfolio=self.object)
|
||||||
|
return redirect(self.get_success_url())
|
||||||
|
|
||||||
|
def submit_new_member(self, form):
|
||||||
|
"""Add the specified user as a member
|
||||||
|
for this portfolio.
|
||||||
|
Throws EmailSendingError."""
|
||||||
|
requested_email = form.cleaned_data["email"]
|
||||||
|
requestor = self.request.user
|
||||||
|
|
||||||
|
requested_user = User.objects.filter(email=requested_email).first()
|
||||||
|
permission_exists = UserPortfolioPermission.objects.filter(user=requested_user, portfolio=self.object).exists()
|
||||||
|
if not requested_user or not permission_exists:
|
||||||
|
return self._make_invitation(requested_email, requestor)
|
||||||
|
else:
|
||||||
|
if permission_exists:
|
||||||
|
messages.warning(self.request, "User is already a member of this portfolio.")
|
||||||
|
return redirect(self.get_success_url())
|
||||||
|
|
|
@ -9,6 +9,5 @@ from .permission_views import (
|
||||||
PortfolioMembersPermission,
|
PortfolioMembersPermission,
|
||||||
DomainRequestPortfolioViewonlyView,
|
DomainRequestPortfolioViewonlyView,
|
||||||
DomainInvitationPermissionCancelView,
|
DomainInvitationPermissionCancelView,
|
||||||
PortfolioInvitationCreatePermissionView,
|
|
||||||
)
|
)
|
||||||
from .api_views import get_senior_official_from_federal_agency_json
|
from .api_views import get_senior_official_from_federal_agency_json
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
"""View classes that enforce authorization."""
|
"""View classes that enforce authorization."""
|
||||||
|
|
||||||
import abc # abstract base class
|
import abc # abstract base class
|
||||||
from django.views.generic.edit import CreateView
|
|
||||||
from django.views.generic import DetailView, DeleteView, TemplateView, UpdateView
|
from django.views.generic import DetailView, DeleteView, TemplateView, UpdateView
|
||||||
from registrar.models import Domain, DomainRequest, DomainInvitation, Portfolio
|
from registrar.models import Domain, DomainRequest, DomainInvitation, Portfolio
|
||||||
from registrar.models.portfolio_invitation import PortfolioInvitation
|
from registrar.models.portfolio_invitation import PortfolioInvitation
|
||||||
|
@ -227,25 +226,6 @@ class PortfolioBasePermissionView(PortfolioBasePermission, DetailView, abc.ABC):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class PortfolioInvitationCreatePermissionView(PortfolioInvitationCreatePermission, CreateView, abc.ABC):
|
|
||||||
"""Abstract base view for portfolio views that enforces permissions.
|
|
||||||
|
|
||||||
This abstract view cannot be instantiated. Actual views must specify
|
|
||||||
`template_name`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# DetailView property for what model this is viewing
|
|
||||||
model = PortfolioInvitation
|
|
||||||
# variable name in template context for the model object
|
|
||||||
context_object_name = "portfolio_invitation"
|
|
||||||
|
|
||||||
# Abstract property enforces NotImplementedError on an attribute.
|
|
||||||
@property
|
|
||||||
@abc.abstractmethod
|
|
||||||
def template_name(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
class PortfolioDomainsPermissionView(PortfolioDomainsPermission, PortfolioBasePermissionView, abc.ABC):
|
class PortfolioDomainsPermissionView(PortfolioDomainsPermission, PortfolioBasePermissionView, abc.ABC):
|
||||||
"""Abstract base view for portfolio domains views that enforces permissions.
|
"""Abstract base view for portfolio domains views that enforces permissions.
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue