Bypass Django internal form rendering

The purpose of this change is to allow direct use of Django templates
to make edits to how form fields and labels are rendered, while
retaining the validation logic provided by Django's extensive field and
widget library.

This commit additional contains:
 - Remove `REQUIRED_SUFFIX`, as it is handled by the templates now
 - Remove `required=True` from form fields, as this is the default
 - Remove `required=False` from fields where it was added as workaround
   for conditionally required questions which Django form wizard
   couldn't handle
 - Replace `clean_is_policy_acknowledged` with `error_messages` dict
 - Remove duplicate nil entry for Federal Agency dropdown
 - Update `DOMAIN_REGEX` to match whole string
 - Subtle changes to `is_valid` and `get_forms` in ApplicationWizard to
   reduce database calls and use form object's initial data correctly
This commit is contained in:
Seamus Johnston 2023-02-02 14:30:00 -06:00
parent 1d6b0da391
commit 0e3f68622f
No known key found for this signature in database
GPG key ID: 2F21225985069105
42 changed files with 740 additions and 652 deletions

View file

@ -6,20 +6,12 @@ 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 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 +62,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 +149,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 +168,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 +205,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 +230,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 +245,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 +252,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 +261,6 @@ class OrganizationContactForm(RegistrarForm):
)
zipcode = forms.CharField(
label="Zip code",
label_suffix=REQUIRED_SUFFIX,
validators=[
RegexValidator(
"^[0-9]{5}(?:-[0-9]{4})?$|^$",
@ -313,7 +299,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 +311,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 +340,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 +352,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 +360,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 +369,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 +377,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."
},
@ -600,8 +575,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 +583,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 +596,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 +605,6 @@ class YourContactForm(RegistrarForm):
)
phone = PhoneNumberField(
label="Phone",
label_suffix=REQUIRED_SUFFIX,
required=True,
error_messages={"required": "Enter your phone number."},
)
@ -647,8 +612,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 +622,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 +637,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 +645,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 +700,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,14 @@
{% 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 %}
{% csrf_token %}
{% with add_label_class="usa-sr-only" attr_maxlength=500 %}
{% input_with_errors forms.0.anything_else %}
{% endwith %}
{% endblock %}

View file

@ -1,28 +1,27 @@
{% 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>
{% block form_fields %}
{% csrf_token %}
<fieldset class="usa-fieldset">
<legend class="usa-sr-only">
Who is the authorizing official for your organization?
@ -38,12 +37,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 %}

View file

@ -1,17 +1,17 @@
{% extends 'application_form.html' %}
{% load widget_tweaks field_helpers %}
{% load static %}
{% load field_helpers %}
{% block form_content %}
{% block form_instructions %}
<p>Enter your organizations public website, if you have one. For example,
www.city.com.</p>
{% endblock %}
<form id="step__{{steps.current}}" class="usa-form usa-form--large" method="post" novalidate>
{% block form_fields %}
{% csrf_token %}
<div>
{{ forms.0.management_form }}
{# TODO: aria-describedby to associate these instructions with the input! #}
<p id="website_instructions">
Enter your organizations public website, if you have one. For example, www.city.com.
</p>
{% for form in forms.0 %}
{% input_with_errors form.website %}
{% endfor %}
@ -21,11 +21,4 @@
<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>
{% endblock %}

View file

@ -1,58 +1,83 @@
{% extends 'application_form.html' %}
{% load widget_tweaks field_helpers static %}
{% load static %}
{% load 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:
{% 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>
<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>
{% block form_fields %}
{% csrf_token %}
{% input_with_errors forms.0.requested_domain www_gov=True %}
{{ forms.0.management_form }}
<fieldset class="usa-fieldset">
<legend>
<h2>What .gov domain do you want?</h2>
</legend>
<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" %}
{% with www_gov=True attr_validate="domain" %}
{% input_with_errors forms.0.requested_domain %}
{% endwith %}
{% endwith %}
<button type="button" class="usa-button">Check availability</button>
<h2>Alternative domains</h2>
</fieldset>
<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>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" %}
{% with www_gov=True attr_validate="domain" %}
{% for form in forms.1 %}
{% input_with_errors form.alternative_domain www_gov=True %}
{% 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>
{{ block.super }}
</form>
</fieldset>
<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

@ -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,10 +35,22 @@
{% include "includes/form_errors.html" with form=outer %}
{% endif %}
{% endfor %}
{% endblock %}
<h1> {{form_titles|get_item:steps.current}} </h1>
{% block form_content %}
{% 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>
{% block form_fields %}{% endblock %}
{% block form_buttons %}
<div class="stepnav">
{% if steps.next %}
<button
@ -59,10 +72,14 @@
class="usa-button usa-button--outline"
>Save progress</button>
</div>
{% endblock %}
</form>
{% block after_form_content %}{% endblock %}
</main>
</div>
{% endblock %}
</div>
</div>
{% endblock %}

View file

@ -1,29 +1,30 @@
{% 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>
{% block form_fields %}
{% csrf_token %}
<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 +35,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 %}

View file

@ -1,23 +1,16 @@
{% 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 %}
{% csrf_token %}
{% with add_class="usa-radio__input--tile" %}
{% input_with_errors forms.0.is_election_board %}
{% endwith %}
{% endblock %}

View file

@ -1,23 +1,16 @@
{% 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 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 %}
{% 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 %}
{% csrf_token %}
{% with add_class="usa-radio__input--tile" %}
{% input_with_errors forms.0.federal_type %}
{% endwith %}
{% endblock %}

View file

@ -1,26 +1,16 @@
{% 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 %}
{% csrf_token %}
{% with add_class="usa-radio__input--tile" %}
{% input_with_errors forms.0.organization_type %}
{% endwith %}
{% endblock %}

View file

@ -1,41 +1,48 @@
{% extends 'application_form.html' %}
{% load widget_tweaks %}
{% load static %}
{% load 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>
{% block form_fields %}
{% csrf_token %}
{{ 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>
{% if form.is_bound %}HAS INITIAL DATA{% else %}NO INITIAL DATA{% endif %}
{% 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 %}

View file

@ -1,41 +1,21 @@
{% 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 %}
{% csrf_token %}
{% with attr_maxlength=500 %}
{% input_with_errors forms.0.purpose %}
{% endwith %}
{% endblock %}

View file

@ -1,159 +1,195 @@
{% 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">
<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>
<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>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>
<li>Emails or phone calls to the government organization, a parent organization,
or affiliated entities</li>
</ul>
</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>
<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>
<h2>Requirements for authorizing officials</h2>
<h2>Requirements for authorizing officials</h2>
<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>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>
<h3>Executive branch federal agencies</h3>
<h3>Executive branch federal agencies</h3>
<p>Domain requests from executive branch agencies must be authorized by CIOs or agency heads.</p>
<p>Domain requests from executive branch agencies must be authorized by CIOs or agency
heads.</p>
<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 executive branch agencies are subject to guidance issued by
the U.S. Office of Management and Budget.</p>
<h3>Judicial branch federal agencies</h3>
<h3>Judicial branch federal agencies</h3>
<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 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. Supreme Court must be authorized by the director of information technology for the U.S. Supreme Court.</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>Legislative branch federal agencies</h3>
<h3>Legislative branch federal agencies</h3>
<h4>U.S. Senate</h4>
<h4>U.S. Senate</h4>
<p>Domain requests from the U.S. Senate must come from the Senate Sergeant at Arms.</p>
<p>Domain requests from the U.S. Senate must come from the Senate Sergeant at Arms.</p>
<h4>U.S. House of Representatives</h4>
<h4>U.S. House of Representatives</h4>
<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. House of Representatives must come from the House
Chief Administrative Officer.</p>
<h4>Other legislative branch agencies</h4>
<h4>Other legislative branch agencies</h4>
<p>Domain requests from legislative branch agencies must come from the agencys head or CIO.</p>
<p>Domain requests from legislative branch agencies must come from the agencys head
or 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>
<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>
<h3>Interstate</h3>
<h3>Interstate</h3>
<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 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>U.S. states and territories</h3>
<h3>U.S. states and territories</h3>
<h4>States and territories: executive branch</h4>
<h4>States and territories: executive branch</h4>
<p>Domain requests from states and territories must be authorized by the governor or the state CIO.</p>
<p>Domain requests from states and territories must be authorized by the governor or
the state CIO.</p>
<h4>States and territories: judicial and legislative branches</h4>
<h4>States and territories: judicial and legislative branches</h4>
<p>Domain requests from state legislatures and courts must be authorized by an agencys CIO or highest-ranking executive.</p>
<p>Domain requests from state legislatures and courts must be authorized by an
agencys CIO or highest-ranking executive.</p>
<h3>Tribal governments</h3>
<h3>Tribal governments</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 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>
<h3>Counties</h3>
<h3>Counties</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 counties must be authorized by the chair of the county
commission or the equivalent highest elected official.</p>
<h3>Cities</h3>
<h3>Cities</h3>
<p>Domain requests from cities must be authorized by the mayor or the equivalent highest elected official.</p>
<p>Domain requests from cities must be authorized by the mayor or the equivalent
highest elected official.</p>
<h3>Special districts</h3>
<h3>Special districts</h3>
<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>
<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>
<h3>School districts</h3>
<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>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>
<h2>Requirements for .gov domain names</h2>
<p>.Gov domains must:</p>
<ul class="usa-list">
<h2>Requirements for .gov domain names</h2>
<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>
<li>Be clear to the general public. Your domain name must not be easily confused
with other organizations.</li>
</ul>
</p>
<h2>HSTS preloading</h2>
<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>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>
<h2>Acknowledgement of .gov domain requirements</h2>
<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 %}
{% 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 }}
</form>
<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 %}
{% csrf_token %}
<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,9 +1,7 @@
{% extends 'application_form.html' %}
{% load static widget_tweaks namespaced_urls %}
{% load static namespaced_urls %}
{% block form_content %}
<form id="step__{{steps.current}}" class="usa-form usa-form--large" method="post" novalidate>
{% block form_fields %}
{% csrf_token %}
{% for step in steps.all|slice:":-1" %}
@ -97,9 +95,4 @@
</div>
</section>
{% endfor %}
{{ block.super }}
</form>
{% endblock %}

View file

@ -1,30 +1,15 @@
{% 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 %}
{% csrf_token %}
{% input_with_errors forms.0.security_email %}
{% endblock %}

View file

@ -1,27 +1,16 @@
<!-- 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">
{% block form_fields %}
{% 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>
{% 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,11 @@
{% 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">
{% block form_fields %}
{% 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 %}
{% with attr_maxlength=500 %}
{% input_with_errors forms.0.type_of_work %}
{% input_with_errors forms.0.more_organization_information %}
{% 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 %}

View file

@ -1,27 +1,26 @@
{% 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>
{% block form_fields %}
{% csrf_token %}
<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 +31,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 %}

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" }}
{% 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" id="input-error-message" role="alert">
<span class="usa-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" }}
{% endif %}
{% 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 %}
{# 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

@ -1,42 +1,139 @@
"""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 }
"""
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
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

@ -218,9 +218,9 @@ class ApplicationWizard(LoginRequiredMixin, TemplateView):
return render(request, self.template_name, context)
def get_all_forms(self) -> list:
def get_all_forms(self, **kwargs) -> 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)
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 +252,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 +295,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 +306,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 +321,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)