mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-08-06 09:45:23 +02:00
852 lines
34 KiB
Python
852 lines
34 KiB
Python
import json
|
|
import logging
|
|
from django.conf import settings
|
|
|
|
from django.http import Http404, JsonResponse
|
|
from django.shortcuts import get_object_or_404, redirect, render
|
|
from django.urls import reverse
|
|
from django.utils.safestring import mark_safe
|
|
from django.contrib import messages
|
|
from registrar.forms import portfolio as portfolioForms
|
|
from registrar.models import Portfolio, User
|
|
from registrar.models.domain_invitation import DomainInvitation
|
|
from registrar.models.portfolio_invitation import PortfolioInvitation
|
|
from registrar.models.user_domain_role import UserDomainRole
|
|
from registrar.models.user_portfolio_permission import UserPortfolioPermission
|
|
from registrar.models.utility.portfolio_helper import UserPortfolioPermissionChoices, UserPortfolioRoleChoices
|
|
from registrar.utility.email import EmailSendingError
|
|
from registrar.utility.enums import DefaultUserValues
|
|
from registrar.views.utility.mixins import PortfolioMemberPermission
|
|
from registrar.views.utility.permission_views import (
|
|
PortfolioDomainRequestsPermissionView,
|
|
PortfolioDomainsPermissionView,
|
|
PortfolioBasePermissionView,
|
|
NoPortfolioDomainsPermissionView,
|
|
PortfolioMemberDomainsPermissionView,
|
|
PortfolioMemberDomainsEditPermissionView,
|
|
PortfolioMemberEditPermissionView,
|
|
PortfolioMemberPermissionView,
|
|
PortfolioMembersPermissionView,
|
|
)
|
|
from django.views.generic import View
|
|
from django.views.generic.edit import FormMixin
|
|
from django.db import IntegrityError
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class PortfolioDomainsView(PortfolioDomainsPermissionView, View):
|
|
|
|
template_name = "portfolio_domains.html"
|
|
|
|
def get(self, request):
|
|
context = {}
|
|
if self.request and self.request.user and self.request.user.is_authenticated:
|
|
context["user_domain_count"] = self.request.user.get_user_domain_ids(request).count()
|
|
context["num_expiring_domains"] = request.user.get_num_expiring_domains(request)
|
|
|
|
return render(request, "portfolio_domains.html", context)
|
|
|
|
|
|
class PortfolioDomainRequestsView(PortfolioDomainRequestsPermissionView, View):
|
|
|
|
template_name = "portfolio_requests.html"
|
|
|
|
def get(self, request):
|
|
return render(request, "portfolio_requests.html")
|
|
|
|
|
|
class PortfolioMemberView(PortfolioMemberPermissionView, View):
|
|
|
|
template_name = "portfolio_member.html"
|
|
|
|
def get(self, request, pk):
|
|
portfolio_permission = get_object_or_404(UserPortfolioPermission, pk=pk)
|
|
member = portfolio_permission.user
|
|
|
|
# We have to explicitely name these with member_ otherwise we'll have conflicts with context preprocessors
|
|
member_has_view_all_requests_portfolio_permission = member.has_view_all_requests_portfolio_permission(
|
|
portfolio_permission.portfolio
|
|
)
|
|
member_has_edit_request_portfolio_permission = member.has_edit_request_portfolio_permission(
|
|
portfolio_permission.portfolio
|
|
)
|
|
member_has_view_members_portfolio_permission = member.has_view_members_portfolio_permission(
|
|
portfolio_permission.portfolio
|
|
)
|
|
member_has_edit_members_portfolio_permission = member.has_edit_members_portfolio_permission(
|
|
portfolio_permission.portfolio
|
|
)
|
|
|
|
return render(
|
|
request,
|
|
self.template_name,
|
|
{
|
|
"edit_url": reverse("member-permissions", args=[pk]),
|
|
"domains_url": reverse("member-domains", args=[pk]),
|
|
"portfolio_permission": portfolio_permission,
|
|
"member": member,
|
|
"member_has_view_all_requests_portfolio_permission": member_has_view_all_requests_portfolio_permission,
|
|
"member_has_edit_request_portfolio_permission": member_has_edit_request_portfolio_permission,
|
|
"member_has_view_members_portfolio_permission": member_has_view_members_portfolio_permission,
|
|
"member_has_edit_members_portfolio_permission": member_has_edit_members_portfolio_permission,
|
|
},
|
|
)
|
|
|
|
|
|
class PortfolioMemberDeleteView(PortfolioMemberPermission, View):
|
|
|
|
def post(self, request, 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).
|
|
"""
|
|
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)
|
|
|
|
support_url = "https://get.gov/contact/"
|
|
|
|
error_message = ""
|
|
|
|
if active_requests_count > 0:
|
|
# If they have any in progress requests
|
|
error_message = mark_safe( # nosec
|
|
f"This member has an active domain request and can't be removed from the organization. "
|
|
f"<a href='{support_url}' target='_blank'>Contact the .gov team</a> to remove them."
|
|
)
|
|
elif member.is_only_admin_of_portfolio(portfolio_member_permission.portfolio):
|
|
# If they are the last manager of a domain
|
|
error_message = (
|
|
"There must be at least one admin in your organization. Give another member admin "
|
|
"permissions, make sure they log into the registrar, and then remove this member."
|
|
)
|
|
|
|
# From the Members Table page Else the Member Page
|
|
if error_message:
|
|
if request.headers.get("X-Requested-With") == "XMLHttpRequest":
|
|
return JsonResponse(
|
|
{"error": error_message},
|
|
status=400,
|
|
)
|
|
else:
|
|
messages.error(request, error_message)
|
|
return redirect(reverse("member", kwargs={"pk": pk}))
|
|
|
|
# passed all error conditions
|
|
portfolio_member_permission.delete()
|
|
|
|
# From the Members Table page Else the Member Page
|
|
success_message = f"You've removed {member.email} from the organization."
|
|
if request.headers.get("X-Requested-With") == "XMLHttpRequest":
|
|
return JsonResponse({"success": success_message}, status=200)
|
|
else:
|
|
messages.success(request, success_message)
|
|
return redirect(reverse("members"))
|
|
|
|
|
|
class PortfolioMemberEditView(PortfolioMemberEditPermissionView, View):
|
|
|
|
template_name = "portfolio_member_permissions.html"
|
|
form_class = portfolioForms.BasePortfolioMemberForm
|
|
|
|
def get(self, request, pk):
|
|
portfolio_permission = get_object_or_404(UserPortfolioPermission, pk=pk)
|
|
user = portfolio_permission.user
|
|
|
|
form = self.form_class(instance=portfolio_permission)
|
|
|
|
return render(
|
|
request,
|
|
self.template_name,
|
|
{
|
|
"form": form,
|
|
"member": user,
|
|
},
|
|
)
|
|
|
|
def post(self, request, pk):
|
|
portfolio_permission = get_object_or_404(UserPortfolioPermission, pk=pk)
|
|
user = portfolio_permission.user
|
|
form = self.form_class(request.POST, instance=portfolio_permission)
|
|
if form.is_valid():
|
|
# Check if user is removing their own admin or edit role
|
|
removing_admin_role_on_self = (
|
|
request.user == user
|
|
and UserPortfolioRoleChoices.ORGANIZATION_ADMIN in portfolio_permission.roles
|
|
and UserPortfolioRoleChoices.ORGANIZATION_ADMIN not in form.cleaned_data.get("role", [])
|
|
)
|
|
form.save()
|
|
messages.success(self.request, "The member access and permission changes have been saved.")
|
|
return redirect("member", pk=pk) if not removing_admin_role_on_self else redirect("home")
|
|
|
|
return render(
|
|
request,
|
|
self.template_name,
|
|
{
|
|
"form": form,
|
|
"member": user, # Pass the user object again to the template
|
|
},
|
|
)
|
|
|
|
|
|
class PortfolioMemberDomainsView(PortfolioMemberDomainsPermissionView, View):
|
|
|
|
template_name = "portfolio_member_domains.html"
|
|
|
|
def get(self, request, pk):
|
|
portfolio_permission = get_object_or_404(UserPortfolioPermission, pk=pk)
|
|
member = portfolio_permission.user
|
|
|
|
return render(
|
|
request,
|
|
self.template_name,
|
|
{
|
|
"portfolio_permission": portfolio_permission,
|
|
"member": member,
|
|
},
|
|
)
|
|
|
|
|
|
class PortfolioMemberDomainsEditView(PortfolioMemberDomainsEditPermissionView, View):
|
|
|
|
template_name = "portfolio_member_domains_edit.html"
|
|
|
|
def get(self, request, pk):
|
|
portfolio_permission = get_object_or_404(UserPortfolioPermission, pk=pk)
|
|
member = portfolio_permission.user
|
|
|
|
return render(
|
|
request,
|
|
self.template_name,
|
|
{
|
|
"portfolio_permission": portfolio_permission,
|
|
"member": member,
|
|
},
|
|
)
|
|
|
|
def post(self, request, pk):
|
|
"""
|
|
Handles adding and removing domains for a portfolio member.
|
|
"""
|
|
added_domains = request.POST.get("added_domains")
|
|
removed_domains = request.POST.get("removed_domains")
|
|
portfolio_permission = get_object_or_404(UserPortfolioPermission, pk=pk)
|
|
member = portfolio_permission.user
|
|
|
|
added_domain_ids = self._parse_domain_ids(added_domains, "added domains")
|
|
if added_domain_ids is None:
|
|
return redirect(reverse("member-domains", kwargs={"pk": pk}))
|
|
|
|
removed_domain_ids = self._parse_domain_ids(removed_domains, "removed domains")
|
|
if removed_domain_ids is None:
|
|
return redirect(reverse("member-domains", kwargs={"pk": pk}))
|
|
|
|
if added_domain_ids or removed_domain_ids:
|
|
try:
|
|
self._process_added_domains(added_domain_ids, member)
|
|
self._process_removed_domains(removed_domain_ids, member)
|
|
messages.success(request, "The domain assignment changes have been saved.")
|
|
return redirect(reverse("member-domains", kwargs={"pk": pk}))
|
|
except IntegrityError:
|
|
messages.error(
|
|
request,
|
|
"A database error occurred while saving changes. If the issue persists, "
|
|
f"please contact {DefaultUserValues.HELP_EMAIL}.",
|
|
)
|
|
logger.error("A database error occurred while saving changes.")
|
|
return redirect(reverse("member-domains-edit", kwargs={"pk": pk}))
|
|
except Exception as e:
|
|
messages.error(
|
|
request,
|
|
"An unexpected error occurred: {str(e)}. If the issue persists, "
|
|
f"please contact {DefaultUserValues.HELP_EMAIL}.",
|
|
)
|
|
logger.error(f"An unexpected error occurred: {str(e)}")
|
|
return redirect(reverse("member-domains-edit", kwargs={"pk": pk}))
|
|
else:
|
|
messages.info(request, "No changes detected.")
|
|
return redirect(reverse("member-domains", kwargs={"pk": pk}))
|
|
|
|
def _parse_domain_ids(self, domain_data, domain_type):
|
|
"""
|
|
Parses the domain IDs from the request and handles JSON errors.
|
|
"""
|
|
try:
|
|
return json.loads(domain_data) if domain_data else []
|
|
except json.JSONDecodeError:
|
|
messages.error(
|
|
self.request,
|
|
"Invalid data for {domain_type}. If the issue persists, "
|
|
f"please contact {DefaultUserValues.HELP_EMAIL}.",
|
|
)
|
|
logger.error(f"Invalid data for {domain_type}")
|
|
return None
|
|
|
|
def _process_added_domains(self, added_domain_ids, member):
|
|
"""
|
|
Processes added domains by bulk creating UserDomainRole instances.
|
|
"""
|
|
if added_domain_ids:
|
|
# Bulk create UserDomainRole instances for added domains
|
|
UserDomainRole.objects.bulk_create(
|
|
[UserDomainRole(domain_id=domain_id, user=member) for domain_id in added_domain_ids],
|
|
ignore_conflicts=True, # Avoid duplicate entries
|
|
)
|
|
|
|
def _process_removed_domains(self, removed_domain_ids, member):
|
|
"""
|
|
Processes removed domains by deleting corresponding UserDomainRole instances.
|
|
"""
|
|
if removed_domain_ids:
|
|
# Delete UserDomainRole instances for removed domains
|
|
UserDomainRole.objects.filter(domain_id__in=removed_domain_ids, user=member).delete()
|
|
|
|
|
|
class PortfolioInvitedMemberView(PortfolioMemberPermissionView, View):
|
|
|
|
template_name = "portfolio_member.html"
|
|
# form_class = PortfolioInvitedMemberForm
|
|
|
|
def get(self, request, pk):
|
|
portfolio_invitation = get_object_or_404(PortfolioInvitation, pk=pk)
|
|
# form = self.form_class(instance=portfolio_invitation)
|
|
|
|
# We have to explicitely name these with member_ otherwise we'll have conflicts with context preprocessors
|
|
member_has_view_all_requests_portfolio_permission = (
|
|
UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS in portfolio_invitation.get_portfolio_permissions()
|
|
)
|
|
member_has_edit_request_portfolio_permission = (
|
|
UserPortfolioPermissionChoices.EDIT_REQUESTS in portfolio_invitation.get_portfolio_permissions()
|
|
)
|
|
member_has_view_members_portfolio_permission = (
|
|
UserPortfolioPermissionChoices.VIEW_MEMBERS in portfolio_invitation.get_portfolio_permissions()
|
|
)
|
|
member_has_edit_members_portfolio_permission = (
|
|
UserPortfolioPermissionChoices.EDIT_MEMBERS in portfolio_invitation.get_portfolio_permissions()
|
|
)
|
|
|
|
return render(
|
|
request,
|
|
self.template_name,
|
|
{
|
|
"edit_url": reverse("invitedmember-permissions", args=[pk]),
|
|
"domains_url": reverse("invitedmember-domains", args=[pk]),
|
|
"portfolio_invitation": portfolio_invitation,
|
|
"member_has_view_all_requests_portfolio_permission": member_has_view_all_requests_portfolio_permission,
|
|
"member_has_edit_request_portfolio_permission": member_has_edit_request_portfolio_permission,
|
|
"member_has_view_members_portfolio_permission": member_has_view_members_portfolio_permission,
|
|
"member_has_edit_members_portfolio_permission": member_has_edit_members_portfolio_permission,
|
|
},
|
|
)
|
|
|
|
|
|
class PortfolioInvitedMemberDeleteView(PortfolioMemberPermission, View):
|
|
|
|
def post(self, request, pk):
|
|
"""
|
|
Find and delete the portfolio invited member using the provided primary key (pk).
|
|
Redirect to a success page after deletion (or any other appropriate page).
|
|
"""
|
|
portfolio_invitation = get_object_or_404(PortfolioInvitation, pk=pk)
|
|
|
|
portfolio_invitation.delete()
|
|
|
|
success_message = f"You've removed {portfolio_invitation.email} from the organization."
|
|
# From the Members Table page Else the Member Page
|
|
if request.headers.get("X-Requested-With") == "XMLHttpRequest":
|
|
return JsonResponse({"success": success_message}, status=200)
|
|
else:
|
|
messages.success(request, success_message)
|
|
return redirect(reverse("members"))
|
|
|
|
|
|
class PortfolioInvitedMemberEditView(PortfolioMemberEditPermissionView, View):
|
|
|
|
template_name = "portfolio_member_permissions.html"
|
|
form_class = portfolioForms.BasePortfolioMemberForm
|
|
|
|
def get(self, request, pk):
|
|
portfolio_invitation = get_object_or_404(PortfolioInvitation, pk=pk)
|
|
form = self.form_class(instance=portfolio_invitation)
|
|
|
|
return render(
|
|
request,
|
|
self.template_name,
|
|
{
|
|
"form": form,
|
|
"invitation": portfolio_invitation,
|
|
},
|
|
)
|
|
|
|
def post(self, request, pk):
|
|
portfolio_invitation = get_object_or_404(PortfolioInvitation, pk=pk)
|
|
form = self.form_class(request.POST, instance=portfolio_invitation)
|
|
if form.is_valid():
|
|
form.save()
|
|
messages.success(self.request, "The member access and permission changes have been saved.")
|
|
return redirect("invitedmember", pk=pk)
|
|
|
|
return render(
|
|
request,
|
|
self.template_name,
|
|
{
|
|
"form": form,
|
|
"invitation": portfolio_invitation, # Pass the user object again to the template
|
|
},
|
|
)
|
|
|
|
|
|
class PortfolioInvitedMemberDomainsView(PortfolioMemberDomainsPermissionView, View):
|
|
|
|
template_name = "portfolio_member_domains.html"
|
|
|
|
def get(self, request, pk):
|
|
portfolio_invitation = get_object_or_404(PortfolioInvitation, pk=pk)
|
|
|
|
return render(
|
|
request,
|
|
self.template_name,
|
|
{
|
|
"portfolio_invitation": portfolio_invitation,
|
|
},
|
|
)
|
|
|
|
|
|
class PortfolioInvitedMemberDomainsEditView(PortfolioMemberDomainsEditPermissionView, View):
|
|
|
|
template_name = "portfolio_member_domains_edit.html"
|
|
|
|
def get(self, request, pk):
|
|
portfolio_invitation = get_object_or_404(PortfolioInvitation, pk=pk)
|
|
|
|
return render(
|
|
request,
|
|
self.template_name,
|
|
{
|
|
"portfolio_invitation": portfolio_invitation,
|
|
},
|
|
)
|
|
|
|
def post(self, request, pk):
|
|
"""
|
|
Handles adding and removing domains for a portfolio invitee.
|
|
"""
|
|
added_domains = request.POST.get("added_domains")
|
|
removed_domains = request.POST.get("removed_domains")
|
|
portfolio_invitation = get_object_or_404(PortfolioInvitation, pk=pk)
|
|
email = portfolio_invitation.email
|
|
|
|
added_domain_ids = self._parse_domain_ids(added_domains, "added domains")
|
|
if added_domain_ids is None:
|
|
return redirect(reverse("invitedmember-domains", kwargs={"pk": pk}))
|
|
|
|
removed_domain_ids = self._parse_domain_ids(removed_domains, "removed domains")
|
|
if removed_domain_ids is None:
|
|
return redirect(reverse("invitedmember-domains", kwargs={"pk": pk}))
|
|
|
|
if added_domain_ids or removed_domain_ids:
|
|
try:
|
|
self._process_added_domains(added_domain_ids, email)
|
|
self._process_removed_domains(removed_domain_ids, email)
|
|
messages.success(request, "The domain assignment changes have been saved.")
|
|
return redirect(reverse("invitedmember-domains", kwargs={"pk": pk}))
|
|
except IntegrityError:
|
|
messages.error(
|
|
request,
|
|
"A database error occurred while saving changes. If the issue persists, "
|
|
f"please contact {DefaultUserValues.HELP_EMAIL}.",
|
|
)
|
|
logger.error("A database error occurred while saving changes.")
|
|
return redirect(reverse("invitedmember-domains-edit", kwargs={"pk": pk}))
|
|
except Exception as e:
|
|
messages.error(
|
|
request,
|
|
"An unexpected error occurred: {str(e)}. If the issue persists, "
|
|
f"please contact {DefaultUserValues.HELP_EMAIL}.",
|
|
)
|
|
logger.error(f"An unexpected error occurred: {str(e)}.")
|
|
return redirect(reverse("invitedmember-domains-edit", kwargs={"pk": pk}))
|
|
else:
|
|
messages.info(request, "No changes detected.")
|
|
return redirect(reverse("invitedmember-domains", kwargs={"pk": pk}))
|
|
|
|
def _parse_domain_ids(self, domain_data, domain_type):
|
|
"""
|
|
Parses the domain IDs from the request and handles JSON errors.
|
|
"""
|
|
try:
|
|
return json.loads(domain_data) if domain_data else []
|
|
except json.JSONDecodeError:
|
|
messages.error(
|
|
self.request,
|
|
"Invalid data for {domain_type}. If the issue persists, "
|
|
f"please contact {DefaultUserValues.HELP_EMAIL}.",
|
|
)
|
|
logger.error(f"Invalid data for {domain_type}.")
|
|
return None
|
|
|
|
def _process_added_domains(self, added_domain_ids, email):
|
|
"""
|
|
Processes added domain invitations by updating existing invitations
|
|
or creating new ones.
|
|
"""
|
|
if not added_domain_ids:
|
|
return
|
|
|
|
# Update existing invitations from CANCELED to INVITED
|
|
existing_invitations = DomainInvitation.objects.filter(domain_id__in=added_domain_ids, email=email)
|
|
existing_invitations.update(status=DomainInvitation.DomainInvitationStatus.INVITED)
|
|
|
|
# Determine which domains need new invitations
|
|
existing_domain_ids = existing_invitations.values_list("domain_id", flat=True)
|
|
new_domain_ids = set(added_domain_ids) - set(existing_domain_ids)
|
|
|
|
# Bulk create new invitations
|
|
DomainInvitation.objects.bulk_create(
|
|
[
|
|
DomainInvitation(
|
|
domain_id=domain_id,
|
|
email=email,
|
|
status=DomainInvitation.DomainInvitationStatus.INVITED,
|
|
)
|
|
for domain_id in new_domain_ids
|
|
]
|
|
)
|
|
|
|
def _process_removed_domains(self, removed_domain_ids, email):
|
|
"""
|
|
Processes removed domain invitations by updating their status to CANCELED.
|
|
"""
|
|
if not removed_domain_ids:
|
|
return
|
|
|
|
# Update invitations from INVITED to CANCELED
|
|
DomainInvitation.objects.filter(
|
|
domain_id__in=removed_domain_ids,
|
|
email=email,
|
|
status=DomainInvitation.DomainInvitationStatus.INVITED,
|
|
).update(status=DomainInvitation.DomainInvitationStatus.CANCELED)
|
|
|
|
|
|
class PortfolioNoDomainsView(NoPortfolioDomainsPermissionView, View):
|
|
"""Some users have access to the underlying portfolio, but not any domains.
|
|
This is a custom view which explains that to the user - and denotes who to contact.
|
|
"""
|
|
|
|
model = Portfolio
|
|
template_name = "portfolio_no_domains.html"
|
|
|
|
def get(self, request):
|
|
return render(request, self.template_name, context=self.get_context_data())
|
|
|
|
def get_context_data(self, **kwargs):
|
|
"""Add additional context data to the template."""
|
|
# We can override the base class. This view only needs this item.
|
|
context = {}
|
|
portfolio = self.request.session.get("portfolio")
|
|
if portfolio:
|
|
admin_ids = UserPortfolioPermission.objects.filter(
|
|
portfolio=portfolio,
|
|
roles__overlap=[
|
|
UserPortfolioRoleChoices.ORGANIZATION_ADMIN,
|
|
],
|
|
).values_list("user__id", flat=True)
|
|
|
|
admin_users = User.objects.filter(id__in=admin_ids)
|
|
context["portfolio_administrators"] = admin_users
|
|
return context
|
|
|
|
|
|
class PortfolioNoDomainRequestsView(NoPortfolioDomainsPermissionView, View):
|
|
"""Some users have access to the underlying portfolio, but not any domain requests.
|
|
This is a custom view which explains that to the user - and denotes who to contact.
|
|
"""
|
|
|
|
model = Portfolio
|
|
template_name = "portfolio_no_requests.html"
|
|
|
|
def get(self, request):
|
|
return render(request, self.template_name, context=self.get_context_data())
|
|
|
|
def get_context_data(self, **kwargs):
|
|
"""Add additional context data to the template."""
|
|
# We can override the base class. This view only needs this item.
|
|
context = {}
|
|
portfolio = self.request.session.get("portfolio")
|
|
if portfolio:
|
|
admin_ids = UserPortfolioPermission.objects.filter(
|
|
portfolio=portfolio,
|
|
roles__overlap=[
|
|
UserPortfolioRoleChoices.ORGANIZATION_ADMIN,
|
|
],
|
|
).values_list("user__id", flat=True)
|
|
|
|
admin_users = User.objects.filter(id__in=admin_ids)
|
|
context["portfolio_administrators"] = admin_users
|
|
return context
|
|
|
|
|
|
class PortfolioOrganizationView(PortfolioBasePermissionView, FormMixin):
|
|
"""
|
|
View to handle displaying and updating the portfolio's organization details.
|
|
"""
|
|
|
|
model = Portfolio
|
|
template_name = "portfolio_organization.html"
|
|
form_class = portfolioForms.PortfolioOrgAddressForm
|
|
context_object_name = "portfolio"
|
|
|
|
def get_context_data(self, **kwargs):
|
|
"""Add additional context data to the template."""
|
|
context = super().get_context_data(**kwargs)
|
|
portfolio = self.request.session.get("portfolio")
|
|
context["has_edit_org_portfolio_permission"] = self.request.user.has_edit_org_portfolio_permission(portfolio)
|
|
return context
|
|
|
|
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):
|
|
"""Include the instance in the form kwargs."""
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs["instance"] = self.get_object()
|
|
return kwargs
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
"""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 post(self, request, *args, **kwargs):
|
|
"""Handle POST requests to process form submission."""
|
|
self.object = self.get_object()
|
|
form = self.get_form()
|
|
if form.is_valid():
|
|
return self.form_valid(form)
|
|
else:
|
|
return self.form_invalid(form)
|
|
|
|
def form_valid(self, form):
|
|
"""Handle the case when the form is valid."""
|
|
self.object = form.save(commit=False)
|
|
self.object.creator = self.request.user
|
|
self.object.save()
|
|
messages.success(self.request, "The organization information for this portfolio has been updated.")
|
|
return super().form_valid(form)
|
|
|
|
def form_invalid(self, form):
|
|
"""Handle the case when the form is invalid."""
|
|
return self.render_to_response(self.get_context_data(form=form))
|
|
|
|
def get_success_url(self):
|
|
"""Redirect to the overview page for the portfolio."""
|
|
return reverse("organization")
|
|
|
|
|
|
class PortfolioSeniorOfficialView(PortfolioBasePermissionView, FormMixin):
|
|
"""
|
|
View to handle displaying and updating the portfolio's senior official details.
|
|
For now, this view is readonly.
|
|
"""
|
|
|
|
model = Portfolio
|
|
template_name = "portfolio_senior_official.html"
|
|
form_class = portfolioForms.PortfolioSeniorOfficialForm
|
|
context_object_name = "portfolio"
|
|
|
|
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):
|
|
"""Include the instance in the form kwargs."""
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs["instance"] = self.get_object().senior_official
|
|
return kwargs
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
"""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))
|
|
|
|
|
|
class PortfolioMembersView(PortfolioMembersPermissionView, View):
|
|
|
|
template_name = "portfolio_members.html"
|
|
|
|
def get(self, request):
|
|
"""Add additional context data to the template."""
|
|
return render(request, "portfolio_members.html")
|
|
|
|
|
|
class NewMemberView(PortfolioMembersPermissionView, FormMixin):
|
|
|
|
template_name = "portfolio_members_add_new.html"
|
|
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):
|
|
"""Include the instance in the form kwargs."""
|
|
kwargs = super().get_form_kwargs()
|
|
kwargs["instance"] = self.get_object()
|
|
return kwargs
|
|
|
|
def get(self, request, *args, **kwargs):
|
|
"""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 post(self, request, *args, **kwargs):
|
|
"""Handle POST requests to process form submission."""
|
|
self.object = self.get_object()
|
|
form = self.get_form()
|
|
|
|
if form.is_valid():
|
|
return self.form_valid(form)
|
|
else:
|
|
return self.form_invalid(form)
|
|
|
|
def is_ajax(self):
|
|
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())
|