Merge pull request #2224 from cisagov/rh/1686-missing-info-no-submit

ISSUE #1686: Disallow users to submit an incomplete form
This commit is contained in:
Rebecca H 2024-06-04 07:25:58 -07:00 committed by GitHub
commit a3ed38c0b4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 876 additions and 41 deletions

View file

@ -938,3 +938,131 @@ class DomainRequest(TimeStampedModel):
for field in opts.many_to_many: for field in opts.many_to_many:
data[field.name] = field.value_from_object(self) data[field.name] = field.value_from_object(self)
return data return data
def _is_federal_complete(self):
# Federal -> "Federal government branch" page can't be empty + Federal Agency selection can't be None
return not (self.federal_type is None or self.federal_agency is None)
def _is_interstate_complete(self):
# Interstate -> "About your organization" page can't be empty
return self.about_your_organization is not None
def _is_state_or_territory_complete(self):
# State -> ""Election office" page can't be empty
return self.is_election_board is not None
def _is_tribal_complete(self):
# Tribal -> "Tribal name" and "Election office" page can't be empty
return self.tribe_name is not None and self.is_election_board is not None
def _is_county_complete(self):
# County -> "Election office" page can't be empty
return self.is_election_board is not None
def _is_city_complete(self):
# City -> "Election office" page can't be empty
return self.is_election_board is not None
def _is_special_district_complete(self):
# Special District -> "Election office" and "About your organization" page can't be empty
return self.is_election_board is not None and self.about_your_organization is not None
def _is_organization_name_and_address_complete(self):
return not (
self.organization_name is None
and self.address_line1 is None
and self.city is None
and self.state_territory is None
and self.zipcode is None
)
def _is_authorizing_official_complete(self):
return self.authorizing_official is not None
def _is_requested_domain_complete(self):
return self.requested_domain is not None
def _is_purpose_complete(self):
return self.purpose is not None
def _is_submitter_complete(self):
return self.submitter is not None
def _has_other_contacts_and_filled(self):
# Other Contacts Radio button is Yes and if all required fields are filled
return (
self.has_other_contacts()
and self.other_contacts.filter(
first_name__isnull=False,
last_name__isnull=False,
title__isnull=False,
email__isnull=False,
phone__isnull=False,
).exists()
)
def _has_no_other_contacts_gives_rationale(self):
# Other Contacts Radio button is No and a rationale is provided
return self.has_other_contacts() is False and self.no_other_contacts_rationale is not None
def _is_other_contacts_complete(self):
if self._has_other_contacts_and_filled() or self._has_no_other_contacts_gives_rationale():
return True
return False
def _cisa_rep_and_email_check(self):
# Has a CISA rep + email is NOT empty or NOT an empty string OR doesn't have CISA rep
return (
self.has_cisa_representative is True
and self.cisa_representative_email is not None
and self.cisa_representative_email != ""
) or self.has_cisa_representative is False
def _anything_else_radio_button_and_text_field_check(self):
# Anything else boolean is True + filled text field and it's not an empty string OR the boolean is No
return (
self.has_anything_else_text is True and self.anything_else is not None and self.anything_else != ""
) or self.has_anything_else_text is False
def _is_additional_details_complete(self):
return self._cisa_rep_and_email_check() and self._anything_else_radio_button_and_text_field_check()
def _is_policy_acknowledgement_complete(self):
return self.is_policy_acknowledged is not None
def _is_general_form_complete(self):
return (
self._is_organization_name_and_address_complete()
and self._is_authorizing_official_complete()
and self._is_requested_domain_complete()
and self._is_purpose_complete()
and self._is_submitter_complete()
and self._is_other_contacts_complete()
and self._is_additional_details_complete()
and self._is_policy_acknowledgement_complete()
)
def _form_complete(self):
match self.generic_org_type:
case DomainRequest.OrganizationChoices.FEDERAL:
is_complete = self._is_federal_complete()
case DomainRequest.OrganizationChoices.INTERSTATE:
is_complete = self._is_interstate_complete()
case DomainRequest.OrganizationChoices.STATE_OR_TERRITORY:
is_complete = self._is_state_or_territory_complete()
case DomainRequest.OrganizationChoices.TRIBAL:
is_complete = self._is_tribal_complete()
case DomainRequest.OrganizationChoices.COUNTY:
is_complete = self._is_county_complete()
case DomainRequest.OrganizationChoices.CITY:
is_complete = self._is_city_complete()
case DomainRequest.OrganizationChoices.SPECIAL_DISTRICT:
is_complete = self._is_special_district_complete()
case _:
# NOTE: Shouldn't happen, this is only if somehow they didn't choose an org type
is_complete = False
if not is_complete or not self._is_general_form_complete():
return False
return True

View file

@ -105,7 +105,7 @@
aria-describedby="Are you sure you want to submit a domain request?" aria-describedby="Are you sure you want to submit a domain request?"
data-force-action data-force-action
> >
{% include 'includes/modal.html' with modal_heading=modal_heading|safe modal_description="Once you submit this request, you wont be able to edit it until we review it. Youll only be able to withdraw your request." modal_button=modal_button|safe %} {% include 'includes/modal.html' with is_domain_request_form=True review_form_is_complete=review_form_is_complete modal_heading=modal_heading|safe modal_description=modal_description|safe modal_button=modal_button|safe %}
</div> </div>
{% block after_form_content %}{% endblock %} {% block after_form_content %}{% endblock %}

View file

@ -25,11 +25,11 @@
{% if step == Step.ORGANIZATION_TYPE %} {% if step == Step.ORGANIZATION_TYPE %}
{% namespaced_url 'domain-request' step as domain_request_url %} {% namespaced_url 'domain-request' step as domain_request_url %}
{% if domain_request.generic_org_type is not None %} {% if domain_request.generic_org_type is not None %}
{% with title=form_titles|get_item:step value=domain_request.get_generic_org_type_display|default:"Incomplete" %} {% with title=form_titles|get_item:step value=domain_request.get_generic_org_type_display|default:"<span class='text-bold text-secondary-dark'>Incomplete</span>"|safe %}
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %} {% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %}
{% endwith %} {% endwith %}
{% else %} {% else %}
{% with title=form_titles|get_item:step value="Incomplete" %} {% with title=form_titles|get_item:step value="<span class='text-bold text-secondary-dark'>Incomplete</span>"|safe %}
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %} {% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %}
{% endwith %} {% endwith %}
{% endif %} {% endif %}
@ -37,7 +37,7 @@
{% if step == Step.TRIBAL_GOVERNMENT %} {% if step == Step.TRIBAL_GOVERNMENT %}
{% namespaced_url 'domain-request' step as domain_request_url %} {% namespaced_url 'domain-request' step as domain_request_url %}
{% with title=form_titles|get_item:step value=domain_request.tribe_name|default:"Incomplete" %} {% with title=form_titles|get_item:step value=domain_request.tribe_name|default:"<span class='text-bold text-secondary-dark'>Incomplete</span>"|safe %}
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %} {% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %}
{% endwith %} {% endwith %}
{% if domain_request.federally_recognized_tribe %}<p>Federally-recognized tribe</p>{% endif %} {% if domain_request.federally_recognized_tribe %}<p>Federally-recognized tribe</p>{% endif %}
@ -47,7 +47,7 @@
{% if step == Step.ORGANIZATION_FEDERAL %} {% if step == Step.ORGANIZATION_FEDERAL %}
{% namespaced_url 'domain-request' step as domain_request_url %} {% namespaced_url 'domain-request' step as domain_request_url %}
{% with title=form_titles|get_item:step value=domain_request.get_federal_type_display|default:"Incomplete" %} {% with title=form_titles|get_item:step value=domain_request.get_federal_type_display|default:"<span class='text-bold text-secondary-dark'>Incomplete</span>"|safe %}
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %} {% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %}
{% endwith %} {% endwith %}
{% endif %} {% endif %}
@ -66,7 +66,7 @@
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url address='true' %} {% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url address='true' %}
{% endwith %} {% endwith %}
{% else %} {% else %}
{% with title=form_titles|get_item:step value='Incomplete' %} {% with title=form_titles|get_item:step value="<span class='text-bold text-secondary-dark'>Incomplete</span>"|safe %}
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %} {% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %}
{% endwith %} {% endwith %}
{% endif %} {% endif %}
@ -74,7 +74,7 @@
{% if step == Step.ABOUT_YOUR_ORGANIZATION %} {% if step == Step.ABOUT_YOUR_ORGANIZATION %}
{% namespaced_url 'domain-request' step as domain_request_url %} {% namespaced_url 'domain-request' step as domain_request_url %}
{% with title=form_titles|get_item:step value=domain_request.about_your_organization|default:"Incomplete" %} {% with title=form_titles|get_item:step value=domain_request.about_your_organization|default:"<span class='text-bold text-secondary-dark'>Incomplete</span>"|safe %}
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %} {% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %}
{% endwith %} {% endwith %}
{% endif %} {% endif %}
@ -86,7 +86,7 @@
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url contact='true' %} {% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url contact='true' %}
{% endwith %} {% endwith %}
{% else %} {% else %}
{% with title=form_titles|get_item:step value="Incomplete" %} {% with title=form_titles|get_item:step value="<span class='text-bold text-secondary-dark'>Incomplete</span>"|safe %}
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %} {% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %}
{% endwith %} {% endwith %}
{% endif %} {% endif %}
@ -107,7 +107,7 @@
{% if step == Step.DOTGOV_DOMAIN %} {% if step == Step.DOTGOV_DOMAIN %}
{% namespaced_url 'domain-request' step as domain_request_url %} {% namespaced_url 'domain-request' step as domain_request_url %}
{% with title=form_titles|get_item:step value=domain_request.requested_domain.name|default:"Incomplete" %} {% with title=form_titles|get_item:step value=domain_request.requested_domain.name|default:"<span class='text-bold text-secondary-dark'>Incomplete</span>"|safe%}
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %} {% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %}
{% endwith %} {% endwith %}
@ -123,7 +123,7 @@
{% if step == Step.PURPOSE %} {% if step == Step.PURPOSE %}
{% namespaced_url 'domain-request' step as domain_request_url %} {% namespaced_url 'domain-request' step as domain_request_url %}
{% with title=form_titles|get_item:step value=domain_request.purpose|default:"Incomplete" %} {% with title=form_titles|get_item:step value=domain_request.purpose|default:"<span class='text-bold text-secondary-dark'>Incomplete</span>"|safe %}
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %} {% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %}
{% endwith %} {% endwith %}
{% endif %} {% endif %}
@ -135,7 +135,7 @@
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url contact='true' %} {% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url contact='true' %}
{% endwith %} {% endwith %}
{% else %} {% else %}
{% with title=form_titles|get_item:step value="Incomplete" %} {% with title=form_titles|get_item:step value="<span class='text-bold text-secondary-dark'>Incomplete</span>"|safe %}
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %} {% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %}
{% endwith %} {% endwith %}
{% endif %} {% endif %}
@ -148,7 +148,7 @@
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url contact='true' list='true' %} {% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url contact='true' list='true' %}
{% endwith %} {% endwith %}
{% else %} {% else %}
{% with title=form_titles|get_item:step value=domain_request.no_other_contacts_rationale|default:"Incomplete" %} {% with title=form_titles|get_item:step value=domain_request.no_other_contacts_rationale|default:"<span class='text-bold text-secondary-dark'>Incomplete</span>"|safe %}
{% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %} {% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=True edit_link=domain_request_url %}
{% endwith %} {% endwith %}
{% endif %} {% endif %}
@ -157,7 +157,7 @@
{% if step == Step.ADDITIONAL_DETAILS %} {% if step == Step.ADDITIONAL_DETAILS %}
{% namespaced_url 'domain-request' step as domain_request_url %} {% namespaced_url 'domain-request' step as domain_request_url %}
{% with title=form_titles|get_item:step value=domain_request.requested_domain.name|default:"Incomplete" %} {% with title=form_titles|get_item:step value=domain_request.requested_domain.name|default:"<span class='text-bold text-secondary-dark'>Incomplete</span>"|safe %}
{% include "includes/summary_item.html" with title=title sub_header_text='CISA regional representative' value=domain_request.cisa_representative_email heading_level=heading_level editable=True edit_link=domain_request_url custom_text_for_value_none='No' %} {% include "includes/summary_item.html" with title=title sub_header_text='CISA regional representative' value=domain_request.cisa_representative_email heading_level=heading_level editable=True edit_link=domain_request_url custom_text_for_value_none='No' %}
{% endwith %} {% endwith %}

View file

@ -18,12 +18,18 @@
<div class="usa-modal__footer"> <div class="usa-modal__footer">
<ul class="usa-button-group"> <ul class="usa-button-group">
{% if not_form %}
<li class="usa-button-group__item"> <li class="usa-button-group__item">
<form method="post">
{% csrf_token %}
{{ modal_button }} {{ modal_button }}
</form>
</li> </li>
{% else %}
<li class="usa-button-group__item">
<form method="post">
{% csrf_token %}
{{ modal_button }}
</form>
</li>
{% endif %}
<li class="usa-button-group__item"> <li class="usa-button-group__item">
{% comment %} The cancel button the DS form actually triggers a context change in the view, {% comment %} The cancel button the DS form actually triggers a context change in the view,
in addition to being a close modal hook {% endcomment %} in addition to being a close modal hook {% endcomment %}
@ -39,7 +45,7 @@
Cancel Cancel
</button> </button>
</form> </form>
{% else %} {% elif not is_domain_request_form or review_form_is_complete %}
<button <button
type="button" type="button"
class="usa-button usa-button--unstyled padding-105 text-center" class="usa-button usa-button--unstyled padding-105 text-center"

View file

@ -1,6 +1,7 @@
from django.test import TestCase from django.test import TestCase
from django.db.utils import IntegrityError from django.db.utils import IntegrityError
from unittest.mock import patch from unittest.mock import patch
from django.contrib.auth import get_user_model
from registrar.models import ( from registrar.models import (
Contact, Contact,
@ -1602,3 +1603,369 @@ class TestDomainInformationCustomSave(TestCase):
) )
self.assertEqual(domain_information_election.is_election_board, True) self.assertEqual(domain_information_election.is_election_board, True)
self.assertEqual(domain_information_election.generic_org_type, DomainRequest.OrganizationChoices.CITY) self.assertEqual(domain_information_election.generic_org_type, DomainRequest.OrganizationChoices.CITY)
class TestDomainRequestIncomplete(TestCase):
def setUp(self):
super().setUp()
username = "test_user"
first_name = "First"
last_name = "Last"
email = "info@example.com"
self.user = get_user_model().objects.create(
username=username, first_name=first_name, last_name=last_name, email=email
)
ao, _ = Contact.objects.get_or_create(
first_name="Meowy",
last_name="Meoward",
title="Chief Cat",
email="meoward@chiefcat.com",
phone="(206) 206 2060",
)
draft_domain, _ = DraftDomain.objects.get_or_create(name="MeowardMeowardMeoward.gov")
you, _ = Contact.objects.get_or_create(
first_name="Testy you",
last_name="Tester you",
title="Admin Tester",
email="testy-admin@town.com",
phone="(555) 555 5556",
)
other, _ = Contact.objects.get_or_create(
first_name="Testy2",
last_name="Tester2",
title="Another Tester",
email="testy2@town.com",
phone="(555) 555 5557",
)
alt, _ = Website.objects.get_or_create(website="MeowardMeowardMeoward1.gov")
current, _ = Website.objects.get_or_create(website="MeowardMeowardMeoward.com")
self.domain_request = DomainRequest.objects.create(
generic_org_type=DomainRequest.OrganizationChoices.FEDERAL,
federal_type="executive",
federal_agency=FederalAgency.objects.get(agency="AMTRAK"),
about_your_organization="Some description",
is_election_board=True,
tribe_name="Some tribe name",
organization_name="Some organization",
address_line1="address 1",
state_territory="CA",
zipcode="94044",
authorizing_official=ao,
requested_domain=draft_domain,
purpose="Some purpose",
submitter=you,
no_other_contacts_rationale=None,
has_cisa_representative=True,
cisa_representative_email="somerep@cisa.com",
has_anything_else_text=True,
anything_else="Anything else",
is_policy_acknowledged=True,
creator=self.user,
)
self.domain_request.other_contacts.add(other)
self.domain_request.current_websites.add(current)
self.domain_request.alternative_domains.add(alt)
def tearDown(self):
super().tearDown()
DomainRequest.objects.all().delete()
Contact.objects.all().delete()
def test_is_federal_complete(self):
self.assertTrue(self.domain_request._is_federal_complete())
self.domain_request.federal_type = None
self.domain_request.save()
self.assertFalse(self.domain_request._is_federal_complete())
def test_is_interstate_complete(self):
self.domain_request.generic_org_type = DomainRequest.OrganizationChoices.INTERSTATE
self.domain_request.about_your_organization = "Something something about your organization"
self.domain_request.save()
self.assertTrue(self.domain_request._is_interstate_complete())
self.domain_request.about_your_organization = None
self.domain_request.save()
self.assertFalse(self.domain_request._is_interstate_complete())
def test_is_state_or_territory_complete(self):
self.domain_request.generic_org_type = DomainRequest.OrganizationChoices.STATE_OR_TERRITORY
self.domain_request.is_election_board = True
self.domain_request.save()
self.assertTrue(self.domain_request._is_state_or_territory_complete())
self.domain_request.is_election_board = None
self.domain_request.save()
# is_election_board will overwrite to False bc of _update_org_type_from_generic_org_and_election
self.assertTrue(self.domain_request._is_state_or_territory_complete())
def test_is_tribal_complete(self):
self.domain_request.generic_org_type = DomainRequest.OrganizationChoices.TRIBAL
self.domain_request.tribe_name = "Tribe Name"
self.domain_request.is_election_board = False
self.domain_request.save()
self.assertTrue(self.domain_request._is_tribal_complete())
self.domain_request.tribe_name = None
self.domain_request.is_election_board = None
self.domain_request.save()
# is_election_board will overwrite to False bc of _update_org_type_from_generic_org_and_election
self.assertFalse(self.domain_request._is_tribal_complete())
def test_is_county_complete(self):
self.domain_request.generic_org_type = DomainRequest.OrganizationChoices.COUNTY
self.domain_request.is_election_board = False
self.domain_request.save()
self.assertTrue(self.domain_request._is_county_complete())
self.domain_request.is_election_board = None
self.domain_request.save()
# is_election_board will overwrite to False bc of _update_org_type_from_generic_org_and_election
self.assertTrue(self.domain_request._is_county_complete())
def test_is_city_complete(self):
self.domain_request.generic_org_type = DomainRequest.OrganizationChoices.CITY
self.domain_request.is_election_board = False
self.domain_request.save()
self.assertTrue(self.domain_request._is_city_complete())
self.domain_request.is_election_board = None
self.domain_request.save()
# is_election_board will overwrite to False bc of _update_org_type_from_generic_org_and_election
self.assertTrue(self.domain_request._is_city_complete())
def test_is_special_district_complete(self):
self.domain_request.generic_org_type = DomainRequest.OrganizationChoices.SPECIAL_DISTRICT
self.domain_request.about_your_organization = "Something something about your organization"
self.domain_request.is_election_board = False
self.domain_request.save()
self.assertTrue(self.domain_request._is_special_district_complete())
self.domain_request.about_your_organization = None
self.domain_request.is_election_board = None
self.domain_request.save()
# is_election_board will overwrite to False bc of _update_org_type_from_generic_org_and_election
self.assertFalse(self.domain_request._is_special_district_complete())
def test_is_organization_name_and_address_complete(self):
self.assertTrue(self.domain_request._is_organization_name_and_address_complete())
self.domain_request.organization_name = None
self.domain_request.address_line1 = None
self.domain_request.save()
self.assertTrue(self.domain_request._is_organization_name_and_address_complete())
def test_is_authorizing_official_complete(self):
self.assertTrue(self.domain_request._is_authorizing_official_complete())
self.domain_request.authorizing_official = None
self.domain_request.save()
self.assertFalse(self.domain_request._is_authorizing_official_complete())
def test_is_requested_domain_complete(self):
self.assertTrue(self.domain_request._is_requested_domain_complete())
self.domain_request.requested_domain = None
self.domain_request.save()
self.assertFalse(self.domain_request._is_requested_domain_complete())
def test_is_purpose_complete(self):
self.assertTrue(self.domain_request._is_purpose_complete())
self.domain_request.purpose = None
self.domain_request.save()
self.assertFalse(self.domain_request._is_purpose_complete())
def test_is_submitter_complete(self):
self.assertTrue(self.domain_request._is_submitter_complete())
self.domain_request.submitter = None
self.domain_request.save()
self.assertFalse(self.domain_request._is_submitter_complete())
def test_is_other_contacts_complete_missing_one_field(self):
self.assertTrue(self.domain_request._is_other_contacts_complete())
contact = self.domain_request.other_contacts.first()
contact.first_name = None
contact.save()
self.assertFalse(self.domain_request._is_other_contacts_complete())
def test_is_other_contacts_complete_all_none(self):
self.domain_request.other_contacts.clear()
self.assertFalse(self.domain_request._is_other_contacts_complete())
def test_is_other_contacts_False_and_has_rationale(self):
# Click radio button "No" for no other contacts and give rationale
self.domain_request.other_contacts.clear()
self.domain_request.other_contacts.exists = False
self.domain_request.no_other_contacts_rationale = "Some rationale"
self.assertTrue(self.domain_request._is_other_contacts_complete())
def test_is_other_contacts_False_and_NO_rationale(self):
# Click radio button "No" for no other contacts and DONT give rationale
self.domain_request.other_contacts.clear()
self.domain_request.other_contacts.exists = False
self.domain_request.no_other_contacts_rationale = None
self.assertFalse(self.domain_request._is_other_contacts_complete())
def test_is_additional_details_complete(self):
test_cases = [
# CISA Rep - Yes
# Email - Yes
# Anything Else Radio - Yes
# Anything Else Text - Yes
{
"has_cisa_representative": True,
"cisa_representative_email": "some@cisarepemail.com",
"has_anything_else_text": True,
"anything_else": "Some text",
"expected": True,
},
# CISA Rep - Yes
# Email - Yes
# Anything Else Radio - Yes
# Anything Else Text - None
{
"has_cisa_representative": True,
"cisa_representative_email": "some@cisarepemail.com",
"has_anything_else_text": True,
"anything_else": None,
"expected": True,
},
# CISA Rep - Yes
# Email - Yes
# Anything Else Radio - No
# Anything Else Text - No
{
"has_cisa_representative": True,
"cisa_representative_email": "some@cisarepemail.com",
"has_anything_else_text": False,
"anything_else": None,
"expected": True,
},
# CISA Rep - Yes
# Email - Yes
# Anything Else Radio - None
# Anything Else Text - None
{
"has_cisa_representative": True,
"cisa_representative_email": "some@cisarepemail.com",
"has_anything_else_text": None,
"anything_else": None,
"expected": False,
},
# CISA Rep - Yes
# Email - None
# Anything Else Radio - None
# Anything Else Text - None
{
"has_cisa_representative": True,
"cisa_representative_email": None,
"has_anything_else_text": None,
"anything_else": None,
"expected": False,
},
# CISA Rep - Yes
# Email - None
# Anything Else Radio - No
# Anything Else Text - No
# sync_yes_no will override has_cisa_representative to be False if cisa_representative_email is None
# therefore, our expected will be True
{
"has_cisa_representative": True,
# Above will be overridden to False if cisa_rep_email is None bc of sync_yes_no_form_fields
"cisa_representative_email": None,
"has_anything_else_text": False,
"anything_else": None,
"expected": True,
},
# CISA Rep - Yes
# Email - None
# Anything Else Radio - Yes
# Anything Else Text - None
{
"has_cisa_representative": True,
# Above will be overridden to False if cisa_rep_email is None bc of sync_yes_no_form_fields
"cisa_representative_email": None,
"has_anything_else_text": True,
"anything_else": None,
"expected": True,
},
# CISA Rep - Yes
# Email - None
# Anything Else Radio - Yes
# Anything Else Text - Yes
{
"has_cisa_representative": True,
# Above will be overridden to False if cisa_rep_email is None bc of sync_yes_no_form_fields
"cisa_representative_email": None,
"has_anything_else_text": True,
"anything_else": "Some text",
"expected": True,
},
# CISA Rep - No
# Anything Else Radio - Yes
# Anything Else Text - Yes
{
"has_cisa_representative": False,
"cisa_representative_email": None,
"has_anything_else_text": True,
"anything_else": "Some text",
"expected": True,
},
# CISA Rep - No
# Anything Else Radio - Yes
# Anything Else Text - None
{
"has_cisa_representative": False,
"cisa_representative_email": None,
"has_anything_else_text": True,
"anything_else": None,
"expected": True,
},
# CISA Rep - No
# Anything Else Radio - None
# Anything Else Text - None
{
"has_cisa_representative": False,
"cisa_representative_email": None,
"has_anything_else_text": None,
"anything_else": None,
# Above is both None, so it does NOT get overwritten
"expected": False,
},
# CISA Rep - No
# Anything Else Radio - No
# Anything Else Text - No
{
"has_cisa_representative": False,
"cisa_representative_email": None,
"has_anything_else_text": False,
"anything_else": None,
"expected": True,
},
# CISA Rep - None
# Anything Else Radio - None
{
"has_cisa_representative": None,
"cisa_representative_email": None,
"has_anything_else_text": None,
"anything_else": None,
"expected": False,
},
]
for case in test_cases:
with self.subTest(case=case):
self.domain_request.has_cisa_representative = case["has_cisa_representative"]
self.domain_request.cisa_representative_email = case["cisa_representative_email"]
self.domain_request.has_anything_else_text = case["has_anything_else_text"]
self.domain_request.anything_else = case["anything_else"]
self.domain_request.save()
self.domain_request.refresh_from_db()
self.assertEqual(
self.domain_request._is_additional_details_complete(),
case["expected"],
msg=f"Failed for case: {case}",
)
def test_is_policy_acknowledgement_complete(self):
self.assertTrue(self.domain_request._is_policy_acknowledgement_complete())
self.domain_request.is_policy_acknowledged = False
self.assertTrue(self.domain_request._is_policy_acknowledgement_complete())
self.domain_request.is_policy_acknowledged = None
self.assertFalse(self.domain_request._is_policy_acknowledgement_complete())
def test_form_complete(self):
self.assertTrue(self.domain_request._form_complete())
self.domain_request.generic_org_type = None
self.domain_request.save()
self.assertFalse(self.domain_request._form_complete())

View file

@ -467,6 +467,323 @@ class DomainRequestTests(TestWithUser, WebTest):
# check that any new pages are added to this test # check that any new pages are added to this test
self.assertEqual(num_pages, num_pages_tested) self.assertEqual(num_pages, num_pages_tested)
@boto3_mocking.patching
def test_domain_request_form_submission_incomplete(self):
num_pages_tested = 0
# skipping elections, type_of_work, tribal_government
intro_page = self.app.get(reverse("domain-request:"))
# django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here
# and then setting the cookie on each request.
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
intro_form = intro_page.forms[0]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
intro_result = intro_form.submit()
# follow first redirect
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
type_page = intro_result.follow()
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
# ---- TYPE PAGE ----
type_form = type_page.forms[0]
type_form["generic_org_type-generic_org_type"] = "federal"
# test next button and validate data
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
type_result = type_form.submit()
# should see results in db
domain_request = DomainRequest.objects.get() # there's only one
self.assertEqual(domain_request.generic_org_type, "federal")
# the post request should return a redirect to the next form in
# the domain request page
self.assertEqual(type_result.status_code, 302)
self.assertEqual(type_result["Location"], "/request/organization_federal/")
num_pages_tested += 1
# ---- FEDERAL BRANCH PAGE ----
# Follow the redirect to the next form page
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
federal_page = type_result.follow()
federal_form = federal_page.forms[0]
federal_form["organization_federal-federal_type"] = "executive"
# test next button
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
federal_result = federal_form.submit()
# validate that data from this step are being saved
domain_request = DomainRequest.objects.get() # there's only one
self.assertEqual(domain_request.federal_type, "executive")
# the post request should return a redirect to the next form in
# the domain request page
self.assertEqual(federal_result.status_code, 302)
self.assertEqual(federal_result["Location"], "/request/organization_contact/")
num_pages_tested += 1
# ---- ORG CONTACT PAGE ----
# Follow the redirect to the next form page
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
org_contact_page = federal_result.follow()
org_contact_form = org_contact_page.forms[0]
# federal agency so we have to fill in federal_agency
federal_agency, _ = FederalAgency.objects.get_or_create(agency="General Services Administration")
org_contact_form["organization_contact-federal_agency"] = federal_agency.id
org_contact_form["organization_contact-organization_name"] = "Testorg"
org_contact_form["organization_contact-address_line1"] = "address 1"
org_contact_form["organization_contact-address_line2"] = "address 2"
org_contact_form["organization_contact-city"] = "NYC"
org_contact_form["organization_contact-state_territory"] = "NY"
org_contact_form["organization_contact-zipcode"] = "10002"
org_contact_form["organization_contact-urbanization"] = "URB Royal Oaks"
# test next button
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
org_contact_result = org_contact_form.submit()
# validate that data from this step are being saved
domain_request = DomainRequest.objects.get() # there's only one
self.assertEqual(domain_request.organization_name, "Testorg")
self.assertEqual(domain_request.address_line1, "address 1")
self.assertEqual(domain_request.address_line2, "address 2")
self.assertEqual(domain_request.city, "NYC")
self.assertEqual(domain_request.state_territory, "NY")
self.assertEqual(domain_request.zipcode, "10002")
self.assertEqual(domain_request.urbanization, "URB Royal Oaks")
# the post request should return a redirect to the next form in
# the domain request page
self.assertEqual(org_contact_result.status_code, 302)
self.assertEqual(org_contact_result["Location"], "/request/authorizing_official/")
num_pages_tested += 1
# ---- AUTHORIZING OFFICIAL PAGE ----
# Follow the redirect to the next form page
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
ao_page = org_contact_result.follow()
ao_form = ao_page.forms[0]
ao_form["authorizing_official-first_name"] = "Testy ATO"
ao_form["authorizing_official-last_name"] = "Tester ATO"
ao_form["authorizing_official-title"] = "Chief Tester"
ao_form["authorizing_official-email"] = "testy@town.com"
# test next button
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
ao_result = ao_form.submit()
# validate that data from this step are being saved
domain_request = DomainRequest.objects.get() # there's only one
self.assertEqual(domain_request.authorizing_official.first_name, "Testy ATO")
self.assertEqual(domain_request.authorizing_official.last_name, "Tester ATO")
self.assertEqual(domain_request.authorizing_official.title, "Chief Tester")
self.assertEqual(domain_request.authorizing_official.email, "testy@town.com")
# the post request should return a redirect to the next form in
# the domain request page
self.assertEqual(ao_result.status_code, 302)
self.assertEqual(ao_result["Location"], "/request/current_sites/")
num_pages_tested += 1
# ---- CURRENT SITES PAGE ----
# Follow the redirect to the next form page
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
current_sites_page = ao_result.follow()
current_sites_form = current_sites_page.forms[0]
current_sites_form["current_sites-0-website"] = "www.city.com"
# test next button
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
current_sites_result = current_sites_form.submit()
# validate that data from this step are being saved
domain_request = DomainRequest.objects.get() # there's only one
self.assertEqual(
domain_request.current_websites.filter(website="http://www.city.com").count(),
1,
)
# the post request should return a redirect to the next form in
# the domain request page
self.assertEqual(current_sites_result.status_code, 302)
self.assertEqual(current_sites_result["Location"], "/request/dotgov_domain/")
num_pages_tested += 1
# ---- DOTGOV DOMAIN PAGE ----
# Follow the redirect to the next form page
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
dotgov_page = current_sites_result.follow()
dotgov_form = dotgov_page.forms[0]
dotgov_form["dotgov_domain-requested_domain"] = "city"
dotgov_form["dotgov_domain-0-alternative_domain"] = "city1"
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
dotgov_result = dotgov_form.submit()
# validate that data from this step are being saved
domain_request = DomainRequest.objects.get() # there's only one
self.assertEqual(domain_request.requested_domain.name, "city.gov")
self.assertEqual(domain_request.alternative_domains.filter(website="city1.gov").count(), 1)
# the post request should return a redirect to the next form in
# the domain request page
self.assertEqual(dotgov_result.status_code, 302)
self.assertEqual(dotgov_result["Location"], "/request/purpose/")
num_pages_tested += 1
# ---- PURPOSE PAGE ----
# Follow the redirect to the next form page
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
purpose_page = dotgov_result.follow()
purpose_form = purpose_page.forms[0]
purpose_form["purpose-purpose"] = "For all kinds of things."
# test next button
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
purpose_result = purpose_form.submit()
# validate that data from this step are being saved
domain_request = DomainRequest.objects.get() # there's only one
self.assertEqual(domain_request.purpose, "For all kinds of things.")
# the post request should return a redirect to the next form in
# the domain request page
self.assertEqual(purpose_result.status_code, 302)
self.assertEqual(purpose_result["Location"], "/request/your_contact/")
num_pages_tested += 1
# ---- YOUR CONTACT INFO PAGE ----
# Follow the redirect to the next form page
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
your_contact_page = purpose_result.follow()
your_contact_form = your_contact_page.forms[0]
your_contact_form["your_contact-first_name"] = "Testy you"
your_contact_form["your_contact-last_name"] = "Tester you"
your_contact_form["your_contact-title"] = "Admin Tester"
your_contact_form["your_contact-email"] = "testy-admin@town.com"
your_contact_form["your_contact-phone"] = "(201) 555 5556"
# test next button
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
your_contact_result = your_contact_form.submit()
# validate that data from this step are being saved
domain_request = DomainRequest.objects.get() # there's only one
self.assertEqual(domain_request.submitter.first_name, "Testy you")
self.assertEqual(domain_request.submitter.last_name, "Tester you")
self.assertEqual(domain_request.submitter.title, "Admin Tester")
self.assertEqual(domain_request.submitter.email, "testy-admin@town.com")
self.assertEqual(domain_request.submitter.phone, "(201) 555 5556")
# the post request should return a redirect to the next form in
# the domain request page
self.assertEqual(your_contact_result.status_code, 302)
self.assertEqual(your_contact_result["Location"], "/request/other_contacts/")
num_pages_tested += 1
# ---- OTHER CONTACTS PAGE ----
# Follow the redirect to the next form page
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
other_contacts_page = your_contact_result.follow()
# This page has 3 forms in 1.
# Let's set the yes/no radios to enable the other contacts fieldsets
other_contacts_form = other_contacts_page.forms[0]
other_contacts_form["other_contacts-has_other_contacts"] = "True"
other_contacts_form["other_contacts-0-first_name"] = "Testy2"
other_contacts_form["other_contacts-0-last_name"] = "Tester2"
other_contacts_form["other_contacts-0-title"] = "Another Tester"
other_contacts_form["other_contacts-0-email"] = "testy2@town.com"
other_contacts_form["other_contacts-0-phone"] = "(201) 555 5557"
# test next button
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
other_contacts_result = other_contacts_form.submit()
# validate that data from this step are being saved
domain_request = DomainRequest.objects.get() # there's only one
self.assertEqual(
domain_request.other_contacts.filter(
first_name="Testy2",
last_name="Tester2",
title="Another Tester",
email="testy2@town.com",
phone="(201) 555 5557",
).count(),
1,
)
# the post request should return a redirect to the next form in
# the domain request page
self.assertEqual(other_contacts_result.status_code, 302)
self.assertEqual(other_contacts_result["Location"], "/request/additional_details/")
num_pages_tested += 1
# ---- ADDITIONAL DETAILS PAGE ----
# Follow the redirect to the next form page
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
additional_details_page = other_contacts_result.follow()
additional_details_form = additional_details_page.forms[0]
# load inputs with test data
additional_details_form["additional_details-has_cisa_representative"] = "True"
additional_details_form["additional_details-has_anything_else_text"] = "True"
additional_details_form["additional_details-cisa_representative_email"] = "FakeEmail@gmail.com"
additional_details_form["additional_details-anything_else"] = "Nothing else."
# test next button
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
additional_details_result = additional_details_form.submit()
# validate that data from this step are being saved
domain_request = DomainRequest.objects.get() # there's only one
self.assertEqual(domain_request.cisa_representative_email, "FakeEmail@gmail.com")
self.assertEqual(domain_request.anything_else, "Nothing else.")
# the post request should return a redirect to the next form in
# the domain request page
self.assertEqual(additional_details_result.status_code, 302)
self.assertEqual(additional_details_result["Location"], "/request/requirements/")
num_pages_tested += 1
# ---- REQUIREMENTS PAGE ----
# Follow the redirect to the next form page
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
requirements_page = additional_details_result.follow()
requirements_form = requirements_page.forms[0]
requirements_form["requirements-is_policy_acknowledged"] = True
# Before we go to the review page, let's remove some of the data from the request:
domain_request = DomainRequest.objects.get() # there's only one
domain_request.generic_org_type = None
domain_request.save()
# test next button
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
requirements_result = requirements_form.submit()
# validate that data from this step are being saved
domain_request.refresh_from_db()
self.assertEqual(domain_request.is_policy_acknowledged, True)
# the post request should return a redirect to the next form in
# the domain request page
self.assertEqual(requirements_result.status_code, 302)
self.assertEqual(requirements_result["Location"], "/request/review/")
num_pages_tested += 1
# ---- REVIEW AND FINSIHED PAGES ----
# Follow the redirect to the next form page
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
review_page = requirements_result.follow()
# review_form = review_page.forms[0]
# Review page contains all the previously entered data
# Let's make sure the long org name is displayed
self.assertNotContains(review_page, "Federal")
# self.assertContains(review_page, "Executive")
self.assertContains(review_page, "Incomplete", count=1)
# We can't test the modal itself as it relies on JS for init and triggering,
# but we can test for the existence of its trigger:
self.assertContains(review_page, "toggle-submit-domain-request")
# And the existence of the modal's data parked and ready for the js init.
# The next assert also tests for the passed requested domain context from
# the view > domain_request_form > modal
self.assertNotContains(review_page, "You are about to submit a domain request for city.gov")
self.assertContains(review_page, "Your request form is incomplete")
# This is the start of a test to check an existing domain_request, it currently # This is the start of a test to check an existing domain_request, it currently
# does not work and results in errors as noted in: # does not work and results in errors as noted in:
# https://github.com/cisagov/getgov/pull/728 # https://github.com/cisagov/getgov/pull/728
@ -2385,7 +2702,7 @@ class DomainRequestTests(TestWithUser, WebTest):
review_page = self.app.get(reverse("domain-request:review")) review_page = self.app.get(reverse("domain-request:review"))
self.assertContains(review_page, "toggle-submit-domain-request") self.assertContains(review_page, "toggle-submit-domain-request")
self.assertContains(review_page, "You are about to submit an incomplete request") self.assertContains(review_page, "Your request form is incomplete")
class DomainRequestTestDifferentStatuses(TestWithUser, WebTest): class DomainRequestTestDifferentStatuses(TestWithUser, WebTest):

View file

@ -379,30 +379,45 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
def get_context_data(self): def get_context_data(self):
"""Define context for access on all wizard pages.""" """Define context for access on all wizard pages."""
# Build the submit button that we'll pass to the modal.
modal_button = '<button type="submit" ' 'class="usa-button" ' ">Submit request</button>"
# Concatenate the modal header that we'll pass to the modal.
if self.domain_request.requested_domain:
modal_heading = "You are about to submit a domain request for " + str(self.domain_request.requested_domain)
else:
modal_heading = "You are about to submit an incomplete request"
has_profile_flag = flag_is_active(self.request, "profile_feature") has_profile_flag = flag_is_active(self.request, "profile_feature")
logger.debug("PROFILE FLAG is %s" % has_profile_flag) logger.debug("PROFILE FLAG is %s" % has_profile_flag)
context = { context_stuff = {}
"form_titles": self.TITLES, if DomainRequest._form_complete(self.domain_request):
"steps": self.steps, modal_button = '<button type="submit" ' 'class="usa-button" ' ">Submit request</button>"
# Add information about which steps should be unlocked context_stuff = {
"visited": self.storage.get("step_history", []), "not_form": False,
"is_federal": self.domain_request.is_federal(), "form_titles": self.TITLES,
"modal_button": modal_button, "steps": self.steps,
"modal_heading": modal_heading, "visited": self.storage.get("step_history", []),
# Use the profile waffle feature flag to toggle profile features throughout domain requests "is_federal": self.domain_request.is_federal(),
"has_profile_feature_flag": has_profile_flag, "modal_button": modal_button,
"user": self.request.user, "modal_heading": "You are about to submit a domain request for "
} + str(self.domain_request.requested_domain),
return context "modal_description": "Once you submit this request, you wont be able to edit it until we review it.\
Youll only be able to withdraw your request.",
"review_form_is_complete": True,
# Use the profile waffle feature flag to toggle profile features throughout domain requests
"has_profile_feature_flag": has_profile_flag,
"user": self.request.user,
}
else: # form is not complete
modal_button = '<button type="button" class="usa-button" data-close-modal>Return to request</button>'
context_stuff = {
"not_form": True,
"form_titles": self.TITLES,
"steps": self.steps,
"visited": self.storage.get("step_history", []),
"is_federal": self.domain_request.is_federal(),
"modal_button": modal_button,
"modal_heading": "Your request form is incomplete",
"modal_description": 'This request cannot be submitted yet.\
Return to the request and visit the steps that are marked as "incomplete."',
"review_form_is_complete": False,
"has_profile_feature_flag": has_profile_flag,
"user": self.request.user,
}
return context_stuff
def get_step_list(self) -> list: def get_step_list(self) -> list:
"""Dynamically generated list of steps in the form wizard.""" """Dynamically generated list of steps in the form wizard."""
@ -670,6 +685,8 @@ class Review(DomainRequestWizard):
forms = [] # type: ignore forms = [] # type: ignore
def get_context_data(self): def get_context_data(self):
if DomainRequest._form_complete(self.domain_request) is False:
logger.warning("User arrived at review page with an incomplete form.")
context = super().get_context_data() context = super().get_context_data()
context["Step"] = Step.__members__ context["Step"] = Step.__members__
context["domain_request"] = self.domain_request context["domain_request"] = self.domain_request