diff --git a/src/registrar/assets/src/js/getgov/main.js b/src/registrar/assets/src/js/getgov/main.js index 139c8484a..724f8b9d0 100644 --- a/src/registrar/assets/src/js/getgov/main.js +++ b/src/registrar/assets/src/js/getgov/main.js @@ -25,6 +25,8 @@ nameserversFormListener(); hookupYesNoListener("other_contacts-has_other_contacts",'other-employees', 'no-other-employees'); hookupYesNoListener("additional_details-has_anything_else_text",'anything-else', null); hookupYesNoListener("additional_details-has_cisa_representative",'cisa-representative', null); +hookupYesNoListener("portfolio_additional_details-working_with_eop", "eop-contact-container", null); +hookupYesNoListener("portfolio_additional_details-has_anything_else_text", 'anything-else-details-container', null); hookupYesNoListener("dotgov_domain-feb_naming_requirements", null, "domain-naming-requirements-details-container"); hookupCallbacksToRadioToggler("purpose-feb_purpose_choice", domain_purpose_choice_callbacks); @@ -32,7 +34,6 @@ hookupCallbacksToRadioToggler("purpose-feb_purpose_choice", domain_purpose_choic hookupYesNoListener("purpose-has_timeframe", "purpose-timeframe-details-container", null); hookupYesNoListener("purpose-is_interagency_initiative", "purpose-interagency-initaitive-details-container", null); - initializeUrbanizationToggle(); userProfileListener(); diff --git a/src/registrar/forms/domain_request_wizard.py b/src/registrar/forms/domain_request_wizard.py index 7cbb159b4..8c89a35ec 100644 --- a/src/registrar/forms/domain_request_wizard.py +++ b/src/registrar/forms/domain_request_wizard.py @@ -599,7 +599,7 @@ class DotGovDomainForm(RegistrarForm): return_type=ValidationReturnType.FORM_VALIDATION_ERROR, ) return validated - + def is_valid(self): return super().is_valid() diff --git a/src/registrar/forms/domainrequestwizard/additional_details.py b/src/registrar/forms/domainrequestwizard/additional_details.py new file mode 100644 index 000000000..8ae0629d5 --- /dev/null +++ b/src/registrar/forms/domainrequestwizard/additional_details.py @@ -0,0 +1,85 @@ +from django import forms +from django.core.validators import MaxLengthValidator +from registrar.forms.utility.wizard_form_helper import BaseDeletableRegistrarForm, BaseYesNoForm +from registrar.models.contact import Contact + + +class WorkingWithEOPYesNoForm(BaseDeletableRegistrarForm, BaseYesNoForm): + """ + Form for determining if the Federal Executive Branch (FEB) agency is working with the + Executive Office of the President (EOP) on the domain request. + """ + + field_name = "working_with_eop" + + @property + def form_is_checked(self): + """ + Determines the initial checked state of the form based on the domain_request's attributes. + """ + return self.domain_request.working_with_eop + + +class EOPContactForm(BaseDeletableRegistrarForm): + """ + Form for contact information of the representative of the + Executive Office of the President (EOP) that the Federal + Executive Branch (FEB) agency is working with. + """ + + field_name = "eop_contact" + + first_name = forms.CharField( + label="First name / given name", + error_messages={"required": "Enter the first name / given name of this contact."}, + required=True, + ) + last_name = forms.CharField( + label="Last name / family name", + error_messages={"required": "Enter the last name / family name of this contact."}, + required=True, + ) + email = forms.EmailField( + label="Email", + max_length=None, + error_messages={ + "required": ("Enter an email address in the required format, like name@example.com."), + "invalid": ("Enter an email address in the required format, like name@example.com."), + }, + validators=[ + MaxLengthValidator( + 320, + message="Response must be less than 320 characters.", + ) + ], + required=True, + help_text="Enter an email address in the required format, like name@example.com.", + ) + + @classmethod + def from_database(cls, obj): + # if not obj.eop_contact: + # return {} + # return { + # "first_name": obj.feb_eop_contact.first_name, + # "last_name": obj.feb_eop_contact.last_name, + # "email": obj.feb_eop_contact.email, + # } + return {} + + def to_database(self, obj): + if not self.is_valid(): + return + obj.eop_contact = Contact.objects.create( + first_name=self.cleaned_data["first_name"], + last_name=self.cleaned_data["last_name"], + email=self.cleaned_data["email"], + ) + obj.save() + + +class FEBAnythingElseYesNoForm(BaseYesNoForm, BaseDeletableRegistrarForm): + """Yes/no toggle for the anything else question on additional details""" + + form_is_checked = property(lambda self: self.domain_request.has_anything_else_text) # type: ignore + field_name = "has_anything_else_text" diff --git a/src/registrar/migrations/0143_domainrequest_feb_eop_contact_and_more.py b/src/registrar/migrations/0143_domainrequest_feb_eop_contact_and_more.py new file mode 100644 index 000000000..7001e47d6 --- /dev/null +++ b/src/registrar/migrations/0143_domainrequest_feb_eop_contact_and_more.py @@ -0,0 +1,30 @@ +# Generated by Django 4.2.17 on 2025-03-05 15:48 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ("registrar", "0142_domainrequest_feb_purpose_choice_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="domainrequest", + name="eop_contact", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + related_name="eop_contact", + to="registrar.contact", + ), + ), + migrations.AddField( + model_name="domainrequest", + name="working_with_eop", + field=models.BooleanField(blank=True, null=True), + ), + ] diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py index b7aaff65d..b181094aa 100644 --- a/src/registrar/models/domain_request.py +++ b/src/registrar/models/domain_request.py @@ -523,6 +523,19 @@ class DomainRequest(TimeStampedModel): choices=FEBPurposeChoices.choices, ) + working_with_eop = models.BooleanField( + null=True, + blank=True, + ) + + eop_contact = models.ForeignKey( + "registrar.Contact", + null=True, + blank=True, + related_name="eop_contact", + on_delete=models.PROTECT, + ) + # This field is alternately used for generic domain purpose explanations # and for explanations of the specific purpose chosen with feb_purpose_choice # by a Federal Executive Branch agency. diff --git a/src/registrar/templates/portfolio_domain_request_additional_details.html b/src/registrar/templates/portfolio_domain_request_additional_details.html index 5bc529243..d7d53dd1a 100644 --- a/src/registrar/templates/portfolio_domain_request_additional_details.html +++ b/src/registrar/templates/portfolio_domain_request_additional_details.html @@ -6,16 +6,60 @@ {% endblock %} {% block form_fields %} + {% if requires_feb_questions %} + {{forms.2.management_form}} + {{forms.3.management_form}} + {{forms.4.management_form}} + {{forms.5.management_form}} +
-This question is optional.
- {% with attr_maxlength=2000 add_label_class="usa-sr-only" %} - {% input_with_errors forms.0.anything_else %} - {% endwith %} -This question is optional.
+ {% with attr_maxlength=2000 add_label_class="usa-sr-only" %} + {% input_with_errors forms.0.anything_else %} + {% endwith %} +