From 8f99c2eafa73a4412bd75bdbebef5361cf608e57 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Mon, 22 Apr 2024 18:39:09 -0400 Subject: [PATCH 01/11] Add a link on empty domains tables --- src/registrar/assets/sass/_theme/_links.scss | 19 + src/registrar/assets/sass/_theme/_tables.scss | 16 - src/registrar/assets/sass/_theme/styles.scss | 1 + src/registrar/templates/home.html | 8 + src/registrar/tests/test_views.py | 372 +++++++++++++++++- src/registrar/tests/test_views_request.py | 363 ----------------- 6 files changed, 399 insertions(+), 380 deletions(-) create mode 100644 src/registrar/assets/sass/_theme/_links.scss diff --git a/src/registrar/assets/sass/_theme/_links.scss b/src/registrar/assets/sass/_theme/_links.scss new file mode 100644 index 000000000..aa5a85f01 --- /dev/null +++ b/src/registrar/assets/sass/_theme/_links.scss @@ -0,0 +1,19 @@ +@use "uswds-core" as *; + +p, .dotgov-table { + a { + display: flex; + align-items: flex-start; + color: color('primary'); + + &:visited { + color: color('primary'); + } + + .usa-icon { + // align icon with x height + margin-top: units(0.5); + margin-right: units(0.5); + } + } +} diff --git a/src/registrar/assets/sass/_theme/_tables.scss b/src/registrar/assets/sass/_theme/_tables.scss index 0d58b5878..8d4ebbc59 100644 --- a/src/registrar/assets/sass/_theme/_tables.scss +++ b/src/registrar/assets/sass/_theme/_tables.scss @@ -56,22 +56,6 @@ .dotgov-table { width: 100%; - a { - display: flex; - align-items: flex-start; - color: color('primary'); - - &:visited { - color: color('primary'); - } - - .usa-icon { - // align icon with x height - margin-top: units(0.5); - margin-right: units(0.5); - } - } - th[data-sortable]:not([aria-sort]) .usa-table__header__button { right: auto; } diff --git a/src/registrar/assets/sass/_theme/styles.scss b/src/registrar/assets/sass/_theme/styles.scss index 942501110..64b113a29 100644 --- a/src/registrar/assets/sass/_theme/styles.scss +++ b/src/registrar/assets/sass/_theme/styles.scss @@ -10,6 +10,7 @@ --- Custom Styles ---------------------------------*/ @forward "base"; @forward "typography"; +@forward "links"; @forward "lists"; @forward "buttons"; @forward "forms"; diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index ea9276b9f..6254fc3bb 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -98,6 +98,14 @@ > {% else %}

You don't have any registered domains.

+

+ + + Why don't I see my domain when I sign in to the registrar? + +

{% endif %} diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index b8055f288..217e24b9a 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -1,12 +1,16 @@ +from datetime import date from django.test import Client, TestCase, override_settings from django.contrib.auth import get_user_model from api.tests.common import less_console_noise_decorator +from registrar.models.contact import Contact from registrar.models.domain import Domain +from registrar.models.draft_domain import DraftDomain +from registrar.models.user import User from registrar.models.user_domain_role import UserDomainRole from registrar.views.domain import DomainNameserversView -from .common import MockEppLib # type: ignore +from .common import MockEppLib, less_console_noise # type: ignore from unittest.mock import patch from django.urls import reverse @@ -135,3 +139,369 @@ class TestEnvironmentVariablesEffects(TestCase): self.assertEqual(contact_page_500.status_code, 500) self.assertNotContains(contact_page_500, "You are on a test site.") + + +class HomeTests(TestWithUser): + """A series of tests that target the two tables on home.html""" + + def setUp(self): + super().setUp() + self.client.force_login(self.user) + + def tearDown(self): + super().tearDown() + Contact.objects.all().delete() + + def test_empty_domain_table(self): + response = self.client.get("/") + self.assertContains(response, "You don't have any registered domains.") + self.assertContains(response, "Why don't I see my domain when I sign in to the registrar?") + + def test_home_lists_domain_requests(self): + response = self.client.get("/") + self.assertNotContains(response, "igorville.gov") + site = DraftDomain.objects.create(name="igorville.gov") + domain_request = DomainRequest.objects.create(creator=self.user, requested_domain=site) + response = self.client.get("/") + + # count = 7 because of screenreader content + self.assertContains(response, "igorville.gov", count=7) + + # clean up + domain_request.delete() + + def test_state_help_text(self): + """Tests if each domain state has help text""" + + # Get the expected text content of each state + deleted_text = "This domain has been removed and " "is no longer registered to your organization." + dns_needed_text = "Before this domain can be used, " "you’ll need to add name server addresses." + ready_text = "This domain has name servers and is ready for use." + on_hold_text = ( + "This domain is administratively paused, " + "so it can’t be edited and won’t resolve in DNS. " + "Contact help@get.gov for details." + ) + deleted_text = "This domain has been removed and " "is no longer registered to your organization." + # Generate a mapping of domain names, the state, and expected messages for the subtest + test_cases = [ + ("deleted.gov", Domain.State.DELETED, deleted_text), + ("dnsneeded.gov", Domain.State.DNS_NEEDED, dns_needed_text), + ("unknown.gov", Domain.State.UNKNOWN, dns_needed_text), + ("onhold.gov", Domain.State.ON_HOLD, on_hold_text), + ("ready.gov", Domain.State.READY, ready_text), + ] + for domain_name, state, expected_message in test_cases: + with self.subTest(domain_name=domain_name, state=state, expected_message=expected_message): + # Create a domain and a UserRole with the given params + test_domain, _ = Domain.objects.get_or_create(name=domain_name, state=state) + test_domain.expiration_date = date.today() + test_domain.save() + + user_role, _ = UserDomainRole.objects.get_or_create( + user=self.user, domain=test_domain, role=UserDomainRole.Roles.MANAGER + ) + + # Grab the home page + response = self.client.get("/") + + # Make sure the user can actually see the domain. + # We expect two instances because of SR content. + self.assertContains(response, domain_name, count=2) + + # Check that we have the right text content. + self.assertContains(response, expected_message, count=1) + + # Delete the role and domain to ensure we're testing in isolation + user_role.delete() + test_domain.delete() + + def test_state_help_text_expired(self): + """Tests if each domain state has help text when expired""" + expired_text = "This domain has expired, but it is still online. " "To renew this domain, contact help@get.gov." + test_domain, _ = Domain.objects.get_or_create(name="expired.gov", state=Domain.State.READY) + test_domain.expiration_date = date(2011, 10, 10) + test_domain.save() + + UserDomainRole.objects.get_or_create(user=self.user, domain=test_domain, role=UserDomainRole.Roles.MANAGER) + + # Grab the home page + response = self.client.get("/") + + # Make sure the user can actually see the domain. + # We expect two instances because of SR content. + self.assertContains(response, "expired.gov", count=2) + + # Check that we have the right text content. + self.assertContains(response, expired_text, count=1) + + def test_state_help_text_no_expiration_date(self): + """Tests if each domain state has help text when expiration date is None""" + + # == Test a expiration of None for state ready. This should be expired. == # + expired_text = "This domain has expired, but it is still online. " "To renew this domain, contact help@get.gov." + test_domain, _ = Domain.objects.get_or_create(name="imexpired.gov", state=Domain.State.READY) + test_domain.expiration_date = None + test_domain.save() + + UserDomainRole.objects.get_or_create(user=self.user, domain=test_domain, role=UserDomainRole.Roles.MANAGER) + + # Grab the home page + response = self.client.get("/") + + # Make sure the user can actually see the domain. + # We expect two instances because of SR content. + self.assertContains(response, "imexpired.gov", count=2) + + # Make sure the expiration date is None + self.assertEqual(test_domain.expiration_date, None) + + # Check that we have the right text content. + self.assertContains(response, expired_text, count=1) + + # == Test a expiration of None for state unknown. This should not display expired text. == # + unknown_text = "Before this domain can be used, " "you’ll need to add name server addresses." + test_domain_2, _ = Domain.objects.get_or_create(name="notexpired.gov", state=Domain.State.UNKNOWN) + test_domain_2.expiration_date = None + test_domain_2.save() + + UserDomainRole.objects.get_or_create(user=self.user, domain=test_domain_2, role=UserDomainRole.Roles.MANAGER) + + # Grab the home page + response = self.client.get("/") + + # Make sure the user can actually see the domain. + # We expect two instances because of SR content. + self.assertContains(response, "notexpired.gov", count=2) + + # Make sure the expiration date is None + self.assertEqual(test_domain_2.expiration_date, None) + + # Check that we have the right text content. + self.assertContains(response, unknown_text, count=1) + + def test_home_deletes_withdrawn_domain_request(self): + """Tests if the user can delete a DomainRequest in the 'withdrawn' status""" + + site = DraftDomain.objects.create(name="igorville.gov") + domain_request = DomainRequest.objects.create( + creator=self.user, requested_domain=site, status=DomainRequest.DomainRequestStatus.WITHDRAWN + ) + + # Ensure that igorville.gov exists on the page + home_page = self.client.get("/") + self.assertContains(home_page, "igorville.gov") + + # Check if the delete button exists. We can do this by checking for its id and text content. + self.assertContains(home_page, "Delete") + self.assertContains(home_page, "button-toggle-delete-domain-alert-1") + + # Trigger the delete logic + response = self.client.post(reverse("domain-request-delete", kwargs={"pk": domain_request.pk}), follow=True) + + self.assertNotContains(response, "igorville.gov") + + # clean up + domain_request.delete() + + def test_home_deletes_started_domain_request(self): + """Tests if the user can delete a DomainRequest in the 'started' status""" + + site = DraftDomain.objects.create(name="igorville.gov") + domain_request = DomainRequest.objects.create( + creator=self.user, requested_domain=site, status=DomainRequest.DomainRequestStatus.STARTED + ) + + # Ensure that igorville.gov exists on the page + home_page = self.client.get("/") + self.assertContains(home_page, "igorville.gov") + + # Check if the delete button exists. We can do this by checking for its id and text content. + self.assertContains(home_page, "Delete") + self.assertContains(home_page, "button-toggle-delete-domain-alert-1") + + # Trigger the delete logic + response = self.client.post(reverse("domain-request-delete", kwargs={"pk": domain_request.pk}), follow=True) + + self.assertNotContains(response, "igorville.gov") + + # clean up + domain_request.delete() + + def test_home_doesnt_delete_other_domain_requests(self): + """Tests to ensure the user can't delete domain requests not in the status of STARTED or WITHDRAWN""" + + # Given that we are including a subset of items that can be deleted while excluding the rest, + # subTest is appropriate here as otherwise we would need many duplicate tests for the same reason. + with less_console_noise(): + draft_domain = DraftDomain.objects.create(name="igorville.gov") + for status in DomainRequest.DomainRequestStatus: + if status not in [ + DomainRequest.DomainRequestStatus.STARTED, + DomainRequest.DomainRequestStatus.WITHDRAWN, + ]: + with self.subTest(status=status): + domain_request = DomainRequest.objects.create( + creator=self.user, requested_domain=draft_domain, status=status + ) + + # Trigger the delete logic + response = self.client.post( + reverse("domain-request-delete", kwargs={"pk": domain_request.pk}), follow=True + ) + + # Check for a 403 error - the end user should not be allowed to do this + self.assertEqual(response.status_code, 403) + + desired_domain_request = DomainRequest.objects.filter(requested_domain=draft_domain) + + # Make sure the DomainRequest wasn't deleted + self.assertEqual(desired_domain_request.count(), 1) + + # clean up + domain_request.delete() + + def test_home_deletes_domain_request_and_orphans(self): + """Tests if delete for DomainRequest deletes orphaned Contact objects""" + + # Create the site and contacts to delete (orphaned) + contact = Contact.objects.create( + first_name="Henry", + last_name="Mcfakerson", + ) + contact_shared = Contact.objects.create( + first_name="Relative", + last_name="Aether", + ) + + # Create two non-orphaned contacts + contact_2 = Contact.objects.create( + first_name="Saturn", + last_name="Mars", + ) + + # Attach a user object to a contact (should not be deleted) + contact_user, _ = Contact.objects.get_or_create(user=self.user) + + site = DraftDomain.objects.create(name="igorville.gov") + domain_request = DomainRequest.objects.create( + creator=self.user, + requested_domain=site, + status=DomainRequest.DomainRequestStatus.WITHDRAWN, + authorizing_official=contact, + submitter=contact_user, + ) + domain_request.other_contacts.set([contact_2]) + + # Create a second domain request to attach contacts to + site_2 = DraftDomain.objects.create(name="teaville.gov") + domain_request_2 = DomainRequest.objects.create( + creator=self.user, + requested_domain=site_2, + status=DomainRequest.DomainRequestStatus.STARTED, + authorizing_official=contact_2, + submitter=contact_shared, + ) + domain_request_2.other_contacts.set([contact_shared]) + + # Ensure that igorville.gov exists on the page + home_page = self.client.get("/") + self.assertContains(home_page, "igorville.gov") + + # Trigger the delete logic + response = self.client.post(reverse("domain-request-delete", kwargs={"pk": domain_request.pk}), follow=True) + + # igorville is now deleted + self.assertNotContains(response, "igorville.gov") + + # Check if the orphaned contact was deleted + orphan = Contact.objects.filter(id=contact.id) + self.assertFalse(orphan.exists()) + + # All non-orphan contacts should still exist and are unaltered + try: + current_user = Contact.objects.filter(id=contact_user.id).get() + except Contact.DoesNotExist: + self.fail("contact_user (a non-orphaned contact) was deleted") + + self.assertEqual(current_user, contact_user) + try: + edge_case = Contact.objects.filter(id=contact_2.id).get() + except Contact.DoesNotExist: + self.fail("contact_2 (a non-orphaned contact) was deleted") + + self.assertEqual(edge_case, contact_2) + + def test_home_deletes_domain_request_and_shared_orphans(self): + """Test the edge case for an object that will become orphaned after a delete + (but is not an orphan at the time of deletion)""" + + # Create the site and contacts to delete (orphaned) + contact = Contact.objects.create( + first_name="Henry", + last_name="Mcfakerson", + ) + contact_shared = Contact.objects.create( + first_name="Relative", + last_name="Aether", + ) + + # Create two non-orphaned contacts + contact_2 = Contact.objects.create( + first_name="Saturn", + last_name="Mars", + ) + + # Attach a user object to a contact (should not be deleted) + contact_user, _ = Contact.objects.get_or_create(user=self.user) + + site = DraftDomain.objects.create(name="igorville.gov") + domain_request = DomainRequest.objects.create( + creator=self.user, + requested_domain=site, + status=DomainRequest.DomainRequestStatus.WITHDRAWN, + authorizing_official=contact, + submitter=contact_user, + ) + domain_request.other_contacts.set([contact_2]) + + # Create a second domain request to attach contacts to + site_2 = DraftDomain.objects.create(name="teaville.gov") + domain_request_2 = DomainRequest.objects.create( + creator=self.user, + requested_domain=site_2, + status=DomainRequest.DomainRequestStatus.STARTED, + authorizing_official=contact_2, + submitter=contact_shared, + ) + domain_request_2.other_contacts.set([contact_shared]) + + home_page = self.client.get("/") + self.assertContains(home_page, "teaville.gov") + + # Trigger the delete logic + response = self.client.post(reverse("domain-request-delete", kwargs={"pk": domain_request_2.pk}), follow=True) + + self.assertNotContains(response, "teaville.gov") + + # Check if the orphaned contact was deleted + orphan = Contact.objects.filter(id=contact_shared.id) + self.assertFalse(orphan.exists()) + + def test_domain_request_form_view(self): + response = self.client.get("/request/", follow=True) + self.assertContains( + response, + "You’re about to start your .gov domain request.", + ) + + def test_domain_request_form_with_ineligible_user(self): + """Domain request form not accessible for an ineligible user. + This test should be solid enough since all domain request wizard + views share the same permissions class""" + self.user.status = User.RESTRICTED + self.user.save() + + with less_console_noise(): + response = self.client.get("/request/", follow=True) + self.assertEqual(response.status_code, 403) diff --git a/src/registrar/tests/test_views_request.py b/src/registrar/tests/test_views_request.py index a4cb210bc..51e1b753f 100644 --- a/src/registrar/tests/test_views_request.py +++ b/src/registrar/tests/test_views_request.py @@ -3,7 +3,6 @@ from unittest.mock import Mock from django.conf import settings from django.urls import reverse -from datetime import date from .common import MockSESClient, completed_domain_request # type: ignore from django_webtest import WebTest # type: ignore @@ -17,7 +16,6 @@ from registrar.models import ( Contact, User, Website, - UserDomainRole, ) from registrar.views.domain_request import DomainRequestWizard, Step @@ -2328,364 +2326,3 @@ class TestWizardUnlockingSteps(TestWithUser, WebTest): else: self.fail(f"Expected a redirect, but got a different response: {response}") - - -class HomeTests(TestWithUser): - """A series of tests that target the two tables on home.html""" - - def setUp(self): - super().setUp() - self.client.force_login(self.user) - - def tearDown(self): - super().tearDown() - Contact.objects.all().delete() - - def test_home_lists_domain_requests(self): - response = self.client.get("/") - self.assertNotContains(response, "igorville.gov") - site = DraftDomain.objects.create(name="igorville.gov") - domain_request = DomainRequest.objects.create(creator=self.user, requested_domain=site) - response = self.client.get("/") - - # count = 7 because of screenreader content - self.assertContains(response, "igorville.gov", count=7) - - # clean up - domain_request.delete() - - def test_state_help_text(self): - """Tests if each domain state has help text""" - - # Get the expected text content of each state - deleted_text = "This domain has been removed and " "is no longer registered to your organization." - dns_needed_text = "Before this domain can be used, " "you’ll need to add name server addresses." - ready_text = "This domain has name servers and is ready for use." - on_hold_text = ( - "This domain is administratively paused, " - "so it can’t be edited and won’t resolve in DNS. " - "Contact help@get.gov for details." - ) - deleted_text = "This domain has been removed and " "is no longer registered to your organization." - # Generate a mapping of domain names, the state, and expected messages for the subtest - test_cases = [ - ("deleted.gov", Domain.State.DELETED, deleted_text), - ("dnsneeded.gov", Domain.State.DNS_NEEDED, dns_needed_text), - ("unknown.gov", Domain.State.UNKNOWN, dns_needed_text), - ("onhold.gov", Domain.State.ON_HOLD, on_hold_text), - ("ready.gov", Domain.State.READY, ready_text), - ] - for domain_name, state, expected_message in test_cases: - with self.subTest(domain_name=domain_name, state=state, expected_message=expected_message): - # Create a domain and a UserRole with the given params - test_domain, _ = Domain.objects.get_or_create(name=domain_name, state=state) - test_domain.expiration_date = date.today() - test_domain.save() - - user_role, _ = UserDomainRole.objects.get_or_create( - user=self.user, domain=test_domain, role=UserDomainRole.Roles.MANAGER - ) - - # Grab the home page - response = self.client.get("/") - - # Make sure the user can actually see the domain. - # We expect two instances because of SR content. - self.assertContains(response, domain_name, count=2) - - # Check that we have the right text content. - self.assertContains(response, expected_message, count=1) - - # Delete the role and domain to ensure we're testing in isolation - user_role.delete() - test_domain.delete() - - def test_state_help_text_expired(self): - """Tests if each domain state has help text when expired""" - expired_text = "This domain has expired, but it is still online. " "To renew this domain, contact help@get.gov." - test_domain, _ = Domain.objects.get_or_create(name="expired.gov", state=Domain.State.READY) - test_domain.expiration_date = date(2011, 10, 10) - test_domain.save() - - UserDomainRole.objects.get_or_create(user=self.user, domain=test_domain, role=UserDomainRole.Roles.MANAGER) - - # Grab the home page - response = self.client.get("/") - - # Make sure the user can actually see the domain. - # We expect two instances because of SR content. - self.assertContains(response, "expired.gov", count=2) - - # Check that we have the right text content. - self.assertContains(response, expired_text, count=1) - - def test_state_help_text_no_expiration_date(self): - """Tests if each domain state has help text when expiration date is None""" - - # == Test a expiration of None for state ready. This should be expired. == # - expired_text = "This domain has expired, but it is still online. " "To renew this domain, contact help@get.gov." - test_domain, _ = Domain.objects.get_or_create(name="imexpired.gov", state=Domain.State.READY) - test_domain.expiration_date = None - test_domain.save() - - UserDomainRole.objects.get_or_create(user=self.user, domain=test_domain, role=UserDomainRole.Roles.MANAGER) - - # Grab the home page - response = self.client.get("/") - - # Make sure the user can actually see the domain. - # We expect two instances because of SR content. - self.assertContains(response, "imexpired.gov", count=2) - - # Make sure the expiration date is None - self.assertEqual(test_domain.expiration_date, None) - - # Check that we have the right text content. - self.assertContains(response, expired_text, count=1) - - # == Test a expiration of None for state unknown. This should not display expired text. == # - unknown_text = "Before this domain can be used, " "you’ll need to add name server addresses." - test_domain_2, _ = Domain.objects.get_or_create(name="notexpired.gov", state=Domain.State.UNKNOWN) - test_domain_2.expiration_date = None - test_domain_2.save() - - UserDomainRole.objects.get_or_create(user=self.user, domain=test_domain_2, role=UserDomainRole.Roles.MANAGER) - - # Grab the home page - response = self.client.get("/") - - # Make sure the user can actually see the domain. - # We expect two instances because of SR content. - self.assertContains(response, "notexpired.gov", count=2) - - # Make sure the expiration date is None - self.assertEqual(test_domain_2.expiration_date, None) - - # Check that we have the right text content. - self.assertContains(response, unknown_text, count=1) - - def test_home_deletes_withdrawn_domain_request(self): - """Tests if the user can delete a DomainRequest in the 'withdrawn' status""" - - site = DraftDomain.objects.create(name="igorville.gov") - domain_request = DomainRequest.objects.create( - creator=self.user, requested_domain=site, status=DomainRequest.DomainRequestStatus.WITHDRAWN - ) - - # Ensure that igorville.gov exists on the page - home_page = self.client.get("/") - self.assertContains(home_page, "igorville.gov") - - # Check if the delete button exists. We can do this by checking for its id and text content. - self.assertContains(home_page, "Delete") - self.assertContains(home_page, "button-toggle-delete-domain-alert-1") - - # Trigger the delete logic - response = self.client.post(reverse("domain-request-delete", kwargs={"pk": domain_request.pk}), follow=True) - - self.assertNotContains(response, "igorville.gov") - - # clean up - domain_request.delete() - - def test_home_deletes_started_domain_request(self): - """Tests if the user can delete a DomainRequest in the 'started' status""" - - site = DraftDomain.objects.create(name="igorville.gov") - domain_request = DomainRequest.objects.create( - creator=self.user, requested_domain=site, status=DomainRequest.DomainRequestStatus.STARTED - ) - - # Ensure that igorville.gov exists on the page - home_page = self.client.get("/") - self.assertContains(home_page, "igorville.gov") - - # Check if the delete button exists. We can do this by checking for its id and text content. - self.assertContains(home_page, "Delete") - self.assertContains(home_page, "button-toggle-delete-domain-alert-1") - - # Trigger the delete logic - response = self.client.post(reverse("domain-request-delete", kwargs={"pk": domain_request.pk}), follow=True) - - self.assertNotContains(response, "igorville.gov") - - # clean up - domain_request.delete() - - def test_home_doesnt_delete_other_domain_requests(self): - """Tests to ensure the user can't delete domain requests not in the status of STARTED or WITHDRAWN""" - - # Given that we are including a subset of items that can be deleted while excluding the rest, - # subTest is appropriate here as otherwise we would need many duplicate tests for the same reason. - with less_console_noise(): - draft_domain = DraftDomain.objects.create(name="igorville.gov") - for status in DomainRequest.DomainRequestStatus: - if status not in [ - DomainRequest.DomainRequestStatus.STARTED, - DomainRequest.DomainRequestStatus.WITHDRAWN, - ]: - with self.subTest(status=status): - domain_request = DomainRequest.objects.create( - creator=self.user, requested_domain=draft_domain, status=status - ) - - # Trigger the delete logic - response = self.client.post( - reverse("domain-request-delete", kwargs={"pk": domain_request.pk}), follow=True - ) - - # Check for a 403 error - the end user should not be allowed to do this - self.assertEqual(response.status_code, 403) - - desired_domain_request = DomainRequest.objects.filter(requested_domain=draft_domain) - - # Make sure the DomainRequest wasn't deleted - self.assertEqual(desired_domain_request.count(), 1) - - # clean up - domain_request.delete() - - def test_home_deletes_domain_request_and_orphans(self): - """Tests if delete for DomainRequest deletes orphaned Contact objects""" - - # Create the site and contacts to delete (orphaned) - contact = Contact.objects.create( - first_name="Henry", - last_name="Mcfakerson", - ) - contact_shared = Contact.objects.create( - first_name="Relative", - last_name="Aether", - ) - - # Create two non-orphaned contacts - contact_2 = Contact.objects.create( - first_name="Saturn", - last_name="Mars", - ) - - # Attach a user object to a contact (should not be deleted) - contact_user, _ = Contact.objects.get_or_create(user=self.user) - - site = DraftDomain.objects.create(name="igorville.gov") - domain_request = DomainRequest.objects.create( - creator=self.user, - requested_domain=site, - status=DomainRequest.DomainRequestStatus.WITHDRAWN, - authorizing_official=contact, - submitter=contact_user, - ) - domain_request.other_contacts.set([contact_2]) - - # Create a second domain request to attach contacts to - site_2 = DraftDomain.objects.create(name="teaville.gov") - domain_request_2 = DomainRequest.objects.create( - creator=self.user, - requested_domain=site_2, - status=DomainRequest.DomainRequestStatus.STARTED, - authorizing_official=contact_2, - submitter=contact_shared, - ) - domain_request_2.other_contacts.set([contact_shared]) - - # Ensure that igorville.gov exists on the page - home_page = self.client.get("/") - self.assertContains(home_page, "igorville.gov") - - # Trigger the delete logic - response = self.client.post(reverse("domain-request-delete", kwargs={"pk": domain_request.pk}), follow=True) - - # igorville is now deleted - self.assertNotContains(response, "igorville.gov") - - # Check if the orphaned contact was deleted - orphan = Contact.objects.filter(id=contact.id) - self.assertFalse(orphan.exists()) - - # All non-orphan contacts should still exist and are unaltered - try: - current_user = Contact.objects.filter(id=contact_user.id).get() - except Contact.DoesNotExist: - self.fail("contact_user (a non-orphaned contact) was deleted") - - self.assertEqual(current_user, contact_user) - try: - edge_case = Contact.objects.filter(id=contact_2.id).get() - except Contact.DoesNotExist: - self.fail("contact_2 (a non-orphaned contact) was deleted") - - self.assertEqual(edge_case, contact_2) - - def test_home_deletes_domain_request_and_shared_orphans(self): - """Test the edge case for an object that will become orphaned after a delete - (but is not an orphan at the time of deletion)""" - - # Create the site and contacts to delete (orphaned) - contact = Contact.objects.create( - first_name="Henry", - last_name="Mcfakerson", - ) - contact_shared = Contact.objects.create( - first_name="Relative", - last_name="Aether", - ) - - # Create two non-orphaned contacts - contact_2 = Contact.objects.create( - first_name="Saturn", - last_name="Mars", - ) - - # Attach a user object to a contact (should not be deleted) - contact_user, _ = Contact.objects.get_or_create(user=self.user) - - site = DraftDomain.objects.create(name="igorville.gov") - domain_request = DomainRequest.objects.create( - creator=self.user, - requested_domain=site, - status=DomainRequest.DomainRequestStatus.WITHDRAWN, - authorizing_official=contact, - submitter=contact_user, - ) - domain_request.other_contacts.set([contact_2]) - - # Create a second domain request to attach contacts to - site_2 = DraftDomain.objects.create(name="teaville.gov") - domain_request_2 = DomainRequest.objects.create( - creator=self.user, - requested_domain=site_2, - status=DomainRequest.DomainRequestStatus.STARTED, - authorizing_official=contact_2, - submitter=contact_shared, - ) - domain_request_2.other_contacts.set([contact_shared]) - - home_page = self.client.get("/") - self.assertContains(home_page, "teaville.gov") - - # Trigger the delete logic - response = self.client.post(reverse("domain-request-delete", kwargs={"pk": domain_request_2.pk}), follow=True) - - self.assertNotContains(response, "teaville.gov") - - # Check if the orphaned contact was deleted - orphan = Contact.objects.filter(id=contact_shared.id) - self.assertFalse(orphan.exists()) - - def test_domain_request_form_view(self): - response = self.client.get("/request/", follow=True) - self.assertContains( - response, - "You’re about to start your .gov domain request.", - ) - - def test_domain_request_form_with_ineligible_user(self): - """Domain request form not accessible for an ineligible user. - This test should be solid enough since all domain request wizard - views share the same permissions class""" - self.user.status = User.RESTRICTED - self.user.save() - - with less_console_noise(): - response = self.client.get("/request/", follow=True) - self.assertEqual(response.status_code, 403) From e45ea48898a44e918ddd1ee2e23e0394597dc141 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Tue, 23 Apr 2024 17:57:41 -0400 Subject: [PATCH 02/11] cleanup --- src/Pipfile | 2 +- src/Pipfile.lock | 218 +++++++++++------------ src/registrar/config/settings.py | 7 + src/registrar/tests/test_views_domain.py | 2 - src/requirements.txt | 10 +- 5 files changed, 122 insertions(+), 117 deletions(-) diff --git a/src/Pipfile b/src/Pipfile index 1565af79b..5bab9cc95 100644 --- a/src/Pipfile +++ b/src/Pipfile @@ -9,7 +9,7 @@ cfenv = "*" django-cors-headers = "*" pycryptodomex = "*" django-allow-cidr = "*" -django-auditlog = "2.3.0" +django-auditlog = "*" django-csp = "*" environs = {extras=["django"]} Faker = "*" diff --git a/src/Pipfile.lock b/src/Pipfile.lock index 5940f455e..117290daa 100644 --- a/src/Pipfile.lock +++ b/src/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "ce10883aef7e1ce10421d99b3ac35ebf419857a3fe468f0e2d93785f4323eaa8" + "sha256": "16a0db98015509322cf1d27f06fced5b7635057c4eb98921a9419d63d51925ab" }, "pipfile-spec": 6, "requires": {}, @@ -32,20 +32,20 @@ }, "boto3": { "hashes": [ - "sha256:168894499578a9d69d6f7deb5811952bf4171c51b95749a9aef32cf67bc71f87", - "sha256:1bd4cef11b7c5f293cede50f3d33ca89fe3413c51f1864f40163c56a732dd6b3" + "sha256:2824e3dd18743ca50e5b10439d20e74647b1416e8a94509cb30beac92d27a18d", + "sha256:b2e5cb5b95efcc881e25a3bc872d7a24e75ff4e76f368138e4baf7b9d6ee3422" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.34.88" + "version": "==1.34.90" }, "botocore": { "hashes": [ - "sha256:36f2e9e8dfa856e55dbbe703aea601f134db3fddc3615f1020a755b27fd26a5e", - "sha256:e87a660599ed3e14b2a770f4efc3df2f2f6d04f3c7bfd64ddbae186667864a7b" + "sha256:113cd4c0cb63e13163ccbc2bb13d551be314ba7f8ba5bfab1c51a19ca01aa133", + "sha256:d48f152498e2c60b43ce25b579d26642346a327b6fb2c632d57219e0a4f63392" ], "markers": "python_version >= '3.8'", - "version": "==1.34.88" + "version": "==1.34.90" }, "cachetools": { "hashes": [ @@ -313,12 +313,12 @@ }, "django-auditlog": { "hashes": [ - "sha256:7bc2c87e4aff62dec9785d1b2359a2b27148f8c286f8a52b9114fc7876c5a9f7", - "sha256:b9d3acebb64f3f2785157efe3f2f802e0929aafc579d85bbfb9827db4adab532" + "sha256:92db1cf4a51ceca5c26b3ff46997d9e3305a02da1bd435e2efb5b8b6d300ce1f", + "sha256:9de49f80a4911135d136017123cd73461f869b4947eec14d5e76db4b88182f3f" ], "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==2.3.0" + "markers": "python_version >= '3.8'", + "version": "==3.0.0" }, "django-cache-url": { "hashes": [ @@ -958,96 +958,96 @@ }, "pydantic": { "hashes": [ - "sha256:9dee74a271705f14f9a1567671d144a851c675b072736f0a7b2608fd9e495352", - "sha256:b5ecdd42262ca2462e2624793551e80911a1e989f462910bb81aef974b4bb383" + "sha256:e029badca45266732a9a79898a15ae2e8b14840b1eabbb25844be28f0b33f3d5", + "sha256:e9dbb5eada8abe4d9ae5f46b9939aead650cd2b68f249bb3a8139dbe125803cc" ], "markers": "python_version >= '3.8'", - "version": "==2.7.0" + "version": "==2.7.1" }, "pydantic-core": { "hashes": [ - "sha256:030e4f9516f9947f38179249778709a460a3adb516bf39b5eb9066fcfe43d0e6", - "sha256:09f03dfc0ef8c22622eaa8608caa4a1e189cfb83ce847045eca34f690895eccb", - "sha256:12a05db5013ec0ca4a32cc6433f53faa2a014ec364031408540ba858c2172bb0", - "sha256:14fe73881cf8e4cbdaded8ca0aa671635b597e42447fec7060d0868b52d074e6", - "sha256:1a0c3e718f4e064efde68092d9d974e39572c14e56726ecfaeebbe6544521f47", - "sha256:1be91ad664fc9245404a789d60cba1e91c26b1454ba136d2a1bf0c2ac0c0505a", - "sha256:201713f2f462e5c015b343e86e68bd8a530a4f76609b33d8f0ec65d2b921712a", - "sha256:2027493cc44c23b598cfaf200936110433d9caa84e2c6cf487a83999638a96ac", - "sha256:250ae39445cb5475e483a36b1061af1bc233de3e9ad0f4f76a71b66231b07f88", - "sha256:2533ad2883f001efa72f3d0e733fb846710c3af6dcdd544fe5bf14fa5fe2d7db", - "sha256:25595ac311f20e5324d1941909b0d12933f1fd2171075fcff763e90f43e92a0d", - "sha256:2684a94fdfd1b146ff10689c6e4e815f6a01141781c493b97342cdc5b06f4d5d", - "sha256:27f1009dc292f3b7ca77feb3571c537276b9aad5dd4efb471ac88a8bd09024e9", - "sha256:2adaeea59849ec0939af5c5d476935f2bab4b7f0335b0110f0f069a41024278e", - "sha256:2ae80f72bb7a3e397ab37b53a2b49c62cc5496412e71bc4f1277620a7ce3f52b", - "sha256:2d5728e93d28a3c63ee513d9ffbac9c5989de8c76e049dbcb5bfe4b923a9739d", - "sha256:2e91711e36e229978d92642bfc3546333a9127ecebb3f2761372e096395fc649", - "sha256:2fe0c1ce5b129455e43f941f7a46f61f3d3861e571f2905d55cdbb8b5c6f5e2c", - "sha256:38a5024de321d672a132b1834a66eeb7931959c59964b777e8f32dbe9523f6b1", - "sha256:3e352f0191d99fe617371096845070dee295444979efb8f27ad941227de6ad09", - "sha256:48dd883db92e92519201f2b01cafa881e5f7125666141a49ffba8b9facc072b0", - "sha256:54764c083bbe0264f0f746cefcded6cb08fbbaaf1ad1d78fb8a4c30cff999a90", - "sha256:54c7375c62190a7845091f521add19b0f026bcf6ae674bdb89f296972272e86d", - "sha256:561cf62c8a3498406495cfc49eee086ed2bb186d08bcc65812b75fda42c38294", - "sha256:56823a92075780582d1ffd4489a2e61d56fd3ebb4b40b713d63f96dd92d28144", - "sha256:582cf2cead97c9e382a7f4d3b744cf0ef1a6e815e44d3aa81af3ad98762f5a9b", - "sha256:58aca931bef83217fca7a390e0486ae327c4af9c3e941adb75f8772f8eeb03a1", - "sha256:5f7973c381283783cd1043a8c8f61ea5ce7a3a58b0369f0ee0ee975eaf2f2a1b", - "sha256:6395a4435fa26519fd96fdccb77e9d00ddae9dd6c742309bd0b5610609ad7fb2", - "sha256:63d7523cd95d2fde0d28dc42968ac731b5bb1e516cc56b93a50ab293f4daeaad", - "sha256:641a018af4fe48be57a2b3d7a1f0f5dbca07c1d00951d3d7463f0ac9dac66622", - "sha256:667880321e916a8920ef49f5d50e7983792cf59f3b6079f3c9dac2b88a311d17", - "sha256:684d840d2c9ec5de9cb397fcb3f36d5ebb6fa0d94734f9886032dd796c1ead06", - "sha256:68717c38a68e37af87c4da20e08f3e27d7e4212e99e96c3d875fbf3f4812abfc", - "sha256:6b7bbb97d82659ac8b37450c60ff2e9f97e4eb0f8a8a3645a5568b9334b08b50", - "sha256:72722ce529a76a4637a60be18bd789d8fb871e84472490ed7ddff62d5fed620d", - "sha256:73c1bc8a86a5c9e8721a088df234265317692d0b5cd9e86e975ce3bc3db62a59", - "sha256:76909849d1a6bffa5a07742294f3fa1d357dc917cb1fe7b470afbc3a7579d539", - "sha256:76b86e24039c35280ceee6dce7e62945eb93a5175d43689ba98360ab31eebc4a", - "sha256:7a5d83efc109ceddb99abd2c1316298ced2adb4570410defe766851a804fcd5b", - "sha256:80e0e57cc704a52fb1b48f16d5b2c8818da087dbee6f98d9bf19546930dc64b5", - "sha256:85233abb44bc18d16e72dc05bf13848a36f363f83757541f1a97db2f8d58cfd9", - "sha256:907a4d7720abfcb1c81619863efd47c8a85d26a257a2dbebdb87c3b847df0278", - "sha256:9376d83d686ec62e8b19c0ac3bf8d28d8a5981d0df290196fb6ef24d8a26f0d6", - "sha256:94b9769ba435b598b547c762184bcfc4783d0d4c7771b04a3b45775c3589ca44", - "sha256:9a29726f91c6cb390b3c2338f0df5cd3e216ad7a938762d11c994bb37552edb0", - "sha256:9b6431559676a1079eac0f52d6d0721fb8e3c5ba43c37bc537c8c83724031feb", - "sha256:9ece8a49696669d483d206b4474c367852c44815fca23ac4e48b72b339807f80", - "sha256:a139fe9f298dc097349fb4f28c8b81cc7a202dbfba66af0e14be5cfca4ef7ce5", - "sha256:a32204489259786a923e02990249c65b0f17235073149d0033efcebe80095570", - "sha256:a3982b0a32d0a88b3907e4b0dc36809fda477f0757c59a505d4e9b455f384b8b", - "sha256:aad17e462f42ddbef5984d70c40bfc4146c322a2da79715932cd8976317054de", - "sha256:b560b72ed4816aee52783c66854d96157fd8175631f01ef58e894cc57c84f0f6", - "sha256:b6b0e4912030c6f28bcb72b9ebe4989d6dc2eebcd2a9cdc35fefc38052dd4fe8", - "sha256:baf1c7b78cddb5af00971ad5294a4583188bda1495b13760d9f03c9483bb6203", - "sha256:c0295d52b012cbe0d3059b1dba99159c3be55e632aae1999ab74ae2bd86a33d7", - "sha256:c562b49c96906b4029b5685075fe1ebd3b5cc2601dfa0b9e16c2c09d6cbce048", - "sha256:c69567ddbac186e8c0aadc1f324a60a564cfe25e43ef2ce81bcc4b8c3abffbae", - "sha256:ca71d501629d1fa50ea7fa3b08ba884fe10cefc559f5c6c8dfe9036c16e8ae89", - "sha256:ca976884ce34070799e4dfc6fbd68cb1d181db1eefe4a3a94798ddfb34b8867f", - "sha256:d0491006a6ad20507aec2be72e7831a42efc93193d2402018007ff827dc62926", - "sha256:d074b07a10c391fc5bbdcb37b2f16f20fcd9e51e10d01652ab298c0d07908ee2", - "sha256:d2ce426ee691319d4767748c8e0895cfc56593d725594e415f274059bcf3cb76", - "sha256:d4284c621f06a72ce2cb55f74ea3150113d926a6eb78ab38340c08f770eb9b4d", - "sha256:d5e6b7155b8197b329dc787356cfd2684c9d6a6b1a197f6bbf45f5555a98d411", - "sha256:d816f44a51ba5175394bc6c7879ca0bd2be560b2c9e9f3411ef3a4cbe644c2e9", - "sha256:dd3f79e17b56741b5177bcc36307750d50ea0698df6aa82f69c7db32d968c1c2", - "sha256:dd63cec4e26e790b70544ae5cc48d11b515b09e05fdd5eff12e3195f54b8a586", - "sha256:de9d3e8717560eb05e28739d1b35e4eac2e458553a52a301e51352a7ffc86a35", - "sha256:df4249b579e75094f7e9bb4bd28231acf55e308bf686b952f43100a5a0be394c", - "sha256:e178e5b66a06ec5bf51668ec0d4ac8cfb2bdcb553b2c207d58148340efd00143", - "sha256:e60defc3c15defb70bb38dd605ff7e0fae5f6c9c7cbfe0ad7868582cb7e844a6", - "sha256:ee2794111c188548a4547eccc73a6a8527fe2af6cf25e1a4ebda2fd01cdd2e60", - "sha256:ee7ccc7fb7e921d767f853b47814c3048c7de536663e82fbc37f5eb0d532224b", - "sha256:ee9cf33e7fe14243f5ca6977658eb7d1042caaa66847daacbd2117adb258b226", - "sha256:f0f17814c505f07806e22b28856c59ac80cee7dd0fbb152aed273e116378f519", - "sha256:f3202a429fe825b699c57892d4371c74cc3456d8d71b7f35d6028c96dfecad31", - "sha256:f7054fdc556f5421f01e39cbb767d5ec5c1139ea98c3e5b350e02e62201740c7", - "sha256:fd1a9edb9dd9d79fbeac1ea1f9a8dd527a6113b18d2e9bcc0d541d308dae639b" + "sha256:0098300eebb1c837271d3d1a2cd2911e7c11b396eac9661655ee524a7f10587b", + "sha256:042473b6280246b1dbf530559246f6842b56119c2926d1e52b631bdc46075f2a", + "sha256:05b7133a6e6aeb8df37d6f413f7705a37ab4031597f64ab56384c94d98fa0e90", + "sha256:0680b1f1f11fda801397de52c36ce38ef1c1dc841a0927a94f226dea29c3ae3d", + "sha256:0d69b4c2f6bb3e130dba60d34c0845ba31b69babdd3f78f7c0c8fae5021a253e", + "sha256:1404c69d6a676245199767ba4f633cce5f4ad4181f9d0ccb0577e1f66cf4c46d", + "sha256:182245ff6b0039e82b6bb585ed55a64d7c81c560715d1bad0cbad6dfa07b4027", + "sha256:1a388a77e629b9ec814c1b1e6b3b595fe521d2cdc625fcca26fbc2d44c816804", + "sha256:1d90c3265ae107f91a4f279f4d6f6f1d4907ac76c6868b27dc7fb33688cfb347", + "sha256:20aca1e2298c56ececfd8ed159ae4dde2df0781988c97ef77d5c16ff4bd5b400", + "sha256:219da3f096d50a157f33645a1cf31c0ad1fe829a92181dd1311022f986e5fbe3", + "sha256:22057013c8c1e272eb8d0eebc796701167d8377441ec894a8fed1af64a0bf399", + "sha256:223ee893d77a310a0391dca6df00f70bbc2f36a71a895cecd9a0e762dc37b349", + "sha256:224c421235f6102e8737032483f43c1a8cfb1d2f45740c44166219599358c2cd", + "sha256:2334ce8c673ee93a1d6a65bd90327588387ba073c17e61bf19b4fd97d688d63c", + "sha256:269322dcc3d8bdb69f054681edff86276b2ff972447863cf34c8b860f5188e2e", + "sha256:2728b01246a3bba6de144f9e3115b532ee44bd6cf39795194fb75491824a1413", + "sha256:2b8ed04b3582771764538f7ee7001b02e1170223cf9b75dff0bc698fadb00cf3", + "sha256:2e29d20810dfc3043ee13ac7d9e25105799817683348823f305ab3f349b9386e", + "sha256:36789b70d613fbac0a25bb07ab3d9dba4d2e38af609c020cf4d888d165ee0bf3", + "sha256:390193c770399861d8df9670fb0d1874f330c79caaca4642332df7c682bf6b91", + "sha256:3a6515ebc6e69d85502b4951d89131ca4e036078ea35533bb76327f8424531ce", + "sha256:3f9a801e7c8f1ef8718da265bba008fa121243dfe37c1cea17840b0944dfd72c", + "sha256:43f0f463cf89ace478de71a318b1b4f05ebc456a9b9300d027b4b57c1a2064fb", + "sha256:4456f2dca97c425231d7315737d45239b2b51a50dc2b6f0c2bb181fce6207664", + "sha256:470b94480bb5ee929f5acba6995251ada5e059a5ef3e0dfc63cca287283ebfa6", + "sha256:4774f3184d2ef3e14e8693194f661dea5a4d6ca4e3dc8e39786d33a94865cefd", + "sha256:4b4356d3538c3649337df4074e81b85f0616b79731fe22dd11b99499b2ebbdf3", + "sha256:553ef617b6836fc7e4df130bb851e32fe357ce36336d897fd6646d6058d980af", + "sha256:6132dd3bd52838acddca05a72aafb6eab6536aa145e923bb50f45e78b7251043", + "sha256:6a46e22a707e7ad4484ac9ee9f290f9d501df45954184e23fc29408dfad61350", + "sha256:6e5c584d357c4e2baf0ff7baf44f4994be121e16a2c88918a5817331fc7599d7", + "sha256:75250dbc5290e3f1a0f4618db35e51a165186f9034eff158f3d490b3fed9f8a0", + "sha256:75f7e9488238e920ab6204399ded280dc4c307d034f3924cd7f90a38b1829563", + "sha256:78363590ef93d5d226ba21a90a03ea89a20738ee5b7da83d771d283fd8a56761", + "sha256:7ca4ae5a27ad7a4ee5170aebce1574b375de390bc01284f87b18d43a3984df72", + "sha256:800d60565aec896f25bc3cfa56d2277d52d5182af08162f7954f938c06dc4ee3", + "sha256:82d5d4d78e4448683cb467897fe24e2b74bb7b973a541ea1dcfec1d3cbce39fb", + "sha256:852e966fbd035a6468fc0a3496589b45e2208ec7ca95c26470a54daed82a0788", + "sha256:868649da93e5a3d5eacc2b5b3b9235c98ccdbfd443832f31e075f54419e1b96b", + "sha256:886eec03591b7cf058467a70a87733b35f44707bd86cf64a615584fd72488b7c", + "sha256:8b172601454f2d7701121bbec3425dd71efcb787a027edf49724c9cefc14c038", + "sha256:95b9d5e72481d3780ba3442eac863eae92ae43a5f3adb5b4d0a1de89d42bb250", + "sha256:98758d627ff397e752bc339272c14c98199c613f922d4a384ddc07526c86a2ec", + "sha256:997abc4df705d1295a42f95b4eec4950a37ad8ae46d913caeee117b6b198811c", + "sha256:9b5155ff768083cb1d62f3e143b49a8a3432e6789a3abee8acd005c3c7af1c74", + "sha256:9e08e867b306f525802df7cd16c44ff5ebbe747ff0ca6cf3fde7f36c05a59a81", + "sha256:9fdad8e35f278b2c3eb77cbdc5c0a49dada440657bf738d6905ce106dc1de439", + "sha256:a1874c6dd4113308bd0eb568418e6114b252afe44319ead2b4081e9b9521fe75", + "sha256:a8309f67285bdfe65c372ea3722b7a5642680f3dba538566340a9d36e920b5f0", + "sha256:ae0a8a797a5e56c053610fa7be147993fe50960fa43609ff2a9552b0e07013e8", + "sha256:b14d82cdb934e99dda6d9d60dc84a24379820176cc4a0d123f88df319ae9c150", + "sha256:b1bd7e47b1558ea872bd16c8502c414f9e90dcf12f1395129d7bb42a09a95438", + "sha256:b3ef08e20ec49e02d5c6717a91bb5af9b20f1805583cb0adfe9ba2c6b505b5ae", + "sha256:b89ed9eb7d616ef5714e5590e6cf7f23b02d0d539767d33561e3675d6f9e3857", + "sha256:c4fcf5cd9c4b655ad666ca332b9a081112cd7a58a8b5a6ca7a3104bc950f2038", + "sha256:c6fdc8627910eed0c01aed6a390a252fe3ea6d472ee70fdde56273f198938374", + "sha256:c9bd70772c720142be1020eac55f8143a34ec9f82d75a8e7a07852023e46617f", + "sha256:ca7b0c1f1c983e064caa85f3792dd2fe3526b3505378874afa84baf662e12241", + "sha256:cbca948f2d14b09d20268cda7b0367723d79063f26c4ffc523af9042cad95592", + "sha256:cc1cfd88a64e012b74e94cd00bbe0f9c6df57049c97f02bb07d39e9c852e19a4", + "sha256:ccdd111c03bfd3666bd2472b674c6899550e09e9f298954cfc896ab92b5b0e6d", + "sha256:cfeecd1ac6cc1fb2692c3d5110781c965aabd4ec5d32799773ca7b1456ac636b", + "sha256:d4d938ec0adf5167cb335acb25a4ee69a8107e4984f8fbd2e897021d9e4ca21b", + "sha256:d7d904828195733c183d20a54230c0df0eb46ec746ea1a666730787353e87182", + "sha256:d91cb5ea8b11607cc757675051f61b3d93f15eca3cefb3e6c704a5d6e8440f4e", + "sha256:d9319e499827271b09b4e411905b24a426b8fb69464dfa1696258f53a3334641", + "sha256:e0e8b1be28239fc64a88a8189d1df7fad8be8c1ae47fcc33e43d4be15f99cc70", + "sha256:e18609ceaa6eed63753037fc06ebb16041d17d28199ae5aba0052c51449650a9", + "sha256:e1b395e58b10b73b07b7cf740d728dd4ff9365ac46c18751bf8b3d8cca8f625a", + "sha256:e23ec367a948b6d812301afc1b13f8094ab7b2c280af66ef450efc357d2ae543", + "sha256:e25add29b8f3b233ae90ccef2d902d0ae0432eb0d45370fe315d1a5cf231004b", + "sha256:e6dac87ddb34aaec85f873d737e9d06a3555a1cc1a8e0c44b7f8d5daeb89d86f", + "sha256:ef26c9e94a8c04a1b2924149a9cb081836913818e55681722d7f29af88fe7b38", + "sha256:eff2de745698eb46eeb51193a9f41d67d834d50e424aef27df2fcdee1b153845", + "sha256:f0a21cbaa69900cbe1a2e7cad2aa74ac3cf21b10c3efb0fa0b80305274c0e8a2", + "sha256:f459a5ce8434614dfd39bbebf1041952ae01da6bed9855008cb33b875cb024c0", + "sha256:f93a8a2e3938ff656a7c1bc57193b1319960ac015b6e87d76c76bf14fe0244b4", + "sha256:fb2bd7be70c0fe4dfd32c951bc813d9fe6ebcbfdd15a07527796c8204bd36242" ], "markers": "python_version >= '3.8'", - "version": "==2.18.1" + "version": "==2.18.2" }, "pydantic-settings": { "hashes": [ @@ -1281,12 +1281,12 @@ }, "boto3": { "hashes": [ - "sha256:168894499578a9d69d6f7deb5811952bf4171c51b95749a9aef32cf67bc71f87", - "sha256:1bd4cef11b7c5f293cede50f3d33ca89fe3413c51f1864f40163c56a732dd6b3" + "sha256:2824e3dd18743ca50e5b10439d20e74647b1416e8a94509cb30beac92d27a18d", + "sha256:b2e5cb5b95efcc881e25a3bc872d7a24e75ff4e76f368138e4baf7b9d6ee3422" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.34.88" + "version": "==1.34.90" }, "boto3-mocking": { "hashes": [ @@ -1299,28 +1299,28 @@ }, "boto3-stubs": { "hashes": [ - "sha256:23ca9e0cd0d3e7702d6631a1e94a4208a26b39fa6b12c734427e68a7fa649477", - "sha256:8f472d1bf09743c3d33304ecc8830d70ebe3ca19ac9604ae8da9af55421b0fce" + "sha256:7361f162523168ddcfb3e0cc70e5208e78f95b9f1f2553032036a2b67ab33355", + "sha256:c82f3db8558e28f766361ba1eea7c77dff735f72fef2a0b9dffaa9c0d9ae76a3" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.34.88" + "version": "==1.34.90" }, "botocore": { "hashes": [ - "sha256:36f2e9e8dfa856e55dbbe703aea601f134db3fddc3615f1020a755b27fd26a5e", - "sha256:e87a660599ed3e14b2a770f4efc3df2f2f6d04f3c7bfd64ddbae186667864a7b" + "sha256:113cd4c0cb63e13163ccbc2bb13d551be314ba7f8ba5bfab1c51a19ca01aa133", + "sha256:d48f152498e2c60b43ce25b579d26642346a327b6fb2c632d57219e0a4f63392" ], "markers": "python_version >= '3.8'", - "version": "==1.34.88" + "version": "==1.34.90" }, "botocore-stubs": { "hashes": [ - "sha256:656e966ea152a4f2828892aa7a9673bc91799998f5a8efd8e8fe390f61c2f4f1", - "sha256:f55b03ae2e1706bd56299fd2975bb048f96aa49012a866e931a040a74f85c3cc" + "sha256:b2d7416b524bce7325aa5fe09bb5e0b6bc9531d4136f4407fa39b6bc58507f34", + "sha256:d9b66542cbb8fbe28eef3c22caf941ae22d36cc1ef55b93fc0b52239457cab57" ], "markers": "python_version >= '3.8' and python_version < '4.0'", - "version": "==1.34.88" + "version": "==1.34.89" }, "click": { "hashes": [ @@ -1497,11 +1497,11 @@ }, "platformdirs": { "hashes": [ - "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", - "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768" + "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf", + "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1" ], "markers": "python_version >= '3.8'", - "version": "==4.2.0" + "version": "==4.2.1" }, "pycodestyle": { "hashes": [ diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index 54b65e83e..cef2ec59d 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -780,3 +780,10 @@ if DEBUG: # due to Docker, bypass Debug Toolbar's check on INTERNAL_IPS "SHOW_TOOLBAR_CALLBACK": lambda _: True, } + +# From https://django-auditlog.readthedocs.io/en/latest/upgrade.html +# Run cf run-task getgov-<> --wait --command 'python manage.py auditlogmigratejson --traceback' --name auditlogmigratejson +# on our staging and stable, then remove these 2 variables or set to False +AUDITLOG_TWO_STEP_MIGRATION = True + +AUDITLOG_USE_TEXT_CHANGES_IF_JSON_IS_NOT_PRESENT = True diff --git a/src/registrar/tests/test_views_domain.py b/src/registrar/tests/test_views_domain.py index fc391f8b5..6448e91e1 100644 --- a/src/registrar/tests/test_views_domain.py +++ b/src/registrar/tests/test_views_domain.py @@ -344,8 +344,6 @@ class TestDomainManagers(TestDomainOverview): def tearDown(self): """Ensure that the user has its original permissions""" super().tearDown() - self.user.is_staff = False - self.user.save() def test_domain_managers(self): response = self.client.get(reverse("domain-users", kwargs={"pk": self.domain.id})) diff --git a/src/requirements.txt b/src/requirements.txt index 3c005f162..017d42542 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -1,8 +1,8 @@ -i https://pypi.python.org/simple annotated-types==0.6.0; python_version >= '3.8' asgiref==3.8.1; python_version >= '3.8' -boto3==1.34.88; python_version >= '3.8' -botocore==1.34.88; python_version >= '3.8' +boto3==1.34.90; python_version >= '3.8' +botocore==1.34.90; python_version >= '3.8' cachetools==5.3.3; python_version >= '3.7' certifi==2024.2.2; python_version >= '3.6' cfenv==0.5.3 @@ -15,7 +15,7 @@ dj-email-url==1.0.6 django==4.2.10; python_version >= '3.8' django-admin-multiple-choice-list-filter==0.1.1 django-allow-cidr==0.7.1 -django-auditlog==2.3.0; python_version >= '3.7' +django-auditlog==3.0.0; python_version >= '3.8' django-cache-url==3.4.5 django-cors-headers==4.3.1; python_version >= '3.8' django-csp==3.8 @@ -44,8 +44,8 @@ phonenumberslite==8.13.35 psycopg2-binary==2.9.9; python_version >= '3.7' pycparser==2.22; python_version >= '3.8' pycryptodomex==3.20.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' -pydantic==2.7.0; python_version >= '3.8' -pydantic-core==2.18.1; python_version >= '3.8' +pydantic==2.7.1; python_version >= '3.8' +pydantic-core==2.18.2; python_version >= '3.8' pydantic-settings==2.2.1; python_version >= '3.8' pyjwkest==1.4.2 python-dateutil==2.9.0.post0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' From ab1a63ee9c82b0762813644a2a49b4c21833918d Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Tue, 23 Apr 2024 18:07:43 -0400 Subject: [PATCH 03/11] lint --- src/registrar/config/settings.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index cef2ec59d..9817476bb 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -782,7 +782,8 @@ if DEBUG: } # From https://django-auditlog.readthedocs.io/en/latest/upgrade.html -# Run cf run-task getgov-<> --wait --command 'python manage.py auditlogmigratejson --traceback' --name auditlogmigratejson +# Run: +# cf run-task getgov-<> --wait --command 'python manage.py auditlogmigratejson --traceback' --name auditlogmigratejson # on our staging and stable, then remove these 2 variables or set to False AUDITLOG_TWO_STEP_MIGRATION = True From f754dfcfad44959df3680983c91df36aff5c70d2 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Wed, 24 Apr 2024 13:15:07 -0400 Subject: [PATCH 04/11] Fix icon --- src/registrar/templates/home.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index 6254fc3bb..14dc7b3e2 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -101,7 +101,7 @@

Why don't I see my domain when I sign in to the registrar? From 3d8e99cf5bae540876b2598e02f10a0c3f621da3 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Wed, 24 Apr 2024 16:36:41 -0400 Subject: [PATCH 05/11] tweaks --- src/registrar/assets/sass/_theme/_links.scss | 10 +++++----- src/registrar/templates/home.html | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_links.scss b/src/registrar/assets/sass/_theme/_links.scss index aa5a85f01..1d51014c5 100644 --- a/src/registrar/assets/sass/_theme/_links.scss +++ b/src/registrar/assets/sass/_theme/_links.scss @@ -5,11 +5,7 @@ p, .dotgov-table { display: flex; align-items: flex-start; color: color('primary'); - - &:visited { - color: color('primary'); - } - + .usa-icon { // align icon with x height margin-top: units(0.5); @@ -17,3 +13,7 @@ p, .dotgov-table { } } } + +.dotgov-table a:visited { + color: color('primary'); +} diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index 14dc7b3e2..fd727963b 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -99,7 +99,7 @@ {% else %}

You don't have any registered domains.

- + From aef8e818afc529133ea6982d3c4ba8fefd0bfcb2 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Wed, 24 Apr 2024 16:56:12 -0400 Subject: [PATCH 06/11] Cleanup --- src/registrar/assets/sass/_theme/_links.scss | 20 +++++++++++++------- src/registrar/templates/home.html | 2 +- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_links.scss b/src/registrar/assets/sass/_theme/_links.scss index 1d51014c5..0a3321be0 100644 --- a/src/registrar/assets/sass/_theme/_links.scss +++ b/src/registrar/assets/sass/_theme/_links.scss @@ -1,19 +1,25 @@ @use "uswds-core" as *; -p, .dotgov-table { +.dotgov-table { a { display: flex; align-items: flex-start; color: color('primary'); - .usa-icon { - // align icon with x height - margin-top: units(0.5); - margin-right: units(0.5); + &:visited { + color: color('primary'); } } } -.dotgov-table a:visited { - color: color('primary'); +a { + + // display: flex; + // align-items: flex-start; + + .usa-icon { + // align icon with x height + margin-top: units(0.5); + margin-right: units(0.5); + } } diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index fd727963b..7d73ba473 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -99,7 +99,7 @@ {% else %}

You don't have any registered domains.

- + From 13a4782688cf4d51a1b9b069c17c36e6fca2a0d0 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Wed, 24 Apr 2024 17:01:00 -0400 Subject: [PATCH 07/11] Cleanp --- src/registrar/assets/sass/_theme/_links.scss | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_links.scss b/src/registrar/assets/sass/_theme/_links.scss index 0a3321be0..6d2e75a68 100644 --- a/src/registrar/assets/sass/_theme/_links.scss +++ b/src/registrar/assets/sass/_theme/_links.scss @@ -13,10 +13,6 @@ } a { - - // display: flex; - // align-items: flex-start; - .usa-icon { // align icon with x height margin-top: units(0.5); From a92acc5edbbf83da704e596523183bb5444e6c1a Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 26 Apr 2024 12:05:44 -0400 Subject: [PATCH 08/11] override h2s with h3s for multiselects --- src/registrar/assets/js/get-gov-admin.js | 231 +++---------------- src/registrar/assets/sass/_theme/_admin.scss | 22 ++ 2 files changed, 57 insertions(+), 196 deletions(-) diff --git a/src/registrar/assets/js/get-gov-admin.js b/src/registrar/assets/js/get-gov-admin.js index 126ab0a2a..17662a367 100644 --- a/src/registrar/assets/js/get-gov-admin.js +++ b/src/registrar/assets/js/get-gov-admin.js @@ -233,10 +233,8 @@ function openInNewTab(el, removeAttribute = false){ // Initialize custom filter_horizontal widgets; each widget has a "from" select list // and a "to" select list; initialization is based off of the presence of the // "to" select list - checkToListThenInitWidget('id_other_contacts_to', 0); - checkToListThenInitWidget('id_domain_info-0-other_contacts_to', 0); - checkToListThenInitWidget('id_current_websites_to', 0); - checkToListThenInitWidget('id_alternative_domains_to', 0); + checkToListThenInitWidget('id_groups_to', 0); + // checkToListThenInitWidget('id_user_permissions_to', 0); })(); // Function to check for the existence of the "to" select list element in the DOM, and if and when found, @@ -245,215 +243,56 @@ function checkToListThenInitWidget(toListId, attempts) { let toList = document.getElementById(toListId); attempts++; - if (attempts < 6) { - if ((toList !== null)) { + if (attempts < 12) { + if ((toList)) { // toList found, handle it - // Add an event listener on the element - // Add disabled buttons on the element's great-grandparent - initializeWidgetOnToList(toList, toListId); + // Then get fromList and handle it + initializeWidgetOnList(toList, ".selector-chosen"); + let fromList = toList.closest('.selector').querySelector(".selector-available select"); + initializeWidgetOnList(fromList, ".selector-available"); } else { // Element not found, check again after a delay - setTimeout(() => checkToListThenInitWidget(toListId, attempts), 1000); // Check every 1000 milliseconds (1 second) + setTimeout(() => checkToListThenInitWidget(toListId, attempts), 300); // Check every 1000 milliseconds (1 second) } } } // Initialize the widget: -// add related buttons to the widget for edit, delete and view -// add event listeners on the from list, the to list, and selector buttons which either enable or disable the related buttons -function initializeWidgetOnToList(toList, toListId) { - // create the change button - let changeLink = createAndCustomizeLink( - toList, - toListId, - 'related-widget-wrapper-link change-related', - 'Change', - '/public/admin/img/icon-changelink.svg', - { - 'contacts': '/admin/registrar/contact/__fk__/change/?_to_field=id&_popup=1', - 'websites': '/admin/registrar/website/__fk__/change/?_to_field=id&_popup=1', - 'alternative_domains': '/admin/registrar/website/__fk__/change/?_to_field=id&_popup=1', - }, - true, - true - ); +// Replace h2 with more semantic h3 +function initializeWidgetOnList(list, parentId) { + if (list) { + // Get h2 and its container + const parentElement = list.closest(parentId); + const h2Element = parentElement.querySelector('h2'); - let hasDeletePermission = hasDeletePermissionOnPage(); + // One last check + if (parentElement && h2Element) { + // Create a new

element + const h3Element = document.createElement('h3'); - let deleteLink = null; - if (hasDeletePermission) { - // create the delete button if user has permission to delete - deleteLink = createAndCustomizeLink( - toList, - toListId, - 'related-widget-wrapper-link delete-related', - 'Delete', - '/public/admin/img/icon-deletelink.svg', - { - 'contacts': '/admin/registrar/contact/__fk__/delete/?_to_field=id&_popup=1', - 'websites': '/admin/registrar/website/__fk__/delete/?_to_field=id&_popup=1', - 'alternative_domains': '/admin/registrar/website/__fk__/delete/?_to_field=id&_popup=1', - }, - true, - false - ); - } + // Copy the text content from the

element to the

element + h3Element.textContent = h2Element.textContent; - // create the view button - let viewLink = createAndCustomizeLink( - toList, - toListId, - 'related-widget-wrapper-link view-related', - 'View', - '/public/admin/img/icon-viewlink.svg', - { - 'contacts': '/admin/registrar/contact/__fk__/change/?_to_field=id', - 'websites': '/admin/registrar/website/__fk__/change/?_to_field=id', - 'alternative_domains': '/admin/registrar/website/__fk__/change/?_to_field=id', - }, - // NOTE: If we open view in the same window then use the back button - // to go back, the 'chosen' list will fail to initialize correctly in - // sandbozes (but will work fine on local). This is related to how the - // Django JS runs (SelectBox.js) and is probably due to a race condition. - true, - false - ); + // Find the nested element inside the

+ const nestedSpan = h2Element.querySelector('span[class][title]'); - // identify the fromList element in the DOM - let fromList = toList.closest('.selector').querySelector(".selector-available select"); + // If the nested element exists + if (nestedSpan) { + // Create a new element + const newSpan = document.createElement('span'); - fromList.addEventListener('click', function(event) { - handleSelectClick(fromList, changeLink, deleteLink, viewLink); - }); - - toList.addEventListener('click', function(event) { - handleSelectClick(toList, changeLink, deleteLink, viewLink); - }); - - // Disable buttons when the selectors are interacted with (items are moved from one column to the other) - let selectorButtons = []; - selectorButtons.push(toList.closest(".selector").querySelector(".selector-chooseall")); - selectorButtons.push(toList.closest(".selector").querySelector(".selector-add")); - selectorButtons.push(toList.closest(".selector").querySelector(".selector-remove")); + // Copy the class and title attributes from the nested element + newSpan.className = nestedSpan.className; + newSpan.title = nestedSpan.title; - selectorButtons.forEach((selector) => { - selector.addEventListener("click", ()=>{disableRelatedWidgetButtons(changeLink, deleteLink, viewLink)}); - }); -} - -// create and customize the button, then add to the DOM, relative to the toList -// toList - the element in the DOM for the toList -// toListId - the ID of the element in the DOM -// className - className to add to the created link -// action - the action to perform on the item {change, delete, view} -// imgSrc - the img.src for the created link -// dataMappings - dictionary which relates toListId to href for the created link -// dataPopup - boolean for whether the link should produce a popup window -// firstPosition - boolean indicating if link should be first position in list of links, otherwise, should be last link -function createAndCustomizeLink(toList, toListId, className, action, imgSrc, dataMappings, dataPopup, firstPosition) { - // Create a link element - var link = document.createElement('a'); - - // Set class attribute for the link - link.className = className; - - // Set id - // Determine function {change, link, view} from the className - // Add {function}_ to the beginning of the string - let modifiedLinkString = className.split('-')[0] + '_' + toListId; - // Remove '_to' from the end of the string - modifiedLinkString = modifiedLinkString.replace('_to', ''); - link.id = modifiedLinkString; - - // Set data-href-template - for (const [idPattern, template] of Object.entries(dataMappings)) { - if (toListId.includes(idPattern)) { - link.setAttribute('data-href-template', template); - break; // Stop checking once a match is found - } - } - - if (dataPopup) - link.setAttribute('data-popup', 'yes'); - - link.setAttribute('title-template', action + " selected item") - link.title = link.getAttribute('title-template'); - - // Create an 'img' element - var img = document.createElement('img'); - - // Set attributes for the new image - img.src = imgSrc; - img.alt = action; - - // Append the image to the link - link.appendChild(img); - - let relatedWidgetWrapper = toList.closest('.related-widget-wrapper'); - // If firstPosition is true, insert link as the first child element - if (firstPosition) { - relatedWidgetWrapper.insertBefore(link, relatedWidgetWrapper.children[0]); - } else { - // otherwise, insert the link prior to the last child (which is a div) - // and also prior to any text elements immediately preceding the last - // child node - var lastChild = relatedWidgetWrapper.lastChild; - - // Check if lastChild is an element node (not a text node, comment, etc.) - if (lastChild.nodeType === 1) { - var previousSibling = lastChild.previousSibling; - // need to work around some white space which has been inserted into the dom - while (previousSibling.nodeType !== 1) { - previousSibling = previousSibling.previousSibling; + // Append the new element to the

element + h3Element.appendChild(newSpan); } - relatedWidgetWrapper.insertBefore(link, previousSibling.nextSibling); + + // Replace the

element with the new

element + parentElement.replaceChild(h3Element, h2Element); } } - - // Return the link, which we'll use in the disable and enable functions - return link; -} - -// Either enable or disable widget buttons when select is clicked. Action (enable or disable) taken depends on the count -// of selected items in selectElement. If exactly one item is selected, buttons are enabled, and urls for the buttons are -// associated with the selected item -function handleSelectClick(selectElement, changeLink, deleteLink, viewLink) { - - // If one item is selected (across selectElement and relatedSelectElement), enable buttons; otherwise, disable them - if (selectElement.selectedOptions.length === 1) { - // enable buttons for selected item in selectElement - enableRelatedWidgetButtons(changeLink, deleteLink, viewLink, selectElement.selectedOptions[0].value, selectElement.selectedOptions[0].text); - } else { - disableRelatedWidgetButtons(changeLink, deleteLink, viewLink); - } -} - -// return true if there exist elements on the page with classname of delete-related. -// presence of one or more of these elements indicates user has permission to delete -function hasDeletePermissionOnPage() { - return document.querySelector('.delete-related') != null -} - -function disableRelatedWidgetButtons(changeLink, deleteLink, viewLink) { - changeLink.removeAttribute('href'); - changeLink.setAttribute('title', changeLink.getAttribute('title-template')); - if (deleteLink) { - deleteLink.removeAttribute('href'); - deleteLink.setAttribute('title', deleteLink.getAttribute('title-template')); - } - viewLink.removeAttribute('href'); - viewLink.setAttribute('title', viewLink.getAttribute('title-template')); -} - -function enableRelatedWidgetButtons(changeLink, deleteLink, viewLink, elementPk, elementText) { - changeLink.setAttribute('href', changeLink.getAttribute('data-href-template').replace('__fk__', elementPk)); - changeLink.setAttribute('title', changeLink.getAttribute('title-template').replace('selected item', elementText)); - if (deleteLink) { - deleteLink.setAttribute('href', deleteLink.getAttribute('data-href-template').replace('__fk__', elementPk)); - deleteLink.setAttribute('title', deleteLink.getAttribute('title-template').replace('selected item', elementText)); - } - viewLink.setAttribute('href', viewLink.getAttribute('data-href-template').replace('__fk__', elementPk)); - viewLink.setAttribute('title', viewLink.getAttribute('title-template').replace('selected item', elementText)); } /** An IIFE for admin in DjangoAdmin to listen to changes on the domain request diff --git a/src/registrar/assets/sass/_theme/_admin.scss b/src/registrar/assets/sass/_theme/_admin.scss index 2dcbfbb06..a9a71a616 100644 --- a/src/registrar/assets/sass/_theme/_admin.scss +++ b/src/registrar/assets/sass/_theme/_admin.scss @@ -630,3 +630,25 @@ address.dja-address-contact-list { .usa-button__small-text { font-size: small; } + +// We override the DJA header on multi list selects from h2 to h3 +// The following block of code styles our generated h3s to match the old h2s +.selector .selector-available h3 { + background: var(--darkened-bg); + color: var(--body-quiet-color); +} +.selector-available h3, .selector-chosen h3 { + border: 1px solid var(--border-color); + border-radius: 4px 4px 0 0; + margin: 0; + padding: 8px; + font-size: 0.8125rem; + text-align: left; + margin: 0; + padding: 8px; + line-height: 1.3; +} +.selector .selector-chosen h3 { + background: var(--primary); + color: var(--header-link-color); +} From 15a74243a93d354f271f42e4639c20a894c1c053 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 26 Apr 2024 12:20:00 -0400 Subject: [PATCH 09/11] cleanup --- src/registrar/assets/js/get-gov-admin.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/registrar/assets/js/get-gov-admin.js b/src/registrar/assets/js/get-gov-admin.js index 17662a367..5a24469bb 100644 --- a/src/registrar/assets/js/get-gov-admin.js +++ b/src/registrar/assets/js/get-gov-admin.js @@ -234,7 +234,7 @@ function openInNewTab(el, removeAttribute = false){ // and a "to" select list; initialization is based off of the presence of the // "to" select list checkToListThenInitWidget('id_groups_to', 0); - // checkToListThenInitWidget('id_user_permissions_to', 0); + checkToListThenInitWidget('id_user_permissions_to', 0); })(); // Function to check for the existence of the "to" select list element in the DOM, and if and when found, @@ -244,7 +244,7 @@ function checkToListThenInitWidget(toListId, attempts) { attempts++; if (attempts < 12) { - if ((toList)) { + if ((toList && toList !== undefined)) { // toList found, handle it // Then get fromList and handle it initializeWidgetOnList(toList, ".selector-chosen"); From b75ae04812509f64a67e9d609af3fea567931c1a Mon Sep 17 00:00:00 2001 From: Rebecca Hsieh Date: Tue, 30 Apr 2024 15:02:17 -0700 Subject: [PATCH 10/11] Staging hotfix for gulp issue --- .github/workflows/deploy-development.yaml | 15 +++++++++++---- .github/workflows/deploy-sandbox.yaml | 15 +++++++++++---- .github/workflows/deploy-stable.yaml | 13 ++++++++++--- .github/workflows/deploy-staging.yaml | 15 +++++++++++---- src/node.Dockerfile | 6 ++++-- src/package-lock.json | 4 ++++ src/package.json | 7 ++++++- 7 files changed, 57 insertions(+), 18 deletions(-) diff --git a/.github/workflows/deploy-development.yaml b/.github/workflows/deploy-development.yaml index 686635c20..e62607d95 100644 --- a/.github/workflows/deploy-development.yaml +++ b/.github/workflows/deploy-development.yaml @@ -22,9 +22,16 @@ jobs: - name: Compile USWDS assets working-directory: ./src run: | - docker compose run node npm install && - docker compose run node npx gulp copyAssets && - docker compose run node npx gulp compile + docker compose run node bash -c "\ + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash && \ + export NVM_DIR=\"\$HOME/.nvm\" && \ + [ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" && \ + [ -s \"\$NVM_DIR/bash_completion\" ] && \. \"\$NVM_DIR/bash_completion\" && \ + nvm install 21.7.3 && \ + nvm use 21.7.3 && \ + npm install && \ + npx gulp copyAssets && \ + npx gulp compile" - name: Collect static assets working-directory: ./src run: docker compose run app python manage.py collectstatic --no-input @@ -45,4 +52,4 @@ jobs: cf_password: ${{ secrets.CF_DEVELOPMENT_PASSWORD }} cf_org: cisa-dotgov cf_space: development - cf_command: "run-task getgov-development --command 'python manage.py migrate' --name migrate" + cf_command: "run-task getgov-development --command 'python manage.py migrate' --name migrate" \ No newline at end of file diff --git a/.github/workflows/deploy-sandbox.yaml b/.github/workflows/deploy-sandbox.yaml index f7f4a0d65..d9d7cbe14 100644 --- a/.github/workflows/deploy-sandbox.yaml +++ b/.github/workflows/deploy-sandbox.yaml @@ -42,9 +42,16 @@ jobs: - name: Compile USWDS assets working-directory: ./src run: | - docker compose run node npm install && - docker compose run node npx gulp copyAssets && - docker compose run node npx gulp compile + docker compose run node bash -c "\ + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash && \ + export NVM_DIR=\"\$HOME/.nvm\" && \ + [ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" && \ + [ -s \"\$NVM_DIR/bash_completion\" ] && \. \"\$NVM_DIR/bash_completion\" && \ + nvm install 21.7.3 && \ + nvm use 21.7.3 && \ + npm install && \ + npx gulp copyAssets && \ + npx gulp compile" - name: Collect static assets working-directory: ./src run: docker compose run app python manage.py collectstatic --no-input @@ -75,4 +82,4 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, body: '🥳 Successfully deployed to developer sandbox **[${{ env.ENVIRONMENT }}](https://getgov-${{ env.ENVIRONMENT }}.app.cloud.gov/)**.' - }) + }) \ No newline at end of file diff --git a/.github/workflows/deploy-stable.yaml b/.github/workflows/deploy-stable.yaml index 0ded4a3a6..9d0573e01 100644 --- a/.github/workflows/deploy-stable.yaml +++ b/.github/workflows/deploy-stable.yaml @@ -23,9 +23,16 @@ jobs: - name: Compile USWDS assets working-directory: ./src run: | - docker compose run node npm install && - docker compose run node npx gulp copyAssets && - docker compose run node npx gulp compile + docker compose run node bash -c "\ + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash && \ + export NVM_DIR=\"\$HOME/.nvm\" && \ + [ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" && \ + [ -s \"\$NVM_DIR/bash_completion\" ] && \. \"\$NVM_DIR/bash_completion\" && \ + nvm install 21.7.3 && \ + nvm use 21.7.3 && \ + npm install && \ + npx gulp copyAssets && \ + npx gulp compile" - name: Collect static assets working-directory: ./src run: docker compose run app python manage.py collectstatic --no-input diff --git a/.github/workflows/deploy-staging.yaml b/.github/workflows/deploy-staging.yaml index 1df08f412..9584985f0 100644 --- a/.github/workflows/deploy-staging.yaml +++ b/.github/workflows/deploy-staging.yaml @@ -23,9 +23,16 @@ jobs: - name: Compile USWDS assets working-directory: ./src run: | - docker compose run node npm install && - docker compose run node npx gulp copyAssets && - docker compose run node npx gulp compile + docker compose run node bash -c "\ + curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash && \ + export NVM_DIR=\"\$HOME/.nvm\" && \ + [ -s \"\$NVM_DIR/nvm.sh\" ] && \. \"\$NVM_DIR/nvm.sh\" && \ + [ -s \"\$NVM_DIR/bash_completion\" ] && \. \"\$NVM_DIR/bash_completion\" && \ + nvm install 21.7.3 && \ + nvm use 21.7.3 && \ + npm install && \ + npx gulp copyAssets && \ + npx gulp compile" - name: Collect static assets working-directory: ./src run: docker compose run app python manage.py collectstatic --no-input @@ -44,4 +51,4 @@ jobs: cf_password: ${{ secrets.CF_STAGING_PASSWORD }} cf_org: cisa-dotgov cf_space: staging - cf_command: "run-task getgov-staging --command 'python manage.py migrate' --name migrate" + cf_command: "run-task getgov-staging --command 'python manage.py migrate' --name migrate" \ No newline at end of file diff --git a/src/node.Dockerfile b/src/node.Dockerfile index b478a8a26..cf0b6acc6 100644 --- a/src/node.Dockerfile +++ b/src/node.Dockerfile @@ -1,5 +1,5 @@ FROM docker.io/cimg/node:current-browsers - +FROM node:21.7.3 WORKDIR /app # Install app dependencies @@ -7,4 +7,6 @@ WORKDIR /app # where available (npm@5+) COPY --chown=circleci:circleci package*.json ./ -RUN npm install + +RUN npm install -g npm@10.5.0 +RUN npm install \ No newline at end of file diff --git a/src/package-lock.json b/src/package-lock.json index dc1464ee8..9df99a739 100644 --- a/src/package-lock.json +++ b/src/package-lock.json @@ -15,6 +15,10 @@ }, "devDependencies": { "@uswds/compile": "^1.0.0-beta.3" + }, + "engines": { + "node": "21.7.3", + "npm": "10.5.0" } }, "node_modules/@gulp-sourcemaps/identity-map": { diff --git a/src/package.json b/src/package.json index 274e0e282..3afce297f 100644 --- a/src/package.json +++ b/src/package.json @@ -3,6 +3,11 @@ "version": "1.0.0", "description": "========================", "main": "index.js", + "engines": { + "node": "21.7.3", + "npm": "10.5.0" + }, + "engineStrict": true, "scripts": { "pa11y-ci": "pa11y-ci", "test": "echo \"Error: no test specified\" && exit 1" @@ -17,4 +22,4 @@ "devDependencies": { "@uswds/compile": "^1.0.0-beta.3" } -} +} \ No newline at end of file From 8d98434352ebd1a271f511d5234f15d617919f75 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Tue, 30 Apr 2024 20:56:24 -0400 Subject: [PATCH 11/11] cleanup --- src/registrar/assets/js/get-gov-admin.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/registrar/assets/js/get-gov-admin.js b/src/registrar/assets/js/get-gov-admin.js index 5a24469bb..f38afd252 100644 --- a/src/registrar/assets/js/get-gov-admin.js +++ b/src/registrar/assets/js/get-gov-admin.js @@ -244,7 +244,7 @@ function checkToListThenInitWidget(toListId, attempts) { attempts++; if (attempts < 12) { - if ((toList && toList !== undefined)) { + if (toList) { // toList found, handle it // Then get fromList and handle it initializeWidgetOnList(toList, ".selector-chosen"); @@ -252,7 +252,7 @@ function checkToListThenInitWidget(toListId, attempts) { initializeWidgetOnList(fromList, ".selector-available"); } else { // Element not found, check again after a delay - setTimeout(() => checkToListThenInitWidget(toListId, attempts), 300); // Check every 1000 milliseconds (1 second) + setTimeout(() => checkToListThenInitWidget(toListId, attempts), 300); // Check every 300 milliseconds } } }