mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-05-21 11:59:24 +02:00
Merge pull request #331 from cisagov/nmb/field-validation
Application form field validation
This commit is contained in:
commit
f30b83f904
28 changed files with 704 additions and 159 deletions
|
@ -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
44
src/Pipfile.lock
generated
|
@ -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": {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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-----------------------------------------------------------###
|
||||
|
||||
|
|
|
@ -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 you’d like if we can’t 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
|
||||
|
|
26
src/registrar/migrations/0006_alter_contact_phone.py
Normal file
26
src/registrar/migrations/0006_alter_contact_phone.py
Normal 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,
|
||||
),
|
||||
),
|
||||
]
|
|
@ -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",
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
{% extends 'application_form.html' %}
|
||||
{% load widget_tweaks %}
|
||||
{% load static %}
|
||||
{% load field_helpers %}
|
||||
|
||||
{% block form_content %}
|
||||
|
||||
|
@ -18,32 +19,27 @@
|
|||
<p>We’ll 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>
|
||||
|
||||
|
||||
|
|
|
@ -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 }}
|
||||
|
||||
|
|
|
@ -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, we’ll make sure it’s available and that it meets some of our naming requirements. If your domain passes these initial checks, we’ll 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 you’re not sure this is the domain you want, that’s okay. You can change it later.</p>
|
||||
|
||||
{{ block.super }}
|
||||
|
||||
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -2,36 +2,31 @@
|
|||
{% extends 'application_form.html' %}
|
||||
{% load widget_tweaks %}
|
||||
{% load static %}
|
||||
{% load field_helpers %}
|
||||
|
||||
{% block form_content %}
|
||||
|
||||
<p id="instructions">We’d 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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 }}
|
||||
|
|
|
@ -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@<domain.gov>.</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>
|
||||
|
||||
|
|
|
@ -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 won’t 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>
|
||||
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
29
src/registrar/templates/includes/input_with_errors.html
Normal file
29
src/registrar/templates/includes/input_with_errors.html
Normal 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 %}
|
|
@ -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 }}" >
|
||||
|
|
3
src/registrar/templates/includes/required_fields.html
Normal file
3
src/registrar/templates/includes/required_fields.html
Normal file
|
@ -0,0 +1,3 @@
|
|||
<p>
|
||||
Required fields are marked with an asterisk (<abbr class="usa-hint usa-hint--required" title="required">*</abbr>).
|
||||
</p>
|
40
src/registrar/templatetags/field_helpers.py
Normal file
40
src/registrar/templatetags/field_helpers.py
Normal 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)
|
138
src/registrar/tests/test_forms.py
Normal file
138
src/registrar/tests/test_forms.py
Normal 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."],
|
||||
)
|
|
@ -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.")
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue