From 4727ce6b63459f2990e89cd0a647b60a0a9bcbf3 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 14 Dec 2023 10:41:44 -0700 Subject: [PATCH 01/11] PR suggestion --- src/registrar/management/commands/extend_expiration_dates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/management/commands/extend_expiration_dates.py b/src/registrar/management/commands/extend_expiration_dates.py index f969faa62..dd366cddd 100644 --- a/src/registrar/management/commands/extend_expiration_dates.py +++ b/src/registrar/management/commands/extend_expiration_dates.py @@ -134,7 +134,7 @@ class Command(BaseCommand): ==Proposed Changes== Domains to change: {domains_to_change_count} """, - prompt_title="Do you wish to proceed?", + prompt_title="Do you wish to proceed with these changes?", ) logger.info(f"{TerminalColors.MAGENTA}" "Preparing to extend expiration dates..." f"{TerminalColors.ENDC}") From ea7d34907c0bff77464a9a5bc9730b0d7d862bcb Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 14 Dec 2023 11:23:11 -0700 Subject: [PATCH 02/11] Add max cutoff date --- .../management/commands/extend_expiration_dates.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/registrar/management/commands/extend_expiration_dates.py b/src/registrar/management/commands/extend_expiration_dates.py index dd366cddd..84d26e544 100644 --- a/src/registrar/management/commands/extend_expiration_dates.py +++ b/src/registrar/management/commands/extend_expiration_dates.py @@ -29,7 +29,8 @@ class Command(BaseCommand): self.update_success = [] self.update_skipped = [] self.update_failed = [] - self.expiration_cutoff = date(2023, 11, 15) + self.expiration_minimum_cutoff = date(2023, 11, 15) + self.expiration_maximum_cutoff = date(2023, 12, 30) def add_arguments(self, parser): """Add command line arguments.""" @@ -71,7 +72,9 @@ class Command(BaseCommand): self.check_if_positive_int(limit_parse, "limitParse") valid_domains = Domain.objects.filter( - expiration_date__gte=self.expiration_cutoff, state=Domain.State.READY + expiration_date__gte=self.expiration_minimum_cutoff, + expiration_date__lte=self.expiration_maximum_cutoff, + state=Domain.State.READY ).order_by("name") domains_to_change_count = valid_domains.count() From 2481e89c2a44b976cb64a1a58dc52357709d732e Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 14 Dec 2023 11:38:09 -0700 Subject: [PATCH 03/11] Add unit tests --- .../commands/extend_expiration_dates.py | 2 +- src/registrar/tests/common.py | 10 +++++++ .../test_transition_domain_migrations.py | 28 +++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/registrar/management/commands/extend_expiration_dates.py b/src/registrar/management/commands/extend_expiration_dates.py index 84d26e544..5e203e488 100644 --- a/src/registrar/management/commands/extend_expiration_dates.py +++ b/src/registrar/management/commands/extend_expiration_dates.py @@ -74,7 +74,7 @@ class Command(BaseCommand): valid_domains = Domain.objects.filter( expiration_date__gte=self.expiration_minimum_cutoff, expiration_date__lte=self.expiration_maximum_cutoff, - state=Domain.State.READY + state=Domain.State.READY, ).order_by("name") domains_to_change_count = valid_domains.count() diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index 337b6f31e..ad34d53a7 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -835,6 +835,11 @@ class MockEppLib(TestCase): ex_date=datetime.date(2023, 2, 15), ) + mockMaximumRenewedDomainExpDate = fakedEppObject( + "fakemaximum.gov", + ex_date=datetime.date(2024, 12, 31), + ) + mockRecentRenewedDomainExpDate = fakedEppObject( "waterbutpurple.gov", ex_date=datetime.date(2024, 11, 15), @@ -948,6 +953,11 @@ class MockEppLib(TestCase): res_data=[self.mockDnsNeededRenewedDomainExpDate], code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY, ) + elif getattr(_request, "name", None) == "fakemaximum.gov": + return MagicMock( + res_data=[self.mockMaximumRenewedDomainExpDate], + code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY, + ) else: return MagicMock( res_data=[self.mockRenewedDomainExpDate], diff --git a/src/registrar/tests/test_transition_domain_migrations.py b/src/registrar/tests/test_transition_domain_migrations.py index 2da42462c..363dafc2c 100644 --- a/src/registrar/tests/test_transition_domain_migrations.py +++ b/src/registrar/tests/test_transition_domain_migrations.py @@ -52,6 +52,15 @@ class TestExtendExpirationDates(MockEppLib): domain_name="fakeneeded.gov", epp_expiration_date=datetime.date(2023, 11, 15), ) + # Create a domain with a date greater than the maximum + Domain.objects.get_or_create( + name="fakemaximum.gov", state=Domain.State.READY, expiration_date=datetime.date(2024, 12, 31) + ) + TransitionDomain.objects.get_or_create( + username="fakemaximum@mail.com", + domain_name="fakemaximum.gov", + epp_expiration_date=datetime.date(2024, 12, 31), + ) def tearDown(self): """Deletes all DB objects related to migrations""" @@ -114,6 +123,25 @@ class TestExtendExpirationDates(MockEppLib): # should not be affected by the change. self.assertEqual(current_domain.expiration_date, datetime.date(2022, 5, 25)) + def test_extends_expiration_date_skips_maximum_date(self): + """ + Tests that the extend_expiration_dates method correctly skips domains + with an expiration date more than a certain threshold. + """ + desired_domain = Domain.objects.filter(name="fakemaximum.gov").get() + desired_domain.expiration_date = datetime.date(2024, 12, 31) + + # Run the expiration date script + self.run_extend_expiration_dates() + + current_domain = Domain.objects.filter(name="fakemaximum.gov").get() + self.assertEqual(desired_domain, current_domain) + + # Explicitly test the expiration date. The extend_expiration_dates script + # will skip all dates less than date(2023, 11, 15), meaning that this domain + # should not be affected by the change. + self.assertEqual(current_domain.expiration_date, datetime.date(2024, 12, 31)) + def test_extends_expiration_date_skips_non_ready(self): """ Tests that the extend_expiration_dates method correctly skips domains not in the state "ready" From 493f64e070eb43cc21c1516e076deab9c335287d Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Thu, 14 Dec 2023 17:56:23 -0500 Subject: [PATCH 04/11] added code to json response for check availability so different message types can be handled differently by client --- src/api/views.py | 8 ++++---- src/registrar/utility/errors.py | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/api/views.py b/src/api/views.py index 068844919..0c24d0327 100644 --- a/src/api/views.py +++ b/src/api/views.py @@ -91,15 +91,15 @@ def available(request, domain=""): # validate that the given domain could be a domain name and fail early if # not. if not (DraftDomain.string_could_be_domain(domain) or DraftDomain.string_could_be_domain(domain + ".gov")): - return JsonResponse({"available": False, "message": DOMAIN_API_MESSAGES["invalid"]}) + return JsonResponse({"available": False, "code": "invalid", "message": DOMAIN_API_MESSAGES["invalid"]}) # a domain is available if it is NOT in the list of current domains try: if check_domain_available(domain): - return JsonResponse({"available": True, "message": DOMAIN_API_MESSAGES["success"]}) + return JsonResponse({"available": True, "code": "success", "message": DOMAIN_API_MESSAGES["success"]}) else: - return JsonResponse({"available": False, "message": DOMAIN_API_MESSAGES["unavailable"]}) + return JsonResponse({"available": False, "code": "unavailable", "message": DOMAIN_API_MESSAGES["unavailable"]}) except Exception: - return JsonResponse({"available": False, "message": DOMAIN_API_MESSAGES["error"]}) + return JsonResponse({"available": False, "code": "error", "message": DOMAIN_API_MESSAGES["error"]}) @require_http_methods(["GET"]) diff --git a/src/registrar/utility/errors.py b/src/registrar/utility/errors.py index 9463c1387..d096a30da 100644 --- a/src/registrar/utility/errors.py +++ b/src/registrar/utility/errors.py @@ -43,11 +43,11 @@ class GenericError(Exception): """ _error_mapping = { - GenericErrorCodes.CANNOT_CONTACT_REGISTRY: """ -We’re experiencing a system connection error. Please wait a few minutes -and try again. If you continue to receive this error after a few tries, -contact help@get.gov. - """, + GenericErrorCodes.CANNOT_CONTACT_REGISTRY: ( +"We’re experiencing a system connection error. Please wait a few minutes " +"and try again. If you continue to receive this error after a few tries, " +"contact help@get.gov." + ), GenericErrorCodes.GENERIC_ERROR: ("Value entered was wrong."), } From e1de3dab68f730b69ebf055d598f1756d368395c Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Thu, 14 Dec 2023 18:08:35 -0500 Subject: [PATCH 05/11] linting --- src/api/views.py | 4 +++- src/registrar/utility/errors.py | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/api/views.py b/src/api/views.py index 0c24d0327..a7dd7600a 100644 --- a/src/api/views.py +++ b/src/api/views.py @@ -97,7 +97,9 @@ def available(request, domain=""): if check_domain_available(domain): return JsonResponse({"available": True, "code": "success", "message": DOMAIN_API_MESSAGES["success"]}) else: - return JsonResponse({"available": False, "code": "unavailable", "message": DOMAIN_API_MESSAGES["unavailable"]}) + return JsonResponse( + {"available": False, "code": "unavailable", "message": DOMAIN_API_MESSAGES["unavailable"]} + ) except Exception: return JsonResponse({"available": False, "code": "error", "message": DOMAIN_API_MESSAGES["error"]}) diff --git a/src/registrar/utility/errors.py b/src/registrar/utility/errors.py index d096a30da..455419236 100644 --- a/src/registrar/utility/errors.py +++ b/src/registrar/utility/errors.py @@ -44,9 +44,9 @@ class GenericError(Exception): _error_mapping = { GenericErrorCodes.CANNOT_CONTACT_REGISTRY: ( -"We’re experiencing a system connection error. Please wait a few minutes " -"and try again. If you continue to receive this error after a few tries, " -"contact help@get.gov." + "We’re experiencing a system connection error. Please wait a few minutes " + "and try again. If you continue to receive this error after a few tries, " + "contact help@get.gov." ), GenericErrorCodes.GENERIC_ERROR: ("Value entered was wrong."), } From 53dd308d334620333dd4665ca79c449e291e4162 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 15 Dec 2023 01:30:46 -0500 Subject: [PATCH 06/11] Add modal between DA submit and form submit button, unit test that looks for modal trigger and modal data, fix DA unit tests that query and trigger the original form since we now have 2 forms on the page --- src/registrar/templates/application_form.html | 18 +++- .../test_copy_names_from_contacts_to_users.py | 4 +- src/registrar/tests/test_views.py | 98 ++++++++++--------- src/registrar/views/application.py | 7 ++ 4 files changed, 78 insertions(+), 49 deletions(-) diff --git a/src/registrar/templates/application_form.html b/src/registrar/templates/application_form.html index db72a1fc2..b57c807f6 100644 --- a/src/registrar/templates/application_form.html +++ b/src/registrar/templates/application_form.html @@ -85,16 +85,28 @@ class="usa-button usa-button--outline" >Save and return to manage your domains {% else %} - + aria-controls="toggle-submit-domain-request" + data-open-modal + >Submit your domain request {% endif %} {% endblock %} +
+ {% include 'includes/modal.html' with modal_heading="You are about to submit a domain request for yourcity.gov." modal_description="Once you submit this request, you won’t be able to make further edits until it’s reviewed by our staff. You’ll only be able to withdraw your request." modal_button=modal_button|safe %} +
+ {% block after_form_content %}{% endblock %} diff --git a/src/registrar/tests/test_copy_names_from_contacts_to_users.py b/src/registrar/tests/test_copy_names_from_contacts_to_users.py index 2690578e0..988b57a4b 100644 --- a/src/registrar/tests/test_copy_names_from_contacts_to_users.py +++ b/src/registrar/tests/test_copy_names_from_contacts_to_users.py @@ -18,8 +18,8 @@ class TestDataUpdates(TestCase): self.user2 = User.objects.create(username="user2") self.user3 = User.objects.create(username="user3") self.user4 = User.objects.create(username="user4") - # The last user created triggers the creation of a contact and attaches itself to it. @Neil wth is going on? - # This bs_user defuses that situation so we can test the code. + # The last user created triggers the creation of a contact and attaches itself to it. See signals. + # This bs_user defuses that situation (unwanted user-contact pairing) so we can test the code. self.bs_user = User.objects.create() self.contact1 = Contact.objects.create( diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index 17641636e..fb875db9a 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -141,7 +141,7 @@ class DomainApplicationTests(TestWithUser, WebTest): # 302 redirect to the first form page = self.app.get(reverse("application:")).follow() # submitting should get back the same page if the required field is empty - result = page.form.submit() + result = page.forms[0].submit() self.assertIn("What kind of U.S.-based government organization do you represent?", result) def test_application_multiple_applications_exist(self): @@ -178,11 +178,11 @@ class DomainApplicationTests(TestWithUser, WebTest): session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] # ---- TYPE PAGE ---- - type_form = type_page.form + type_form = type_page.forms[0] type_form["organization_type-organization_type"] = "federal" # test next button and validate data self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_result = type_page.form.submit() + type_result = type_form.submit() # should see results in db application = DomainApplication.objects.get() # there's only one self.assertEqual(application.organization_type, "federal") @@ -197,7 +197,7 @@ class DomainApplicationTests(TestWithUser, WebTest): self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) federal_page = type_result.follow() - federal_form = federal_page.form + federal_form = federal_page.forms[0] federal_form["organization_federal-federal_type"] = "executive" # test next button @@ -216,7 +216,7 @@ class DomainApplicationTests(TestWithUser, WebTest): # Follow the redirect to the next form page self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) org_contact_page = federal_result.follow() - org_contact_form = org_contact_page.form + org_contact_form = org_contact_page.forms[0] # federal agency so we have to fill in federal_agency org_contact_form["organization_contact-federal_agency"] = "General Services Administration" org_contact_form["organization_contact-organization_name"] = "Testorg" @@ -249,7 +249,7 @@ class DomainApplicationTests(TestWithUser, WebTest): # Follow the redirect to the next form page self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) ao_page = org_contact_result.follow() - ao_form = ao_page.form + ao_form = ao_page.forms[0] ao_form["authorizing_official-first_name"] = "Testy ATO" ao_form["authorizing_official-last_name"] = "Tester ATO" ao_form["authorizing_official-title"] = "Chief Tester" @@ -276,7 +276,7 @@ class DomainApplicationTests(TestWithUser, WebTest): # Follow the redirect to the next form page self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) current_sites_page = ao_result.follow() - current_sites_form = current_sites_page.form + current_sites_form = current_sites_page.forms[0] current_sites_form["current_sites-0-website"] = "www.city.com" # test next button @@ -298,7 +298,7 @@ class DomainApplicationTests(TestWithUser, WebTest): # Follow the redirect to the next form page self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) dotgov_page = current_sites_result.follow() - dotgov_form = dotgov_page.form + dotgov_form = dotgov_page.forms[0] dotgov_form["dotgov_domain-requested_domain"] = "city" dotgov_form["dotgov_domain-0-alternative_domain"] = "city1" @@ -318,7 +318,7 @@ class DomainApplicationTests(TestWithUser, WebTest): # Follow the redirect to the next form page self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) purpose_page = dotgov_result.follow() - purpose_form = purpose_page.form + purpose_form = purpose_page.forms[0] purpose_form["purpose-purpose"] = "For all kinds of things." # test next button @@ -337,7 +337,7 @@ class DomainApplicationTests(TestWithUser, WebTest): # Follow the redirect to the next form page self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) your_contact_page = purpose_result.follow() - your_contact_form = your_contact_page.form + your_contact_form = your_contact_page.forms[0] your_contact_form["your_contact-first_name"] = "Testy you" your_contact_form["your_contact-last_name"] = "Tester you" @@ -365,7 +365,7 @@ class DomainApplicationTests(TestWithUser, WebTest): # Follow the redirect to the next form page self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) other_contacts_page = your_contact_result.follow() - other_contacts_form = other_contacts_page.form + other_contacts_form = other_contacts_page.forms[0] other_contacts_form["other_contacts-0-first_name"] = "Testy2" other_contacts_form["other_contacts-0-last_name"] = "Tester2" @@ -398,7 +398,7 @@ class DomainApplicationTests(TestWithUser, WebTest): # Follow the redirect to the next form page self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) anything_else_page = other_contacts_result.follow() - anything_else_form = anything_else_page.form + anything_else_form = anything_else_page.forms[0] anything_else_form["anything_else-anything_else"] = "Nothing else." @@ -418,7 +418,7 @@ class DomainApplicationTests(TestWithUser, WebTest): # Follow the redirect to the next form page self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) requirements_page = anything_else_result.follow() - requirements_form = requirements_page.form + requirements_form = requirements_page.forms[0] requirements_form["requirements-is_policy_acknowledged"] = True @@ -438,7 +438,7 @@ class DomainApplicationTests(TestWithUser, WebTest): # Follow the redirect to the next form page self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) review_page = requirements_result.follow() - review_form = review_page.form + review_form = review_page.forms[0] # Review page contains all the previously entered data # Let's make sure the long org name is displayed @@ -540,7 +540,7 @@ class DomainApplicationTests(TestWithUser, WebTest): # the conditional step titles shouldn't appear initially self.assertNotContains(type_page, self.TITLES["organization_federal"]) self.assertNotContains(type_page, self.TITLES["organization_election"]) - type_form = type_page.form + type_form = type_page.forms[0] type_form["organization_type-organization_type"] = "federal" # set the session ID before .submit() @@ -561,9 +561,9 @@ class DomainApplicationTests(TestWithUser, WebTest): # continuing on in the flow we need to see top-level agency on the # contact page - federal_page.form["organization_federal-federal_type"] = "executive" + federal_page.forms[0]["organization_federal-federal_type"] = "executive" self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - federal_result = federal_page.form.submit() + federal_result = federal_page.forms[0].submit() # the post request should return a redirect to the contact # question self.assertEqual(federal_result.status_code, 302) @@ -586,7 +586,7 @@ class DomainApplicationTests(TestWithUser, WebTest): # the conditional step titles shouldn't appear initially self.assertNotContains(type_page, self.TITLES["organization_federal"]) self.assertNotContains(type_page, self.TITLES["organization_election"]) - type_form = type_page.form + type_form = type_page.forms[0] type_form["organization_type-organization_type"] = "county" # set the session ID before .submit() @@ -606,9 +606,9 @@ class DomainApplicationTests(TestWithUser, WebTest): # continuing on in the flow we need to NOT see top-level agency on the # contact page - election_page.form["organization_election-is_election_board"] = "True" + election_page.forms[0]["organization_election-is_election_board"] = "True" self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - election_result = election_page.form.submit() + election_result = election_page.forms[0].submit() # the post request should return a redirect to the contact # question self.assertEqual(election_result.status_code, 302) @@ -626,10 +626,10 @@ class DomainApplicationTests(TestWithUser, WebTest): # and then setting the cookie on each request. session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - type_form = type_page.form + type_form = type_page.forms[0] type_form["organization_type-organization_type"] = "federal" self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_result = type_page.form.submit() + type_result = type_form.submit() # follow first redirect self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) @@ -654,15 +654,15 @@ class DomainApplicationTests(TestWithUser, WebTest): # and then setting the cookie on each request. session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - type_form = type_page.form + type_form = type_page.forms[0] type_form["organization_type-organization_type"] = DomainApplication.OrganizationChoices.INTERSTATE self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_result = type_page.form.submit() + type_result = type_form.submit() # follow first redirect self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) contact_page = type_result.follow() - org_contact_form = contact_page.form + org_contact_form = contact_page.forms[0] self.assertNotIn("federal_agency", org_contact_form.fields) @@ -690,10 +690,10 @@ class DomainApplicationTests(TestWithUser, WebTest): # and then setting the cookie on each request. session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - type_form = type_page.form + type_form = type_page.forms[0] type_form["organization_type-organization_type"] = DomainApplication.OrganizationChoices.SPECIAL_DISTRICT self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_result = type_page.form.submit() + type_result = type_page.forms[0].submit() # follow first redirect self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) contact_page = type_result.follow() @@ -710,7 +710,7 @@ class DomainApplicationTests(TestWithUser, WebTest): session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - result = contacts_page.form.submit() + result = contacts_page.forms[0].submit() # follow first redirect self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) no_contacts_page = result.follow() @@ -727,10 +727,10 @@ class DomainApplicationTests(TestWithUser, WebTest): # and then setting the cookie on each request. session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - type_form = type_page.form + type_form = type_page.forms[0] type_form["organization_type-organization_type"] = DomainApplication.OrganizationChoices.INTERSTATE self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_result = type_page.form.submit() + type_result = type_form.submit() # follow first redirect self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) contact_page = type_result.follow() @@ -745,10 +745,10 @@ class DomainApplicationTests(TestWithUser, WebTest): # of a "session". We are going to do it manually, saving the session ID here # and then setting the cookie on each request. session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - type_form = type_page.form + type_form = type_page.forms[0] type_form["organization_type-organization_type"] = DomainApplication.OrganizationChoices.TRIBAL self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_result = type_page.form.submit() + type_result = type_form.submit() # the tribal government page comes immediately afterwards self.assertIn("/tribal_government", type_result.headers["Location"]) # follow first redirect @@ -767,18 +767,18 @@ class DomainApplicationTests(TestWithUser, WebTest): session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] # ---- TYPE PAGE ---- - type_form = type_page.form + type_form = type_page.forms[0] type_form["organization_type-organization_type"] = "federal" # test next button self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_result = type_page.form.submit() + type_result = type_form.submit() # ---- FEDERAL BRANCH PAGE ---- # Follow the redirect to the next form page self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) federal_page = type_result.follow() - federal_form = federal_page.form + federal_form = federal_page.forms[0] federal_form["organization_federal-federal_type"] = "executive" self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) federal_result = federal_form.submit() @@ -787,7 +787,7 @@ class DomainApplicationTests(TestWithUser, WebTest): # Follow the redirect to the next form page self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) org_contact_page = federal_result.follow() - org_contact_form = org_contact_page.form + org_contact_form = org_contact_page.forms[0] # federal agency so we have to fill in federal_agency org_contact_form["organization_contact-federal_agency"] = "General Services Administration" org_contact_form["organization_contact-organization_name"] = "Testorg" @@ -828,18 +828,18 @@ class DomainApplicationTests(TestWithUser, WebTest): # and then setting the cookie on each request. session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] # ---- TYPE PAGE ---- - type_form = type_page.form + type_form = type_page.forms[0] type_form["organization_type-organization_type"] = "federal" # test next button self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - type_result = type_page.form.submit() + type_result = type_form.submit() # ---- FEDERAL BRANCH PAGE ---- # Follow the redirect to the next form page self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) federal_page = type_result.follow() - federal_form = federal_page.form + federal_form = federal_page.forms[0] federal_form["organization_federal-federal_type"] = "executive" self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) federal_result = federal_form.submit() @@ -848,7 +848,7 @@ class DomainApplicationTests(TestWithUser, WebTest): # Follow the redirect to the next form page self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) org_contact_page = federal_result.follow() - org_contact_form = org_contact_page.form + org_contact_form = org_contact_page.forms[0] # federal agency so we have to fill in federal_agency org_contact_form["organization_contact-federal_agency"] = "General Services Administration" org_contact_form["organization_contact-organization_name"] = "Testorg" @@ -870,7 +870,7 @@ class DomainApplicationTests(TestWithUser, WebTest): # Follow the redirect to the next form page self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) ao_page = org_contact_result.follow() - ao_form = ao_page.form + ao_form = ao_page.forms[0] ao_form["authorizing_official-first_name"] = "Testy ATO" ao_form["authorizing_official-last_name"] = "Tester ATO" ao_form["authorizing_official-title"] = "Chief Tester" @@ -884,7 +884,7 @@ class DomainApplicationTests(TestWithUser, WebTest): # Follow the redirect to the next form page self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) current_sites_page = ao_result.follow() - current_sites_form = current_sites_page.form + current_sites_form = current_sites_page.forms[0] current_sites_form["current_sites-0-website"] = "www.city.com" # test saving the page @@ -917,7 +917,7 @@ class DomainApplicationTests(TestWithUser, WebTest): current_sites_page = self.app.get(reverse("application:current_sites")) session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] # fill in the form field - current_sites_form = current_sites_page.form + current_sites_form = current_sites_page.forms[0] self.assertIn("current_sites-0-website", current_sites_form.fields) self.assertNotIn("current_sites-1-website", current_sites_form.fields) current_sites_form["current_sites-0-website"] = "https://example.com" @@ -926,7 +926,7 @@ class DomainApplicationTests(TestWithUser, WebTest): self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) current_sites_result = current_sites_form.submit("submit_button", value="save") self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) - current_sites_form = current_sites_result.follow().form + current_sites_form = current_sites_result.follow().forms[0] # verify that there are two form fields value = current_sites_form["current_sites-0-website"].value @@ -1086,6 +1086,16 @@ class DomainApplicationTests(TestWithUser, WebTest): detail_page = home_page.click("Manage", index=0) self.assertContains(detail_page, "Federal: an agency of the U.S. government") + def test_submit_modal(self): + """When user clicks on submit your domain request, a modal pops up.""" + completed_application() + review_page = self.app.get(reverse("application:review")) + # We can't test the modal itself as it relies on JS for init and triggering, + # but we can test for the existence of its trigger: + self.assertContains(review_page, "toggle-submit-domain-request") + # And the existence of the modal's data parked and ready for the js init: + self.assertContains(review_page, "You are about to submit a domain request") + class TestWithDomainPermissions(TestWithUser): def setUp(self): diff --git a/src/registrar/views/application.py b/src/registrar/views/application.py index bb1b3aee6..3bf12ec63 100644 --- a/src/registrar/views/application.py +++ b/src/registrar/views/application.py @@ -321,12 +321,19 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView): def get_context_data(self): """Define context for access on all wizard pages.""" + # Create HTML for the submit button: + # The on-page submit button is just a trigger for the modal; + # the submit button we're adding to context will get passed to + # the modal and is the button that triggers the actual domain + # application submission (via post -> goto_next_step -> done). + modal_button = '" return { "form_titles": self.TITLES, "steps": self.steps, # Add information about which steps should be unlocked "visited": self.storage.get("step_history", []), "is_federal": self.application.is_federal(), + "modal_button": modal_button, } def get_step_list(self) -> list: From 8b6ca02de1b52cdb3bdf77e1e49a5e4705c92c1c Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 15 Dec 2023 01:34:48 -0500 Subject: [PATCH 07/11] Make the modal a forced action modal --- src/registrar/templates/application_form.html | 1 + 1 file changed, 1 insertion(+) diff --git a/src/registrar/templates/application_form.html b/src/registrar/templates/application_form.html index b57c807f6..98eab0152 100644 --- a/src/registrar/templates/application_form.html +++ b/src/registrar/templates/application_form.html @@ -103,6 +103,7 @@ id="toggle-submit-domain-request" aria-labelledby="Are you sure you want to continue?" aria-describedby="Your DNSSEC records will be deleted from the registry." + data-force-action > {% include 'includes/modal.html' with modal_heading="You are about to submit a domain request for yourcity.gov." modal_description="Once you submit this request, you won’t be able to make further edits until it’s reviewed by our staff. You’ll only be able to withdraw your request." modal_button=modal_button|safe %} From 6912b03cc0a69a6d0ae426a88003cce1383ca76c Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 15 Dec 2023 02:36:04 -0500 Subject: [PATCH 08/11] Remove review page from pa11y, move the building of modal header into the view, edit test (wip) --- src/.pa11yci | 1 - src/registrar/templates/application_form.html | 6 +++--- src/registrar/tests/test_views.py | 5 +++-- src/registrar/views/application.py | 10 +++++++++- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/.pa11yci b/src/.pa11yci index 6bb5727e0..0ab3f4dd7 100644 --- a/src/.pa11yci +++ b/src/.pa11yci @@ -19,7 +19,6 @@ "http://localhost:8080/register/other_contacts/", "http://localhost:8080/register/anything_else/", "http://localhost:8080/register/requirements/", - "http://localhost:8080/register/review/", "http://localhost:8080/register/finished/" ] } diff --git a/src/registrar/templates/application_form.html b/src/registrar/templates/application_form.html index 98eab0152..cec2416fb 100644 --- a/src/registrar/templates/application_form.html +++ b/src/registrar/templates/application_form.html @@ -101,11 +101,11 @@
- {% include 'includes/modal.html' with modal_heading="You are about to submit a domain request for yourcity.gov." modal_description="Once you submit this request, you won’t be able to make further edits until it’s reviewed by our staff. You’ll only be able to withdraw your request." modal_button=modal_button|safe %} + {% include 'includes/modal.html' with modal_heading=modal_heading|safe modal_description="Once you submit this request, you won’t be able to make further edits until it’s reviewed by our staff. You’ll only be able to withdraw your request." modal_button=modal_button|safe %}
{% block after_form_content %}{% endblock %} diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index fb875db9a..b5574c8e2 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -1088,13 +1088,14 @@ class DomainApplicationTests(TestWithUser, WebTest): def test_submit_modal(self): """When user clicks on submit your domain request, a modal pops up.""" - completed_application() + # completed_application(name="cats.gov") review_page = self.app.get(reverse("application:review")) # We can't test the modal itself as it relies on JS for init and triggering, # but we can test for the existence of its trigger: self.assertContains(review_page, "toggle-submit-domain-request") # And the existence of the modal's data parked and ready for the js init: - self.assertContains(review_page, "You are about to submit a domain request") + print(review_page) + self.assertContains(review_page, "You are about to submit") class TestWithDomainPermissions(TestWithUser): diff --git a/src/registrar/views/application.py b/src/registrar/views/application.py index 3bf12ec63..4e76f55fe 100644 --- a/src/registrar/views/application.py +++ b/src/registrar/views/application.py @@ -321,12 +321,19 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView): def get_context_data(self): """Define context for access on all wizard pages.""" - # Create HTML for the submit button: # The on-page submit button is just a trigger for the modal; # the submit button we're adding to context will get passed to # the modal and is the button that triggers the actual domain # application submission (via post -> goto_next_step -> done). modal_button = '" + # We'll concatenate the modal header here for passing along to the + # modal include. NOTE: We are able to 'fast-forward' a domain application + # by tyoing in review in the URL. The submit button still shows, hence + # the if/else. + if self.application.requested_domain: + modal_heading = 'You are about to submit a domain request for ' + str(self.application.requested_domain) + else: + modal_heading = 'You are about to submit an incomplete request' return { "form_titles": self.TITLES, "steps": self.steps, @@ -334,6 +341,7 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView): "visited": self.storage.get("step_history", []), "is_federal": self.application.is_federal(), "modal_button": modal_button, + "modal_heading": modal_heading, } def get_step_list(self) -> list: From 8718635b94e6b044b9da60d3cc7bdd13f4302b34 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 15 Dec 2023 11:36:32 -0500 Subject: [PATCH 09/11] Update unit tests to look for the dynamic header passed in context to the modal --- src/registrar/tests/test_views.py | 28 ++++++++++++++++++++-------- src/registrar/views/application.py | 4 ++-- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index b5574c8e2..57fa03f52 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -164,6 +164,9 @@ class DomainApplicationTests(TestWithUser, WebTest): this test work. This test also looks for the long organization name on the summary page. + + This also tests for the presence of a modal trigger and the dynamic test + in the modal header on the submit page. """ num_pages_tested = 0 # elections, type_of_work, tribal_government, no_other_contacts @@ -472,6 +475,14 @@ class DomainApplicationTests(TestWithUser, WebTest): self.assertContains(review_page, "(201) 555-5557") self.assertContains(review_page, "Nothing else.") + # We can't test the modal itself as it relies on JS for init and triggering, + # but we can test for the existence of its trigger: + self.assertContains(review_page, "toggle-submit-domain-request") + # And the existence of the modal's data parked and ready for the js init. + # The next assert also tests for the passed requested domain context from + # the view > application_form > modal + self.assertContains(review_page, "You are about to submit a domain request for city.gov") + # final submission results in a redirect to the "finished" URL self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) with less_console_noise(): @@ -1086,16 +1097,17 @@ class DomainApplicationTests(TestWithUser, WebTest): detail_page = home_page.click("Manage", index=0) self.assertContains(detail_page, "Federal: an agency of the U.S. government") - def test_submit_modal(self): - """When user clicks on submit your domain request, a modal pops up.""" - # completed_application(name="cats.gov") + def test_submit_modal_no_domain_text_fallback(self): + """When user clicks on submit your domain request and the requested domain + is null (possible through url direct access to the review page), present + fallback copy in the modal's header. + + NOTE: This may be a moot point if we implement a more solid pattern in the + future, like not a submit action at all on the review page.""" + review_page = self.app.get(reverse("application:review")) - # We can't test the modal itself as it relies on JS for init and triggering, - # but we can test for the existence of its trigger: self.assertContains(review_page, "toggle-submit-domain-request") - # And the existence of the modal's data parked and ready for the js init: - print(review_page) - self.assertContains(review_page, "You are about to submit") + self.assertContains(review_page, "You are about to submit an incomplete request") class TestWithDomainPermissions(TestWithUser): diff --git a/src/registrar/views/application.py b/src/registrar/views/application.py index 4e76f55fe..ba716c117 100644 --- a/src/registrar/views/application.py +++ b/src/registrar/views/application.py @@ -331,9 +331,9 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView): # by tyoing in review in the URL. The submit button still shows, hence # the if/else. if self.application.requested_domain: - modal_heading = 'You are about to submit a domain request for ' + str(self.application.requested_domain) + modal_heading = "You are about to submit a domain request for " + str(self.application.requested_domain) else: - modal_heading = 'You are about to submit an incomplete request' + modal_heading = "You are about to submit an incomplete request" return { "form_titles": self.TITLES, "steps": self.steps, From d5f55ba383735cc07bb7d531888fba9add081fae Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 15 Dec 2023 11:43:20 -0500 Subject: [PATCH 10/11] Clean up comments --- .../tests/test_copy_names_from_contacts_to_users.py | 2 +- src/registrar/views/application.py | 10 ++-------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/registrar/tests/test_copy_names_from_contacts_to_users.py b/src/registrar/tests/test_copy_names_from_contacts_to_users.py index 988b57a4b..032203f4e 100644 --- a/src/registrar/tests/test_copy_names_from_contacts_to_users.py +++ b/src/registrar/tests/test_copy_names_from_contacts_to_users.py @@ -19,7 +19,7 @@ class TestDataUpdates(TestCase): self.user3 = User.objects.create(username="user3") self.user4 = User.objects.create(username="user4") # The last user created triggers the creation of a contact and attaches itself to it. See signals. - # This bs_user defuses that situation (unwanted user-contact pairing) so we can test the code. + # This bs_user defuses that situation. self.bs_user = User.objects.create() self.contact1 = Contact.objects.create( diff --git a/src/registrar/views/application.py b/src/registrar/views/application.py index ba716c117..0a6eb5b7b 100644 --- a/src/registrar/views/application.py +++ b/src/registrar/views/application.py @@ -321,15 +321,9 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView): def get_context_data(self): """Define context for access on all wizard pages.""" - # The on-page submit button is just a trigger for the modal; - # the submit button we're adding to context will get passed to - # the modal and is the button that triggers the actual domain - # application submission (via post -> goto_next_step -> done). + # Build the submit button that we'll pass to the modal. modal_button = '" - # We'll concatenate the modal header here for passing along to the - # modal include. NOTE: We are able to 'fast-forward' a domain application - # by tyoing in review in the URL. The submit button still shows, hence - # the if/else. + # Concatenate the modal header that we'll pass to the modal. if self.application.requested_domain: modal_heading = "You are about to submit a domain request for " + str(self.application.requested_domain) else: From 8479351e8aa2f5415e7e2ae560fd1349c79a7bb9 Mon Sep 17 00:00:00 2001 From: Neil Martinsen-Burrell Date: Mon, 18 Dec 2023 12:56:15 -0600 Subject: [PATCH 11/11] Add missing usa-list class to a ul --- src/registrar/templates/domain_dns.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/templates/domain_dns.html b/src/registrar/templates/domain_dns.html index 0f625e0e3..291319a59 100644 --- a/src/registrar/templates/domain_dns.html +++ b/src/registrar/templates/domain_dns.html @@ -12,7 +12,7 @@

You can enter your name servers, as well as other DNS-related information, in the following sections:

{% url 'domain-dns-nameservers' pk=domain.id as url %} -
    +
    • Name servers
    • {% url 'domain-dns-dnssec' pk=domain.id as url %}