diff --git a/src/Pipfile b/src/Pipfile index 10be9752e..4475c0886 100644 --- a/src/Pipfile +++ b/src/Pipfile @@ -21,6 +21,7 @@ django-widget-tweaks = "*" cachetools = "*" requests = "*" django-fsm = "*" +django-phonenumber-field = {extras = ["phonenumberslite"], version = "*"} [dev-packages] django-debug-toolbar = "*" diff --git a/src/Pipfile.lock b/src/Pipfile.lock index 2a531a89b..05e062c70 100644 --- a/src/Pipfile.lock +++ b/src/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "1668475ce39851bd84ff7be330afe9766f6823cf9095980ba3b220ced3a284f4" + "sha256": "b75e38d4e723e06a194cb607ba3003ed7a9f0460f23d036d9cd18214341d3e77" }, "pipfile-spec": 6, "requires": {}, @@ -24,11 +24,11 @@ }, "cachetools": { "hashes": [ - "sha256:6a94c6402995a99c3970cc7e4884bb60b4a8639938157eeed436098bf9831757", - "sha256:f9f17d2aec496a9aa6b76f53e3b614c965223c061982d434d160f930c698a9db" + "sha256:5991bc0e08a1319bb618d3195ca5b6bc76646a49c21d55962977197b301cc1fe", + "sha256:8462eebf3a6c15d25430a8c27c56ac61340b2ecf60c9ce57afc2b97e450e47da" ], "index": "pypi", - "version": "==5.2.0" + "version": "==5.2.1" }, "certifi": { "hashes": [ @@ -120,7 +120,7 @@ "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845", "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" ], - "markers": "python_full_version >= '3.6.0'", + "markers": "python_version >= '3.6'", "version": "==2.1.1" }, "cryptography": { @@ -221,6 +221,17 @@ "index": "pypi", "version": "==2.8.1" }, + "django-phonenumber-field": { + "extras": [ + "phonenumberslite" + ], + "hashes": [ + "sha256:9edad2b2602af25f2aefc73c4cf53eaf7abf9e17d73c1c4372bd3052bebb26f9", + "sha256:de3e47b986b4959949762c16fd8fe26b3e462ef3e5531ed00950bd20c698576a" + ], + "index": "pypi", + "version": "==7.0.2" + }, "django-widget-tweaks": { "hashes": [ "sha256:9bfc5c705684754a83cc81da328b39ad1b80f32bd0f4340e2a810cbab4b0c00e", @@ -242,11 +253,11 @@ }, "faker": { "hashes": [ - "sha256:2d5443724f640ce07658ca8ca8bbd40d26b58914e63eec6549727869aa67e2cc", - "sha256:c2a2ff9dd8dfd991109b517ab98d5cb465e857acb45f6b643a0e284a9eb2cc76" + "sha256:4a8bc3cec832dde1928f8ce0817452bdadf63863d9e4d8307817247a38e51523", + "sha256:e15becbddc3a69a342e03ca6810caab7299e28e48106ae113a07f65c627d6fd7" ], "index": "pypi", - "version": "==15.3.4" + "version": "==16.1.0" }, "furl": { "hashes": [ @@ -357,11 +368,18 @@ }, "packaging": { "hashes": [ - "sha256:2198ec20bd4c017b8f9717e00f0c8714076fc2fd93816750ab48e2c41de2cfd3", - "sha256:957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3" + "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2", + "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97" ], "markers": "python_version >= '3.7'", - "version": "==22.0" + "version": "==23.0" + }, + "phonenumberslite": { + "hashes": [ + "sha256:44cbb13581122164cd8a83b40f12db854277e8a5f9c6e22bd8dc2d8aa98e3260", + "sha256:469eb263160e243aa02fff643502698f99e77bcb6478e9aaa7115838006be122" + ], + "version": "==8.13.4" }, "psycopg2-binary": { "hashes": [ @@ -581,7 +599,7 @@ "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30", "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693" ], - "markers": "python_full_version >= '3.6.0'", + "markers": "python_version >= '3.6'", "version": "==4.11.1" }, "black": { @@ -925,7 +943,7 @@ "sha256:7500c9625927c8ec60f54377d590f67b30c8e70ef4b8894214ac6e4cad233d2a", "sha256:780a4082c5fbc0fde6a2fcfe5e26e6efc1e8f425730863c04085769781f51eba" ], - "markers": "python_full_version >= '3.7.0'", + "markers": "python_version >= '3.7'", "version": "==2.1.2" }, "webob": { diff --git a/src/registrar/assets/sass/_theme/_uswds-theme-custom-styles.scss b/src/registrar/assets/sass/_theme/_uswds-theme-custom-styles.scss index 7d80d5f76..158309f99 100644 --- a/src/registrar/assets/sass/_theme/_uswds-theme-custom-styles.scss +++ b/src/registrar/assets/sass/_theme/_uswds-theme-custom-styles.scss @@ -100,3 +100,9 @@ footer { //Workaround because USWDS units jump from 10 to 15 margin-top: units(10) + units(2); } + +abbr[title] { + // workaround for underlining abbr element + border-bottom: none; + text-decoration: none; +} diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index 1869ecb58..49c5a25d5 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -90,6 +90,8 @@ INSTALLED_APPS = [ "widget_tweaks", # library for Finite State Machine statuses "django_fsm", + # library for phone numbers + "phonenumber_field", # let's be sure to install our own application! "registrar", # Our internal API application @@ -181,6 +183,8 @@ TEMPLATES = [ }, ] +MESSAGE_STORAGE = "django.contrib.messages.storage.session.SessionStorage" + # IS_DEMO_SITE controls whether or not we show our big red "TEST SITE" banner # underneath the "this is a real government website" banner. IS_DEMO_SITE = True @@ -296,6 +300,9 @@ USE_L10N = True # make datetimes timezone-aware by default USE_TZ = True +# setting for phonenumber library +PHONENUMBER_DEFAULT_REGION = "US" + # endregion # region: Logging-----------------------------------------------------------### diff --git a/src/registrar/forms/application_wizard.py b/src/registrar/forms/application_wizard.py index 9185a26a4..28118ed53 100644 --- a/src/registrar/forms/application_wizard.py +++ b/src/registrar/forms/application_wizard.py @@ -2,11 +2,22 @@ from __future__ import annotations # allows forward references in annotations import logging from django import forms +from django.core.validators import RegexValidator +from django.utils.safestring import mark_safe + +from phonenumber_field.formfields import PhoneNumberField # type: ignore from registrar.models import Contact, DomainApplication, Domain logger = logging.getLogger(__name__) +# no sec because this use of mark_safe does not introduce a cross-site scripting +# vulnerability because there is no untrusted content inside. It is +# only being used to pass a specific HTML entity into a template. +REQUIRED_SUFFIX = mark_safe( # nosec + ' *' +) + class RegistrarForm(forms.Form): """ @@ -20,6 +31,8 @@ class RegistrarForm(forms.Form): def __init__(self, *args, **kwargs): kwargs.setdefault("label_suffix", "") + # save a reference to an application object + self.application = kwargs.pop("application", None) super(RegistrarForm, self).__init__(*args, **kwargs) def to_database(self, obj: DomainApplication | Contact): @@ -49,6 +62,7 @@ class OrganizationTypeForm(RegistrarForm): required=True, choices=DomainApplication.OrganizationChoices.choices, widget=forms.RadioSelect, + error_messages={"required": "This question is required."}, ) @@ -56,46 +70,95 @@ class OrganizationFederalForm(RegistrarForm): federal_type = forms.ChoiceField( choices=DomainApplication.BranchChoices.choices, widget=forms.RadioSelect, + error_messages={"required": "This question is required."}, ) class OrganizationElectionForm(RegistrarForm): - is_election_board = forms.BooleanField( + is_election_board = forms.NullBooleanField( widget=forms.RadioSelect( choices=[ (True, "Yes"), (False, "No"), ], ), + required=False, # use field validation to require an answer ) + def clean_is_election_board(self): + """This box must be checked to proceed but offer a clear error.""" + # already converted to a boolean + is_election_board = self.cleaned_data["is_election_board"] + if is_election_board is None: + raise forms.ValidationError( + "Please select Yes or No.", + code="required", + ) + return is_election_board + class OrganizationContactForm(RegistrarForm): # for federal agencies we also want to know the top-level agency. federal_agency = forms.ChoiceField( label="Federal agency", # not required because this field won't be filled out unless - # it is a federal agency. + # it is a federal agency. Use clean to check programatically + # if it has been filled in when required. required=False, - choices=DomainApplication.AGENCY_CHOICES, + choices=[("", "--Select--")] + DomainApplication.AGENCY_CHOICES, + label_suffix=REQUIRED_SUFFIX, + ) + organization_name = forms.CharField( + label="Organization Name", label_suffix=REQUIRED_SUFFIX + ) + address_line1 = forms.CharField( + label="Street address", + label_suffix=REQUIRED_SUFFIX, ) - organization_name = forms.CharField(label="Organization Name") - address_line1 = forms.CharField(label="Street address") address_line2 = forms.CharField( required=False, label="Street address line 2", ) - city = forms.CharField(label="City") + city = forms.CharField(label="City", label_suffix=REQUIRED_SUFFIX) state_territory = forms.ChoiceField( label="State, territory, or military post", choices=[("", "--Select--")] + DomainApplication.StateTerritoryChoices.choices, + label_suffix=REQUIRED_SUFFIX, + ) + zipcode = forms.CharField( + label="ZIP code", + label_suffix=REQUIRED_SUFFIX, + validators=[ + RegexValidator( + "^[0-9]{5}(?:-[0-9]{4})?$|^$", + message="Please enter a ZIP code in the form 12345 or 12345-6789", + ) + ], ) - zipcode = forms.CharField(label="ZIP code") urbanization = forms.CharField( required=False, label="Urbanization (Puerto Rico only)", ) + def clean_federal_agency(self): + """Require something to be selected when this is a federal agency.""" + federal_agency = self.cleaned_data.get("federal_agency", None) + # need the application object to know if this is federal + if self.application is None: + # hmm, no saved application object?, default require the agency + if not federal_agency: + # no answer was selected + raise forms.ValidationError( + "Please select your federal agency.", code="required" + ) + if self.application.is_federal: + if not federal_agency: + # no answer was selected + raise forms.ValidationError( + "Please select your federal agency.", code="required" + ) + return federal_agency + class AuthorizingOfficialForm(RegistrarForm): def to_database(self, obj): @@ -115,15 +178,31 @@ class AuthorizingOfficialForm(RegistrarForm): contact = getattr(obj, "authorizing_official", None) return super().from_database(contact) - first_name = forms.CharField(label="First name/given name") + first_name = forms.CharField( + label="First name/given name", + label_suffix=REQUIRED_SUFFIX, + ) middle_name = forms.CharField( required=False, - label="Middle name (optional)", + label="Middle name", + ) + last_name = forms.CharField( + label="Last name/family name", + label_suffix=REQUIRED_SUFFIX, + ) + title = forms.CharField( + label="Title or role in your organization", + label_suffix=REQUIRED_SUFFIX, + ) + email = forms.EmailField( + label="Email", + label_suffix=REQUIRED_SUFFIX, + error_messages={"invalid": "Please enter a valid email address."}, + ) + phone = PhoneNumberField( + label="Phone", + label_suffix=REQUIRED_SUFFIX, ) - last_name = forms.CharField(label="Last name/family name") - title = forms.CharField(label="Title or role in your organization") - email = forms.EmailField(label="Email") - phone = forms.CharField(label="Phone") class CurrentSitesForm(RegistrarForm): @@ -150,6 +229,27 @@ class CurrentSitesForm(RegistrarForm): "www.city.com.", ) + def clean_current_site(self): + """This field should be a legal domain name.""" + inputted_site = self.cleaned_data["current_site"] + if not inputted_site: + # empty string is fine + return inputted_site + + # something has been inputted + + if inputted_site.startswith("http://") or inputted_site.startswith("https://"): + # strip of the protocol that the pasted from their web browser + inputted_site = inputted_site.split("//", 1)[1] + + if Domain.string_could_be_domain(inputted_site): + return inputted_site + else: + # string could not be a domain + raise forms.ValidationError( + "Please enter a valid domain name", code="invalid" + ) + class DotGovDomainForm(RegistrarForm): def to_database(self, obj): @@ -189,16 +289,51 @@ class DotGovDomainForm(RegistrarForm): return values - requested_domain = forms.CharField(label="What .gov domain do you want?") + requested_domain = forms.CharField( + label="What .gov domain do you want?", + ) alternative_domain = forms.CharField( required=False, label="Are there other domains you’d like if we can’t give you your first " "choice? Entering alternative domains is optional.", ) + def clean_requested_domain(self): + """Requested domains need to be legal top-level domains, not subdomains. + + If they end with `.gov`, then we can reasonably take that off. If they have + any other dots in them, raise an error. + """ + requested = self.cleaned_data["requested_domain"] + if not requested: + # none or empty string + raise forms.ValidationError( + "Please enter the .gov domain that you are requesting.", code="invalid" + ) + if requested.endswith(".gov"): + requested = requested[:-4] + if "." in requested: + raise forms.ValidationError( + "Please enter a domain without any periods.", + code="invalid", + ) + if not Domain.string_could_be_domain(requested + ".gov"): + raise forms.ValidationError( + "Please enter a valid domain name using only letters, " + "numbers, and hyphens", + code="invalid", + ) + return requested + class PurposeForm(RegistrarForm): - purpose = forms.CharField(label="Purpose", widget=forms.Textarea()) + purpose = forms.CharField( + label="Purpose", + widget=forms.Textarea(), + error_messages={ + "required": "Please enter some information about the purpose of your domain" + }, + ) class YourContactForm(RegistrarForm): @@ -219,15 +354,31 @@ class YourContactForm(RegistrarForm): contact = getattr(obj, "submitter", None) return super().from_database(contact) - first_name = forms.CharField(label="First name/given name") + first_name = forms.CharField( + label="First name/given name", + label_suffix=REQUIRED_SUFFIX, + ) middle_name = forms.CharField( required=False, - label="Middle name (optional)", + label="Middle name", + ) + last_name = forms.CharField( + label="Last name/family name", + label_suffix=REQUIRED_SUFFIX, + ) + title = forms.CharField( + label="Title or role in your organization", + label_suffix=REQUIRED_SUFFIX, + ) + email = forms.EmailField( + label="Email", + label_suffix=REQUIRED_SUFFIX, + error_messages={"invalid": "Please enter a valid email address."}, + ) + phone = PhoneNumberField( + label="Phone", + label_suffix=REQUIRED_SUFFIX, ) - last_name = forms.CharField(label="Last name/family name") - title = forms.CharField(label="Title or role in your organization") - email = forms.EmailField(label="Email") - phone = forms.CharField(label="Phone") class OtherContactsForm(RegistrarForm): @@ -250,21 +401,38 @@ class OtherContactsForm(RegistrarForm): other_contacts = obj.other_contacts.first() return super().from_database(other_contacts) - first_name = forms.CharField(label="First name/given name") + first_name = forms.CharField( + label="First name/given name", + label_suffix=REQUIRED_SUFFIX, + ) middle_name = forms.CharField( required=False, - label="Middle name (optional)", + label="Middle name", + ) + last_name = forms.CharField( + label="Last name/family name", + label_suffix=REQUIRED_SUFFIX, + ) + title = forms.CharField( + label="Title or role in your organization", + label_suffix=REQUIRED_SUFFIX, + ) + email = forms.EmailField( + label="Email", + label_suffix=REQUIRED_SUFFIX, + error_messages={"invalid": "Please enter a valid email address."}, + ) + phone = PhoneNumberField( + label="Phone", + label_suffix=REQUIRED_SUFFIX, ) - last_name = forms.CharField(label="Last name/family name") - title = forms.CharField(label="Title or role in your organization") - email = forms.EmailField(label="Email") - phone = forms.CharField(label="Phone") class SecurityEmailForm(RegistrarForm): security_email = forms.EmailField( required=False, label="Security email", + error_messages={"invalid": "Please enter a valid email address."}, ) @@ -281,5 +449,17 @@ class RequirementsForm(RegistrarForm): label=( "I read and agree to the requirements for registering " "and operating .gov domains." - ) + ), + required=False, # use field validation to enforce this ) + + def clean_is_policy_acknowledged(self): + """This box must be checked to proceed but offer a clear error.""" + # already converted to a boolean + is_acknowledged = self.cleaned_data["is_policy_acknowledged"] + if not is_acknowledged: + raise forms.ValidationError( + "You must read and agree to the .gov domain requirements to proceed.", + code="invalid", + ) + return is_acknowledged diff --git a/src/registrar/migrations/0006_alter_contact_phone.py b/src/registrar/migrations/0006_alter_contact_phone.py new file mode 100644 index 000000000..c971a13d1 --- /dev/null +++ b/src/registrar/migrations/0006_alter_contact_phone.py @@ -0,0 +1,26 @@ +# Generated by Django 4.1.4 on 2022-12-14 20:48 + +from django.db import migrations +import phonenumber_field.modelfields # type: ignore + + +class Migration(migrations.Migration): + + dependencies = [ + ("registrar", "0005_domainapplication_city_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="contact", + name="phone", + field=phonenumber_field.modelfields.PhoneNumberField( + blank=True, + db_index=True, + help_text="Phone", + max_length=128, + null=True, + region=None, + ), + ), + ] diff --git a/src/registrar/models/contact.py b/src/registrar/models/contact.py index 6368a0101..0d3a7c389 100644 --- a/src/registrar/models/contact.py +++ b/src/registrar/models/contact.py @@ -1,5 +1,7 @@ from django.db import models +from phonenumber_field.modelfields import PhoneNumberField # type: ignore + class Contact(models.Model): @@ -33,7 +35,7 @@ class Contact(models.Model): help_text="Email", db_index=True, ) - phone = models.TextField( + phone = PhoneNumberField( null=True, blank=True, help_text="Phone", diff --git a/src/registrar/templates/application_authorizing_official.html b/src/registrar/templates/application_authorizing_official.html index 0f786c868..5fee4bc4b 100644 --- a/src/registrar/templates/application_authorizing_official.html +++ b/src/registrar/templates/application_authorizing_official.html @@ -2,6 +2,7 @@ {% extends 'application_form.html' %} {% load widget_tweaks %} {% load static %} +{% load field_helpers %} {% block form_content %} @@ -18,32 +19,27 @@

We’ll contact your authorizing official to let them know that you made this request and to double check that they approve it.

-

All fields are required unless they are marked optional.

+{% include "includes/required_fields.html" %} -
+ {% csrf_token %}
Who is the authorizing official for your organization? - {{ forms.0.first_name|add_label_class:"usa-label" }} - {{ forms.0.first_name|add_class:"usa-input"}} - {{ forms.0.middle_name|add_label_class:"usa-label" }} - {{ forms.0.middle_name|add_class:"usa-input"}} + {% input_with_errors forms.0.first_name %} - {{ forms.0.last_name|add_label_class:"usa-label" }} - {{ forms.0.last_name|add_class:"usa-input"}} - - {{ forms.0.title|add_label_class:"usa-label" }} - {{ forms.0.title|add_class:"usa-input"}} + {% input_with_errors forms.0.middle_name %} - {{ forms.0.email|add_label_class:"usa-label" }} - {{ forms.0.email|add_class:"usa-input"}} + {% input_with_errors forms.0.last_name %} - {{ forms.0.phone|add_label_class:"usa-label" }} - {{ forms.0.phone|add_class:"usa-input usa-input--medium" }} + {% input_with_errors forms.0.title %} + + {% input_with_errors forms.0.email %} + + {% input_with_errors forms.0.phone add_class="usa-input--medium" %}
diff --git a/src/registrar/templates/application_current_sites.html b/src/registrar/templates/application_current_sites.html index 9777d7695..c72a7391f 100644 --- a/src/registrar/templates/application_current_sites.html +++ b/src/registrar/templates/application_current_sites.html @@ -1,6 +1,6 @@ {% extends 'application_form.html' %} -{% load widget_tweaks %} +{% load widget_tweaks field_helpers %} {% load static %} {% block form_content %} @@ -8,8 +8,7 @@ {% csrf_token %} - {{ forms.0.current_site|add_label_class:"usa-label" }} - {{ forms.0.current_site|add_class:"usa-input" }} + {% input_with_errors forms.0.current_site %} {{ block.super }} diff --git a/src/registrar/templates/application_dotgov_domain.html b/src/registrar/templates/application_dotgov_domain.html index ac7f6065c..4225764e5 100644 --- a/src/registrar/templates/application_dotgov_domain.html +++ b/src/registrar/templates/application_dotgov_domain.html @@ -21,28 +21,60 @@ {% include "includes/domain_example__city.html" %} - +

What .gov domain do you want?

After you enter your domain, we’ll make sure it’s available and that it meets some of our naming requirements. If your domain passes these initial checks, we’ll verify that it meets all of our requirements once you complete and submit the rest of this form.

+

This question is required.

{% csrf_token %} - {{ forms.0.requested_domain|add_label_class:"usa-label" }} -
- www. - {{ forms.0.requested_domain|add_class:"usa-input"|attr:"aria-describedby:domain_instructions" }} - .gov -
+ + {% if forms.0.requested_domain.errors %} +
+ {% for error in forms.0.requested_domain.errors %} + + {{ error }} + + {% endfor %} +
+ www. + {{ forms.0.requested_domain|add_class:"usa-input usa-input--error"|attr:"aria-describedby:domain_instructions"|attr:"aria-invalid:true" }} + .gov +
+
+ {% else %} +
+ www. + {{ forms.0.requested_domain|add_class:"usa-input"|attr:"aria-describedby:domain_instructions" }} + .gov +
+ {% endif %}

Alternative domains

+ {% if forms.0.alternative_domain.errors %} +
+ {{ forms.0.alternative_domain|add_label_class:"usa-label usa-label--error" }} + {% for error in forms.0.alternative_domain.errors %} + + {{ error }} + + {% endfor %} +
+ www. + { forms.0.alternative_domain|add_class:"usa-input usa-input--error"|attr:"aria-describedby:domain_instructions"|attr:"aria-invalid:true" }} + .gov +
+
+ {% else %} {{ forms.0.alternative_domain|add_label_class:"usa-label" }}
www. - {{ forms.0.alternative_domain|add_class:"usa-input" }} + {{ forms.0.alternative_domain|add_class:"usa-input"|attr:"aria-describedby:domain_instructions" }} .gov
+ {% endif %}
-
+ {% csrf_token %}
What is the name and mailing address of your organization? {% if is_federal %} - {{ forms.0.federal_agency|add_label_class:"usa-label" }} - {{ forms.0.federal_agency|add_class:"usa-select" }} + {% select_with_errors forms.0.federal_agency required=True %} {% endif %} - {{ forms.0.organization_name|add_label_class:"usa-label" }} - {{ forms.0.organization_name|add_class:"usa-input" }} - {{ forms.0.address_line1|add_label_class:"usa-label" }} - {{ forms.0.address_line1|add_class:"usa-input" }} - {{ forms.0.address_line2|add_label_class:"usa-label" }} - {{ forms.0.address_line2|add_class:"usa-input" }} - {{ forms.0.city|add_label_class:"usa-label" }} - {{ forms.0.city|add_class:"usa-input" }} - {{ forms.0.state_territory|add_label_class:"usa-label" }} - {{ forms.0.state_territory|add_class:"usa-select" }} - {{ forms.0.zipcode|add_label_class:"usa-label" }} - {{ forms.0.zipcode|add_class:"usa-input usa-input--small" }} - {{ forms.0.urbanization|add_label_class:"usa-label" }} - {{ forms.0.urbanization|add_class:"usa-input usa-input--small" }} + {% input_with_errors forms.0.organization_name %} + + {% input_with_errors forms.0.address_line1 %} + + {% input_with_errors forms.0.address_line2 %} + + {% input_with_errors forms.0.city %} + + {% select_with_errors forms.0.state_territory %} + + {% input_with_errors forms.0.zipcode add_class="usa-input--small" %} + + {% input_with_errors forms.0.urbanization %}
{{ block.super }} - +
{% endblock %} diff --git a/src/registrar/templates/application_org_election.html b/src/registrar/templates/application_org_election.html index 0a9f5b452..e92b0bc8a 100644 --- a/src/registrar/templates/application_org_election.html +++ b/src/registrar/templates/application_org_election.html @@ -5,15 +5,16 @@ {% block form_content %} -
+ {% csrf_token %}

Is your organization an election office?

+

This question is required.

{% radio_buttons_by_value forms.0.is_election_board as choices %} {% for choice in choices.values %} - {% include "includes/radio_button.html" with choice=choice tile="true" %} + {% include "includes/radio_button.html" with choice=choice tile="true" required="true"%} {% endfor %}
diff --git a/src/registrar/templates/application_org_federal.html b/src/registrar/templates/application_org_federal.html index 29bed3fd2..2c54a1356 100644 --- a/src/registrar/templates/application_org_federal.html +++ b/src/registrar/templates/application_org_federal.html @@ -5,11 +5,12 @@ {% block form_content %} - + {% csrf_token %}
-

Which federal branch is your organization in?

+

Which federal branch is your organization in?

+

This question is required.

{% radio_buttons_by_value forms.0.federal_type as choices %} {% for choice in choices.values %} diff --git a/src/registrar/templates/application_org_type.html b/src/registrar/templates/application_org_type.html index 4f5632fda..e20f0ed58 100644 --- a/src/registrar/templates/application_org_type.html +++ b/src/registrar/templates/application_org_type.html @@ -5,14 +5,15 @@ {% block form_content %} - + {% csrf_token %} {% radio_buttons_by_value forms.0.organization_type as choices %}
-

What kind of U.S.-based government organization do you represent?

+

What kind of U.S.-based government organization do you represent?

+

This question is required.

{{ forms.0.organization_type.errors }} {% for choice in choices.values %} @@ -20,7 +21,7 @@ {% endfor %}
- {{ block.super }} + {{ block.super }} {% endblock %} diff --git a/src/registrar/templates/application_other_contacts.html b/src/registrar/templates/application_other_contacts.html index 10efd480b..425ed317a 100644 --- a/src/registrar/templates/application_other_contacts.html +++ b/src/registrar/templates/application_other_contacts.html @@ -2,36 +2,31 @@ {% extends 'application_form.html' %} {% load widget_tweaks %} {% load static %} +{% load field_helpers %} {% block form_content %}

We’d like to contact other employees with administrative or technical responsibilities in your organization. For example, they could be involved in managing your organization or its technical infrastructure. This information will help us assess your eligibility and understand the purpose of the .gov domain. These contacts should be in addition to you and your authorizing official.

-

All fields are required unless they are marked optional.

+{% include "includes/required_fields.html" %} -
+ {% csrf_token %}

Contact 2

- {{ forms.0.first_name|add_label_class:"usa-label" }} - {{ forms.0.first_name|add_class:"usa-input"|attr:"aria-describedby:instructions" }} + {% input_with_errors forms.0.first_name %} - {{ forms.0.middle_name|add_label_class:"usa-label" }} - {{ forms.0.middle_name|add_class:"usa-input"|attr:"aria-describedby:instructions" }} + {% input_with_errors forms.0.middle_name %} - {{ forms.0.last_name|add_label_class:"usa-label" }} - {{ forms.0.last_name|add_class:"usa-input"|attr:"aria-describedby:instructions" }} - - {{ forms.0.title|add_label_class:"usa-label" }} - {{ forms.0.title|add_class:"usa-input"|attr:"aria-describedby:instructions" }} + {% input_with_errors forms.0.last_name %} - {{ forms.0.email|add_label_class:"usa-label" }} - {{ forms.0.email|add_class:"usa-input"|attr:"aria-describedby:instructions" }} + {% input_with_errors forms.0.title %} - {{ forms.0.phone|add_label_class:"usa-label" }} - {{ forms.0.phone|add_class:"usa-input usa-input--medium"|attr:"aria-describedby:instructions" }} + {% input_with_errors forms.0.email %} + + {% input_with_errors forms.0.phone add_class="usa-input--medium" %}
diff --git a/src/registrar/templates/application_purpose.html b/src/registrar/templates/application_purpose.html index 0812534fe..2a6f922ef 100644 --- a/src/registrar/templates/application_purpose.html +++ b/src/registrar/templates/application_purpose.html @@ -5,16 +5,32 @@ {% block form_content %}

.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.

- -

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 activities that are prohibited on .gov domains.

- +

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 activities that are prohibited on .gov domains.

+ +

This question is required.

+ +
{% csrf_token %} - +
- {{ forms.0.purpose|add_label_class:"usa-label usa-sr-only" }} - {{ forms.0.purpose|add_class:"usa-textarea usa-character-count__field"|attr:"aria-describedby:instructions"|attr:"maxlength=500" }} + {% with field=forms.0.purpose %} + {% if field.errors %} +
+ {{ field|add_label_class:"usa-label usa-label--error usa-sr-only" }} + {% for error in field.errors %} + + {{ error }} + + {% endfor %} + {{ field|add_class:"usa-input--error usa-textarea usa-character-count__field"|attr:"aria-describedby:instructions"|attr:"maxlength=500"|attr:"aria-invalid:true" }} +
+ {% 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 %} You can enter up to 500 characters
diff --git a/src/registrar/templates/application_requirements.html b/src/registrar/templates/application_requirements.html index f0e995851..d313b901d 100644 --- a/src/registrar/templates/application_requirements.html +++ b/src/registrar/templates/application_requirements.html @@ -127,14 +127,30 @@

Acknowledgement of .gov domain requirements

- +

This question is required.

+ +
{% csrf_token %} -
- {{ forms.0.is_policy_acknowledged|add_class:"usa-checkbox__input"}} - {{ forms.0.is_policy_acknowledged|add_label_class:"usa-checkbox__label" }} -
+ {% if forms.0.is_policy_acknowledged.errors %} +
+ {% for error in forms.0.is_policy_acknowledged.errors %} + + {{ error }} + + {% endfor %} +
+ {{ 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" }} +
+
+ {% else %} +
+ {{ forms.0.is_policy_acknowledged|add_class:"usa-checkbox__input"|attr:"required"}} + {{ forms.0.is_policy_acknowledged|add_label_class:"usa-checkbox__label" }} +
+ {% endif %}
{{ block.super }} diff --git a/src/registrar/templates/application_security_email.html b/src/registrar/templates/application_security_email.html index 1d0b6f640..36595ad38 100644 --- a/src/registrar/templates/application_security_email.html +++ b/src/registrar/templates/application_security_email.html @@ -7,13 +7,24 @@

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. Security emails are made public. We recommend using an alias, like security@<domain.gov>.

- + {% csrf_token %} - {{ forms.0.security_email|add_label_class:"usa-label" }} - {{ forms.0.security_email|add_class:"usa-input"|attr:"aria-describedby:instructions" }} - - {{ block.super }} + {% if forms.0.security_email.errors %} +
+ {{ forms.0.security_email|add_label_class:"usa-label usa-label--error" }} + {% for error in forms.0.security_email.errors %} + + {{ error }} + + {% endfor %} + {{ forms.0.security_email|add_class:"usa-input"|add_class:"usa-input--error"|attr:"aria-describedby:instructions"|attr:"aria-invalid:true" }} +
+ {% 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 }} diff --git a/src/registrar/templates/application_your_contact.html b/src/registrar/templates/application_your_contact.html index 5a9ec58b7..b3cab07d0 100644 --- a/src/registrar/templates/application_your_contact.html +++ b/src/registrar/templates/application_your_contact.html @@ -2,6 +2,7 @@ {% extends 'application_form.html' %} {% load widget_tweaks %} {% load static %} +{% load field_helpers %} {% block form_content %} @@ -13,32 +14,26 @@

The contact information you provide here won’t be public and will only be used for the .gov registry.

-

All fields are required unless they are marked optional.

+{% include "includes/required_fields.html" %} -
+ {% csrf_token %}
Your contact information - {{ forms.0.first_name|add_label_class:"usa-label" }} - {{ forms.0.first_name|add_class:"usa-input"|attr:"aria-describedby:instructions" }} + {% input_with_errors forms.0.first_name %} - {{ forms.0.middle_name|add_label_class:"usa-label" }} - {{ forms.0.middle_name|add_class:"usa-input"|attr:"aria-describedby:instructions" }} + {% input_with_errors forms.0.middle_name %} - {{ forms.0.last_name|add_label_class:"usa-label" }} - {{ forms.0.last_name|add_class:"usa-input"|attr:"aria-describedby:instructions" }} - - {{ forms.0.title|add_label_class:"usa-label" }} - {{ forms.0.title|add_class:"usa-input"|attr:"aria-describedby:instructions" }} + {% input_with_errors forms.0.last_name %} - {{ forms.0.email|add_label_class:"usa-label" }} - {{ forms.0.email|add_class:"usa-input"|attr:"aria-describedby:instructions" }} + {% input_with_errors forms.0.title %} - {{ forms.0.phone|add_label_class:"usa-label" }} - {{ forms.0.phone|add_class:"usa-input usa-input--medium"|attr:"aria-describedby:instructions" }} + {% input_with_errors forms.0.email %} + + {% input_with_errors forms.0.phone add_class="usa-input--medium" %}
diff --git a/src/registrar/templates/includes/contact.html b/src/registrar/templates/includes/contact.html index 193de3187..68c243558 100644 --- a/src/registrar/templates/includes/contact.html +++ b/src/registrar/templates/includes/contact.html @@ -2,5 +2,5 @@ {{ contact.get_formatted_name }}
{% if contact.title %}{{ contact.title }}
{% endif %} {% if contact.email %}{{ contact.email }}
{% endif %} - {% if contact.phone %}{{ contact.phone }}{% endif %} - \ No newline at end of file + {% if contact.phone %}{{ contact.phone.as_national }}{% endif %} + diff --git a/src/registrar/templates/includes/input_with_errors.html b/src/registrar/templates/includes/input_with_errors.html new file mode 100644 index 000000000..a1662c40b --- /dev/null +++ b/src/registrar/templates/includes/input_with_errors.html @@ -0,0 +1,29 @@ +{% comment %} +Template include for form fields with classes and their corresponding +error messages, if necessary. +{% endcomment %} + +{% load widget_tweaks %} + +{% if field.errors %} +
+ {{ field|add_label_class:"usa-label usa-label--error" }} + {% for error in field.errors %} + + {{ error }} + + {% 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 %} +
+{% else %} + {{ field|add_label_class:"usa-label" }} + {% if required %} + {{ field|add_class:input_class|attr:"required" }} + {% else %} + {{ field|add_class:input_class }} + {% endif %} +{% endif %} diff --git a/src/registrar/templates/includes/radio_button.html b/src/registrar/templates/includes/radio_button.html index 85f64b831..5a3edc9f4 100644 --- a/src/registrar/templates/includes/radio_button.html +++ b/src/registrar/templates/includes/radio_button.html @@ -5,7 +5,7 @@ value="{{ choice.data.value }}" class="usa-radio__input {% if tile %}usa-radio__input--tile {%endif%}" id="{{ choice.id_for_label }}" - {% if choice.data.attrs.required %}required{% endif %} + {% if choice.data.attrs.required or required %}required{% endif %} {% if choice.data.selected %}checked{% endif %} />