Save button for domain application

This commit is contained in:
Seamus Johnston 2022-11-28 07:55:57 -06:00
parent 19c360f3bf
commit 07eb374d25
No known key found for this signature in database
GPG key ID: 2F21225985069105
21 changed files with 979 additions and 312 deletions

View file

@ -1,7 +1,12 @@
[mypy] [mypy]
plugins = plugins =
mypy_django_plugin.main mypy_django_plugin.main
# strict_optional: treat None as compatible with every type?
# `var: int` is equal to `var: int|None`
strict_optional = True strict_optional = True
# implicit_optional: treat arguments a None default value as implicitly Optional?
# `var: int = None` is equal to `var: Optional[int] = None`
implicit_optional = True
[mypy.plugins.django-stubs] [mypy.plugins.django-stubs]
django_settings_module = "registrar.config.settings" django_settings_module = "registrar.config.settings"

View file

@ -24,6 +24,7 @@ urlpatterns = [
path("", index.index, name="home"), path("", index.index, name="home"),
path("whoami/", whoami.whoami, name="whoami"), path("whoami/", whoami.whoami, name="whoami"),
path("admin/", admin.site.urls), path("admin/", admin.site.urls),
path("application/<id>/edit/", application_wizard, name="edit-application"),
path("health/", health.health), path("health/", health.health),
path("edit_profile/", profile.edit_profile, name="edit-profile"), path("edit_profile/", profile.edit_profile, name="edit-profile"),
path("openid/", include("djangooidc.urls")), path("openid/", include("djangooidc.urls")),

View file

@ -6,52 +6,46 @@ import logging
from django import forms from django import forms
from django.shortcuts import render from django.shortcuts import render
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import resolve
from formtools.wizard.views import NamedUrlSessionWizardView # type: ignore from formtools.wizard.views import NamedUrlSessionWizardView # type: ignore
from registrar.models import DomainApplication, Domain from registrar.models import Contact, DomainApplication, Domain
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Subclass used to remove the default colon suffix from all fields
class RegistrarForm(forms.Form): class RegistrarForm(forms.Form):
"""Subclass used to remove the default colon suffix from all fields."""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
kwargs.setdefault("label_suffix", "") kwargs.setdefault("label_suffix", "")
super(RegistrarForm, self).__init__(*args, **kwargs) super(RegistrarForm, self).__init__(*args, **kwargs)
def to_database(self, obj: DomainApplication | Contact):
"""
Adds this form's cleaned data to `obj` and saves `obj`.
Does nothing if form is not valid.
"""
if not self.is_valid():
return
for name, value in self.cleaned_data.items():
setattr(obj, name, value)
obj.save()
def from_database(self, obj: DomainApplication | Contact):
"""Initializes this form's fields with values gotten from `obj`."""
for name in self.declared_fields.keys():
self.initial[name] = getattr(obj, name) # type: ignore
class OrganizationTypeForm(RegistrarForm): class OrganizationTypeForm(RegistrarForm):
organization_type = forms.ChoiceField( organization_type = forms.ChoiceField(
required=True, required=True,
choices=[ choices=DomainApplication.OrganizationChoices.choices,
("Federal", "Federal: a federal agency"),
("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 "
"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",
),
],
widget=forms.RadioSelect, widget=forms.RadioSelect,
) )
@ -59,20 +53,20 @@ class OrganizationTypeForm(RegistrarForm):
class OrganizationFederalForm(RegistrarForm): class OrganizationFederalForm(RegistrarForm):
federal_type = forms.ChoiceField( federal_type = forms.ChoiceField(
required=False, required=False,
choices=DomainApplication.BRANCH_CHOICES, choices=DomainApplication.BranchChoices.choices,
widget=forms.RadioSelect, widget=forms.RadioSelect,
) )
class OrganizationElectionForm(RegistrarForm): class OrganizationElectionForm(RegistrarForm):
is_election_board = forms.BooleanField( is_election_board = forms.BooleanField(
required=False,
widget=forms.RadioSelect( widget=forms.RadioSelect(
choices=[ choices=[
(True, "Yes"), (True, "Yes"),
(False, "No"), (False, "No"),
], ],
), ),
required=False,
) )
@ -83,71 +77,32 @@ class OrganizationContactForm(RegistrarForm):
required=False, required=False,
label="Address line 2", label="Address line 2",
) )
us_state = forms.ChoiceField( state_territory = forms.ChoiceField(
label="State", label="State", choices=DomainApplication.StateTerritoryChoices.choices
choices=[
("AL", "Alabama"),
("AK", "Alaska"),
("AZ", "Arizona"),
("AR", "Arkansas"),
("CA", "California"),
("CO", "Colorado"),
("CT", "Connecticut"),
("DE", "Delaware"),
("DC", "District of Columbia"),
("FL", "Florida"),
("GA", "Georgia"),
("HI", "Hawaii"),
("ID", "Idaho"),
("IL", "Illinois"),
("IN", "Indiana"),
("IA", "Iowa"),
("KS", "Kansas"),
("KY", "Kentucky"),
("LA", "Louisiana"),
("ME", "Maine"),
("MD", "Maryland"),
("MA", "Massachusetts"),
("MI", "Michigan"),
("MN", "Minnesota"),
("MS", "Mississippi"),
("MO", "Missouri"),
("MT", "Montana"),
("NE", "Nebraska"),
("NV", "Nevada"),
("NH", "New Hampshire"),
("NJ", "New Jersey"),
("NM", "New Mexico"),
("NY", "New York"),
("NC", "North Carolina"),
("ND", "North Dakota"),
("OH", "Ohio"),
("OK", "Oklahoma"),
("OR", "Oregon"),
("PA", "Pennsylvania"),
("RI", "Rhode Island"),
("SC", "South Carolina"),
("SD", "South Dakota"),
("TN", "Tennessee"),
("TX", "Texas"),
("UT", "Utah"),
("VT", "Vermont"),
("VA", "Virginia"),
("WA", "Washington"),
("WV", "West Virginia"),
("WI", "Wisconsin"),
("WY", "Wyoming"),
("AS", "American Samoa"),
("GU", "Guam"),
("MP", "Northern Mariana Islands"),
("PR", "Puerto Rico"),
("VI", "Virgin Islands"),
],
) )
zipcode = forms.CharField(label="ZIP code") zipcode = forms.CharField(label="ZIP code")
class AuthorizingOfficialForm(RegistrarForm): class AuthorizingOfficialForm(RegistrarForm):
def to_database(self, obj):
"""Adds this form's cleaned data to `obj` and saves `obj`."""
if not self.is_valid():
return
contact = getattr(obj, "authorizing_official", None)
if contact is not None:
super().to_database(contact)
else:
contact = Contact()
super().to_database(contact)
obj.authorizing_official = contact
obj.save()
def from_database(self, obj):
"""Initializes this form's fields with values gotten from `obj`."""
contact = getattr(obj, "authorizing_official", None)
if contact is not None:
super().from_database(contact)
first_name = forms.CharField(label="First name/given name") first_name = forms.CharField(label="First name/given name")
middle_name = forms.CharField( middle_name = forms.CharField(
required=False, required=False,
@ -160,6 +115,21 @@ class AuthorizingOfficialForm(RegistrarForm):
class CurrentSitesForm(RegistrarForm): class CurrentSitesForm(RegistrarForm):
def to_database(self, obj):
"""Adds this form's cleaned data to `obj` and saves `obj`."""
if not self.is_valid():
return
obj.save()
normalized = Domain.normalize(self.cleaned_data["current_site"])
# TODO: ability to update existing records
obj.current_websites.create(website=normalized)
def from_database(self, obj):
"""Initializes this form's fields with values gotten from `obj`."""
current_website = obj.current_websites.first()
if current_website is not None:
self.initial["current_site"] = current_website.website
current_site = forms.CharField( current_site = forms.CharField(
required=False, required=False,
label="Enter your organizations public website, if you have one. For example, " label="Enter your organizations public website, if you have one. For example, "
@ -168,7 +138,36 @@ class CurrentSitesForm(RegistrarForm):
class DotGovDomainForm(RegistrarForm): class DotGovDomainForm(RegistrarForm):
dotgov_domain = forms.CharField(label="What .gov domain do you want?") def to_database(self, obj):
"""Adds this form's cleaned data to `obj` and saves `obj`."""
if not self.is_valid():
return
normalized = Domain.normalize(self.cleaned_data["requested_domain"], "gov")
requested_domain = getattr(obj, "requested_domain", None)
if requested_domain is not None:
requested_domain.name = normalized
requested_domain.save()
else:
requested_domain = Domain.objects.create(name=normalized)
obj.requested_domain = requested_domain
obj.save()
obj.save()
normalized = Domain.normalize(self.cleaned_data["alternative_domain"], "gov")
# TODO: ability to update existing records
obj.alternative_domains.create(website=normalized)
def from_database(self, obj):
"""Initializes this form's fields with values gotten from `obj`."""
requested_domain = getattr(obj, "requested_domain", None)
if requested_domain is not None:
self.initial["requested_domain"] = requested_domain.sld
alternative_domain = obj.alternative_domains.first()
if alternative_domain is not None:
self.initial["alternative_domain"] = alternative_domain.sld
requested_domain = forms.CharField(label="What .gov domain do you want?")
alternative_domain = forms.CharField( alternative_domain = forms.CharField(
required=False, required=False,
label="Are there other domains youd like if we cant give you your first " label="Are there other domains youd like if we cant give you your first "
@ -177,10 +176,29 @@ class DotGovDomainForm(RegistrarForm):
class PurposeForm(RegistrarForm): class PurposeForm(RegistrarForm):
purpose_field = forms.CharField(label="Purpose", widget=forms.Textarea()) purpose = forms.CharField(label="Purpose", widget=forms.Textarea())
class YourContactForm(RegistrarForm): class YourContactForm(RegistrarForm):
def to_database(self, obj):
"""Adds this form's cleaned data to `obj` and saves `obj`."""
if not self.is_valid():
return
contact = getattr(obj, "submitter", None)
if contact is not None:
super().to_database(contact)
else:
contact = Contact()
super().to_database(contact)
obj.submitter = contact
obj.save()
def from_database(self, obj):
"""Initializes this form's fields with values gotten from `obj`."""
contact = getattr(obj, "submitter", None)
if contact is not None:
super().from_database(contact)
first_name = forms.CharField(label="First name/given name") first_name = forms.CharField(label="First name/given name")
middle_name = forms.CharField( middle_name = forms.CharField(
required=False, required=False,
@ -193,6 +211,27 @@ class YourContactForm(RegistrarForm):
class OtherContactsForm(RegistrarForm): class OtherContactsForm(RegistrarForm):
def to_database(self, obj):
"""Adds this form's cleaned data to `obj` and saves `obj`."""
if not self.is_valid():
return
obj.save()
# TODO: ability to handle multiple contacts
contact = obj.other_contacts.filter(email=self.cleaned_data["email"]).first()
if contact is not None:
super().to_database(contact)
else:
contact = Contact()
super().to_database(contact)
obj.other_contacts.add(contact)
def from_database(self, obj):
"""Initializes this form's fields with values gotten from `obj`."""
other_contacts = obj.other_contacts.first()
if other_contacts is not None:
super().from_database(other_contacts)
first_name = forms.CharField(label="First name/given name") first_name = forms.CharField(label="First name/given name")
middle_name = forms.CharField( middle_name = forms.CharField(
required=False, required=False,
@ -205,7 +244,7 @@ class OtherContactsForm(RegistrarForm):
class SecurityEmailForm(RegistrarForm): class SecurityEmailForm(RegistrarForm):
email = forms.EmailField( security_email = forms.EmailField(
required=False, required=False,
label="Security email", label="Security email",
) )
@ -218,72 +257,97 @@ class AnythingElseForm(RegistrarForm):
class RequirementsForm(RegistrarForm): class RequirementsForm(RegistrarForm):
agree_check = forms.BooleanField( is_policy_acknowledged = forms.BooleanField(
label="I read and agree to the .gov domain requirements." label="I read and agree to the .gov domain requirements."
) )
# Empty class for the review page which gets included as part of the form, but does not
# have any form fields itself
class ReviewForm(RegistrarForm): class ReviewForm(RegistrarForm):
"""
Empty class for the review page.
It gets included as part of the form, but does not have any form fields itself.
"""
def to_database(self, _):
"""This form has no data. Do nothing."""
pass
pass pass
# List of forms in our wizard. Each entry is a tuple of a name and a form class Step:
# subclass """Names for each page of the application wizard."""
ORGANIZATION_TYPE = "organization_type"
ORGANIZATION_FEDERAL = "organization_federal"
ORGANIZATION_ELECTION = "organization_election"
ORGANIZATION_CONTACT = "organization_contact"
AUTHORIZING_OFFICIAL = "authorizing_official"
CURRENT_SITES = "current_sites"
DOTGOV_DOMAIN = "dotgov_domain"
PURPOSE = "purpose"
YOUR_CONTACT = "your_contact"
OTHER_CONTACTS = "other_contacts"
SECURITY_EMAIL = "security_email"
ANYTHING_ELSE = "anything_else"
REQUIREMENTS = "requirements"
REVIEW = "review"
# List of forms in our wizard.
# Each entry is a tuple of a name and a form subclass
FORMS = [ FORMS = [
("organization_type", OrganizationTypeForm), (Step.ORGANIZATION_TYPE, OrganizationTypeForm),
("organization_federal", OrganizationFederalForm), (Step.ORGANIZATION_FEDERAL, OrganizationFederalForm),
("organization_election", OrganizationElectionForm), (Step.ORGANIZATION_ELECTION, OrganizationElectionForm),
("organization_contact", OrganizationContactForm), (Step.ORGANIZATION_CONTACT, OrganizationContactForm),
("authorizing_official", AuthorizingOfficialForm), (Step.AUTHORIZING_OFFICIAL, AuthorizingOfficialForm),
("current_sites", CurrentSitesForm), (Step.CURRENT_SITES, CurrentSitesForm),
("dotgov_domain", DotGovDomainForm), (Step.DOTGOV_DOMAIN, DotGovDomainForm),
("purpose", PurposeForm), (Step.PURPOSE, PurposeForm),
("your_contact", YourContactForm), (Step.YOUR_CONTACT, YourContactForm),
("other_contacts", OtherContactsForm), (Step.OTHER_CONTACTS, OtherContactsForm),
("security_email", SecurityEmailForm), (Step.SECURITY_EMAIL, SecurityEmailForm),
("anything_else", AnythingElseForm), (Step.ANYTHING_ELSE, AnythingElseForm),
("requirements", RequirementsForm), (Step.REQUIREMENTS, RequirementsForm),
("review", ReviewForm), (Step.REVIEW, ReviewForm),
] ]
# Dict to match up the right template with the right step. Keys here must # Dict to match up the right template with the right step.
# match the first elements of the tuples in FORMS
TEMPLATES = { TEMPLATES = {
"organization_type": "application_org_type.html", Step.ORGANIZATION_TYPE: "application_org_type.html",
"organization_federal": "application_org_federal.html", Step.ORGANIZATION_FEDERAL: "application_org_federal.html",
"organization_election": "application_org_election.html", Step.ORGANIZATION_ELECTION: "application_org_election.html",
"organization_contact": "application_org_contact.html", Step.ORGANIZATION_CONTACT: "application_org_contact.html",
"authorizing_official": "application_authorizing_official.html", Step.AUTHORIZING_OFFICIAL: "application_authorizing_official.html",
"current_sites": "application_current_sites.html", Step.CURRENT_SITES: "application_current_sites.html",
"dotgov_domain": "application_dotgov_domain.html", Step.DOTGOV_DOMAIN: "application_dotgov_domain.html",
"purpose": "application_purpose.html", Step.PURPOSE: "application_purpose.html",
"your_contact": "application_your_contact.html", Step.YOUR_CONTACT: "application_your_contact.html",
"other_contacts": "application_other_contacts.html", Step.OTHER_CONTACTS: "application_other_contacts.html",
"security_email": "application_security_email.html", Step.SECURITY_EMAIL: "application_security_email.html",
"anything_else": "application_anything_else.html", Step.ANYTHING_ELSE: "application_anything_else.html",
"requirements": "application_requirements.html", Step.REQUIREMENTS: "application_requirements.html",
"review": "application_review.html", Step.REVIEW: "application_review.html",
} }
# We need to pass our page titles as context to the templates, indexed # We need to pass our page titles as context to the templates
# by the step names
TITLES = { TITLES = {
"organization_type": "Type of organization", Step.ORGANIZATION_TYPE: "Type of organization",
"organization_federal": "Type of organization — Federal", Step.ORGANIZATION_FEDERAL: "Type of organization — Federal",
"organization_election": "Type of organization — Election board", Step.ORGANIZATION_ELECTION: "Type of organization — Election board",
"organization_contact": "Organization name and mailing address", Step.ORGANIZATION_CONTACT: "Organization name and mailing address",
"authorizing_official": "Authorizing official", Step.AUTHORIZING_OFFICIAL: "Authorizing official",
"current_sites": "Organization website", Step.CURRENT_SITES: "Organization website",
"dotgov_domain": ".gov domain", Step.DOTGOV_DOMAIN: ".gov domain",
"purpose": "Purpose of your domain", Step.PURPOSE: "Purpose of your domain",
"your_contact": "Your contact information", Step.YOUR_CONTACT: "Your contact information",
"other_contacts": "Other contacts for your domain", Step.OTHER_CONTACTS: "Other contacts for your domain",
"security_email": "Security email for public use", Step.SECURITY_EMAIL: "Security email for public use",
"anything_else": "Anything else we should know?", Step.ANYTHING_ELSE: "Anything else we should know?",
"requirements": "Requirements for registration and operation of .gov domains", Step.REQUIREMENTS: "Requirements for registration and operation of .gov domains",
"review": "Review and submit your domain request", Step.REVIEW: "Review and submit your domain request",
} }
@ -303,6 +367,10 @@ class ApplicationWizard(LoginRequiredMixin, NamedUrlSessionWizardView):
domain applications. Each form in the sequence has its own URL and domain applications. Each form in the sequence has its own URL and
the progress through the form is stored in the Django session (thus the progress through the form is stored in the Django session (thus
"NamedUrlSessionWizardView"). "NamedUrlSessionWizardView").
Caution: due to the redirect performed by using NamedUrlSessionWizardView,
many methods, such as `process_step`, are called TWICE per request. For
this reason, methods in this class need to be idempotent.
""" """
form_list = FORMS form_list = FORMS
@ -320,42 +388,97 @@ class ApplicationWizard(LoginRequiredMixin, NamedUrlSessionWizardView):
context["form_titles"] = TITLES context["form_titles"] = TITLES
return context return context
def forms_to_object(self, form_dict: dict) -> DomainApplication: def get_application_object(self) -> DomainApplication:
"""Unpack the form responses onto the model object properties.""" """
Attempt to match the current wizard with a DomainApplication.
Will create an application if none exists.
"""
if "application_id" in self.storage.extra_data:
id = self.storage.extra_data["application_id"]
try:
return DomainApplication.objects.get(
creator=self.request.user,
pk=id,
)
except DomainApplication.DoesNotExist:
logger.debug("Application id %s did not have a DomainApplication" % id)
application = DomainApplication.objects.create(creator=self.request.user) application = DomainApplication.objects.create(creator=self.request.user)
self.storage.extra_data["application_id"] = application.id
# organization type information
organization_type_data = form_dict["organization_type"].cleaned_data
application.organization_type = organization_type_data["organization_type"]
# federal branch information may not exist
federal_branch_data = form_dict.get("organization_federal")
if federal_branch_data is not None:
federal_branch_data = federal_branch_data.cleaned_data
application.federal_branch = federal_branch_data["federal_type"]
# election board information may not exist.
election_board_data = form_dict.get("organization_election")
if election_board_data is not None:
election_board_data = election_board_data.cleaned_data
application.is_election_office = election_board_data["is_election_board"]
# contact information
contact_data = form_dict["organization_contact"].cleaned_data
application.organization_name = contact_data["organization_name"]
application.street_address = contact_data["address_line1"]
# TODO: add the rest of these fields when they are created in the forms
# This isn't really the requested_domain field
# but we need something in this field to make the form submittable
requested_site, _ = Domain.objects.get_or_create(
name=contact_data["organization_name"] + ".gov"
)
application.requested_domain = requested_site
return application return application
def forms_to_database(
self, forms: dict = None, form: RegistrarForm = None
) -> DomainApplication:
"""
Unpack the form responses onto the model object properties.
Saves the application to the database.
"""
application = self.get_application_object()
if forms:
itr = forms
elif form:
itr = {"form": form}
else:
raise TypeError("forms and form cannot both be None")
for form in itr.values():
if form is not None and hasattr(form, "to_database"):
form.to_database(application)
return application
def process_step(self, form):
"""
Hook called on every POST request, if the form is valid.
Do not manipulate the form data here.
"""
# save progress
self.forms_to_database(form=form)
return self.get_form_step_data(form)
def get_form(self, step=None, data=None, files=None):
"""This method constructs the form for a given step."""
form = super().get_form(step, data, files)
# restore from database, but only if a record has already
# been associated with this wizard instance
if "application_id" in self.storage.extra_data:
application = self.get_application_object()
form.from_database(application)
return form
def post(self, *args, **kwargs):
"""This method handles POST requests."""
step = self.steps.current
# always call super() first, to do important pre-processing
rendered = super().post(*args, **kwargs)
# if user opted to save their progress,
# return them to the page they were already on
button = self.request.POST.get("submit_button", None)
if button == "save":
return self.render_goto_step(step)
# otherwise, proceed as normal
return rendered
def get(self, *args, **kwargs):
"""This method handles GET requests."""
current_url = resolve(self.request.path_info).url_name
# always call super(), it handles important redirect logic
rendered = super().get(*args, **kwargs)
# if user visited via an "edit" url, associate the id of the
# application they are trying to edit to this wizard instance
if current_url == "edit-application" and "id" in kwargs:
self.storage.extra_data["application_id"] = kwargs["id"]
return rendered
def done(self, form_list, form_dict, **kwargs): def done(self, form_list, form_dict, **kwargs):
application = self.forms_to_object(form_dict) """Called when the data for every form is submitted and validated."""
application = self.forms_to_database(forms=form_dict)
application.submit() # change the status to submitted application.submit() # change the status to submitted
application.save() application.save()
logger.debug("Application object saved: %s", application.id) logger.debug("Application object saved: %s", application.id)

View file

@ -0,0 +1,99 @@
# Generated by Django 4.1.3 on 2022-12-02 21:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("registrar", "0002_domain_host_nameserver_hostip_and_more"),
]
operations = [
migrations.RenameField(
model_name="domainapplication",
old_name="is_election_office",
new_name="is_election_board",
),
migrations.RenameField(
model_name="domainapplication",
old_name="acknowledged_policy",
new_name="is_policy_acknowledged",
),
migrations.RenameField(
model_name="domainapplication",
old_name="zip_code",
new_name="zipcode",
),
migrations.RemoveField(
model_name="domainapplication",
name="federal_branch",
),
migrations.RemoveField(
model_name="domainapplication",
name="street_address",
),
migrations.RemoveField(
model_name="domainapplication",
name="unit_number",
),
migrations.RemoveField(
model_name="domainapplication",
name="unit_type",
),
migrations.AddField(
model_name="domainapplication",
name="address_line1",
field=models.TextField(blank=True, help_text="Address line 1", null=True),
),
migrations.AddField(
model_name="domainapplication",
name="address_line2",
field=models.CharField(
blank=True, help_text="Address line 2", max_length=15, null=True
),
),
migrations.AddField(
model_name="domainapplication",
name="federal_type",
field=models.CharField(
blank=True,
choices=[
("executive", "Executive"),
("judicial", "Judicial"),
("legislative", "Legislative"),
],
help_text="Branch of federal government",
max_length=50,
null=True,
),
),
migrations.AlterField(
model_name="domainapplication",
name="organization_type",
field=models.CharField(
blank=True,
choices=[
("federal", "Federal: a federal agency"),
("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 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",
),
],
help_text="Type of Organization",
max_length=255,
null=True,
),
),
]

View file

@ -1,14 +1,14 @@
import logging import logging
import re import re
from django.apps import apps
from django.core.exceptions import ValidationError
from django.db import models from django.db import models
from django_fsm import FSMField, transition # type: ignore from django_fsm import FSMField, transition # type: ignore
from epp.mock_epp import domain_info, domain_check from epp.mock_epp import domain_info, domain_check
from .utility.time_stamped_model import TimeStampedModel from .utility.time_stamped_model import TimeStampedModel
from .domain_application import DomainApplication
from .user import User
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -93,11 +93,58 @@ class Domain(TimeStampedModel):
DOMAIN_REGEX = re.compile(r"^(?!-)[A-Za-z0-9-]{1,63}(?<!-)\.[A-Za-z]{2,6}") DOMAIN_REGEX = re.compile(r"^(?!-)[A-Za-z0-9-]{1,63}(?<!-)\.[A-Za-z]{2,6}")
@classmethod @classmethod
def string_could_be_domain(cls, domain: str) -> bool: def normalize(cls, domain: str, tld=None) -> str: # noqa: C901
"""Return `domain` in form `<second level>.<tld>`, if possible.
This does not guarantee the returned string is a valid domain name."""
cleaned = domain.lower()
# starts with https or http
if cleaned.startswith("https://"):
cleaned = cleaned[8:]
if cleaned.startswith("http://"):
cleaned = cleaned[7:]
# has url parts
if "/" in cleaned:
cleaned = cleaned.split("/")[0]
# has query parts
if "?" in cleaned:
cleaned = cleaned.split("?")[0]
# has fragments
if "#" in cleaned:
cleaned = cleaned.split("#")[0]
# replace disallowed chars
re.sub(r"^[^A-Za-z0-9.-]+", "", cleaned)
parts = cleaned.split(".")
# has subdomains or invalid repetitions
if cleaned.count(".") > 0:
# remove invalid repetitions
while parts[-1] == parts[-2]:
parts.pop()
# remove subdomains
parts = parts[-2:]
hasTLD = len(parts) == 2
if hasTLD:
# set correct tld
if tld is not None:
parts[-1] = tld
else:
# add tld
if tld is not None:
parts.append(tld)
else:
raise ValueError("You must specify a tld for %s" % domain)
cleaned = ".".join(parts)
return cleaned
@classmethod
def string_could_be_domain(cls, domain: str | None) -> bool:
"""Return True if the string could be a domain name, otherwise False.""" """Return True if the string could be a domain name, otherwise False."""
if cls.DOMAIN_REGEX.match(domain): if not isinstance(domain, str):
return True return False
return False return bool(cls.DOMAIN_REGEX.match(domain))
@classmethod @classmethod
def available(cls, domain: str) -> bool: def available(cls, domain: str) -> bool:
@ -137,16 +184,10 @@ class Domain(TimeStampedModel):
# TODO: return an error if registry cannot be contacted # TODO: return an error if registry cannot be contacted
return None return None
def could_be_domain(self) -> bool:
"""Could this instance be a domain?"""
# short-circuit if self.website is null/None
if not self.name:
return False
return self.string_could_be_domain(str(self.name))
@transition(field="is_active", source="*", target=True) @transition(field="is_active", source="*", target=True)
def activate(self): def activate(self):
"""This domain should be made live.""" """This domain should be made live."""
DomainApplication = apps.get_model("registrar.DomainApplication")
if hasattr(self, "domain_application"): if hasattr(self, "domain_application"):
if self.domain_application.status != DomainApplication.APPROVED: if self.domain_application.status != DomainApplication.APPROVED:
raise ValueError("Cannot activate. Application must be approved.") raise ValueError("Cannot activate. Application must be approved.")
@ -166,6 +207,34 @@ class Domain(TimeStampedModel):
# if there is a feature request to implement this # if there is a feature request to implement this
raise Exception("Cannot revoke, contact registry.") raise Exception("Cannot revoke, contact registry.")
@property
def sld(self):
"""Get or set the second level domain string."""
return self.name.split(".")[0]
@sld.setter
def sld(self, value: str):
parts = self.name.split(".")
tld = parts[1] if len(parts) > 1 else ""
if Domain.string_could_be_domain(f"{value}.{tld}"):
self.name = f"{value}.{tld}"
else:
raise ValidationError("%s is not a valid second level domain" % value)
@property
def tld(self):
"""Get or set the top level domain string."""
parts = self.name.split(".")
return parts[1] if len(parts) > 1 else ""
@tld.setter
def tld(self, value: str):
sld = self.name.split(".")[0]
if Domain.string_could_be_domain(f"{sld}.{value}"):
self.name = f"{sld}.{value}"
else:
raise ValidationError("%s is not a valid top level domain" % value)
def __str__(self) -> str: def __str__(self) -> str:
return self.name return self.name
@ -232,6 +301,6 @@ class Domain(TimeStampedModel):
# TODO: determine the relationship between this field # TODO: determine the relationship between this field
# and the domain application's `creator` and `submitter` # and the domain application's `creator` and `submitter`
owners = models.ManyToManyField( owners = models.ManyToManyField(
User, "registrar.User",
help_text="", help_text="",
) )

View file

@ -1,14 +1,11 @@
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING, Union
from django.apps import apps
from django.db import models from django.db import models
from django_fsm import FSMField, transition # type: ignore from django_fsm import FSMField, transition # type: ignore
from .utility.time_stamped_model import TimeStampedModel from .utility.time_stamped_model import TimeStampedModel
from .contact import Contact
from .user import User
from .website import Website
from typing import TYPE_CHECKING, Union
if TYPE_CHECKING: if TYPE_CHECKING:
from ..forms.application_wizard import ApplicationWizard from ..forms.application_wizard import ApplicationWizard
@ -30,35 +27,86 @@ class DomainApplication(TimeStampedModel):
(APPROVED, APPROVED), (APPROVED, APPROVED),
] ]
FEDERAL = "federal" class StateTerritoryChoices(models.TextChoices):
INTERSTATE = "interstate" ALABAMA = "AL", "Alabama"
STATE_OR_TERRITORY = "state_or_territory" ALASKA = "AK", "Alaska"
TRIBAL = "tribal" ARIZONA = "AZ", "Arizona"
COUNTY = "county" ARKANSAS = "AR", "Arkansas"
CITY = "city" CALIFORNIA = "CA", "California"
SPECIAL_DISTRICT = "special_district" COLORADO = "CO", "Colorado"
ORGANIZATION_CHOICES = [ CONNECTICUT = "CT", "Connecticut"
(FEDERAL, "a federal agency"), DELAWARE = "DE", "Delaware"
(INTERSTATE, "an organization of two or more states"), DISTRICT_OF_COLUMBIA = "DC", "District of Columbia"
( FLORIDA = "FL", "Florida"
STATE_OR_TERRITORY, GEORGIA = "GA", "Georgia"
"one of the 50 U.S. states, the District of " HAWAII = "HI", "Hawaii"
"Columbia, American Samoa, Guam, Northern Mariana Islands, " IDAHO = "ID", "Idaho"
"Puerto Rico, or the U.S. Virgin Islands", ILLINOIS = "IL", "Illinois"
), INDIANA = "IN", "Indiana"
( IOWA = "IA", "Iowa"
TRIBAL, KANSAS = "KS", "Kansas"
"a tribal government recognized by the federal or " "state government", KENTUCKY = "KY", "Kentucky"
), LOUISIANA = "LA", "Louisiana"
(COUNTY, "a county, parish, or borough"), MAINE = "ME", "Maine"
(CITY, "a city, town, township, village, etc."), MARYLAND = "MD", "Maryland"
(SPECIAL_DISTRICT, "an independent organization within a single state"), MASSACHUSETTS = "MA", "Massachusetts"
] MICHIGAN = "MI", "Michigan"
MINNESOTA = "MN", "Minnesota"
MISSISSIPPI = "MS", "Mississippi"
MISSOURI = "MO", "Missouri"
MONTANA = "MT", "Montana"
NEBRASKA = "NE", "Nebraska"
NEVADA = "NV", "Nevada"
NEW_HAMPSHIRE = "NH", "New Hampshire"
NEW_JERSEY = "NJ", "New Jersey"
NEW_MEXICO = "NM", "New Mexico"
NEW_YORK = "NY", "New York"
NORTH_CAROLINA = "NC", "North Carolina"
NORTH_DAKOTA = "ND", "North Dakota"
OHIO = "OH", "Ohio"
OKLAHOMA = "OK", "Oklahoma"
OREGON = "OR", "Oregon"
PENNSYLVANIA = "PA", "Pennsylvania"
RHODE_ISLAND = "RI", "Rhode Island"
SOUTH_CAROLINA = "SC", "South Carolina"
SOUTH_DAKOTA = "SD", "South Dakota"
TENNESSEE = "TN", "Tennessee"
TEXAS = "TX", "Texas"
UTAH = "UT", "Utah"
VERMONT = "VT", "Vermont"
VIRGINIA = "VA", "Virginia"
WASHINGTON = "WA", "Washington"
WEST_VIRGINIA = "WV", "West Virginia"
WISCONSIN = "WI", "Wisconsin"
WYOMING = "WY", "Wyoming"
AMERICAN_SAMOA = "AS", "American Samoa"
GUAM = "GU", "Guam"
NORTHERN_MARIANA_ISLANDS = "MP", "Northern Mariana Islands"
PUERTO_RICO = "PR", "Puerto Rico"
VIRGIN_ISLANDS = "VI", "Virgin Islands"
EXECUTIVE = "Executive" class OrganizationChoices(models.TextChoices):
JUDICIAL = "Judicial" FEDERAL = "federal", "Federal: a federal agency"
LEGISLATIVE = "Legislative" INTERSTATE = "interstate", "Interstate: an organization of two or more states"
BRANCH_CHOICES = [(x, x) for x in (EXECUTIVE, JUDICIAL, LEGISLATIVE)] STATE_OR_TERRITORY = "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", (
"Tribal: a tribal government recognized by the federal or "
"state government"
)
COUNTY = "county", "County: a county, parish, or borough"
CITY = "city", "City: a city, town, township, village, etc."
SPECIAL_DISTRICT = "special_district", (
"Special District: an independent organization within a single state"
)
class BranchChoices(models.TextChoices):
EXECUTIVE = "executive", "Executive"
JUDICIAL = "judicial", "Judicial"
LEGISLATIVE = "legislative", "Legislative"
# #### Internal fields about the application ##### # #### Internal fields about the application #####
status = FSMField( status = FSMField(
@ -69,10 +117,12 @@ class DomainApplication(TimeStampedModel):
# This is the application user who created this application. The contact # This is the application user who created this application. The contact
# information that they gave is in the `submitter` field # information that they gave is in the `submitter` field
creator = models.ForeignKey( creator = models.ForeignKey(
User, on_delete=models.PROTECT, related_name="applications_created" "registrar.User",
on_delete=models.PROTECT,
related_name="applications_created",
) )
investigator = models.ForeignKey( investigator = models.ForeignKey(
User, "registrar.User",
null=True, null=True,
blank=True, blank=True,
on_delete=models.SET_NULL, on_delete=models.SET_NULL,
@ -82,21 +132,21 @@ class DomainApplication(TimeStampedModel):
# ##### data fields from the initial form ##### # ##### data fields from the initial form #####
organization_type = models.CharField( organization_type = models.CharField(
max_length=255, max_length=255,
choices=ORGANIZATION_CHOICES, choices=OrganizationChoices.choices,
null=True, null=True,
blank=True, blank=True,
help_text="Type of Organization", help_text="Type of Organization",
) )
federal_branch = models.CharField( federal_type = models.CharField(
max_length=50, max_length=50,
choices=BRANCH_CHOICES, choices=BranchChoices.choices,
null=True, null=True,
blank=True, blank=True,
help_text="Branch of federal government", help_text="Branch of federal government",
) )
is_election_office = models.BooleanField( is_election_board = models.BooleanField(
null=True, null=True,
blank=True, blank=True,
help_text="Is your ogranization an election office?", help_text="Is your ogranization an election office?",
@ -108,22 +158,16 @@ class DomainApplication(TimeStampedModel):
help_text="Organization name", help_text="Organization name",
db_index=True, db_index=True,
) )
street_address = models.TextField( address_line1 = models.TextField(
null=True, null=True,
blank=True, blank=True,
help_text="Street Address", help_text="Address line 1",
) )
unit_type = models.CharField( address_line2 = models.CharField(
max_length=15, max_length=15,
null=True, null=True,
blank=True, blank=True,
help_text="Unit type", help_text="Address line 2",
)
unit_number = models.CharField(
max_length=255,
null=True,
blank=True,
help_text="Unit number",
) )
state_territory = models.CharField( state_territory = models.CharField(
max_length=2, max_length=2,
@ -131,7 +175,7 @@ class DomainApplication(TimeStampedModel):
blank=True, blank=True,
help_text="State/Territory", help_text="State/Territory",
) )
zip_code = models.CharField( zipcode = models.CharField(
max_length=10, max_length=10,
null=True, null=True,
blank=True, blank=True,
@ -140,7 +184,7 @@ class DomainApplication(TimeStampedModel):
) )
authorizing_official = models.ForeignKey( authorizing_official = models.ForeignKey(
Contact, "registrar.Contact",
null=True, null=True,
blank=True, blank=True,
related_name="authorizing_official", related_name="authorizing_official",
@ -149,7 +193,7 @@ class DomainApplication(TimeStampedModel):
# "+" means no reverse relation to lookup applications from Website # "+" means no reverse relation to lookup applications from Website
current_websites = models.ManyToManyField( current_websites = models.ManyToManyField(
Website, "registrar.Website",
blank=True, blank=True,
related_name="current+", related_name="current+",
) )
@ -163,7 +207,7 @@ class DomainApplication(TimeStampedModel):
on_delete=models.PROTECT, on_delete=models.PROTECT,
) )
alternative_domains = models.ManyToManyField( alternative_domains = models.ManyToManyField(
Website, "registrar.Website",
blank=True, blank=True,
related_name="alternatives+", related_name="alternatives+",
) )
@ -171,7 +215,7 @@ class DomainApplication(TimeStampedModel):
# This is the contact information provided by the applicant. The # This is the contact information provided by the applicant. The
# application user who created it is in the `creator` field. # application user who created it is in the `creator` field.
submitter = models.ForeignKey( submitter = models.ForeignKey(
Contact, "registrar.Contact",
null=True, null=True,
blank=True, blank=True,
related_name="submitted_applications", related_name="submitted_applications",
@ -185,7 +229,7 @@ class DomainApplication(TimeStampedModel):
) )
other_contacts = models.ManyToManyField( other_contacts = models.ManyToManyField(
Contact, "registrar.Contact",
blank=True, blank=True,
related_name="contact_applications", related_name="contact_applications",
) )
@ -203,7 +247,7 @@ class DomainApplication(TimeStampedModel):
help_text="Anything else we should know?", help_text="Anything else we should know?",
) )
acknowledged_policy = models.BooleanField( is_policy_acknowledged = models.BooleanField(
null=True, null=True,
blank=True, blank=True,
help_text="Acknowledged .gov acceptable use policy", help_text="Acknowledged .gov acceptable use policy",
@ -226,8 +270,15 @@ class DomainApplication(TimeStampedModel):
# can raise more informative exceptions # can raise more informative exceptions
# requested_domain could be None here # requested_domain could be None here
if (not self.requested_domain) or (not self.requested_domain.could_be_domain()): if not hasattr(self, "requested_domain"):
raise ValueError("Requested domain is not a legal domain name.") raise ValueError("Requested domain is missing.")
if self.requested_domain is None:
raise ValueError("Requested domain is missing.")
Domain = apps.get_model("registrar.Domain")
if not Domain.string_could_be_domain(self.requested_domain.name):
raise ValueError("Requested domain is not a valid domain name.")
# if no exception was raised, then we don't need to do anything # if no exception was raised, then we don't need to do anything
# inside this method, keep the `pass` here to remind us of that # inside this method, keep the `pass` here to remind us of that
@ -253,7 +304,8 @@ class DomainApplication(TimeStampedModel):
@staticmethod @staticmethod
def show_organization_federal(wizard: ApplicationWizard) -> bool: def show_organization_federal(wizard: ApplicationWizard) -> bool:
"""Show this step if the answer to the first question was "federal".""" """Show this step if the answer to the first question was "federal"."""
return DomainApplication._get_organization_type(wizard) == "Federal" user_choice = DomainApplication._get_organization_type(wizard)
return user_choice == DomainApplication.OrganizationChoices.FEDERAL
@staticmethod @staticmethod
def show_organization_election(wizard: ApplicationWizard) -> bool: def show_organization_election(wizard: ApplicationWizard) -> bool:
@ -261,7 +313,9 @@ class DomainApplication(TimeStampedModel):
This shows for answers that aren't "Federal" or "Interstate". This shows for answers that aren't "Federal" or "Interstate".
""" """
type_answer = DomainApplication._get_organization_type(wizard) user_choice = DomainApplication._get_organization_type(wizard)
if type_answer and type_answer not in ("Federal", "Interstate"): excluded = [
return True DomainApplication.OrganizationChoices.FEDERAL,
return False DomainApplication.OrganizationChoices.INTERSTATE,
]
return bool(user_choice and user_choice not in excluded)

View file

@ -1,7 +1,6 @@
from django.db import models from django.db import models
from .utility.time_stamped_model import TimeStampedModel from .utility.time_stamped_model import TimeStampedModel
from .domain import Domain
class Host(TimeStampedModel): class Host(TimeStampedModel):
@ -26,7 +25,7 @@ class Host(TimeStampedModel):
) )
domain = models.ForeignKey( domain = models.ForeignKey(
Domain, "registrar.Domain",
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name="host", # access this Host via the Domain as `domain.host` related_name="host", # access this Host via the Domain as `domain.host`
help_text="Domain to which this host belongs", help_text="Domain to which this host belongs",

View file

@ -2,7 +2,6 @@ from django.db import models
from django.core.validators import validate_ipv46_address from django.core.validators import validate_ipv46_address
from .utility.time_stamped_model import TimeStampedModel from .utility.time_stamped_model import TimeStampedModel
from .host import Host
class HostIP(TimeStampedModel): class HostIP(TimeStampedModel):
@ -25,7 +24,7 @@ class HostIP(TimeStampedModel):
) )
host = models.ForeignKey( host = models.ForeignKey(
Host, "registrar.Host",
on_delete=models.PROTECT, on_delete=models.PROTECT,
related_name="ip", # access this HostIP via the Host as `host.ip` related_name="ip", # access this HostIP via the Host as `host.ip`
help_text="Host to which this IP address belongs", help_text="Host to which this IP address belongs",

View file

@ -4,7 +4,6 @@ from .utility.time_stamped_model import TimeStampedModel
from .utility.address_model import AddressModel from .utility.address_model import AddressModel
from .contact import Contact from .contact import Contact
from .user import User
class UserProfile(TimeStampedModel, Contact, AddressModel): class UserProfile(TimeStampedModel, Contact, AddressModel):
@ -12,7 +11,7 @@ class UserProfile(TimeStampedModel, Contact, AddressModel):
"""User information, unrelated to their login/auth details.""" """User information, unrelated to their login/auth details."""
user = models.OneToOneField( user = models.OneToOneField(
User, "registrar.User",
null=True, null=True,
blank=True, blank=True,
on_delete=models.CASCADE, on_delete=models.CASCADE,

View file

@ -1,3 +1,5 @@
from django.apps import apps
from django.core.exceptions import ValidationError
from django.db import models from django.db import models
@ -14,5 +16,35 @@ class Website(models.Model):
help_text="", help_text="",
) )
@property
def sld(self):
"""Get or set the second level domain string."""
return self.website.split(".")[0]
@sld.setter
def sld(self, value: str):
Domain = apps.get_model("registrar.Domain")
parts = self.website.split(".")
tld = parts[1] if len(parts) > 1 else ""
if Domain.string_could_be_domain(f"{value}.{tld}"):
self.website = f"{value}.{tld}"
else:
raise ValidationError("%s is not a valid second level domain" % value)
@property
def tld(self):
"""Get or set the top level domain string."""
parts = self.website.split(".")
return parts[1] if len(parts) > 1 else ""
@tld.setter
def tld(self, value: str):
Domain = apps.get_model("registrar.Domain")
sld = self.website.split(".")[0]
if Domain.string_could_be_domain(f"{sld}.{value}"):
self.website = f"{sld}.{value}"
else:
raise ValidationError("%s is not a valid top level domain" % value)
def __str__(self) -> str: def __str__(self) -> str:
return str(self.website) return str(self.website)

View file

@ -27,10 +27,10 @@
{{ wizard.management_form }} {{ wizard.management_form }}
{% csrf_token %} {% csrf_token %}
{{ wizard.form.dotgov_domain|add_label_class:"usa-label" }} {{ wizard.form.requested_domain|add_label_class:"usa-label" }}
<div class="display-flex flex-align-center"> <div class="display-flex flex-align-center">
<span class="padding-top-05 padding-right-2px">www.</span> <span class="padding-top-05 padding-right-2px">www.</span>
{{ wizard.form.dotgov_domain|add_class:"usa-input"|attr:"aria-describedby:domain_instructions" }} {{ wizard.form.requested_domain|add_class:"usa-input"|attr:"aria-describedby:domain_instructions" }}
<span class="padding-top-05 padding-left-2px">.gov </span> <span class="padding-top-05 padding-left-2px">.gov </span>
</div> </div>
<button type="button" class="usa-button">Check availability </button> <button type="button" class="usa-button">Check availability </button>

View file

@ -20,11 +20,11 @@
<h1> {{form_titles|get_item:wizard.steps.current}} </h1> <h1> {{form_titles|get_item:wizard.steps.current}} </h1>
{% block form_content %} {% block form_content %}
{% if wizard.steps.next %} {% if wizard.steps.next %}
<button type="submit" class="usa-button">Next</button> <button type="submit" name="submit_button" value="next" class="usa-button">Next</button>
{% else %} {% else %}
<button type="submit" class="usa-button">Submit your domain request</button> <button type="submit" class="usa-button">Submit your domain request</button>
{% endif %} {% endif %}
<button type="button" class="usa-button usa-button--outline">Save</button> <button type="submit" name="submit_button" value="save" class="usa-button usa-button--outline">Save</button>
</main> </main>
</div> </div>
{% endblock %} {% endblock %}

View file

@ -28,9 +28,9 @@
{{ wizard.form.address_line1|add_class:"usa-input" }} {{ wizard.form.address_line1|add_class:"usa-input" }}
{{ wizard.form.address_line2|add_label_class:"usa-label" }} {{ wizard.form.address_line2|add_label_class:"usa-label" }}
{{ wizard.form.address_line2|add_class:"usa-input" }} {{ wizard.form.address_line2|add_class:"usa-input" }}
{{ wizard.form.us_state|add_label_class:"usa-label" }} {{ wizard.form.state_territory|add_label_class:"usa-label" }}
<div class="usa-combo-box"> <div class="usa-combo-box" data-default-value>
{{ wizard.form.us_state|add_class:"usa-select" }} {{ wizard.form.state_territory|add_class:"usa-select" }}
</div> </div>
{{ wizard.form.zipcode|add_label_class:"usa-label" }} {{ wizard.form.zipcode|add_label_class:"usa-label" }}
{{ wizard.form.zipcode|add_class:"usa-input usa-input--small" }} {{ wizard.form.zipcode|add_class:"usa-input usa-input--small" }}

View file

@ -13,8 +13,9 @@
<h2>Is your organization an election office?</h2> <h2>Is your organization an election office?</h2>
</legend> </legend>
{% radio_buttons_by_value wizard.form.is_election_board as choices %} {% radio_buttons_by_value wizard.form.is_election_board as choices %}
{% include "includes/radio_button.html" with choice=choices|get_item:True %} {% for choice in choices.values %}
{% include "includes/radio_button.html" with choice=choices|get_item:False %} {% include "includes/radio_button.html" with choice=choice tile="true" %}
{% endfor %}
</fieldset> </fieldset>
{{ block.super }} {{ block.super }}

View file

@ -12,10 +12,10 @@
<legend> <legend>
<h2>Which federal branch is your organization in?</h2> <h2>Which federal branch is your organization in?</h2>
</legend> </legend>
{% radio_buttons_by_value wizard.form.federal_type as federal_choices %} {% radio_buttons_by_value wizard.form.federal_type as choices %}
{% include "includes/radio_button.html" with choice=federal_choices.Executive%} {% for choice in choices.values %}
{% include "includes/radio_button.html" with choice=federal_choices.Judicial%} {% include "includes/radio_button.html" with choice=choice tile="true" %}
{% include "includes/radio_button.html" with choice=federal_choices.Legislative%} {% endfor %}
</fieldset> </fieldset>
{{ block.super }} {{ block.super }}

View file

@ -16,13 +16,9 @@
<h2> What kind of government organization do you represent?</h2> <h2> What kind of government organization do you represent?</h2>
</legend> </legend>
{{ wizard.form.organization_type.errors }} {{ wizard.form.organization_type.errors }}
{% include "includes/radio_button.html" with choice=choices.Federal tile="true" %} {% for choice in choices.values %}
{% include "includes/radio_button.html" with choice=choices.Interstate tile="true" %} {% include "includes/radio_button.html" with choice=choice tile="true" %}
{% include "includes/radio_button.html" with choice=choices.State_or_Territory tile="true" %} {% endfor %}
{% include "includes/radio_button.html" with choice=choices.Tribal tile="true" %}
{% include "includes/radio_button.html" with choice=choices.County tile="true" %}
{% include "includes/radio_button.html" with choice=choices.City tile="true" %}
{% include "includes/radio_button.html" with choice=choices.Special_District tile="true" %}
</fieldset> </fieldset>
{{ block.super }} {{ block.super }}

View file

@ -12,8 +12,8 @@
{% csrf_token %} {% csrf_token %}
<div class="usa-character-count"> <div class="usa-character-count">
{{ wizard.form.purpose_field|add_label_class:"usa-label usa-sr-only" }} {{ wizard.form.purpose|add_label_class:"usa-label usa-sr-only" }}
{{ wizard.form.purpose_field|add_class:"usa-textarea usa-character-count__field"|attr:"aria-describedby:instructions"|attr:"maxlength=500" }} {{ wizard.form.purpose|add_class:"usa-textarea usa-character-count__field"|attr:"aria-describedby:instructions"|attr:"maxlength=500" }}
<span class="usa-character-count__message" id="with-hint-textarea-info with-hint-textarea-hint"> You can enter up to 500 characters </span> <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> </div>

View file

@ -60,8 +60,8 @@
{% csrf_token %} {% csrf_token %}
<div class="usa-checkbox"> <div class="usa-checkbox">
{{ wizard.form.agree_check|add_class:"usa-checkbox__input"}} {{ wizard.form.is_policy_acknowledged|add_class:"usa-checkbox__input"}}
{{ wizard.form.agree_check|add_label_class:"usa-checkbox__label" }} {{ wizard.form.is_policy_acknowledged|add_label_class:"usa-checkbox__label" }}
</div> </div>
</div> </div>

View file

@ -11,8 +11,8 @@
{{ wizard.management_form }} {{ wizard.management_form }}
{% csrf_token %} {% csrf_token %}
{{ wizard.form.email|add_label_class:"usa-label" }} {{ wizard.form.security_email|add_label_class:"usa-label" }}
{{ wizard.form.email|add_class:"usa-input"|attr:"aria-describedby:instructions" }} {{ wizard.form.security_email|add_class:"usa-input"|attr:"aria-describedby:instructions" }}
{{ block.super }} {{ block.super }}

View file

@ -27,22 +27,21 @@ class TestDomainApplication(TestCase):
application = DomainApplication.objects.create( application = DomainApplication.objects.create(
creator=user, creator=user,
investigator=user, investigator=user,
organization_type=DomainApplication.FEDERAL, organization_type=DomainApplication.OrganizationChoices.FEDERAL,
federal_branch=DomainApplication.EXECUTIVE, federal_type=DomainApplication.BranchChoices.EXECUTIVE,
is_election_office=False, is_election_board=False,
organization_name="Test", organization_name="Test",
street_address="100 Main St.", address_line1="100 Main St.",
unit_type="APT", address_line2="APT 1A",
unit_number="1A",
state_territory="CA", state_territory="CA",
zip_code="12345-6789", zipcode="12345-6789",
authorizing_official=contact, authorizing_official=contact,
requested_domain=domain, requested_domain=domain,
submitter=contact, submitter=contact,
purpose="Igorville rules!", purpose="Igorville rules!",
security_email="security@igorville.gov", security_email="security@igorville.gov",
anything_else="All of Igorville loves the dotgov program.", anything_else="All of Igorville loves the dotgov program.",
acknowledged_policy=True, is_policy_acknowledged=True,
) )
application.current_websites.add(com_website) application.current_websites.add(com_website)
application.alternative_domains.add(gov_website) application.alternative_domains.add(gov_website)

View file

@ -1,3 +1,5 @@
from unittest import skip
from django.conf import settings from django.conf import settings
from django.test import Client, TestCase from django.test import Client, TestCase
from django.urls import reverse from django.urls import reverse
@ -5,7 +7,7 @@ from django.contrib.auth import get_user_model
from django_webtest import WebTest # type: ignore from django_webtest import WebTest # type: ignore
from registrar.models import DomainApplication, Domain from registrar.models import DomainApplication, Domain, Contact, Website
from registrar.forms.application_wizard import TITLES from registrar.forms.application_wizard import TITLES
from .common import less_console_noise from .common import less_console_noise
@ -87,9 +89,9 @@ class LoggedInTests(TestWithUser):
) )
class FormTests(TestWithUser, WebTest): class DomainApplicationTests(TestWithUser, WebTest):
"""Webtests for forms to test filling and submitting.""" """Webtests for domain application to test filling and submitting."""
# Doesn't work with CSRF checking # Doesn't work with CSRF checking
# hypothesis is that CSRF_USE_SESSIONS is incompatible with WebTest # hypothesis is that CSRF_USE_SESSIONS is incompatible with WebTest
@ -100,7 +102,7 @@ class FormTests(TestWithUser, WebTest):
self.app.set_user(self.user.username) self.app.set_user(self.user.username)
def tearDown(self): def tearDown(self):
# delete any applications we made so that users can be deleted\ # delete any applications we made so that users can be deleted
DomainApplication.objects.all().delete() DomainApplication.objects.all().delete()
super().tearDown() super().tearDown()
@ -116,6 +118,10 @@ class FormTests(TestWithUser, WebTest):
As we add additional form pages, we need to include them here to make As we add additional form pages, we need to include them here to make
this test work. this test work.
""" """
num_pages_tested = 0
SKIPPED_PAGES = 1 # elections
num_pages = len(TITLES) - SKIPPED_PAGES
type_page = self.app.get(reverse("application")).follow() type_page = self.app.get(reverse("application")).follow()
# django-webtest does not handle cookie-based sessions well because it keeps # django-webtest does not handle cookie-based sessions well because it keeps
# resetting the session key on each new request, thus destroying the concept # resetting the session key on each new request, thus destroying the concept
@ -125,9 +131,18 @@ class FormTests(TestWithUser, WebTest):
# ---- TYPE PAGE ---- # ---- TYPE PAGE ----
type_form = type_page.form type_form = type_page.form
type_form["organization_type-organization_type"] = "Federal" type_form["organization_type-organization_type"] = "federal"
# set the session ID before .submit() # test saving the page
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
result = type_page.form.submit("submit_button", value="save")
# should remain on the same page
self.assertEquals(result["Location"], "/register/organization_type/")
# should see results in db
application = DomainApplication.objects.get() # there's only one
self.assertEquals(application.organization_type, "federal")
# test next button
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
type_result = type_page.form.submit() type_result = type_page.form.submit()
@ -135,20 +150,31 @@ class FormTests(TestWithUser, WebTest):
# the application # the application
self.assertEquals(type_result.status_code, 302) self.assertEquals(type_result.status_code, 302)
self.assertEquals(type_result["Location"], "/register/organization_federal/") self.assertEquals(type_result["Location"], "/register/organization_federal/")
num_pages_tested += 1
# ---- FEDERAL BRANCH PAGE ---- # ---- FEDERAL BRANCH PAGE ----
# Follow the redirect to the next form page # Follow the redirect to the next form page
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
federal_page = type_result.follow() federal_page = type_result.follow()
federal_form = federal_page.form federal_form = federal_page.form
federal_form["organization_federal-federal_type"] = "Executive" federal_form["organization_federal-federal_type"] = "executive"
# set the session ID before .submit() # test saving the page
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
result = federal_page.form.submit("submit_button", value="save")
# should remain on the same page
self.assertEquals(result["Location"], "/register/organization_federal/")
# should see results in db
application = DomainApplication.objects.get() # there's only one
self.assertEquals(application.federal_type, "executive")
# test next button
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
federal_result = federal_form.submit() federal_result = federal_form.submit()
self.assertEquals(federal_result.status_code, 302) self.assertEquals(federal_result.status_code, 302)
self.assertEquals(federal_result["Location"], "/register/organization_contact/") self.assertEquals(federal_result["Location"], "/register/organization_contact/")
num_pages_tested += 1
# ---- ORG CONTACT PAGE ---- # ---- ORG CONTACT PAGE ----
# Follow the redirect to the next form page # Follow the redirect to the next form page
@ -156,9 +182,22 @@ class FormTests(TestWithUser, WebTest):
org_contact_form = org_contact_page.form org_contact_form = org_contact_page.form
org_contact_form["organization_contact-organization_name"] = "Testorg" org_contact_form["organization_contact-organization_name"] = "Testorg"
org_contact_form["organization_contact-address_line1"] = "address 1" org_contact_form["organization_contact-address_line1"] = "address 1"
org_contact_form["organization_contact-us_state"] = "NY" org_contact_form["organization_contact-state_territory"] = "NY"
org_contact_form["organization_contact-zipcode"] = "10002" org_contact_form["organization_contact-zipcode"] = "10002"
# test saving the page
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
result = org_contact_page.form.submit("submit_button", value="save")
# should remain on the same page
self.assertEquals(result["Location"], "/register/organization_contact/")
# should see results in db
application = DomainApplication.objects.get() # there's only one
self.assertEquals(application.organization_name, "Testorg")
self.assertEquals(application.address_line1, "address 1")
self.assertEquals(application.state_territory, "NY")
self.assertEquals(application.zipcode, "10002")
# test next button
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
org_contact_result = org_contact_form.submit() org_contact_result = org_contact_form.submit()
@ -166,6 +205,8 @@ class FormTests(TestWithUser, WebTest):
self.assertEquals( self.assertEquals(
org_contact_result["Location"], "/register/authorizing_official/" org_contact_result["Location"], "/register/authorizing_official/"
) )
num_pages_tested += 1
# ---- AUTHORIZING OFFICIAL PAGE ---- # ---- AUTHORIZING OFFICIAL PAGE ----
# Follow the redirect to the next form page # Follow the redirect to the next form page
ao_page = org_contact_result.follow() ao_page = org_contact_result.follow()
@ -176,11 +217,26 @@ class FormTests(TestWithUser, WebTest):
ao_form["authorizing_official-email"] = "testy@town.com" ao_form["authorizing_official-email"] = "testy@town.com"
ao_form["authorizing_official-phone"] = "(555) 555 5555" ao_form["authorizing_official-phone"] = "(555) 555 5555"
# test saving the page
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
result = ao_page.form.submit("submit_button", value="save")
# should remain on the same page
self.assertEquals(result["Location"], "/register/authorizing_official/")
# should see results in db
application = DomainApplication.objects.get() # there's only one
self.assertEquals(application.authorizing_official.first_name, "Testy")
self.assertEquals(application.authorizing_official.last_name, "Tester")
self.assertEquals(application.authorizing_official.title, "Chief Tester")
self.assertEquals(application.authorizing_official.email, "testy@town.com")
self.assertEquals(application.authorizing_official.phone, "(555) 555 5555")
# test next button
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
ao_result = ao_form.submit() ao_result = ao_form.submit()
self.assertEquals(ao_result.status_code, 302) self.assertEquals(ao_result.status_code, 302)
self.assertEquals(ao_result["Location"], "/register/current_sites/") self.assertEquals(ao_result["Location"], "/register/current_sites/")
num_pages_tested += 1
# ---- CURRENT SITES PAGE ---- # ---- CURRENT SITES PAGE ----
# Follow the redirect to the next form page # Follow the redirect to the next form page
@ -188,35 +244,73 @@ class FormTests(TestWithUser, WebTest):
current_sites_form = current_sites_page.form current_sites_form = current_sites_page.form
current_sites_form["current_sites-current_site"] = "www.city.com" current_sites_form["current_sites-current_site"] = "www.city.com"
# test saving the page
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
result = current_sites_page.form.submit("submit_button", value="save")
# should remain on the same page
self.assertEquals(result["Location"], "/register/current_sites/")
# should see results in db
application = DomainApplication.objects.get() # there's only one
self.assertEquals(
application.current_websites.filter(website="city.com").count(), 1
)
# test next button
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
current_sites_result = current_sites_form.submit() current_sites_result = current_sites_form.submit()
self.assertEquals(current_sites_result.status_code, 302) self.assertEquals(current_sites_result.status_code, 302)
self.assertEquals(current_sites_result["Location"], "/register/dotgov_domain/") self.assertEquals(current_sites_result["Location"], "/register/dotgov_domain/")
num_pages_tested += 1
# ---- DOTGOV DOMAIN PAGE ---- # ---- DOTGOV DOMAIN PAGE ----
# Follow the redirect to the next form page # Follow the redirect to the next form page
dotgov_page = current_sites_result.follow() dotgov_page = current_sites_result.follow()
dotgov_form = dotgov_page.form dotgov_form = dotgov_page.form
dotgov_form["dotgov_domain-dotgov_domain"] = "city" dotgov_form["dotgov_domain-requested_domain"] = "city"
dotgov_form["dotgov_domain-alternative_domain"] = "city1"
# test saving the page
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
result = dotgov_page.form.submit("submit_button", value="save")
# should remain on the same page
self.assertEquals(result["Location"], "/register/dotgov_domain/")
# should see results in db
application = DomainApplication.objects.get() # there's only one
self.assertEquals(application.requested_domain.name, "city.gov")
self.assertEquals(
application.alternative_domains.filter(website="city1.gov").count(), 1
)
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
dotgov_result = dotgov_form.submit() dotgov_result = dotgov_form.submit()
self.assertEquals(dotgov_result.status_code, 302) self.assertEquals(dotgov_result.status_code, 302)
self.assertEquals(dotgov_result["Location"], "/register/purpose/") self.assertEquals(dotgov_result["Location"], "/register/purpose/")
num_pages_tested += 1
# ---- PURPOSE DOMAIN PAGE ---- # ---- PURPOSE PAGE ----
# Follow the redirect to the next form page # Follow the redirect to the next form page
purpose_page = dotgov_result.follow() purpose_page = dotgov_result.follow()
purpose_form = purpose_page.form purpose_form = purpose_page.form
purpose_form["purpose-purpose_field"] = "Purpose of the site" purpose_form["purpose-purpose"] = "Purpose of the site"
# test saving the page
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
result = purpose_page.form.submit("submit_button", value="save")
# should remain on the same page
self.assertEquals(result["Location"], "/register/purpose/")
# should see results in db
application = DomainApplication.objects.get() # there's only one
self.assertEquals(application.purpose, "Purpose of the site")
# test next button
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
purpose_result = purpose_form.submit() purpose_result = purpose_form.submit()
self.assertEquals(purpose_result.status_code, 302) self.assertEquals(purpose_result.status_code, 302)
self.assertEquals(purpose_result["Location"], "/register/your_contact/") self.assertEquals(purpose_result["Location"], "/register/your_contact/")
num_pages_tested += 1
# ---- YOUR CONTACT INFO PAGE ---- # ---- YOUR CONTACT INFO PAGE ----
# Follow the redirect to the next form page # Follow the redirect to the next form page
@ -229,11 +323,26 @@ class FormTests(TestWithUser, WebTest):
your_contact_form["your_contact-email"] = "testy-admin@town.com" your_contact_form["your_contact-email"] = "testy-admin@town.com"
your_contact_form["your_contact-phone"] = "(555) 555 5556" your_contact_form["your_contact-phone"] = "(555) 555 5556"
# test saving the page
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
result = your_contact_page.form.submit("submit_button", value="save")
# should remain on the same page
self.assertEquals(result["Location"], "/register/your_contact/")
# should see results in db
application = DomainApplication.objects.get() # there's only one
self.assertEquals(application.submitter.first_name, "Testy you")
self.assertEquals(application.submitter.last_name, "Tester you")
self.assertEquals(application.submitter.title, "Admin Tester")
self.assertEquals(application.submitter.email, "testy-admin@town.com")
self.assertEquals(application.submitter.phone, "(555) 555 5556")
# test next button
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
your_contact_result = your_contact_form.submit() your_contact_result = your_contact_form.submit()
self.assertEquals(your_contact_result.status_code, 302) self.assertEquals(your_contact_result.status_code, 302)
self.assertEquals(your_contact_result["Location"], "/register/other_contacts/") self.assertEquals(your_contact_result["Location"], "/register/other_contacts/")
num_pages_tested += 1
# ---- OTHER CONTACTS PAGE ---- # ---- OTHER CONTACTS PAGE ----
# Follow the redirect to the next form page # Follow the redirect to the next form page
@ -246,6 +355,25 @@ class FormTests(TestWithUser, WebTest):
other_contacts_form["other_contacts-email"] = "testy2@town.com" other_contacts_form["other_contacts-email"] = "testy2@town.com"
other_contacts_form["other_contacts-phone"] = "(555) 555 5557" other_contacts_form["other_contacts-phone"] = "(555) 555 5557"
# test saving the page
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
result = other_contacts_page.form.submit("submit_button", value="save")
# should remain on the same page
self.assertEquals(result["Location"], "/register/other_contacts/")
# should see results in db
application = DomainApplication.objects.get() # there's only one
self.assertEquals(
application.other_contacts.filter(
first_name="Testy2",
last_name="Tester2",
title="Another Tester",
email="testy2@town.com",
phone="(555) 555 5557",
).count(),
1,
)
# test next button
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
other_contacts_result = other_contacts_form.submit() other_contacts_result = other_contacts_form.submit()
@ -253,19 +381,31 @@ class FormTests(TestWithUser, WebTest):
self.assertEquals( self.assertEquals(
other_contacts_result["Location"], "/register/security_email/" other_contacts_result["Location"], "/register/security_email/"
) )
num_pages_tested += 1
# ---- SECURITY EMAIL PAGE ---- # ---- SECURITY EMAIL PAGE ----
# Follow the redirect to the next form page # Follow the redirect to the next form page
security_email_page = other_contacts_result.follow() security_email_page = other_contacts_result.follow()
security_email_form = security_email_page.form security_email_form = security_email_page.form
security_email_form["security_email-email"] = "security@city.com" security_email_form["security_email-security_email"] = "security@city.com"
# test saving the page
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
result = security_email_page.form.submit("submit_button", value="save")
# should remain on the same page
self.assertEquals(result["Location"], "/register/security_email/")
# should see results in db
application = DomainApplication.objects.get() # there's only one
self.assertEquals(application.security_email, "security@city.com")
# test next button
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
security_email_result = security_email_form.submit() security_email_result = security_email_form.submit()
self.assertEquals(security_email_result.status_code, 302) self.assertEquals(security_email_result.status_code, 302)
self.assertEquals(security_email_result["Location"], "/register/anything_else/") self.assertEquals(security_email_result["Location"], "/register/anything_else/")
num_pages_tested += 1
# ---- ANYTHING ELSE PAGE ---- # ---- ANYTHING ELSE PAGE ----
# Follow the redirect to the next form page # Follow the redirect to the next form page
@ -274,37 +414,65 @@ class FormTests(TestWithUser, WebTest):
anything_else_form["anything_else-anything_else"] = "No" anything_else_form["anything_else-anything_else"] = "No"
# test saving the page
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
result = anything_else_page.form.submit("submit_button", value="save")
# should remain on the same page
self.assertEquals(result["Location"], "/register/anything_else/")
# should see results in db
application = DomainApplication.objects.get() # there's only one
self.assertEquals(application.anything_else, "No")
# test next button
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
anything_else_result = anything_else_form.submit() anything_else_result = anything_else_form.submit()
self.assertEquals(anything_else_result.status_code, 302) self.assertEquals(anything_else_result.status_code, 302)
self.assertEquals(anything_else_result["Location"], "/register/requirements/") self.assertEquals(anything_else_result["Location"], "/register/requirements/")
num_pages_tested += 1
# ---- REQUIREMENTS PAGE ---- # ---- REQUIREMENTS PAGE ----
# Follow the redirect to the next form page # Follow the redirect to the next form page
requirements_page = anything_else_result.follow() requirements_page = anything_else_result.follow()
requirements_form = requirements_page.form requirements_form = requirements_page.form
requirements_form["requirements-agree_check"] = True requirements_form["requirements-is_policy_acknowledged"] = True
# test saving the page
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
result = requirements_page.form.submit("submit_button", value="save")
# should remain on the same page
self.assertEquals(result["Location"], "/register/requirements/")
# should see results in db
application = DomainApplication.objects.get() # there's only one
self.assertEquals(application.is_policy_acknowledged, True)
# test next button
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
requirements_result = requirements_form.submit() requirements_result = requirements_form.submit()
self.assertEquals(requirements_result.status_code, 302) self.assertEquals(requirements_result.status_code, 302)
self.assertEquals(requirements_result["Location"], "/register/review/") self.assertEquals(requirements_result["Location"], "/register/review/")
num_pages_tested += 1
# ---- REVIEW AND FINSIHED PAGES ---- # ---- REVIEW AND FINSIHED PAGES ----
# Follow the redirect to the next form page # Follow the redirect to the next form page
review_page = requirements_result.follow() review_page = requirements_result.follow()
review_form = review_page.form review_form = review_page.form
# final submission results in a redirect to the "finished" URL # test saving the page
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
result = review_page.form.submit("submit_button", value="save")
# should remain on the same page
self.assertEquals(result["Location"], "/register/review/")
# final submission results in a redirect to the "finished" URL
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
review_result = review_form.submit() review_result = review_form.submit()
self.assertEquals(review_result.status_code, 302) self.assertEquals(review_result.status_code, 302)
self.assertEquals(review_result["Location"], "/register/finished/") self.assertEquals(review_result["Location"], "/register/finished/")
num_pages_tested += 1
# following this redirect is a GET request, so include the cookie # following this redirect is a GET request, so include the cookie
# here too. # here too.
@ -313,6 +481,9 @@ class FormTests(TestWithUser, WebTest):
final_result = review_result.follow() final_result = review_result.follow()
self.assertContains(final_result, "Thank you for your domain request") self.assertContains(final_result, "Thank you for your domain request")
# check that any new pages are added to this test
self.assertEqual(num_pages, num_pages_tested)
def test_application_form_conditional_federal(self): def test_application_form_conditional_federal(self):
"""Federal branch question is shown for federal organizations.""" """Federal branch question is shown for federal organizations."""
type_page = self.app.get(reverse("application")).follow() type_page = self.app.get(reverse("application")).follow()
@ -328,7 +499,7 @@ class FormTests(TestWithUser, WebTest):
self.assertNotContains(type_page, TITLES["organization_federal"]) self.assertNotContains(type_page, TITLES["organization_federal"])
self.assertNotContains(type_page, TITLES["organization_election"]) self.assertNotContains(type_page, TITLES["organization_election"])
type_form = type_page.form type_form = type_page.form
type_form["organization_type-organization_type"] = "Federal" type_form["organization_type-organization_type"] = "federal"
# set the session ID before .submit() # set the session ID before .submit()
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
@ -361,14 +532,13 @@ class FormTests(TestWithUser, WebTest):
self.assertNotContains(type_page, TITLES["organization_federal"]) self.assertNotContains(type_page, TITLES["organization_federal"])
self.assertNotContains(type_page, TITLES["organization_election"]) self.assertNotContains(type_page, TITLES["organization_election"])
type_form = type_page.form type_form = type_page.form
type_form["organization_type-organization_type"] = "County" type_form["organization_type-organization_type"] = "county"
# set the session ID before .submit() # set the session ID before .submit()
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
type_result = type_form.submit() type_result = type_form.submit()
# the post request should return a redirect to the federal branch # the post request should return a redirect to the elections question
# question
self.assertEquals(type_result.status_code, 302) self.assertEquals(type_result.status_code, 302)
self.assertEquals(type_result["Location"], "/register/organization_election/") self.assertEquals(type_result["Location"], "/register/organization_election/")
@ -378,3 +548,124 @@ class FormTests(TestWithUser, WebTest):
election_page = type_result.follow() election_page = type_result.follow()
self.assertContains(election_page, TITLES["organization_election"]) self.assertContains(election_page, TITLES["organization_election"])
self.assertNotContains(election_page, TITLES["organization_federal"]) self.assertNotContains(election_page, TITLES["organization_federal"])
@skip("WIP")
def test_application_edit_restore(self):
"""
Test that a previously saved application is available at the /edit endpoint.
"""
ao, _ = Contact.objects.get_or_create(
first_name="Testy",
last_name="Tester",
title="Chief Tester",
email="testy@town.com",
phone="(555) 555 5555",
)
domain, _ = Domain.objects.get_or_create(name="city.gov")
alt, _ = Website.objects.get_or_create(website="city1.gov")
current, _ = Website.objects.get_or_create(website="city.com")
you, _ = Contact.objects.get_or_create(
first_name="Testy you",
last_name="Tester you",
title="Admin Tester",
email="testy-admin@town.com",
phone="(555) 555 5556",
)
other, _ = Contact.objects.get_or_create(
first_name="Testy2",
last_name="Tester2",
title="Another Tester",
email="testy2@town.com",
phone="(555) 555 5557",
)
application, _ = DomainApplication.objects.get_or_create(
organization_type="federal",
federal_type="executive",
purpose="Purpose of the site",
security_email="security@city.com",
anything_else="No",
is_policy_acknowledged=True,
organization_name="Testorg",
address_line1="address 1",
state_territory="NY",
zipcode="10002",
authorizing_official=ao,
requested_domain=domain,
submitter=you,
creator=self.user,
)
application.other_contacts.add(other)
application.current_websites.add(current)
application.alternative_domains.add(alt)
# prime the form by visiting /edit
url = reverse("edit-application", kwargs={"id": application.pk})
response = self.client.get(url)
url = reverse("application_step", kwargs={"step": "organization_type"})
response = self.client.get(url, follow=True)
self.assertContains(response, "<input>")
# choices = response.context['wizard']['form']['organization_type'].subwidgets
# radio = [ x for x in choices if x.data["value"] == "federal" ][0]
# checked = radio.data["selected"]
# self.assertTrue(checked)
# url = reverse("application_step", kwargs={"step": "organization_federal"})
# self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
# page = self.app.get(url)
# self.assertNotContains(page, "VALUE")
# url = reverse("application_step", kwargs={"step": "organization_contact"})
# self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
# page = self.app.get(url)
# self.assertNotContains(page, "VALUE")
# url = reverse("application_step", kwargs={"step": "authorizing_official"})
# self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
# page = self.app.get(url)
# self.assertNotContains(page, "VALUE")
# url = reverse("application_step", kwargs={"step": "current_sites"})
# self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
# page = self.app.get(url)
# self.assertNotContains(page, "VALUE")
# url = reverse("application_step", kwargs={"step": "dotgov_domain"})
# self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
# page = self.app.get(url)
# self.assertNotContains(page, "VALUE")
# url = reverse("application_step", kwargs={"step": "purpose"})
# self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
# page = self.app.get(url)
# self.assertNotContains(page, "VALUE")
# url = reverse("application_step", kwargs={"step": "your_contact"})
# self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
# page = self.app.get(url)
# self.assertNotContains(page, "VALUE")
# url = reverse("application_step", kwargs={"step": "other_contacts"})
# self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
# page = self.app.get(url)
# self.assertNotContains(page, "VALUE")
# url = reverse("application_step", kwargs={"step": "other_contacts"})
# self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
# page = self.app.get(url)
# self.assertNotContains(page, "VALUE")
# url = reverse("application_step", kwargs={"step": "security_email"})
# self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
# page = self.app.get(url)
# self.assertNotContains(page, "VALUE")
# url = reverse("application_step", kwargs={"step": "anything_else"})
# self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
# page = self.app.get(url)
# self.assertNotContains(page, "VALUE")
# url = reverse("application_step", kwargs={"step": "requirements"})
# self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
# page = self.app.get(url)
# self.assertNotContains(page, "VALUE")