portfolio invitations working in admin view as well as user view

This commit is contained in:
David Kennedy 2024-12-16 22:53:49 -05:00
parent ae3ce1f9cd
commit 7bbee19bfe
No known key found for this signature in database
GPG key ID: 6528A5386E66B96B
6 changed files with 44 additions and 116 deletions

View file

@ -14,7 +14,6 @@ from django.db.models import (
from django.db.models.functions import Concat, Coalesce
from django.http import HttpResponseRedirect
from registrar.models.federal_agency import FederalAgency
from registrar.models.portfolio_invitation import PortfolioInvitation
from registrar.utility.admin_helpers import (
AutocompleteSelectWithPlaceholder,
get_action_needed_reason_default_email,
@ -42,7 +41,7 @@ from waffle.admin import FlagAdmin
from waffle.models import Sample, Switch
from registrar.models import Contact, Domain, DomainRequest, DraftDomain, User, Website, SeniorOfficial
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.views.utility.mixins import OrderableFieldsMixin
from django.contrib.admin.views.main import ORDER_VAR
@ -1495,11 +1494,7 @@ class PortfolioInvitationAdmin(ListHeaderAdmin):
"""Handle exceptions raised during the process."""
if isinstance(exception, EmailSendingError):
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.")
elif isinstance(exception, AlreadyPortfolioMemberError):
messages.warning(request, str(exception))
elif isinstance(exception, AlreadyPortfolioInvitedError):
messages.warning(request, str(exception))
messages.error(request, "Could not send email invitation. Portfolio invitation not saved.")
elif isinstance(exception, MissingEmailError):
messages.error(request, str(exception))
logger.error(
@ -1508,7 +1503,7 @@ class PortfolioInvitationAdmin(ListHeaderAdmin):
)
else:
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):
"""
@ -1522,7 +1517,6 @@ class PortfolioInvitationAdmin(ListHeaderAdmin):
# Re-render the change form if there are errors or warnings
# Prepare context for rendering the change form
opts = self.model._meta
app_label = opts.app_label
# Get the model form
ModelForm = self.get_form(request, obj=obj)
@ -1532,37 +1526,38 @@ class PortfolioInvitationAdmin(ListHeaderAdmin):
admin_form = AdminForm(
form,
list(self.get_fieldsets(request, obj)),
self.prepopulated_fields,
self.get_prepopulated_fields(request, obj),
self.get_readonly_fields(request, obj),
model_admin=self,
)
media = self.media + form.media
opts = obj._meta
change_form_context = {
**self.admin_site.each_context(request), # Add admin context
"title": f"Change {opts.verbose_name}",
"title": f"Add {opts.verbose_name}",
"opts": opts,
"original": obj,
"save_as": self.save_as,
"has_change_permission": self.has_change_permission(request, obj),
"add": False, # Indicate this is not an "Add" form
"change": True, # Indicate this is a "Change" form
"add": True, # Indicate this is an "Add" form
"change": False, # Indicate this is not a "Change" form
"is_popup": False,
"inline_admin_formsets": [],
"save_on_top": self.save_on_top,
"show_delete": self.has_delete_permission(request, obj),
"obj": obj,
"adminform": admin_form, # Pass the AdminForm instance
"media": media,
"errors": None, # You can use this to pass custom form errors
}
return self.render_change_form(
request,
context=change_form_context,
add=False,
change=True,
add=True,
change=False,
obj=obj,
)
return super().response_add(request, obj, post_url_continue)

View file

@ -226,28 +226,7 @@ class PortfolioNewMemberForm(forms.ModelForm):
model = PortfolioInvitation
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):
logger.info(self.cleaned_data)
logger.info(self.initial)
# Lowercase the value of the 'email' field
email_value = self.cleaned_data.get("email")
if email_value:

View file

@ -111,12 +111,7 @@ class PortfolioInvitation(TimeStampedModel):
user_portfolio_permission.additional_permissions = self.additional_permissions
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):
"""Extends clean method to perform additional validation, which can raise errors in django admin."""
print(f'portfolio invitation model clean')
super().clean()
validate_portfolio_invitation(self)

View file

@ -162,21 +162,11 @@ def validate_portfolio_invitation(portfolio_invitation):
has_portfolio = bool(portfolio_invitation.portfolio_id)
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 == #
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.")
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.")
# == Validate role permissions. Compares existing permissions to forbidden ones. == #

View file

@ -93,8 +93,6 @@ def send_portfolio_invitation_email(email: str, requestor, portfolio):
Raises:
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.
"""
@ -108,16 +106,6 @@ def send_portfolio_invitation_email(email: str, requestor, portfolio):
else:
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(
"emails/portfolio_invitation.txt",
"emails/portfolio_invitation_subject.txt",

View file

@ -14,7 +14,7 @@ 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.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.permission_views import (
PortfolioDomainRequestsPermissionView,
@ -480,6 +480,22 @@ class PortfolioAddMemberView(PortfolioMembersPermissionView, FormMixin):
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 = 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):
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
def form_valid(self, form):
super().form_valid(form)
if self.is_ajax():
return JsonResponse({"is_valid": True}) # Return a JSON response
else:
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):
"""Redirect to members table."""
return reverse("members")
def submit_new_member(self, form):
"""Add the specified user as a member for this portfolio."""
# Retrieve the portfolio from the session
portfolio = self.request.session.get("portfolio")
if not 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_email = form.cleaned_data["email"]
requestor = self.request.user
portfolio = form.cleaned_data["portfolio"]
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())
def get_success_url(self):
@ -560,10 +545,6 @@ class PortfolioAddMemberView(PortfolioMembersPermissionView, FormMixin):
if isinstance(exception, EmailSendingError):
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.")
elif isinstance(exception, AlreadyPortfolioMemberError):
messages.warning(self.request, str(exception))
elif isinstance(exception, AlreadyPortfolioInvitedError):
messages.warning(self.request, str(exception))
elif isinstance(exception, MissingEmailError):
messages.error(self.request, str(exception))
logger.error(