mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-05-14 16:47:02 +02:00
Merge pull request #1899 from cisagov/za/1864-error-message-rejection-placement
(on getgov-za) Ticket #1864: Move error message for rejection reason required
This commit is contained in:
commit
35ffdc7bd0
6 changed files with 60 additions and 10 deletions
|
@ -16,7 +16,7 @@ from django.urls import reverse
|
||||||
from dateutil.relativedelta import relativedelta # type: ignore
|
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.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
|
||||||
|
@ -133,6 +133,7 @@ class DomainRequestAdminForm(forms.ModelForm):
|
||||||
cleaned_data = super().clean()
|
cleaned_data = super().clean()
|
||||||
status = cleaned_data.get("status")
|
status = cleaned_data.get("status")
|
||||||
investigator = cleaned_data.get("investigator")
|
investigator = cleaned_data.get("investigator")
|
||||||
|
rejection_reason = cleaned_data.get("rejection_reason")
|
||||||
|
|
||||||
# Get the old status
|
# Get the old status
|
||||||
initial_status = self.initial.get("status", None)
|
initial_status = self.initial.get("status", None)
|
||||||
|
@ -153,8 +154,32 @@ class DomainRequestAdminForm(forms.ModelForm):
|
||||||
# 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 or rejection_reason == "":
|
||||||
|
# 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 +192,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
|
||||||
|
|
||||||
|
@ -994,7 +1019,7 @@ class DomainRequestAdmin(ListHeaderAdmin):
|
||||||
if self.value() == "0":
|
if self.value() == "0":
|
||||||
return queryset.filter(Q(is_election_board=False) | Q(is_election_board=None))
|
return queryset.filter(Q(is_election_board=False) | Q(is_election_board=None))
|
||||||
|
|
||||||
change_form_template = "django/admin/domain_application_change_form.html"
|
change_form_template = "django/admin/domain_request_change_form.html"
|
||||||
|
|
||||||
# Columns
|
# Columns
|
||||||
list_display = [
|
list_display = [
|
||||||
|
@ -1209,7 +1234,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
|
||||||
|
@ -1218,11 +1243,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
|
||||||
|
|
||||||
|
|
|
@ -346,3 +346,7 @@ input.admin-confirm-button {
|
||||||
.usa-summary-box__dhs-color {
|
.usa-summary-box__dhs-color {
|
||||||
color: $dhs-blue-70;
|
color: $dhs-blue-70;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.errors span.select2-selection {
|
||||||
|
border: 1px solid var(--error-fg) !important;
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -555,6 +555,24 @@ class TestDomainRequestAdminForm(TestCase):
|
||||||
expected_choices = [("started", "Started"), ("submitted", "Submitted")]
|
expected_choices = [("started", "Started"), ("submitted", "Submitted")]
|
||||||
self.assertEqual(form.fields["status"].widget.choices, expected_choices)
|
self.assertEqual(form.fields["status"].widget.choices, expected_choices)
|
||||||
|
|
||||||
|
def test_form_no_rejection_reason(self):
|
||||||
|
with less_console_noise():
|
||||||
|
# Create a form instance with the test domain request
|
||||||
|
form = DomainRequestAdminForm(instance=self.domain_request)
|
||||||
|
|
||||||
|
form = DomainRequestAdminForm(
|
||||||
|
instance=self.domain_request,
|
||||||
|
data={
|
||||||
|
"status": DomainRequest.DomainRequestStatus.REJECTED,
|
||||||
|
"rejection_reason": None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertFalse(form.is_valid())
|
||||||
|
self.assertIn("rejection_reason", form.errors)
|
||||||
|
|
||||||
|
rejection_reason = form.errors.get("rejection_reason")
|
||||||
|
self.assertEqual(rejection_reason, ["A rejection reason is required."])
|
||||||
|
|
||||||
def test_form_choices_when_no_instance(self):
|
def test_form_choices_when_no_instance(self):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
# Create a form instance without an instance
|
# Create a form instance without an instance
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue