mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-28 13:36:30 +02:00
portfolio invitations working in admin view as well as user view
This commit is contained in:
parent
ae3ce1f9cd
commit
7bbee19bfe
6 changed files with 44 additions and 116 deletions
|
@ -14,7 +14,6 @@ from django.db.models import (
|
||||||
from django.db.models.functions import Concat, Coalesce
|
from django.db.models.functions import Concat, Coalesce
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from registrar.models.federal_agency import FederalAgency
|
from registrar.models.federal_agency import FederalAgency
|
||||||
from registrar.models.portfolio_invitation import PortfolioInvitation
|
|
||||||
from registrar.utility.admin_helpers import (
|
from registrar.utility.admin_helpers import (
|
||||||
AutocompleteSelectWithPlaceholder,
|
AutocompleteSelectWithPlaceholder,
|
||||||
get_action_needed_reason_default_email,
|
get_action_needed_reason_default_email,
|
||||||
|
@ -42,7 +41,7 @@ from waffle.admin import FlagAdmin
|
||||||
from waffle.models import Sample, Switch
|
from waffle.models import Sample, Switch
|
||||||
from registrar.models import Contact, Domain, DomainRequest, DraftDomain, User, Website, SeniorOfficial
|
from registrar.models import Contact, Domain, DomainRequest, DraftDomain, User, Website, SeniorOfficial
|
||||||
from registrar.utility.constants import BranchChoices
|
from registrar.utility.constants import BranchChoices
|
||||||
from registrar.utility.errors import AlreadyPortfolioInvitedError, AlreadyPortfolioMemberError, FSMDomainRequestError, FSMErrorCodes, MissingEmailError
|
from registrar.utility.errors import FSMDomainRequestError, FSMErrorCodes, MissingEmailError
|
||||||
from registrar.utility.waffle import flag_is_active_for_user
|
from registrar.utility.waffle import flag_is_active_for_user
|
||||||
from registrar.views.utility.mixins import OrderableFieldsMixin
|
from registrar.views.utility.mixins import OrderableFieldsMixin
|
||||||
from django.contrib.admin.views.main import ORDER_VAR
|
from django.contrib.admin.views.main import ORDER_VAR
|
||||||
|
@ -1495,11 +1494,7 @@ class PortfolioInvitationAdmin(ListHeaderAdmin):
|
||||||
"""Handle exceptions raised during the process."""
|
"""Handle exceptions raised during the process."""
|
||||||
if isinstance(exception, EmailSendingError):
|
if isinstance(exception, EmailSendingError):
|
||||||
logger.warning("Could not sent email invitation to %s for portfolio %s (EmailSendingError)", obj.email, obj.portfolio, exc_info=True)
|
logger.warning("Could not sent email invitation to %s for portfolio %s (EmailSendingError)", obj.email, obj.portfolio, exc_info=True)
|
||||||
messages.warning(request, "Could not send email invitation.")
|
messages.error(request, "Could not send email invitation. Portfolio invitation not saved.")
|
||||||
elif isinstance(exception, AlreadyPortfolioMemberError):
|
|
||||||
messages.warning(request, str(exception))
|
|
||||||
elif isinstance(exception, AlreadyPortfolioInvitedError):
|
|
||||||
messages.warning(request, str(exception))
|
|
||||||
elif isinstance(exception, MissingEmailError):
|
elif isinstance(exception, MissingEmailError):
|
||||||
messages.error(request, str(exception))
|
messages.error(request, str(exception))
|
||||||
logger.error(
|
logger.error(
|
||||||
|
@ -1508,7 +1503,7 @@ class PortfolioInvitationAdmin(ListHeaderAdmin):
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.warning("Could not send email invitation (Other Exception)", obj.portfolio, exc_info=True)
|
logger.warning("Could not send email invitation (Other Exception)", obj.portfolio, exc_info=True)
|
||||||
messages.warning(request, "Could not send email invitation.")
|
messages.error(request, "Could not send email invitation. Portfolio invitation not saved.")
|
||||||
|
|
||||||
def response_add(self, request, obj, post_url_continue=None):
|
def response_add(self, request, obj, post_url_continue=None):
|
||||||
"""
|
"""
|
||||||
|
@ -1522,7 +1517,6 @@ class PortfolioInvitationAdmin(ListHeaderAdmin):
|
||||||
# Re-render the change form if there are errors or warnings
|
# Re-render the change form if there are errors or warnings
|
||||||
# Prepare context for rendering the change form
|
# Prepare context for rendering the change form
|
||||||
opts = self.model._meta
|
opts = self.model._meta
|
||||||
app_label = opts.app_label
|
|
||||||
|
|
||||||
# Get the model form
|
# Get the model form
|
||||||
ModelForm = self.get_form(request, obj=obj)
|
ModelForm = self.get_form(request, obj=obj)
|
||||||
|
@ -1532,37 +1526,38 @@ class PortfolioInvitationAdmin(ListHeaderAdmin):
|
||||||
admin_form = AdminForm(
|
admin_form = AdminForm(
|
||||||
form,
|
form,
|
||||||
list(self.get_fieldsets(request, obj)),
|
list(self.get_fieldsets(request, obj)),
|
||||||
self.prepopulated_fields,
|
self.get_prepopulated_fields(request, obj),
|
||||||
self.get_readonly_fields(request, obj),
|
self.get_readonly_fields(request, obj),
|
||||||
model_admin=self,
|
model_admin=self,
|
||||||
)
|
)
|
||||||
|
media = self.media + form.media
|
||||||
|
|
||||||
opts = obj._meta
|
opts = obj._meta
|
||||||
change_form_context = {
|
change_form_context = {
|
||||||
**self.admin_site.each_context(request), # Add admin context
|
**self.admin_site.each_context(request), # Add admin context
|
||||||
"title": f"Change {opts.verbose_name}",
|
"title": f"Add {opts.verbose_name}",
|
||||||
"opts": opts,
|
"opts": opts,
|
||||||
"original": obj,
|
"original": obj,
|
||||||
"save_as": self.save_as,
|
"save_as": self.save_as,
|
||||||
"has_change_permission": self.has_change_permission(request, obj),
|
"has_change_permission": self.has_change_permission(request, obj),
|
||||||
"add": False, # Indicate this is not an "Add" form
|
"add": True, # Indicate this is an "Add" form
|
||||||
"change": True, # Indicate this is a "Change" form
|
"change": False, # Indicate this is not a "Change" form
|
||||||
"is_popup": False,
|
"is_popup": False,
|
||||||
"inline_admin_formsets": [],
|
"inline_admin_formsets": [],
|
||||||
"save_on_top": self.save_on_top,
|
"save_on_top": self.save_on_top,
|
||||||
"show_delete": self.has_delete_permission(request, obj),
|
"show_delete": self.has_delete_permission(request, obj),
|
||||||
"obj": obj,
|
"obj": obj,
|
||||||
"adminform": admin_form, # Pass the AdminForm instance
|
"adminform": admin_form, # Pass the AdminForm instance
|
||||||
|
"media": media,
|
||||||
"errors": None, # You can use this to pass custom form errors
|
"errors": None, # You can use this to pass custom form errors
|
||||||
}
|
}
|
||||||
return self.render_change_form(
|
return self.render_change_form(
|
||||||
request,
|
request,
|
||||||
context=change_form_context,
|
context=change_form_context,
|
||||||
add=False,
|
add=True,
|
||||||
change=True,
|
change=False,
|
||||||
obj=obj,
|
obj=obj,
|
||||||
)
|
)
|
||||||
|
|
||||||
return super().response_add(request, obj, post_url_continue)
|
return super().response_add(request, obj, post_url_continue)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -226,28 +226,7 @@ class PortfolioNewMemberForm(forms.ModelForm):
|
||||||
model = PortfolioInvitation
|
model = PortfolioInvitation
|
||||||
fields = ["portfolio", "email", "roles", "additional_permissions"]
|
fields = ["portfolio", "email", "roles", "additional_permissions"]
|
||||||
|
|
||||||
def is_valid(self):
|
|
||||||
logger.info("is valid()")
|
|
||||||
return super().is_valid()
|
|
||||||
|
|
||||||
def full_clean(self):
|
|
||||||
logger.info("full_clean()")
|
|
||||||
super().full_clean()
|
|
||||||
|
|
||||||
def _clean_fields(self):
|
|
||||||
logger.info("clean fields")
|
|
||||||
logger.info(self.fields)
|
|
||||||
super()._clean_fields()
|
|
||||||
|
|
||||||
def _post_clean(self):
|
|
||||||
logger.info("post clean")
|
|
||||||
logger.info(self.cleaned_data)
|
|
||||||
super()._post_clean()
|
|
||||||
logger.info(self.instance)
|
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
logger.info(self.cleaned_data)
|
|
||||||
logger.info(self.initial)
|
|
||||||
# Lowercase the value of the 'email' field
|
# Lowercase the value of the 'email' field
|
||||||
email_value = self.cleaned_data.get("email")
|
email_value = self.cleaned_data.get("email")
|
||||||
if email_value:
|
if email_value:
|
||||||
|
|
|
@ -111,12 +111,7 @@ class PortfolioInvitation(TimeStampedModel):
|
||||||
user_portfolio_permission.additional_permissions = self.additional_permissions
|
user_portfolio_permission.additional_permissions = self.additional_permissions
|
||||||
user_portfolio_permission.save()
|
user_portfolio_permission.save()
|
||||||
|
|
||||||
def full_clean(self, exclude=True, validate_unique=False):
|
|
||||||
logger.info("full clean")
|
|
||||||
super().full_clean(exclude=exclude, validate_unique=validate_unique)
|
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
"""Extends clean method to perform additional validation, which can raise errors in django admin."""
|
"""Extends clean method to perform additional validation, which can raise errors in django admin."""
|
||||||
print(f'portfolio invitation model clean')
|
|
||||||
super().clean()
|
super().clean()
|
||||||
validate_portfolio_invitation(self)
|
validate_portfolio_invitation(self)
|
||||||
|
|
|
@ -162,21 +162,11 @@ def validate_portfolio_invitation(portfolio_invitation):
|
||||||
has_portfolio = bool(portfolio_invitation.portfolio_id)
|
has_portfolio = bool(portfolio_invitation.portfolio_id)
|
||||||
portfolio_permissions = set(portfolio_invitation.get_portfolio_permissions())
|
portfolio_permissions = set(portfolio_invitation.get_portfolio_permissions())
|
||||||
|
|
||||||
print(f"has_portfolio {has_portfolio}")
|
|
||||||
|
|
||||||
print(f"portfolio_permissions {portfolio_permissions}")
|
|
||||||
|
|
||||||
print(f"roles {portfolio_invitation.roles}")
|
|
||||||
|
|
||||||
print(f"additional permissions {portfolio_invitation.additional_permissions}")
|
|
||||||
|
|
||||||
# == Validate required fields == #
|
# == Validate required fields == #
|
||||||
if not has_portfolio and portfolio_permissions:
|
if not has_portfolio and portfolio_permissions:
|
||||||
print(f"not has_portfolio and portfolio_permissions {portfolio_permissions}")
|
|
||||||
raise ValidationError("When portfolio roles or additional permissions are assigned, portfolio is required.")
|
raise ValidationError("When portfolio roles or additional permissions are assigned, portfolio is required.")
|
||||||
|
|
||||||
if has_portfolio and not portfolio_permissions:
|
if has_portfolio and not portfolio_permissions:
|
||||||
print(f"has_portfolio and not portfolio_permissions {portfolio_permissions}")
|
|
||||||
raise ValidationError("When portfolio is assigned, portfolio roles or additional permissions are required.")
|
raise ValidationError("When portfolio is assigned, portfolio roles or additional permissions are required.")
|
||||||
|
|
||||||
# == Validate role permissions. Compares existing permissions to forbidden ones. == #
|
# == Validate role permissions. Compares existing permissions to forbidden ones. == #
|
||||||
|
|
|
@ -93,8 +93,6 @@ def send_portfolio_invitation_email(email: str, requestor, portfolio):
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
MissingEmailError: If the requestor has no email associated with their account.
|
MissingEmailError: If the requestor has no email associated with their account.
|
||||||
AlreadyPortfolioMemberError: If the email corresponds to an existing portfolio member.
|
|
||||||
AlreadyPortfolioInvitedError: If an invitation has already been sent.
|
|
||||||
EmailSendingError: If there is an error while sending the email.
|
EmailSendingError: If there is an error while sending the email.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -108,16 +106,6 @@ def send_portfolio_invitation_email(email: str, requestor, portfolio):
|
||||||
else:
|
else:
|
||||||
requestor_email = requestor.email
|
requestor_email = requestor.email
|
||||||
|
|
||||||
# Check to see if an invite has already been sent
|
|
||||||
try:
|
|
||||||
invite = PortfolioInvitation.objects.get(email=email, portfolio=portfolio)
|
|
||||||
if invite.status == PortfolioInvitation.PortfolioInvitationStatus.RETRIEVED:
|
|
||||||
raise AlreadyPortfolioMemberError(email)
|
|
||||||
else:
|
|
||||||
raise AlreadyPortfolioInvitedError(email, portfolio)
|
|
||||||
except PortfolioInvitation.DoesNotExist:
|
|
||||||
pass
|
|
||||||
|
|
||||||
send_templated_email(
|
send_templated_email(
|
||||||
"emails/portfolio_invitation.txt",
|
"emails/portfolio_invitation.txt",
|
||||||
"emails/portfolio_invitation_subject.txt",
|
"emails/portfolio_invitation_subject.txt",
|
||||||
|
|
|
@ -14,7 +14,7 @@ from registrar.models.user_portfolio_permission import UserPortfolioPermission
|
||||||
from registrar.models.utility.portfolio_helper import UserPortfolioPermissionChoices, UserPortfolioRoleChoices
|
from registrar.models.utility.portfolio_helper import UserPortfolioPermissionChoices, UserPortfolioRoleChoices
|
||||||
from registrar.utility.email import EmailSendingError
|
from registrar.utility.email import EmailSendingError
|
||||||
from registrar.utility.email_invitations import send_portfolio_invitation_email
|
from registrar.utility.email_invitations import send_portfolio_invitation_email
|
||||||
from registrar.utility.errors import AlreadyPortfolioInvitedError, AlreadyPortfolioMemberError, MissingEmailError
|
from registrar.utility.errors import MissingEmailError
|
||||||
from registrar.views.utility.mixins import PortfolioMemberPermission
|
from registrar.views.utility.mixins import PortfolioMemberPermission
|
||||||
from registrar.views.utility.permission_views import (
|
from registrar.views.utility.permission_views import (
|
||||||
PortfolioDomainRequestsPermissionView,
|
PortfolioDomainRequestsPermissionView,
|
||||||
|
@ -480,6 +480,22 @@ class PortfolioAddMemberView(PortfolioMembersPermissionView, FormMixin):
|
||||||
form = self.get_form()
|
form = self.get_form()
|
||||||
return self.render_to_response(self.get_context_data(form=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 = None # For a new invitation, there's no existing model instance
|
||||||
|
|
||||||
|
# portfolio not submitted with form, so override the value
|
||||||
|
data = request.POST.copy()
|
||||||
|
if not data.get("portfolio"):
|
||||||
|
data["portfolio"] = self.request.session.get("portfolio").id
|
||||||
|
# Pass the modified data to the form
|
||||||
|
form = portfolioForms.PortfolioNewMemberForm(data)
|
||||||
|
|
||||||
|
if form.is_valid():
|
||||||
|
return self.form_valid(form)
|
||||||
|
else:
|
||||||
|
return self.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"
|
||||||
|
|
||||||
|
@ -490,65 +506,34 @@ class PortfolioAddMemberView(PortfolioMembersPermissionView, FormMixin):
|
||||||
return super().form_invalid(form) # Handle non-AJAX requests normally
|
return super().form_invalid(form) # Handle non-AJAX requests normally
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
|
super().form_valid(form)
|
||||||
if self.is_ajax():
|
if self.is_ajax():
|
||||||
return JsonResponse({"is_valid": True}) # Return a JSON response
|
return JsonResponse({"is_valid": True}) # Return a JSON response
|
||||||
else:
|
else:
|
||||||
return self.submit_new_member(form)
|
return self.submit_new_member(form)
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
"""Handle POST requests to process form submission."""
|
|
||||||
self.object = None # For a new invitation, there's no existing model instance
|
|
||||||
|
|
||||||
data = request.POST.copy()
|
|
||||||
|
|
||||||
# Override the 'portfolio' field value
|
|
||||||
if not data.get("portfolio"):
|
|
||||||
data["portfolio"] = self.request.session.get("portfolio").id
|
|
||||||
|
|
||||||
# Pass the modified data to the form
|
|
||||||
form = portfolioForms.PortfolioNewMemberForm(data)
|
|
||||||
#form = self.get_form()
|
|
||||||
#logger.info(form.fields["portfolio"])
|
|
||||||
|
|
||||||
print('before is_valid')
|
|
||||||
if form.is_valid():
|
|
||||||
print('form is_valid')
|
|
||||||
return self.form_valid(form)
|
|
||||||
else:
|
|
||||||
print('form NOT is_valid')
|
|
||||||
return self.form_invalid(form)
|
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
"""Redirect to members table."""
|
"""Redirect to members table."""
|
||||||
return reverse("members")
|
return reverse("members")
|
||||||
|
|
||||||
def submit_new_member(self, form):
|
def submit_new_member(self, form):
|
||||||
"""Add the specified user as a member for this portfolio."""
|
"""Add the specified user as a member for this portfolio."""
|
||||||
# Retrieve the portfolio from the session
|
requested_email = form.cleaned_data["email"]
|
||||||
portfolio = self.request.session.get("portfolio")
|
requestor = self.request.user
|
||||||
if not portfolio:
|
portfolio = form.cleaned_data["portfolio"]
|
||||||
messages.error(self.request, "No portfolio found in session.")
|
|
||||||
return self.form_invalid(form)
|
|
||||||
|
|
||||||
# Save the invitation instance
|
|
||||||
invitation = form.save(commit=False)
|
|
||||||
invitation.portfolio = portfolio
|
|
||||||
|
|
||||||
# Send invitation email and show a success message
|
|
||||||
send_portfolio_invitation_email(
|
|
||||||
email=invitation.email,
|
|
||||||
requestor=self.request.user,
|
|
||||||
portfolio=portfolio,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Use processed data from the form
|
|
||||||
invitation.roles = form.cleaned_data["roles"]
|
|
||||||
invitation.additional_permissions = form.cleaned_data["additional_permissions"]
|
|
||||||
invitation.save()
|
|
||||||
|
|
||||||
messages.success(self.request, f"{invitation.email} has been invited.")
|
|
||||||
|
|
||||||
|
requested_user = User.objects.filter(email=requested_email).first()
|
||||||
|
permission_exists = UserPortfolioPermission.objects.filter(user=requested_user, portfolio=portfolio).exists()
|
||||||
|
try:
|
||||||
|
if not requested_user or not permission_exists:
|
||||||
|
send_portfolio_invitation_email(email=requested_email, requestor=requestor, portfolio=portfolio)
|
||||||
|
form.save()
|
||||||
|
messages.success(self.request, f"{requested_email} has been invited.")
|
||||||
|
else:
|
||||||
|
if permission_exists:
|
||||||
|
messages.warning(self.request, "User is already a member of this portfolio.")
|
||||||
|
except Exception as e:
|
||||||
|
self._handle_exceptions(e, portfolio, requested_email)
|
||||||
return redirect(self.get_success_url())
|
return redirect(self.get_success_url())
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
|
@ -560,10 +545,6 @@ class PortfolioAddMemberView(PortfolioMembersPermissionView, FormMixin):
|
||||||
if isinstance(exception, EmailSendingError):
|
if isinstance(exception, EmailSendingError):
|
||||||
logger.warning("Could not sent email invitation to %s for portfolio %s (EmailSendingError)", email, portfolio, exc_info=True)
|
logger.warning("Could not sent email invitation to %s for portfolio %s (EmailSendingError)", email, portfolio, exc_info=True)
|
||||||
messages.warning(self.request, "Could not send email invitation.")
|
messages.warning(self.request, "Could not send email invitation.")
|
||||||
elif isinstance(exception, AlreadyPortfolioMemberError):
|
|
||||||
messages.warning(self.request, str(exception))
|
|
||||||
elif isinstance(exception, AlreadyPortfolioInvitedError):
|
|
||||||
messages.warning(self.request, str(exception))
|
|
||||||
elif isinstance(exception, MissingEmailError):
|
elif isinstance(exception, MissingEmailError):
|
||||||
messages.error(self.request, str(exception))
|
messages.error(self.request, str(exception))
|
||||||
logger.error(
|
logger.error(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue