correct form validation and dynamic elements

This commit is contained in:
Matt-Spence 2025-02-25 14:34:01 -05:00
parent 190bca2cac
commit 065febd496
No known key found for this signature in database
4 changed files with 32 additions and 83 deletions

View file

@ -607,7 +607,8 @@ class DotGovDomainForm(RegistrarForm):
},
)
class ExecutiveNamingRequirementsYesNoForm(BaseYesNoForm):
class ExecutiveNamingRequirementsYesNoForm(BaseYesNoForm, BaseDeletableRegistrarForm):
"""
Form for verifying if the domain request meets the Federal Executive Branch domain naming requirements.
If the "no" option is selected, details must be provided via the separate details form.
@ -621,77 +622,25 @@ class ExecutiveNamingRequirementsYesNoForm(BaseYesNoForm):
Determines the initial checked state of the form based on the domain_request's attributes.
"""
return self.domain_request.feb_naming_requirements
def clean(self):
# Skip validation if this form is not applicable.
if not (self.domain_request.is_federal() and self.domain_request.federal_type == "Executive"):
# Initialize cleaned_data if it doesn't exist
if not hasattr(self, 'cleaned_data'):
self.cleaned_data = {}
# If not executive, default to None
self.cleaned_data["feb_naming_requirements"] = None
return self.cleaned_data
# Only validate the yes/no field here; details are handled by the separate details form.
cleaned = super().clean()
return cleaned
def to_database(self, obj: DomainRequest):
"""
Saves the cleaned data from this form to the DomainRequest object.
"""
if not self.is_valid():
return
obj.feb_naming_requirements = (self.cleaned_data.get("feb_naming_requirements", None) == "yes")
obj.save()
@classmethod
def from_database(cls, obj):
"""
Retrieves initial data from the DomainRequest object to prepopulate the form.
"""
initial = {}
if hasattr(obj, "feb_naming_requirements"):
initial["feb_naming_requirements"] = "yes" if obj.feb_naming_requirements else "no"
return initial
class ExecutiveNamingRequirementsDetailsForm(BaseDeletableRegistrarForm):
JOIN = "feb_naming_requirements_details"
# Text area for additional details; rendered conditionally when "no" is selected.
feb_naming_requirements_details = forms.CharField(
widget=forms.Textarea(attrs={'maxlength': '2000'}),
widget=forms.Textarea(attrs={"maxlength": "2000"}),
max_length=2000,
required=True,
error_messages={"required": ("This field is required.")},
validators=[
MaxLengthValidator(
2000,
message="Response must be less than 2000 characters.",
)
],
label="",
help_text="Maximum 2000 characters allowed.",
)
def to_database(self, obj: DomainRequest):
if not self.is_valid():
return
obj.feb_naming_requirements_details = self.cleaned_data["feb_naming_requirements_details"]
obj.save()
def is_valid(self):
"""
Validate that details are provided when required.
If the form is marked for deletion, bypass validation.
"""
if self.form_data_marked_for_deletion:
return True
is_valid = super().is_valid()
if not is_valid:
return False
# Check if the details field has content
details = self.cleaned_data.get('feb_naming_requirements_details', '').strip()
if not details:
return False
return True
class PurposeForm(RegistrarForm):
purpose = forms.CharField(
@ -710,7 +659,7 @@ class PurposeForm(RegistrarForm):
],
error_messages={"required": "Describe how youll use the .gov domain youre requesting."},
)
class OtherContactsYesNoForm(BaseYesNoForm):
"""The yes/no field for the OtherContacts form."""

View file

@ -1398,7 +1398,7 @@ class DomainRequest(TimeStampedModel):
if self.has_anything_else_text is None or self.has_cisa_representative is None:
has_details = False
return has_details
def is_feb(self) -> bool:
"""Is this domain request for a Federal Executive Branch agency?"""
if not self.generic_org_type:

View file

@ -2,7 +2,7 @@
{% load static field_helpers url_helpers %}
{% block form_instructions %}
<p>Before requesting a .gov domain, please make sure it meets <a class="usa-link" rel="noopener noreferrer" target="_blank" href="{% if is_feb %}https://get.gov/domains/executive-branch-guidance/{% else %}{% public_site_url 'domains/choosing' %}{% endif %}">our naming requirements</a>. Your domain name must:
<p>Before requesting a .gov domain, please make sure it meets <a class="usa-link" rel="noopener noreferrer" target="_blank" href="{% if requires_feb_questions %}https://get.gov/domains/executive-branch-guidance/{% else %}{% public_site_url 'domains/choosing' %}{% endif %}">our naming requirements</a>. Your domain name must:
<ul class="usa-list">
<li>Be available </li>
<li>Relate to your organization's name, location, and/or services </li>
@ -101,7 +101,7 @@
{{ forms.2.management_form }}
{{ forms.3.management_form }}
{% if is_feb %}
{% if requires_feb_questions %}
<fieldset class="usa-fieldset margin-top-0 dotgov-domain-form">
<legend>
<h2>Does this submission meet each domain naming requirement?</h2>

View file

@ -12,6 +12,7 @@ from registrar.forms.utility.wizard_form_helper import request_step_list
from registrar.models import DomainRequest
from registrar.models.contact import Contact
from registrar.models.user import User
from registrar.utility.waffle import flag_is_active_for_user
from registrar.views.utility import StepsHelper
from registrar.views.utility.permission_views import DomainRequestPermissionDeleteView
from registrar.utility.enums import Step, PortfolioDomainRequestStep
@ -180,6 +181,9 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
"""Determines which step enum we should use for the wizard"""
return PortfolioDomainRequestStep if self.is_portfolio else Step
def requires_feb_questions(self) -> bool:
return self.domain_request.is_feb() and flag_is_active_for_user(self.request.user, "organization_feature")
@property
def prefix(self):
"""Namespace the wizard to avoid clashes in session variable names."""
@ -227,7 +231,12 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
if portfolio and not self._domain_request.generic_org_type:
self._domain_request.generic_org_type = portfolio.organization_type
self._domain_request.save()
if portfolio and not self._domain_request.federal_type:
logger.debug(f"Setting fed type to {portfolio.federal_type}")
self._domain_request.federal_type = portfolio.federal_type
self._domain_request.save()
else:
logger.debug("Did not find domain request in DomainRequestWizard, creating new one.")
self._domain_request = DomainRequest.objects.create(creator=self.request.user)
return self._domain_request
@ -654,7 +663,7 @@ class CurrentSites(DomainRequestWizard):
class DotgovDomain(DomainRequestWizard):
template_name = "domain_request_dotgov_domain.html"
forms = [
forms.DotGovDomainForm,
forms.DotGovDomainForm,
forms.AlternativeDomainFormSet,
forms.ExecutiveNamingRequirementsYesNoForm,
forms.ExecutiveNamingRequirementsDetailsForm,
@ -662,8 +671,7 @@ class DotgovDomain(DomainRequestWizard):
def get_context_data(self):
context = super().get_context_data()
context["generic_org_type"] = self.domain_request.generic_org_type
context["is_feb"] = self.domain_request.is_feb()
context["requires_feb_questions"] = self.requires_feb_questions()
return context
def is_valid(self, forms_list: list) -> bool:
@ -675,34 +683,26 @@ class DotgovDomain(DomainRequestWizard):
3: ExecutiveNamingRequirementsDetailsForm
"""
logger.debug("Validating dotgov domain form")
# If not a federal executive branch agency, mark executive-related forms for deletion.
if not (self.domain_request.is_feb()):
# If FEB questions aren't required, validate only non-FEB forms
if not self.requires_feb_questions():
forms_list[2].mark_form_for_deletion()
forms_list[3].mark_form_for_deletion()
return all(form.is_valid() for form in forms_list)
return forms_list[0].is_valid() and forms_list[1].is_valid()
if not forms_list[2].is_valid():
logger.debug("Dotgov domain form is invalid")
if forms_list[2].cleaned_data.get("feb_naming_requirements", None) != "no":
forms_list[3].mark_form_for_deletion()
# mark details form for deletion so that its errors don't show up
forms_list[3].mark_form_for_deletion()
return False
logger.debug(f"feb_naming_requirements: {forms_list[2].cleaned_data.get('feb_naming_requirements', None)}")
if forms_list[2].cleaned_data.get("feb_naming_requirements", None) != "no":
if forms_list[2].cleaned_data.get("feb_naming_requirements", None):
logger.debug("Marking details form for deletion")
# If the user selects "yes" or has made no selection, no details are needed.
forms_list[3].mark_form_for_deletion()
valid = all(
form.is_valid() for i, form in enumerate(forms_list) if i != 3
)
valid = all(form.is_valid() for i, form in enumerate(forms_list) if i != 3)
else:
# "No" was selected details are required.
valid = (
forms_list[2].is_valid() and
forms_list[3].is_valid() and
all(form.is_valid() for i, form in enumerate(forms_list) if i not in [2, 3])
)
valid = all(form.is_valid() for form in forms_list)
return valid