Add Type of Work section for special districts/interstate orgs

This commit is contained in:
Neil Martinsen-Burrell 2023-01-10 14:52:24 -06:00
parent f30b83f904
commit 8b76490794
No known key found for this signature in database
GPG key ID: 6A3C818CC10D0184
8 changed files with 311 additions and 3 deletions

View file

@ -27,6 +27,7 @@ for step, view in [
(Step.ORGANIZATION_FEDERAL, views.OrganizationFederal),
(Step.ORGANIZATION_ELECTION, views.OrganizationElection),
(Step.ORGANIZATION_CONTACT, views.OrganizationContact),
(Step.TYPE_OF_WORK, views.TypeOfWork),
(Step.AUTHORIZING_OFFICIAL, views.AuthorizingOfficial),
(Step.CURRENT_SITES, views.CurrentSites),
(Step.DOTGOV_DOMAIN, views.DotgovDomain),

View file

@ -151,7 +151,7 @@ class OrganizationContactForm(RegistrarForm):
raise forms.ValidationError(
"Please select your federal agency.", code="required"
)
if self.application.is_federal:
if self.application.is_federal():
if not federal_agency:
# no answer was selected
raise forms.ValidationError(
@ -160,6 +160,17 @@ class OrganizationContactForm(RegistrarForm):
return federal_agency
class TypeOfWorkForm(RegistrarForm):
type_of_work = forms.CharField(
label="What type of work does your organization do?", widget=forms.Textarea()
)
more_organization_information = forms.CharField(
label="Describe how your organization is a government organization that is independent of a state government. Include links to authorizing legislation, applicable bylaws or charter, or other documentation to support your claims.",
widget=forms.Textarea(),
)
class AuthorizingOfficialForm(RegistrarForm):
def to_database(self, obj):
if not self.is_valid():

View file

@ -0,0 +1,131 @@
# Generated by Django 4.1.5 on 2023-01-10 20:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("registrar", "0006_alter_contact_phone"),
]
operations = [
migrations.AddField(
model_name="domainapplication",
name="more_organization_information",
field=models.TextField(
blank=True,
help_text="Further information about the government organization",
null=True,
),
),
migrations.AddField(
model_name="domainapplication",
name="type_of_work",
field=models.TextField(
blank=True, help_text="Type of work of the organization", null=True
),
),
migrations.AlterField(
model_name="domainapplication",
name="address_line1",
field=models.TextField(blank=True, help_text="Street address", null=True),
),
migrations.AlterField(
model_name="domainapplication",
name="address_line2",
field=models.CharField(
blank=True, help_text="Street address line 2", max_length=15, null=True
),
),
migrations.AlterField(
model_name="domainapplication",
name="federal_agency",
field=models.TextField(blank=True, help_text="Federal agency", null=True),
),
migrations.AlterField(
model_name="domainapplication",
name="federal_type",
field=models.CharField(
blank=True,
choices=[
("executive", "Executive"),
("judicial", "Judicial"),
("legislative", "Legislative"),
],
help_text="Federal government branch",
max_length=50,
null=True,
),
),
migrations.AlterField(
model_name="domainapplication",
name="organization_type",
field=models.CharField(
blank=True,
choices=[
(
"federal",
"Federal: an agency of the U.S. government's executive, legislative, or judicial branches",
),
("interstate", "Interstate: an organization of two or more states"),
(
"state_or_territory",
"State or territory: one of the 50 U.S. states, the District of Columbia, American Samoa, Guam, Northern Mariana Islands, Puerto Rico, or the U.S. Virgin Islands",
),
(
"tribal",
"Tribal: a tribal government recognized by the federal or a state government",
),
("county", "County: a county, parish, or borough"),
("city", "City: a city, town, township, village, etc."),
(
"special_district",
"Special district: an independent organization within a single state",
),
(
"school_district",
"School district: a school district that is not part of a local government",
),
],
help_text="Type of Organization",
max_length=255,
null=True,
),
),
migrations.AlterField(
model_name="domainapplication",
name="purpose",
field=models.TextField(
blank=True, help_text="Purpose of your domain", null=True
),
),
migrations.AlterField(
model_name="domainapplication",
name="state_territory",
field=models.CharField(
blank=True,
help_text="State, territory, or military post",
max_length=2,
null=True,
),
),
migrations.AlterField(
model_name="domainapplication",
name="urbanization",
field=models.TextField(
blank=True, help_text="Urbanization (Puerto Rico only)", null=True
),
),
migrations.AlterField(
model_name="domainapplication",
name="zipcode",
field=models.CharField(
blank=True,
db_index=True,
help_text="Zip code",
max_length=10,
null=True,
),
),
]

View file

@ -350,6 +350,18 @@ class DomainApplication(TimeStampedModel):
help_text="Urbanization (Puerto Rico only)",
)
type_of_work = models.TextField(
null=True,
blank=True,
help_text="Type of work of the organization",
)
more_organization_information = models.TextField(
null=True,
blank=True,
help_text="Further information about the government organization",
)
authorizing_official = models.ForeignKey(
"registrar.Contact",
null=True,
@ -474,6 +486,14 @@ class DomainApplication(TimeStampedModel):
]
return bool(user_choice and user_choice not in excluded)
def show_type_of_work(self) -> bool:
"""Show this step if this is a special district or interstate."""
user_choice = self.organization_type
return user_choice in [
DomainApplication.OrganizationChoices.SPECIAL_DISTRICT,
DomainApplication.OrganizationChoices.INTERSTATE,
]
def is_federal(self) -> Union[bool, None]:
"""Is this application for a federal agency?

View file

@ -30,6 +30,10 @@
Incomplete
{% endif %}
{% endif %}
{% if step == Step.TYPE_OF_WORK %}
<p>{{ application.type_of_work|default:"Incomplete" }}</p>
<p>{{ application.more_organization_information|default:"Incomplete" }}</p>
{% endif %}
{% if step == Step.AUTHORIZING_OFFICIAL %}
{% if application.authorizing_official %}
{% include "includes/contact.html" with contact=application.authorizing_official %}

View file

@ -0,0 +1,57 @@
<!-- Test page -->
{% extends 'application_form.html' %}
{% load widget_tweaks %}
{% block form_content %}
<form id="step__{{steps.current}}" class="usa-form usa-form--large" method="post" novalidate>
<div class="usa-form-group">
{% csrf_token %}
<div class="usa-character-count">
{% with field=forms.0.type_of_work %}
{% if field.errors %}
<div class="usa-form-group usa-form-group--error">
{{ field|add_label_class:"usa-label usa-label--error" }}
{% for error in field.errors %}
<span class="usa-error-message" id="input-error-message" role="alert">
{{ error }}
</span>
{% endfor %}
{{ field|add_class:"usa-input--error usa-textarea usa-character-count__field"|attr:"aria-describedby:instructions"|attr:"maxlength=500"|attr:"aria-invalid:true" }}
</div>
{% else %}
{{ field|add_label_class:"usa-label" }}
{{ field|add_class:"usa-textarea usa-character-count__field"|attr:"aria-describedby:instructions"|attr:"maxlength=500" }}
{% endif %}
{% endwith %}
<span class="usa-character-count__message" id="with-hint-textarea-info with-hint-textarea-hint"> You can enter up to 500 characters </span>
</div>
</div>
<div class="usa-form-group">
<div class="usa-character-count">
{% with field=forms.0.more_organization_information %}
{% if field.errors %}
<div class="usa-form-group usa-form-group--error">
{{ field|add_label_class:"usa-label usa-label--error" }}
{% for error in field.errors %}
<span class="usa-error-message" id="input-error-message" role="alert">
{{ error }}
</span>
{% endfor %}
{{ field|add_class:"usa-input--error usa-textarea usa-character-count__field"|attr:"aria-describedby:instructions"|attr:"maxlength=500"|attr:"aria-invalid:true" }}
</div>
{% else %}
{{ field|add_label_class:"usa-label" }}
{{ field|add_class:"usa-textarea usa-character-count__field"|attr:"aria-describedby:instructions"|attr:"maxlength=500" }}
{% endif %}
{% endwith %}
<span class="usa-character-count__message" id="with-hint-textarea-info with-hint-textarea-hint"> You can enter up to 500 characters </span>
</div>
</div>
{{ block.super }}
</form>
{% endblock %}

View file

@ -8,7 +8,7 @@ from django.contrib.auth import get_user_model
from django_webtest import WebTest # type: ignore
from registrar.models import DomainApplication, Domain, Contact, Website
from registrar.views.application import ApplicationWizard
from registrar.views.application import ApplicationWizard, Step
from .common import less_console_noise
@ -120,7 +120,7 @@ class DomainApplicationTests(TestWithUser, WebTest):
this test work.
"""
num_pages_tested = 0
SKIPPED_PAGES = 1 # elections
SKIPPED_PAGES = 2 # elections, type_of_work
num_pages = len(self.TITLES) - SKIPPED_PAGES
type_page = self.app.get(reverse("application:")).follow()
@ -660,6 +660,82 @@ class DomainApplicationTests(TestWithUser, WebTest):
0,
)
def test_application_form_nonfederal(self):
"""Non-federal organizations don't have to provide their federal agency."""
type_page = self.app.get(reverse("application:")).follow()
# django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here
# and then setting the cookie on each request.
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
type_form = type_page.form
type_form["organization_type-organization_type"] = DomainApplication.OrganizationChoices.INTERSTATE
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
type_result = type_page.form.submit()
# follow first redirect
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
contact_page = type_result.follow()
org_contact_form = contact_page.form
self.assertNotIn("federal_agency", org_contact_form.fields)
# minimal fields that must be filled out
org_contact_form["organization_contact-organization_name"] = "Testorg"
org_contact_form["organization_contact-address_line1"] = "address 1"
org_contact_form["organization_contact-city"] = "NYC"
org_contact_form["organization_contact-state_territory"] = "NY"
org_contact_form["organization_contact-zipcode"] = "10002"
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
contact_result = org_contact_form.submit()
# the post request should return a redirect to the type of work page
# if it was successful.
self.assertEquals(contact_result.status_code, 302)
self.assertEquals(contact_result["Location"], "/register/type_of_work/")
def test_application_type_of_work_special(self):
"""Special districts have to answer an additional question."""
type_page = self.app.get(reverse("application:")).follow()
# django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here
# and then setting the cookie on each request.
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
type_form = type_page.form
type_form["organization_type-organization_type"] = DomainApplication.OrganizationChoices.SPECIAL_DISTRICT
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
type_result = type_page.form.submit()
# follow first redirect
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
contact_page = type_result.follow()
self.assertContains(contact_page, self.TITLES[Step.TYPE_OF_WORK])
def test_application_type_of_work_interstate(self):
"""Special districts have to answer an additional question."""
type_page = self.app.get(reverse("application:")).follow()
# django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept
# of a "session". We are going to do it manually, saving the session ID here
# and then setting the cookie on each request.
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
type_form = type_page.form
type_form["organization_type-organization_type"] = DomainApplication.OrganizationChoices.INTERSTATE
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
type_result = type_page.form.submit()
# follow first redirect
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
contact_page = type_result.follow()
self.assertContains(contact_page, self.TITLES[Step.TYPE_OF_WORK])
@skip("WIP")
def test_application_edit_restore(self):
"""

View file

@ -27,6 +27,7 @@ class Step(StrEnum):
ORGANIZATION_FEDERAL = "organization_federal"
ORGANIZATION_ELECTION = "organization_election"
ORGANIZATION_CONTACT = "organization_contact"
TYPE_OF_WORK = "type_of_work"
AUTHORIZING_OFFICIAL = "authorizing_official"
CURRENT_SITES = "current_sites"
DOTGOV_DOMAIN = "dotgov_domain"
@ -70,6 +71,7 @@ class ApplicationWizard(LoginRequiredMixin, TemplateView):
Step.ORGANIZATION_FEDERAL: _("Type of organization — Federal"),
Step.ORGANIZATION_ELECTION: _("Type of organization — Election board"),
Step.ORGANIZATION_CONTACT: _("Organization name and mailing address"),
Step.TYPE_OF_WORK: _("Type of Work"),
Step.AUTHORIZING_OFFICIAL: _("Authorizing official"),
Step.CURRENT_SITES: _("Organization website"),
Step.DOTGOV_DOMAIN: _(".gov domain"),
@ -93,6 +95,7 @@ class ApplicationWizard(LoginRequiredMixin, TemplateView):
Step.ORGANIZATION_ELECTION: lambda w: w.from_model(
"show_organization_election", False
),
Step.TYPE_OF_WORK: lambda w: w.from_model("show_type_of_work", False),
}
def __init__(self):
@ -353,6 +356,11 @@ class OrganizationContact(ApplicationWizard):
return context
class TypeOfWork(ApplicationWizard):
template_name = "application_type_of_work.html"
forms = [forms.TypeOfWorkForm]
class AuthorizingOfficial(ApplicationWizard):
template_name = "application_authorizing_official.html"
forms = [forms.AuthorizingOfficialForm]