From 0e3f68622f11d1db21f2224d93adfa7acde6669a Mon Sep 17 00:00:00 2001 From: Seamus Johnston Date: Thu, 2 Feb 2023 14:30:00 -0600 Subject: [PATCH] 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 --- src/registrar/forms/application_wizard.py | 77 +----- src/registrar/models/domain.py | 2 +- src/registrar/models/domain_application.py | 1 - .../templates/application_anything_else.html | 31 +-- .../application_authorizing_official.html | 38 ++- .../templates/application_current_sites.html | 47 ++-- .../templates/application_dotgov_domain.html | 93 ++++--- src/registrar/templates/application_form.html | 65 +++-- .../templates/application_org_contact.html | 42 ++-- .../templates/application_org_election.html | 33 +-- .../templates/application_org_federal.html | 33 +-- .../templates/application_org_type.html | 36 +-- .../templates/application_other_contacts.html | 47 ++-- .../templates/application_purpose.html | 54 ++-- .../templates/application_requirements.html | 234 ++++++++++-------- .../templates/application_review.html | 13 +- .../templates/application_security_email.html | 39 +-- .../application_tribal_government.html | 37 +-- .../templates/application_type_of_work.html | 63 +---- .../templates/application_your_contact.html | 36 ++- .../templates/django/forms/label.html | 9 + .../templates/django/forms/widgets/attrs.html | 3 + .../django/forms/widgets/checkbox.html | 3 + .../templates/django/forms/widgets/email.html | 3 + .../django/forms/widgets/hidden.html | 1 + .../templates/django/forms/widgets/input.html | 8 + .../django/forms/widgets/multiple_input.html | 20 ++ .../django/forms/widgets/number.html | 3 + .../django/forms/widgets/password.html | 3 + .../templates/django/forms/widgets/radio.html | 3 + .../django/forms/widgets/radio_option.html | 1 + .../forms/widgets/regionalphonenumber.html | 3 + .../django/forms/widgets/select.html | 14 ++ .../django/forms/widgets/select_option.html | 1 + .../templates/django/forms/widgets/text.html | 3 + .../django/forms/widgets/textarea.html | 5 + .../templates/django/forms/widgets/url.html | 3 + .../templates/includes/input_with_errors.html | 95 ++++--- .../templates/includes/radio_button.html | 14 -- src/registrar/templatetags/field_helpers.py | 155 +++++++++--- src/registrar/views/application.py | 19 +- src/registrar/views/utility/steps_helper.py | 2 +- 42 files changed, 740 insertions(+), 652 deletions(-) create mode 100644 src/registrar/templates/django/forms/label.html create mode 100644 src/registrar/templates/django/forms/widgets/attrs.html create mode 100644 src/registrar/templates/django/forms/widgets/checkbox.html create mode 100644 src/registrar/templates/django/forms/widgets/email.html create mode 100644 src/registrar/templates/django/forms/widgets/hidden.html create mode 100644 src/registrar/templates/django/forms/widgets/input.html create mode 100644 src/registrar/templates/django/forms/widgets/multiple_input.html create mode 100644 src/registrar/templates/django/forms/widgets/number.html create mode 100644 src/registrar/templates/django/forms/widgets/password.html create mode 100644 src/registrar/templates/django/forms/widgets/radio.html create mode 100644 src/registrar/templates/django/forms/widgets/radio_option.html create mode 100644 src/registrar/templates/django/forms/widgets/regionalphonenumber.html create mode 100644 src/registrar/templates/django/forms/widgets/select.html create mode 100644 src/registrar/templates/django/forms/widgets/select_option.html create mode 100644 src/registrar/templates/django/forms/widgets/text.html create mode 100644 src/registrar/templates/django/forms/widgets/textarea.html create mode 100644 src/registrar/templates/django/forms/widgets/url.html delete mode 100644 src/registrar/templates/includes/radio_button.html diff --git a/src/registrar/forms/application_wizard.py b/src/registrar/forms/application_wizard.py index 6f08a692b..cec6a777f 100644 --- a/src/registrar/forms/application_wizard.py +++ b/src/registrar/forms/application_wizard.py @@ -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 - ' *' -) - 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 + }, + ) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index e05f2fda4..eb89cd387 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -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}(? bool: diff --git a/src/registrar/models/domain_application.py b/src/registrar/models/domain_application.py index 7db923a92..7924b2d2f 100644 --- a/src/registrar/models/domain_application.py +++ b/src/registrar/models/domain_application.py @@ -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", diff --git a/src/registrar/templates/application_anything_else.html b/src/registrar/templates/application_anything_else.html index 3893101d6..aba7ded8a 100644 --- a/src/registrar/templates/application_anything_else.html +++ b/src/registrar/templates/application_anything_else.html @@ -1,23 +1,14 @@ {% extends 'application_form.html' %} -{% load widget_tweaks %} - -{% block form_content %} - -

Is there anything else we should know about your domain request?

- -
-
- {% csrf_token %} - -
- {{ 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" }} - You can enter up to 500 characters -
-
- - {{ block.super }} - -
+{% load field_helpers %} +{% block form_instructions %} +

Is there anything else we should know about your domain request?

{% 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 %} \ No newline at end of file diff --git a/src/registrar/templates/application_authorizing_official.html b/src/registrar/templates/application_authorizing_official.html index b7fde0fb9..3ef5c3892 100644 --- a/src/registrar/templates/application_authorizing_official.html +++ b/src/registrar/templates/application_authorizing_official.html @@ -1,28 +1,27 @@ {% extends 'application_form.html' %} -{% load widget_tweaks %} -{% load static %} {% load field_helpers %} -{% block form_content %} +{% block form_instructions %} +

+ Who is the authorizing official for your organization? +

-

Who is the authorizing official for your organization?

- -
-

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 who can serve as an authorizing official. -

+

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 who can serve as an + authorizing official.

{% include "includes/ao_example.html" %}
-

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

-
+

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

+{% endblock %} -{% include "includes/required_fields.html" %} -
+{% block form_fields %} {% csrf_token %} -
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 %} +
- - - {{ block.super }} - -
- -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/src/registrar/templates/application_current_sites.html b/src/registrar/templates/application_current_sites.html index 2d858353f..a99da2095 100644 --- a/src/registrar/templates/application_current_sites.html +++ b/src/registrar/templates/application_current_sites.html @@ -1,31 +1,24 @@ {% extends 'application_form.html' %} -{% load widget_tweaks field_helpers %} {% load static %} +{% load field_helpers %} -{% block form_content %} - -
- {% csrf_token %} -
- {{ forms.0.management_form }} - {# TODO: aria-describedby to associate these instructions with the input! #} -

- Enter your organization’s public website, if you have one. For example, www.city.com. -

- {% for form in forms.0 %} - {% input_with_errors form.website %} - {% endfor %} - - -
-
- - {{ block.super }} - - - +{% block form_instructions %} +

Enter your organization’s public website, if you have one. For example, + www.city.com.

{% endblock %} + + +{% block form_fields %} + {% csrf_token %} + {{ forms.0.management_form }} + + {% for form in forms.0 %} + {% input_with_errors form.website %} + {% endfor %} + + +{% endblock %} \ No newline at end of file diff --git a/src/registrar/templates/application_dotgov_domain.html b/src/registrar/templates/application_dotgov_domain.html index 0c80a8692..02bc9522b 100644 --- a/src/registrar/templates/application_dotgov_domain.html +++ b/src/registrar/templates/application_dotgov_domain.html @@ -1,58 +1,83 @@ {% extends 'application_form.html' %} -{% load widget_tweaks field_helpers static %} +{% load static %} +{% load field_helpers %} -{% block form_content %} -
-

Before requesting a .gov domain, please make sure it meets our naming requirements. Your domain name must: -

    -
  • Be available
  • -
  • Be unique
  • -
  • Relate to your organization’s name, location, and/or services
  • -
  • Be clear to the general public. Your domain name must not be easily confused with other organizations.
  • -
+{% block form_instructions %} +

Before requesting a .gov domain, please make sure it + meets our naming requirements. Your domain name must: +

    +
  • Be available
  • +
  • Be unique
  • +
  • Relate to your organization’s name, location, and/or services
  • +
  • Be clear to the general public. Your domain name must not be easily confused + with other organizations.
  • +

-

Note that only federal agencies can request generic terms like vote.gov.

+

Note that only federal agencies can request generic terms like + vote.gov.

-

We’ll try to give you the domain you want. We first need to make sure your request meets our requirements. We’ll work with you to find the best domain for your organization.

+

We’ll try to give you the domain you want. We first need to make sure your request + meets our requirements. We’ll work with you to find the best domain for your + organization.

Here are a few domain examples for your type of organization.

{% include "includes/domain_example.html" %}
-
+{% endblock %} -
-

What .gov domain do you want?

- {# TODO: aria-describedby to associate these instructions with the input! #} -

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.

+{% block form_fields %} {% csrf_token %} - {% input_with_errors forms.0.requested_domain www_gov=True %} - -

Alternative domains

+ {{ forms.0.management_form }} -
- {{ forms.1.management_form }} - {# TODO: aria-describedby to associate these instructions with the input! #} -

Are there other domains you’d like if we can’t give you your first choice? Entering alternative domains is optional.

+
+ +

What .gov domain do you want?

+
- {% for form in forms.1 %} - {% input_with_errors form.alternative_domain www_gov=True %} - {% endfor %} +

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.

+ + {% with attr_aria_describedby="domain_instructions domain_instructions2" %} + {% with www_gov=True attr_validate="domain" %} + {% input_with_errors forms.0.requested_domain %} + {% endwith %} + {% endwith %} + + +
+ + {{ forms.1.management_form }} + +
+ +

Alternative domains

+
+ +

Are there other domains you’d like if we can’t give + you your first choice? Entering alternative domains is optional.

+ + {% 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 %} + {% endfor %} + {% endwith %} + {% endwith %} -
-

If you’re not sure this is the domain you want, that’s okay. You can change it later.

+ - {{ block.super }} - -
- -{% endblock %} +

If you’re not sure this is the domain you want, that’s + okay. You can change it later.

+{% endblock %} \ No newline at end of file diff --git a/src/registrar/templates/application_form.html b/src/registrar/templates/application_form.html index f67efb071..d349b7015 100644 --- a/src/registrar/templates/application_form.html +++ b/src/registrar/templates/application_form.html @@ -18,6 +18,7 @@ {% endif %} +{% block form_errors %} {% comment %} to make sense of this loop, consider that a context variable of `forms` contains all @@ -34,35 +35,51 @@ {% include "includes/form_errors.html" with form=outer %} {% endif %} {% endfor %} +{% endblock %}

{{form_titles|get_item:steps.current}}

- {% block form_content %} -
- {% if steps.next %} - - {% else %} - - {% endif %} - -
+{% block form_instructions %} +{% endblock %} + +{% block form_required_fields_help_text %} + {% include "includes/required_fields.html" %} +{% endblock %} + +
+ +{% block form_fields %}{% endblock %} + +{% block form_buttons %} +
+ {% if steps.next %} + + {% else %} + + {% endif %} + +
+{% endblock %} + +
+ +{% block after_form_content %}{% endblock %}
- {% endblock %} -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/src/registrar/templates/application_org_contact.html b/src/registrar/templates/application_org_contact.html index cf3090c20..12a475c83 100644 --- a/src/registrar/templates/application_org_contact.html +++ b/src/registrar/templates/application_org_contact.html @@ -1,29 +1,30 @@ {% extends 'application_form.html' %} -{% load widget_tweaks field_helpers %} +{% load field_helpers %} -{% block form_content %} +{% block form_instructions %} +

+ What is the name and mailing address of your organization? +

- -

What is the name and mailing address of your organization?

- -

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.

-

If your domain request is approved, the name of your organization will be publicly listed as the domain registrant.

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

If your domain request is approved, the name of your organization will be publicly + listed as the domain registrant.

+{% endblock %} -
+{% block form_fields %} {% csrf_token %} -
- What is the name and mailing address of your organization? + + What is the name and mailing address of your organization? + {% 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 %}
- - {{ block.super }} - -
- -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/src/registrar/templates/application_org_election.html b/src/registrar/templates/application_org_election.html index 1b7b92994..321711afe 100644 --- a/src/registrar/templates/application_org_election.html +++ b/src/registrar/templates/application_org_election.html @@ -1,23 +1,16 @@ {% extends 'application_form.html' %} -{% load widget_tweaks %} -{% load dynamic_question_tags %} +{% load field_helpers %} -{% 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" required="true"%} - {% endfor %} -
- - {{ block.super }} - -
+{% block form_instructions %} +

+ Is your organization an election office? +

{% endblock %} + + +{% block form_fields %} + {% csrf_token %} + {% with add_class="usa-radio__input--tile" %} + {% input_with_errors forms.0.is_election_board %} + {% endwith %} +{% endblock %} \ No newline at end of file diff --git a/src/registrar/templates/application_org_federal.html b/src/registrar/templates/application_org_federal.html index 26d84a864..d0cb8dfcb 100644 --- a/src/registrar/templates/application_org_federal.html +++ b/src/registrar/templates/application_org_federal.html @@ -1,23 +1,16 @@ {% extends 'application_form.html' %} -{% load widget_tweaks %} -{% load dynamic_question_tags %} +{% load field_helpers %} -{% block form_content %} - -
- {% csrf_token %} -
- -

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 %} - {% include "includes/radio_button.html" with choice=choice tile="true" %} - {% endfor %} -
- - {{ block.super }} - -
+{% block form_instructions %} +

+ Which federal branch is your organization in? +

{% endblock %} + + +{% block form_fields %} + {% csrf_token %} + {% with add_class="usa-radio__input--tile" %} + {% input_with_errors forms.0.federal_type %} + {% endwith %} +{% endblock %} \ No newline at end of file diff --git a/src/registrar/templates/application_org_type.html b/src/registrar/templates/application_org_type.html index f74b4059d..2f59340f4 100644 --- a/src/registrar/templates/application_org_type.html +++ b/src/registrar/templates/application_org_type.html @@ -1,26 +1,16 @@ {% extends 'application_form.html' %} -{% load widget_tweaks %} -{% load dynamic_question_tags %} +{% load field_helpers %} -{% 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?

-

This question is required.

-
- {{ forms.0.organization_type.errors }} - {% for choice in choices.values %} - {% include "includes/radio_button.html" with choice=choice tile="true" %} - {% endfor %} -
- - {{ block.super }} - -
+{% block form_instructions %} +

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

{% endblock %} + + +{% block form_fields %} + {% csrf_token %} + {% with add_class="usa-radio__input--tile" %} + {% input_with_errors forms.0.organization_type %} + {% endwith %} +{% endblock %} \ No newline at end of file diff --git a/src/registrar/templates/application_other_contacts.html b/src/registrar/templates/application_other_contacts.html index 834dd9805..f2d2d5e6f 100644 --- a/src/registrar/templates/application_other_contacts.html +++ b/src/registrar/templates/application_other_contacts.html @@ -1,41 +1,48 @@ {% extends 'application_form.html' %} -{% load widget_tweaks %} {% load static %} {% load field_helpers %} -{% block form_content %} +{% block form_instructions %} +

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

+{% endblock %} -

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.

-{% include "includes/required_fields.html" %} -
+{% 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 %}
-

Contact {{ forloop.counter }}

+

Contact {{ forloop.counter }}

+ + {% 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 %} +
{% endfor %} -
- -
- - {{ block.super }} - -
- -{% endblock %} + +{% endblock %} \ No newline at end of file diff --git a/src/registrar/templates/application_purpose.html b/src/registrar/templates/application_purpose.html index c2ed0a8ba..326358c5a 100644 --- a/src/registrar/templates/application_purpose.html +++ b/src/registrar/templates/application_purpose.html @@ -1,41 +1,21 @@ {% extends 'application_form.html' %} -{% load widget_tweaks %} +{% load field_helpers %} -{% 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.

-

This question is required.

-
- -
-
- {% csrf_token %} - -
- {% 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 -
-
- - {{ block.super }} - -
+{% block form_instructions %} +

.Gov domain names are intended for use on the internet. They should be registered + with an intent to deploy services, not simply to reserve a name. .Gov domains should + not be registered for primarily internal use.

+

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.

{% endblock %} + + +{% block form_fields %} + {% csrf_token %} + {% with attr_maxlength=500 %} + {% input_with_errors forms.0.purpose %} + {% endwith %} +{% endblock %} \ No newline at end of file diff --git a/src/registrar/templates/application_requirements.html b/src/registrar/templates/application_requirements.html index b44ea2eee..18b3fc76c 100644 --- a/src/registrar/templates/application_requirements.html +++ b/src/registrar/templates/application_requirements.html @@ -1,159 +1,195 @@ {% extends 'application_form.html' %} -{% load widget_tweaks %} +{% load field_helpers %} -{% block form_content %} +{% block form_instructions %} +

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.

-

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.

+

Prohibited activities for .gov domains

-

Prohibited activities for .gov domains

+

Commercial purposes

-

Commercial purposes

-

A .gov domain must not be used for commercial purposes, such as advertising benefitting private individuals or entities.

+

A .gov domain must not be used for commercial purposes, such as advertising + benefitting private individuals or entities.

-

Political campaigns

-

A .gov domain must not be used for political campaigns.

+

Political campaigns

-

Illegal content

-

A .gov domain must not be used to distribute or promote material whose distribution violates applicable law.

+

A .gov domain must not be used for political campaigns.

-

Malicious cyber activity

-

.Gov is a trusted and safe space. .Gov domains must not distribute malware, host open redirects, or otherwise engage in malicious cyber activity.

+

Illegal content

+

A .gov domain must not be used to distribute or promote material whose distribution + violates applicable law.

-

Required activities for .gov domain registrants

+

Malicious cyber activity

-

Keep your contact information updated

-

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.

+

.Gov is a trusted and safe space. .Gov domains must not distribute malware, host + open redirects, or otherwise engage in malicious cyber activity.

-

Be responsive if we contact you

-

Registrants should respond in a timely manner to communications about required and prohibited activities.

+

Required activities for .gov domain registrants

+

Keep your contact information updated

-

Domains can be suspended or terminated for violations

-

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.

-

When we discover a violation, we will make reasonable efforts to contact a registrant, including: -

-

+

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.

-

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.

+

Be responsive if we contact you

-

Requirements for authorizing officials

+

Registrants should respond in a timely manner to communications about required and + prohibited activities.

-

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.

+

Domains can be suspended or terminated for violations

-

Executive branch federal agencies

+

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.

-

Domain requests from executive branch agencies must be authorized by CIOs or agency heads.

+

When we discover a violation, we will make reasonable efforts to contact a + registrant, including: +

+

-

Domain requests from executive branch agencies are subject to guidance issued by the U.S. Office of Management and Budget.

+

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.

-

Judicial branch federal agencies

+

Requirements for authorizing officials

-

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.

+

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.

-

Domain requests from the U.S. Supreme Court must be authorized by the director of information technology for the U.S. Supreme Court.

+

Executive branch federal agencies

-

Legislative branch federal agencies

+

Domain requests from executive branch agencies must be authorized by CIOs or agency + heads.

-

U.S. Senate

+

Domain requests from executive branch agencies are subject to guidance issued by + the U.S. Office of Management and Budget.

-

Domain requests from the U.S. Senate must come from the Senate Sergeant at Arms.

+

Judicial branch federal agencies

-

U.S. House of Representatives

+

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.

-

Domain requests from the U.S. House of Representatives must come from the House Chief Administrative Officer.

+

Domain requests from the U.S. Supreme Court must be authorized by the director of + information technology for the U.S. Supreme Court.

-

Other legislative branch agencies

+

Legislative branch federal agencies

-

Domain requests from legislative branch agencies must come from the agency’s head or CIO.

+

U.S. Senate

-

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.

+

Domain requests from the U.S. Senate must come from the Senate Sergeant at Arms.

-

Interstate

+

U.S. House of Representatives

-

Domain requests from interstate organizations must be authorized by the highest-ranking executive (president, director, chair, or equivalent) or one of the state’s governors or CIOs.

+

Domain requests from the U.S. House of Representatives must come from the House + Chief Administrative Officer.

-

U.S. states and territories

+

Other legislative branch agencies

-

States and territories: executive branch

+

Domain requests from legislative branch agencies must come from the agency’s head + or CIO.

-

Domain requests from states and territories must be authorized by the governor or the state CIO.

+

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.

-

States and territories: judicial and legislative branches

+

Interstate

-

Domain requests from state legislatures and courts must be authorized by an agency’s CIO or highest-ranking executive.

+

Domain requests from interstate organizations must be authorized by the + highest-ranking executive (president, director, chair, or equivalent) or one of the + state’s governors or CIOs.

-

Tribal governments

+

U.S. states and territories

-

Domain requests from federally-recognized tribal governments must be authorized by tribal chiefs as noted by the Bureau of Indian Affairs.

+

States and territories: executive branch

-

Counties

+

Domain requests from states and territories must be authorized by the governor or + the state CIO.

-

Domain requests from counties must be authorized by the chair of the county commission or the equivalent highest elected official.

+

States and territories: judicial and legislative branches

-

Cities

+

Domain requests from state legislatures and courts must be authorized by an + agency’s CIO or highest-ranking executive.

-

Domain requests from cities must be authorized by the mayor or the equivalent highest elected official.

+

Tribal governments

-

Special districts

+

Domain requests from federally-recognized tribal governments must be authorized by + tribal chiefs as noted by the + Bureau of Indian + Affairs.

-

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.

+

Counties

-

School districts

+

Domain requests from counties must be authorized by the chair of the county + commission or the equivalent highest elected official.

-

Domain requests from school district governments must be authorized by the highest-ranking executive (the chair of a school district’s board or a superintendent).

+

Cities

-

Requirements for .gov domain names

-

.Gov domains must:

- +

Domain requests from cities must be authorized by the mayor or the equivalent + highest elected official.

+

Special districts

-

HSTS preloading

-

The .gov program will preload all newly registered .gov domains for HTTP Strict Transport Security (HSTS).

-

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

-

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.

+

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.

+

School districts

-

Acknowledgement of .gov domain requirements

+

Domain requests from school district governments must be authorized by the highest-ranking + executive (the chair of a school district’s board or a superintendent).

-

This question is required.

+

Requirements for .gov domain names

-
-
- {% csrf_token %} +

.Gov domains must: +

    +
  • Be available
  • +
  • Be unique
  • +
  • Relate to your organization’s name, location, and/or services
  • +
  • Be clear to the general public. Your domain name must not be easily confused + with other organizations.
  • +
+

- {% 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 %} -
+

HSTS preloading

- {{ block.super }} +

The .gov program will preload all newly registered .gov domains for HTTP Strict + Transport Security (HSTS).

-
+

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

+

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.

{% endblock %} + + +{% block form_fields %} + {% csrf_token %} +
+ +

Acknowledgement of .gov domain requirements

+
+ + {% input_with_errors forms.0.is_policy_acknowledged %} + +
+{% endblock %} \ No newline at end of file diff --git a/src/registrar/templates/application_review.html b/src/registrar/templates/application_review.html index 615c245f7..b3118d747 100644 --- a/src/registrar/templates/application_review.html +++ b/src/registrar/templates/application_review.html @@ -1,9 +1,7 @@ {% extends 'application_form.html' %} -{% load static widget_tweaks namespaced_urls %} +{% load static namespaced_urls %} -{% block form_content %} - -
+{% block form_fields %} {% csrf_token %} {% for step in steps.all|slice:":-1" %} @@ -97,9 +95,4 @@ {% endfor %} - - {{ block.super }} - -
- -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/src/registrar/templates/application_security_email.html b/src/registrar/templates/application_security_email.html index f909a024b..9a240c264 100644 --- a/src/registrar/templates/application_security_email.html +++ b/src/registrar/templates/application_security_email.html @@ -1,30 +1,15 @@ {% extends 'application_form.html' %} -{% load widget_tweaks %} -{% load static %} - -{% block form_content %} - -

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 %} - - {% 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 }} - -
+{% load field_helpers %} +{% block form_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. + Security emails are made public. We recommend using an alias, like + security@<domain.gov>.

{% endblock %} + + +{% block form_fields %} + {% csrf_token %} + {% input_with_errors forms.0.security_email %} +{% endblock %} \ No newline at end of file diff --git a/src/registrar/templates/application_tribal_government.html b/src/registrar/templates/application_tribal_government.html index 972baba7a..48e26fcb7 100644 --- a/src/registrar/templates/application_tribal_government.html +++ b/src/registrar/templates/application_tribal_government.html @@ -1,27 +1,16 @@ - {% extends 'application_form.html' %} +{% load field_helpers %} -{% block form_content %} -{% load widget_tweaks dynamic_question_tags field_helpers %} -
- {% input_with_errors forms.0.tribe_name %} - -

Please check all that apply.

-
- {% csrf_token %} - -
- {{ forms.0.federally_recognized_tribe|add_class:"usa-checkbox__input"|attr:"required"}} - {{ forms.0.federally_recognized_tribe|add_label_class:"usa-checkbox__label" }} -
-
- {{ forms.0.state_recognized_tribe|add_class:"usa-checkbox__input"|attr:"required"}} - {{ forms.0.state_recognized_tribe|add_label_class:"usa-checkbox__label" }} -
-
- - {{ block.super }} - -
-{% endblock %} +{% block form_fields %} + {% csrf_token %} + {% input_with_errors forms.0.tribe_name %} +
+ +

Please check all that apply. + *

+
+ {% input_with_errors forms.0.federally_recognized_tribe %} + {% input_with_errors forms.0.state_recognized_tribe %} +
+{% endblock %} \ No newline at end of file diff --git a/src/registrar/templates/application_type_of_work.html b/src/registrar/templates/application_type_of_work.html index f7769785e..78650eb01 100644 --- a/src/registrar/templates/application_type_of_work.html +++ b/src/registrar/templates/application_type_of_work.html @@ -1,58 +1,11 @@ {% extends 'application_form.html' %} -{% load widget_tweaks %} +{% load field_helpers %} -{% block form_content %} -{% include "includes/required_fields.html" %} - -
-
- {% csrf_token %} - -
- {% with field=forms.0.type_of_work %} - {% if field.errors %} -
- {{ field|add_label_class:"usa-label usa-label--error" }} - {% for error in field.errors %} - - {{ error }} - - {% endfor %} - {{ field|add_class:"usa-input--error usa-textarea usa-character-count__field"|attr:"maxlength=500"|attr:"aria-invalid:true" }} -
- {% else %} - {{ field|add_label_class:"usa-label" }} - {{ field|add_class:"usa-textarea usa-character-count__field"|attr:"maxlength=500" }} - {% endif %} - {% endwith %} - You can enter up to 500 characters -
-
- -
-
- {% with field=forms.0.more_organization_information %} - {% if field.errors %} -
- {{ field|add_label_class:"usa-label usa-label--error" }} - {% for error in field.errors %} - - {{ error }} - - {% endfor %} - {{ field|add_class:"usa-input--error usa-textarea usa-character-count__field"|attr:"maxlength=500"|attr:"aria-invalid:true" }} -
- {% else %} - {{ field|add_label_class:"usa-label" }} - {{ field|add_class:"usa-textarea usa-character-count__field"|attr:"maxlength=500" }} - {% endif %} - {% endwith %} - You can enter up to 500 characters -
-
- {{ block.super }} - -
- -{% endblock %} +{% block form_fields %} + {% csrf_token %} + {% with attr_maxlength=500 %} + {% input_with_errors forms.0.type_of_work %} + {% input_with_errors forms.0.more_organization_information %} + {% endwith %} +{% endblock %} \ No newline at end of file diff --git a/src/registrar/templates/application_your_contact.html b/src/registrar/templates/application_your_contact.html index 6acec24d4..a5d962c35 100644 --- a/src/registrar/templates/application_your_contact.html +++ b/src/registrar/templates/application_your_contact.html @@ -1,27 +1,26 @@ {% extends 'application_form.html' %} -{% load widget_tweaks %} -{% load static %} {% load field_helpers %} -{% block form_content %} +{% block form_instructions %} +

We’ll use the following information to contact you about your domain request and, + once your request is approved, about managing your domain.

-
-

We’ll use the following information to contact you about your domain request and, once your request is approved, about managing your domain.

+

If you’d like us to use a different name, email, or phone number you can make those + changes below. Changing your contact information here won’t affect your login.gov + account information.

-

If you’d like us to use a different name, email, or phone number you can make those changes below. Changing your contact information here won’t affect your login.gov account information.

+

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

+{% endblock %} -

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

-
-{% include "includes/required_fields.html" %} - -
+{% block form_fields %} {% csrf_token %} -
Your contact information + {% 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 %} +
- - - {{ block.super }} - -
- -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/src/registrar/templates/django/forms/label.html b/src/registrar/templates/django/forms/label.html new file mode 100644 index 000000000..9a1b6ba6e --- /dev/null +++ b/src/registrar/templates/django/forms/label.html @@ -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 %} + * +{% endif %} + \ No newline at end of file diff --git a/src/registrar/templates/django/forms/widgets/attrs.html b/src/registrar/templates/django/forms/widgets/attrs.html new file mode 100644 index 000000000..78dcb5070 --- /dev/null +++ b/src/registrar/templates/django/forms/widgets/attrs.html @@ -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 %} \ No newline at end of file diff --git a/src/registrar/templates/django/forms/widgets/checkbox.html b/src/registrar/templates/django/forms/widgets/checkbox.html new file mode 100644 index 000000000..7af5802a2 --- /dev/null +++ b/src/registrar/templates/django/forms/widgets/checkbox.html @@ -0,0 +1,3 @@ +{% with uswds_input_class="usa-checkbox__input" %} + {% include "django/forms/widgets/input.html" %} +{% endwith %} \ No newline at end of file diff --git a/src/registrar/templates/django/forms/widgets/email.html b/src/registrar/templates/django/forms/widgets/email.html new file mode 100644 index 000000000..9d266b8a9 --- /dev/null +++ b/src/registrar/templates/django/forms/widgets/email.html @@ -0,0 +1,3 @@ +{% with uswds_input_class="usa-input" %} + {% include "django/forms/widgets/input.html" %} +{% endwith %} \ No newline at end of file diff --git a/src/registrar/templates/django/forms/widgets/hidden.html b/src/registrar/templates/django/forms/widgets/hidden.html new file mode 100644 index 000000000..1f4676a22 --- /dev/null +++ b/src/registrar/templates/django/forms/widgets/hidden.html @@ -0,0 +1 @@ +{% include "django/forms/widgets/input.html" %} \ No newline at end of file diff --git a/src/registrar/templates/django/forms/widgets/input.html b/src/registrar/templates/django/forms/widgets/input.html new file mode 100644 index 000000000..ba287df3a --- /dev/null +++ b/src/registrar/templates/django/forms/widgets/input.html @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/src/registrar/templates/django/forms/widgets/multiple_input.html b/src/registrar/templates/django/forms/widgets/multiple_input.html new file mode 100644 index 000000000..90c241366 --- /dev/null +++ b/src/registrar/templates/django/forms/widgets/multiple_input.html @@ -0,0 +1,20 @@ +
+ {% for group, options, index in widget.optgroups %} + {% if group %}
{% endif %} + {% for option in options %} + + + {% endfor %} + {% if group %}
{% endif %} + {% endfor %} +
\ No newline at end of file diff --git a/src/registrar/templates/django/forms/widgets/number.html b/src/registrar/templates/django/forms/widgets/number.html new file mode 100644 index 000000000..9d266b8a9 --- /dev/null +++ b/src/registrar/templates/django/forms/widgets/number.html @@ -0,0 +1,3 @@ +{% with uswds_input_class="usa-input" %} + {% include "django/forms/widgets/input.html" %} +{% endwith %} \ No newline at end of file diff --git a/src/registrar/templates/django/forms/widgets/password.html b/src/registrar/templates/django/forms/widgets/password.html new file mode 100644 index 000000000..9d266b8a9 --- /dev/null +++ b/src/registrar/templates/django/forms/widgets/password.html @@ -0,0 +1,3 @@ +{% with uswds_input_class="usa-input" %} + {% include "django/forms/widgets/input.html" %} +{% endwith %} \ No newline at end of file diff --git a/src/registrar/templates/django/forms/widgets/radio.html b/src/registrar/templates/django/forms/widgets/radio.html new file mode 100644 index 000000000..11881e42a --- /dev/null +++ b/src/registrar/templates/django/forms/widgets/radio.html @@ -0,0 +1,3 @@ +{% with uswds_input_class="usa-radio" %} + {% include "django/forms/widgets/multiple_input.html" %} +{% endwith %} \ No newline at end of file diff --git a/src/registrar/templates/django/forms/widgets/radio_option.html b/src/registrar/templates/django/forms/widgets/radio_option.html new file mode 100644 index 000000000..a10ecaea1 --- /dev/null +++ b/src/registrar/templates/django/forms/widgets/radio_option.html @@ -0,0 +1 @@ +{% include "django/forms/widgets/input_option.html" %} \ No newline at end of file diff --git a/src/registrar/templates/django/forms/widgets/regionalphonenumber.html b/src/registrar/templates/django/forms/widgets/regionalphonenumber.html new file mode 100644 index 000000000..9d266b8a9 --- /dev/null +++ b/src/registrar/templates/django/forms/widgets/regionalphonenumber.html @@ -0,0 +1,3 @@ +{% with uswds_input_class="usa-input" %} + {% include "django/forms/widgets/input.html" %} +{% endwith %} \ No newline at end of file diff --git a/src/registrar/templates/django/forms/widgets/select.html b/src/registrar/templates/django/forms/widgets/select.html new file mode 100644 index 000000000..cc62eb91d --- /dev/null +++ b/src/registrar/templates/django/forms/widgets/select.html @@ -0,0 +1,14 @@ + \ No newline at end of file diff --git a/src/registrar/templates/django/forms/widgets/select_option.html b/src/registrar/templates/django/forms/widgets/select_option.html new file mode 100644 index 000000000..c8405eee2 --- /dev/null +++ b/src/registrar/templates/django/forms/widgets/select_option.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/registrar/templates/django/forms/widgets/text.html b/src/registrar/templates/django/forms/widgets/text.html new file mode 100644 index 000000000..9d266b8a9 --- /dev/null +++ b/src/registrar/templates/django/forms/widgets/text.html @@ -0,0 +1,3 @@ +{% with uswds_input_class="usa-input" %} + {% include "django/forms/widgets/input.html" %} +{% endwith %} \ No newline at end of file diff --git a/src/registrar/templates/django/forms/widgets/textarea.html b/src/registrar/templates/django/forms/widgets/textarea.html new file mode 100644 index 000000000..068f15399 --- /dev/null +++ b/src/registrar/templates/django/forms/widgets/textarea.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/src/registrar/templates/django/forms/widgets/url.html b/src/registrar/templates/django/forms/widgets/url.html new file mode 100644 index 000000000..9d266b8a9 --- /dev/null +++ b/src/registrar/templates/django/forms/widgets/url.html @@ -0,0 +1,3 @@ +{% with uswds_input_class="usa-input" %} + {% include "django/forms/widgets/input.html" %} +{% endwith %} \ No newline at end of file diff --git a/src/registrar/templates/includes/input_with_errors.html b/src/registrar/templates/includes/input_with_errors.html index 4859c8617..54ae475bf 100644 --- a/src/registrar/templates/includes/input_with_errors.html +++ b/src/registrar/templates/includes/input_with_errors.html @@ -5,33 +5,70 @@ error messages, if necessary. {% 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 www_gov %} -
- www. - {% endif %} - {% if required %} - {{ field|add_class:input_class|attr:"required" }} - {% else %} - {{ field|add_class:input_class }} - {% endif %} - {% if www_gov %} - .gov -
- {% endif %} +{% if widget.attrs.maxlength %} +
{% endif %} + +{% if field.use_fieldset %} +
+{% elif field.widget_type == 'checkbox' %} +
+{% else %} +
+{% endif %} + + {% if not field.widget_type == "checkbox" %} + {% include "django/forms/label.html" %} + {% endif %} + + {% if field.errors %} +
+ {% for error in field.errors %} + + {{ error }} + + {% endfor %} +
+ {% endif %} + + {% if www_gov %} +
+ www. + {% endif %} + + {# this is the input field, itself #} + {% include widget.template_name %} + + {% if www_gov %} + .gov +
+ {% endif %} + + {% if field.widget_type == "checkbox" %} + {% include "django/forms/label.html" %} + {% endif %} + +{% if field.use_fieldset %} +
+{% else %} +
+{% endif %} + +{% if widget.attrs.maxlength %} + + You can enter up to {{ widget.attrs.maxlength }} characters + + +
+{% endif %} \ No newline at end of file diff --git a/src/registrar/templates/includes/radio_button.html b/src/registrar/templates/includes/radio_button.html deleted file mode 100644 index 5a3edc9f4..000000000 --- a/src/registrar/templates/includes/radio_button.html +++ /dev/null @@ -1,14 +0,0 @@ -
- - -
diff --git a/src/registrar/templatetags/field_helpers.py b/src/registrar/templatetags/field_helpers.py index ce3b6fed3..f1be0a605 100644 --- a/src/registrar/templatetags/field_helpers.py +++ b/src/registrar/templatetags/field_helpers.py @@ -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 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 diff --git a/src/registrar/views/application.py b/src/registrar/views/application.py index 401171842..9a3f95cc5 100644 --- a/src/registrar/views/application.py +++ b/src/registrar/views/application.py @@ -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 diff --git a/src/registrar/views/utility/steps_helper.py b/src/registrar/views/utility/steps_helper.py index 695f73dc0..f5eca2b55 100644 --- a/src/registrar/views/utility/steps_helper.py +++ b/src/registrar/views/utility/steps_helper.py @@ -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)