mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-29 05:56:30 +02:00
additional details forms
This commit is contained in:
parent
9ddf7a6cd0
commit
b022b13706
9 changed files with 260 additions and 15 deletions
|
@ -25,6 +25,8 @@ nameserversFormListener();
|
||||||
hookupYesNoListener("other_contacts-has_other_contacts",'other-employees', 'no-other-employees');
|
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_anything_else_text",'anything-else', null);
|
||||||
hookupYesNoListener("additional_details-has_cisa_representative",'cisa-representative', 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");
|
hookupYesNoListener("dotgov_domain-feb_naming_requirements", null, "domain-naming-requirements-details-container");
|
||||||
|
|
||||||
hookupCallbacksToRadioToggler("purpose-feb_purpose_choice", domain_purpose_choice_callbacks);
|
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-has_timeframe", "purpose-timeframe-details-container", null);
|
||||||
hookupYesNoListener("purpose-is_interagency_initiative", "purpose-interagency-initaitive-details-container", null);
|
hookupYesNoListener("purpose-is_interagency_initiative", "purpose-interagency-initaitive-details-container", null);
|
||||||
|
|
||||||
|
|
||||||
initializeUrbanizationToggle();
|
initializeUrbanizationToggle();
|
||||||
|
|
||||||
userProfileListener();
|
userProfileListener();
|
||||||
|
|
|
@ -599,7 +599,7 @@ class DotGovDomainForm(RegistrarForm):
|
||||||
return_type=ValidationReturnType.FORM_VALIDATION_ERROR,
|
return_type=ValidationReturnType.FORM_VALIDATION_ERROR,
|
||||||
)
|
)
|
||||||
return validated
|
return validated
|
||||||
|
|
||||||
def is_valid(self):
|
def is_valid(self):
|
||||||
return super().is_valid()
|
return super().is_valid()
|
||||||
|
|
||||||
|
|
|
@ -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"
|
|
@ -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),
|
||||||
|
),
|
||||||
|
]
|
|
@ -523,6 +523,19 @@ class DomainRequest(TimeStampedModel):
|
||||||
choices=FEBPurposeChoices.choices,
|
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
|
# This field is alternately used for generic domain purpose explanations
|
||||||
# and for explanations of the specific purpose chosen with feb_purpose_choice
|
# and for explanations of the specific purpose chosen with feb_purpose_choice
|
||||||
# by a Federal Executive Branch agency.
|
# by a Federal Executive Branch agency.
|
||||||
|
|
|
@ -6,16 +6,60 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block form_fields %}
|
{% block form_fields %}
|
||||||
|
{% if requires_feb_questions %}
|
||||||
|
{{forms.2.management_form}}
|
||||||
|
{{forms.3.management_form}}
|
||||||
|
{{forms.4.management_form}}
|
||||||
|
{{forms.5.management_form}}
|
||||||
|
<fieldset class="usa-fieldset">
|
||||||
|
<h2 class="margin-top-0 margin-bottom-0">Are you working with someone in the Executive Office of the President (EOP) on this request?</h2>
|
||||||
|
|
||||||
|
<p class="margin-bottom-0 margin-top-1">
|
||||||
|
<em>Select one. <abbr class="usa-hint usa-hint--required" title="required">*</abbr></em>
|
||||||
|
</p>
|
||||||
|
{% with add_class="usa-radio__input--tile" add_legend_class="usa-sr-only" %}
|
||||||
|
{% input_with_errors forms.0.working_with_eop %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
<fieldset class="usa-fieldset">
|
<div id="eop-contact-container" class="conditional-panel display-none">
|
||||||
|
<p class="margin-bottom-0 margin-top-1">
|
||||||
|
Provide the name and email of the person you're working with.<span class="usa-label--required">*</span>
|
||||||
|
</p>
|
||||||
|
{% with add_class="usa-radio__input--tile" add_legend_class="usa-sr-only" %}
|
||||||
|
{% input_with_errors forms.1.first_name %}
|
||||||
|
{% input_with_errors forms.1.last_name %}
|
||||||
|
{% input_with_errors forms.1.email %}
|
||||||
|
{% endwith %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2 class="margin-top-0 margin-bottom-0">Is there anything else you'd like us to know about your domain request?</h2>
|
||||||
|
<p class="margin-bottom-0 margin-top-1">
|
||||||
|
<em>Select one. <abbr class="usa-hint usa-hint--required" title="required">*</abbr></em>
|
||||||
|
</p>
|
||||||
|
{% with add_class="usa-radio__input--tile" add_legend_class="usa-sr-only" %}
|
||||||
|
{% input_with_errors forms.2.has_anything_else_text %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
<div id="anything-else-details-container" class="conditional-panel display-none">
|
||||||
|
<p class="usa-label">
|
||||||
|
<em>Provide details below <span class="usa-label--required">*</span></em>
|
||||||
|
</p>
|
||||||
|
{% with add_label_class="usa-sr-only" attr_required="required" attr_maxlength="2000" %}
|
||||||
|
{% input_with_errors forms.3.anything_else %}
|
||||||
|
{% endwith %}
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
{% else %}
|
||||||
|
<fieldset class="usa-fieldset">
|
||||||
<h2 class="margin-top-0 margin-bottom-0">Is there anything else you’d like us to know about your domain request?</h2>
|
<h2 class="margin-top-0 margin-bottom-0">Is there anything else you’d like us to know about your domain request?</h2>
|
||||||
</legend>
|
</legend>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<div id="anything-else">
|
<div id="anything-else">
|
||||||
<p><em>This question is optional.</em></p>
|
<p><em>This question is optional.</em></p>
|
||||||
{% with attr_maxlength=2000 add_label_class="usa-sr-only" %}
|
{% with attr_maxlength=2000 add_label_class="usa-sr-only" %}
|
||||||
{% input_with_errors forms.0.anything_else %}
|
{% input_with_errors forms.0.anything_else %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
</div>
|
</div>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -1984,6 +1984,8 @@ class TestDomainRequestAdmin(MockEppLib):
|
||||||
"feb_naming_requirements",
|
"feb_naming_requirements",
|
||||||
"feb_naming_requirements_details",
|
"feb_naming_requirements_details",
|
||||||
"feb_purpose_choice",
|
"feb_purpose_choice",
|
||||||
|
"working_with_eop",
|
||||||
|
"eop_contact",
|
||||||
"purpose",
|
"purpose",
|
||||||
"has_timeframe",
|
"has_timeframe",
|
||||||
"time_frame_details",
|
"time_frame_details",
|
||||||
|
|
|
@ -2550,7 +2550,7 @@ class DomainRequestTests(TestWithUser, WebTest):
|
||||||
|
|
||||||
# @less_console_noise_decorator
|
# @less_console_noise_decorator
|
||||||
@override_flag("organization_feature", active=True)
|
@override_flag("organization_feature", active=True)
|
||||||
def test_domain_request_dotgov_domain_FEB_questions(self):
|
def test_domain_request_FEB_questions(self):
|
||||||
"""
|
"""
|
||||||
Test that for a member of a federal executive branch portfolio with org feature on, the dotgov domain page
|
Test that for a member of a federal executive branch portfolio with org feature on, the dotgov domain page
|
||||||
contains additional questions for OMB.
|
contains additional questions for OMB.
|
||||||
|
@ -2612,13 +2612,14 @@ class DomainRequestTests(TestWithUser, WebTest):
|
||||||
# separate out these tests for readability
|
# separate out these tests for readability
|
||||||
self.feb_dotgov_domain_tests(dotgov_page)
|
self.feb_dotgov_domain_tests(dotgov_page)
|
||||||
|
|
||||||
# Now proceed with the actual test
|
|
||||||
domain_form = dotgov_page.forms[0]
|
domain_form = dotgov_page.forms[0]
|
||||||
domain = "test.gov"
|
domain = "test.gov"
|
||||||
domain_form["dotgov_domain-requested_domain"] = domain
|
domain_form["dotgov_domain-requested_domain"] = domain
|
||||||
domain_form["dotgov_domain-feb_naming_requirements"] = "True"
|
domain_form["dotgov_domain-feb_naming_requirements"] = "True"
|
||||||
domain_form["dotgov_domain-feb_naming_requirements_details"] = "test"
|
domain_form["dotgov_domain-feb_naming_requirements_details"] = "test"
|
||||||
with patch('registrar.forms.domain_request_wizard.DotGovDomainForm.clean_requested_domain', return_value=domain): # noqa
|
with patch(
|
||||||
|
"registrar.forms.domain_request_wizard.DotGovDomainForm.clean_requested_domain", return_value=domain
|
||||||
|
): # noqa
|
||||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
domain_result = domain_form.submit()
|
domain_result = domain_form.submit()
|
||||||
|
|
||||||
|
@ -2628,6 +2629,20 @@ class DomainRequestTests(TestWithUser, WebTest):
|
||||||
|
|
||||||
self.feb_purpose_page_tests(purpose_page)
|
self.feb_purpose_page_tests(purpose_page)
|
||||||
|
|
||||||
|
purpose_form = purpose_page.forms[0]
|
||||||
|
purpose_form["purpose-feb_purpose_choice"] = "redirect"
|
||||||
|
purpose_form["purpose-purpose"] = "test"
|
||||||
|
purpose_form["purpose-has_timeframe"] = "True"
|
||||||
|
purpose_form["purpose-time_frame_details"] = "test"
|
||||||
|
purpose_form["purpose-is_interagency_initiative"] = "True"
|
||||||
|
purpose_form["purpose-interagency_initiative_details"] = "test"
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
purpose_result = purpose_form.submit()
|
||||||
|
|
||||||
|
# ---- ADDITIONAL DETAILS PAGE ----
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
additional_details_page = purpose_result.follow()
|
||||||
|
self.feb_additional_details_page_tests(additional_details_page)
|
||||||
|
|
||||||
def feb_purpose_page_tests(self, purpose_page):
|
def feb_purpose_page_tests(self, purpose_page):
|
||||||
self.assertContains(purpose_page, "What is the purpose of your requested domain?")
|
self.assertContains(purpose_page, "What is the purpose of your requested domain?")
|
||||||
|
@ -2669,6 +2684,23 @@ class DomainRequestTests(TestWithUser, WebTest):
|
||||||
# Check that the details form was included
|
# Check that the details form was included
|
||||||
self.assertContains(dotgov_page, "feb_naming_requirements_details")
|
self.assertContains(dotgov_page, "feb_naming_requirements_details")
|
||||||
|
|
||||||
|
def feb_additional_details_page_tests(self, additional_details_page):
|
||||||
|
test_text = "Are you working with someone in the Executive Office of the President (EOP) on this request?"
|
||||||
|
self.assertContains(additional_details_page, test_text)
|
||||||
|
|
||||||
|
# Make sure the EOP form is present
|
||||||
|
self.assertContains(additional_details_page, "working_with_eop")
|
||||||
|
|
||||||
|
# Make sure the EOP contact form is present
|
||||||
|
self.assertContains(additional_details_page, "eop-contact-container")
|
||||||
|
self.assertContains(additional_details_page, "additional_details-first_name")
|
||||||
|
self.assertContains(additional_details_page, "additional_details-last_name")
|
||||||
|
self.assertContains(additional_details_page, "additional_details-email")
|
||||||
|
|
||||||
|
# Make sure the additional details form is present
|
||||||
|
self.assertContains(additional_details_page, "additional_details-has_anything_else_text")
|
||||||
|
self.assertContains(additional_details_page, "additional_details-anything_else")
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_domain_request_formsets(self):
|
def test_domain_request_formsets(self):
|
||||||
"""Users are able to add more than one of some fields."""
|
"""Users are able to add more than one of some fields."""
|
||||||
|
|
|
@ -15,7 +15,7 @@ from registrar.decorators import (
|
||||||
grant_access,
|
grant_access,
|
||||||
)
|
)
|
||||||
from registrar.forms import domain_request_wizard as forms
|
from registrar.forms import domain_request_wizard as forms
|
||||||
from registrar.forms.domainrequestwizard import purpose
|
from registrar.forms.domainrequestwizard import (purpose, additional_details)
|
||||||
from registrar.forms.utility.wizard_form_helper import request_step_list
|
from registrar.forms.utility.wizard_form_helper import request_step_list
|
||||||
from registrar.models import DomainRequest
|
from registrar.models import DomainRequest
|
||||||
from registrar.models.contact import Contact
|
from registrar.models.contact import Contact
|
||||||
|
@ -609,7 +609,45 @@ class RequestingEntity(DomainRequestWizard):
|
||||||
class PortfolioAdditionalDetails(DomainRequestWizard):
|
class PortfolioAdditionalDetails(DomainRequestWizard):
|
||||||
template_name = "portfolio_domain_request_additional_details.html"
|
template_name = "portfolio_domain_request_additional_details.html"
|
||||||
|
|
||||||
forms = [forms.PortfolioAnythingElseForm]
|
forms = [
|
||||||
|
additional_details.WorkingWithEOPYesNoForm,
|
||||||
|
additional_details.EOPContactForm,
|
||||||
|
additional_details.FEBAnythingElseYesNoForm,
|
||||||
|
forms.PortfolioAnythingElseForm,
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_context_data(self):
|
||||||
|
context = super().get_context_data()
|
||||||
|
context["requires_feb_questions"] = self.requires_feb_questions()
|
||||||
|
return context
|
||||||
|
|
||||||
|
def is_valid(self, forms: list) -> bool:
|
||||||
|
"""
|
||||||
|
Validates the forms for portfolio additional details.
|
||||||
|
|
||||||
|
Expected order of forms_list:
|
||||||
|
0: WorkingWithEOPYesNoForm
|
||||||
|
1: EOPContactForm
|
||||||
|
2: FEBAnythingElseYesNoForm
|
||||||
|
3: PortfolioAnythingElseForm
|
||||||
|
"""
|
||||||
|
eop_forms_valid = True
|
||||||
|
if not forms[0].is_valid():
|
||||||
|
# If the user isn't working with EOP, don't validate the EOP contact form
|
||||||
|
forms[1].mark_form_for_deletion()
|
||||||
|
eop_forms_valid = False
|
||||||
|
if forms[0].cleaned_data.get("working_with_eop"):
|
||||||
|
eop_forms_valid = forms[1].is_valid()
|
||||||
|
else:
|
||||||
|
forms[1].mark_form_for_deletion()
|
||||||
|
anything_else_forms_valid = True
|
||||||
|
if not forms[2].is_valid():
|
||||||
|
forms[3].mark_form_for_deletion()
|
||||||
|
anything_else_forms_valid = False
|
||||||
|
if forms[2].cleaned_data.get("has_anything_else_text"):
|
||||||
|
forms[3].fields["anything_else"].required = True
|
||||||
|
anything_else_forms_valid = forms[3].is_valid()
|
||||||
|
return (eop_forms_valid and anything_else_forms_valid)
|
||||||
|
|
||||||
|
|
||||||
# Non-portfolio pages
|
# Non-portfolio pages
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue