initial logic

This commit is contained in:
zandercymatics 2024-10-23 11:00:32 -06:00
parent 65f1e628a7
commit 860f8f4e3c
No known key found for this signature in database
GPG key ID: FF4636ABEC9682B7
7 changed files with 302 additions and 94 deletions

View file

@ -22,15 +22,17 @@ var SUCCESS = "success";
* *
*/ */
const hideElement = (element) => { const hideElement = (element) => {
element.classList.add('display-none'); if (element && !element.classList.contains("display-none"))
element.classList.add('display-none');
}; };
/** /**
* Show element * Show element
* *
*/ */
const showElement = (element) => { const showElement = (element) => {
element.classList.remove('display-none'); if (element && element.classList.contains("display-none"))
element.classList.remove('display-none');
}; };
/** /**
@ -2385,81 +2387,68 @@ document.addEventListener('DOMContentLoaded', function() {
// This determines if we are on the requesting entity page or not. // This determines if we are on the requesting entity page or not.
const fieldset = document.getElementById("requesting-entity-fieldset"); const fieldset = document.getElementById("requesting-entity-fieldset");
if (!fieldset) return; if (!fieldset) return;
console.log("past here")
// Get the is_suborganization radio buttons // Get the is_suborganization radio buttons
// Sadly, these ugly ids are the auto generated // Sadly, these ugly ids are the auto generated
const formPrefix = "portfolio_requesting_entity" const formPrefix = "portfolio_requesting_entity"
const isSuborgRadios = document.querySelectorAll(`input[name="${formPrefix}-is_suborganization"]`); const isSuborgRadios = document.querySelectorAll(`input[name="${formPrefix}-is_suborganization"]`);
var selectedRequestingEntityValue = document.querySelector(`input[name="${formPrefix}-is_suborganization"]:checked`)?.value;
const subOrgSelect = document.querySelector(`#id_${formPrefix}-sub_organization`); const subOrgSelect = document.querySelector(`#id_${formPrefix}-sub_organization`);
const orgName = document.querySelector(`#id_${formPrefix}-organization_name`);
const city = document.querySelector(`#id_${formPrefix}-city`);
const stateTerritory = document.querySelector(`#id_${formPrefix}-state_territory`);
console.log(isSuborgRadios) // The suborganization section is its own div
console.log(subOrgSelect) const suborganizationFieldset = document.querySelector("#requesting-entity-fieldset__suborganization");
console.log(orgName)
console.log(city) // Within the suborganization section, we also have a div that contains orgname, city, and stateterritory
console.log(stateTerritory) const suborganizationDetailsFieldset = document.querySelector("#requesting-entity-fieldset__suborganization__details");
console.log(selectedRequestingEntityValue)
// Use a variable to determine which option has been selected on the yes/no form.
// Don't do anything if we are missing crucial page elements // Don't do anything if we are missing crucial page elements
if (!isSuborgRadios || !subOrgSelect || !orgName || !city || !stateTerritory) return; if (!isSuborgRadios || !subOrgSelect || !suborganizationFieldset || !suborganizationDetailsFieldset) return;
console.log("past here x2")
// Add fake "other" option to sub_organization select // Function to toggle suborganization based on is_suborganization selection
if (subOrgSelect && !Array.from(subOrgSelect.options).some(option => option.value === "other")) { function toggleSuborganization(radio) {
const fakeOption = document.createElement("option"); if (radio && radio.checked && radio.value === "True") {
fakeOption.value = "other"; showElement(suborganizationFieldset);
fakeOption.text = "Other (enter your organization manually)"; toggleSuborganizationDetails();
subOrgSelect.add(fakeOption); } else {
} hideElement(suborganizationFieldset);
hideElement(suborganizationDetailsFieldset);
// Hide organization_name, city, state_territory by default }
hideElement(orgName.parentElement);
hideElement(city.parentElement);
hideElement(stateTerritory.parentElement);
// Function to toggle forms based on is_suborganization selection
function toggleSubOrganizationFields () {
selectedRequestingEntityValue = document.querySelector(`input[name="${formPrefix}-is_suborganization"]:checked`)?.value;
if (selectedRequestingEntityValue === "True") {
showElement(subOrgSelect.parentElement);
toggleOrganizationDetails();
} else {
hideElement(subOrgSelect.parentElement);
hideElement(orgName.parentElement);
hideElement(city.parentElement);
hideElement(stateTerritory.parentElement);
}
}; };
// Function to toggle organization details based on sub_organization selection // Function to toggle organization details based on sub_organization selection
function toggleOrganizationDetails () { function toggleSuborganizationDetails () {
// We should hide the org name fields when we select the special other value // We should hide the org name fields when we select the special other value
if (subOrgSelect.value === "other") { if (subOrgSelect.value === "other") {
showElement(orgName.parentElement); showElement(suborganizationDetailsFieldset);
showElement(city.parentElement); } else {
showElement(stateTerritory.parentElement); hideElement(suborganizationDetailsFieldset);
} else { }
hideElement(orgName.parentElement);
hideElement(city.parentElement);
hideElement(stateTerritory.parentElement);
}
}; };
// Initialize visibility // Add fake "other" option to sub_organization select
toggleSubOrganizationFields(); if (subOrgSelect && !Array.from(subOrgSelect.options).some(option => option.value === "other")) {
const fakeOption = document.createElement("option");
fakeOption.value = "other";
fakeOption.text = "Other (enter your organization manually)";
subOrgSelect.add(fakeOption);
}
// Add event listeners to is_suborganization radio buttons // Add event listener to is_suborganization radio buttons
isSuborgRadios.forEach(radio => { isSuborgRadios.forEach(radio => {
radio.addEventListener("change", () => { // Run this here for initial display.
toggleSubOrganizationFields(); // Since there are only two radio buttons and since this has (practically speaking) no performance impact, this is fine to do.
}); toggleSuborganization(radio);
// Add an event listener to each to show/hide the relevant fields
radio.addEventListener("click", () => {
toggleSuborganization(radio);
});
}); });
// Add event listener to the suborg dropdown to show/hide the suborg details section
subOrgSelect.addEventListener("change", () => { subOrgSelect.addEventListener("change", () => {
if (selectedRequestingEntityValue === "True") { toggleSuborganizationDetails();
toggleOrganizationDetails();
}
}); });
})(); })();

View file

@ -31,17 +31,20 @@ class RequestingEntityForm(RegistrarForm):
queryset=Suborganization.objects.none(), queryset=Suborganization.objects.none(),
empty_label="--Select--", empty_label="--Select--",
) )
organization_name = forms.CharField(
# We are using the current sub_organization naming convention here.
# We may want to refactor this to suborganization eventually.
requested_suborganization = forms.CharField(
label="Requested suborganization", label="Requested suborganization",
required=False, required=False,
error_messages={"required": "Enter the name of your organization."}, error_messages={"required": "Enter the name of your organization."},
) )
city = forms.CharField( suborganization_city = forms.CharField(
label="City", label="City",
required=False, required=False,
error_messages={"required": "Enter the city where your organization is located."}, error_messages={"required": "Enter the city where your organization is located."},
) )
state_territory = forms.ChoiceField( suborganization_state_territory = forms.ChoiceField(
label="State, territory, or military post", label="State, territory, or military post",
required=False, required=False,
choices=[("", "--Select--")] + DomainRequest.StateTerritoryChoices.choices, choices=[("", "--Select--")] + DomainRequest.StateTerritoryChoices.choices,
@ -66,26 +69,52 @@ class RequestingEntityForm(RegistrarForm):
def clean_sub_organization(self): def clean_sub_organization(self):
"""Require something to be selected when this is a federal agency.""" """Require something to be selected when this is a federal agency."""
is_suborganization = self.cleaned_data.get("is_suborganization", None)
sub_organization = self.cleaned_data.get("sub_organization", None) sub_organization = self.cleaned_data.get("sub_organization", None)
if self.cleaned_data.get("is_suborganization", None): print(f"sub org is: {sub_organization} vs is_suborg: {is_suborganization}")
# TODO - logic for if other is selected, display other stuff if is_suborganization and not sub_organization:
if not sub_organization: raise forms.ValidationError(
# no answer was selected "Select a suborganization.",
code="required",
)
return sub_organization
def clean_requested_suborganization(self):
field = self.cleaned_data.get("requested_suborganization")
if self.is_custom_suborg() and not field:
raise forms.ValidationError(
"Enter details for your organization name.",
code="required",
)
return field
def clean_suborganization_city(self):
field = self.cleaned_data.get("suborganization_city")
if self.is_custom_suborg():
if not field:
raise forms.ValidationError( raise forms.ValidationError(
"Select a suborganization.", "Enter details for your city.",
code="required", code="required",
) )
# Maybe we just represent this with none? return field
elif sub_organization == "other":
org_name = self.cleaned_data.get("organization_name", None) def clean_suborganization_state_territory(self):
city = self.cleaned_data.get("city", None) field = self.cleaned_data.get("suborganization_state_territory")
state = self.cleaned_data.get("state_territory", None) if self.is_custom_suborg():
if not org_name or not city or not state: if not field:
raise forms.ValidationError( raise forms.ValidationError(
"Enter details for your suborganization.", "Enter details for your state or territory.",
code="required", code="required",
) )
return sub_organization return field
def is_custom_suborg(self):
"""Checks that the select suborg is 'other', which is a custom field indicating
that we should create a new suborganization."""
is_suborganization = self.cleaned_data.get("is_suborganization")
sub_organization = self.cleaned_data.get("sub_organization")
return is_suborganization and sub_organization == "other"
class RequestingEntityYesNoForm(BaseYesNoForm): class RequestingEntityYesNoForm(BaseYesNoForm):
"""The yes/no field for the RequestingEntity form.""" """The yes/no field for the RequestingEntity form."""
@ -107,10 +136,11 @@ class RequestingEntityYesNoForm(BaseYesNoForm):
Determines the initial checked state of the form based on the domain_request's attributes. Determines the initial checked state of the form based on the domain_request's attributes.
""" """
if self.domain_request.portfolio and (self.domain_request.sub_organization or self.domain_request.organization_name): if self.domain_request.portfolio and self.domain_request.organization_name == self.domain_request.portfolio.organization_name:
return self.domain_request.organization_name != self.domain_request.portfolio.organization_name return False
elif self.domain_request.is_suborganization():
return True
else: else:
# No pre-selection for new domain requests
return None return None
class OrganizationTypeForm(RegistrarForm): class OrganizationTypeForm(RegistrarForm):

View file

@ -0,0 +1,95 @@
# Generated by Django 4.2.10 on 2024-10-23 15:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("registrar", "0133_domainrequest_rejection_reason_email_and_more"),
]
operations = [
migrations.AddField(
model_name="domainrequest",
name="requested_suborganization",
field=models.CharField(blank=True, null=True),
),
migrations.AddField(
model_name="domainrequest",
name="suborganization_city",
field=models.CharField(blank=True, null=True),
),
migrations.AddField(
model_name="domainrequest",
name="suborganization_state_territory",
field=models.CharField(
blank=True,
choices=[
("AL", "Alabama (AL)"),
("AK", "Alaska (AK)"),
("AS", "American Samoa (AS)"),
("AZ", "Arizona (AZ)"),
("AR", "Arkansas (AR)"),
("CA", "California (CA)"),
("CO", "Colorado (CO)"),
("CT", "Connecticut (CT)"),
("DE", "Delaware (DE)"),
("DC", "District of Columbia (DC)"),
("FL", "Florida (FL)"),
("GA", "Georgia (GA)"),
("GU", "Guam (GU)"),
("HI", "Hawaii (HI)"),
("ID", "Idaho (ID)"),
("IL", "Illinois (IL)"),
("IN", "Indiana (IN)"),
("IA", "Iowa (IA)"),
("KS", "Kansas (KS)"),
("KY", "Kentucky (KY)"),
("LA", "Louisiana (LA)"),
("ME", "Maine (ME)"),
("MD", "Maryland (MD)"),
("MA", "Massachusetts (MA)"),
("MI", "Michigan (MI)"),
("MN", "Minnesota (MN)"),
("MS", "Mississippi (MS)"),
("MO", "Missouri (MO)"),
("MT", "Montana (MT)"),
("NE", "Nebraska (NE)"),
("NV", "Nevada (NV)"),
("NH", "New Hampshire (NH)"),
("NJ", "New Jersey (NJ)"),
("NM", "New Mexico (NM)"),
("NY", "New York (NY)"),
("NC", "North Carolina (NC)"),
("ND", "North Dakota (ND)"),
("MP", "Northern Mariana Islands (MP)"),
("OH", "Ohio (OH)"),
("OK", "Oklahoma (OK)"),
("OR", "Oregon (OR)"),
("PA", "Pennsylvania (PA)"),
("PR", "Puerto Rico (PR)"),
("RI", "Rhode Island (RI)"),
("SC", "South Carolina (SC)"),
("SD", "South Dakota (SD)"),
("TN", "Tennessee (TN)"),
("TX", "Texas (TX)"),
("UM", "United States Minor Outlying Islands (UM)"),
("UT", "Utah (UT)"),
("VT", "Vermont (VT)"),
("VI", "Virgin Islands (VI)"),
("VA", "Virginia (VA)"),
("WA", "Washington (WA)"),
("WV", "West Virginia (WV)"),
("WI", "Wisconsin (WI)"),
("WY", "Wyoming (WY)"),
("AA", "Armed Forces Americas (AA)"),
("AE", "Armed Forces Africa, Canada, Europe, Middle East (AE)"),
("AP", "Armed Forces Pacific (AP)"),
],
max_length=2,
null=True,
verbose_name="state, territory, or military post",
),
),
]

View file

@ -344,6 +344,24 @@ class DomainRequest(TimeStampedModel):
verbose_name="Suborganization", verbose_name="Suborganization",
) )
requested_suborganization = models.CharField(
null=True,
blank=True,
)
suborganization_city = models.CharField(
null=True,
blank=True,
)
suborganization_state_territory = models.CharField(
max_length=2,
choices=StateTerritoryChoices.choices,
null=True,
blank=True,
verbose_name="state, territory, or military post",
)
# This is the domain request user who created this domain request. # This is the domain request user who created this domain request.
creator = models.ForeignKey( creator = models.ForeignKey(
"registrar.User", "registrar.User",
@ -1104,6 +1122,15 @@ class DomainRequest(TimeStampedModel):
self.creator.restrict_user() self.creator.restrict_user()
# Form unlocking steps
# These methods control the conditions in which we should unlock certain domain wizard steps.
def unlock_requesting_entity(self) -> bool:
"""Unlocks the requesting entity step """
if self.portfolio and self.organization_name == self.portfolio.organization_name:
return True
else:
return self.is_suborganization()
# ## Form policies ### # ## Form policies ###
# #
# These methods control what questions need to be answered by applicants # These methods control what questions need to be answered by applicants
@ -1174,14 +1201,23 @@ class DomainRequest(TimeStampedModel):
return False return False
def is_suborganization(self) -> bool: def is_suborganization(self) -> bool:
"""Determines if this record is a suborganization or not"""
if self.portfolio: if self.portfolio:
if self.sub_organization: if self.sub_organization:
return True return True
if self.organization_name != self.portfolio.organization_name: if self.has_information_required_to_make_suborganization():
return True return True
return False return False
def has_information_required_to_make_suborganization(self):
"""Checks if we have all the information we need to create a new suborganization object.
Checks for a the existence of requested_suborganization, suborganization_city, suborganization_state_territory"""
return (
self.requested_domain and
self.suborganization_city and
self.suborganization_state_territory
)
def to_dict(self): def to_dict(self):
"""This is to process to_dict for Domain Information, making it friendly """This is to process to_dict for Domain Information, making it friendly

View file

@ -1,4 +1,5 @@
from django.db import models from django.db import models
from registrar.models import DomainRequest
from .utility.time_stamped_model import TimeStampedModel from .utility.time_stamped_model import TimeStampedModel

View file

@ -11,13 +11,38 @@
<legend> <legend>
<h2>Who will use the domain youre requesting?</h2> <h2>Who will use the domain youre requesting?</h2>
</legend> </legend>
{% with add_class="usa-radio__input--tile" add_legend_class="usa-sr-only" %}
{% input_with_errors forms.0.is_suborganization %}
{% endwith %}
{# forms.0 is a small yes/no form that toggles the visibility of "requesting entity" formset #} {# forms.0 is a small yes/no form that toggles the visibility of "requesting entity" formset #}
{% input_with_errors forms.1.sub_organization %} {% with add_class="usa-radio__input--tile" add_legend_class="usa-sr-only" %}
{% input_with_errors forms.1.organization_name %} {% with attr_required=True %}
{% input_with_errors forms.1.city %} {% input_with_errors forms.0.is_suborganization %}
{% input_with_errors forms.1.state_territory %} {% endwith %}
{% endwith %}
<div id="requesting-entity-fieldset__suborganization" class="margin-top-4">
<h2>Add suborganization information</h2>
<p>
This information will be published in <a class="usa-link usa-link--always-blue" href="{% public_site_url 'about/data' %}">.govs public data</a>. If you dont see your suborganization in the list,
select “other” and enter the name or your suborganization.
</p>
{% with attr_required=True %}
{% input_with_errors forms.1.sub_organization %}
{% endwith %}
{% comment %} This will be toggled if a special value, "other", is selected.
Otherwise this field is invisible.
{% endcomment %}
<div id="requesting-entity-fieldset__suborganization__details">
{% with attr_required=True %}
{% input_with_errors forms.1.requested_suborganization %}
{% endwith %}
{% with attr_required=True %}
{% input_with_errors forms.1.suborganization_city %}
{% endwith %}
{% with attr_required=True %}
{% input_with_errors forms.1.suborganization_state_territory %}
{% endwith %}
</div>
</div>
</fieldset> </fieldset>
{% endblock %} {% endblock %}

View file

@ -11,6 +11,7 @@ from registrar.forms import domain_request_wizard as forms
from registrar.forms.utility.wizard_form_helper import request_step_list from registrar.forms.utility.wizard_form_helper import request_step_list
from registrar.models import DomainRequest from registrar.models import DomainRequest
from registrar.models.contact import Contact from registrar.models.contact import Contact
from registrar.models.suborganization import Suborganization
from registrar.models.user import User from registrar.models.user import User
from registrar.views.utility import StepsHelper from registrar.views.utility import StepsHelper
from registrar.views.utility.permission_views import DomainRequestPermissionDeleteView from registrar.views.utility.permission_views import DomainRequestPermissionDeleteView
@ -137,7 +138,7 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
} }
PORTFOLIO_UNLOCKING_STEPS = { PORTFOLIO_UNLOCKING_STEPS = {
PortfolioDomainRequestStep.REQUESTING_ENTITY: lambda self: self.domain_request.organization_name is not None, PortfolioDomainRequestStep.REQUESTING_ENTITY: lambda w: w.from_model("unlock_requesting_entity", False),
PortfolioDomainRequestStep.CURRENT_SITES: lambda self: ( PortfolioDomainRequestStep.CURRENT_SITES: lambda self: (
self.domain_request.current_websites.exists() or self.domain_request.requested_domain is not None self.domain_request.current_websites.exists() or self.domain_request.requested_domain is not None
), ),
@ -590,6 +591,37 @@ class RequestingEntity(DomainRequestWizard):
template_name = "domain_request_requesting_entity.html" template_name = "domain_request_requesting_entity.html"
forms = [forms.RequestingEntityYesNoForm, forms.RequestingEntityForm] forms = [forms.RequestingEntityYesNoForm, forms.RequestingEntityForm]
def save(self, forms: list):
requesting_entity_form = forms[1]
cleaned_data = requesting_entity_form.cleaned_data
is_suborganization = cleaned_data.get("is_suborganization")
sub_organization = cleaned_data.get("sub_organization")
requested_suborganization = cleaned_data.get("requested_suborganization")
# If no suborganization presently exists but the user filled out org information then create a suborg automatically.
if is_suborganization and (sub_organization or requested_suborganization):
# Cleanup the organization name field, as this isn't for suborganizations.
self.domain_request.organization_name = None
# Create or get the Suborganization.
# Then update the domain_request with the new or existing suborganization
if not sub_organization:
sub_organization, created = Suborganization.objects.get_or_create(
name=cleaned_data.get("requested_suborganization"),
portfolio=self.domain_request.portfolio,
)
self.domain_request.sub_organization = sub_organization
else:
# If the user doesn't intend to create a suborg, simply don't make one and do some data cleanup
self.domain_request.organization_name = self.domain_request.portfolio.organization_name
self.domain_request.sub_organization = None
self.domain_request.requested_suborganization = None
self.domain_request.suborganization_city = None
self.domain_request.suborganization_state_territory = None
super().save(forms)
class PortfolioAdditionalDetails(DomainRequestWizard): class PortfolioAdditionalDetails(DomainRequestWizard):
template_name = "portfolio_domain_request_additional_details.html" template_name = "portfolio_domain_request_additional_details.html"