Merge pull request #331 from cisagov/nmb/field-validation

Application form field validation
This commit is contained in:
Neil MartinsenBurrell 2023-01-10 09:33:31 -06:00 committed by GitHub
commit f30b83f904
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 704 additions and 159 deletions

View file

@ -21,6 +21,7 @@ django-widget-tweaks = "*"
cachetools = "*"
requests = "*"
django-fsm = "*"
django-phonenumber-field = {extras = ["phonenumberslite"], version = "*"}
[dev-packages]
django-debug-toolbar = "*"

44
src/Pipfile.lock generated
View file

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "1668475ce39851bd84ff7be330afe9766f6823cf9095980ba3b220ced3a284f4"
"sha256": "b75e38d4e723e06a194cb607ba3003ed7a9f0460f23d036d9cd18214341d3e77"
},
"pipfile-spec": 6,
"requires": {},
@ -24,11 +24,11 @@
},
"cachetools": {
"hashes": [
"sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757",
"sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db"
"sha256:5991bc0e08a1319bb618d3195ca5b6bc76646a49c21d55962977197b301cc1fe",
"sha256:8462eebf3a6c15d25430a8c27c56ac61340b2ecf60c9ce57afc2b97e450e47da"
],
"index": "pypi",
"version": "==5.2.0"
"version": "==5.2.1"
},
"certifi": {
"hashes": [
@ -120,7 +120,7 @@
"sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845",
"sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"
],
"markers": "python_full_version >= '3.6.0'",
"markers": "python_version >= '3.6'",
"version": "==2.1.1"
},
"cryptography": {
@ -221,6 +221,17 @@
"index": "pypi",
"version": "==2.8.1"
},
"django-phonenumber-field": {
"extras": [
"phonenumberslite"
],
"hashes": [
"sha256:9edad2b2602af25f2aefc73c4cf53eaf7abf9e17d73c1c4372bd3052bebb26f9",
"sha256:de3e47b986b4959949762c16fd8fe26b3e462ef3e5531ed00950bd20c698576a"
],
"index": "pypi",
"version": "==7.0.2"
},
"django-widget-tweaks": {
"hashes": [
"sha256:9bfc5c705684754a83cc81da328b39ad1b80f32bd0f4340e2a810cbab4b0c00e",
@ -242,11 +253,11 @@
},
"faker": {
"hashes": [
"sha256:2d5443724f640ce07658ca8ca8bbd40d26b58914e63eec6549727869aa67e2cc",
"sha256:c2a2ff9dd8dfd991109b517ab98d5cb465e857acb45f6b643a0e284a9eb2cc76"
"sha256:4a8bc3cec832dde1928f8ce0817452bdadf63863d9e4d8307817247a38e51523",
"sha256:e15becbddc3a69a342e03ca6810caab7299e28e48106ae113a07f65c627d6fd7"
],
"index": "pypi",
"version": "==15.3.4"
"version": "==16.1.0"
},
"furl": {
"hashes": [
@ -357,11 +368,18 @@
},
"packaging": {
"hashes": [
"sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3",
"sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3"
"sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2",
"sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"
],
"markers": "python_version >= '3.7'",
"version": "==22.0"
"version": "==23.0"
},
"phonenumberslite": {
"hashes": [
"sha256:44cbb13581122164cd8a83b40f12db854277e8a5f9c6e22bd8dc2d8aa98e3260",
"sha256:469eb263160e243aa02fff643502698f99e77bcb6478e9aaa7115838006be122"
],
"version": "==8.13.4"
},
"psycopg2-binary": {
"hashes": [
@ -581,7 +599,7 @@
"sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30",
"sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"
],
"markers": "python_full_version >= '3.6.0'",
"markers": "python_version >= '3.6'",
"version": "==4.11.1"
},
"black": {
@ -925,7 +943,7 @@
"sha256:7500c9625927c8ec60f54377d590f67b30c8e70ef4b8894214ac6e4cad233d2a",
"sha256:780a4082c5fbc0fde6a2fcfe5e26e6efc1e8f425730863c04085769781f51eba"
],
"markers": "python_full_version >= '3.7.0'",
"markers": "python_version >= '3.7'",
"version": "==2.1.2"
},
"webob": {

View file

@ -100,3 +100,9 @@ footer {
//Workaround because USWDS units jump from 10 to 15
margin-top: units(10) + units(2);
}
abbr[title] {
// workaround for underlining abbr element
border-bottom: none;
text-decoration: none;
}

View file

@ -90,6 +90,8 @@ INSTALLED_APPS = [
"widget_tweaks",
# library for Finite State Machine statuses
"django_fsm",
# library for phone numbers
"phonenumber_field",
# let's be sure to install our own application!
"registrar",
# Our internal API application
@ -181,6 +183,8 @@ TEMPLATES = [
},
]
MESSAGE_STORAGE = "django.contrib.messages.storage.session.SessionStorage"
# IS_DEMO_SITE controls whether or not we show our big red "TEST SITE" banner
# underneath the "this is a real government website" banner.
IS_DEMO_SITE = True
@ -296,6 +300,9 @@ USE_L10N = True
# make datetimes timezone-aware by default
USE_TZ = True
# setting for phonenumber library
PHONENUMBER_DEFAULT_REGION = "US"
# endregion
# region: Logging-----------------------------------------------------------###

View file

@ -2,11 +2,22 @@ from __future__ import annotations # allows forward references in annotations
import logging
from django import forms
from django.core.validators import RegexValidator
from django.utils.safestring import mark_safe
from phonenumber_field.formfields import PhoneNumberField # type: ignore
from registrar.models import Contact, DomainApplication, Domain
logger = logging.getLogger(__name__)
# no sec because this use of mark_safe does not introduce a cross-site scripting
# vulnerability because there is no untrusted content inside. It is
# only being used to pass a specific HTML entity into a template.
REQUIRED_SUFFIX = mark_safe( # nosec
' <abbr class="usa-hint usa-hint--required" title="required">*</abbr>'
)
class RegistrarForm(forms.Form):
"""
@ -20,6 +31,8 @@ class RegistrarForm(forms.Form):
def __init__(self, *args, **kwargs):
kwargs.setdefault("label_suffix", "")
# save a reference to an application object
self.application = kwargs.pop("application", None)
super(RegistrarForm, self).__init__(*args, **kwargs)
def to_database(self, obj: DomainApplication | Contact):
@ -49,6 +62,7 @@ class OrganizationTypeForm(RegistrarForm):
required=True,
choices=DomainApplication.OrganizationChoices.choices,
widget=forms.RadioSelect,
error_messages={"required": "This question is required."},
)
@ -56,46 +70,95 @@ class OrganizationFederalForm(RegistrarForm):
federal_type = forms.ChoiceField(
choices=DomainApplication.BranchChoices.choices,
widget=forms.RadioSelect,
error_messages={"required": "This question is required."},
)
class OrganizationElectionForm(RegistrarForm):
is_election_board = forms.BooleanField(
is_election_board = forms.NullBooleanField(
widget=forms.RadioSelect(
choices=[
(True, "Yes"),
(False, "No"),
],
),
required=False, # use field validation to require an answer
)
def clean_is_election_board(self):
"""This box must be checked to proceed but offer a clear error."""
# already converted to a boolean
is_election_board = self.cleaned_data["is_election_board"]
if is_election_board is None:
raise forms.ValidationError(
"Please select Yes or No.",
code="required",
)
return is_election_board
class OrganizationContactForm(RegistrarForm):
# for federal agencies we also want to know the top-level agency.
federal_agency = forms.ChoiceField(
label="Federal agency",
# not required because this field won't be filled out unless
# it is a federal agency.
# it is a federal agency. Use clean to check programatically
# if it has been filled in when required.
required=False,
choices=DomainApplication.AGENCY_CHOICES,
choices=[("", "--Select--")] + DomainApplication.AGENCY_CHOICES,
label_suffix=REQUIRED_SUFFIX,
)
organization_name = forms.CharField(
label="Organization Name", label_suffix=REQUIRED_SUFFIX
)
address_line1 = forms.CharField(
label="Street address",
label_suffix=REQUIRED_SUFFIX,
)
organization_name = forms.CharField(label="Organization Name")
address_line1 = forms.CharField(label="Street address")
address_line2 = forms.CharField(
required=False,
label="Street address line 2",
)
city = forms.CharField(label="City")
city = forms.CharField(label="City", label_suffix=REQUIRED_SUFFIX)
state_territory = forms.ChoiceField(
label="State, territory, or military post",
choices=[("", "--Select--")] + DomainApplication.StateTerritoryChoices.choices,
label_suffix=REQUIRED_SUFFIX,
)
zipcode = forms.CharField(
label="ZIP code",
label_suffix=REQUIRED_SUFFIX,
validators=[
RegexValidator(
"^[0-9]{5}(?:-[0-9]{4})?$|^$",
message="Please enter a ZIP code in the form 12345 or 12345-6789",
)
],
)
zipcode = forms.CharField(label="ZIP code")
urbanization = forms.CharField(
required=False,
label="Urbanization (Puerto Rico only)",
)
def clean_federal_agency(self):
"""Require something to be selected when this is a federal agency."""
federal_agency = self.cleaned_data.get("federal_agency", None)
# need the application object to know if this is federal
if self.application is None:
# hmm, no saved application object?, default require the agency
if not federal_agency:
# no answer was selected
raise forms.ValidationError(
"Please select your federal agency.", code="required"
)
if self.application.is_federal:
if not federal_agency:
# no answer was selected
raise forms.ValidationError(
"Please select your federal agency.", code="required"
)
return federal_agency
class AuthorizingOfficialForm(RegistrarForm):
def to_database(self, obj):
@ -115,15 +178,31 @@ class AuthorizingOfficialForm(RegistrarForm):
contact = getattr(obj, "authorizing_official", None)
return super().from_database(contact)
first_name = forms.CharField(label="First name/given name")
first_name = forms.CharField(
label="First name/given name",
label_suffix=REQUIRED_SUFFIX,
)
middle_name = forms.CharField(
required=False,
label="Middle name (optional)",
label="Middle name",
)
last_name = forms.CharField(
label="Last name/family name",
label_suffix=REQUIRED_SUFFIX,
)
title = forms.CharField(
label="Title or role in your organization",
label_suffix=REQUIRED_SUFFIX,
)
email = forms.EmailField(
label="Email",
label_suffix=REQUIRED_SUFFIX,
error_messages={"invalid": "Please enter a valid email address."},
)
phone = PhoneNumberField(
label="Phone",
label_suffix=REQUIRED_SUFFIX,
)
last_name = forms.CharField(label="Last name/family name")
title = forms.CharField(label="Title or role in your organization")
email = forms.EmailField(label="Email")
phone = forms.CharField(label="Phone")
class CurrentSitesForm(RegistrarForm):
@ -150,6 +229,27 @@ class CurrentSitesForm(RegistrarForm):
"www.city.com.",
)
def clean_current_site(self):
"""This field should be a legal domain name."""
inputted_site = self.cleaned_data["current_site"]
if not inputted_site:
# empty string is fine
return inputted_site
# something has been inputted
if inputted_site.startswith("http://") or inputted_site.startswith("https://"):
# strip of the protocol that the pasted from their web browser
inputted_site = inputted_site.split("//", 1)[1]
if Domain.string_could_be_domain(inputted_site):
return inputted_site
else:
# string could not be a domain
raise forms.ValidationError(
"Please enter a valid domain name", code="invalid"
)
class DotGovDomainForm(RegistrarForm):
def to_database(self, obj):
@ -189,16 +289,51 @@ class DotGovDomainForm(RegistrarForm):
return values
requested_domain = forms.CharField(label="What .gov domain do you want?")
requested_domain = forms.CharField(
label="What .gov domain do you want?",
)
alternative_domain = forms.CharField(
required=False,
label="Are there other domains youd like if we cant give you your first "
"choice? Entering alternative domains is optional.",
)
def clean_requested_domain(self):
"""Requested domains need to be legal top-level domains, not subdomains.
If they end with `.gov`, then we can reasonably take that off. If they have
any other dots in them, raise an error.
"""
requested = self.cleaned_data["requested_domain"]
if not requested:
# none or empty string
raise forms.ValidationError(
"Please enter the .gov domain that you are requesting.", code="invalid"
)
if requested.endswith(".gov"):
requested = requested[:-4]
if "." in requested:
raise forms.ValidationError(
"Please enter a domain without any periods.",
code="invalid",
)
if not Domain.string_could_be_domain(requested + ".gov"):
raise forms.ValidationError(
"Please enter a valid domain name using only letters, "
"numbers, and hyphens",
code="invalid",
)
return requested
class PurposeForm(RegistrarForm):
purpose = forms.CharField(label="Purpose", widget=forms.Textarea())
purpose = forms.CharField(
label="Purpose",
widget=forms.Textarea(),
error_messages={
"required": "Please enter some information about the purpose of your domain"
},
)
class YourContactForm(RegistrarForm):
@ -219,15 +354,31 @@ class YourContactForm(RegistrarForm):
contact = getattr(obj, "submitter", None)
return super().from_database(contact)
first_name = forms.CharField(label="First name/given name")
first_name = forms.CharField(
label="First name/given name",
label_suffix=REQUIRED_SUFFIX,
)
middle_name = forms.CharField(
required=False,
label="Middle name (optional)",
label="Middle name",
)
last_name = forms.CharField(
label="Last name/family name",
label_suffix=REQUIRED_SUFFIX,
)
title = forms.CharField(
label="Title or role in your organization",
label_suffix=REQUIRED_SUFFIX,
)
email = forms.EmailField(
label="Email",
label_suffix=REQUIRED_SUFFIX,
error_messages={"invalid": "Please enter a valid email address."},
)
phone = PhoneNumberField(
label="Phone",
label_suffix=REQUIRED_SUFFIX,
)
last_name = forms.CharField(label="Last name/family name")
title = forms.CharField(label="Title or role in your organization")
email = forms.EmailField(label="Email")
phone = forms.CharField(label="Phone")
class OtherContactsForm(RegistrarForm):
@ -250,21 +401,38 @@ class OtherContactsForm(RegistrarForm):
other_contacts = obj.other_contacts.first()
return super().from_database(other_contacts)
first_name = forms.CharField(label="First name/given name")
first_name = forms.CharField(
label="First name/given name",
label_suffix=REQUIRED_SUFFIX,
)
middle_name = forms.CharField(
required=False,
label="Middle name (optional)",
label="Middle name",
)
last_name = forms.CharField(
label="Last name/family name",
label_suffix=REQUIRED_SUFFIX,
)
title = forms.CharField(
label="Title or role in your organization",
label_suffix=REQUIRED_SUFFIX,
)
email = forms.EmailField(
label="Email",
label_suffix=REQUIRED_SUFFIX,
error_messages={"invalid": "Please enter a valid email address."},
)
phone = PhoneNumberField(
label="Phone",
label_suffix=REQUIRED_SUFFIX,
)
last_name = forms.CharField(label="Last name/family name")
title = forms.CharField(label="Title or role in your organization")
email = forms.EmailField(label="Email")
phone = forms.CharField(label="Phone")
class SecurityEmailForm(RegistrarForm):
security_email = forms.EmailField(
required=False,
label="Security email",
error_messages={"invalid": "Please enter a valid email address."},
)
@ -281,5 +449,17 @@ class RequirementsForm(RegistrarForm):
label=(
"I read and agree to the requirements for registering "
"and operating .gov domains."
)
),
required=False, # use field validation to enforce this
)
def clean_is_policy_acknowledged(self):
"""This box must be checked to proceed but offer a clear error."""
# already converted to a boolean
is_acknowledged = self.cleaned_data["is_policy_acknowledged"]
if not is_acknowledged:
raise forms.ValidationError(
"You must read and agree to the .gov domain requirements to proceed.",
code="invalid",
)
return is_acknowledged

View file

@ -0,0 +1,26 @@
# Generated by Django 4.1.4 on 2022-12-14 20:48
from django.db import migrations
import phonenumber_field.modelfields # type: ignore
class Migration(migrations.Migration):
dependencies = [
("registrar", "0005_domainapplication_city_and_more"),
]
operations = [
migrations.AlterField(
model_name="contact",
name="phone",
field=phonenumber_field.modelfields.PhoneNumberField(
blank=True,
db_index=True,
help_text="Phone",
max_length=128,
null=True,
region=None,
),
),
]

View file

@ -1,5 +1,7 @@
from django.db import models
from phonenumber_field.modelfields import PhoneNumberField # type: ignore
class Contact(models.Model):
@ -33,7 +35,7 @@ class Contact(models.Model):
help_text="Email",
db_index=True,
)
phone = models.TextField(
phone = PhoneNumberField(
null=True,
blank=True,
help_text="Phone",

View file

@ -2,6 +2,7 @@
{% extends 'application_form.html' %}
{% load widget_tweaks %}
{% load static %}
{% load field_helpers %}
{% block form_content %}
@ -18,32 +19,27 @@
<p>Well contact your authorizing official to let them know that you made this request and to double check that they approve it.</p>
</div>
<p>All fields are required unless they are marked optional.</p>
{% include "includes/required_fields.html" %}
<form class="usa-form usa-form--large" id="step__{{steps.current}}" method="post">
<form class="usa-form usa-form--large" id="step__{{steps.current}}" method="post" novalidate>
{% csrf_token %}
<fieldset class="usa-fieldset">
<legend class="usa-sr-only">
Who is the authorizing official for your organization?
</legend>
{{ forms.0.first_name|add_label_class:"usa-label" }}
{{ forms.0.first_name|add_class:"usa-input"}}
{{ forms.0.middle_name|add_label_class:"usa-label" }}
{{ forms.0.middle_name|add_class:"usa-input"}}
{% input_with_errors forms.0.first_name %}
{{ forms.0.last_name|add_label_class:"usa-label" }}
{{ forms.0.last_name|add_class:"usa-input"}}
{{ forms.0.title|add_label_class:"usa-label" }}
{{ forms.0.title|add_class:"usa-input"}}
{% input_with_errors forms.0.middle_name %}
{{ forms.0.email|add_label_class:"usa-label" }}
{{ forms.0.email|add_class:"usa-input"}}
{% input_with_errors forms.0.last_name %}
{{ forms.0.phone|add_label_class:"usa-label" }}
{{ forms.0.phone|add_class:"usa-input usa-input--medium" }}
{% input_with_errors forms.0.title %}
{% input_with_errors forms.0.email %}
{% input_with_errors forms.0.phone add_class="usa-input--medium" %}
</fieldset>

View file

@ -1,6 +1,6 @@
<!-- Test page -->
{% extends 'application_form.html' %}
{% load widget_tweaks %}
{% load widget_tweaks field_helpers %}
{% load static %}
{% block form_content %}
@ -8,8 +8,7 @@
<form class="usa-form usa-form--large" id="step__{{steps.current}}" method="post">
{% csrf_token %}
{{ forms.0.current_site|add_label_class:"usa-label" }}
{{ forms.0.current_site|add_class:"usa-input" }}
{% input_with_errors forms.0.current_site %}
{{ block.super }}

View file

@ -21,28 +21,60 @@
{% include "includes/domain_example__city.html" %}
</div>
<form id="step__{{steps.current}}" class="usa-form usa-form--large" method="post">
<form id="step__{{steps.current}}" class="usa-form usa-form--large" method="post" novalidate>
<h2> What .gov domain do you want? </h2>
<p class="domain_instructions"> After you enter your domain, well make sure its available and that it meets some of our naming requirements. If your domain passes these initial checks, well verify that it meets all of our requirements once you complete and submit the rest of this form. </p>
<p> This question is required. </p>
{% csrf_token %}
{{ forms.0.requested_domain|add_label_class:"usa-label" }}
<div class="display-flex flex-align-center">
<span class="padding-top-05 padding-right-2px">www.</span>
{{ forms.0.requested_domain|add_class:"usa-input"|attr:"aria-describedby:domain_instructions" }}
<span class="padding-top-05 padding-left-2px">.gov </span>
</div>
{% if forms.0.requested_domain.errors %}
<div class="usa-form-group usa-form-group--error">
{% for error in forms.0.requested_domain.errors %}
<span class="usa-error-message" id="input-error-message" role="alert">
{{ error }}
</span>
{% endfor %}
<div class="display-flex flex-align-center">
<span class="padding-top-05 padding-right-2px">www.</span>
{{ forms.0.requested_domain|add_class:"usa-input usa-input--error"|attr:"aria-describedby:domain_instructions"|attr:"aria-invalid:true" }}
<span class="padding-top-05 padding-left-2px">.gov </span>
</div>
</div>
{% else %}
<div class="display-flex flex-align-center">
<span class="padding-top-05 padding-right-2px">www.</span>
{{ forms.0.requested_domain|add_class:"usa-input"|attr:"aria-describedby:domain_instructions" }}
<span class="padding-top-05 padding-left-2px">.gov </span>
</div>
{% endif %}
<button type="button" class="usa-button">Check availability </button>
<h2>Alternative domains</h2>
<div>
{% if forms.0.alternative_domain.errors %}
<div class="usa-form-group usa-form-group--error">
{{ forms.0.alternative_domain|add_label_class:"usa-label usa-label--error" }}
{% for error in forms.0.alternative_domain.errors %}
<span class="usa-error-message" id="input-error-message" role="alert">
{{ error }}
</span>
{% endfor %}
<div class="display-flex flex-align-center">
<span class="padding-top-05 padding-right-2px">www.</span>
{ forms.0.alternative_domain|add_class:"usa-input usa-input--error"|attr:"aria-describedby:domain_instructions"|attr:"aria-invalid:true" }}
<span class="padding-top-05 padding-left-2px">.gov </span>
</div>
</div>
{% else %}
{{ forms.0.alternative_domain|add_label_class:"usa-label" }}
<div class="display-flex flex-align-center">
<span class="padding-top-05 padding-right-2px">www.</span>
{{ forms.0.alternative_domain|add_class:"usa-input" }}
{{ forms.0.alternative_domain|add_class:"usa-input"|attr:"aria-describedby:domain_instructions" }}
<span class="padding-top-05 padding-left-2px">.gov </span>
</div>
{% endif %}
<button type="button" class="usa-button usa-button--unstyled">
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img">
@ -54,7 +86,7 @@
<p>If youre not sure this is the domain you want, thats okay. You can change it later.</p>
{{ block.super }}
</form>
{% endblock %}

View file

@ -17,8 +17,31 @@
</svg><span class="margin-left-05">Previous step </span>
</a>
{% endif %}
{% for form in forms %}
{% if form.errors %}
{% for error in form.non_field_errors %}
<div class="usa-alert usa-alert--error usa-alert--slim margin-bottom-2">
<div class="usa-alert__body">
{{ error|escape }}
</div>
</div>
{% endfor %}
{% for field in form %}
{% for error in field.errors %}
<div class="usa-alert usa-alert--error usa-alert--slim margin-bottom-2">
<div class="usa-alert__body">
{{ error|escape }}
</div>
</div>
{% endfor %}
{% endfor %}
{% endif %}
{% endfor %}
<h1> {{form_titles|get_item:steps.current}} </h1>
{% block form_content %}
<div class="stepnav">
{% if steps.next %}
<button

View file

@ -1,6 +1,6 @@
<!-- Test page -->
{% extends 'application_form.html' %}
{% load widget_tweaks %}
{% load widget_tweaks field_helpers %}
{% block form_content %}
@ -13,39 +13,37 @@
<p>If your domain request is approved, the name of your organization will be publicly listed as the domain registrant. </p>
<p>All fields are required unless they are marked optional.</p>
{% include "includes/required_fields.html" %}
</div>
<form id="step__{{steps.current}}" class="usa-form usa-form--large" method="post">
<form id="step__{{steps.current}}" class="usa-form usa-form--large" method="post" novalidate>
{% csrf_token %}
<fieldset class="usa-fieldset">
<legend class="usa-sr-only">What is the name and mailing address of your organization?</legend>
{% if is_federal %}
{{ forms.0.federal_agency|add_label_class:"usa-label" }}
{{ forms.0.federal_agency|add_class:"usa-select" }}
{% select_with_errors forms.0.federal_agency required=True %}
{% endif %}
{{ forms.0.organization_name|add_label_class:"usa-label" }}
{{ forms.0.organization_name|add_class:"usa-input" }}
{{ forms.0.address_line1|add_label_class:"usa-label" }}
{{ forms.0.address_line1|add_class:"usa-input" }}
{{ forms.0.address_line2|add_label_class:"usa-label" }}
{{ forms.0.address_line2|add_class:"usa-input" }}
{{ forms.0.city|add_label_class:"usa-label" }}
{{ forms.0.city|add_class:"usa-input" }}
{{ forms.0.state_territory|add_label_class:"usa-label" }}
{{ forms.0.state_territory|add_class:"usa-select" }}
{{ forms.0.zipcode|add_label_class:"usa-label" }}
{{ forms.0.zipcode|add_class:"usa-input usa-input--small" }}
{{ forms.0.urbanization|add_label_class:"usa-label" }}
{{ forms.0.urbanization|add_class:"usa-input usa-input--small" }}
{% input_with_errors forms.0.organization_name %}
{% input_with_errors forms.0.address_line1 %}
{% input_with_errors forms.0.address_line2 %}
{% input_with_errors forms.0.city %}
{% select_with_errors forms.0.state_territory %}
{% input_with_errors forms.0.zipcode add_class="usa-input--small" %}
{% input_with_errors forms.0.urbanization %}
</fieldset>
{{ block.super }}
</form>
{% endblock %}

View file

@ -5,15 +5,16 @@
{% block form_content %}
<form id="step__{{steps.current}}" class="usa-form usa-form--large" method="post">
<form id="step__{{steps.current}}" class="usa-form usa-form--large" method="post" novalidate>
{% csrf_token %}
<fieldset id="election_board__fieldset" class="usa-fieldset">
<legend>
<h2 class="margin-bottom-05">Is your organization an election office?</h2>
<p> This question is required.</p>
</legend>
{% radio_buttons_by_value forms.0.is_election_board as choices %}
{% for choice in choices.values %}
{% include "includes/radio_button.html" with choice=choice tile="true" %}
{% include "includes/radio_button.html" with choice=choice tile="true" required="true"%}
{% endfor %}
</fieldset>

View file

@ -5,11 +5,12 @@
{% block form_content %}
<form id="step__{{steps.current}}" class="usa-form usa-form--large" method="post">
<form id="step__{{steps.current}}" class="usa-form usa-form--large" method="post" novalidate>
{% csrf_token %}
<fieldset id="federal_type__fieldset" class="usa-fieldset">
<legend>
<h2 class="margin-bottom-5">Which federal branch is your organization in?</h2>
<h2>Which federal branch is your organization in?</h2>
<p class="margin-bottom-5">This question is required.</p>
</legend>
{% radio_buttons_by_value forms.0.federal_type as choices %}
{% for choice in choices.values %}

View file

@ -5,14 +5,15 @@
{% block form_content %}
<form id="step__{{steps.current}}" class="usa-form usa-form--large" method="post">
<form id="step__{{steps.current}}" class="usa-form usa-form--large" method="post" novalidate>
{% csrf_token %}
{% radio_buttons_by_value forms.0.organization_type as choices %}
<fieldset id="organization_type__fieldset" class="usa-fieldset">
<legend class="usa-legend">
<h2> What kind of U.S.-based government organization do you represent?</h2>
<h2> What kind of U.S.-based government organization do you represent? </h2>
<p>This question is required.</p>
</legend>
{{ forms.0.organization_type.errors }}
{% for choice in choices.values %}
@ -20,7 +21,7 @@
{% endfor %}
</fieldset>
{{ block.super }}
{{ block.super }}
</form>
{% endblock %}

View file

@ -2,36 +2,31 @@
{% extends 'application_form.html' %}
{% load widget_tweaks %}
{% load static %}
{% load field_helpers %}
{% block form_content %}
<p id="instructions">Wed like to contact other employees with administrative or technical responsibilities in your organization. For example, they could be involved in managing your organization or its technical infrastructure. This information will help us assess your eligibility and understand the purpose of the .gov domain. These contacts should be in addition to you and your authorizing official. </p>
<p>All fields are required unless they are marked optional.</p>
{% include "includes/required_fields.html" %}
<form class="usa-form usa-form--large" id="step__{{steps.current}}" method="post">
<form class="usa-form usa-form--large" id="step__{{steps.current}}" method="post" novalidate>
{% csrf_token %}
<fieldset class="usa-fieldset">
<legend>
<h2 class="margin-bottom-05"> Contact 2 </h2>
</legend>
{{ forms.0.first_name|add_label_class:"usa-label" }}
{{ forms.0.first_name|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
{% input_with_errors forms.0.first_name %}
{{ forms.0.middle_name|add_label_class:"usa-label" }}
{{ forms.0.middle_name|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
{% input_with_errors forms.0.middle_name %}
{{ forms.0.last_name|add_label_class:"usa-label" }}
{{ forms.0.last_name|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
{{ forms.0.title|add_label_class:"usa-label" }}
{{ forms.0.title|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
{% input_with_errors forms.0.last_name %}
{{ forms.0.email|add_label_class:"usa-label" }}
{{ forms.0.email|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
{% input_with_errors forms.0.title %}
{{ forms.0.phone|add_label_class:"usa-label" }}
{{ forms.0.phone|add_class:"usa-input usa-input--medium"|attr:"aria-describedby:instructions" }}
{% input_with_errors forms.0.email %}
{% input_with_errors forms.0.phone add_class="usa-input--medium" %}
</fieldset>
<div>

View file

@ -5,16 +5,32 @@
{% block form_content %}
<p id="instructions">.Gov domain names are intended for use on the internet. They should be registered with an intent to deploy services, not simply to reserve a name. .Gov domains should not be registered for primarily internal use.</p>
<p id="instructions">Describe the reason for your domain request. Explain how you plan to use this domain. Will you use it for a website and/or email? Are you moving your website from another top-level domain (like .com or .org)? Read about <a href="{% url 'todo' %}">activities that are prohibited on .gov domains.</a></p>
<form id="step__{{steps.current}}" class="usa-form usa-form--large" method="post">
<p id="instructions">Describe the reason for your domain request. Explain how you plan to use this domain. Will you use it for a website and/or email? Are you moving your website from another top-level domain (like .com or .org)? Read about <a href="#">activities that are prohibited on .gov domains.</a></p>
<p> This question is required. </p>
<form id="step__{{steps.current}}" class="usa-form usa-form--large" method="post" novalidate>
<div class="usa-form-group">
{% csrf_token %}
<div class="usa-character-count">
{{ forms.0.purpose|add_label_class:"usa-label usa-sr-only" }}
{{ forms.0.purpose|add_class:"usa-textarea usa-character-count__field"|attr:"aria-describedby:instructions"|attr:"maxlength=500" }}
{% with field=forms.0.purpose %}
{% if field.errors %}
<div class="usa-form-group usa-form-group--error">
{{ field|add_label_class:"usa-label usa-label--error usa-sr-only" }}
{% for error in field.errors %}
<span class="usa-error-message" id="input-error-message" role="alert">
{{ error }}
</span>
{% endfor %}
{{ field|add_class:"usa-input--error usa-textarea usa-character-count__field"|attr:"aria-describedby:instructions"|attr:"maxlength=500"|attr:"aria-invalid:true" }}
</div>
{% else %}
{{ field|add_label_class:"usa-label usa-sr-only" }}
{{ field|add_class:"usa-textarea usa-character-count__field"|attr:"aria-describedby:instructions"|attr:"maxlength=500" }}
{% endif %}
{% endwith %}
<span class="usa-character-count__message" id="with-hint-textarea-info with-hint-textarea-hint"> You can enter up to 500 characters </span>
</div>
</div>

View file

@ -127,14 +127,30 @@
<h2>Acknowledgement of .gov domain requirements</h2>
<form id="step__{{steps.current}}" class="usa-form usa-form--large" method="post">
<p>This question is required.</p>
<form id="step__{{steps.current}}" class="usa-form usa-form--large" method="post" novalidate>
<div class="usa-form-group">
{% csrf_token %}
<div class="usa-checkbox">
{{ forms.0.is_policy_acknowledged|add_class:"usa-checkbox__input"}}
{{ forms.0.is_policy_acknowledged|add_label_class:"usa-checkbox__label" }}
</div>
{% if forms.0.is_policy_acknowledged.errors %}
<div class="usa-form-group usa-form-group--error">
{% for error in forms.0.is_policy_acknowledged.errors %}
<span class="usa-error-message margin-bottom-1" id="input-error-message" role="alert">
{{ error }}
</span>
{% endfor %}
<div class="usa-checkbox">
{{ forms.0.is_policy_acknowledged|add_class:"usa-checkbox__input"|add_class:"usa-input--error"|attr:"aria-invalid:true"|attr:"required" }}
{{ forms.0.is_policy_acknowledged|add_label_class:"usa-checkbox__label usa-label--error" }}
</div>
</div>
{% else %}
<div class="usa-checkbox">
{{ forms.0.is_policy_acknowledged|add_class:"usa-checkbox__input"|attr:"required"}}
{{ forms.0.is_policy_acknowledged|add_label_class:"usa-checkbox__label" }}
</div>
{% endif %}
</div>
{{ block.super }}

View file

@ -7,13 +7,24 @@
<p id="instructions"> We strongly recommend that you provide a security email. This email will allow the public to report observed or suspected security issues on your domain. <strong> Security emails are made public.</strong> We recommend using an alias, like security@&lt;domain.gov&gt;.</p>
<form class="usa-form usa-form--large" id="step__{{steps.current}}" method="post">
<form class="usa-form usa-form--large" id="step__{{steps.current}}" method="post" novalidate>
{% csrf_token %}
{{ forms.0.security_email|add_label_class:"usa-label" }}
{{ forms.0.security_email|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
{{ block.super }}
{% if forms.0.security_email.errors %}
<div class="usa-form-group usa-form-group--error">
{{ forms.0.security_email|add_label_class:"usa-label usa-label--error" }}
{% for error in forms.0.security_email.errors %}
<span class="usa-error-message" id="input-error-message" role="alert">
{{ error }}
</span>
{% endfor %}
{{ forms.0.security_email|add_class:"usa-input"|add_class:"usa-input--error"|attr:"aria-describedby:instructions"|attr:"aria-invalid:true" }}
</div>
{% else %}
{{ forms.0.security_email|add_label_class:"usa-label" }}
{{ forms.0.security_email|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
{% endif %}
{{ block.super }}
</form>

View file

@ -2,6 +2,7 @@
{% extends 'application_form.html' %}
{% load widget_tweaks %}
{% load static %}
{% load field_helpers %}
{% block form_content %}
@ -13,32 +14,26 @@
<p>The contact information you provide here wont be public and will only be used for the .gov registry.</p>
<div>
<p>All fields are required unless they are marked optional.</p>
{% include "includes/required_fields.html" %}
<form class="usa-form usa-form--large" id="step__{{steps.current}}" method="post">
<form class="usa-form usa-form--large" id="step__{{steps.current}}" method="post" novalidate>
{% csrf_token %}
<fieldset class="usa-fieldset">
<legend class="usa-sr-only">
Your contact information
</legend>
{{ forms.0.first_name|add_label_class:"usa-label" }}
{{ forms.0.first_name|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
{% input_with_errors forms.0.first_name %}
{{ forms.0.middle_name|add_label_class:"usa-label" }}
{{ forms.0.middle_name|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
{% input_with_errors forms.0.middle_name %}
{{ forms.0.last_name|add_label_class:"usa-label" }}
{{ forms.0.last_name|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
{{ forms.0.title|add_label_class:"usa-label" }}
{{ forms.0.title|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
{% input_with_errors forms.0.last_name %}
{{ forms.0.email|add_label_class:"usa-label" }}
{{ forms.0.email|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
{% input_with_errors forms.0.title %}
{{ forms.0.phone|add_label_class:"usa-label" }}
{{ forms.0.phone|add_class:"usa-input usa-input--medium"|attr:"aria-describedby:instructions" }}
{% input_with_errors forms.0.email %}
{% input_with_errors forms.0.phone add_class="usa-input--medium" %}
</fieldset>

View file

@ -2,5 +2,5 @@
{{ contact.get_formatted_name }}<br />
{% if contact.title %}{{ contact.title }}<br />{% endif %}
{% if contact.email %}{{ contact.email }}<br />{% endif %}
{% if contact.phone %}{{ contact.phone }}{% endif %}
</address>
{% if contact.phone %}{{ contact.phone.as_national }}{% endif %}
</address>

View file

@ -0,0 +1,29 @@
{% comment %}
Template include for form fields with classes and their corresponding
error messages, if necessary.
{% endcomment %}
{% load widget_tweaks %}
{% if field.errors %}
<div class="usa-form-group usa-form-group--error">
{{ field|add_label_class:"usa-label usa-label--error" }}
{% for error in field.errors %}
<span class="usa-error-message" id="input-error-message" role="alert">
{{ error }}
</span>
{% endfor %}
{% if required %}
{{ field|add_class:input_class|add_class:"usa-input--error"|attr:"aria-invalid:true"|attr:"required" }}
{% else %}
{{ field|add_class:input_class|add_class:"usa-input--error"|attr:"aria-invalid:true" }}
{% endif %}
</div>
{% else %}
{{ field|add_label_class:"usa-label" }}
{% if required %}
{{ field|add_class:input_class|attr:"required" }}
{% else %}
{{ field|add_class:input_class }}
{% endif %}
{% endif %}

View file

@ -5,7 +5,7 @@
value="{{ choice.data.value }}"
class="usa-radio__input {% if tile %}usa-radio__input--tile {%endif%}"
id="{{ choice.id_for_label }}"
{% if choice.data.attrs.required %}required{% endif %}
{% if choice.data.attrs.required or required %}required{% endif %}
{% if choice.data.selected %}checked{% endif %}
/>
<label class="usa-radio__label" for="{{ choice.id_for_label }}" >

View file

@ -0,0 +1,3 @@
<p>
Required fields are marked with an asterisk (<abbr class="usa-hint usa-hint--required" title="required">*</abbr>).
</p>

View file

@ -0,0 +1,40 @@
"""Custom field helpers for our inputs."""
from django import template
register = template.Library()
def _field_context(field, input_class, add_class, required=False):
"""Helper to construct template context.
input_class is the CSS class to use on the input element, add_class
will be added to that if given, and required will be set if
it is provided and not False.
"""
if add_class:
input_class += " " + add_class
context = {"field": field, "input_class": input_class}
if required:
context["required"] = True
return context
@register.inclusion_tag("includes/input_with_errors.html")
def input_with_errors(field, add_class=None):
"""Make an input field along with error handling.
field is a form field instance. add_class is a string of additional
classes (space separated) to add to "usa-input" on the <input> field.
"""
return _field_context(field, "usa-input", add_class)
@register.inclusion_tag("includes/input_with_errors.html")
def select_with_errors(field, add_class=None, required=False):
"""Make a select field along with error handling.
field is a form field instance. add_class is a string of additional
classes (space separated) to add to "usa-select" on the field.
"""
return _field_context(field, "usa-select", add_class, required)

View file

@ -0,0 +1,138 @@
"""Test form validation requirements."""
from django.test import TestCase
from registrar.forms.application_wizard import (
CurrentSitesForm,
DotGovDomainForm,
AuthorizingOfficialForm,
OrganizationContactForm,
YourContactForm,
OtherContactsForm,
SecurityEmailForm,
RequirementsForm,
)
class TestFormValidation(TestCase):
def test_org_contact_zip_invalid(self):
form = OrganizationContactForm(data={"zipcode": "nah"})
self.assertEqual(
form.errors["zipcode"],
["Please enter a ZIP code in the form 12345 or 12345-6789"],
)
def test_org_contact_zip_valid(self):
for zipcode in ["12345", "12345-6789"]:
form = OrganizationContactForm(data={"zipcode": zipcode})
self.assertNotIn("zipcode", form.errors)
def test_current_site_invalid(self):
form = CurrentSitesForm(data={"current_site": "nah"})
self.assertEqual(
form.errors["current_site"], ["Please enter a valid domain name"]
)
def test_current_site_valid(self):
form = CurrentSitesForm(data={"current_site": "hyphens-rule.gov.uk"})
self.assertEqual(len(form.errors), 0)
def test_current_site_scheme_valid(self):
form = CurrentSitesForm(data={"current_site": "http://hyphens-rule.gov.uk"})
self.assertEqual(len(form.errors), 0)
form = CurrentSitesForm(data={"current_site": "https://hyphens-rule.gov.uk"})
self.assertEqual(len(form.errors), 0)
def test_requested_domain_valid(self):
"""Just a valid domain name with no .gov at the end."""
form = DotGovDomainForm(data={"requested_domain": "top-level-agency"})
self.assertEqual(len(form.errors), 0)
def test_requested_domain_ending_dotgov(self):
"""Just a valid domain name with .gov at the end."""
form = DotGovDomainForm(data={"requested_domain": "top-level-agency.gov"})
self.assertEqual(len(form.errors), 0)
self.assertEqual(form.cleaned_data["requested_domain"], "top-level-agency")
def test_requested_domain_ending_dotcom_invalid(self):
"""don't accept domains ending other than .gov."""
form = DotGovDomainForm(data={"requested_domain": "top-level-agency.com"})
self.assertEqual(
form.errors["requested_domain"],
["Please enter a domain without any periods."],
)
def test_requested_domain_invalid_characters(self):
"""must be a valid .gov domain name."""
form = DotGovDomainForm(data={"requested_domain": "underscores_forever"})
self.assertEqual(
form.errors["requested_domain"],
[
"Please enter a valid domain name using only letters, "
"numbers, and hyphens"
],
)
def test_authorizing_official_email_invalid(self):
"""must be a valid email address."""
form = AuthorizingOfficialForm(data={"email": "boss@boss"})
self.assertEqual(form.errors["email"], ["Please enter a valid email address."])
def test_authorizing_official_phone_invalid(self):
"""Must be a valid phone number."""
form = AuthorizingOfficialForm(data={"phone": "boss@boss"})
self.assertTrue(
form.errors["phone"][0].startswith("Enter a valid phone number")
)
def test_your_contact_email_invalid(self):
"""must be a valid email address."""
form = YourContactForm(data={"email": "boss@boss"})
self.assertEqual(form.errors["email"], ["Please enter a valid email address."])
def test_your_contact_phone_invalid(self):
"""Must be a valid phone number."""
form = YourContactForm(data={"phone": "boss@boss"})
self.assertTrue(
form.errors["phone"][0].startswith("Enter a valid phone number")
)
def test_other_contact_email_invalid(self):
"""must be a valid email address."""
form = OtherContactsForm(data={"email": "boss@boss"})
self.assertEqual(form.errors["email"], ["Please enter a valid email address."])
def test_other_contact_phone_invalid(self):
"""Must be a valid phone number."""
form = OtherContactsForm(data={"phone": "boss@boss"})
self.assertTrue(
form.errors["phone"][0].startswith("Enter a valid phone number")
)
def test_security_email_form_blank(self):
"""Can leave the security_email field blank."""
form = SecurityEmailForm(data={})
self.assertEqual(len(form.errors), 0)
def test_security_email_form_invalid(self):
"""Can leave the security_email field blank."""
form = SecurityEmailForm(data={"security_email": "boss@boss"})
self.assertEqual(
form.errors["security_email"], ["Please enter a valid email address."]
)
def test_requirements_form_blank(self):
"""Requirements box unchecked is an error."""
form = RequirementsForm(data={})
self.assertEqual(
form.errors["is_policy_acknowledged"],
["You must read and agree to the .gov domain requirements to proceed."],
)
def test_requirements_form_unchecked(self):
"""Requirements box unchecked is an error."""
form = RequirementsForm(data={"is_policy_acknowledged": False})
self.assertEqual(
form.errors["is_policy_acknowledged"],
["You must read and agree to the .gov domain requirements to proceed."],
)

View file

@ -51,6 +51,8 @@ class TestWithUser(TestCase):
)
def tearDown(self):
# delete any applications too
DomainApplication.objects.all().delete()
self.user.delete()
@ -103,11 +105,6 @@ class DomainApplicationTests(TestWithUser, WebTest):
self.app.set_user(self.user.username)
self.TITLES = ApplicationWizard.TITLES
def tearDown(self):
# delete any applications we made so that users can be deleted
DomainApplication.objects.all().delete()
super().tearDown()
def test_application_form_empty_submit(self):
# 302 redirect to the first form
page = self.app.get(reverse("application:")).follow()
@ -185,6 +182,10 @@ class DomainApplicationTests(TestWithUser, WebTest):
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
org_contact_page = federal_result.follow()
org_contact_form = org_contact_page.form
# federal agency so we have to fill in federal_agency
org_contact_form[
"organization_contact-federal_agency"
] = "General Services Administration"
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"
@ -220,13 +221,14 @@ class DomainApplicationTests(TestWithUser, WebTest):
# ---- 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.form
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"
ao_form["authorizing_official-phone"] = "(555) 555 5555"
ao_form["authorizing_official-phone"] = "(201) 555 5555"
# test saving the page
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
@ -239,7 +241,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
self.assertEquals(application.authorizing_official.last_name, "Tester ATO")
self.assertEquals(application.authorizing_official.title, "Chief Tester")
self.assertEquals(application.authorizing_official.email, "testy@town.com")
self.assertEquals(application.authorizing_official.phone, "(555) 555 5555")
self.assertEquals(application.authorizing_official.phone, "(201) 555 5555")
# test next button
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
@ -251,6 +253,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
# ---- 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.form
current_sites_form["current_sites-current_site"] = "www.city.com"
@ -276,6 +279,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
# ---- 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.form
dotgov_form["dotgov_domain-requested_domain"] = "city"
@ -302,6 +306,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
# ---- 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.form
purpose_form["purpose-purpose"] = "For all kinds of things."
@ -325,6 +330,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
# ---- 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.form
@ -332,7 +338,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
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"] = "(555) 555 5556"
your_contact_form["your_contact-phone"] = "(201) 555 5556"
# test saving the page
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
@ -345,7 +351,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
self.assertEquals(application.submitter.last_name, "Tester you")
self.assertEquals(application.submitter.title, "Admin Tester")
self.assertEquals(application.submitter.email, "testy-admin@town.com")
self.assertEquals(application.submitter.phone, "(555) 555 5556")
self.assertEquals(application.submitter.phone, "(201) 555 5556")
# test next button
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
@ -357,6 +363,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
# ---- 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()
other_contacts_form = other_contacts_page.form
@ -364,7 +371,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
other_contacts_form["other_contacts-last_name"] = "Tester2"
other_contacts_form["other_contacts-title"] = "Another Tester"
other_contacts_form["other_contacts-email"] = "testy2@town.com"
other_contacts_form["other_contacts-phone"] = "(555) 555 5557"
other_contacts_form["other_contacts-phone"] = "(201) 555 5557"
# test saving the page
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
@ -379,7 +386,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
last_name="Tester2",
title="Another Tester",
email="testy2@town.com",
phone="(555) 555 5557",
phone="(201) 555 5557",
).count(),
1,
)
@ -396,6 +403,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
# ---- SECURITY EMAIL PAGE ----
# Follow the redirect to the next form page
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
security_email_page = other_contacts_result.follow()
security_email_form = security_email_page.form
@ -420,6 +428,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
# ---- ANYTHING ELSE PAGE ----
# Follow the redirect to the next form page
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
anything_else_page = security_email_result.follow()
anything_else_form = anything_else_page.form
@ -444,6 +453,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
# ---- REQUIREMENTS PAGE ----
# Follow the redirect to the next form page
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
requirements_page = anything_else_result.follow()
requirements_form = requirements_page.form
@ -486,7 +496,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
self.assertContains(review_page, "Tester ATO")
self.assertContains(review_page, "Chief Tester")
self.assertContains(review_page, "testy@town.com")
self.assertContains(review_page, "(555) 555 5555")
self.assertContains(review_page, "(201) 555-5555")
self.assertContains(review_page, "city.com")
self.assertContains(review_page, "city.gov")
self.assertContains(review_page, "city1.gov")
@ -495,12 +505,12 @@ class DomainApplicationTests(TestWithUser, WebTest):
self.assertContains(review_page, "Tester you")
self.assertContains(review_page, "Admin Tester")
self.assertContains(review_page, "testy-admin@town.com")
self.assertContains(review_page, "(555) 555 5556")
self.assertContains(review_page, "(201) 555-5556")
self.assertContains(review_page, "Testy2")
self.assertContains(review_page, "Tester2")
self.assertContains(review_page, "Another Tester")
self.assertContains(review_page, "testy2@town.com")
self.assertContains(review_page, "(555) 555 5557")
self.assertContains(review_page, "(201) 555-5557")
self.assertContains(review_page, "security@city.com")
self.assertContains(review_page, "Nothing else.")

View file

@ -230,6 +230,7 @@ class ApplicationWizard(LoginRequiredMixin, TemplateView):
kwargs = {
"files": files,
"prefix": self.steps.current,
"application": self.application, # this is a property, not an object
}
if step is None: