initial work on feb purpose questions

This commit is contained in:
Matt-Spence 2025-02-26 11:46:35 -05:00
parent b2c57b5aa1
commit df303fa66f
No known key found for this signature in database
6 changed files with 341 additions and 36 deletions

View file

@ -642,25 +642,6 @@ class ExecutiveNamingRequirementsDetailsForm(BaseDeletableRegistrarForm):
)
class PurposeForm(RegistrarForm):
purpose = forms.CharField(
label="Purpose",
widget=forms.Textarea(
attrs={
"aria-label": "What is the purpose of your requested domain? Describe how youll use your .gov domain. \
Will it be used for a website, email, or something else?"
}
),
validators=[
MaxLengthValidator(
2000,
message="Response must be less than 2000 characters.",
)
],
error_messages={"required": "Describe how youll use the .gov domain youre requesting."},
)
class OtherContactsYesNoForm(BaseYesNoForm):
"""The yes/no field for the OtherContacts form."""

View file

@ -0,0 +1,114 @@
from django import forms
from django.core.validators import MaxLengthValidator
from registrar.forms.utility.wizard_form_helper import BaseDeletableRegistrarForm, BaseYesNoForm, RegistrarForm
class FEBPurposeOptionsForm(BaseDeletableRegistrarForm):
field_name = "feb_purpose_choice"
form_choices = (("new", "Used for a new website"), ("redirect", "Used as a redirect for an existing website"), ("other", "Not for a website"))
feb_purpose_choice = forms.ChoiceField(
required=True,
choices=form_choices,
widget=forms.RadioSelect,
error_messages={
"required": "This question is required.",
},
label = "Select one"
)
class PurposeDetailsForm(BaseDeletableRegistrarForm):
labels = {
"new": "Explain why a new domain is required and why a subdomain of an existing domain doesn't meet your needs. \
Include any data that supports a clear public benefit or evident user need for this new domain.",
"redirect": "Explain why a redirect is necessary",
"other": "Describe how this domain will be used",
}
field_name="purpose"
purpose = forms.CharField(
label="Purpose",
widget=forms.Textarea(
attrs={
"aria-label": "What is the purpose of your requested domain? Describe how youll use your .gov domain. \
Will it be used for a website, email, or something else?"
}
),
validators=[
MaxLengthValidator(
2000,
message="Response must be less than 2000 characters.",
)
],
error_messages={"required": "Describe how youll use the .gov domain youre requesting."},
)
class FEBTimeFrameYesNoForm(BaseDeletableRegistrarForm, BaseYesNoForm):
"""
Form for determining whether the domain request comes with a target timeframe for launch.
If the "no" option is selected, details must be provided via the separate details form.
"""
field_name = "has_timeframe"
@property
def form_is_checked(self):
"""
Determines the initial checked state of the form based on the domain_request's attributes.
"""
return self.domain_request.has_timeframe
class FEBTimeFrameDetailsForm(BaseDeletableRegistrarForm):
time_frame_details = forms.CharField(
label="time_frame_details",
widget=forms.Textarea(
attrs={
"aria-label": "Provide details on your target timeframe. \
Is there a special significance to this date (legal requirement, announcement, event, etc)?"
}
),
validators=[
MaxLengthValidator(
2000,
message="Response must be less than 2000 characters.",
)
],
error_messages={"required": "Provide details on your target timeframe."},
)
class FEBInteragencyInitiativeYesNoForm(BaseDeletableRegistrarForm, BaseYesNoForm):
"""
Form for determining whether the domain request is part of an interagency initative.
If the "no" option is selected, details must be provided via the separate details form.
"""
field_name = "is_interagency_initiative"
@property
def form_is_checked(self):
"""
Determines the initial checked state of the form based on the domain_request's attributes.
"""
return self.domain_request.is_interagency_initiative
class FEBInteragencyInitiativeDetailsForm(BaseDeletableRegistrarForm):
interagency_initiative_details = forms.CharField(
label="interagency_initiative_details",
widget=forms.Textarea(
attrs={
"aria-label": "Name the agencies that will be involved in this initiative."
}
),
validators=[
MaxLengthValidator(
2000,
message="Response must be less than 2000 characters.",
)
],
error_messages={"required": "Name the agencies that will be involved in this initiative."},
)

View file

@ -0,0 +1,40 @@
# Generated by Django 4.2.17 on 2025-02-25 23:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("registrar", "0141_domainrequest_feb_naming_requirements_and_more"),
]
operations = [
migrations.AddField(
model_name="domainrequest",
name="feb_purpose_choice",
field=models.CharField(
blank=True, choices=[("website", "Website"), ("redirect", "Redirect"), ("other", "Other")], null=True
),
),
migrations.AddField(
model_name="domainrequest",
name="has_timeframe",
field=models.BooleanField(blank=True, null=True),
),
migrations.AddField(
model_name="domainrequest",
name="interagency_initiative_details",
field=models.TextField(blank=True, null=True),
),
migrations.AddField(
model_name="domainrequest",
name="is_interagency_initiative",
field=models.BooleanField(blank=True, null=True),
),
migrations.AddField(
model_name="domainrequest",
name="time_frame_details",
field=models.TextField(blank=True, null=True),
),
]

View file

@ -54,6 +54,11 @@ class DomainRequest(TimeStampedModel):
"""Returns the associated label for a given status name"""
return cls(status_name).label if status_name else None
class FEBPurposeChoices(models.TextChoices):
WEBSITE = "website"
REDIRECT = "redirect"
OTHER = "other"
class StateTerritoryChoices(models.TextChoices):
ALABAMA = "AL", "Alabama (AL)"
ALASKA = "AK", "Alaska (AK)"
@ -501,6 +506,7 @@ class DomainRequest(TimeStampedModel):
on_delete=models.PROTECT,
)
# Fields specific to Federal Executive Branch agencies, used by OMB for reviewing requests
feb_naming_requirements = models.BooleanField(
null=True,
blank=True,
@ -511,6 +517,40 @@ class DomainRequest(TimeStampedModel):
blank=True,
)
feb_purpose_choice = models.CharField(
null=True,
blank=True,
choices=FEBPurposeChoices.choices,
)
# This field is alternately used for generic domain purpose explanations
# and for explanations of the specific purpose chosen with feb_purpose_choice
# by a Federal Executive Branch agency.
purpose = models.TextField(
null=True,
blank=True,
)
has_timeframe = models.BooleanField(
null=True,
blank=True,
)
time_frame_details = models.TextField(
null=True,
blank=True,
)
is_interagency_initiative = models.BooleanField(
null=True,
blank=True,
)
interagency_initiative_details = models.TextField(
null=True,
blank=True,
)
alternative_domains = models.ManyToManyField(
"registrar.Website",
blank=True,
@ -518,10 +558,7 @@ class DomainRequest(TimeStampedModel):
help_text="Other domain names the creator provided for consideration",
)
purpose = models.TextField(
null=True,
blank=True,
)
other_contacts = models.ManyToManyField(
"registrar.Contact",

View file

@ -5,15 +5,88 @@
<p>.Gov domains are intended for public use. Domains will not be given to organizations that only want to reserve a domain name (defensive registration) or that only intend to use the domain internally (as for an intranet).</p>
<p>Read about <a class="usa-link" rel="noopener noreferrer" target="_blank" href="{% public_site_url 'domains/requirements/' %}">activities that are prohibited on .gov domains.</a></p>
<h2>What is the purpose of your requested domain?</h2>
<p>Describe how youll use your .gov domain. Will it be used for a website, email, or something else?</p>
<h2>{{requires_feb_questions}}</h2>
{% endblock %}
{% if requires_feb_questions %}
{% block feb_fields %}
<fieldset class="usa-fieldset margin-top-0 dotgov-domain-form">
{{forms.0.management_form}}
{{forms.1.management_form}}
{{forms.2.management_form}}
{{forms.3.management_form}}
{{forms.4.management_form}}
{{forms.5.management_form}}
<p class="usa-label">
Select One <span class="usa-label--required">*</span>
</p>
{% with add_label_class="usa-sr-only" attr_required="required" maxlength="2000" %}
{% input_with_errors forms.0.feb_purpose_choice %}
{% endwith %}
<div id="domain-purpose-details-container" class="conditional-panel" style="display: none;">
<p class="usa-label">
Provide details below <span class="usa-label--required">*</span>
</p>
{% with add_label_class="usa-sr-only" attr_required="required" maxlength="2000" %}
{% input_with_errors forms.1.purpose %}
{% endwith %}
<p class="usa-hint">Maximum 2000 characters allowed.</p>
</div>
<legend>
<h3>Do you have a target time frame for launching this domain?</h3>
</legend>
<p class="usa-label">
Select One <span class="usa-label--required">*</span>
</p>
{% with add_label_class="usa-sr-only" attr_required="required" maxlength="2000" %}
{% input_with_errors forms.2.has_timeframe %}
{% endwith %}
<div id="domain-timeframe-details-container" class="conditional-panel" style="display: none;">
<p class="usa-label">
Provide details below <span class="usa-label--required">*</span>
</p>
{% with add_label_class="usa-sr-only" attr_required="required" maxlength="2000" %}
{% input_with_errors forms.3.time_frame_details %}
{% endwith %}
<p class="usa-hint">Maximum 2000 characters allowed.</p>
</div>
<legend>
<h3>Will the domain name be used for an interagency initiative?</h3>
</legend>
<p class="usa-label">
Select One <span class="usa-label--required">*</span>
</p>
{% with add_label_class="usa-sr-only" attr_required="required" maxlength="2000" %}
{% input_with_errors forms.4.is_interagency_initiative %}
{% endwith %}
<div id="domain-interagency-initaitive-details-container" class="conditional-panel" style="display: none;">
<p class="usa-label">
Provide details below <span class="usa-label--required">*</span>
</p>
{% with add_label_class="usa-sr-only" attr_required="required" maxlength="2000" %}
{% input_with_errors forms.5.interagency_initiative_details %}
{% endwith %}
<p class="usa-hint">Maximum 2000 characters allowed.</p>
</div>
</fieldset>
{% endblock %}
{% else %}
<p>Describe how youll use your .gov domain. Will it be used for a website, email, or something else?</p>
{% block form_required_fields_help_text %}
{# commented out so it does not appear on this page #}
{% endblock %}
{% block form_fields %}
{% with attr_maxlength=2000 add_label_class="usa-sr-only" %}
{% input_with_errors forms.0.purpose %}
{% input_with_errors forms.1.purpose %}
{% endwith %}
{% endblock %}
{% endif %}

View file

@ -8,6 +8,7 @@ from django.utils.translation import gettext_lazy as _
from django.views.generic import TemplateView
from django.contrib import messages
from registrar.forms import domain_request_wizard as forms
from registrar.forms.domainrequestwizard import purpose
from registrar.forms.utility.wizard_form_helper import request_step_list
from registrar.models import DomainRequest
from registrar.models.contact import Contact
@ -182,7 +183,9 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
return PortfolioDomainRequestStep if self.is_portfolio else Step
def requires_feb_questions(self) -> bool:
return self.domain_request.is_feb() and flag_is_active_for_user(self.request.user, "organization_feature")
# TODO: this is for testing, revert later
return False
# return self.domain_request.is_feb() and flag_is_active_for_user(self.request.user, "organization_feature")
@property
def prefix(self):
@ -709,7 +712,64 @@ class DotgovDomain(DomainRequestWizard):
class Purpose(DomainRequestWizard):
template_name = "domain_request_purpose.html"
forms = [forms.PurposeForm]
forms = [purpose.FEBPurposeOptionsForm,
purpose.PurposeDetailsForm,
purpose.FEBTimeFrameYesNoForm,
purpose.FEBTimeFrameDetailsForm,
purpose.FEBInteragencyInitiativeYesNoForm,
purpose.FEBInteragencyInitiativeDetailsForm
]
def get_context_data(self):
context= super().get_context_data()
context["requires_feb_questions"] = self.requires_feb_questions()
return context
def is_valid(self, forms_list: list) -> bool:
"""
Expected order of forms_list:
0: FEBPurposeOptionsForm
1: PurposeDetailsForm
2: FEBTimeFrameYesNoForm
3: FEBTimeFrameDetailsForm
4: FEBInteragencyInitiativeYesNoForm
5: FEBInteragencyInitiativeDetailsForm
"""
feb_purpose_options_form = forms_list[0]
purpose_details_form = forms_list[1]
feb_timeframe_yes_no_form = forms_list[2]
feb_timeframe_details_form = forms_list[3]
feb_initiative_yes_no_form = forms_list[4]
feb_initiative_details_form = forms_list[5]
if not self.requires_feb_questions():
# if FEB questions don't apply, mark all other forms for deletion
feb_purpose_options_form.mark_form_for_deletion()
feb_timeframe_yes_no_form.mark_form_for_deletion()
feb_timeframe_details_form.mark_form_for_deletion()
feb_initiative_yes_no_form.mark_form_for_deletion()
feb_initiative_details_form.mark_form_for_deletion()
# we only care about the purpose form in this case since it's used in both instances
return purpose_details_form.is_valid()
if not feb_purpose_options_form.id_valid():
# Ensure details form doesn't throw errors if it's not showing
purpose_details_form.mark_form_for_deletion()
if not feb_initiative_yes_no_form.is_valid() or not feb_timeframe_yes_no_form.cleaned_data.get("has_timeframe"):
# Ensure details form doesn't throw errors if it's not showing
feb_initiative_details_form.mark_form_for_deletion()
if not feb_timeframe_yes_no_form.is_valid() or not feb_initiative_yes_no_form.cleaned_data.get("is_interagency_initiative"):
# Ensure details form doesn't throw errors if it's not showing
feb_timeframe_details_form.mark_form_for_delation()
valid = all(form.is_valid() for form in forms_list if not form.form_data_marked_for_deletion)
return valid
class OtherContacts(DomainRequestWizard):