Skip to content

Commit

Permalink
correct form validation and dynamic elements
Browse files Browse the repository at this point in the history
  • Loading branch information
Matt-Spence committed Feb 25, 2025
1 parent 190bca2 commit 065febd
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 83 deletions.
71 changes: 10 additions & 61 deletions src/registrar/forms/domain_request_wizard.py
Original file line number Diff line number Diff line change
Expand Up @@ -607,7 +607,8 @@ def clean_requested_domain(self):
},
)

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.
Expand All @@ -621,77 +622,25 @@ def form_is_checked(self):
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(
Expand All @@ -710,7 +659,7 @@ class PurposeForm(RegistrarForm):
],
error_messages={"required": "Describe how you’ll use the .gov domain you’re requesting."},
)


class OtherContactsYesNoForm(BaseYesNoForm):
"""The yes/no field for the OtherContacts form."""
Expand Down
2 changes: 1 addition & 1 deletion src/registrar/models/domain_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -1398,7 +1398,7 @@ def has_additional_details(self) -> bool:
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:
Expand Down
4 changes: 2 additions & 2 deletions src/registrar/templates/domain_request_dotgov_domain.html
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -101,7 +101,7 @@ <h2>Alternative domains (optional)</h2>
{{ 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>
Expand Down
38 changes: 19 additions & 19 deletions src/registrar/views/domain_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
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
Expand Down Expand Up @@ -180,6 +181,9 @@ def get_step_enum(self):
"""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."""
Expand Down Expand Up @@ -227,7 +231,12 @@ def domain_request(self) -> DomainRequest:
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

Expand Down Expand Up @@ -654,16 +663,15 @@ class CurrentSites(DomainRequestWizard):
class DotgovDomain(DomainRequestWizard):
template_name = "domain_request_dotgov_domain.html"
forms = [
forms.DotGovDomainForm,
forms.DotGovDomainForm,
forms.AlternativeDomainFormSet,
forms.ExecutiveNamingRequirementsYesNoForm,
forms.ExecutiveNamingRequirementsDetailsForm,
]

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:
Expand All @@ -675,34 +683,26 @@ def is_valid(self, forms_list: list) -> bool:
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


Expand Down

0 comments on commit 065febd

Please sign in to comment.