Merge pull request #391 from cisagov/sspj/domain-validation

Domain Validation and Form Refactoring
This commit is contained in:
Seamus Johnston 2023-02-06 16:02:09 -06:00 committed by GitHub
commit 2b902be00f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
51 changed files with 917 additions and 932 deletions

View file

@ -2,7 +2,6 @@
import json
from django.core.exceptions import BadRequest
from django.contrib.auth import get_user_model
from django.test import TestCase, RequestFactory
@ -85,8 +84,8 @@ class AvailableViewTest(TestCase):
bad_string = "blah!;"
request = self.factory.get(API_BASE_PATH + bad_string)
request.user = self.user
with self.assertRaisesMessage(BadRequest, "Invalid"):
available(request, domain=bad_string)
response = available(request, domain=bad_string)
self.assertFalse(json.loads(response.content)["available"])
class AvailableAPITest(TestCase):
@ -108,9 +107,3 @@ class AvailableAPITest(TestCase):
with less_console_noise():
response = self.client.post(API_BASE_PATH + "nonsense")
self.assertEqual(response.status_code, 405)
def test_available_bad_input(self):
self.client.force_login(self.user)
with less_console_noise():
response = self.client.get(API_BASE_PATH + "blah!;")
self.assertEqual(response.status_code, 400)

View file

@ -1,7 +1,5 @@
"""Internal API views"""
from django.apps import apps
from django.core.exceptions import BadRequest
from django.views.decorators.http import require_http_methods
from django.http import JsonResponse
@ -17,6 +15,19 @@ DOMAIN_FILE_URL = (
)
DOMAIN_API_MESSAGES = {
"required": "Enter the .gov domain you want. Dont include “www” or “.gov.”"
" For example, if you want www.city.gov, you would enter “city”"
" (without the quotes).",
"extra_dots": "Enter the .gov domain you want without any periods.",
"unavailable": "That domain isnt available. Try entering another one."
" Contact us if you need help coming up with a domain.",
"invalid": "Enter a domain using only letters,"
" numbers, or hyphens (though we don't recommend using hyphens).",
"success": "That domain is available!",
}
# this file doesn't change that often, nor is it that big, so cache the result
# in memory for ten minutes
@ttl_cache(ttl=600)
@ -72,6 +83,15 @@ def available(request, domain=""):
Domain.string_could_be_domain(domain)
or Domain.string_could_be_domain(domain + ".gov")
):
raise BadRequest("Invalid request.")
return JsonResponse(
{"available": False, "message": DOMAIN_API_MESSAGES["invalid"]}
)
# a domain is available if it is NOT in the list of current domains
return JsonResponse({"available": not in_domains(domain)})
if in_domains(domain):
return JsonResponse(
{"available": False, "message": DOMAIN_API_MESSAGES["unavailable"]}
)
else:
return JsonResponse(
{"available": True, "message": DOMAIN_API_MESSAGES["success"]}
)

View file

@ -6,16 +6,13 @@
* Initialization (run-on-load) stuff goes at the bottom.
*/
/** Strings announced to assistive technology users. */
var ARIA = {
QUESTION_REMOVED: "Previous follow-up question removed",
QUESTION_ADDED: "New follow-up question required"
}
var DEFAULT_ERROR = "Please check this field for errors.";
var REQUIRED = "required";
var INPUT = "input";
var INFORMATIVE = "info";
var WARNING = "warning";
var ERROR = "error";
var SUCCESS = "success";
// <<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>
// Helper functions.
@ -38,104 +35,24 @@ function makeVisible(el) {
el.style.visibility = "visible";
}
/** Executes `func` once per selected child of a given element. */
function forEachChild(el, selector, func) {
const children = el.querySelectorAll(selector);
for (const child of children) {
func(child)
}
}
/** Removes `required` attribute from input. */
const removeRequired = input => input.removeAttribute(REQUIRED);
/** Adds `required` attribute to input. */
const setRequired = input => input.setAttribute(REQUIRED, "");
/** Removes `checked` attribute from input. */
const removeChecked = input => input.checked = false;
/** Adds `checked` attribute to input. */
const setChecked = input => input.checked = true;
/** Creates and returns a live region element. */
function createLiveRegion(id) {
const liveRegion = document.createElement("div");
liveRegion.setAttribute("role", "region");
liveRegion.setAttribute("aria-live", "polite");
liveRegion.setAttribute("id", id + "-live-region");
liveRegion.classList.add("sr-only");
liveRegion.classList.add("usa-sr-only");
document.body.appendChild(liveRegion);
return liveRegion;
}
/** Currently selected radio buttons. */
var selected = {};
/** Mapping of radio buttons to the toggleables they control. */
var radioToggles = {};
/**
* Tracks state of selected radio button.
*
* This is required due to JavaScript not having a native
* event trigger for "deselect" on radio buttons. Tracking
* which button has been deselected (and hiding the associated
* toggleable) is a manual task.
* */
function rememberSelected(radioButton) {
selected[radioButton.name] = radioButton;
}
/** Announces changes to assistive technology users. */
function announce(id, text) {
const liveRegion = document.getElementById(id + "-live-region");
let liveRegion = document.getElementById(id + "-live-region");
if (!liveRegion) liveRegion = createLiveRegion(id);
liveRegion.innerHTML = text;
}
/**
* Used by an event handler to hide HTML.
*
* Hides any previously visible HTML associated with
* previously selected radio buttons.
*/
function hideToggleable(e) {
// has any button in this radio button group been selected?
const selectionExists = e.target.name in selected;
if (selectionExists) {
// does the selection have any hidden content associated with it?
const hasToggleable = selected[e.target.name].id in radioToggles;
if (hasToggleable) {
const prevRadio = selected[e.target.name];
const prevToggleable = radioToggles[prevRadio.id];
// is this event handler for a different button?
const selectionHasChanged = (e.target != prevRadio);
// is the previous button's content still visible?
const prevSelectionVisible = (prevToggleable.style.visibility !== "hidden");
if (selectionHasChanged && prevSelectionVisible) {
makeHidden(prevToggleable);
forEachChild(prevToggleable, INPUT, removeChecked);
forEachChild(prevToggleable, INPUT, removeRequired);
announce(prevToggleable.id, ARIA.QUESTION_REMOVED);
}
}
}
}
function revealToggleable(e) {
// if the currently selected radio button has a toggle
// make it visible
if (e.target.id in radioToggles) {
const toggleable = radioToggles[e.target.id];
rememberSelected(e.target);
if (e.target.required) forEachChild(toggleable, INPUT, setRequired);
makeVisible(toggleable);
announce(toggleable.id, ARIA.QUESTION_ADDED);
}
}
/**
* Slow down event handlers by limiting how frequently they fire.
*
@ -177,51 +94,114 @@ function toggleInputValidity(el, valid, msg=DEFAULT_ERROR) {
el.classList.remove('usa-input--success');
el.setAttribute("aria-invalid", "true");
el.setCustomValidity(msg);
// this is here for testing: in actual use, we might not want to
// visually display these errors until the user tries to submit
el.classList.add('usa-input--error');
}
}
function _checkDomainAvailability(e) {
/** Display (or hide) a message beneath an element. */
function inlineToast(el, id, style, msg) {
if (!el.id && !id) {
console.error("Elements must have an `id` to show an inline toast.");
return;
}
let toast = document.getElementById((el.id || id) + "--toast");
if (style) {
if (!toast) {
// create and insert the message div
toast = document.createElement("div");
const toastBody = document.createElement("div");
const p = document.createElement("p");
toast.setAttribute("id", (el.id || id) + "--toast");
toast.className = `usa-alert usa-alert--${style} usa-alert--slim`;
toastBody.classList.add("usa-alert__body");
p.classList.add("usa-alert__text");
p.innerText = msg;
toastBody.appendChild(p);
toast.appendChild(toastBody);
el.parentNode.insertBefore(toast, el.nextSibling);
} else {
// update and show the existing message div
toast.className = `usa-alert usa-alert--${style} usa-alert--slim`;
toast.querySelector("div p").innerText = msg;
makeVisible(toast);
}
} else {
if (toast) makeHidden(toast);
}
}
function _checkDomainAvailability(el) {
const callback = (response) => {
toggleInputValidity(e.target, (response && response.available));
if (e.target.validity.valid) {
e.target.classList.add('usa-input--success');
// do other stuff, like display a toast?
toggleInputValidity(el, (response && response.available), msg=response.message);
announce(el.id, response.message);
if (el.validity.valid) {
el.classList.add('usa-input--success');
// use of `parentElement` due to .gov inputs being wrapped in www/.gov decoration
inlineToast(el.parentElement, el.id, SUCCESS, response.message);
} else {
inlineToast(el.parentElement, el.id, ERROR, response.message);
}
}
fetchJSON(`available/${e.target.value}`, callback);
fetchJSON(`available/${el.value}`, callback);
}
/** Call the API to see if the domain is good. */
const checkDomainAvailability = debounce(_checkDomainAvailability);
// <<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>
// Event handlers.
/** On radio button selection change, handles associated toggleables. */
function handleToggle(e) {
// hide any previously visible HTML associated with previously selected radio buttons
hideToggleable(e);
// display any HTML associated with the newly selected radio button
revealToggleable(e);
/** Hides the toast message and clears the aira live region. */
function clearDomainAvailability(el) {
el.classList.remove('usa-input--success');
announce(el.id, "");
// use of `parentElement` due to .gov inputs being wrapped in www/.gov decoration
inlineToast(el.parentElement, el.id);
}
/** On input change, handles running any associated validators. */
function handleInputValidation(e) {
const attribute = e.target.getAttribute("validate") || "";
/** Runs all the validators associated with this element. */
function runValidators(el) {
const attribute = el.getAttribute("validate") || "";
if (!attribute.length) return;
const validators = attribute.split(" ");
let isInvalid = false;
for (const validator of validators) {
switch (validator) {
case "domain":
checkDomainAvailability(e);
checkDomainAvailability(el);
break;
}
}
toggleInputValidity(e.target, !isInvalid);
toggleInputValidity(el, !isInvalid);
}
/** Clears all the validators associated with this element. */
function clearValidators(el) {
const attribute = el.getAttribute("validate") || "";
if (!attribute.length) return;
const validators = attribute.split(" ");
for (const validator of validators) {
switch (validator) {
case "domain":
clearDomainAvailability(el);
break;
}
}
toggleInputValidity(el, true);
}
// <<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>
// Event handlers.
/** On input change, handles running any associated validators. */
function handleInputValidation(e) {
clearValidators(e.target);
if (e.target.hasAttribute("auto-validate")) runValidators(e.target);
}
/** On button click, handles running any associated validators. */
function handleValidationClick(e) {
const attribute = e.target.getAttribute("validate-for") || "";
if (!attribute.length) return;
const input = document.getElementById(attribute);
runValidators(input);
}
// <<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>><<>>
@ -230,8 +210,12 @@ function handleInputValidation(e) {
/**
* An IIFE that will attach validators to inputs.
*
* It looks for elements with `validate="<type> <type>"` and adds
* change handlers for each known type.
* It looks for elements with `validate="<type> <type>"` and adds change handlers.
*
* These handlers know about two other attributes:
* - `validate-for="<id>"` creates a button which will run the validator(s) on <id>
* - `auto-validate` will run validator(s) when the user stops typing (otherwise,
* they will only run when a user clicks the button with `validate-for`)
*/
(function validatorsInit() {
"use strict";
@ -239,58 +223,11 @@ function handleInputValidation(e) {
for(const input of needsValidation) {
input.addEventListener('input', handleInputValidation);
}
})();
/**
* An IIFE that will hide any elements with `hide-on-load` attribute.
*
* Why not start with `hidden`? Because this is for use with form questions:
* if Javascript fails, users will still need access to those questions.
*/
(function hiddenInit() {
"use strict";
const hiddens = document.querySelectorAll('[hide-on-load]');
for(const hidden of hiddens) {
makeHidden(hidden);
forEachChild(hidden, INPUT, removeRequired);
const activatesValidation = document.querySelectorAll('[validate-for]');
for(const button of activatesValidation) {
button.addEventListener('click', handleValidationClick);
}
})();
/**
* An IIFE that adds onChange listeners to radio buttons.
*
* An element with `toggle-by="<id>,<id>"` will be hidden/shown
* by a radio button with `id="<id>"`.
*
* It also inserts the ARIA live region to be used when
* announcing show/hide to screen reader users.
*/
(function toggleInit() {
"use strict";
// get elements to show/hide
const toggleables = document.querySelectorAll('[toggle-by]');
for(const toggleable of toggleables) {
// get the (comma-seperated) list of radio button ids
// which trigger this toggleable to become visible
const attribute = toggleable.getAttribute("toggle-by") || "";
if (!attribute.length) continue;
const radioIDs = attribute.split(",");
createLiveRegion(toggleable.id)
for (const id of radioIDs) {
radioToggles[id] = toggleable;
// if it is already selected, track that
const radioButton = document.getElementById(id);
if (radioButton.checked) rememberSelected(radioButton);
}
}
// all radio buttons must react to selection changes
const radioButtons = document.querySelectorAll('input[type="radio"]');
for (const radioButton of Array.from(radioButtons)) {
radioButton.addEventListener('change', handleToggle);
}
})();

View file

@ -21,7 +21,7 @@ i.e.
*/
// Finer grained letterspacing adjustments
$letter-space--xs: .0125em;
$letter-space--xs: .0125em;
@use "uswds-core" as *;
@ -68,7 +68,7 @@ $letter-space--xs: .0125em;
//Tighter spacing when H2 is immediatly after H1
.register-form-step .usa-fieldset:first-of-type h2:first-of-type,
.register-form-step h1 + h2 {
margin-top: units(1);
margin-top: units(1);
}
.register-form-step h3 {
@ -85,14 +85,14 @@ $letter-space--xs: .0125em;
.register-form-step h4 {
margin-bottom: 0;
+ p {
+ p {
margin-top: units(0.5);
}
}
.register-form-step p,
.register-form-step .usa-list li,
.dashboard p
.dashboard p
{
@include typeset('sans', 'sm', 5);
max-width: measure(5);
@ -110,6 +110,13 @@ $letter-space--xs: .0125em;
margin-top: units(1);
}
/* Make "placeholder" links visually obvious */
a[href$="todo"]::after {
background-color: yellow;
content: " [link TBD]";
font-style: italic;
}
a.breadcrumb__back {
display:flex;
align-items: center;
@ -155,7 +162,7 @@ a.breadcrumb__back {
.usa-list {
margin-top: units(0.5);
}
}
}
.review__step {
margin-top: units(3);
@ -245,13 +252,13 @@ section.dashboard {
}
}
td[data-label]:before,
td[data-label]:before,
th[data-label]:before {
color: color('primary-darker');
padding-bottom: units(2px);
}
}
@include at-media(mobile-lg) {
margin-top: units(5);

View file

@ -6,20 +6,14 @@ from phonenumber_field.formfields import PhoneNumberField # type: ignore
from django import forms
from django.core.validators import RegexValidator
from django.utils.safestring import mark_safe
from api.views import DOMAIN_API_MESSAGES
from registrar.models import Contact, DomainApplication, Domain
from registrar.utility import errors
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):
"""
@ -70,6 +64,13 @@ class RegistrarFormSet(forms.BaseFormSet):
# save a reference to an application object
self.application = kwargs.pop("application", None)
super(RegistrarFormSet, self).__init__(*args, **kwargs)
# quick workaround to ensure that the HTML `required`
# attribute shows up on required fields for any forms
# in the formset which have data already (stated another
# way: you can leave a form in the formset blank, but
# if you opt to fill it out, you must fill it out _right_)
for index in range(self.initial_form_count()):
self.forms[index].use_required_attribute = True
def should_delete(self, cleaned):
"""Should this entry be deleted from the database?"""
@ -150,7 +151,6 @@ class RegistrarFormSet(forms.BaseFormSet):
class OrganizationTypeForm(RegistrarForm):
organization_type = forms.ChoiceField(
required=True,
choices=DomainApplication.OrganizationChoices.choices,
widget=forms.RadioSelect,
error_messages={"required": "Select the type of organization you represent."},
@ -170,7 +170,6 @@ class TribalGovernmentForm(RegistrarForm):
tribe_name = forms.CharField(
label="Enter the tribe that you represent",
label_suffix=REQUIRED_SUFFIX,
error_messages={"required": "Enter the tribe you represent."},
)
@ -208,8 +207,7 @@ class OrganizationElectionForm(RegistrarForm):
(True, "Yes"),
(False, "No"),
],
),
required=False, # use field validation to require an answer
)
)
def clean_is_election_board(self):
@ -234,18 +232,13 @@ class OrganizationContactForm(RegistrarForm):
# if it has been filled in when required.
required=False,
choices=[("", "--Select--")] + DomainApplication.AGENCY_CHOICES,
label_suffix=REQUIRED_SUFFIX,
)
organization_name = forms.CharField(
label="Organization name",
label_suffix=REQUIRED_SUFFIX,
required=True,
error_messages={"required": "Enter the name of your organization."},
)
address_line1 = forms.CharField(
label="Street address",
label_suffix=REQUIRED_SUFFIX,
required=True,
error_messages={"required": "Enter the street address of your organization."},
)
address_line2 = forms.CharField(
@ -254,8 +247,6 @@ class OrganizationContactForm(RegistrarForm):
)
city = forms.CharField(
label="City",
label_suffix=REQUIRED_SUFFIX,
required=True,
error_messages={
"required": "Enter the city where your organization is located."
},
@ -263,8 +254,6 @@ class OrganizationContactForm(RegistrarForm):
state_territory = forms.ChoiceField(
label="State, territory, or military post",
choices=[("", "--Select--")] + DomainApplication.StateTerritoryChoices.choices,
label_suffix=REQUIRED_SUFFIX,
required=True,
error_messages={
"required": (
"Select the state, territory, or military post where your organization"
@ -274,7 +263,6 @@ class OrganizationContactForm(RegistrarForm):
)
zipcode = forms.CharField(
label="Zip code",
label_suffix=REQUIRED_SUFFIX,
validators=[
RegexValidator(
"^[0-9]{5}(?:-[0-9]{4})?$|^$",
@ -313,7 +301,6 @@ class TypeOfWorkForm(RegistrarForm):
type_of_work = forms.CharField(
# label has to end in a space to get the label_suffix to show
label="What type of work does your organization do? ",
label_suffix=REQUIRED_SUFFIX,
widget=forms.Textarea(),
error_messages={"required": "Enter the type of work your organization does."},
)
@ -326,7 +313,6 @@ class TypeOfWorkForm(RegistrarForm):
" legislation, applicable bylaws or charter, or other documentation to"
" support your claims. "
),
label_suffix=REQUIRED_SUFFIX,
widget=forms.Textarea(),
error_messages={
"required": (
@ -356,8 +342,6 @@ class AuthorizingOfficialForm(RegistrarForm):
first_name = forms.CharField(
label="First name / given name",
label_suffix=REQUIRED_SUFFIX,
required=True,
error_messages={
"required": (
"Enter the first name / given name of your authorizing official."
@ -370,8 +354,6 @@ class AuthorizingOfficialForm(RegistrarForm):
)
last_name = forms.CharField(
label="Last name / family name",
label_suffix=REQUIRED_SUFFIX,
required=True,
error_messages={
"required": (
"Enter the last name / family name of your authorizing official."
@ -380,8 +362,6 @@ class AuthorizingOfficialForm(RegistrarForm):
)
title = forms.CharField(
label="Title or role in your organization",
label_suffix=REQUIRED_SUFFIX,
required=True,
error_messages={
"required": (
"Enter the title or role your authorizing official has in your"
@ -391,7 +371,6 @@ class AuthorizingOfficialForm(RegistrarForm):
)
email = forms.EmailField(
label="Email",
label_suffix=REQUIRED_SUFFIX,
error_messages={
"invalid": (
"Enter an email address in the required format, like name@example.com."
@ -400,8 +379,6 @@ class AuthorizingOfficialForm(RegistrarForm):
)
phone = PhoneNumberField(
label="Phone",
label_suffix=REQUIRED_SUFFIX,
required=True,
error_messages={
"required": "Enter the phone number for your authorizing official."
},
@ -441,11 +418,6 @@ CurrentSitesFormSet = forms.formset_factory(
class AlternativeDomainForm(RegistrarForm):
alternative_domain = forms.CharField(
required=False,
label="Alternative domain",
)
def clean_alternative_domain(self):
"""Validation code for domain names."""
try:
@ -453,22 +425,21 @@ class AlternativeDomainForm(RegistrarForm):
validated = Domain.validate(requested, blank_ok=True)
except errors.ExtraDotsError:
raise forms.ValidationError(
"Please enter a domain without any periods.",
code="invalid",
DOMAIN_API_MESSAGES["extra_dots"], code="extra_dots"
)
except errors.DomainUnavailableError:
raise forms.ValidationError(
"ERROR MESSAGE GOES HERE",
code="invalid",
DOMAIN_API_MESSAGES["unavailable"], code="unavailable"
)
except ValueError:
raise forms.ValidationError(
"Please enter a valid domain name using only letters, "
"numbers, and hyphens",
code="invalid",
)
raise forms.ValidationError(DOMAIN_API_MESSAGES["invalid"], code="invalid")
return validated
alternative_domain = forms.CharField(
required=False,
label="Alternative domain",
)
class BaseAlternativeDomainFormSet(RegistrarFormSet):
JOIN = "alternative_domains"
@ -542,29 +513,19 @@ class DotGovDomainForm(RegistrarForm):
requested = self.cleaned_data.get("requested_domain", None)
validated = Domain.validate(requested)
except errors.BlankValueError:
# none or empty string
raise forms.ValidationError(
"Enter the .gov domain you want. Dont include “www” or “.gov.” For"
" example, if you want www.city.gov, you would enter “city” (without"
" the quotes).",
code="invalid",
DOMAIN_API_MESSAGES["required"], code="required"
)
except errors.ExtraDotsError:
raise forms.ValidationError(
"Enter the .gov domain you want without any periods.",
code="invalid",
DOMAIN_API_MESSAGES["extra_dots"], code="extra_dots"
)
except errors.DomainUnavailableError:
raise forms.ValidationError(
"ERROR MESSAGE GOES HERE",
code="invalid",
DOMAIN_API_MESSAGES["unavailable"], code="unavailable"
)
except ValueError:
raise forms.ValidationError(
"Enter a domain using only letters, "
"numbers, or hyphens (though we don't recommend using hyphens).",
code="invalid",
)
raise forms.ValidationError(DOMAIN_API_MESSAGES["invalid"], code="invalid")
return validated
requested_domain = forms.CharField(label="What .gov domain do you want?")
@ -600,8 +561,6 @@ class YourContactForm(RegistrarForm):
first_name = forms.CharField(
label="First name / given name",
label_suffix=REQUIRED_SUFFIX,
required=True,
error_messages={"required": "Enter your first name / given name."},
)
middle_name = forms.CharField(
@ -610,14 +569,10 @@ class YourContactForm(RegistrarForm):
)
last_name = forms.CharField(
label="Last name / family name",
label_suffix=REQUIRED_SUFFIX,
required=True,
error_messages={"required": "Enter your last name / family name."},
)
title = forms.CharField(
label="Title or role in your organization",
required=True,
label_suffix=REQUIRED_SUFFIX,
error_messages={
"required": (
"Enter your title or role in your organization (e.g., Chief Information"
@ -627,8 +582,6 @@ class YourContactForm(RegistrarForm):
)
email = forms.EmailField(
label="Email",
required=True,
label_suffix=REQUIRED_SUFFIX,
error_messages={
"invalid": (
"Enter your email address in the required format, like"
@ -638,8 +591,6 @@ class YourContactForm(RegistrarForm):
)
phone = PhoneNumberField(
label="Phone",
label_suffix=REQUIRED_SUFFIX,
required=True,
error_messages={"required": "Enter your phone number."},
)
@ -647,8 +598,6 @@ class YourContactForm(RegistrarForm):
class OtherContactsForm(RegistrarForm):
first_name = forms.CharField(
label="First name / given name",
label_suffix=REQUIRED_SUFFIX,
required=True,
error_messages={
"required": "Enter the first name / given name of this contact."
},
@ -659,16 +608,12 @@ class OtherContactsForm(RegistrarForm):
)
last_name = forms.CharField(
label="Last name / family name",
label_suffix=REQUIRED_SUFFIX,
required=True,
error_messages={
"required": "Enter the last name / family name of this contact."
},
)
title = forms.CharField(
label="Title or role in your organization",
label_suffix=REQUIRED_SUFFIX,
required=True,
error_messages={
"required": (
"Enter the title or role in your organization of this contact (e.g.,"
@ -678,7 +623,6 @@ class OtherContactsForm(RegistrarForm):
)
email = forms.EmailField(
label="Email",
label_suffix=REQUIRED_SUFFIX,
error_messages={
"invalid": (
"Enter an email address in the required format, like name@example.com."
@ -687,8 +631,6 @@ class OtherContactsForm(RegistrarForm):
)
phone = PhoneNumberField(
label="Phone",
label_suffix=REQUIRED_SUFFIX,
required=True,
error_messages={"required": "Enter a phone number for this contact."},
)
@ -744,17 +686,10 @@ class RequirementsForm(RegistrarForm):
"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(
error_messages={
"required": (
"Check the box if you read and agree to the requirements for"
" registering and operating .gov domains.",
code="invalid",
" registering and operating .gov domains."
)
return is_acknowledged
},
)

View file

@ -92,7 +92,7 @@ class Domain(TimeStampedModel):
# a domain name is alphanumeric or hyphen, up to 63 characters, doesn't
# begin or end with a hyphen, followed by a TLD of 2-6 alphabetic characters
DOMAIN_REGEX = re.compile(r"^(?!-)[A-Za-z0-9-]{1,63}(?<!-)\.[A-Za-z]{2,6}")
DOMAIN_REGEX = re.compile(r"^(?!-)[A-Za-z0-9-]{1,63}(?<!-)\.[A-Za-z]{2,6}$")
@classmethod
def string_could_be_domain(cls, domain: str | None) -> bool:

View file

@ -120,7 +120,6 @@ class DomainApplication(TimeStampedModel):
LEGISLATIVE = "legislative", "Legislative"
AGENCIES = [
"",
"Administrative Conference of the United States",
"Advisory Council on Historic Preservation",
"American Battle Monuments Commission",

View file

@ -1,23 +1,13 @@
{% extends 'application_form.html' %}
{% load widget_tweaks %}
{% block form_content %}
<p id="instructions">Is there anything else we should know about your domain request?</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.anything_else|add_label_class:"usa-label usa-sr-only" }}
{{ forms.0.anything_else|add_class:"usa-textarea usa-character-count__field"|attr:"aria-describedby:instructions"|attr:"maxlength=500" }}
<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>
{{ block.super }}
</form>
{% load field_helpers %}
{% block form_instructions %}
<p>Is there anything else we should know about your domain request?</p>
{% endblock %}
{% block form_fields %}
{% with add_label_class="usa-sr-only" attr_maxlength=500 %}
{% input_with_errors forms.0.anything_else %}
{% endwith %}
{% endblock %}

View file

@ -1,28 +1,26 @@
{% extends 'application_form.html' %}
{% load widget_tweaks %}
{% load static %}
{% load field_helpers %}
{% block form_content %}
{% block form_instructions %}
<h2 class="margin-bottom-05">
Who is the authorizing official for your organization?
</h2>
<h2>Who is the authorizing official for your organization?</h2>
<div id="instructions">
<p>Your authorizing official is the person within your organization who can authorize your domain request. This is generally the highest ranking or highest elected official in your organization. Read more about <a href="{% url 'todo' %}">who can serve as an authorizing official</a>.
</p>
<p>Your authorizing official is the person within your organization who can authorize
your domain request. This is generally the highest ranking or highest elected official
in your organization. Read more about <a href="{% url 'todo' %}">who can serve as an
authorizing official</a>.</p>
<div class="ao_example">
{% include "includes/ao_example.html" %}
</div>
<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>Well contact your authorizing official to let them know that you made this request
and to double check that they approve it.</p>
{% endblock %}
{% include "includes/required_fields.html" %}
<form id="step__{{steps.current}}" class="usa-form usa-form--large" method="post" novalidate>
{% csrf_token %}
{% block form_fields %}
<fieldset class="usa-fieldset">
<legend class="usa-sr-only">
Who is the authorizing official for your organization?
@ -38,12 +36,9 @@
{% input_with_errors forms.0.email %}
{% input_with_errors forms.0.phone add_class="usa-input--medium" %}
{% with add_class="usa-input--medium" %}
{% input_with_errors forms.0.phone %}
{% endwith %}
</fieldset>
{{ block.super }}
</form>
{% endblock %}
{% endblock %}

View file

@ -1,31 +1,22 @@
{% extends 'application_form.html' %}
{% load widget_tweaks field_helpers %}
{% load static %}
{% block form_content %}
<form id="step__{{steps.current}}" class="usa-form usa-form--large" method="post" novalidate>
{% csrf_token %}
<div>
{{ forms.0.management_form }}
{# TODO: aria-describedby to associate these instructions with the input! #}
<p id="website_instructions">
Enter your organizations current public website, if you have one. For example, www.city.com.
</p>
{% for form in forms.0 %}
{% input_with_errors form.website %}
{% endfor %}
<button type="submit" name="submit_button" value="save" class="usa-button usa-button--unstyled">
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24" height="24">
<use xlink:href="{%static 'img/sprite.svg'%}#add_circle"></use>
</svg><span class="margin-left-05">Add another site</span>
</button>
</div>
<div>
{{ block.super }}
</form>
{% load static field_helpers %}
{% block form_instructions %}
<p>Enter your organizations public website, if you have one. For example,
www.city.com.</p>
{% endblock %}
{% block form_fields %}
{{ forms.0.management_form }}
{% for form in forms.0 %}
{% input_with_errors form.website %}
{% endfor %}
<button type="submit" name="submit_button" value="save" class="usa-button usa-button--unstyled">
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24" height="24">
<use xlink:href="{%static 'img/sprite.svg'%}#add_circle"></use>
</svg><span class="margin-left-05">Add another site</span>
</button>
{% endblock %}

View file

@ -1,58 +1,87 @@
{% extends 'application_form.html' %}
{% load widget_tweaks field_helpers static %}
{% load static field_helpers %}
{% block form_content %}
<div id="preamble">
<p>Before requesting a .gov domain, <a href="{% url 'todo' %}">please make sure it meets our naming requirements.</a> Your domain name must:
<ul class="usa-list">
<li>Be available </li>
<li>Be unique </li>
<li>Relate to your organizations name, location, and/or services </li>
<li>Be clear to the general public. Your domain name must not be easily confused with other organizations.</li>
</ul>
{% block form_instructions %}
<p>Before requesting a .gov domain, <a href="{% url 'todo' %}">please make sure it
meets our naming requirements.</a> Your domain name must:
<ul class="usa-list">
<li>Be available </li>
<li>Be unique </li>
<li>Relate to your organizations name, location, and/or services </li>
<li>Be clear to the general public. Your domain name must not be easily confused
with other organizations.</li>
</ul>
</p>
<p>Note that <strong>only federal agencies can request generic terms</strong> like vote.gov.</p>
<p>Note that <strong>only federal agencies can request generic terms</strong> like
vote.gov.</p>
<p>Well try to give you the domain you want. We first need to make sure your request meets our requirements. Well work with you to find the best domain for your organization.</p>
<p>Well try to give you the domain you want. We first need to make sure your request
meets our requirements. Well work with you to find the best domain for your
organization.</p>
<p>Here are a few domain examples for your type of organization.</p>
<div class="domain_example">
{% include "includes/domain_example.html" %}
</div>
</div>
{% endblock %}
<form id="step__{{steps.current}}" class="usa-form usa-form--large" method="post" novalidate>
<h2>What .gov domain do you want?</h2>
{# TODO: aria-describedby to associate these instructions with the input! #}
<p id="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>
{% csrf_token %}
{% input_with_errors forms.0.requested_domain www_gov=True %}
<button type="button" class="usa-button">Check availability</button>
{% block form_fields %}
<h2>Alternative domains</h2>
{{ forms.0.management_form }}
<div>
{{ forms.1.management_form }}
{# TODO: aria-describedby to associate these instructions with the input! #}
<p id="alt_domain_instructions">Are there other domains youd like if we cant give you your first choice? Entering alternative domains is optional.</p>
<fieldset class="usa-fieldset">
<legend>
<h2>What .gov domain do you want?</h2>
</legend>
{% for form in forms.1 %}
{% input_with_errors form.alternative_domain www_gov=True %}
{% endfor %}
<p id="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>
{% with attr_aria_describedby="domain_instructions domain_instructions2" %}
{# attr_validate / validate="domain" invokes code in get-gov.js #}
{% with www_gov=True attr_validate="domain" %}
{% input_with_errors forms.0.requested_domain %}
{% endwith %}
{% endwith %}
<button
type="button"
class="usa-button"
validate-for="{{ forms.0.requested_domain.auto_id }}"
>Check availability</button>
</fieldset>
{{ forms.1.management_form }}
<fieldset class="usa-fieldset">
<legend>
<h2>Alternative domains</h2>
</legend>
<p id="alt_domain_instructions">Are there other domains youd like if we cant give
you your first choice? Entering alternative domains is optional.</p>
{% with attr_aria_describedby="alt_domain_instructions" %}
{# attr_validate / validate="domain" invokes code in get-gov.js #}
{# attr_auto_validate likewise triggers behavior in get-gov.js #}
{% with www_gov=True attr_validate="domain" attr_auto_validate=True %}
{% for form in forms.1 %}
{% input_with_errors form.alternative_domain %}
{% endfor %}
{% endwith %}
{% endwith %}
<button type="submit" name="submit_button" value="save" class="usa-button usa-button--unstyled">
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24" height="24">
<use xlink:href="{%static 'img/sprite.svg'%}#add_circle"></use>
</svg><span class="margin-left-05">Add another alternative</span>
</button>
</div>
<p>If youre not sure this is the domain you want, thats okay. You can change it later.</p>
</fieldset>
{{ block.super }}
</form>
{% endblock %}
<p id="domain_instructions2">If youre not sure this is the domain you want, thats
okay. You can change it later.</p>
{% endblock %}

View file

@ -1,5 +1,5 @@
{% extends 'base.html' %}
{% load static widget_tweaks dynamic_question_tags namespaced_urls %}
{% load static form_helpers url_helpers %}
{% block title %}Apply for a .gov domain {{form_titles|get_item:steps.current}}{% endblock %}
{% block content %}
@ -18,6 +18,7 @@
</a>
{% endif %}
{% block form_errors %}
{% comment %}
to make sense of this loop, consider that
a context variable of `forms` contains all
@ -34,35 +35,52 @@
{% include "includes/form_errors.html" with form=outer %}
{% endif %}
{% endfor %}
{% endblock %}
<h1> {{form_titles|get_item:steps.current}} </h1>
{% block form_content %}
<div class="stepnav">
{% if steps.next %}
<button
type="submit"
name="submit_button"
value="next"
class="usa-button"
>Save and continue</button>
{% else %}
<button
type="submit"
class="usa-button"
>Submit your domain request</button>
{% endif %}
<button
type="submit"
name="submit_button"
value="save"
class="usa-button usa-button--outline"
>Save progress</button>
</div>
{% block form_instructions %}
{% endblock %}
{% block form_required_fields_help_text %}
{% include "includes/required_fields.html" %}
{% endblock %}
<form id="step__{{steps.current}}" class="usa-form usa-form--large" method="post" novalidate>
{% csrf_token %}
{% block form_fields %}{% endblock %}
{% block form_buttons %}
<div class="stepnav">
{% if steps.next %}
<button
type="submit"
name="submit_button"
value="next"
class="usa-button"
>Save and continue</button>
{% else %}
<button
type="submit"
class="usa-button"
>Submit your domain request</button>
{% endif %}
<button
type="submit"
name="submit_button"
value="save"
class="usa-button usa-button--outline"
>Save progress</button>
</div>
{% endblock %}
</form>
{% block after_form_content %}{% endblock %}
</main>
</div>
{% endblock %}
</div>
</div>
{% endblock %}
{% endblock %}

View file

@ -1,29 +1,29 @@
{% extends 'application_form.html' %}
{% load widget_tweaks field_helpers %}
{% load field_helpers %}
{% block form_content %}
{% block form_instructions %}
<h2 class="margin-bottom-05">
What is the name and mailing address of your organization?
</h2>
<h2>What is the name and mailing address of your organization?</h2>
<div id="instructions">
<p>Enter the name of the organization you represent. Your organization might be part
of a larger entity. If so, enter information about your part of the larger entity.</p>
<p>If your domain request is approved, the name of your organization will be publicly listed as the domain registrant. </p>
{% include "includes/required_fields.html" %}
</div>
<p>If your domain request is approved, the name of your organization will be publicly
listed as the domain registrant.</p>
{% endblock %}
<form id="step__{{steps.current}}" class="usa-form usa-form--large" method="post" novalidate>
{% csrf_token %}
{% block form_fields %}
<fieldset class="usa-fieldset">
<legend class="usa-sr-only">What is the name and mailing address of your organization?</legend>
<legend class="usa-sr-only">
What is the name and mailing address of your organization?
</legend>
{% if is_federal %}
{% select_with_errors forms.0.federal_agency required=True %}
{% with attr_required=True %}
{% input_with_errors forms.0.federal_agency %}
{% endwith %}
{% endif %}
{% input_with_errors forms.0.organization_name %}
@ -34,16 +34,13 @@
{% input_with_errors forms.0.city %}
{% select_with_errors forms.0.state_territory %}
{% input_with_errors forms.0.state_territory %}
{% input_with_errors forms.0.zipcode add_class="usa-input--small" %}
{% with add_class="usa-input--small" %}
{% input_with_errors forms.0.zipcode %}
{% endwith %}
{% input_with_errors forms.0.urbanization %}
</fieldset>
{{ block.super }}
</form>
{% endblock %}
{% endblock %}

View file

@ -1,23 +1,15 @@
{% extends 'application_form.html' %}
{% load widget_tweaks %}
{% load dynamic_question_tags %}
{% load field_helpers %}
{% block form_content %}
<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" required="true"%}
{% endfor %}
</fieldset>
{{ block.super }}
</form>
{% block form_instructions %}
<h2 class="margin-bottom-05">
Is your organization an election office?
</h2>
{% endblock %}
{% block form_fields %}
{% with add_class="usa-radio__input--tile" %}
{% input_with_errors forms.0.is_election_board %}
{% endwith %}
{% endblock %}

View file

@ -1,23 +1,15 @@
{% extends 'application_form.html' %}
{% load widget_tweaks %}
{% load dynamic_question_tags %}
{% load field_helpers %}
{% block form_content %}
<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>Which federal branch is your organization in?</h2>
<p>This question is required.</p>
</legend>
{% radio_buttons_by_value forms.0.federal_type as choices %}
{% for choice in choices.values %}
{% include "includes/radio_button.html" with choice=choice tile="true" %}
{% endfor %}
</fieldset>
{{ block.super }}
</form>
{% block form_instructions %}
<h2 class="margin-bottom-5">
Which federal branch is your organization in?
</h2>
{% endblock %}
{% block form_fields %}
{% with add_class="usa-radio__input--tile" %}
{% input_with_errors forms.0.federal_type %}
{% endwith %}
{% endblock %}

View file

@ -1,26 +1,15 @@
{% extends 'application_form.html' %}
{% load widget_tweaks %}
{% load dynamic_question_tags %}
{% load field_helpers %}
{% block form_content %}
<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>
<p>This question is required.</p>
</legend>
{{ forms.0.organization_type.errors }}
{% for choice in choices.values %}
{% include "includes/radio_button.html" with choice=choice tile="true" %}
{% endfor %}
</fieldset>
{{ block.super }}
</form>
{% block form_instructions %}
<h2 class="margin-bottom-05">
What kind of U.S.-based government organization do you represent?
</h2>
{% endblock %}
{% block form_fields %}
{% with add_class="usa-radio__input--tile" %}
{% input_with_errors forms.0.organization_type %}
{% endwith %}
{% endblock %}

View file

@ -1,41 +1,44 @@
{% extends 'application_form.html' %}
{% load widget_tweaks %}
{% load static %}
{% load field_helpers %}
{% load static field_helpers %}
{% block form_content %}
{% block form_instructions %}
<p>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>
{% endblock %}
<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>
{% include "includes/required_fields.html" %}
<form id="step__{{steps.current}}" class="usa-form usa-form--large" method="post" novalidate>
{% csrf_token %}
{% block form_fields %}
{{ forms.0.management_form }}
{# forms.0 is a formset and this iterates over its forms #}
{% for form in forms.0.forms %}
<fieldset class="usa-fieldset">
<legend>
<h2 class="margin-bottom-05">Contact {{ forloop.counter }}</h2>
<h2>Contact {{ forloop.counter }}</h2>
</legend>
{% input_with_errors form.first_name %}
{% input_with_errors form.middle_name %}
{% input_with_errors form.last_name %}
{% input_with_errors form.title %}
{% input_with_errors form.email %}
{% input_with_errors form.phone add_class="usa-input--medium" %}
{% with add_class="usa-input--medium" %}
{% input_with_errors form.phone %}
{% endwith %}
</fieldset>
{% endfor %}
<div>
<button type="submit" name="submit_button" value="save" class="usa-button usa-button--unstyled">
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24" height="24">
<use xlink:href="{%static 'img/sprite.svg'%}#add_circle"></use>
</svg><span class="margin-left-05">Add another contact</span>
</button>
</div>
{{ block.super }}
</form>
{% endblock %}
<button type="submit" name="submit_button" value="save" class="usa-button usa-button--unstyled">
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24" height="24">
<use xlink:href="{%static 'img/sprite.svg'%}#add_circle"></use>
</svg><span class="margin-left-05">Add another contact</span>
</button>
{% endblock %}

View file

@ -1,41 +1,20 @@
{% extends 'application_form.html' %}
{% load widget_tweaks %}
{% load field_helpers %}
{% block form_content %}
<div id="instructions">
<p>.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>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>
</div>
<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">
{% 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>
{{ block.super }}
</form>
{% block form_instructions %}
<p>.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>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>
{% endblock %}
{% block form_fields %}
{% with attr_maxlength=500 %}
{% input_with_errors forms.0.purpose %}
{% endwith %}
{% endblock %}

View file

@ -1,159 +1,194 @@
{% extends 'application_form.html' %}
{% load widget_tweaks %}
{% load field_helpers %}
{% block form_content %}
{% block form_instructions %}
<p>The .gov domain exists to support a broad diversity of government missions and
public initiatives. Generally, the .gov registry does not review or audit how
government organizations use their domains. However, misuse of an individual .gov
domain can reflect upon the integrity of the entire .gov space. There are categories
of misuse that are statutorily prohibited or abusive in nature.</p>
<p>The .gov domain exists to support a broad diversity of government missions and public initiatives. Generally, the .gov registry does not review or audit how government organizations use their domains. However, misuse of an individual .gov domain can reflect upon the integrity of the entire .gov space. There are categories of misuse that are statutorily prohibited or abusive in nature.</p>
<h2>Prohibited activities for .gov domains</h2>
<h2>Prohibited activities for .gov domains</h2>
<h3>Commercial purposes </h3>
<h3>Commercial purposes </h3>
<p>A .gov domain must not be used for commercial purposes, such as advertising benefitting private individuals or entities.</p>
<p>A .gov domain must not be used for commercial purposes, such as advertising
benefitting private individuals or entities.</p>
<h3>Political campaigns</h3>
<p>A .gov domain must not be used for political campaigns.</p>
<h3>Political campaigns</h3>
<h3>Illegal content</h3>
<p>A .gov domain must not be used to distribute or promote material whose distribution violates applicable law.</p>
<p>A .gov domain must not be used for political campaigns.</p>
<h3>Malicious cyber activity </h3>
<p>.Gov is a trusted and safe space. .Gov domains must not distribute malware, host open redirects, or otherwise engage in malicious cyber activity.</p>
<h3>Illegal content</h3>
<p>A .gov domain must not be used to distribute or promote material whose distribution
violates applicable law.</p>
<h2>Required activities for .gov domain registrants </h2>
<h3>Malicious cyber activity </h3>
<h3>Keep your contact information updated</h3>
<p>As a .gov domain registrant, maintain current and accurate contact information in the .gov registrar. We strongly recommend that you create and use a security contact.</p>
<p>.Gov is a trusted and safe space. .Gov domains must not distribute malware, host
open redirects, or otherwise engage in malicious cyber activity.</p>
<h3>Be responsive if we contact you</h3>
<p>Registrants should respond in a timely manner to communications about required and prohibited activities.</p>
<h2>Required activities for .gov domain registrants </h2>
<h3>Keep your contact information updated</h3>
<h2>Domains can be suspended or terminated for violations</h2>
<p>The .gov program may need to suspend or terminate a domain registration for violations. Registrants should respond in a timely manner to communications about prohibited activities.</p>
<p>When we discover a violation, we will make reasonable efforts to contact a registrant, including:
<ul class="usa-list">
<li>Emails to domain contacts </li>
<li>Phone calls to domain contacts</li>
<li>Email or phone call to the authorizing official</li>
<li>Emails or phone calls to the government organization, a parent organization, or affiliated entities</li>
</ul>
</p>
<p>As a .gov domain registrant, maintain current and accurate contact information in the
.gov registrar. We strongly recommend that you create and use a security contact.</p>
<p>We understand the critical importance of the availability of .gov domains. Suspending or terminating a .gov domain is reserved only for prolonged, unresolved serious violations where the registrant is non-responsive. We will make extensive efforts to contact registrants and to identify potential solutions, and will make reasonable accommodations for remediation timelines proportional to the severity of the issue.</p>
<h3>Be responsive if we contact you</h3>
<h2>Requirements for authorizing officials</h2>
<p>Registrants should respond in a timely manner to communications about required and
prohibited activities.</p>
<p>Your authorizing official is the person within your organization who can authorize your domain request. This is generally the highest ranking or highest elected official in your organization.</p>
<h2>Domains can be suspended or terminated for violations</h2>
<h3>Executive branch federal agencies</h3>
<p>The .gov program may need to suspend or terminate a domain registration for
violations. Registrants should respond in a timely manner to communications about
prohibited activities.</p>
<p>Domain requests from executive branch agencies must be authorized by CIOs or agency heads.</p>
<p>When we discover a violation, we will make reasonable efforts to contact a
registrant, including:
<ul class="usa-list">
<li>Emails to domain contacts </li>
<li>Phone calls to domain contacts</li>
<li>Email or phone call to the authorizing official</li>
<li>Emails or phone calls to the government organization, a parent organization,
or affiliated entities</li>
</ul>
</p>
<p>Domain requests from executive branch agencies are subject to guidance issued by the U.S. Office of Management and Budget.</p>
<p>We understand the critical importance of the availability of .gov domains.
Suspending or terminating a .gov domain is reserved only for prolonged, unresolved
serious violations where the registrant is non-responsive. We will make extensive
efforts to contact registrants and to identify potential solutions, and will make
reasonable accommodations for remediation timelines proportional to the severity of
the issue.</p>
<h3>Judicial branch federal agencies</h3>
<h2>Requirements for authorizing officials</h2>
<p>Domain requests for judicial branch agencies, except the U.S. Supreme Court, must be authorized by the director or CIO of the Administrative Office (AO) of the United States Courts.</p>
<p>Your authorizing official is the person within your organization who can authorize
your domain request. This is generally the highest ranking or highest elected official
in your organization.</p>
<p>Domain requests from the U.S. Supreme Court must be authorized by the director of information technology for the U.S. Supreme Court.</p>
<h3>Executive branch federal agencies</h3>
<h3>Legislative branch federal agencies</h3>
<p>Domain requests from executive branch agencies must be authorized by CIOs or agency
heads.</p>
<h4>U.S. Senate</h4>
<p>Domain requests from executive branch agencies are subject to guidance issued by
the U.S. Office of Management and Budget.</p>
<p>Domain requests from the U.S. Senate must come from the Senate Sergeant at Arms.</p>
<h3>Judicial branch federal agencies</h3>
<h4>U.S. House of Representatives</h4>
<p>Domain requests for judicial branch agencies, except the U.S. Supreme Court, must
be authorized by the director or CIO of the Administrative Office (AO) of the United
States Courts.</p>
<p>Domain requests from the U.S. House of Representatives must come from the House Chief Administrative Officer.</p>
<p>Domain requests from the U.S. Supreme Court must be authorized by the director of
information technology for the U.S. Supreme Court.</p>
<h4>Other legislative branch agencies</h4>
<h3>Legislative branch federal agencies</h3>
<p>Domain requests from legislative branch agencies must come from the agencys head or CIO.</p>
<h4>U.S. Senate</h4>
<p>Domain requests from legislative commissions must come from the head of the commission, or the head or CIO of the parent agency, if there is one.</p>
<p>Domain requests from the U.S. Senate must come from the Senate Sergeant at Arms.</p>
<h3>Interstate</h3>
<h4>U.S. House of Representatives</h4>
<p>Domain requests from interstate organizations must be authorized by the highest-ranking executive (president, director, chair, or equivalent) or one of the states governors or CIOs.</p>
<p>Domain requests from the U.S. House of Representatives must come from the House
Chief Administrative Officer.</p>
<h3>U.S. states and territories</h3>
<h4>Other legislative branch agencies</h4>
<h4>States and territories: executive branch</h4>
<p>Domain requests from legislative branch agencies must come from the agencys head
or CIO.</p>
<p>Domain requests from states and territories must be authorized by the governor or the state CIO.</p>
<p>Domain requests from legislative commissions must come from the head of the
commission, or the head or CIO of the parent agency, if there is one.</p>
<h4>States and territories: judicial and legislative branches</h4>
<h3>Interstate</h3>
<p>Domain requests from state legislatures and courts must be authorized by an agencys CIO or highest-ranking executive.</p>
<p>Domain requests from interstate organizations must be authorized by the
highest-ranking executive (president, director, chair, or equivalent) or one of the
states governors or CIOs.</p>
<h3>Tribal governments</h3>
<h3>U.S. states and territories</h3>
<p>Domain requests from federally-recognized tribal governments must be authorized by tribal chiefs as noted by the <a href="https://www.bia.gov/service/tribal-leaders-directory">Bureau of Indian Affairs</a>.</p>
<h4>States and territories: executive branch</h4>
<h3>Counties</h3>
<p>Domain requests from states and territories must be authorized by the governor or
the state CIO.</p>
<p>Domain requests from counties must be authorized by the chair of the county commission or the equivalent highest elected official.</p>
<h4>States and territories: judicial and legislative branches</h4>
<h3>Cities</h3>
<p>Domain requests from state legislatures and courts must be authorized by an
agencys CIO or highest-ranking executive.</p>
<p>Domain requests from cities must be authorized by the mayor or the equivalent highest elected official.</p>
<h3>Tribal governments</h3>
<h3>Special districts</h3>
<p>Domain requests from federally-recognized tribal governments must be authorized by
tribal chiefs as noted by the
<a href="https://www.bia.gov/service/tribal-leaders-directory">Bureau of Indian
Affairs</a>.</p>
<p>Domain requests from special districts must be authorized by the highest-ranking executive (president, director, chair, or equivalent) or state CIOs for state-based organizations.</p>
<h3>Counties</h3>
<h3>School districts</h3>
<p>Domain requests from counties must be authorized by the chair of the county
commission or the equivalent highest elected official.</p>
<p>Domain requests from school district governments must be authorized by the highest-ranking executive (the chair of a school districts board or a superintendent).</p>
<h3>Cities</h3>
<h2>Requirements for .gov domain names</h2>
<p>.Gov domains must:</p>
<ul class="usa-list">
<li>Be available</li>
<li>Be unique</li>
<li>Relate to your organizations name, location, and/or services</li>
<li>Be clear to the general public. Your domain name must not be easily confused with other organizations.</li>
</ul>
<p>Domain requests from cities must be authorized by the mayor or the equivalent
highest elected official.</p>
<h3>Special districts</h3>
<h2>HSTS preloading</h2>
<p>The .gov program will preload all newly registered .gov domains for HTTP Strict Transport Security (HSTS).</p>
<p>HSTS is a simple and widely-supported standard that protects visitors by ensuring that their browsers always connect to a website over HTTPS. HSTS removes the need to redirect users from http:// to https:// URLs. (This redirection is a security risk that HSTS eliminates.)</p>
<p>HSTS preloading impacts web traffic only. Once a domain is on the HSTS preload list, modern web browsers will enforce HTTPS connections for all websites hosted on the .gov domain. Users will not be able to click through warnings to reach a site. Non-web uses of .gov (email, VPN, APIs, etc.) are not affected.</p>
<p>Domain requests from special districts must be authorized by the highest-ranking
executive (president, director, chair, or equivalent) or state CIOs for state-based
organizations.</p>
<h3>School districts</h3>
<h2>Acknowledgement of .gov domain requirements</h2>
<p>Domain requests from school district governments must be authorized by the highest-ranking
executive (the chair of a school districts board or a superintendent).</p>
<p>This question is required.</p>
<h2>Requirements for .gov domain names</h2>
<form id="step__{{steps.current}}" class="usa-form usa-form--large" method="post" novalidate>
<div class="usa-form-group">
{% csrf_token %}
<p>.Gov domains must:
<ul class="usa-list">
<li>Be available</li>
<li>Be unique</li>
<li>Relate to your organizations name, location, and/or services</li>
<li>Be clear to the general public. Your domain name must not be easily confused
with other organizations.</li>
</ul>
</p>
{% 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>
<h2>HSTS preloading</h2>
{{ block.super }}
<p>The .gov program will preload all newly registered .gov domains for HTTP Strict
Transport Security (HSTS).</p>
</form>
<p>HSTS is a simple and widely-supported standard that protects visitors by ensuring
that their browsers always connect to a website over HTTPS. HSTS removes the need to
redirect users from http:// to https:// URLs. (This redirection is a security risk
that HSTS eliminates.)</p>
<p>HSTS preloading impacts web traffic only. Once a domain is on the HSTS preload
list, modern web browsers will enforce HTTPS connections for all websites hosted on
the .gov domain. Users will not be able to click through warnings to reach a site.
Non-web uses of .gov (email, VPN, APIs, etc.) are not affected.</p>
{% endblock %}
{% block form_fields %}
<fieldset class="usa-fieldset">
<legend>
<h2>Acknowledgement of .gov domain requirements</h2>
</legend>
{% input_with_errors forms.0.is_policy_acknowledged %}
</fieldset>
{% endblock %}

View file

@ -1,11 +1,7 @@
{% extends 'application_form.html' %}
{% load static widget_tweaks namespaced_urls %}
{% block form_content %}
<form id="step__{{steps.current}}" class="usa-form usa-form--large" method="post" novalidate>
{% csrf_token %}
{% load static url_helpers %}
{% block form_fields %}
{% for step in steps.all|slice:":-1" %}
<section class="review__step">
<hr />
@ -97,9 +93,4 @@
</div>
</section>
{% endfor %}
{{ block.super }}
</form>
{% endblock %}
{% endblock %}

View file

@ -1,30 +1,14 @@
{% extends 'application_form.html' %}
{% load widget_tweaks %}
{% load static %}
{% block form_content %}
<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 id="step__{{steps.current}}" class="usa-form usa-form--large" method="post" novalidate>
{% csrf_token %}
{% 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>
{% load field_helpers %}
{% block form_instructions %}
<p>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>
{% endblock %}
{% block form_fields %}
{% input_with_errors forms.0.security_email %}
{% endblock %}

View file

@ -1,4 +1,4 @@
{% load static namespaced_urls %}
{% load static url_helpers %}
<div class="margin-bottom-4 tablet:margin-bottom-0">
<nav aria-label="Form steps,">

View file

@ -1,27 +1,15 @@
<!-- Test page -->
{% extends 'application_form.html' %}
{% load field_helpers %}
{% block form_content %}
{% load widget_tweaks dynamic_question_tags field_helpers %}
<form id="step__{{steps.current}}" class="usa-form usa-form--large" method="post" novalidate>
{% input_with_errors forms.0.tribe_name %}
<p>Please check all that apply.</p>
<div class="usa-form-group">
{% csrf_token %}
<div class="usa-checkbox">
{{ forms.0.federally_recognized_tribe|add_class:"usa-checkbox__input"|attr:"required"}}
{{ forms.0.federally_recognized_tribe|add_label_class:"usa-checkbox__label" }}
</div>
<div class="usa-checkbox">
{{ forms.0.state_recognized_tribe|add_class:"usa-checkbox__input"|attr:"required"}}
{{ forms.0.state_recognized_tribe|add_label_class:"usa-checkbox__label" }}
</div>
</div>
{{ block.super }}
</form>
{% endblock %}
{% block form_fields %}
{% input_with_errors forms.0.tribe_name %}
<fieldset class="usa-fieldset">
<legend class="usa-legend">
<p>Please check all that apply.
<abbr class="usa-hint usa-hint--required" title="required">*</abbr></p>
</legend>
{% input_with_errors forms.0.federally_recognized_tribe %}
{% input_with_errors forms.0.state_recognized_tribe %}
</fieldset>
{% endblock %}

View file

@ -1,58 +1,10 @@
{% extends 'application_form.html' %}
{% load widget_tweaks %}
{% load field_helpers %}
{% block form_content %}
{% include "includes/required_fields.html" %}
<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">
{% with field=forms.0.type_of_work %}
{% 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 %}
{{ field|add_class:"usa-input--error usa-textarea usa-character-count__field"|attr:"maxlength=500"|attr:"aria-invalid:true" }}
</div>
{% else %}
{{ field|add_label_class:"usa-label" }}
{{ field|add_class:"usa-textarea usa-character-count__field"|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>
<div class="usa-form-group">
<div class="usa-character-count">
{% with field=forms.0.more_organization_information %}
{% 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 %}
{{ field|add_class:"usa-input--error usa-textarea usa-character-count__field"|attr:"maxlength=500"|attr:"aria-invalid:true" }}
</div>
{% else %}
{{ field|add_label_class:"usa-label" }}
{{ field|add_class:"usa-textarea usa-character-count__field"|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>
{{ block.super }}
</form>
{% endblock %}
{% block form_fields %}
{% with attr_maxlength=500 %}
{% input_with_errors forms.0.type_of_work %}
{% input_with_errors forms.0.more_organization_information %}
{% endwith %}
{% endblock %}

View file

@ -1,27 +1,25 @@
{% extends 'application_form.html' %}
{% load widget_tweaks %}
{% load static %}
{% load field_helpers %}
{% block form_content %}
{% block form_instructions %}
<p> Well use the following information to contact you about your domain request and,
once your request is approved, about managing your domain.</p>
<div id="instructions">
<p> Well use the following information to contact you about your domain request and, once your request is approved, about managing your domain.</p>
<p>If youd like us to use a different name, email, or phone number you can make those
changes below. Changing your contact information here wont affect your login.gov
account information.</p>
<p>If youd like us to use a different name, email, or phone number you can make those changes below. Changing your contact information here wont affect your login.gov account information.</p>
<p>The contact information you provide here wont be public and will only be used for
the .gov registry.</p>
{% endblock %}
<p>The contact information you provide here wont be public and will only be used for the .gov registry.</p>
<div>
{% include "includes/required_fields.html" %}
<form id="step__{{steps.current}}" class="usa-form usa-form--large" method="post" novalidate>
{% csrf_token %}
{% block form_fields %}
<fieldset class="usa-fieldset">
<legend class="usa-sr-only">
Your contact information
</legend>
{% input_with_errors forms.0.first_name %}
{% input_with_errors forms.0.middle_name %}
@ -32,12 +30,9 @@
{% input_with_errors forms.0.email %}
{% input_with_errors forms.0.phone add_class="usa-input--medium" %}
{% with add_class="usa-input--medium" %}
{% input_with_errors forms.0.phone %}
{% endwith %}
</fieldset>
{{ block.super }}
</form>
{% endblock %}
{% endblock %}

View file

@ -0,0 +1,9 @@
<{{ label_tag }}
class="{% if label_classes %} {{ label_classes }}{% endif %}"
{% if not field.use_fieldset %}for="{{ widget.attrs.id }}"{% endif %}
>
{{ field.label }}
{% if widget.attrs.required %}
<abbr class="usa-hint usa-hint--required" title="required">*</abbr>
{% endif %}
</{{ label_tag }}>

View file

@ -0,0 +1,3 @@
{% for name, value in widget.attrs.items %}{% if value is not False %}
{{ name }}{% if value is not True %}="{{ value|stringformat:'s' }}"
{% endif %}{% endif %}{% endfor %}

View file

@ -0,0 +1,3 @@
{% with uswds_input_class="usa-checkbox__input" %}
{% include "django/forms/widgets/input.html" %}
{% endwith %}

View file

@ -0,0 +1,3 @@
{% with uswds_input_class="usa-input" %}
{% include "django/forms/widgets/input.html" %}
{% endwith %}

View file

@ -0,0 +1 @@
{% include "django/forms/widgets/input.html" %}

View file

@ -0,0 +1,8 @@
<input
type="{{ widget.type }}"
name="{{ widget.name }}"
{# hint: spacing in the class string matters #}
class="{{ uswds_input_class }}{% if classes %} {{ classes }}{% endif %}"
{% if widget.value != None %}value="{{ widget.value|stringformat:'s' }}"{% endif %}
{% include "django/forms/widgets/attrs.html" %}
/>

View file

@ -0,0 +1,20 @@
<div class="{{ uswds_input_class }}">
{% for group, options, index in widget.optgroups %}
{% if group %}<div><label>{{ group }}</label>{% endif %}
{% for option in options %}
<input
type="{{ option.type }}"
name="{{ option.name }}"
{# hint: spacing in the class string matters #}
class="{{ uswds_input_class }}__input {% if classes %} {{ classes }}{% endif %}"
{% if option.value != None %}value="{{ option.value|stringformat:'s' }}"{% endif %}
{% include "django/forms/widgets/attrs.html" with widget=option %}
/>
<label
class="{{ uswds_input_class }}__label{% if label_classes %} {{ label_classes }}{% endif %}"
for="{{ option.attrs.id }}"
>{{ option.label }}</label>
{% endfor %}
{% if group %}</div>{% endif %}
{% endfor %}
</div>

View file

@ -0,0 +1,3 @@
{% with uswds_input_class="usa-input" %}
{% include "django/forms/widgets/input.html" %}
{% endwith %}

View file

@ -0,0 +1,3 @@
{% with uswds_input_class="usa-input" %}
{% include "django/forms/widgets/input.html" %}
{% endwith %}

View file

@ -0,0 +1,3 @@
{% with uswds_input_class="usa-radio" %}
{% include "django/forms/widgets/multiple_input.html" %}
{% endwith %}

View file

@ -0,0 +1 @@
{% include "django/forms/widgets/input_option.html" %}

View file

@ -0,0 +1,3 @@
{% with uswds_input_class="usa-input" %}
{% include "django/forms/widgets/input.html" %}
{% endwith %}

View file

@ -0,0 +1,14 @@
<select
name="{{ widget.name }}"
{# hint: spacing in the class string matters #}
class="usa-select{% if classes %} {{ classes }}{% endif %}"
{% include "django/forms/widgets/attrs.html" %}
>
{% for group, options, index in widget.optgroups %}
{% if group %}<optgroup label="{{ group }}">{% endif %}
{% for option in options %}
{% include option.template_name with widget=option %}
{% endfor %}
{% if group %}</optgroup>{% endif %}
{% endfor %}
</select>

View file

@ -0,0 +1 @@
<option value="{{ widget.value|stringformat:'s' }}"{% include "django/forms/widgets/attrs.html" %}>{{ widget.label }}</option>

View file

@ -0,0 +1,3 @@
{% with uswds_input_class="usa-input" %}
{% include "django/forms/widgets/input.html" %}
{% endwith %}

View file

@ -0,0 +1,5 @@
<textarea
name="{{ widget.name }}"
class="usa-textarea usa-character-count__field {{ widget.attrs.class }}"
{% include "django/forms/widgets/attrs.html" %}
>{% if widget.value %}{{ widget.value }}{% endif %}</textarea>

View file

@ -0,0 +1,3 @@
{% with uswds_input_class="usa-input" %}
{% include "django/forms/widgets/input.html" %}
{% endwith %}

View file

@ -5,33 +5,70 @@ error messages, if necessary.
{% 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 www_gov %}
<div class="display-flex flex-align-center">
<span class="padding-top-05 padding-right-2px">www.</span>
{% endif %}
{% if required %}
{{ field|add_class:input_class|attr:"required" }}
{% else %}
{{ field|add_class:input_class }}
{% endif %}
{% if www_gov %}
<span class="padding-top-05 padding-left-2px">.gov </span>
</div>
{% endif %}
{% if widget.attrs.maxlength %}
<div class="usa-character-count">
{% endif %}
{% if field.use_fieldset %}
<fieldset
id="{{ widget.attrs.id }}__fieldset"
class="usa-fieldset usa-form-group{% if group_classes %} {{ group_classes }}{% endif %}"
>
{% elif field.widget_type == 'checkbox' %}
<div
class="usa-checkbox{% if group_classes %} {{ group_classes }}{% endif %}"
>
{% else %}
<div
class="usa-form-group{% if group_classes %} {{ group_classes }}{% endif %}"
>
{% endif %}
{% if not field.widget_type == "checkbox" %}
{% include "django/forms/label.html" %}
{% endif %}
{% if field.errors %}
<div id="{{ widget.attrs.id }}__error-message">
{% for error in field.errors %}
<span class="usa-error-message" role="alert">
{{ error }}
</span>
{% endfor %}
</div>
{% endif %}
{% if www_gov %}
<div class="display-flex flex-align-center">
<span class="padding-top-05 padding-right-2px">www.</span>
{% endif %}
{# this is the input field, itself #}
{% include widget.template_name %}
{% if www_gov %}
<span class="padding-top-05 padding-left-2px">.gov </span>
</div>
{% endif %}
{% if field.widget_type == "checkbox" %}
{% include "django/forms/label.html" %}
{% endif %}
{% if field.use_fieldset %}
</fieldset>
{% else %}
</div>
{% endif %}
{% if widget.attrs.maxlength %}
<span
id="{{ widget.attrs.id }}__message"
class="usa-character-count__message"
aria-live="polite"
>
You can enter up to {{ widget.attrs.maxlength }} characters
</span>
</div>
{% endif %}

View file

@ -1,14 +0,0 @@
<div class="usa-radio">
<input
type="{{ choice.data.type }}"
name="{{ choice.data.name }}"
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 or required %}required{% endif %}
{% if choice.data.selected %}checked{% endif %}
/>
<label class="usa-radio__label" for="{{ choice.id_for_label }}" >
{{ choice.data.label }}
</label >
</div>

View file

@ -7,7 +7,6 @@ Edit your User Profile
{% block content %}
<main id="main-content" class="grid-container">
<form class="usa-form usa-form--large" method="post" enctype="multipart/form-data">
{% csrf_token %}
<fieldset class="usa-fieldset">
<legend class="usa-legend usa-legend--large">Your profile</legend>
<p>

View file

@ -1,54 +0,0 @@
from django import template
from django.forms import BaseFormSet
from django.utils.html import format_html
register = template.Library()
@register.filter
def isformset(value):
return isinstance(value, BaseFormSet)
@register.simple_tag
def radio_buttons_by_value(boundfield):
"""
Returns radio button subwidgets indexed by their value.
This makes it easier to visually identify the choices when
using them in templates.
"""
return {w.data["value"]: w for w in boundfield.subwidgets}
@register.simple_tag
def trigger(boundfield, *triggers):
"""
Inserts HTML to link a dynamic question to its trigger(s).
Currently only works for radio buttons.
Also checks whether the question should be hidden on page load.
If the question is already answered or if the question's
trigger is already selected, it should not be hidden.
Returns HTML attributes which will be read by a JavaScript
helper, if the user has JavaScript enabled.
"""
ids = []
hide_on_load = True
for choice in boundfield.subwidgets:
if choice.data["selected"]:
hide_on_load = False
for trigger in triggers:
ids.append(trigger.id_for_label)
if trigger.data["selected"]:
hide_on_load = False
html = format_html(
'toggle-by="{}"{}', ",".join(ids), " hide-on-load" if hide_on_load else ""
)
return html

View file

@ -1,42 +1,150 @@
"""Custom field helpers for our inputs."""
import re
from django import template
register = template.Library()
def _field_context(field, input_class, add_class, *, required=False, www_gov=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
if www_gov:
context["www_gov"] = True
return context
@register.inclusion_tag("includes/input_with_errors.html")
def input_with_errors(field, add_class=None, www_gov=False):
@register.inclusion_tag("includes/input_with_errors.html", takes_context=True)
def input_with_errors(context, field=None): # noqa: C901
"""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.
Args:
field: The field instance.
In addition to the explicit `field` argument, this inclusion_tag takes the
following "widget-tweak-esque" parameters from the surrounding context.
Context args:
add_class: append to input element's `class` attribute
add_error_class: like `add_class` but only if field.errors is not empty
add_required_class: like `add_class` but only if field is required
add_label_class: append to input element's label's `class` attribute
add_group_class: append to input element's surrounding tag's `class` attribute
attr_* - adds or replaces any single html attribute for the input
add_error_attr_* - like `attr_*` but only if field.errors is not empty
Example usage:
```
{% for form in forms.0 %}
{% with add_class="usa-input--medium" %}
{% with attr_required=True attr_disabled=False %}
{% input_with_errors form.street_address1 %}
{% endwith %}
{% endwith %}
{% endfor }
There are a few edge cases to keep in mind:
- a "maxlength" attribute will cause the input to use USWDS Character counter
- the field's `use_fieldset` controls whether the output is label/field or
fieldset/legend/field
- checkbox label styling is different (this is handled, don't worry about it)
"""
return _field_context(field, "usa-input", add_class, www_gov=www_gov)
context = context.flatten()
context["field"] = field
# get any attributes specified in the field's definition
attrs = dict(field.field.widget.attrs)
@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.
# these will be converted to CSS strings
classes = []
label_classes = []
group_classes = []
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=required)
# this will be converted to an attribute string
described_by = []
if "class" in attrs:
classes.append(attrs.pop("class"))
# parse context for field attributes and classes
# ---
# here we loop through all items in the context dictionary
# (this is the context which was being used to render the
# outer template in which this {% input_with_errors %} appeared!)
# and look for "magic" keys -- these are used to modify the
# appearance and behavior of the final HTML
for key, value in context.items():
if key.startswith("attr_"):
attr_name = re.sub("_", "-", key[5:])
attrs[attr_name] = value
elif key.startswith("add_error_attr_") and field.errors:
attr_name = re.sub("_", "-", key[15:])
attrs[attr_name] = value
elif key == "add_class":
classes.append(value)
elif key == "add_required_class" and field.required:
classes.append(value)
elif key == "add_error_class" and field.errors:
classes.append(value)
elif key == "add_label_class":
label_classes.append(value)
elif key == "add_group_class":
group_classes.append(value)
attrs["id"] = field.auto_id
# do some work for various edge cases
if "maxlength" in attrs:
# associate the field programmatically with its hint text
described_by.append(f"{attrs['id']}__message")
if field.use_fieldset:
context["label_tag"] = "legend"
else:
context["label_tag"] = "label"
if field.use_fieldset:
label_classes.append("usa-legend")
if field.widget_type == "checkbox":
label_classes.append("usa-checkbox__label")
elif not field.use_fieldset:
label_classes.append("usa-label")
if field.errors:
# associate the field programmatically with its error message
message_div_id = f"{attrs['id']}__error-message"
described_by.append(message_div_id)
# set the field invalid
# due to weirdness, this must be a string, not a boolean
attrs["aria-invalid"] = "true"
# style the invalid field
classes.append("usa-input--error")
label_classes.append("usa-label--error")
group_classes.append("usa-form-group--error")
# convert lists into strings
if classes:
context["classes"] = " ".join(classes)
if label_classes:
context["label_classes"] = " ".join(label_classes)
if group_classes:
context["group_classes"] = " ".join(group_classes)
if described_by:
# ensure we don't overwrite existing attribute value
if "aria-describedby" in attrs:
described_by.append(attrs["aria-describedby"])
attrs["aria-describedby"] = " ".join(described_by)
# ask Django to give us the widget dict
# see Widget.get_context() on
# https://docs.djangoproject.com/en/4.1/ref/forms/widgets
widget = field.field.widget.get_context(
field.html_name, field.value() or field.initial, field.build_widget_attrs(attrs)
) # -> {"widget": {"name": ...}}
context["widget"] = widget["widget"]
return context

View file

@ -0,0 +1,9 @@
from django import template
from django.forms import BaseFormSet
register = template.Library()
@register.filter
def isformset(value):
return isinstance(value, BaseFormSet)

View file

@ -218,9 +218,13 @@ class ApplicationWizard(LoginRequiredMixin, TemplateView):
return render(request, self.template_name, context)
def get_all_forms(self) -> list:
"""Calls `get_forms` for all steps and returns a flat list."""
nested = (self.get_forms(step=step, use_db=True) for step in self.steps)
def get_all_forms(self, **kwargs) -> list:
"""
Calls `get_forms` for all steps and returns a flat list.
All arguments (**kwargs) are passed directly to `get_forms`.
"""
nested = (self.get_forms(step=step, **kwargs) for step in self.steps)
flattened = [form for lst in nested for form in lst]
return flattened
@ -252,14 +256,12 @@ class ApplicationWizard(LoginRequiredMixin, TemplateView):
for form in forms:
data = form.from_database(self.application) if self.has_pk() else None
kwargs["initial"] = data
if use_post:
kwargs["data"] = self.request.POST
instantiated.append(form(self.request.POST, **kwargs))
elif use_db:
kwargs["data"] = data
instantiated.append(form(data, **kwargs))
else:
kwargs["data"] = None
instantiated.append(form(**kwargs))
instantiated.append(form(initial=data, **kwargs))
return instantiated
@ -297,9 +299,8 @@ class ApplicationWizard(LoginRequiredMixin, TemplateView):
else:
raise Http404()
def is_valid(self, forms: list = None) -> bool:
def is_valid(self, forms: list) -> bool:
"""Returns True if all forms in the wizard are valid."""
forms = forms if forms is not None else self.get_all_forms()
are_valid = (form.is_valid() for form in forms)
return all(are_valid)
@ -309,6 +310,9 @@ class ApplicationWizard(LoginRequiredMixin, TemplateView):
if self.__class__ == ApplicationWizard:
return self.goto(self.steps.first)
# which button did the user press?
button: str = request.POST.get("submit_button", "")
forms = self.get_forms(use_post=True)
if self.is_valid(forms):
# always save progress
@ -321,7 +325,6 @@ class ApplicationWizard(LoginRequiredMixin, TemplateView):
# if user opted to save their progress,
# return them to the page they were already on
button = request.POST.get("submit_button", None)
if button == "save":
return self.goto(self.steps.current)
# otherwise, proceed as normal

View file

@ -71,7 +71,7 @@ class StepsHelper:
@property
def current(self):
"""
Returns the current step. If no current step is stored in the
Returns the current step (a string). If no current step is stored in the
storage backend, the first step will be returned.
"""
step = getattr(self._wizard.storage, "current_step", None)