From 09d92f6edcc41896e5f64d959d40b574b6d7fefd Mon Sep 17 00:00:00 2001 From: matthewswspence Date: Thu, 13 Mar 2025 11:59:55 -0500 Subject: [PATCH 01/41] tests and linter fixes --- src/registrar/forms/feb.py | 3 +- .../portfolio_request_review_steps.html | 39 ++++++++++++ src/registrar/tests/test_views_request.py | 60 +++++++++++++++---- src/registrar/views/domain_request.py | 26 ++++---- 4 files changed, 106 insertions(+), 22 deletions(-) diff --git a/src/registrar/forms/feb.py b/src/registrar/forms/feb.py index d4322f512..05b2acf3f 100644 --- a/src/registrar/forms/feb.py +++ b/src/registrar/forms/feb.py @@ -3,6 +3,7 @@ from django.core.validators import MaxLengthValidator from registrar.forms.utility.wizard_form_helper import BaseDeletableRegistrarForm, BaseYesNoForm from registrar.models.contact import Contact + class ExecutiveNamingRequirementsYesNoForm(BaseYesNoForm, BaseDeletableRegistrarForm): """ Form for verifying if the domain request meets the Federal Executive Branch domain naming requirements. @@ -206,4 +207,4 @@ class FEBAnythingElseYesNoForm(BaseYesNoForm, BaseDeletableRegistrarForm): """Yes/no toggle for the anything else question on additional details""" form_is_checked = property(lambda self: self.domain_request.has_anything_else_text) # type: ignore - field_name = "has_anything_else_text" \ No newline at end of file + field_name = "has_anything_else_text" diff --git a/src/registrar/templates/includes/portfolio_request_review_steps.html b/src/registrar/templates/includes/portfolio_request_review_steps.html index 53ad36a3f..e545beca5 100644 --- a/src/registrar/templates/includes/portfolio_request_review_steps.html +++ b/src/registrar/templates/includes/portfolio_request_review_steps.html @@ -53,18 +53,57 @@ {% endfor %} {% endif %} + {% if requires_feb_questions %} +

Meets Naming Requirements

+

{{domain_request.feb_naming_requirements|yesno:"Yes,No"}}

+ {% if not domain_request.feb_naming_requirements %} +

{{domain_request.feb_naming_requirements_details}}

+ {% endif %} + {% endif %} {% endif %} {% if step == Step.PURPOSE %} {% with title=form_titles|get_item:step value=domain_request.purpose|default:"Incomplete"|safe %} {% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=is_editable edit_link=domain_request_url %} {% endwith %} + {% if requires_feb_questions %} +

Purpose

+ {% if domain_request.feb_purpose_choice == "website" %} +

Used for a new website

+ {% elif domain_request.feb_purpose_choice == "redirect" %} +

Used as a redirect for an existing website

+ {% else %} +

Not for a website

+ {% endif %} +

{{domain_request.purpose}}

+

Target Time Frame

+ {% if domain_request.has_timeframe %} +

{{domain_request.time_frame_details}}

+ {% else %} +

No

+ {% endif %} +

Interagency Initiative

+ {% if domain_request.is_interagency_initiative %} +

{{domain_request.interagency_initiative_details}}

+ {% else %} +

No

+ {% endif %} + {% endif %} {% endif %} {% if step == Step.ADDITIONAL_DETAILS %} {% with title=form_titles|get_item:step value=domain_request.anything_else|default:"None" %} {% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=is_editable edit_link=domain_request_url %} {% endwith %} + {% if requires_feb_questions %} +

EOP Stakeholder

+ {% if domain_request.working_with_eop %} +

{{domain_request.eop_contact.first_name}} {{domain_request.eop_contact.last_name}}

+

{{domain_request.eop_contact.email}}

+ {% else %} +

No

+ {% endif %} + {% endif %} {% endif %} {% if step == Step.REQUIREMENTS %} diff --git a/src/registrar/tests/test_views_request.py b/src/registrar/tests/test_views_request.py index 4c1311d47..47057c193 100644 --- a/src/registrar/tests/test_views_request.py +++ b/src/registrar/tests/test_views_request.py @@ -2615,8 +2615,8 @@ class DomainRequestTests(TestWithUser, WebTest): domain_form = dotgov_page.forms[0] domain = "test.gov" domain_form["dotgov_domain-requested_domain"] = domain - domain_form["dotgov_domain-feb_naming_requirements"] = "True" - domain_form["dotgov_domain-feb_naming_requirements_details"] = "test" + domain_form["dotgov_domain-feb_naming_requirements"] = "False" + domain_form["dotgov_domain-feb_naming_requirements_details"] = "Because this is a test" with patch( "registrar.forms.domain_request_wizard.DotGovDomainForm.clean_requested_domain", return_value=domain ): # noqa @@ -2631,11 +2631,11 @@ class DomainRequestTests(TestWithUser, WebTest): purpose_form = purpose_page.forms[0] purpose_form["purpose-feb_purpose_choice"] = "redirect" - purpose_form["purpose-purpose"] = "test" + purpose_form["purpose-purpose"] = "testPurpose123" purpose_form["purpose-has_timeframe"] = "True" - purpose_form["purpose-time_frame_details"] = "test" + purpose_form["purpose-time_frame_details"] = "1/2/2025 - 1/2/2026" purpose_form["purpose-is_interagency_initiative"] = "True" - purpose_form["purpose-interagency_initiative_details"] = "test" + purpose_form["purpose-interagency_initiative_details"] = "FakeInteragencyInitiative" self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) purpose_result = purpose_form.submit() @@ -2646,8 +2646,8 @@ class DomainRequestTests(TestWithUser, WebTest): additional_details_form = additional_details_page.forms[0] additional_details_form["portfolio_additional_details-working_with_eop"] = "True" - additional_details_form["portfolio_additional_details-first_name"] = "Testy" - additional_details_form["portfolio_additional_details-last_name"] = "Tester" + additional_details_form["portfolio_additional_details-first_name"] = "TesterFirstName" + additional_details_form["portfolio_additional_details-last_name"] = "TesterLastName" additional_details_form["portfolio_additional_details-email"] = "testy@town.com" additional_details_form["portfolio_additional_details-has_anything_else_text"] = "True" additional_details_form["portfolio_additional_details-anything_else"] = "test" @@ -2659,6 +2659,16 @@ class DomainRequestTests(TestWithUser, WebTest): requirements_page = additional_details_result.follow() self.feb_requirements_page_tests(requirements_page) + requirements_form = requirements_page.forms[0] + requirements_form["requirements-is_policy_acknowledged"] = "True" + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + requirements_result = requirements_form.submit() + + # ---- REVIEW PAGE ---- + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + review_page = requirements_result.follow() + self.feb_review_page_tests(review_page) + def feb_purpose_page_tests(self, purpose_page): self.assertContains(purpose_page, "What is the purpose of your requested domain?") @@ -2718,12 +2728,40 @@ class DomainRequestTests(TestWithUser, WebTest): def feb_requirements_page_tests(self, requirements_page): # Check for the 21st Century IDEA Act links - self.assertContains(requirements_page, "https://digital.gov/resources/delivering-digital-first-public-experience-act/") - self.assertContains(requirements_page, "https://bidenwhitehouse.gov/wp-content/uploads/2023/09/M-23-22-Delivering-a-Digital-First-Public-Experience.pdf") - + self.assertContains( + requirements_page, "https://digital.gov/resources/delivering-digital-first-public-experience-act/" + ) + self.assertContains( + requirements_page, + "https://bidenwhitehouse.gov/wp-content/uploads/2023/09/M-23-22-Delivering-a-Digital-First-Public-Experience.pdf", + ) + # Check for the policy acknowledgement form self.assertContains(requirements_page, "is_policy_acknowledged") - self.assertContains(requirements_page, "I read and understand the guidance outlined in the DOTGOV Act for operating a .gov domain.") + self.assertContains( + requirements_page, + "I read and understand the guidance outlined in the DOTGOV Act for operating a .gov domain.", + ) + + def feb_review_page_tests(self, review_page): + # Meets Naming Requirements + self.assertContains(review_page, "

Meets Naming Requirements

") + self.assertContains(review_page, "No") + self.assertContains(review_page, "Because this is a test") + # Purpose + self.assertContains(review_page, "

Purpose

") + self.assertContains(review_page, "Used as a redirect for an existing website") + self.assertContains(review_page, "testPurpose123") + # Target Time Frame + self.assertContains(review_page, "

Target Time Frame

") + self.assertContains(review_page, "1/2/2025 - 1/2/2026") + # Interagency Initiative + self.assertContains(review_page, "

Interagency Initiative

") + self.assertContains(review_page, "FakeInteragencyInitiative") + # EOP Stakeholder + self.assertContains(review_page, "

EOP Stakeholder

") + self.assertContains(review_page, "TesterFirstName TesterLastName") + self.assertContains(review_page, "testy@town.com") @less_console_noise_decorator def test_domain_request_formsets(self): diff --git a/src/registrar/views/domain_request.py b/src/registrar/views/domain_request.py index ef6df0151..64987c08d 100644 --- a/src/registrar/views/domain_request.py +++ b/src/registrar/views/domain_request.py @@ -631,21 +631,23 @@ class PortfolioAdditionalDetails(DomainRequestWizard): if not forms[0].is_valid(): # If the user isn't working with EOP, don't validate the EOP contact form forms[1].mark_form_for_deletion() - eop_forms_valid = False + eop_forms_valid = False if forms[0].cleaned_data.get("working_with_eop"): eop_forms_valid = forms[1].is_valid() else: - forms[1].mark_form_for_deletion() + forms[1].mark_form_for_deletion() anything_else_forms_valid = True if not forms[2].is_valid(): forms[3].mark_form_for_deletion() - anything_else_forms_valid = False + anything_else_forms_valid = False if forms[2].cleaned_data.get("has_anything_else_text"): forms[3].fields["anything_else"].required = True - forms[3].fields["anything_else"].error_messages["required"] = "Please provide additional details you'd like us to know. \ + forms[3].fields["anything_else"].error_messages[ + "required" + ] = "Please provide additional details you'd like us to know. \ If you have nothing to add, select 'No'." - anything_else_forms_valid = forms[3].is_valid() - return (eop_forms_valid and anything_else_forms_valid) + anything_else_forms_valid = forms[3].is_valid() + return eop_forms_valid and anything_else_forms_valid # Non-portfolio pages @@ -937,15 +939,19 @@ class Requirements(DomainRequestWizard): # Override the get_forms method to set the policy acknowledgement label conditionally based on feb status def get_forms(self, step=None, use_post=False, use_db=False, files=None): forms_list = super().get_forms(step, use_post, use_db, files) - + # Pass the is_federal context to the form for form in forms_list: if isinstance(form, forms.RequirementsForm): if self.requires_feb_questions(): - form.fields['is_policy_acknowledged'].label = "I read and understand the guidance outlined in the DOTGOV Act for operating a .gov domain." # noqa: E501 + form.fields["is_policy_acknowledged"].label = ( + "I read and understand the guidance outlined in the DOTGOV Act for operating a .gov domain." # noqa: E501 + ) else: - form.fields['is_policy_acknowledged'].label = "I read and agree to the requirements for operating a .gov domain." # noqa: E501 - + form.fields["is_policy_acknowledged"].label = ( + "I read and agree to the requirements for operating a .gov domain." # noqa: E501 + ) + return forms_list From 24313a971529ee704a0e9727de7eea89e74f5ced Mon Sep 17 00:00:00 2001 From: matthewswspence Date: Thu, 13 Mar 2025 13:59:21 -0500 Subject: [PATCH 02/41] add FEB questions to view-only page --- src/registrar/views/domain_request.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/registrar/views/domain_request.py b/src/registrar/views/domain_request.py index 64987c08d..a604bfadc 100644 --- a/src/registrar/views/domain_request.py +++ b/src/registrar/views/domain_request.py @@ -1182,6 +1182,9 @@ class PortfolioDomainRequestStatusViewOnly(DetailView): context["Step"] = PortfolioDomainRequestStep.__members__ context["steps"] = request_step_list(wizard, PortfolioDomainRequestStep) context["form_titles"] = wizard.titles + logger.debug(self.object.is_feb()) + logger.debug(flag_is_active_for_user(self.request.user, "organization_feature")) + context["requires_feb_questions"] = self.object.is_feb() and flag_is_active_for_user(self.request.user, "organization_feature") return context From 2eee8831e8eeee0190eadd0c67e207266beac14a Mon Sep 17 00:00:00 2001 From: matthewswspence Date: Thu, 13 Mar 2025 14:03:54 -0500 Subject: [PATCH 03/41] fix spacing --- .../includes/portfolio_request_review_steps.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/registrar/templates/includes/portfolio_request_review_steps.html b/src/registrar/templates/includes/portfolio_request_review_steps.html index e545beca5..067bce1b4 100644 --- a/src/registrar/templates/includes/portfolio_request_review_steps.html +++ b/src/registrar/templates/includes/portfolio_request_review_steps.html @@ -54,7 +54,7 @@ {% endif %} {% if requires_feb_questions %} -

Meets Naming Requirements

+

Meets Naming Requirements

{{domain_request.feb_naming_requirements|yesno:"Yes,No"}}

{% if not domain_request.feb_naming_requirements %}

{{domain_request.feb_naming_requirements_details}}

@@ -67,7 +67,7 @@ {% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=is_editable edit_link=domain_request_url %} {% endwith %} {% if requires_feb_questions %} -

Purpose

+

Purpose

{% if domain_request.feb_purpose_choice == "website" %}

Used for a new website

{% elif domain_request.feb_purpose_choice == "redirect" %} @@ -76,13 +76,13 @@

Not for a website

{% endif %}

{{domain_request.purpose}}

-

Target Time Frame

+

Target Time Frame

{% if domain_request.has_timeframe %}

{{domain_request.time_frame_details}}

{% else %}

No

{% endif %} -

Interagency Initiative

+

Interagency Initiative

{% if domain_request.is_interagency_initiative %}

{{domain_request.interagency_initiative_details}}

{% else %} @@ -96,7 +96,7 @@ {% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=is_editable edit_link=domain_request_url %} {% endwith %} {% if requires_feb_questions %} -

EOP Stakeholder

+

EOP Stakeholder

{% if domain_request.working_with_eop %}

{{domain_request.eop_contact.first_name}} {{domain_request.eop_contact.last_name}}

{{domain_request.eop_contact.email}}

From 4c089ff9040ed1fa998974527c958212b5d9225a Mon Sep 17 00:00:00 2001 From: matthewswspence Date: Thu, 13 Mar 2025 15:31:43 -0500 Subject: [PATCH 04/41] add incomplete handling in review --- .../portfolio_request_review_steps.html | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/registrar/templates/includes/portfolio_request_review_steps.html b/src/registrar/templates/includes/portfolio_request_review_steps.html index 067bce1b4..2a255b532 100644 --- a/src/registrar/templates/includes/portfolio_request_review_steps.html +++ b/src/registrar/templates/includes/portfolio_request_review_steps.html @@ -55,8 +55,12 @@ {% endif %} {% if requires_feb_questions %}

Meets Naming Requirements

-

{{domain_request.feb_naming_requirements|yesno:"Yes,No"}}

- {% if not domain_request.feb_naming_requirements %} + {% if domain_request.feb_naming_requirements is None %} +

Incomplete

+ {% elif domain_request.feb_naming_requirements %} +

Yes

+ {% else %} +

No

{{domain_request.feb_naming_requirements_details}}

{% endif %} {% endif %} @@ -72,18 +76,24 @@

Used for a new website

{% elif domain_request.feb_purpose_choice == "redirect" %}

Used as a redirect for an existing website

- {% else %} + {% elif domain_request.feb_purpose_choice == "other" %}

Not for a website

+ {% else %} +

Incomplete

{% endif %}

{{domain_request.purpose}}

Target Time Frame

- {% if domain_request.has_timeframe %} + {% if domain_request.has_timeframe is None %} +

Incomplete

+ {% elif domain_request.has_timeframe %}

{{domain_request.time_frame_details}}

{% else %}

No

{% endif %} -

Interagency Initiative

- {% if domain_request.is_interagency_initiative %} +

EOP Stakeholder

+ {% if domain_request.is_interagency_initiative is None %} +

Incomplete

+ {% elif domain_request.is_interagency_initiative %}

{{domain_request.interagency_initiative_details}}

{% else %}

No

@@ -97,7 +107,9 @@ {% endwith %} {% if requires_feb_questions %}

EOP Stakeholder

- {% if domain_request.working_with_eop %} + {% if domain_request.working_with_eop is None %} +

Incomplete

+ {% elif domain_request.working_with_eop %}

{{domain_request.eop_contact.first_name}} {{domain_request.eop_contact.last_name}}

{{domain_request.eop_contact.email}}

{% else %} From db0433fb5fc23d7d374dde35bbecef9315514612 Mon Sep 17 00:00:00 2001 From: matthewswspence Date: Thu, 13 Mar 2025 15:35:19 -0500 Subject: [PATCH 05/41] minor fixes --- .../templates/includes/portfolio_request_review_steps.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/registrar/templates/includes/portfolio_request_review_steps.html b/src/registrar/templates/includes/portfolio_request_review_steps.html index 2a255b532..eda1bbf4e 100644 --- a/src/registrar/templates/includes/portfolio_request_review_steps.html +++ b/src/registrar/templates/includes/portfolio_request_review_steps.html @@ -73,15 +73,17 @@ {% if requires_feb_questions %}

Purpose

{% if domain_request.feb_purpose_choice == "website" %} -

Used for a new website

+

Used for a new website

+

{{domain_request.purpose}}

{% elif domain_request.feb_purpose_choice == "redirect" %}

Used as a redirect for an existing website

+

{{domain_request.purpose}}

{% elif domain_request.feb_purpose_choice == "other" %}

Not for a website

+

{{domain_request.purpose}}

{% else %}

Incomplete

{% endif %} -

{{domain_request.purpose}}

Target Time Frame

{% if domain_request.has_timeframe is None %}

Incomplete

From e86ad5f9a8d2f45a4220892b8c01c545fd880805 Mon Sep 17 00:00:00 2001 From: matthewswspence Date: Fri, 14 Mar 2025 09:31:34 -0500 Subject: [PATCH 06/41] modify domain request summary email --- src/registrar/models/domain_request.py | 2 + .../includes/domain_request_summary.txt | 44 +++++++++++++++++-- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py index f80ab08d0..73b71bf2d 100644 --- a/src/registrar/models/domain_request.py +++ b/src/registrar/models/domain_request.py @@ -1005,11 +1005,13 @@ class DomainRequest(TimeStampedModel): if not context: has_organization_feature_flag = flag_is_active_for_user(recipient, "organization_feature") is_org_user = has_organization_feature_flag and recipient.has_view_portfolio_permission(self.portfolio) + requires_feb_questions = self.requires_feb_questions() context = { "domain_request": self, # This is the user that we refer to in the email "recipient": recipient, "is_org_user": is_org_user, + "requires_feb_questions": requires_feb_questions, } if custom_email_content: diff --git a/src/registrar/templates/emails/includes/domain_request_summary.txt b/src/registrar/templates/emails/includes/domain_request_summary.txt index c58aef5e1..300edd49f 100644 --- a/src/registrar/templates/emails/includes/domain_request_summary.txt +++ b/src/registrar/templates/emails/includes/domain_request_summary.txt @@ -35,12 +35,38 @@ Current websites: {% for site in domain_request.current_websites.all %} {% endfor %}{% endif %} .gov domain: {{ domain_request.requested_domain.name }} +{% if requires_feb_questions %} + Meets Naming Requirements + {% if domain_request.feb_naming_requirements %} + {{ domain_request.feb_naming_requirements }} + {% else %} + No + {{ domain_request.feb_naming_requirements_details }} + {% endif %} +{% endif %} {% if domain_request.alternative_domains.all %} Alternative domains: {% for site in domain_request.alternative_domains.all %}{% spaceless %}{{ site.website }}{% endspaceless %} {% endfor %}{% endif %} Purpose of your domain: -{{ domain_request.purpose }} +{% if requires_feb_questions %} + {{ domain_request.feb_purpose_choice }} + {{ domain_request.purpose }} + Target time frame: + {% if domain_request.has_target_time_frame %} + {{ domain_request.time_frame_details }} + {% else %} + No + {% endif %} + Interagency initiative: + {% if domain_request.is_interagency_initiative %} + {{ domain_request.interagency_initiative_details }} + {% else %} + No + {% endif %} +{% else %} + {{ domain_request.purpose }} +{% endif %} Your contact information: {% spaceless %}{% include "emails/includes/contact.txt" with contact=recipient %}{% endspaceless %} @@ -49,7 +75,17 @@ Other employees from your organization:{% for other in domain_request.other_cont {% spaceless %}{% include "emails/includes/contact.txt" with contact=other %}{% endspaceless %} {% empty %} {{ domain_request.no_other_contacts_rationale }} -{% endfor %}{% if domain_request.anything_else %} -Anything else? -{{ domain_request.anything_else }} +{% endfor %} +{% if requires_feb_questions %} + EOP Stakeholder: + {% if domain_request.working_with_eop %} + {{ domain_request.eop_contact.first_name }} {{ domain_request.eop_contact.last_name }} + {{ domain_request.eop_contact.email }} + {% else %} + No + {% endif %} +{% endif %} +{% if domain_request.anything_else %} + Anything else? + {{ domain_request.anything_else }} {% endif %} \ No newline at end of file From 61c32351ddadf38f8ae04c6d3fd3e375d90f6edd Mon Sep 17 00:00:00 2001 From: matthewswspence Date: Fri, 14 Mar 2025 15:58:05 -0500 Subject: [PATCH 07/41] add new fields to admin and refactor a bit --- src/registrar/admin.py | 76 ++++++++++++++++++- src/registrar/forms/feb.py | 18 ++--- src/registrar/models/domain_request.py | 24 ++++-- .../portfolio_request_review_steps.html | 4 +- src/registrar/tests/test_admin_request.py | 4 +- 5 files changed, 104 insertions(+), 22 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 09d0eaa81..98b833f5f 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -2535,6 +2535,40 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): portfolio_urbanization.short_description = "Urbanization" # type: ignore + # ------ FEB fields ------ + def feb_naming_requirements_details(self, obj): + return obj.feb_naming_requirements_details if obj.feb_naming_requirements else "" + + feb_naming_requirements_details.short_description = "Domain Name Rationale:" # type: ignore + + def feb_purpose_choice(self, obj): + return obj.feb_purpose_choice if obj.feb_purpose_choice else "" + + feb_purpose_choice.short_description = "Purpose type:" # type: ignore + + def time_frame_details(self, obj): + return obj.time_frame_details if obj.has_timeframe else "" + + time_frame_details.short_description = "Target time frame:" # type: ignore + + def interagency_initiative_details(self, obj): + return obj.interagency_initiative_details if obj.is_interagency_initiative else "" + + interagency_initiative_details.short_description = "Interagency Initiative:" # type: ignore + + def eop_stakeholder_first_name(self, obj): + return obj.eop_stakeholder_first_name if obj.eop_stakeholder_first_name else "" + + def eop_stakeholder_last_name(self, obj): + return obj.eop_stakeholder_last_name if obj.eop_stakeholder_last_name else "" + + def eop_stakeholder_email(self, obj): + return obj.eop_stakeholder_email if obj.eop_stakeholder_email else "" + + eop_stakeholder_first_name.short_description = "EOP Stakeholder First Name" # type: ignore + eop_stakeholder_last_name.short_description = "EOP Stakeholder Last Name" # type: ignore + eop_stakeholder_email.short_description = "EOP Stakeholder Email" # type: ignore + # This is just a placeholder. This field will be populated in the detail_table_fieldset view. # This is not a field that exists on the model. def status_history(self, obj): @@ -2615,7 +2649,16 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): ] }, ), - (".gov domain", {"fields": ["requested_domain", "alternative_domains"]}), + ( + ".gov domain", + { + "fields": [ + "requested_domain", + "alternative_domains", + "feb_naming_requirements_details", + ] + }, + ), ( "Contacts", { @@ -2627,10 +2670,25 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): "cisa_representative_first_name", "cisa_representative_last_name", "cisa_representative_email", + "eop_stakeholder_first_name", + "eop_stakeholder_last_name", + "eop_stakeholder_email", + ] + }, + ), + ( + "Background info", + { + "fields": [ + "feb_purpose_choice", + "purpose", + "time_frame_details", + "interagency_initiative_details", + "anything_else", + "current_websites", ] }, ), - ("Background info", {"fields": ["purpose", "anything_else", "current_websites"]}), ( "Type of organization", { @@ -2781,6 +2839,20 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): "suborganization_city", "suborganization_state_territory", ] + # Hide FEB fields behind the organization requests flag + # and only show them if the portfolio is executive + if not (obj and obj.portfolio and obj.portfolio.federal_type == BranchChoices.EXECUTIVE): + excluded_fields.extend([ + "feb_naming_requirements_details", + "feb_purpose_choice", + "time_frame_details", + "interagency_initiative_details", + "eop_stakeholder_first_name", + "eop_stakeholder_last_name", + "eop_stakeholder_email", + ]) + # This makes .gov domain collapsable underneath Requested By + fieldsets[2][1]["classes"] = ["collapse--dgfieldset"] modified_fieldsets = [] for name, data in fieldsets: fields = data.get("fields", []) diff --git a/src/registrar/forms/feb.py b/src/registrar/forms/feb.py index 05b2acf3f..339151c71 100644 --- a/src/registrar/forms/feb.py +++ b/src/registrar/forms/feb.py @@ -147,8 +147,6 @@ class EOPContactForm(BaseDeletableRegistrarForm): Executive Branch (FEB) agency is working with. """ - field_name = "eop_contact" - first_name = forms.CharField( label="First name / given name", error_messages={"required": "Enter the first name / given name of this contact."}, @@ -178,12 +176,10 @@ class EOPContactForm(BaseDeletableRegistrarForm): @classmethod def from_database(cls, obj): - if not obj.eop_contact: - return {} return { - "first_name": obj.eop_contact.first_name, - "last_name": obj.eop_contact.last_name, - "email": obj.eop_contact.email, + "first_name": obj.eop_stakeholder_first_name, + "last_name": obj.eop_stakeholder_last_name, + "email": obj.eop_stakeholder_email, } def to_database(self, obj): @@ -195,11 +191,9 @@ class EOPContactForm(BaseDeletableRegistrarForm): return if not self.is_valid(): return - obj.eop_contact = Contact.objects.create( - first_name=self.cleaned_data["first_name"], - last_name=self.cleaned_data["last_name"], - email=self.cleaned_data["email"], - ) + obj.eop_stakeholder_first_name = self.cleaned_data["first_name"] + obj.eop_stakeholder_last_name = self.cleaned_data["last_name"] + obj.eop_stakeholder_email = self.cleaned_data["email"] obj.save() diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py index f80ab08d0..28079b1e1 100644 --- a/src/registrar/models/domain_request.py +++ b/src/registrar/models/domain_request.py @@ -515,12 +515,15 @@ class DomainRequest(TimeStampedModel): feb_naming_requirements_details = models.TextField( null=True, blank=True, + help_text="Required if requested domain that doesn't meet naming requirements", + verbose_name="Domain name rationale", ) feb_purpose_choice = models.CharField( null=True, blank=True, choices=FEBPurposeChoices.choices, + verbose_name="Purpose type", ) working_with_eop = models.BooleanField( @@ -528,17 +531,26 @@ class DomainRequest(TimeStampedModel): blank=True, ) - eop_contact = models.ForeignKey( - "registrar.Contact", + eop_stakeholder_first_name = models.CharField( null=True, blank=True, - related_name="eop_contact", - on_delete=models.PROTECT, + verbose_name="EOP Stakeholder First Name", + ) + + eop_stakeholder_last_name = models.CharField( + null=True, + blank=True, + verbose_name="EOP Stakeholder Last Name", + ) + + eop_stakeholder_email = models.EmailField( + null=True, + blank=True, + verbose_name="EOP Stakeholder Email", ) # 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, @@ -552,6 +564,7 @@ class DomainRequest(TimeStampedModel): time_frame_details = models.TextField( null=True, blank=True, + verbose_name="Target time frame", ) is_interagency_initiative = models.BooleanField( @@ -562,6 +575,7 @@ class DomainRequest(TimeStampedModel): interagency_initiative_details = models.TextField( null=True, blank=True, + verbose_name="Interagency initiative", ) alternative_domains = models.ManyToManyField( diff --git a/src/registrar/templates/includes/portfolio_request_review_steps.html b/src/registrar/templates/includes/portfolio_request_review_steps.html index 2a255b532..3a3c29ee2 100644 --- a/src/registrar/templates/includes/portfolio_request_review_steps.html +++ b/src/registrar/templates/includes/portfolio_request_review_steps.html @@ -110,8 +110,8 @@ {% if domain_request.working_with_eop is None %}

Incomplete

{% elif domain_request.working_with_eop %} -

{{domain_request.eop_contact.first_name}} {{domain_request.eop_contact.last_name}}

-

{{domain_request.eop_contact.email}}

+

{{domain_request.eop_stakeholder_first_name}} {{domain_request.eop_stakeholder_last_name}}

+

{{domain_request.eop_stakeholder_email}}

{% else %}

No

{% endif %} diff --git a/src/registrar/tests/test_admin_request.py b/src/registrar/tests/test_admin_request.py index 7bc150326..b7d9b7e81 100644 --- a/src/registrar/tests/test_admin_request.py +++ b/src/registrar/tests/test_admin_request.py @@ -1985,7 +1985,9 @@ class TestDomainRequestAdmin(MockEppLib): "feb_naming_requirements_details", "feb_purpose_choice", "working_with_eop", - "eop_contact", + "eop_stakeholder_first_name", + "eop_stakeholder_last_name", + "eop_stakeholder_email", "purpose", "has_timeframe", "time_frame_details", From bbcd2ce08d4a870a0a137ce054a5f9b7213200f2 Mon Sep 17 00:00:00 2001 From: matthewswspence Date: Mon, 17 Mar 2025 11:09:29 -0500 Subject: [PATCH 08/41] add label function for feb purpose choices --- src/registrar/models/domain_request.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py index f80ab08d0..c12c5f495 100644 --- a/src/registrar/models/domain_request.py +++ b/src/registrar/models/domain_request.py @@ -59,6 +59,16 @@ class DomainRequest(TimeStampedModel): REDIRECT = "redirect" OTHER = "other" + @classmethod + def get_purpose_label(cls, purpose_name: str): + """Returns the associated label for a given purpose name""" + if purpose_name == cls.WEBSITE: + return "Used for a new website" + elif purpose_name == cls.REDIRECT: + return "Used as a redirect for an existing website" + else: + return "Not for a website" + class StateTerritoryChoices(models.TextChoices): ALABAMA = "AL", "Alabama (AL)" ALASKA = "AK", "Alaska (AK)" From 165571a1710df0b931f251917b22eaa5f9543775 Mon Sep 17 00:00:00 2001 From: matthewswspence Date: Mon, 17 Mar 2025 15:31:46 -0500 Subject: [PATCH 09/41] add new fields to csv exports --- src/registrar/utility/csv_export.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/registrar/utility/csv_export.py b/src/registrar/utility/csv_export.py index fad58b2e2..16f374106 100644 --- a/src/registrar/utility/csv_export.py +++ b/src/registrar/utility/csv_export.py @@ -1839,6 +1839,16 @@ class DomainRequestExport(BaseExport): details = [cisa_rep, model.get("anything_else")] additional_details = " | ".join([field for field in details if field]) + # FEB fields + purpose_type = model.get("feb_purpose_choice") + purpose_type_display = DomainRequest.FEBPurposeChoices.get_purpose_label(purpose_type) if purpose_type else "N/A" + eop_stakeholder_first_name = model.get("eop_stakeholder_first_name") + eop_stakeholder_last_name = model.get("eop_stakeholder_last_name") + if not eop_stakeholder_first_name or not eop_stakeholder_last_name: + eop_stakeholder_name = None + else: + eop_stakeholder_name = f"{eop_stakeholder_first_name} {eop_stakeholder_last_name}" + # create a dictionary of fields which can be included in output. # "extra_fields" are precomputed fields (generated in the DB or parsed). FIELDS = { @@ -1880,6 +1890,13 @@ class DomainRequestExport(BaseExport): "Last submitted date": model.get("last_submitted_date"), "First submitted date": model.get("first_submitted_date"), "Last status update": model.get("last_status_update"), + # FEB only fields + "Purpose": purpose_type_display, + "Domain name rationale": model.get("feb_naming_requirements_details", None), + "Target time frame": model.get("time_frame_details", None), + "Interagency initiative": model.get("interagency_initiative_details", None), + "EOP stakeholder name": eop_stakeholder_name, + "EOP stakeholder email": model.get("eop_stakeholder_email", None), } row = [FIELDS.get(column, "") for column in columns] @@ -1925,6 +1942,12 @@ class DomainRequestDataType(DomainRequestExport): "Last submitted date", "First submitted date", "Last status update", + "Purpose", + "Domain name rationale", + "Target time frame", + "Interagency initiative", + "EOP stakeholder name", + "EOP stakeholder email", ] @classmethod @@ -2069,6 +2092,12 @@ class DomainRequestDataFull(DomainRequestExport): "CISA regional representative", "Current websites", "Investigator", + "Purpose", + "Domain name rationale", + "Target time frame", + "Interagency initiative", + "EOP stakeholder name", + "EOP stakeholder email", ] @classmethod From 3ddb916b8ba260b67504c5f375792c13bfd5578f Mon Sep 17 00:00:00 2001 From: matthewswspence Date: Tue, 18 Mar 2025 10:59:54 -0500 Subject: [PATCH 10/41] test fix --- .../includes/portfolio_request_review_steps.html | 6 +++--- src/registrar/tests/test_views_request.py | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/registrar/templates/includes/portfolio_request_review_steps.html b/src/registrar/templates/includes/portfolio_request_review_steps.html index 2a255b532..a7b4dea0b 100644 --- a/src/registrar/templates/includes/portfolio_request_review_steps.html +++ b/src/registrar/templates/includes/portfolio_request_review_steps.html @@ -90,7 +90,7 @@ {% else %}

No

{% endif %} -

EOP Stakeholder

+

Interagency Initiative

{% if domain_request.is_interagency_initiative is None %}

Incomplete

{% elif domain_request.is_interagency_initiative %} @@ -110,8 +110,8 @@ {% if domain_request.working_with_eop is None %}

Incomplete

{% elif domain_request.working_with_eop %} -

{{domain_request.eop_contact.first_name}} {{domain_request.eop_contact.last_name}}

-

{{domain_request.eop_contact.email}}

+

{{domain_request.eop_stakeholder_first_name}} {{domain_request.eop_stakeholder_last_name}}

+

{{domain_request.eop_stakeholder_email}}

{% else %}

No

{% endif %} diff --git a/src/registrar/tests/test_views_request.py b/src/registrar/tests/test_views_request.py index e5162bdb1..914bb210d 100644 --- a/src/registrar/tests/test_views_request.py +++ b/src/registrar/tests/test_views_request.py @@ -2745,21 +2745,21 @@ class DomainRequestTests(TestWithUser, WebTest): def feb_review_page_tests(self, review_page): # Meets Naming Requirements - self.assertContains(review_page, "

Meets Naming Requirements

") + self.assertContains(review_page, "Meets Naming Requirements") self.assertContains(review_page, "No") self.assertContains(review_page, "Because this is a test") # Purpose - self.assertContains(review_page, "

Purpose

") + self.assertContains(review_page, "Purpose") self.assertContains(review_page, "Used as a redirect for an existing website") self.assertContains(review_page, "testPurpose123") # Target Time Frame - self.assertContains(review_page, "

Target Time Frame

") + self.assertContains(review_page, "Target Time Frame") self.assertContains(review_page, "1/2/2025 - 1/2/2026") # Interagency Initiative - self.assertContains(review_page, "

Interagency Initiative

") + self.assertContains(review_page, "Interagency Initiative") self.assertContains(review_page, "FakeInteragencyInitiative") # EOP Stakeholder - self.assertContains(review_page, "

EOP Stakeholder

") + self.assertContains(review_page, "EOP Stakeholder") self.assertContains(review_page, "TesterFirstName TesterLastName") self.assertContains(review_page, "testy@town.com") From 4cd0747af1f47bd39c8b02aec2dc581f356e580a Mon Sep 17 00:00:00 2001 From: matthewswspence Date: Tue, 25 Mar 2025 12:16:05 -0500 Subject: [PATCH 11/41] linter and bug fix --- src/registrar/admin.py | 20 +++++++++++--------- src/registrar/models/domain_request.py | 2 +- src/registrar/utility/csv_export.py | 4 +++- src/registrar/views/domain_request.py | 4 +++- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 0699008b9..bff9b86cb 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -3096,15 +3096,17 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportRegistrarModelAdmin): # Hide FEB fields behind the organization requests flag # and only show them if the portfolio is executive if not (obj and obj.portfolio and obj.portfolio.federal_type == BranchChoices.EXECUTIVE): - excluded_fields.extend([ - "feb_naming_requirements_details", - "feb_purpose_choice", - "time_frame_details", - "interagency_initiative_details", - "eop_stakeholder_first_name", - "eop_stakeholder_last_name", - "eop_stakeholder_email", - ]) + excluded_fields.extend( + [ + "feb_naming_requirements_details", + "feb_purpose_choice", + "time_frame_details", + "interagency_initiative_details", + "eop_stakeholder_first_name", + "eop_stakeholder_last_name", + "eop_stakeholder_email", + ] + ) # This makes .gov domain collapsable underneath Requested By fieldsets[2][1]["classes"] = ["collapse--dgfieldset"] modified_fieldsets = [] diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py index e747a3828..8a64aad63 100644 --- a/src/registrar/models/domain_request.py +++ b/src/registrar/models/domain_request.py @@ -1029,7 +1029,7 @@ class DomainRequest(TimeStampedModel): if not context: has_organization_feature_flag = flag_is_active_for_user(recipient, "organization_feature") is_org_user = has_organization_feature_flag and recipient.has_view_portfolio_permission(self.portfolio) - requires_feb_questions = self.requires_feb_questions() + requires_feb_questions = self.is_feb() and is_org_user context = { "domain_request": self, # This is the user that we refer to in the email diff --git a/src/registrar/utility/csv_export.py b/src/registrar/utility/csv_export.py index 62179c24a..6d6a7325d 100644 --- a/src/registrar/utility/csv_export.py +++ b/src/registrar/utility/csv_export.py @@ -1841,7 +1841,9 @@ class DomainRequestExport(BaseExport): # FEB fields purpose_type = model.get("feb_purpose_choice") - purpose_type_display = DomainRequest.FEBPurposeChoices.get_purpose_label(purpose_type) if purpose_type else "N/A" + purpose_type_display = ( + DomainRequest.FEBPurposeChoices.get_purpose_label(purpose_type) if purpose_type else "N/A" + ) eop_stakeholder_first_name = model.get("eop_stakeholder_first_name") eop_stakeholder_last_name = model.get("eop_stakeholder_last_name") if not eop_stakeholder_first_name or not eop_stakeholder_last_name: diff --git a/src/registrar/views/domain_request.py b/src/registrar/views/domain_request.py index 5cd3715d3..13ff9e264 100644 --- a/src/registrar/views/domain_request.py +++ b/src/registrar/views/domain_request.py @@ -1180,7 +1180,9 @@ class PortfolioDomainRequestStatusViewOnly(DetailView): context["form_titles"] = wizard.titles logger.debug(self.object.is_feb()) logger.debug(flag_is_active_for_user(self.request.user, "organization_feature")) - context["requires_feb_questions"] = self.object.is_feb() and flag_is_active_for_user(self.request.user, "organization_feature") + context["requires_feb_questions"] = self.object.is_feb() and flag_is_active_for_user( + self.request.user, "organization_feature" + ) return context From 3bf81824ef3d9c2b3c88137320323130dd89ad7b Mon Sep 17 00:00:00 2001 From: matthewswspence Date: Tue, 25 Mar 2025 12:45:00 -0500 Subject: [PATCH 12/41] modify email templates --- .../includes/domain_request_summary.txt | 38 +------------------ .../portfolio_domain_request_summary.txt | 35 +++++++++++++++++ 2 files changed, 36 insertions(+), 37 deletions(-) diff --git a/src/registrar/templates/emails/includes/domain_request_summary.txt b/src/registrar/templates/emails/includes/domain_request_summary.txt index 300edd49f..b5f216e03 100644 --- a/src/registrar/templates/emails/includes/domain_request_summary.txt +++ b/src/registrar/templates/emails/includes/domain_request_summary.txt @@ -35,39 +35,12 @@ Current websites: {% for site in domain_request.current_websites.all %} {% endfor %}{% endif %} .gov domain: {{ domain_request.requested_domain.name }} -{% if requires_feb_questions %} - Meets Naming Requirements - {% if domain_request.feb_naming_requirements %} - {{ domain_request.feb_naming_requirements }} - {% else %} - No - {{ domain_request.feb_naming_requirements_details }} - {% endif %} -{% endif %} {% if domain_request.alternative_domains.all %} Alternative domains: {% for site in domain_request.alternative_domains.all %}{% spaceless %}{{ site.website }}{% endspaceless %} {% endfor %}{% endif %} Purpose of your domain: -{% if requires_feb_questions %} - {{ domain_request.feb_purpose_choice }} - {{ domain_request.purpose }} - Target time frame: - {% if domain_request.has_target_time_frame %} - {{ domain_request.time_frame_details }} - {% else %} - No - {% endif %} - Interagency initiative: - {% if domain_request.is_interagency_initiative %} - {{ domain_request.interagency_initiative_details }} - {% else %} - No - {% endif %} -{% else %} - {{ domain_request.purpose }} -{% endif %} - +{{ domain_request.purpose }} Your contact information: {% spaceless %}{% include "emails/includes/contact.txt" with contact=recipient %}{% endspaceless %} @@ -76,15 +49,6 @@ Other employees from your organization:{% for other in domain_request.other_cont {% empty %} {{ domain_request.no_other_contacts_rationale }} {% endfor %} -{% if requires_feb_questions %} - EOP Stakeholder: - {% if domain_request.working_with_eop %} - {{ domain_request.eop_contact.first_name }} {{ domain_request.eop_contact.last_name }} - {{ domain_request.eop_contact.email }} - {% else %} - No - {% endif %} -{% endif %} {% if domain_request.anything_else %} Anything else? {{ domain_request.anything_else }} diff --git a/src/registrar/templates/emails/includes/portfolio_domain_request_summary.txt b/src/registrar/templates/emails/includes/portfolio_domain_request_summary.txt index 866fde50f..66b0a0970 100644 --- a/src/registrar/templates/emails/includes/portfolio_domain_request_summary.txt +++ b/src/registrar/templates/emails/includes/portfolio_domain_request_summary.txt @@ -8,12 +8,47 @@ Current websites: {% for site in domain_request.current_websites.all %} {% endfor %}{% endif %} .gov domain: {{ domain_request.requested_domain.name }} +{% if requires_feb_questions %} + Meets Naming Requirements + {% if domain_request.feb_naming_requirements %} + {{ domain_request.feb_naming_requirements }} + {% else %} + No + {{ domain_request.feb_naming_requirements_details }} + {% endif %} +{% endif %} {% if domain_request.alternative_domains.all %} Alternative domains: {% for site in domain_request.alternative_domains.all %}{% spaceless %}{{ site.website }}{% endspaceless %} {% endfor %}{% endif %} Purpose of your domain: +{% if requires_feb_questions %} +{{ domain_request.feb_purpose_choice }} {{ domain_request.purpose }} +Target time frame: +{% if domain_request.has_target_time_frame %} +{{ domain_request.time_frame_details }} +{% else %} +No +{% endif %} +Interagency initiative: +{% if domain_request.is_interagency_initiative %} +{{ domain_request.interagency_initiative_details }} +{% else %} +No +{% endif %} +{% else %} +{{ domain_request.purpose }} +{% endif %} +{% if requires_feb_questions %} +EOP Stakeholder: +{% if domain_request.working_with_eop %} +{{ domain_request.eop_contact.first_name }} {{ domain_request.eop_contact.last_name }} +{{ domain_request.eop_contact.email }} +{% else %} +No +{% endif %} +{% endif %} {% if domain_request.anything_else %} Additional details: {{ domain_request.anything_else }} From bffd8e209060431593e6b85d3fdeb1b94d0916c4 Mon Sep 17 00:00:00 2001 From: matthewswspence Date: Tue, 25 Mar 2025 13:28:50 -0500 Subject: [PATCH 13/41] fix tests --- .../templates/emails/includes/domain_request_summary.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/registrar/templates/emails/includes/domain_request_summary.txt b/src/registrar/templates/emails/includes/domain_request_summary.txt index b5f216e03..c58aef5e1 100644 --- a/src/registrar/templates/emails/includes/domain_request_summary.txt +++ b/src/registrar/templates/emails/includes/domain_request_summary.txt @@ -41,6 +41,7 @@ Alternative domains: {% endfor %}{% endif %} Purpose of your domain: {{ domain_request.purpose }} + Your contact information: {% spaceless %}{% include "emails/includes/contact.txt" with contact=recipient %}{% endspaceless %} @@ -48,8 +49,7 @@ Other employees from your organization:{% for other in domain_request.other_cont {% spaceless %}{% include "emails/includes/contact.txt" with contact=other %}{% endspaceless %} {% empty %} {{ domain_request.no_other_contacts_rationale }} -{% endfor %} -{% if domain_request.anything_else %} - Anything else? - {{ domain_request.anything_else }} +{% endfor %}{% if domain_request.anything_else %} +Anything else? +{{ domain_request.anything_else }} {% endif %} \ No newline at end of file From 20d4923e8711e67bc27be20388690a3484062674 Mon Sep 17 00:00:00 2001 From: Matt-Spence Date: Tue, 25 Mar 2025 15:21:10 -0500 Subject: [PATCH 14/41] Update src/registrar/views/domain_request.py Co-authored-by: zandercymatics <141044360+zandercymatics@users.noreply.github.com> --- src/registrar/views/domain_request.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/registrar/views/domain_request.py b/src/registrar/views/domain_request.py index 13ff9e264..ddd8ddf7d 100644 --- a/src/registrar/views/domain_request.py +++ b/src/registrar/views/domain_request.py @@ -1178,8 +1178,6 @@ class PortfolioDomainRequestStatusViewOnly(DetailView): context["Step"] = PortfolioDomainRequestStep.__members__ context["steps"] = request_step_list(wizard, PortfolioDomainRequestStep) context["form_titles"] = wizard.titles - logger.debug(self.object.is_feb()) - logger.debug(flag_is_active_for_user(self.request.user, "organization_feature")) context["requires_feb_questions"] = self.object.is_feb() and flag_is_active_for_user( self.request.user, "organization_feature" ) From 968a68f248f1cbc0e8958300f6ee21b1820bd272 Mon Sep 17 00:00:00 2001 From: matthewswspence Date: Wed, 26 Mar 2025 11:17:21 -0500 Subject: [PATCH 15/41] review fixes --- src/registrar/admin.py | 16 +--------------- src/registrar/models/domain_request.py | 19 ++++++++++--------- .../portfolio_domain_request_summary.txt | 2 +- .../portfolio_request_review_steps.html | 10 ++-------- src/registrar/views/domain_request.py | 3 +-- 5 files changed, 15 insertions(+), 35 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index bff9b86cb..dcb92c145 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -2737,23 +2737,15 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportRegistrarModelAdmin): def feb_naming_requirements_details(self, obj): return obj.feb_naming_requirements_details if obj.feb_naming_requirements else "" - feb_naming_requirements_details.short_description = "Domain Name Rationale:" # type: ignore - def feb_purpose_choice(self, obj): return obj.feb_purpose_choice if obj.feb_purpose_choice else "" - feb_purpose_choice.short_description = "Purpose type:" # type: ignore - def time_frame_details(self, obj): return obj.time_frame_details if obj.has_timeframe else "" - time_frame_details.short_description = "Target time frame:" # type: ignore - def interagency_initiative_details(self, obj): return obj.interagency_initiative_details if obj.is_interagency_initiative else "" - interagency_initiative_details.short_description = "Interagency Initiative:" # type: ignore - def eop_stakeholder_first_name(self, obj): return obj.eop_stakeholder_first_name if obj.eop_stakeholder_first_name else "" @@ -2762,11 +2754,7 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportRegistrarModelAdmin): def eop_stakeholder_email(self, obj): return obj.eop_stakeholder_email if obj.eop_stakeholder_email else "" - - eop_stakeholder_first_name.short_description = "EOP Stakeholder First Name" # type: ignore - eop_stakeholder_last_name.short_description = "EOP Stakeholder Last Name" # type: ignore - eop_stakeholder_email.short_description = "EOP Stakeholder Email" # type: ignore - + # This is just a placeholder. This field will be populated in the detail_table_fieldset view. # This is not a field that exists on the model. def status_history(self, obj): @@ -3107,8 +3095,6 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportRegistrarModelAdmin): "eop_stakeholder_email", ] ) - # This makes .gov domain collapsable underneath Requested By - fieldsets[2][1]["classes"] = ["collapse--dgfieldset"] modified_fieldsets = [] for name, data in fieldsets: fields = data.get("fields", []) diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py index 8a64aad63..d13fde518 100644 --- a/src/registrar/models/domain_request.py +++ b/src/registrar/models/domain_request.py @@ -55,19 +55,17 @@ class DomainRequest(TimeStampedModel): return cls(status_name).label if status_name else None class FEBPurposeChoices(models.TextChoices): - WEBSITE = "website" - REDIRECT = "redirect" - OTHER = "other" + WEBSITE = "website", "Used for a new website" + REDIRECT = "redirect", "Used as a redirect for an existing website" + OTHER = "other", "Not for a website" @classmethod def get_purpose_label(cls, purpose_name: str): """Returns the associated label for a given purpose name""" - if purpose_name == cls.WEBSITE: - return "Used for a new website" - elif purpose_name == cls.REDIRECT: - return "Used as a redirect for an existing website" - else: - return "Not for a website" + logger.debug(f"purpose_name: {purpose_name}") + logger.debug(f"label: {cls(purpose_name).label}") + return cls(purpose_name).label if purpose_name else None + class StateTerritoryChoices(models.TextChoices): ALABAMA = "AL", "Alabama (AL)" @@ -520,6 +518,7 @@ class DomainRequest(TimeStampedModel): feb_naming_requirements = models.BooleanField( null=True, blank=True, + verbose_name="Meets Naming Requirements", ) feb_naming_requirements_details = models.TextField( @@ -1030,12 +1029,14 @@ class DomainRequest(TimeStampedModel): has_organization_feature_flag = flag_is_active_for_user(recipient, "organization_feature") is_org_user = has_organization_feature_flag and recipient.has_view_portfolio_permission(self.portfolio) requires_feb_questions = self.is_feb() and is_org_user + purpose_label = DomainRequest.FEBPurposeChoices.get_purpose_label(self.purpose) context = { "domain_request": self, # This is the user that we refer to in the email "recipient": recipient, "is_org_user": is_org_user, "requires_feb_questions": requires_feb_questions, + "purpose_label": purpose_label, } if custom_email_content: diff --git a/src/registrar/templates/emails/includes/portfolio_domain_request_summary.txt b/src/registrar/templates/emails/includes/portfolio_domain_request_summary.txt index 66b0a0970..42d75e4b2 100644 --- a/src/registrar/templates/emails/includes/portfolio_domain_request_summary.txt +++ b/src/registrar/templates/emails/includes/portfolio_domain_request_summary.txt @@ -23,7 +23,7 @@ Alternative domains: {% endfor %}{% endif %} Purpose of your domain: {% if requires_feb_questions %} -{{ domain_request.feb_purpose_choice }} +{{ purpose_label }} {{ domain_request.purpose }} Target time frame: {% if domain_request.has_target_time_frame %} diff --git a/src/registrar/templates/includes/portfolio_request_review_steps.html b/src/registrar/templates/includes/portfolio_request_review_steps.html index 14706275d..98f951d9c 100644 --- a/src/registrar/templates/includes/portfolio_request_review_steps.html +++ b/src/registrar/templates/includes/portfolio_request_review_steps.html @@ -72,14 +72,8 @@ {% endwith %} {% if requires_feb_questions %}

Purpose

- {% if domain_request.feb_purpose_choice == "website" %} -

Used for a new website

-

{{domain_request.purpose}}

- {% elif domain_request.feb_purpose_choice == "redirect" %} -

Used as a redirect for an existing website

-

{{domain_request.purpose}}

- {% elif domain_request.feb_purpose_choice == "other" %} -

Not for a website

+ {% if domain_request.feb_purpose_choice %} +

{{purpose_label}}

{{domain_request.purpose}}

{% else %}

Incomplete

diff --git a/src/registrar/views/domain_request.py b/src/registrar/views/domain_request.py index 13ff9e264..f3deab36f 100644 --- a/src/registrar/views/domain_request.py +++ b/src/registrar/views/domain_request.py @@ -1178,11 +1178,10 @@ class PortfolioDomainRequestStatusViewOnly(DetailView): context["Step"] = PortfolioDomainRequestStep.__members__ context["steps"] = request_step_list(wizard, PortfolioDomainRequestStep) context["form_titles"] = wizard.titles - logger.debug(self.object.is_feb()) - logger.debug(flag_is_active_for_user(self.request.user, "organization_feature")) context["requires_feb_questions"] = self.object.is_feb() and flag_is_active_for_user( self.request.user, "organization_feature" ) + context["purpose_label"] = DomainRequest.FEBPurposeChoices.get_purpose_label(self.object.purpose) return context From 61cc84b67e128a2166d168655bf1c77eb80eed8a Mon Sep 17 00:00:00 2001 From: matthewswspence Date: Wed, 26 Mar 2025 11:24:42 -0500 Subject: [PATCH 16/41] hardwire FEB to true for testing --- src/registrar/views/domain_request.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/registrar/views/domain_request.py b/src/registrar/views/domain_request.py index f3deab36f..90115e03a 100644 --- a/src/registrar/views/domain_request.py +++ b/src/registrar/views/domain_request.py @@ -183,7 +183,8 @@ class DomainRequestWizard(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") + # return self.domain_request.is_feb() and flag_is_active_for_user(self.request.user, "organization_feature") + return True @property def prefix(self): From 241f2b7d81e9d18809b55cdc751440e478409381 Mon Sep 17 00:00:00 2001 From: matthewswspence Date: Wed, 26 Mar 2025 11:31:34 -0500 Subject: [PATCH 17/41] fix 500 error on additional details step --- .../templates/portfolio_domain_request_additional_details.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/templates/portfolio_domain_request_additional_details.html b/src/registrar/templates/portfolio_domain_request_additional_details.html index d7d53dd1a..98b33bf7f 100644 --- a/src/registrar/templates/portfolio_domain_request_additional_details.html +++ b/src/registrar/templates/portfolio_domain_request_additional_details.html @@ -58,7 +58,7 @@

This question is optional.

{% with attr_maxlength=2000 add_label_class="usa-sr-only" %} - {% input_with_errors forms.0.anything_else %} + {% input_with_errors forms.3.anything_else %} {% endwith %}
{% endif %} From 15795dd333e0e446310355615bd446ec2795c558 Mon Sep 17 00:00:00 2001 From: matthewswspence Date: Wed, 26 Mar 2025 11:46:21 -0500 Subject: [PATCH 18/41] review fixes --- src/registrar/forms/feb.py | 7 ++----- src/registrar/models/domain_request.py | 4 ++-- src/registrar/views/domain_request.py | 3 ++- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/registrar/forms/feb.py b/src/registrar/forms/feb.py index 2dabbff0d..fbb11a6ea 100644 --- a/src/registrar/forms/feb.py +++ b/src/registrar/forms/feb.py @@ -1,6 +1,7 @@ from django import forms from django.core.validators import MaxLengthValidator from registrar.forms.utility.wizard_form_helper import BaseDeletableRegistrarForm, BaseYesNoForm +from registrar.models.domain_request import DomainRequest class ExecutiveNamingRequirementsYesNoForm(BaseYesNoForm, BaseDeletableRegistrarForm): @@ -41,11 +42,7 @@ 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"), - ) + form_choices = DomainRequest.FEBPurposeChoices.choices feb_purpose_choice = forms.ChoiceField( required=True, diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py index d13fde518..f2560aae9 100644 --- a/src/registrar/models/domain_request.py +++ b/src/registrar/models/domain_request.py @@ -55,7 +55,7 @@ class DomainRequest(TimeStampedModel): return cls(status_name).label if status_name else None class FEBPurposeChoices(models.TextChoices): - WEBSITE = "website", "Used for a new website" + WEBSITE = "new", "Used for a new website" REDIRECT = "redirect", "Used as a redirect for an existing website" OTHER = "other", "Not for a website" @@ -1029,7 +1029,7 @@ class DomainRequest(TimeStampedModel): has_organization_feature_flag = flag_is_active_for_user(recipient, "organization_feature") is_org_user = has_organization_feature_flag and recipient.has_view_portfolio_permission(self.portfolio) requires_feb_questions = self.is_feb() and is_org_user - purpose_label = DomainRequest.FEBPurposeChoices.get_purpose_label(self.purpose) + purpose_label = DomainRequest.FEBPurposeChoices.get_purpose_label(self.feb_purpose_choice) context = { "domain_request": self, # This is the user that we refer to in the email diff --git a/src/registrar/views/domain_request.py b/src/registrar/views/domain_request.py index 90115e03a..9b11dc104 100644 --- a/src/registrar/views/domain_request.py +++ b/src/registrar/views/domain_request.py @@ -966,6 +966,7 @@ class Review(DomainRequestWizard): context["Step"] = self.get_step_enum().__members__ context["domain_request"] = self.domain_request context["requires_feb_questions"] = self.requires_feb_questions() + context["purpose_label"] = DomainRequest.FEBPurposeChoices.get_purpose_label(self.domain_request.feb_purpose_choice) return context def goto_next_step(self): @@ -1182,7 +1183,7 @@ class PortfolioDomainRequestStatusViewOnly(DetailView): context["requires_feb_questions"] = self.object.is_feb() and flag_is_active_for_user( self.request.user, "organization_feature" ) - context["purpose_label"] = DomainRequest.FEBPurposeChoices.get_purpose_label(self.object.purpose) + context["purpose_label"] = DomainRequest.FEBPurposeChoices.get_purpose_label(self.object.feb_purpose_choice) return context From 8888131b38a96c5c3b0364ce336421daab5948f6 Mon Sep 17 00:00:00 2001 From: matthewswspence Date: Wed, 26 Mar 2025 11:52:32 -0500 Subject: [PATCH 19/41] linter fixes --- src/registrar/admin.py | 2 +- src/registrar/models/domain_request.py | 5 +---- src/registrar/views/domain_request.py | 4 +++- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index dcb92c145..61bb151d5 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -2754,7 +2754,7 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportRegistrarModelAdmin): def eop_stakeholder_email(self, obj): return obj.eop_stakeholder_email if obj.eop_stakeholder_email else "" - + # This is just a placeholder. This field will be populated in the detail_table_fieldset view. # This is not a field that exists on the model. def status_history(self, obj): diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py index f2560aae9..415c7f30b 100644 --- a/src/registrar/models/domain_request.py +++ b/src/registrar/models/domain_request.py @@ -60,13 +60,10 @@ class DomainRequest(TimeStampedModel): OTHER = "other", "Not for a website" @classmethod - def get_purpose_label(cls, purpose_name: str): + def get_purpose_label(cls, purpose_name: str | None): """Returns the associated label for a given purpose name""" - logger.debug(f"purpose_name: {purpose_name}") - logger.debug(f"label: {cls(purpose_name).label}") return cls(purpose_name).label if purpose_name else None - class StateTerritoryChoices(models.TextChoices): ALABAMA = "AL", "Alabama (AL)" ALASKA = "AK", "Alaska (AK)" diff --git a/src/registrar/views/domain_request.py b/src/registrar/views/domain_request.py index 9b11dc104..784b1118c 100644 --- a/src/registrar/views/domain_request.py +++ b/src/registrar/views/domain_request.py @@ -966,7 +966,9 @@ class Review(DomainRequestWizard): context["Step"] = self.get_step_enum().__members__ context["domain_request"] = self.domain_request context["requires_feb_questions"] = self.requires_feb_questions() - context["purpose_label"] = DomainRequest.FEBPurposeChoices.get_purpose_label(self.domain_request.feb_purpose_choice) + context["purpose_label"] = DomainRequest.FEBPurposeChoices.get_purpose_label( + self.domain_request.feb_purpose_choice + ) return context def goto_next_step(self): From 607ec28765d760f01bb367e5d4892d63a7d202c5 Mon Sep 17 00:00:00 2001 From: matthewswspence Date: Wed, 26 Mar 2025 12:22:41 -0500 Subject: [PATCH 20/41] small review fix --- .../includes/portfolio_request_review_steps.html | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/registrar/templates/includes/portfolio_request_review_steps.html b/src/registrar/templates/includes/portfolio_request_review_steps.html index 98f951d9c..43f214a4b 100644 --- a/src/registrar/templates/includes/portfolio_request_review_steps.html +++ b/src/registrar/templates/includes/portfolio_request_review_steps.html @@ -67,10 +67,10 @@ {% endif %} {% if step == Step.PURPOSE %} - {% with title=form_titles|get_item:step value=domain_request.purpose|default:"Incomplete"|safe %} - {% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=is_editable edit_link=domain_request_url %} - {% endwith %} {% if requires_feb_questions %} + {% with title=form_titles|get_item:step %} + {% include "includes/summary_item.html" with title=title value=" " heading_level=heading_level editable=is_editable edit_link=domain_request_url %} + {% endwith %}

Purpose

{% if domain_request.feb_purpose_choice %}

{{purpose_label}}

@@ -94,6 +94,10 @@ {% else %}

No

{% endif %} + {% else %} + {% with title=form_titles|get_item:step value=domain_request.purpose|default:"Incomplete"|safe %} + {% include "includes/summary_item.html" with title=title value=value heading_level=heading_level editable=is_editable edit_link=domain_request_url %} + {% endwith %} {% endif %} {% endif %} From ce69d129986ae70826569875a3ce51a7bf813c81 Mon Sep 17 00:00:00 2001 From: matthewswspence Date: Wed, 26 Mar 2025 12:35:54 -0500 Subject: [PATCH 21/41] add fixture and remove hardcoded feb func --- src/registrar/fixtures/fixtures_users.py | 7 +++++++ src/registrar/views/domain_request.py | 3 +-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/registrar/fixtures/fixtures_users.py b/src/registrar/fixtures/fixtures_users.py index fdaa1c135..a89d63cce 100644 --- a/src/registrar/fixtures/fixtures_users.py +++ b/src/registrar/fixtures/fixtures_users.py @@ -22,6 +22,13 @@ class UserFixture: """ ADMINS = [ + { + "username": "4aa78480-6272-42f9-ac29-a034ebdd9231", + "first_name": "Kaitlin", + "last_name": "Abbitt", + "email": "kaitlin.abbitt@cisa.dhs.gov", + "title": "Captain pirate", + }, { "username": "aad084c3-66cc-4632-80eb-41cdf5c5bcbf", "first_name": "Aditi", diff --git a/src/registrar/views/domain_request.py b/src/registrar/views/domain_request.py index 784b1118c..4b5abf598 100644 --- a/src/registrar/views/domain_request.py +++ b/src/registrar/views/domain_request.py @@ -183,8 +183,7 @@ class DomainRequestWizard(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") - return True + return self.domain_request.is_feb() and flag_is_active_for_user(self.request.user, "organization_feature") @property def prefix(self): From 926aefbd05789a72b1a87035f6d4363d728c7ec6 Mon Sep 17 00:00:00 2001 From: Matt-Spence Date: Wed, 26 Mar 2025 14:10:57 -0500 Subject: [PATCH 22/41] Update src/registrar/admin.py Co-authored-by: zandercymatics <141044360+zandercymatics@users.noreply.github.com> --- src/registrar/admin.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index df87cfee3..eaa0310fa 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -2741,26 +2741,6 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportRegistrarModelAdmin): portfolio_urbanization.short_description = "Urbanization" # type: ignore # ------ FEB fields ------ - def feb_naming_requirements_details(self, obj): - return obj.feb_naming_requirements_details if obj.feb_naming_requirements else "" - - def feb_purpose_choice(self, obj): - return obj.feb_purpose_choice if obj.feb_purpose_choice else "" - - def time_frame_details(self, obj): - return obj.time_frame_details if obj.has_timeframe else "" - - def interagency_initiative_details(self, obj): - return obj.interagency_initiative_details if obj.is_interagency_initiative else "" - - def eop_stakeholder_first_name(self, obj): - return obj.eop_stakeholder_first_name if obj.eop_stakeholder_first_name else "" - - def eop_stakeholder_last_name(self, obj): - return obj.eop_stakeholder_last_name if obj.eop_stakeholder_last_name else "" - - def eop_stakeholder_email(self, obj): - return obj.eop_stakeholder_email if obj.eop_stakeholder_email else "" # This is just a placeholder. This field will be populated in the detail_table_fieldset view. # This is not a field that exists on the model. From cb75ec84c36d385455282e1d27d6c25ee31dc7dc Mon Sep 17 00:00:00 2001 From: matthewswspence Date: Wed, 26 Mar 2025 15:03:12 -0500 Subject: [PATCH 23/41] review fixes --- src/registrar/admin.py | 2 - src/registrar/forms/feb.py | 18 ---- ...nrequest_eop_stakeholder_email_and_more.py | 56 ++++++++++ src/registrar/models/domain_request.py | 6 -- .../templates/domain_request_purpose.html | 5 +- .../portfolio_request_review_steps.html | 1 - ...lio_domain_request_additional_details.html | 102 +++++++++--------- src/registrar/tests/test_admin_request.py | 1 - src/registrar/tests/test_views_request.py | 2 - src/registrar/utility/csv_export.py | 1 - src/registrar/views/domain_request.py | 5 + 11 files changed, 112 insertions(+), 87 deletions(-) create mode 100644 src/registrar/migrations/0145_remove_domainrequest_eop_stakeholder_email_and_more.py diff --git a/src/registrar/admin.py b/src/registrar/admin.py index eaa0310fa..4fb0dfb37 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -2845,7 +2845,6 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportRegistrarModelAdmin): "cisa_representative_email", "eop_stakeholder_first_name", "eop_stakeholder_last_name", - "eop_stakeholder_email", ] }, ), @@ -3079,7 +3078,6 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportRegistrarModelAdmin): "interagency_initiative_details", "eop_stakeholder_first_name", "eop_stakeholder_last_name", - "eop_stakeholder_email", ] ) modified_fieldsets = [] diff --git a/src/registrar/forms/feb.py b/src/registrar/forms/feb.py index fbb11a6ea..97b9b1ef4 100644 --- a/src/registrar/forms/feb.py +++ b/src/registrar/forms/feb.py @@ -153,29 +153,12 @@ class EOPContactForm(BaseDeletableRegistrarForm): error_messages={"required": "Enter the last name / family name of this contact."}, required=True, ) - email = forms.EmailField( - label="Email", - max_length=None, - error_messages={ - "required": ("Enter an email address in the required format, like name@example.com."), - "invalid": ("Enter an email address in the required format, like name@example.com."), - }, - validators=[ - MaxLengthValidator( - 320, - message="Response must be less than 320 characters.", - ) - ], - required=True, - help_text="Enter an email address in the required format, like name@example.com.", - ) @classmethod def from_database(cls, obj): return { "first_name": obj.eop_stakeholder_first_name, "last_name": obj.eop_stakeholder_last_name, - "email": obj.eop_stakeholder_email, } def to_database(self, obj): @@ -189,7 +172,6 @@ class EOPContactForm(BaseDeletableRegistrarForm): return obj.eop_stakeholder_first_name = self.cleaned_data["first_name"] obj.eop_stakeholder_last_name = self.cleaned_data["last_name"] - obj.eop_stakeholder_email = self.cleaned_data["email"] obj.save() diff --git a/src/registrar/migrations/0145_remove_domainrequest_eop_stakeholder_email_and_more.py b/src/registrar/migrations/0145_remove_domainrequest_eop_stakeholder_email_and_more.py new file mode 100644 index 000000000..303ff7698 --- /dev/null +++ b/src/registrar/migrations/0145_remove_domainrequest_eop_stakeholder_email_and_more.py @@ -0,0 +1,56 @@ +# Generated by Django 4.2.20 on 2025-03-26 19:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("registrar", "0144_domainrequest_eop_stakeholder_email_and_more"), + ] + + operations = [ + migrations.RemoveField( + model_name="domainrequest", + name="eop_stakeholder_email", + ), + migrations.AlterField( + model_name="domainrequest", + name="feb_naming_requirements", + field=models.BooleanField(blank=True, null=True, verbose_name="Meets Naming Requirements"), + ), + migrations.AlterField( + model_name="domainrequest", + name="feb_naming_requirements_details", + field=models.TextField( + blank=True, + help_text="Required if requested domain that doesn't meet naming requirements", + null=True, + verbose_name="Domain name rationale", + ), + ), + migrations.AlterField( + model_name="domainrequest", + name="feb_purpose_choice", + field=models.CharField( + blank=True, + choices=[ + ("new", "Used for a new website"), + ("redirect", "Used as a redirect for an existing website"), + ("other", "Not for a website"), + ], + null=True, + verbose_name="Purpose type", + ), + ), + migrations.AlterField( + model_name="domainrequest", + name="interagency_initiative_details", + field=models.TextField(blank=True, null=True, verbose_name="Interagency initiative"), + ), + migrations.AlterField( + model_name="domainrequest", + name="time_frame_details", + field=models.TextField(blank=True, null=True, verbose_name="Target time frame"), + ), + ] diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py index 415c7f30b..11cc53a7f 100644 --- a/src/registrar/models/domain_request.py +++ b/src/registrar/models/domain_request.py @@ -549,12 +549,6 @@ class DomainRequest(TimeStampedModel): verbose_name="EOP Stakeholder Last Name", ) - eop_stakeholder_email = models.EmailField( - null=True, - blank=True, - verbose_name="EOP Stakeholder Email", - ) - # This field is alternately used for generic domain purpose explanations # and for explanations of the specific purpose chosen with feb_purpose_choice purpose = models.TextField( diff --git a/src/registrar/templates/domain_request_purpose.html b/src/registrar/templates/domain_request_purpose.html index 9c6754f22..3893e51ec 100644 --- a/src/registrar/templates/domain_request_purpose.html +++ b/src/registrar/templates/domain_request_purpose.html @@ -34,7 +34,6 @@ {% with add_label_class="usa-sr-only" attr_required="required" attr_maxlength="2000" %} {% input_with_errors forms.1.purpose %} {% endwith %} -

Maximum 2000 characters allowed.

Do you have a target time frame for launching this domain?

@@ -52,7 +51,6 @@ {% with add_label_class="usa-sr-only" attr_required="required" attr_maxlength="2000" %} {% input_with_errors forms.3.time_frame_details %} {% endwith %} -

Maximum 2000 characters allowed.

Will the domain name be used for an interagency initiative?

@@ -65,12 +63,11 @@

- Provide details below. * + Name the agencies that will be involved in this initiative. *

{% with add_label_class="usa-sr-only" attr_required="required" attr_maxlength="2000" %} {% input_with_errors forms.5.interagency_initiative_details %} {% endwith %} -

Maximum 2000 characters allowed.

{% else %} diff --git a/src/registrar/templates/includes/portfolio_request_review_steps.html b/src/registrar/templates/includes/portfolio_request_review_steps.html index 43f214a4b..0855931b3 100644 --- a/src/registrar/templates/includes/portfolio_request_review_steps.html +++ b/src/registrar/templates/includes/portfolio_request_review_steps.html @@ -111,7 +111,6 @@

Incomplete

{% elif domain_request.working_with_eop %}

{{domain_request.eop_stakeholder_first_name}} {{domain_request.eop_stakeholder_last_name}}

-

{{domain_request.eop_stakeholder_email}}

{% else %}

No

{% endif %} diff --git a/src/registrar/templates/portfolio_domain_request_additional_details.html b/src/registrar/templates/portfolio_domain_request_additional_details.html index 98b33bf7f..84bf9ec83 100644 --- a/src/registrar/templates/portfolio_domain_request_additional_details.html +++ b/src/registrar/templates/portfolio_domain_request_additional_details.html @@ -6,60 +6,58 @@ {% endblock %} {% block form_fields %} - {% if requires_feb_questions %} + {% if requires_feb_questions %} +
+ {{forms.0.management_form}} + {{forms.1.management_form}} {{forms.2.management_form}} {{forms.3.management_form}} - {{forms.4.management_form}} - {{forms.5.management_form}} -
-

Are you working with someone in the Executive Office of the President (EOP) on this request?

- -

- Select one. * -

- {% with add_class="usa-radio__input--tile" add_legend_class="usa-sr-only" %} - {% input_with_errors forms.0.working_with_eop %} - {% endwith %} +

Are you working with someone in the Executive Office of the President (EOP) on this request?

+

+ Select one. * +

+ {% with add_class="usa-radio__input--tile" add_legend_class="usa-sr-only" %} + {% input_with_errors forms.0.working_with_eop %} + {% endwith %} - - -

Is there anything else you'd like us to know about your domain request?

-

- Select one. * -

- {% with add_class="usa-radio__input--tile" add_legend_class="usa-sr-only" %} - {% input_with_errors forms.2.has_anything_else_text %} - {% endwith %} - - -
- {% else %} -
-

Is there anything else you’d like us to know about your domain request?

- -
- -
-

This question is optional.

- {% with attr_maxlength=2000 add_label_class="usa-sr-only" %} - {% input_with_errors forms.3.anything_else %} - {% endwith %} + - {% endif %} + +

Is there anything else you'd like us to know about your domain request?

+

+ Select one. * +

+ {% with add_class="usa-radio__input--tile" add_legend_class="usa-sr-only" %} + {% input_with_errors forms.2.has_anything_else_text %} + {% endwith %} + + +
+ {% else %} +
+

Is there anything else you’d like us to know about your domain request?

+ +
+ +
+

This question is optional.

+ {% with attr_maxlength=2000 add_label_class="usa-sr-only" %} + {% input_with_errors forms.3.anything_else %} + {% endwith %} +
+ {% endif %} {% endblock %} diff --git a/src/registrar/tests/test_admin_request.py b/src/registrar/tests/test_admin_request.py index e6ad2ef3e..3e4f88c05 100644 --- a/src/registrar/tests/test_admin_request.py +++ b/src/registrar/tests/test_admin_request.py @@ -2063,7 +2063,6 @@ class TestDomainRequestAdmin(MockEppLib): "working_with_eop", "eop_stakeholder_first_name", "eop_stakeholder_last_name", - "eop_stakeholder_email", "purpose", "has_timeframe", "time_frame_details", diff --git a/src/registrar/tests/test_views_request.py b/src/registrar/tests/test_views_request.py index 914bb210d..da021491d 100644 --- a/src/registrar/tests/test_views_request.py +++ b/src/registrar/tests/test_views_request.py @@ -2648,7 +2648,6 @@ class DomainRequestTests(TestWithUser, WebTest): additional_details_form["portfolio_additional_details-working_with_eop"] = "True" additional_details_form["portfolio_additional_details-first_name"] = "TesterFirstName" additional_details_form["portfolio_additional_details-last_name"] = "TesterLastName" - additional_details_form["portfolio_additional_details-email"] = "testy@town.com" additional_details_form["portfolio_additional_details-has_anything_else_text"] = "True" additional_details_form["portfolio_additional_details-anything_else"] = "test" self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) @@ -2761,7 +2760,6 @@ class DomainRequestTests(TestWithUser, WebTest): # EOP Stakeholder self.assertContains(review_page, "EOP Stakeholder") self.assertContains(review_page, "TesterFirstName TesterLastName") - self.assertContains(review_page, "testy@town.com") @less_console_noise_decorator def test_domain_request_formsets(self): diff --git a/src/registrar/utility/csv_export.py b/src/registrar/utility/csv_export.py index 6d6a7325d..610fc5c4f 100644 --- a/src/registrar/utility/csv_export.py +++ b/src/registrar/utility/csv_export.py @@ -1898,7 +1898,6 @@ class DomainRequestExport(BaseExport): "Target time frame": model.get("time_frame_details", None), "Interagency initiative": model.get("interagency_initiative_details", None), "EOP stakeholder name": eop_stakeholder_name, - "EOP stakeholder email": model.get("eop_stakeholder_email", None), } row = [FIELDS.get(column, "") for column in columns] diff --git a/src/registrar/views/domain_request.py b/src/registrar/views/domain_request.py index 4b5abf598..2e7ece60b 100644 --- a/src/registrar/views/domain_request.py +++ b/src/registrar/views/domain_request.py @@ -625,6 +625,11 @@ class PortfolioAdditionalDetails(DomainRequestWizard): 2: FEBAnythingElseYesNoForm 3: PortfolioAnythingElseForm """ + if not self.requires_feb_questions(): + for i in range(3): + forms[i].mark_form_for_deletion() + # If FEB questions aren't required, validate only the anything else form + return forms[3].is_valid() eop_forms_valid = True if not forms[0].is_valid(): # If the user isn't working with EOP, don't validate the EOP contact form From 00289f50811895d915cde424e120e6b4a8be78f0 Mon Sep 17 00:00:00 2001 From: matthewswspence Date: Thu, 27 Mar 2025 12:19:01 -0500 Subject: [PATCH 24/41] admin page fixes --- src/registrar/admin.py | 65 +++++++++++++---------- src/registrar/tests/test_views_request.py | 1 - 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 4fb0dfb37..078e80770 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -3057,36 +3057,45 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportRegistrarModelAdmin): def get_fieldsets(self, request, obj=None): fieldsets = super().get_fieldsets(request, obj) + excluded_fields = set() + feb_fields = [ + "feb_naming_requirements_details", + "feb_purpose_choice", + "time_frame_details", + "interagency_initiative_details", + "eop_stakeholder_first_name", + "eop_stakeholder_last_name", + ] + + org_fields = [ + "portfolio", + "sub_organization", + "requested_suborganization", + "suborganization_city", + "suborganization_state_territory", + ] + + org_flag = flag_is_active_for_user(request.user, "organization_requests") + # Hide FEB fields for non-FEB requests + if not (obj and obj.portfolio and obj.is_feb()): + logger.debug(f"obj: {obj}") + logger.debug(f"obj.portfolio: {obj.portfolio}") + logger.debug(f"obj.is_feb(): {obj.is_feb()}") + logger.debug(f"feb breakdown: {obj.portfolio.federal_type}") + excluded_fields.update(feb_fields) + # Hide certain portfolio and suborg fields behind the organization requests flag # if it is not enabled - if not flag_is_active_for_user(request.user, "organization_requests"): - excluded_fields = [ - "portfolio", - "sub_organization", - "requested_suborganization", - "suborganization_city", - "suborganization_state_territory", - ] - # Hide FEB fields behind the organization requests flag - # and only show them if the portfolio is executive - if not (obj and obj.portfolio and obj.portfolio.federal_type == BranchChoices.EXECUTIVE): - excluded_fields.extend( - [ - "feb_naming_requirements_details", - "feb_purpose_choice", - "time_frame_details", - "interagency_initiative_details", - "eop_stakeholder_first_name", - "eop_stakeholder_last_name", - ] - ) - modified_fieldsets = [] - for name, data in fieldsets: - fields = data.get("fields", []) - fields = tuple(field for field in fields if field not in excluded_fields) - modified_fieldsets.append((name, {**data, "fields": fields})) - return modified_fieldsets - return fieldsets + if not org_flag: + excluded_fields.update(org_fields) + excluded_fields.update(feb_fields) + + modified_fieldsets = [] + for name, data in fieldsets: + fields = data.get("fields", []) + fields = tuple(field for field in fields if field not in excluded_fields) + modified_fieldsets.append((name, {**data, "fields": fields})) + return modified_fieldsets # Trigger action when a fieldset is changed def save_model(self, request, obj, form, change): diff --git a/src/registrar/tests/test_views_request.py b/src/registrar/tests/test_views_request.py index da021491d..6ede713d6 100644 --- a/src/registrar/tests/test_views_request.py +++ b/src/registrar/tests/test_views_request.py @@ -2719,7 +2719,6 @@ class DomainRequestTests(TestWithUser, WebTest): self.assertContains(additional_details_page, "eop-contact-container") self.assertContains(additional_details_page, "additional_details-first_name") self.assertContains(additional_details_page, "additional_details-last_name") - self.assertContains(additional_details_page, "additional_details-email") # Make sure the additional details form is present self.assertContains(additional_details_page, "additional_details-has_anything_else_text") From 9743bc1cab7d312075363694ae7f544d6d015165 Mon Sep 17 00:00:00 2001 From: CocoByte Date: Thu, 27 Mar 2025 12:59:59 -0600 Subject: [PATCH 25/41] Additional Detail page design changes --- .../portfolio_domain_request_additional_details.html | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/registrar/templates/portfolio_domain_request_additional_details.html b/src/registrar/templates/portfolio_domain_request_additional_details.html index 84bf9ec83..4107e7bc1 100644 --- a/src/registrar/templates/portfolio_domain_request_additional_details.html +++ b/src/registrar/templates/portfolio_domain_request_additional_details.html @@ -6,6 +6,7 @@ {% endblock %} {% block form_fields %} + {% include "includes/required_fields.html" %} {% if requires_feb_questions %}
{{forms.0.management_form}} @@ -13,6 +14,7 @@ {{forms.2.management_form}} {{forms.3.management_form}}

Are you working with someone in the Executive Office of the President (EOP) on this request?

+

Working with the EOP is not required to request a .gov domain.

Select one. *

@@ -21,8 +23,8 @@ {% endwith %}
@@ -114,23 +120,21 @@

OMB will review each request against the domain - naming requirements for executive branch agencies - . - Agency submissions are expected to meet each requirement. + naming requirements for executive branch agencies. Agency submissions are expected to meet each requirement. +

+

+ Select one. *

{% with add_class="usa-radio__input--tile" add_legend_class="usa-sr-only" %} {% input_with_errors forms.2.feb_naming_requirements %} {% endwith %} {# Conditional Details Field – only shown when the executive naming requirements radio is "False" #} -