Remove changes to newmember

This commit is contained in:
zandercymatics 2024-12-18 11:44:58 -07:00
parent c5184e5b29
commit 437981ff30
No known key found for this signature in database
GPG key ID: FF4636ABEC9682B7
6 changed files with 449 additions and 274 deletions

View file

@ -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'
}
);
}
}); });
} }

View file

@ -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

View file

@ -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 cant 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%}

View file

@ -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())

View file

@ -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

View file

@ -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.