This commit is contained in:
zandercymatics 2024-03-13 12:19:03 -06:00
parent cefd2fe86a
commit 4be6979af5
No known key found for this signature in database
GPG key ID: FF4636ABEC9682B7
3 changed files with 36 additions and 9 deletions

View file

@ -16,7 +16,7 @@ from dateutil.relativedelta import relativedelta # type: ignore
from epplibwrapper.errors import ErrorCode, RegistryError from epplibwrapper.errors import ErrorCode, RegistryError
from registrar.models import Contact, Domain, DomainRequest, DraftDomain, User, Website from registrar.models import Contact, Domain, DomainRequest, DraftDomain, User, Website
from registrar.utility import csv_export from registrar.utility import csv_export
from registrar.utility.errors import FSMApplicationError, FSMErrorCodes from registrar.utility.errors import FSMDomainRequestError, FSMErrorCodes
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
from registrar.widgets import NoAutocompleteFilteredSelectMultiple from registrar.widgets import NoAutocompleteFilteredSelectMultiple
@ -152,9 +152,33 @@ class DomainRequestAdminForm(forms.ModelForm):
# That field must obey certain conditions when an domain request is approved. # That field must obey certain conditions when an domain request is approved.
# Will call "add_error" if any issues are found. # Will call "add_error" if any issues are found.
self._check_for_valid_investigator(investigator) self._check_for_valid_investigator(investigator)
# If the status is rejected, a rejection reason must exist
if status == DomainRequest.DomainRequestStatus.REJECTED:
self._check_for_valid_rejection_reason(rejection_reason)
return cleaned_data return cleaned_data
def _check_for_valid_rejection_reason(self, rejection_reason) -> bool:
"""
Checks if the rejection_reason field is not none.
Adds form errors on failure.
"""
is_valid = False
# Check if a rejection reason exists. Rejection is not possible without one.
error_message = None
if rejection_reason is None:
# Lets grab the error message from a common location
error_message = FSMDomainRequestError.get_error_message(FSMErrorCodes.NO_REJECTION_REASON)
else:
is_valid = True
if error_message is not None:
self.add_error("rejection_reason", error_message)
return is_valid
def _check_for_valid_investigator(self, investigator) -> bool: def _check_for_valid_investigator(self, investigator) -> bool:
""" """
Checks if the investigator field is not none, and is staff. Checks if the investigator field is not none, and is staff.
@ -167,9 +191,9 @@ class DomainRequestAdminForm(forms.ModelForm):
error_message = None error_message = None
if investigator is None: if investigator is None:
# Lets grab the error message from a common location # Lets grab the error message from a common location
error_message = FSMApplicationError.get_error_message(FSMErrorCodes.NO_INVESTIGATOR) error_message = FSMDomainRequestError.get_error_message(FSMErrorCodes.NO_INVESTIGATOR)
elif not investigator.is_staff: elif not investigator.is_staff:
error_message = FSMApplicationError.get_error_message(FSMErrorCodes.INVESTIGATOR_NOT_STAFF) error_message = FSMDomainRequestError.get_error_message(FSMErrorCodes.INVESTIGATOR_NOT_STAFF)
else: else:
is_valid = True is_valid = True
@ -1216,7 +1240,7 @@ class DomainRequestAdmin(ListHeaderAdmin):
# This condition should never be triggered. # This condition should never be triggered.
# The opposite of this condition is acceptable (rejected -> other status and rejection_reason) # The opposite of this condition is acceptable (rejected -> other status and rejection_reason)
# because we clean up the rejection reason in the transition in the model. # because we clean up the rejection reason in the transition in the model.
error_message = "A rejection reason is required." error_message = FSMDomainRequestError.get_error_message(FSMErrorCodes.NO_REJECTION_REASON)
else: else:
# This is an fsm in model which will throw an error if the # This is an fsm in model which will throw an error if the
# transition condition is violated, so we roll back the # transition condition is violated, so we roll back the
@ -1225,11 +1249,11 @@ class DomainRequestAdmin(ListHeaderAdmin):
obj.status = original_obj.status obj.status = original_obj.status
# Try to perform the status change. # Try to perform the status change.
# Catch FSMApplicationError's and return the message, # Catch FSMDomainRequestError's and return the message,
# as these are typically user errors. # as these are typically user errors.
try: try:
selected_method() selected_method()
except FSMApplicationError as err: except FSMDomainRequestError as err:
logger.warning(f"An error encountered when trying to change status: {err}") logger.warning(f"An error encountered when trying to change status: {err}")
error_message = err.message error_message = err.message

View file

@ -9,7 +9,7 @@ from django.db import models
from django_fsm import FSMField, transition # type: ignore from django_fsm import FSMField, transition # type: ignore
from django.utils import timezone from django.utils import timezone
from registrar.models.domain import Domain from registrar.models.domain import Domain
from registrar.utility.errors import FSMApplicationError, FSMErrorCodes from registrar.utility.errors import FSMDomainRequestError, FSMErrorCodes
from .utility.time_stamped_model import TimeStampedModel from .utility.time_stamped_model import TimeStampedModel
from ..utility.email import send_templated_email, EmailSendingError from ..utility.email import send_templated_email, EmailSendingError
@ -791,7 +791,7 @@ class DomainRequest(TimeStampedModel):
# == Check that the domain_request is valid == # # == Check that the domain_request is valid == #
if Domain.objects.filter(name=self.requested_domain.name).exists(): if Domain.objects.filter(name=self.requested_domain.name).exists():
raise FSMApplicationError(code=FSMErrorCodes.APPROVE_DOMAIN_IN_USE) raise FSMDomainRequestError(code=FSMErrorCodes.APPROVE_DOMAIN_IN_USE)
# == Create the domain and related components == # # == Create the domain and related components == #
created_domain = Domain.objects.create(name=self.requested_domain.name) created_domain = Domain.objects.create(name=self.requested_domain.name)

View file

@ -78,15 +78,17 @@ class FSMErrorCodes(IntEnum):
- 2 NO_INVESTIGATOR No investigator is assigned - 2 NO_INVESTIGATOR No investigator is assigned
- 3 INVESTIGATOR_NOT_STAFF Investigator is a non-staff user - 3 INVESTIGATOR_NOT_STAFF Investigator is a non-staff user
- 4 INVESTIGATOR_NOT_SUBMITTER The form submitter is not the investigator - 4 INVESTIGATOR_NOT_SUBMITTER The form submitter is not the investigator
- 5 NO_REJECTION_REASON No rejection reason is specified
""" """
APPROVE_DOMAIN_IN_USE = 1 APPROVE_DOMAIN_IN_USE = 1
NO_INVESTIGATOR = 2 NO_INVESTIGATOR = 2
INVESTIGATOR_NOT_STAFF = 3 INVESTIGATOR_NOT_STAFF = 3
INVESTIGATOR_NOT_SUBMITTER = 4 INVESTIGATOR_NOT_SUBMITTER = 4
NO_REJECTION_REASON = 5
class FSMApplicationError(Exception): class FSMDomainRequestError(Exception):
""" """
Used to raise exceptions when doing FSM Transitions. Used to raise exceptions when doing FSM Transitions.
Uses `FSMErrorCodes` as an enum. Uses `FSMErrorCodes` as an enum.
@ -97,6 +99,7 @@ class FSMApplicationError(Exception):
FSMErrorCodes.NO_INVESTIGATOR: ("Investigator is required for this status."), FSMErrorCodes.NO_INVESTIGATOR: ("Investigator is required for this status."),
FSMErrorCodes.INVESTIGATOR_NOT_STAFF: ("Investigator is not a staff user."), FSMErrorCodes.INVESTIGATOR_NOT_STAFF: ("Investigator is not a staff user."),
FSMErrorCodes.INVESTIGATOR_NOT_SUBMITTER: ("Only the assigned investigator can make this change."), FSMErrorCodes.INVESTIGATOR_NOT_SUBMITTER: ("Only the assigned investigator can make this change."),
FSMErrorCodes.NO_REJECTION_REASON: ("A rejection reason is required."),
} }
def __init__(self, *args, code=None, **kwargs): def __init__(self, *args, code=None, **kwargs):