From 5ebae382ed5737a66d681f46736d5c9acc1fd983 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 4 Jan 2024 14:12:46 -0700 Subject: [PATCH 001/120] Update home.html --- src/registrar/templates/home.html | 41 ++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index 15835920b..80b20c258 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -42,6 +42,7 @@ Expires Status Action +       @@ -80,6 +81,7 @@ {% endif %} + {% endfor %} @@ -104,6 +106,7 @@ Date submitted Status Action + Delete Action @@ -121,22 +124,54 @@ {{ application.get_status_display }} - {% if application.status == "started" or application.status == "action needed" or application.status == "withdrawn" %} + {% if application.status == "started" or application.status == "action needed" or application.status == "withdrawn" %} Edit {{ application.requested_domain.name|default:"New domain request" }} - {% else %} + {% else %} Manage {{application.requested_domain.name}} - {% endif %} + {% endif %} + + + + Delete + + + + Delete + + +
+
+ {% with heading="Are you sure you want to remove <"|add:permission.user.email|add:">?" %} + {% include 'includes/modal.html' with modal_heading=heading modal_description="<"|add:permission.user.email|add:"> will no longer be able to manage the domain "|add:domain.name|add:"." modal_button=modal_button|safe %} + {% endwith %} +
+
+ {% endfor %} From 403bfb6433eb15242b4f5cc3f1dbfc98dcd002e8 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 4 Jan 2024 14:34:08 -0700 Subject: [PATCH 002/120] Delete functionality --- src/registrar/config/urls.py | 5 ++ src/registrar/templates/home.html | 50 +++++++++---------- src/registrar/views/application.py | 8 +++ src/registrar/views/index.py | 7 +++ .../views/utility/permission_views.py | 8 +++ 5 files changed, 51 insertions(+), 27 deletions(-) diff --git a/src/registrar/config/urls.py b/src/registrar/config/urls.py index 607bf5f61..bbe91f6fd 100644 --- a/src/registrar/config/urls.py +++ b/src/registrar/config/urls.py @@ -138,6 +138,11 @@ urlpatterns = [ views.DomainInvitationDeleteView.as_view(http_method_names=["post"]), name="invitation-delete", ), + path( + "application//delete", + views.DomainApplicationDeleteView.as_view(http_method_names=["post"]), + name="application-delete", + ), ] # we normally would guard these with `if settings.DEBUG` but tests run with diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index 80b20c258..3a9a5a611 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -141,36 +141,32 @@ - - + + Delete - - - Delete - -
-
- {% with heading="Are you sure you want to remove <"|add:permission.user.email|add:">?" %} - {% include 'includes/modal.html' with modal_heading=heading modal_description="<"|add:permission.user.email|add:"> will no longer be able to manage the domain "|add:domain.name|add:"." modal_button=modal_button|safe %} - {% endwith %} -
-
+
+
+ {% with heading="Are you sure you want to delete "|add:application.requested_domain.name|add:"?" %} + {% include 'includes/modal.html' with modal_heading=heading modal_description="This will remove the domain request from the .gov registrar. This action cannot be undone." modal_button=modal_button|safe %} + {% endwith %} +
+
{% endfor %} diff --git a/src/registrar/views/application.py b/src/registrar/views/application.py index 41052e164..63adbf3d9 100644 --- a/src/registrar/views/application.py +++ b/src/registrar/views/application.py @@ -12,6 +12,7 @@ from registrar.forms import application_wizard as forms from registrar.models import DomainApplication from registrar.utility import StrEnum from registrar.views.utility import StepsHelper +from registrar.views.utility.permission_views import DomainApplicationPermissionDeleteView from .utility import ( DomainApplicationPermissionView, @@ -572,3 +573,10 @@ class ApplicationWithdrawn(DomainApplicationPermissionWithdrawView): application.withdraw() application.save() return HttpResponseRedirect(reverse("home")) + + +class DomainApplicationDeleteView(DomainApplicationPermissionDeleteView): + object: DomainApplication # workaround for type mismatch in DeleteView + + def get_success_url(self): + return reverse("home") \ No newline at end of file diff --git a/src/registrar/views/index.py b/src/registrar/views/index.py index 9605c723d..a4a8e3d90 100644 --- a/src/registrar/views/index.py +++ b/src/registrar/views/index.py @@ -18,4 +18,11 @@ def index(request): domains = Domain.objects.filter(id__in=domain_ids) context["domains"] = domains + modal_button = ( + '' + ) + + context["modal_button"] = modal_button return render(request, "home.html", context) diff --git a/src/registrar/views/utility/permission_views.py b/src/registrar/views/utility/permission_views.py index 1798ec79d..cf6bd930d 100644 --- a/src/registrar/views/utility/permission_views.py +++ b/src/registrar/views/utility/permission_views.py @@ -122,3 +122,11 @@ class DomainInvitationPermissionDeleteView(DomainInvitationPermission, DeleteVie model = DomainInvitation object: DomainInvitation # workaround for type mismatch in DeleteView + + +class DomainApplicationPermissionDeleteView(DomainApplicationPermission, DeleteView, abc.ABC): + + """Abstract view for deleting a DomainApplication.""" + + model = DomainApplication + object: DomainApplication \ No newline at end of file From a251d1ca1ad2d4ec4fe9ba14f5a326728329f247 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 5 Jan 2024 09:54:31 -0700 Subject: [PATCH 003/120] Conditional deletion --- src/registrar/templates/home.html | 53 +++++++++++++++--------------- src/registrar/tests/test_views.py | 5 +-- src/registrar/views/application.py | 14 ++++++++ 3 files changed, 44 insertions(+), 28 deletions(-) diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index 3a9a5a611..33708c8f6 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -81,7 +81,6 @@ {% endif %} - {% endfor %} @@ -141,32 +140,34 @@ - - - Delete - + {% if application.status == "started" or application.status == "withdrawn" %} + + + Delete + -
-
- {% with heading="Are you sure you want to delete "|add:application.requested_domain.name|add:"?" %} - {% include 'includes/modal.html' with modal_heading=heading modal_description="This will remove the domain request from the .gov registrar. This action cannot be undone." modal_button=modal_button|safe %} - {% endwith %} -
-
+
+
+ {% with heading="Are you sure you want to delete "|add:application.requested_domain.name|add:"?" %} + {% include 'includes/modal.html' with modal_heading=heading modal_description="This will remove the domain request from the .gov registrar. This action cannot be undone." modal_button=modal_button|safe %} + {% endwith %} +
+
+ {% endif %} {% endfor %} diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index 8f812b815..246222fe7 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -88,8 +88,9 @@ class LoggedInTests(TestWithUser): site = DraftDomain.objects.create(name="igorville.gov") application = DomainApplication.objects.create(creator=self.user, requested_domain=site) response = self.client.get("/") - # count = 2 because it is also in screenreader content - self.assertContains(response, "igorville.gov", count=2) + + # count = 6 because it is also in screenreader content, and in the delete modal + self.assertContains(response, "igorville.gov", count=6) # clean up application.delete() diff --git a/src/registrar/views/application.py b/src/registrar/views/application.py index 63adbf3d9..5043ef245 100644 --- a/src/registrar/views/application.py +++ b/src/registrar/views/application.py @@ -1,4 +1,5 @@ import logging +from django.forms import ValidationError from django.http import Http404, HttpResponse, HttpResponseRedirect from django.shortcuts import redirect, render @@ -576,7 +577,20 @@ class ApplicationWithdrawn(DomainApplicationPermissionWithdrawView): class DomainApplicationDeleteView(DomainApplicationPermissionDeleteView): + """Delete view for home that allows the end user to delete DomainApplications""" object: DomainApplication # workaround for type mismatch in DeleteView + def has_permission(self): + """Custom override for has_permission to exclude all statuses, except WITHDRAWN and STARTED""" + has_perm = super().has_permission() + if not has_perm: + return False + + status = self.get_object().status + if status not in [DomainApplication.ApplicationStatus.WITHDRAWN, DomainApplication.ApplicationStatus.STARTED]: + return False + + return True + def get_success_url(self): return reverse("home") \ No newline at end of file From 2042fd1146223cb9cab91ea1dd01e90527d191b6 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 5 Jan 2024 09:58:33 -0700 Subject: [PATCH 004/120] Increase padding --- 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 33708c8f6..b5bef3b71 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -9,7 +9,7 @@ {% if user.is_authenticated %} {# the entire logged in page goes here #} -
+

Manage your domains

From 0bbbc895b02f5abf7289ebbf9798768082c1487e Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 5 Jan 2024 11:27:24 -0700 Subject: [PATCH 005/120] Fix styling --- src/registrar/assets/sass/_theme/_tables.scss | 4 ++++ src/registrar/templates/home.html | 13 ++++++++++-- src/registrar/views/index.py | 20 +++++++++++++------ 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_tables.scss b/src/registrar/assets/sass/_theme/_tables.scss index 6dcc6f3bc..6a52a5d08 100644 --- a/src/registrar/assets/sass/_theme/_tables.scss +++ b/src/registrar/assets/sass/_theme/_tables.scss @@ -25,6 +25,10 @@ color: color('primary-darker'); padding-bottom: units(2px); } + + th.action-col-custom-width { + width: 27% !important; + } } .dotgov-table { diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index b5bef3b71..62bbc3ac6 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -41,8 +41,13 @@ Domain name Expires Status - Action -       + + Action + @@ -105,7 +110,9 @@ Date submitted Status Action + {% if has_deletable_applications %} Delete Action + {% endif %} @@ -139,6 +146,7 @@ {% endif %} + {% if has_deletable_applications %} {% if application.status == "started" or application.status == "withdrawn" %} {% endif %} + {% endif %} {% endfor %} diff --git a/src/registrar/views/index.py b/src/registrar/views/index.py index a4a8e3d90..5996c27f6 100644 --- a/src/registrar/views/index.py +++ b/src/registrar/views/index.py @@ -18,11 +18,19 @@ def index(request): domains = Domain.objects.filter(id__in=domain_ids) context["domains"] = domains - modal_button = ( - '' - ) - context["modal_button"] = modal_button + # Determine if the user will see applications that they can delete + valid_statuses = [DomainApplication.ApplicationStatus.STARTED, DomainApplication.ApplicationStatus.WITHDRAWN] + has_deletable_applications = applications.filter(status__in=valid_statuses) + context["has_deletable_applications"] = has_deletable_applications + + if has_deletable_applications: + modal_button = ( + '' + ) + + + context["modal_button"] = modal_button return render(request, "home.html", context) From 90760e6d43d2d4c1e5af47f9811e0315da775a48 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 5 Jan 2024 12:32:08 -0700 Subject: [PATCH 006/120] Add unit tests --- src/registrar/tests/test_views.py | 80 +++++++++++++++++++ src/registrar/views/application.py | 4 +- src/registrar/views/index.py | 1 - .../views/utility/permission_views.py | 2 +- 4 files changed, 83 insertions(+), 4 deletions(-) diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index 246222fe7..cfc03d70d 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -94,6 +94,86 @@ class LoggedInTests(TestWithUser): # clean up application.delete() + def test_home_deletes_withdrawn_domain_application(self): + """Tests if the user can delete a DomainApplication in the 'withdrawn' status""" + + site = DraftDomain.objects.create(name="igorville.gov") + application = DomainApplication.objects.create( + creator=self.user, requested_domain=site, status=DomainApplication.ApplicationStatus.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("application-delete", kwargs={"pk": application.pk}), follow=True) + + self.assertNotContains(response, "igorville.gov") + + # clean up + application.delete() + + def test_home_deletes_started_domain_application(self): + """Tests if the user can delete a DomainApplication in the 'started' status""" + + site = DraftDomain.objects.create(name="igorville.gov") + application = DomainApplication.objects.create( + creator=self.user, requested_domain=site, status=DomainApplication.ApplicationStatus.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("application-delete", kwargs={"pk": application.pk}), follow=True) + + self.assertNotContains(response, "igorville.gov") + + # clean up + application.delete() + + def test_home_doesnt_delete_other_domain_applications(self): + """Tests to ensure the user can't delete Applications 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. + draft_domain = DraftDomain.objects.create(name="igorville.gov") + for status in DomainApplication.ApplicationStatus: + if status not in [ + DomainApplication.ApplicationStatus.STARTED, + DomainApplication.ApplicationStatus.WITHDRAWN, + ]: + with self.subTest(status=status): + application = DomainApplication.objects.create( + creator=self.user, requested_domain=draft_domain, status=status + ) + + # Trigger the delete logic + response = self.client.post( + reverse("application-delete", kwargs={"pk": application.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_application = DomainApplication.objects.filter(requested_domain=draft_domain) + + # Make sure the DomainApplication wasn't deleted + self.assertEqual(desired_application.count(), 1) + + # clean up + application.delete() + def test_home_lists_domains(self): response = self.client.get("/") domain, _ = Domain.objects.get_or_create(name="igorville.gov") diff --git a/src/registrar/views/application.py b/src/registrar/views/application.py index 5043ef245..d50502db4 100644 --- a/src/registrar/views/application.py +++ b/src/registrar/views/application.py @@ -1,5 +1,4 @@ import logging -from django.forms import ValidationError from django.http import Http404, HttpResponse, HttpResponseRedirect from django.shortcuts import redirect, render @@ -578,6 +577,7 @@ class ApplicationWithdrawn(DomainApplicationPermissionWithdrawView): class DomainApplicationDeleteView(DomainApplicationPermissionDeleteView): """Delete view for home that allows the end user to delete DomainApplications""" + object: DomainApplication # workaround for type mismatch in DeleteView def has_permission(self): @@ -593,4 +593,4 @@ class DomainApplicationDeleteView(DomainApplicationPermissionDeleteView): return True def get_success_url(self): - return reverse("home") \ No newline at end of file + return reverse("home") diff --git a/src/registrar/views/index.py b/src/registrar/views/index.py index 5996c27f6..43f251d47 100644 --- a/src/registrar/views/index.py +++ b/src/registrar/views/index.py @@ -31,6 +31,5 @@ def index(request): 'name="delete-application">Yes, delete request' ) - context["modal_button"] = modal_button return render(request, "home.html", context) diff --git a/src/registrar/views/utility/permission_views.py b/src/registrar/views/utility/permission_views.py index cf6bd930d..587eb0b5c 100644 --- a/src/registrar/views/utility/permission_views.py +++ b/src/registrar/views/utility/permission_views.py @@ -129,4 +129,4 @@ class DomainApplicationPermissionDeleteView(DomainApplicationPermission, DeleteV """Abstract view for deleting a DomainApplication.""" model = DomainApplication - object: DomainApplication \ No newline at end of file + object: DomainApplication From 693058c02a2d7a4430fd5c861319508735ad59b3 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 5 Jan 2024 12:49:39 -0700 Subject: [PATCH 007/120] Add padding for mobile view --- src/registrar/assets/sass/_theme/_tables.scss | 7 +++++++ src/registrar/templates/home.html | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/registrar/assets/sass/_theme/_tables.scss b/src/registrar/assets/sass/_theme/_tables.scss index 6a52a5d08..65fb8b487 100644 --- a/src/registrar/assets/sass/_theme/_tables.scss +++ b/src/registrar/assets/sass/_theme/_tables.scss @@ -29,6 +29,13 @@ th.action-col-custom-width { width: 27% !important; } + + // Fix margins in mobile view + @media screen { + td.tablet-margin-top-5 { + margin-top: 40px !important; + } + } } .dotgov-table { diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index 62bbc3ac6..da9212bea 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -147,7 +147,7 @@ {% if has_deletable_applications %} - + {% if application.status == "started" or application.status == "withdrawn" %} Date: Fri, 5 Jan 2024 14:45:37 -0700 Subject: [PATCH 008/120] CSS changes --- src/registrar/assets/sass/_theme/_tables.scss | 2 +- .../assets/sass/_theme/_usa-modal.scss | 12 +++++++++ src/registrar/assets/sass/_theme/styles.scss | 1 + src/registrar/templates/home.html | 26 ++++++++++++++----- src/registrar/templates/includes/modal.html | 2 +- src/registrar/views/index.py | 3 ++- 6 files changed, 37 insertions(+), 9 deletions(-) create mode 100644 src/registrar/assets/sass/_theme/_usa-modal.scss diff --git a/src/registrar/assets/sass/_theme/_tables.scss b/src/registrar/assets/sass/_theme/_tables.scss index 65fb8b487..f3adab18b 100644 --- a/src/registrar/assets/sass/_theme/_tables.scss +++ b/src/registrar/assets/sass/_theme/_tables.scss @@ -27,7 +27,7 @@ } th.action-col-custom-width { - width: 27% !important; + width: 23.25% !important; } // Fix margins in mobile view diff --git a/src/registrar/assets/sass/_theme/_usa-modal.scss b/src/registrar/assets/sass/_theme/_usa-modal.scss new file mode 100644 index 000000000..7106f1961 --- /dev/null +++ b/src/registrar/assets/sass/_theme/_usa-modal.scss @@ -0,0 +1,12 @@ +@use "uswds-core" as *; + +.usa-modal.wider-modal { + width: 40% !important; + max-width: 40% !important; +} + +.usa-modal { + .usa-modal__heading.blue-header{ + color: $dhs-blue; + } +} \ No newline at end of file diff --git a/src/registrar/assets/sass/_theme/styles.scss b/src/registrar/assets/sass/_theme/styles.scss index 8a2e1d2d3..499f9a806 100644 --- a/src/registrar/assets/sass/_theme/styles.scss +++ b/src/registrar/assets/sass/_theme/styles.scss @@ -17,6 +17,7 @@ @forward "tables"; @forward "sidenav"; @forward "register-form"; +@forward "usa-modal"; /*-------------------------------------------------- --- Admin ---------------------------------*/ diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index da9212bea..4e7e9729b 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -48,6 +48,9 @@ > Action + {% if has_deletable_applications %} + + {% endif %} @@ -86,6 +89,9 @@ {% endif %} + {% if has_deletable_applications %} + + {% endif %} {% endfor %} @@ -119,7 +125,13 @@ {% for application in domain_applications %} - {{ application.requested_domain.name|default:"New domain request" }} + {% if application.requested_domain and application.requested_domain.name %} + {{ application.requested_domain.name }} + {% elif forloop.counter != 1 %} + New domain request {{ forloop.counter }} + {% else %} + New domain request + {% endif %} {% if application.submission_date %} @@ -135,7 +147,7 @@ - Edit {{ application.requested_domain.name|default:"New domain request" }} + Edit {{ application.requested_domain.name|default:"New domain request"|add:forloop.counter|add:"" }} {% else %} @@ -163,16 +175,18 @@

- {% with heading="Are you sure you want to delete "|add:application.requested_domain.name|add:"?" %} - {% include 'includes/modal.html' with modal_heading=heading modal_description="This will remove the domain request from the .gov registrar. This action cannot be undone." modal_button=modal_button|safe %} - {% endwith %} + {% if application.requested_domain %} + {% include 'includes/modal.html' with modal_heading="Are you sure you want to delete "|add:application.requested_domain.name|add:"?" modal_description="This will remove the domain request from the .gov registrar. This action cannot be undone." modal_button=modal_button|safe %} + {% else %} + {% include 'includes/modal.html' with modal_heading="Are you sure you want to delete your domain request?" modal_description="This will remove the domain request from the .gov registrar. This action cannot be undone." modal_button=modal_button|safe %} + {% endif %}
{% endif %} diff --git a/src/registrar/templates/includes/modal.html b/src/registrar/templates/includes/modal.html index 1f9fbcfd4..a5ae4b1f2 100644 --- a/src/registrar/templates/includes/modal.html +++ b/src/registrar/templates/includes/modal.html @@ -2,7 +2,7 @@
-
diff --git a/src/registrar/views/index.py b/src/registrar/views/index.py index 43f251d47..75a3944a6 100644 --- a/src/registrar/views/index.py +++ b/src/registrar/views/index.py @@ -21,7 +21,7 @@ def index(request): # Determine if the user will see applications that they can delete valid_statuses = [DomainApplication.ApplicationStatus.STARTED, DomainApplication.ApplicationStatus.WITHDRAWN] - has_deletable_applications = applications.filter(status__in=valid_statuses) + has_deletable_applications = applications.filter(status__in=valid_statuses).exists() context["has_deletable_applications"] = has_deletable_applications if has_deletable_applications: @@ -32,4 +32,5 @@ def index(request): ) context["modal_button"] = modal_button + return render(request, "home.html", context) From d9151febcf7366855de4f30b01830794ec05aa7e Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 5 Jan 2024 15:03:00 -0700 Subject: [PATCH 009/120] Update _usa-modal.scss --- src/registrar/assets/sass/_theme/_usa-modal.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/registrar/assets/sass/_theme/_usa-modal.scss b/src/registrar/assets/sass/_theme/_usa-modal.scss index 7106f1961..962394e42 100644 --- a/src/registrar/assets/sass/_theme/_usa-modal.scss +++ b/src/registrar/assets/sass/_theme/_usa-modal.scss @@ -1,3 +1,4 @@ +@use "cisa_colors" as *; @use "uswds-core" as *; .usa-modal.wider-modal { From dcc8f99553b087d98f9ceb335ee37e660f375380 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 5 Jan 2024 15:37:48 -0700 Subject: [PATCH 010/120] Minor improvements --- src/registrar/assets/sass/_theme/_tables.scss | 4 ++-- src/registrar/templates/home.html | 4 ++-- src/registrar/tests/test_views.py | 4 ++-- src/registrar/views/application.py | 4 +++- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_tables.scss b/src/registrar/assets/sass/_theme/_tables.scss index f3adab18b..bde74cf08 100644 --- a/src/registrar/assets/sass/_theme/_tables.scss +++ b/src/registrar/assets/sass/_theme/_tables.scss @@ -32,8 +32,8 @@ // Fix margins in mobile view @media screen { - td.tablet-margin-top-5 { - margin-top: 40px !important; + td.tablet-margin-top-2 { + margin-top: 16px !important; } } } diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index 4e7e9729b..ba2a41aa3 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -147,7 +147,7 @@ - Edit {{ application.requested_domain.name|default:"New domain request"|add:forloop.counter|add:"" }} + Edit {{ application.requested_domain.name|default:"New domain request" }} {% else %} @@ -159,7 +159,7 @@ {% if has_deletable_applications %} - + {% if application.status == "started" or application.status == "withdrawn" %} Date: Fri, 5 Jan 2024 15:40:21 -0700 Subject: [PATCH 011/120] Fix wonky width --- src/registrar/assets/sass/_theme/_tables.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/assets/sass/_theme/_tables.scss b/src/registrar/assets/sass/_theme/_tables.scss index bde74cf08..e30023d9b 100644 --- a/src/registrar/assets/sass/_theme/_tables.scss +++ b/src/registrar/assets/sass/_theme/_tables.scss @@ -27,7 +27,7 @@ } th.action-col-custom-width { - width: 23.25% !important; + width: 24.25% !important; } // Fix margins in mobile view From daec39a87a160720e785c3e6a8b84ed36584131a Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 5 Jan 2024 15:44:15 -0700 Subject: [PATCH 012/120] Update test_views.py --- src/registrar/tests/test_views.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index 731b7224f..bd9ec3a4f 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -89,8 +89,9 @@ class LoggedInTests(TestWithUser): application = DomainApplication.objects.create(creator=self.user, requested_domain=site) response = self.client.get("/") - # count = 4 because it is also in screenreader content, and in the delete modal - self.assertContains(response, "igorville.gov", count=4) + # count = 5 because it is also in screenreader content, and in the delete modal + self.assertContains(response, "igorville.gov", count=5) + # clean up application.delete() From 0859311be2c8fbf7290b74d22849c85a5d1d9804 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 8 Jan 2024 07:43:53 -0700 Subject: [PATCH 013/120] Remove !important --- src/registrar/assets/sass/_theme/_tables.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_tables.scss b/src/registrar/assets/sass/_theme/_tables.scss index e30023d9b..a42eefd6c 100644 --- a/src/registrar/assets/sass/_theme/_tables.scss +++ b/src/registrar/assets/sass/_theme/_tables.scss @@ -27,13 +27,13 @@ } th.action-col-custom-width { - width: 24.25% !important; + width: 24.25%; } // Fix margins in mobile view @media screen { td.tablet-margin-top-2 { - margin-top: 16px !important; + margin-top: 16px; } } } From 62f460b165d37766edb10b3ed5864ac93baed92d Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 8 Jan 2024 14:22:03 -0700 Subject: [PATCH 014/120] Remove blue header --- src/registrar/assets/sass/_theme/_usa-modal.scss | 6 ------ src/registrar/templates/includes/modal.html | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_usa-modal.scss b/src/registrar/assets/sass/_theme/_usa-modal.scss index 962394e42..68a46a303 100644 --- a/src/registrar/assets/sass/_theme/_usa-modal.scss +++ b/src/registrar/assets/sass/_theme/_usa-modal.scss @@ -5,9 +5,3 @@ width: 40% !important; max-width: 40% !important; } - -.usa-modal { - .usa-modal__heading.blue-header{ - color: $dhs-blue; - } -} \ No newline at end of file diff --git a/src/registrar/templates/includes/modal.html b/src/registrar/templates/includes/modal.html index a5ae4b1f2..1f9fbcfd4 100644 --- a/src/registrar/templates/includes/modal.html +++ b/src/registrar/templates/includes/modal.html @@ -2,7 +2,7 @@
-
From 510b8e3f15f0eb5651527f2a4304c0a8f52811b1 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Mon, 8 Jan 2024 18:13:54 -0500 Subject: [PATCH 015/120] Handle the word 'optional' in nameservers labels on add and delete --- src/registrar/assets/js/get-gov.js | 39 +++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js index 177b771e4..9e3f699bf 100644 --- a/src/registrar/assets/js/get-gov.js +++ b/src/registrar/assets/js/get-gov.js @@ -260,10 +260,22 @@ function removeForm(e, formLabel, isNameserversForm, addButton, formIdentifier){ // h2 and legend for DS form, label for nameservers Array.from(form.querySelectorAll('h2, legend, label, p')).forEach((node) => { + + let innerSpan = node.querySelector('span') + if (innerSpan) { + innerSpan.textContent = innerSpan.textContent.replace(formLabelRegex, `${formLabel} ${index + 1}`); + } else { + node.textContent = node.textContent.replace(formLabelRegex, `${formLabel} ${index + 1}`); + node.textContent = node.textContent.replace(formExampleRegex, `ns${index + 1}`); + } // If the node is a nameserver label, one of the first 2 which was previously 3 and up (not required) // inject the USWDS required markup and make sure the INPUT is required if (isNameserversForm && index <= 1 && node.innerHTML.includes('server') && !node.innerHTML.includes('*')) { + + // Remove the word optional + innerSpan.textContent = innerSpan.textContent.replace(/\s*\(\s*optional\s*\)\s*/, ''); + // Create a new element const newElement = document.createElement('abbr'); newElement.textContent = '*'; @@ -286,13 +298,8 @@ function removeForm(e, formLabel, isNameserversForm, addButton, formIdentifier){ nextInputElement.required = true; } - let innerSpan = node.querySelector('span') - if (innerSpan) { - innerSpan.textContent = innerSpan.textContent.replace(formLabelRegex, `${formLabel} ${index + 1}`); - } else { - node.textContent = node.textContent.replace(formLabelRegex, `${formLabel} ${index + 1}`); - node.textContent = node.textContent.replace(formExampleRegex, `ns${index + 1}`); - } + + }); // Display the add more button if we have less than 13 forms @@ -469,7 +476,7 @@ function hideDeletedForms() { let formLabel = ''; let isNameserversForm = document.title.includes("DNS name servers |"); let isOtherContactsForm = document.title.includes("Other employees from your organization"); - // The Nameservers form st features 2 required and 11 optionals + // The Nameservers formset features 2 required and 11 optionals if (isNameserversForm) { cloneIndex = 2; formLabel = "Name server"; @@ -537,16 +544,24 @@ function hideDeletedForms() { formNum++; newForm.innerHTML = newForm.innerHTML.replace(formNumberRegex, `${formIdentifier}-${formNum-1}-`); - // For the other contacts form, we need to update the fieldset headers based on what's visible vs hidden, - // since the form on the backend employs Django's DELETE widget. For the other formsets, we delete the form - // in JS (completely remove from teh DOM) so we update the headers/labels based on total number of forms. if (isOtherContactsForm) { + // For the other contacts form, we need to update the fieldset headers based on what's visible vs hidden, + // since the form on the backend employs Django's DELETE widget. let totalShownForms = document.querySelectorAll(`.repeatable-form:not([style*="display: none"])`).length; newForm.innerHTML = newForm.innerHTML.replace(formLabelRegex, `${formLabel} ${totalShownForms + 1}`); } else { - newForm.innerHTML = newForm.innerHTML.replace(formLabelRegex, `${formLabel} ${formNum}`); + // Nameservers form is cloned from index 2 which has the word optional on init, does not have the word optional + // if indices 0 or 1 have been deleted + let containsOptional = newForm.innerHTML.includes('(optional)'); + if (isNameserversForm && !containsOptional) { + newForm.innerHTML = newForm.innerHTML.replace(formLabelRegex, `${formLabel} ${formNum} (optional)`); + } else { + newForm.innerHTML = newForm.innerHTML.replace(formLabelRegex, `${formLabel} ${formNum}`); + } } newForm.innerHTML = newForm.innerHTML.replace(formExampleRegex, `ns${formNum}`); + newForm.innerHTML = newForm.innerHTML.replace(/\n/g, ''); // Remove newline characters + newForm.innerHTML = newForm.innerHTML.replace(/>\s*<'); // Remove spaces between tags container.insertBefore(newForm, addButton); newForm.style.display = 'block'; From dff0ef8f3fdb735dff533644eaf8625cbfb595a1 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 9 Jan 2024 07:57:13 -0700 Subject: [PATCH 016/120] Remove underline --- src/registrar/assets/sass/_theme/_base.scss | 4 ++++ src/registrar/templates/home.html | 21 ++++++++++++++++----- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_base.scss b/src/registrar/assets/sass/_theme/_base.scss index 1d936a255..45f7a9383 100644 --- a/src/registrar/assets/sass/_theme/_base.scss +++ b/src/registrar/assets/sass/_theme/_base.scss @@ -125,3 +125,7 @@ abbr[title] { .flex-end { align-items: flex-end; } + +.remove-underline { + text-decoration: none !important; +} \ No newline at end of file diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index ba2a41aa3..30bfd1034 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -147,14 +147,21 @@ - Edit {{ application.requested_domain.name|default:"New domain request" }} - + {% if application.requested_domain and application.requested_domain.name %} + Edit {{ application.requested_domain.name }} + {% else %} + Edit New domain request {{ forloop.counter }} + {% endif %} {% else %} - Manage {{application.requested_domain.name}} + {% if application.requested_domain and application.requested_domain.name %} + Manage {{ application.requested_domain.name }} + {% else %} + Manage New domain request {{ forloop.counter }} + {% endif %} {% endif %} @@ -164,14 +171,18 @@ - Delete + {% if application.requested_domain and application.requested_domain.name %} + Delete {{ application.requested_domain.name }} + {% else %} + Delete New domain request {{ forloop.counter }} + {% endif %}
Date: Tue, 9 Jan 2024 08:42:37 -0700 Subject: [PATCH 017/120] Rewrite logic for "New domain request" --- src/registrar/templates/home.html | 26 ++++---------------------- src/registrar/views/index.py | 15 +++++++++++++-- 2 files changed, 17 insertions(+), 24 deletions(-) diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index 30bfd1034..9ed1d924b 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -125,13 +125,7 @@ {% for application in domain_applications %} - {% if application.requested_domain and application.requested_domain.name %} - {{ application.requested_domain.name }} - {% elif forloop.counter != 1 %} - New domain request {{ forloop.counter }} - {% else %} - New domain request - {% endif %} + {{ application.requested_domain.name|default:"New domain request" }} {% if application.submission_date %} @@ -147,21 +141,13 @@ - {% if application.requested_domain and application.requested_domain.name %} - Edit {{ application.requested_domain.name }} - {% else %} - Edit New domain request {{ forloop.counter }} - {% endif %} + Edit {{ application.requested_domain.name|default:"New domain request" }} {% else %} - {% if application.requested_domain and application.requested_domain.name %} - Manage {{ application.requested_domain.name }} - {% else %} - Manage New domain request {{ forloop.counter }} - {% endif %} + Manage {{ application.requested_domain.name|default:"New domain request" }} {% endif %} @@ -178,11 +164,7 @@ - {% if application.requested_domain and application.requested_domain.name %} - Delete {{ application.requested_domain.name }} - {% else %} - Delete New domain request {{ forloop.counter }} - {% endif %} + Delete {{ application.requested_domain.name|default:"New domain request" }}
Date: Tue, 9 Jan 2024 08:44:33 -0700 Subject: [PATCH 018/120] Update index.py --- src/registrar/views/index.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/registrar/views/index.py b/src/registrar/views/index.py index 681f8e9c1..41cdd9cf2 100644 --- a/src/registrar/views/index.py +++ b/src/registrar/views/index.py @@ -12,8 +12,7 @@ def index(request): # domain_applications context will be used to populate # the active applications table applications = DomainApplication.objects.filter(creator=request.user).exclude(status="approved") - - sorted_applications = applications.filter("-requested_domain__name") + # Adds display logic for empty domain requests counter = 1 for application in applications: From fed4432207a994a3a6ba37d2a0479a7d00a1d6a5 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 9 Jan 2024 08:45:21 -0700 Subject: [PATCH 019/120] Linting --- src/registrar/views/index.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/views/index.py b/src/registrar/views/index.py index 41cdd9cf2..0e7528e08 100644 --- a/src/registrar/views/index.py +++ b/src/registrar/views/index.py @@ -19,7 +19,7 @@ def index(request): if not application.requested_domain or not application.requested_domain.name: application.requested_domain = DraftDomain(name=f"New domain request {counter}") counter += 1 - + # Pass the final context to the application context["domain_applications"] = applications From ba04cf7f878308c396640f64d50bc31fee3ee981 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 9 Jan 2024 09:11:40 -0700 Subject: [PATCH 020/120] Update test_views.py --- src/registrar/tests/test_views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index bd9ec3a4f..11996097b 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -89,8 +89,8 @@ class LoggedInTests(TestWithUser): application = DomainApplication.objects.create(creator=self.user, requested_domain=site) response = self.client.get("/") - # count = 5 because it is also in screenreader content, and in the delete modal - self.assertContains(response, "igorville.gov", count=5) + # count = 6 because it is also in screenreader content, and in the delete modal + self.assertContains(response, "igorville.gov", count=6) # clean up application.delete() From 7c87718e2d21e310443d1482113e060d6d7f47b4 Mon Sep 17 00:00:00 2001 From: Erin <121973038+erinysong@users.noreply.github.com> Date: Tue, 9 Jan 2024 11:15:54 -0800 Subject: [PATCH 021/120] Save initial script --- src/epplibwrapper/tests/test_pool.py | 13 +++++ src/migrationdata/README.md | 8 --- .../update_security_email_disclose.py | 53 +++++++++++++++++++ src/registrar/models/domain.py | 2 +- 4 files changed, 67 insertions(+), 9 deletions(-) delete mode 100644 src/migrationdata/README.md create mode 100644 src/registrar/management/commands/update_security_email_disclose.py diff --git a/src/epplibwrapper/tests/test_pool.py b/src/epplibwrapper/tests/test_pool.py index 1c36d26da..916015980 100644 --- a/src/epplibwrapper/tests/test_pool.py +++ b/src/epplibwrapper/tests/test_pool.py @@ -246,3 +246,16 @@ class TestConnectionPool(TestCase): expected = "InfoDomain failed to execute due to a connection error." result = registry.send(commands.InfoDomain(name="test.gov"), cleaned=True) self.assertEqual(result, expected) + + @patch.object(EPPLibWrapper, "_test_registry_connection_success", patch_success) + def test_retries_on_transport_error(self): + """A .send is invoked on the pool, but transport error occurs and EPP + retries connection.""" + + with ExitStack() as stack: + stack.enter_context(patch.object(EPPConnectionPool, "_create_socket", self.fake_socket)) + stack.enter_context(patch.object(Socket, "connect", self.fake_client)) + + # Pool should be running + self.assertEqual(registry.pool_status.connection_success, True) + self.assertEqual(registry.pool_status.pool_running, True) diff --git a/src/migrationdata/README.md b/src/migrationdata/README.md deleted file mode 100644 index 81190ee3f..000000000 --- a/src/migrationdata/README.md +++ /dev/null @@ -1,8 +0,0 @@ -## Purpose -Use this folder for storing files for the migration process. Should otherwise be empty on local dev environments unless necessary. This folder must exist due to the nature of how data is stored on cloud.gov and the nature of the data we want to send. - -## How do I migrate registrar data? -This process is detailed in [data_migration.md](../../docs/operations/data_migration.md) - -## What kind of files can I store here? -The intent is for PII data or otherwise, but this can exist in any format. Do note that the data contained in this file will be temporary, so after the app is restaged it will lose it. This is ideal for migration files as they write to our DB, but not for something you need to permanently hold onto. \ No newline at end of file diff --git a/src/registrar/management/commands/update_security_email_disclose.py b/src/registrar/management/commands/update_security_email_disclose.py new file mode 100644 index 000000000..13627e220 --- /dev/null +++ b/src/registrar/management/commands/update_security_email_disclose.py @@ -0,0 +1,53 @@ +""""Script description""" + +import logging + +from django.core.management import BaseCommand +from registrar.models import Domain + +logger = logging.getLogger(__name__) + +class Command(BaseCommand): + # TODO: write script description here + help = "Description" + + def __init__(self): + """Sets global variables for code tidyness""" + super().__init__() + # this array is used to store domains with errors, which are not + # successfully updated to disclose + domains_with_errors: List[str] = [] + + def handle(self, **options): + """ + Description for what update_security_email_disclose does + """ + logger.info("Updating security emails to public") + + domains = Domain.objects.filter() + + # Call security_contact on all domains to trigger saving contact information + for domain in domains: + contact = domain.security_contact + + domains_with_contact = Domain.objects.filter( + security_contact_registry_id=True + ) + logger.info("Found %d domains with security contact.", len(domains_with_contact)) + + # Update EPP contact for domains with a security contact + for domain in domains_with_contact: + try: + domain._update_epp_contact(contact=domain.security_contact_registry_id) + logger.info("Updated EPP contact for domain %d to disclose: %d", domain, domain.security_contact.disclose) + except Exception as err: + # error condition if domain not in database + self.domains_with_errors.append(copy.deepcopy(domain.domain_name)) + logger.error(f"error retrieving domain {domain.domain_name}: {err}") + + domains_disclosed = Domain.objects.filter( + security_contact_registry_id=True, + ) + logger.info("Updated %d domains to disclosed.", len(domains_disclosed)) + + diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 001937b89..b3791d4b9 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -1400,7 +1400,7 @@ class Domain(TimeStampedModel, DomainHelper): is_security = contact.contact_type == contact.ContactTypeChoices.SECURITY DF = epp.DiscloseField fields = {DF.EMAIL} - disclose = is_security and contact.email != PublicContact.get_default_security().email + disclose = is_security # Will only disclose DF.EMAIL if its not the default return epp.Disclose( flag=disclose, From 3ffe3002cb625adb826d0a84094517a8e295df01 Mon Sep 17 00:00:00 2001 From: Alysia Broddrick Date: Tue, 9 Jan 2024 12:01:08 -0800 Subject: [PATCH 022/120] removed bl infra --- .github/workflows/migrate.yaml | 1 - .github/workflows/reset-db.yaml | 1 - ops/manifests/manifest-bl.yaml | 32 -------------------------------- src/registrar/config/settings.py | 1 - 4 files changed, 35 deletions(-) delete mode 100644 ops/manifests/manifest-bl.yaml diff --git a/.github/workflows/migrate.yaml b/.github/workflows/migrate.yaml index 8523af013..2033ee51c 100644 --- a/.github/workflows/migrate.yaml +++ b/.github/workflows/migrate.yaml @@ -26,7 +26,6 @@ on: - rb - ko - ab - - bl - rjm - dk diff --git a/.github/workflows/reset-db.yaml b/.github/workflows/reset-db.yaml index 3848a33bd..f8730c865 100644 --- a/.github/workflows/reset-db.yaml +++ b/.github/workflows/reset-db.yaml @@ -26,7 +26,6 @@ on: - rb - ko - ab - - bl - rjm - dk diff --git a/ops/manifests/manifest-bl.yaml b/ops/manifests/manifest-bl.yaml deleted file mode 100644 index 59529278b..000000000 --- a/ops/manifests/manifest-bl.yaml +++ /dev/null @@ -1,32 +0,0 @@ ---- -applications: -- name: getgov-bl - buildpacks: - - python_buildpack - path: ../../src - instances: 1 - memory: 512M - stack: cflinuxfs4 - timeout: 180 - command: ./run.sh - health-check-type: http - health-check-http-endpoint: /health - health-check-invocation-timeout: 40 - env: - # Send stdout and stderr straight to the terminal without buffering - PYTHONUNBUFFERED: yup - # Tell Django where to find its configuration - DJANGO_SETTINGS_MODULE: registrar.config.settings - # Tell Django where it is being hosted - DJANGO_BASE_URL: https://getgov-bl.app.cloud.gov - # Tell Django how much stuff to log - DJANGO_LOG_LEVEL: INFO - # default public site location - GETGOV_PUBLIC_SITE_URL: https://beta.get.gov - # Flag to disable/enable features in prod environments - IS_PRODUCTION: False - routes: - - route: getgov-bl.app.cloud.gov - services: - - getgov-credentials - - getgov-bl-database diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index 2de7e6eb2..f037d4901 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -660,7 +660,6 @@ ALLOWED_HOSTS = [ "getgov-rb.app.cloud.gov", "getgov-ko.app.cloud.gov", "getgov-ab.app.cloud.gov", - "getgov-bl.app.cloud.gov", "getgov-rjm.app.cloud.gov", "getgov-dk.app.cloud.gov", "manage.get.gov", From 683a2f46292d025cc2dd56c21972695bc4405854 Mon Sep 17 00:00:00 2001 From: Erin <121973038+erinysong@users.noreply.github.com> Date: Tue, 9 Jan 2024 17:13:48 -0800 Subject: [PATCH 023/120] Update initial disclose script --- .../update_security_email_disclose.py | 32 +++++++++++-------- src/registrar/models/domain.py | 2 ++ 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/registrar/management/commands/update_security_email_disclose.py b/src/registrar/management/commands/update_security_email_disclose.py index 13627e220..9ba1c58b7 100644 --- a/src/registrar/management/commands/update_security_email_disclose.py +++ b/src/registrar/management/commands/update_security_email_disclose.py @@ -1,6 +1,7 @@ """"Script description""" import logging +import copy from django.core.management import BaseCommand from registrar.models import Domain @@ -14,16 +15,20 @@ class Command(BaseCommand): def __init__(self): """Sets global variables for code tidyness""" super().__init__() - # this array is used to store domains with errors, which are not - # successfully updated to disclose - domains_with_errors: List[str] = [] + # domains and transition domains that must be disclosed to true + self.domains_to_disclose: List[str] = [] + # domains with errors, which are not successfully updated to disclose + self.domains_with_errors: List[str] = [] + # domains that are successfully disclosed + self.disclosed_domain_contacts: List[str] = [] def handle(self, **options): """ Description for what update_security_email_disclose does """ logger.info("Updating security emails to public") - + + # Initializes domains that need to be disclosed domains = Domain.objects.filter() # Call security_contact on all domains to trigger saving contact information @@ -31,23 +36,24 @@ class Command(BaseCommand): contact = domain.security_contact domains_with_contact = Domain.objects.filter( - security_contact_registry_id=True + security_contact_registry_id__isnull=False ) logger.info("Found %d domains with security contact.", len(domains_with_contact)) # Update EPP contact for domains with a security contact for domain in domains_with_contact: try: - domain._update_epp_contact(contact=domain.security_contact_registry_id) - logger.info("Updated EPP contact for domain %d to disclose: %d", domain, domain.security_contact.disclose) + logger.info("Domain %s security contact: %s", domain, domain.security_contact) + domain._update_epp_contact(contact=domain.security_contact) + self.disclosed_domain_contacts.append(copy.deepcopy(domain.security_contact)) except Exception as err: # error condition if domain not in database - self.domains_with_errors.append(copy.deepcopy(domain.domain_name)) - logger.error(f"error retrieving domain {domain.domain_name}: {err}") + self.domains_with_errors.append(copy.deepcopy(domain.domain_info)) + logger.error(f"error retrieving domain {domain.domain_info}: {err}") - domains_disclosed = Domain.objects.filter( - security_contact_registry_id=True, - ) - logger.info("Updated %d domains to disclosed.", len(domains_disclosed)) + # Update transition domains to disclose + + # Inform user how many contacts were disclosed + logger.info("Updated %d contacts to disclosed.", len(self.disclosed_domain_contacts)) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index b3791d4b9..bdca1d4ef 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -1401,6 +1401,8 @@ class Domain(TimeStampedModel, DomainHelper): DF = epp.DiscloseField fields = {DF.EMAIL} disclose = is_security + # Delete after testing + logger.info("Updated domain contact to disclose: %s", disclose) # Will only disclose DF.EMAIL if its not the default return epp.Disclose( flag=disclose, From 0a13ff99766e3eca6705d1fc520314637e463823 Mon Sep 17 00:00:00 2001 From: Erin <121973038+erinysong@users.noreply.github.com> Date: Wed, 10 Jan 2024 10:38:17 -0800 Subject: [PATCH 024/120] Readd default email check for email disclose --- .../update_security_email_disclose.py | 24 ++++++++++--------- src/registrar/models/domain.py | 2 +- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/registrar/management/commands/update_security_email_disclose.py b/src/registrar/management/commands/update_security_email_disclose.py index 9ba1c58b7..ffefc815b 100644 --- a/src/registrar/management/commands/update_security_email_disclose.py +++ b/src/registrar/management/commands/update_security_email_disclose.py @@ -29,30 +29,32 @@ class Command(BaseCommand): logger.info("Updating security emails to public") # Initializes domains that need to be disclosed - domains = Domain.objects.filter() + statuses=["ready", "dns needed"] + domains = Domain.objects.filter( + state__in=statuses + ) # Call security_contact on all domains to trigger saving contact information for domain in domains: contact = domain.security_contact - domains_with_contact = Domain.objects.filter( - security_contact_registry_id__isnull=False - ) - logger.info("Found %d domains with security contact.", len(domains_with_contact)) + logger.info("Found %d domains with status Ready or DNS Needed.", len(domains)) # Update EPP contact for domains with a security contact - for domain in domains_with_contact: + for domain in domains: try: - logger.info("Domain %s security contact: %s", domain, domain.security_contact) - domain._update_epp_contact(contact=domain.security_contact) - self.disclosed_domain_contacts.append(copy.deepcopy(domain.security_contact)) + logger.info("Domain %s security contact: %s", domain.domain_info, domain.security_contact.email) + if domain.security_contact.email != "registrar@dotgov.gov": + domain._update_epp_contact(contact=domain.security_contact) + self.disclosed_domain_contacts.append(copy.deepcopy(domain.security_contact)) + else: + logger.info("Skipping disclose for %s security contact.", + domain.domain_info, domain.security_contact.email) except Exception as err: # error condition if domain not in database self.domains_with_errors.append(copy.deepcopy(domain.domain_info)) logger.error(f"error retrieving domain {domain.domain_info}: {err}") - # Update transition domains to disclose - # Inform user how many contacts were disclosed logger.info("Updated %d contacts to disclosed.", len(self.disclosed_domain_contacts)) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index bdca1d4ef..7f052a581 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -1400,7 +1400,7 @@ class Domain(TimeStampedModel, DomainHelper): is_security = contact.contact_type == contact.ContactTypeChoices.SECURITY DF = epp.DiscloseField fields = {DF.EMAIL} - disclose = is_security + disclose = is_security and contact.email != PublicContact.get_default_security().email # Delete after testing logger.info("Updated domain contact to disclose: %s", disclose) # Will only disclose DF.EMAIL if its not the default From 74b115cd1868b702c0d46d5980cf79ce537ee2ab Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 10 Jan 2024 14:33:01 -0700 Subject: [PATCH 025/120] CSS changes (PR suggestions) --- src/registrar/assets/sass/_theme/_base.scss | 4 ---- src/registrar/assets/sass/_theme/_tables.scss | 2 +- src/registrar/templates/home.html | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_base.scss b/src/registrar/assets/sass/_theme/_base.scss index 45f7a9383..1d936a255 100644 --- a/src/registrar/assets/sass/_theme/_base.scss +++ b/src/registrar/assets/sass/_theme/_base.scss @@ -125,7 +125,3 @@ abbr[title] { .flex-end { align-items: flex-end; } - -.remove-underline { - text-decoration: none !important; -} \ No newline at end of file diff --git a/src/registrar/assets/sass/_theme/_tables.scss b/src/registrar/assets/sass/_theme/_tables.scss index a42eefd6c..ec4ae3cd1 100644 --- a/src/registrar/assets/sass/_theme/_tables.scss +++ b/src/registrar/assets/sass/_theme/_tables.scss @@ -31,7 +31,7 @@ } // Fix margins in mobile view - @media screen { + @include at-media('tablet') { td.tablet-margin-top-2 { margin-top: 16px; } diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index 9ed1d924b..d65ffa152 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -157,7 +157,7 @@ From f87be48c474dd2a2ceac103ae9eb78822bd6888c Mon Sep 17 00:00:00 2001 From: Erin <121973038+erinysong@users.noreply.github.com> Date: Wed, 10 Jan 2024 14:35:59 -0800 Subject: [PATCH 026/120] Add logs for skipped disclose contacts --- .../commands/update_security_email_disclose.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/registrar/management/commands/update_security_email_disclose.py b/src/registrar/management/commands/update_security_email_disclose.py index ffefc815b..7bf2c2533 100644 --- a/src/registrar/management/commands/update_security_email_disclose.py +++ b/src/registrar/management/commands/update_security_email_disclose.py @@ -21,6 +21,8 @@ class Command(BaseCommand): self.domains_with_errors: List[str] = [] # domains that are successfully disclosed self.disclosed_domain_contacts: List[str] = [] + # domains that skip disclose due to having contact registrar@dotgov.gov + self.skipped_domain_contacts: List[str] = [] def handle(self, **options): """ @@ -48,14 +50,17 @@ class Command(BaseCommand): domain._update_epp_contact(contact=domain.security_contact) self.disclosed_domain_contacts.append(copy.deepcopy(domain.security_contact)) else: - logger.info("Skipping disclose for %s security contact.", + logger.info("Skipping disclose for %s security contact %s.", domain.domain_info, domain.security_contact.email) + self.skipped_domain_contacts.append(copy.deepcopy(domain.security_contact)) except Exception as err: # error condition if domain not in database self.domains_with_errors.append(copy.deepcopy(domain.domain_info)) logger.error(f"error retrieving domain {domain.domain_info}: {err}") - # Inform user how many contacts were disclosed + # Inform user how many contacts were disclosed and skipped logger.info("Updated %d contacts to disclosed.", len(self.disclosed_domain_contacts)) + logger.info("Skipped disclosing %d contacts with security email registrar@dotgov.gov.", + len(self.skipped_domain_contacts)) From 935172d5b27b69f7d9d75b84c69c920d65a20aa9 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 10 Jan 2024 15:44:19 -0700 Subject: [PATCH 027/120] Update application.py --- src/registrar/views/application.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/registrar/views/application.py b/src/registrar/views/application.py index 5a13b8a46..f4e76b6ee 100644 --- a/src/registrar/views/application.py +++ b/src/registrar/views/application.py @@ -154,6 +154,7 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView): def storage(self): # marking session as modified on every access # so that updates to nested keys are always saved + # TEST PUSHWILL DELETE self.request.session.modified = True return self.request.session.setdefault(self.prefix, {}) From 3c4ec9ab1ff58b209aa00e0b1bffcbf44a0407e7 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Wed, 10 Jan 2024 18:39:01 -0500 Subject: [PATCH 028/120] CSS cleanup --- src/registrar/assets/sass/_theme/_tables.scss | 25 +++++++++++-------- .../assets/sass/_theme/_usa-modal.scss | 7 ------ src/registrar/assets/sass/_theme/styles.scss | 1 - src/registrar/templates/home.html | 20 ++++++++------- 4 files changed, 26 insertions(+), 27 deletions(-) delete mode 100644 src/registrar/assets/sass/_theme/_usa-modal.scss diff --git a/src/registrar/assets/sass/_theme/_tables.scss b/src/registrar/assets/sass/_theme/_tables.scss index ec4ae3cd1..84c4791e5 100644 --- a/src/registrar/assets/sass/_theme/_tables.scss +++ b/src/registrar/assets/sass/_theme/_tables.scss @@ -26,16 +26,21 @@ padding-bottom: units(2px); } - th.action-col-custom-width { - width: 24.25%; - } - - // Fix margins in mobile view - @include at-media('tablet') { - td.tablet-margin-top-2 { - margin-top: 16px; - } - } + // Ticket #1510 + // @include at-media('desktop') { + // th:first-child { + // width: 220px; + // } + // th:nth-child(2) { + // width: 175px; + // } + // th:nth-child(3) { + // width: 130px; + // } + // th:nth-child(5) { + // width: 130px; + // } + // } } .dotgov-table { diff --git a/src/registrar/assets/sass/_theme/_usa-modal.scss b/src/registrar/assets/sass/_theme/_usa-modal.scss deleted file mode 100644 index 68a46a303..000000000 --- a/src/registrar/assets/sass/_theme/_usa-modal.scss +++ /dev/null @@ -1,7 +0,0 @@ -@use "cisa_colors" as *; -@use "uswds-core" as *; - -.usa-modal.wider-modal { - width: 40% !important; - max-width: 40% !important; -} diff --git a/src/registrar/assets/sass/_theme/styles.scss b/src/registrar/assets/sass/_theme/styles.scss index 499f9a806..8a2e1d2d3 100644 --- a/src/registrar/assets/sass/_theme/styles.scss +++ b/src/registrar/assets/sass/_theme/styles.scss @@ -17,7 +17,6 @@ @forward "tables"; @forward "sidenav"; @forward "register-form"; -@forward "usa-modal"; /*-------------------------------------------------- --- Admin ---------------------------------*/ diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index d65ffa152..9067c7927 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -9,7 +9,7 @@ {% if user.is_authenticated %} {# the entire logged in page goes here #} -
+

Manage your domains

@@ -31,7 +31,7 @@ {% endif %}

-
+

Domains

{% if domains %} @@ -44,13 +44,14 @@ + {% comment %} + #1510 {% if has_deletable_applications %} - {% endif %} + {% endif %} {% endcomment %} @@ -105,7 +106,7 @@ {% endif %} -
+

Domain requests

{% if domain_applications %}
Action
@@ -137,7 +138,7 @@ {% if has_deletable_applications %} - - {% if has_deletable_applications %} - - {% endif %} {% endfor %} From 3ed410aaa8c44185bd708a0fd4ebd4d4fd1ce35a Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 10 Jan 2024 16:46:35 -0700 Subject: [PATCH 030/120] Revert "Update home.html" This reverts commit e490c5de1d70e32cbd4e7e21b5eb9856b32eafe8. --- src/registrar/templates/home.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index 30580389a..9067c7927 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -90,6 +90,9 @@ {% endif %} + {% if has_deletable_applications %} + + {% endif %} {% endfor %} From dfec1d24821d884229d6e76f08957840d67a42ad Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 10 Jan 2024 16:47:40 -0700 Subject: [PATCH 031/120] Update home.html --- src/registrar/templates/home.html | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index 9067c7927..30580389a 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -90,9 +90,6 @@ {% endif %} - {% if has_deletable_applications %} - - {% endif %} {% endfor %} From e3bb4afbb12d5c92213e091b143f83d1b9490604 Mon Sep 17 00:00:00 2001 From: Erin <121973038+erinysong@users.noreply.github.com> Date: Wed, 10 Jan 2024 16:12:08 -0800 Subject: [PATCH 032/120] Add test for disclose_security_emails making EPP calls --- ...isclose.py => disclose_security_emails.py} | 0 src/registrar/tests/test_models_domain.py | 30 +++++++++++++++++++ 2 files changed, 30 insertions(+) rename src/registrar/management/commands/{update_security_email_disclose.py => disclose_security_emails.py} (100%) diff --git a/src/registrar/management/commands/update_security_email_disclose.py b/src/registrar/management/commands/disclose_security_emails.py similarity index 100% rename from src/registrar/management/commands/update_security_email_disclose.py rename to src/registrar/management/commands/disclose_security_emails.py diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index 9026832cd..6b3a7ba05 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -5,6 +5,7 @@ This file tests the various ways in which the registrar interacts with the regis """ from django.test import TestCase from django.db.utils import IntegrityError +from django.core.management import call_command from unittest.mock import MagicMock, patch, call import datetime from registrar.models import Domain, Host, HostIP @@ -548,6 +549,19 @@ class TestRegistrantContacts(MockEppLib): self.domain_contact._invalidate_cache() PublicContact.objects.all().delete() Domain.objects.all().delete() + + def run_disclose_security_emails(self): + """ + This method executes the disclose_security_emails command. + + The 'call_command' function from Django's management framework is then used to + execute the disclose_security_emails command. + """ + with patch( + "registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa + return_value=True, + ): + call_command("extend_expiration_dates") def test_no_security_email(self): """ @@ -963,6 +977,22 @@ class TestRegistrantContacts(MockEppLib): self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True) # Confirm that we are getting the desired email self.assertEqual(domain.security_contact.email, expectedSecContact.email) + + def test_disclose_security_emails(self): + """ + Tests that command disclose_security_emails runs successfully with + appropriate logs. + """ + domain, _ = Domain.objects.get_or_create(name="igorville.gov") + expectedSecContact = PublicContact.get_default_security() + expectedSecContact.domain = domain + expectedSecContact.email = "123@mail.gov" + domain.security_contact = expectedSecContact + self.run_disclose_security_emails() + + # running disclose_security_emails makes EPP calls + expectedUpdateCommand = self._convertPublicContactToEpp(expectedSecContact, disclose_email=True) + self.mockedSendFunction.assert_any_call(expectedUpdateCommand, cleaned=True) @skip("not implemented yet") def test_update_is_unsuccessful(self): From 273d457ac611eb58910c813605a449cac2e0fecc Mon Sep 17 00:00:00 2001 From: Erin <121973038+erinysong@users.noreply.github.com> Date: Wed, 10 Jan 2024 16:12:55 -0800 Subject: [PATCH 033/120] Reword test description --- src/registrar/tests/test_models_domain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index 6b3a7ba05..9c0a73a0a 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -981,7 +981,7 @@ class TestRegistrantContacts(MockEppLib): def test_disclose_security_emails(self): """ Tests that command disclose_security_emails runs successfully with - appropriate logs. + appropriate EPP calll to UpdateContact. """ domain, _ = Domain.objects.get_or_create(name="igorville.gov") expectedSecContact = PublicContact.get_default_security() From 4ddf96aed4a684aa0fb997994461368d764e6c4d Mon Sep 17 00:00:00 2001 From: Erin <121973038+erinysong@users.noreply.github.com> Date: Wed, 10 Jan 2024 16:15:40 -0800 Subject: [PATCH 034/120] Add description to disclose_security_emails script --- .../management/commands/disclose_security_emails.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/registrar/management/commands/disclose_security_emails.py b/src/registrar/management/commands/disclose_security_emails.py index 7bf2c2533..ff26e4882 100644 --- a/src/registrar/management/commands/disclose_security_emails.py +++ b/src/registrar/management/commands/disclose_security_emails.py @@ -1,4 +1,8 @@ -""""Script description""" +"""" +Converts all ready and DNS needed domains with a non-default public contact +to disclose their public contact. Created for Issue#1535 to resolve + disclose issue of domains with missing security emails. +""" import logging import copy @@ -9,8 +13,7 @@ from registrar.models import Domain logger = logging.getLogger(__name__) class Command(BaseCommand): - # TODO: write script description here - help = "Description" + help = "Disclose all nondefault domain security emails." def __init__(self): """Sets global variables for code tidyness""" @@ -26,7 +29,8 @@ class Command(BaseCommand): def handle(self, **options): """ - Description for what update_security_email_disclose does + Converts all ready and DNS needed domains with a non-default public contact + to disclose their public contact. """ logger.info("Updating security emails to public") From d34b49c685ce63b625c8525d40f65393299db032 Mon Sep 17 00:00:00 2001 From: Erin <121973038+erinysong@users.noreply.github.com> Date: Wed, 10 Jan 2024 16:34:30 -0800 Subject: [PATCH 035/120] Fix lint errors --- .../commands/disclose_security_emails.py | 40 +++++++++++-------- src/registrar/tests/test_models_domain.py | 4 +- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/registrar/management/commands/disclose_security_emails.py b/src/registrar/management/commands/disclose_security_emails.py index ff26e4882..a7ea0d2dd 100644 --- a/src/registrar/management/commands/disclose_security_emails.py +++ b/src/registrar/management/commands/disclose_security_emails.py @@ -12,6 +12,7 @@ from registrar.models import Domain logger = logging.getLogger(__name__) + class Command(BaseCommand): help = "Disclose all nondefault domain security emails." @@ -19,13 +20,13 @@ class Command(BaseCommand): """Sets global variables for code tidyness""" super().__init__() # domains and transition domains that must be disclosed to true - self.domains_to_disclose: List[str] = [] + self.contacts_saved: list[str] = [] # domains with errors, which are not successfully updated to disclose - self.domains_with_errors: List[str] = [] + self.domains_with_errors: list[str] = [] # domains that are successfully disclosed - self.disclosed_domain_contacts: List[str] = [] + self.disclosed_domain_contacts: list[str] = [] # domains that skip disclose due to having contact registrar@dotgov.gov - self.skipped_domain_contacts: List[str] = [] + self.skipped_domain_contacts: list[str] = [] def handle(self, **options): """ @@ -33,18 +34,20 @@ class Command(BaseCommand): to disclose their public contact. """ logger.info("Updating security emails to public") - + # Initializes domains that need to be disclosed - statuses=["ready", "dns needed"] - domains = Domain.objects.filter( - state__in=statuses - ) - + + statuses = ["ready", "dns needed"] + domains = Domain.objects.filter(state__in=statuses) + + logger.info("Found %d domains with status Ready or DNS Needed.", len(domains)) + # Call security_contact on all domains to trigger saving contact information for domain in domains: contact = domain.security_contact + self.contacts_saved.append(copy.deepcopy(contact)) - logger.info("Found %d domains with status Ready or DNS Needed.", len(domains)) + logger.info("Found %d security contacts.", len(self.contacts_saved)) # Update EPP contact for domains with a security contact for domain in domains: @@ -54,8 +57,11 @@ class Command(BaseCommand): domain._update_epp_contact(contact=domain.security_contact) self.disclosed_domain_contacts.append(copy.deepcopy(domain.security_contact)) else: - logger.info("Skipping disclose for %s security contact %s.", - domain.domain_info, domain.security_contact.email) + logger.info( + "Skipping disclose for %s security contact %s.", + domain.domain_info, + domain.security_contact.email, + ) self.skipped_domain_contacts.append(copy.deepcopy(domain.security_contact)) except Exception as err: # error condition if domain not in database @@ -64,7 +70,7 @@ class Command(BaseCommand): # Inform user how many contacts were disclosed and skipped logger.info("Updated %d contacts to disclosed.", len(self.disclosed_domain_contacts)) - logger.info("Skipped disclosing %d contacts with security email registrar@dotgov.gov.", - len(self.skipped_domain_contacts)) - - + logger.info( + "Skipped disclosing %d contacts with security email registrar@dotgov.gov.", + len(self.skipped_domain_contacts), + ) diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index 9c0a73a0a..81b63e3f6 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -549,7 +549,7 @@ class TestRegistrantContacts(MockEppLib): self.domain_contact._invalidate_cache() PublicContact.objects.all().delete() Domain.objects.all().delete() - + def run_disclose_security_emails(self): """ This method executes the disclose_security_emails command. @@ -977,7 +977,7 @@ class TestRegistrantContacts(MockEppLib): self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True) # Confirm that we are getting the desired email self.assertEqual(domain.security_contact.email, expectedSecContact.email) - + def test_disclose_security_emails(self): """ Tests that command disclose_security_emails runs successfully with From fa4789d6458bd343edb03558e3402cf5bc0af957 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 11 Jan 2024 08:23:44 -0700 Subject: [PATCH 036/120] Add role="button" --- src/registrar/templates/home.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index 30580389a..8f7eee0b7 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -71,7 +71,7 @@ {% endif %} @@ -164,7 +170,7 @@ - Delete {{ application.requested_domain.name|default:"New domain request" }} + Delete {{ application.requested_domain.name|default:"New domain request ("|add:application.created_at|add:")" }}
- {% if application.requested_domain %} - {% include 'includes/modal.html' with modal_heading="Are you sure you want to delete "|add:application.requested_domain.name|add:"?" modal_description="This will remove the domain request from the .gov registrar. This action cannot be undone." modal_button=modal_button|safe %} - {% else %} - {% include 'includes/modal.html' with modal_heading="Are you sure you want to delete your domain request?" modal_description="This will remove the domain request from the .gov registrar. This action cannot be undone." modal_button=modal_button|safe %} - {% endif %} + {% include 'includes/modal.html' with modal_heading="Are you sure you want to delete "|add:application.requested_domain.name|add:"?" modal_description="This will remove the domain request from the .gov registrar. This action cannot be undone." modal_button=modal_button|safe %}
{% endif %} diff --git a/src/registrar/views/application.py b/src/registrar/views/application.py index c76d285a7..80ff1eff1 100644 --- a/src/registrar/views/application.py +++ b/src/registrar/views/application.py @@ -143,93 +143,13 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView): except DomainApplication.DoesNotExist: logger.debug("Application id %s did not have a DomainApplication" % id) - draft_domain = self._create_default_draft_domain() - - # Check added for linting purposes - if self.request.user and isinstance(self.request.user, User): - self._application = DomainApplication.objects.create( - creator=self.request.user, - requested_domain=draft_domain, - ) - else: - # TODO - Need some sort of front end display for this - raise ValueError("Invalid type for user") + self._application = DomainApplication.objects.create( + creator=self.request.user + ) self.storage["application_id"] = self._application.id return self._application - def _create_default_draft_domain(self): - "Set a default draft name for if the user exits without completing" - default_draft_text = "New domain request" - - # Does the user have any incomplete drafts? - existing_applications = DomainApplication.objects.filter( - Q(requested_domain=None) | Q(requested_domain__is_incomplete=True), - creator=self.request.user, - ) - - name_field = "requested_domain__name" - - incomplete_drafts = ( - existing_applications.exclude(requested_domain=None) - .filter(requested_domain__name__icontains=default_draft_text) - .order_by(name_field) - ) - - incomplete_draft_names = incomplete_drafts.values_list(name_field, flat=True) - - proposed_draft_number = incomplete_drafts.count() + 1 - draft_number = 1 - for application in existing_applications: - if application.requested_domain is not None and application.requested_domain.name is not None: - name = application.requested_domain.name - - # If we already have a list of draft numbers, base the - # subsequent number off of the last numbered field. - # This is to avoid a scenario in which drafts 1, 2, 3 and exist - # and 2 is deleted - meaning we would get two duplicate "3"s if we added another - if name in incomplete_draft_names: - # Get the last numbered draft - last_draft = incomplete_drafts.last() - last_draft_number = last_draft.requested_domain.draft_number - - smallest_number = self._find_smallest_missing_number(incomplete_drafts) - smallest_name = f"New domain request {smallest_number}" - if smallest_name not in incomplete_draft_names: - draft_number = smallest_number - elif proposed_draft_number == last_draft_number: - # If the draft number we are trying to create matches the last draft number, - # simply add one to that number - draft_number = last_draft_number + 1 - - draft_domain = DraftDomain( - # Save a blank string rather then None due to DB requirements - name="", - draft_number=draft_number, - is_incomplete=True, - ) - # Generate a default name based off of a draft_number - draft_domain.name = draft_domain.get_default_request_name() - draft_domain.save() - - return draft_domain - - def _find_smallest_missing_number(self, incomplete_drafts): - draft_numbers = [] - for draft in incomplete_drafts: - number = draft.requested_domain.draft_number - if number is not None: - draft_numbers.append(number) - - draft_numbers = sorted(draft_numbers) - smallest_missing = 1 - for number in draft_numbers: - if number == smallest_missing: - smallest_missing += 1 - elif number > smallest_missing: - break - return smallest_missing - @property def storage(self): # marking session as modified on every access @@ -409,7 +329,7 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView): # Build the submit button that we'll pass to the modal. modal_button = '" # Concatenate the modal header that we'll pass to the modal. - if self.application.requested_domain and not self.application.requested_domain.is_incomplete: + 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" @@ -556,14 +476,6 @@ class DotgovDomain(ApplicationWizard): context["federal_type"] = self.application.federal_type return context - def post(self, request, *args, **kwargs): - """Override for the post method to mark the DraftDomain as complete""" - # Set the DraftDomain to "complete" - print(f"what is the request at this time? {request}") - self.application.requested_domain.is_incomplete = False - response = super().post(request, *args, **kwargs) - return response - class Purpose(ApplicationWizard): template_name = "application_purpose.html" diff --git a/src/registrar/views/index.py b/src/registrar/views/index.py index f9a658942..3c0f7c723 100644 --- a/src/registrar/views/index.py +++ b/src/registrar/views/index.py @@ -1,6 +1,8 @@ +from django.utils import timezone from django.shortcuts import render from registrar.models import DomainApplication, Domain, UserDomainRole +from registrar.models.draft_domain import DraftDomain def index(request): @@ -12,6 +14,22 @@ def index(request): # the active applications table applications = DomainApplication.objects.filter(creator=request.user).exclude(status="approved") + + valid_statuses = [DomainApplication.ApplicationStatus.STARTED, DomainApplication.ApplicationStatus.WITHDRAWN] + + # Create a placeholder DraftDomain for each incomplete draft + deletable_applications = applications.filter(status__in=valid_statuses, requested_domain=None) + for application in applications: + if application in deletable_applications: + created_at = application.created_at.strftime("%b. %d, %Y, %I:%M %p UTC") + _name = f"New domain request ({created_at})" + default_draft_domain = DraftDomain( + name=_name, + is_complete=False + ) + + application.requested_domain = default_draft_domain + # Pass the final context to the application context["domain_applications"] = applications @@ -22,17 +40,16 @@ def index(request): context["domains"] = domains # Determine if the user will see applications that they can delete - valid_statuses = [DomainApplication.ApplicationStatus.STARTED, DomainApplication.ApplicationStatus.WITHDRAWN] - has_deletable_applications = applications.filter(status__in=valid_statuses).exists() + has_deletable_applications = deletable_applications.exists() context["has_deletable_applications"] = has_deletable_applications - if has_deletable_applications: + + # Add the delete modal button to the context modal_button = ( '' ) - context["modal_button"] = modal_button return render(request, "home.html", context) From f623d00fc229d102df05a682dc3207f77238ca22 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 16 Jan 2024 14:16:48 -0700 Subject: [PATCH 052/120] Index.py refactor --- src/registrar/views/index.py | 66 ++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/src/registrar/views/index.py b/src/registrar/views/index.py index 3c0f7c723..0a682b855 100644 --- a/src/registrar/views/index.py +++ b/src/registrar/views/index.py @@ -9,41 +9,21 @@ def index(request): """This page is available to anyone without logging in.""" context = {} if request.user.is_authenticated: - # Let's exclude the approved applications since our - # domain_applications context will be used to populate - # the active applications table - applications = DomainApplication.objects.filter(creator=request.user).exclude(status="approved") - - valid_statuses = [DomainApplication.ApplicationStatus.STARTED, DomainApplication.ApplicationStatus.WITHDRAWN] - - # Create a placeholder DraftDomain for each incomplete draft - deletable_applications = applications.filter(status__in=valid_statuses, requested_domain=None) - for application in applications: - if application in deletable_applications: - created_at = application.created_at.strftime("%b. %d, %Y, %I:%M %p UTC") - _name = f"New domain request ({created_at})" - default_draft_domain = DraftDomain( - name=_name, - is_complete=False - ) - - application.requested_domain = default_draft_domain - - # Pass the final context to the application + # Get all domain applications the user has access to + applications, deletable_applications = _get_applications(request) context["domain_applications"] = applications - user_domain_roles = UserDomainRole.objects.filter(user=request.user) - domain_ids = user_domain_roles.values_list("domain_id", flat=True) - domains = Domain.objects.filter(id__in=domain_ids) - + # Get all domains the user has access to + domains = _get_domains(request) context["domains"] = domains # Determine if the user will see applications that they can delete has_deletable_applications = deletable_applications.exists() context["has_deletable_applications"] = has_deletable_applications - if has_deletable_applications: + # If they can delete applications, add the delete button to the context + if has_deletable_applications: # Add the delete modal button to the context modal_button = ( '
{{ application.get_status_display }} {% if application.status == "started" or application.status == "action needed" or application.status == "withdrawn" %} - + @@ -152,12 +153,13 @@ + {% if application.status == "started" or application.status == "withdrawn" %} @@ -168,7 +170,7 @@
Date: Wed, 10 Jan 2024 16:44:18 -0700 Subject: [PATCH 029/120] Update home.html --- src/registrar/templates/home.html | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index d65ffa152..e62a25de2 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -89,9 +89,6 @@ {% endif %}
- + {{ application.requested_domain.name|default:"New domain request" }} {% else %} - + From 10b589a1ecc09c98e3a54551bd096668bc7cde63 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 11 Jan 2024 15:21:08 -0700 Subject: [PATCH 037/120] Use DB for draft domain naming in progress --- ...in_is_incomplete_alter_draftdomain_name.py | 22 +++++ src/registrar/models/draft_domain.py | 6 +- src/registrar/views/application.py | 90 ++++++++++++++++++- src/registrar/views/index.py | 8 +- 4 files changed, 118 insertions(+), 8 deletions(-) create mode 100644 src/registrar/migrations/0063_draftdomain_is_incomplete_alter_draftdomain_name.py diff --git a/src/registrar/migrations/0063_draftdomain_is_incomplete_alter_draftdomain_name.py b/src/registrar/migrations/0063_draftdomain_is_incomplete_alter_draftdomain_name.py new file mode 100644 index 000000000..fcb462e22 --- /dev/null +++ b/src/registrar/migrations/0063_draftdomain_is_incomplete_alter_draftdomain_name.py @@ -0,0 +1,22 @@ +# Generated by Django 4.2.7 on 2024-01-11 19:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0062_alter_host_name"), + ] + + operations = [ + migrations.AddField( + model_name="draftdomain", + name="is_incomplete", + field=models.BooleanField(default=False, help_text="Determines if this Draft is complete or not"), + ), + migrations.AlterField( + model_name="draftdomain", + name="name", + field=models.CharField(help_text="Fully qualified domain name", max_length=253), + ), + ] diff --git a/src/registrar/models/draft_domain.py b/src/registrar/models/draft_domain.py index fc70a18f3..4d79494f5 100644 --- a/src/registrar/models/draft_domain.py +++ b/src/registrar/models/draft_domain.py @@ -17,6 +17,10 @@ class DraftDomain(TimeStampedModel, DomainHelper): name = models.CharField( max_length=253, blank=False, - default=None, # prevent saving without a value help_text="Fully qualified domain name", ) + + is_incomplete = models.BooleanField( + default=False, + help_text="Determines if this Draft is complete or not" + ) \ No newline at end of file diff --git a/src/registrar/views/application.py b/src/registrar/views/application.py index 9b60c49ab..deda6d0d2 100644 --- a/src/registrar/views/application.py +++ b/src/registrar/views/application.py @@ -1,5 +1,7 @@ import logging +import re +from django.db.models import Q from django.http import Http404, HttpResponse, HttpResponseRedirect from django.shortcuts import redirect, render from django.urls import resolve, reverse @@ -10,6 +12,7 @@ from django.contrib import messages from registrar.forms import application_wizard as forms from registrar.models import DomainApplication +from registrar.models.draft_domain import DraftDomain from registrar.utility import StrEnum from registrar.views.utility import StepsHelper from registrar.views.utility.permission_views import DomainApplicationPermissionDeleteView @@ -139,14 +142,97 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView): return self._application except DomainApplication.DoesNotExist: logger.debug("Application id %s did not have a DomainApplication" % id) - + + # TODO - revert back to using draft_name + draft_domain = self._create_default_draft_domain() self._application = DomainApplication.objects.create( - creator=self.request.user, # type: ignore + creator=self.request.user, + requested_domain=draft_domain, ) self.storage["application_id"] = self._application.id return self._application + def _create_default_draft_domain(self): + "Set a default draft name for if the user exits without completing" + default_draft_text = "New domain request" + + # Does the user have any incomplete drafts? + existing_applications = DomainApplication.objects.filter( + Q(requested_domain=None) | Q(requested_domain__is_incomplete=True), + creator=self.request.user, + ) + + name_field = "requested_domain__name" + + incomplete_drafts = existing_applications.exclude(requested_domain=None).filter( + requested_domain__name__icontains=default_draft_text + ).order_by(name_field) + + incomplete_draft_names = incomplete_drafts.values_list(name_field, flat=True) + + proposed_draft_number = incomplete_drafts.count() + 1 + draft_name = f"New domain request {proposed_draft_number}" + for application in existing_applications: + if application.requested_domain is not None and application.requested_domain.name is not None: + name = application.requested_domain.name + + # If we already have a list of draft numbers, base the + # subsequent number off of the last numbered field. + # This is to avoid a scenario in which drafts 1, 2, 3 and exist + # and 2 is deleted - meaning we would get two duplicate "3"s if we added another + if name in incomplete_draft_names: + # Get the last numbered draft + last_draft = incomplete_draft_names.last() + last_draft_number = self._parse_first_number_from_string(last_draft) + + smallest_number = self._find_smallest_missing_number(incomplete_draft_names) + smallest_name = f"New domain request {smallest_number}" + if smallest_name not in incomplete_draft_names: + draft_name = smallest_name + elif proposed_draft_number == last_draft_number: + # If the draft number we are trying to create matches the last draft number, + # simply add one to that number + draft_name = f"New domain request {last_draft_number + 1}" + + # Handle edge case if the user has an obscene number of domain drafts + if len(draft_name) > 253: + draft_name = default_draft_text + + draft_domain = DraftDomain( + name=draft_name, + is_incomplete=True, + ) + draft_domain.save() + + return draft_domain + + def _find_smallest_missing_number(self, incomplete_drafts): + draft_numbers = [] + for draft in incomplete_drafts: + # Parse the number out of the text + number = self._parse_first_number_from_string(draft) + if number is not None: + draft_numbers.append(number) + + draft_numbers = sorted(draft_numbers) + smallest_missing = 1 + for number in draft_numbers: + if number == smallest_missing: + smallest_missing += 1 + elif number > smallest_missing: + break + return smallest_missing + + def _parse_first_number_from_string(self, string_to_parse: str) -> int | None: + """Given a `string_to_parse`, try to find any number in it and return that. + Returns None if no match is found""" + + # Parse the number out of the text + match = re.search("\d+", string_to_parse) + + number = int(match.group()) if match else None + return number @property def storage(self): # marking session as modified on every access diff --git a/src/registrar/views/index.py b/src/registrar/views/index.py index 0e7528e08..44345e72f 100644 --- a/src/registrar/views/index.py +++ b/src/registrar/views/index.py @@ -12,13 +12,11 @@ def index(request): # domain_applications context will be used to populate # the active applications table applications = DomainApplication.objects.filter(creator=request.user).exclude(status="approved") - + # Adds display logic for empty domain requests - counter = 1 for application in applications: - if not application.requested_domain or not application.requested_domain.name: - application.requested_domain = DraftDomain(name=f"New domain request {counter}") - counter += 1 + if not application.requested_domain.name: + application.requested_domain.name = application.requested_domain.draft_name # Pass the final context to the application context["domain_applications"] = applications From 4d8cb4534553e3b0ca7e2a44862aa6d02be2d58d Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 12 Jan 2024 09:24:14 -0700 Subject: [PATCH 038/120] Persist information correctly --- src/registrar/forms/application_wizard.py | 5 ++- ...draft_number_draftdomain_is_incomplete.py} | 14 ++++---- src/registrar/models/domain_application.py | 5 +++ src/registrar/models/draft_domain.py | 13 ++++++- src/registrar/views/application.py | 34 +++++++------------ 5 files changed, 42 insertions(+), 29 deletions(-) rename src/registrar/migrations/{0063_draftdomain_is_incomplete_alter_draftdomain_name.py => 0063_draftdomain_draft_number_draftdomain_is_incomplete.py} (63%) diff --git a/src/registrar/forms/application_wizard.py b/src/registrar/forms/application_wizard.py index 2d151a08e..d731c3965 100644 --- a/src/registrar/forms/application_wizard.py +++ b/src/registrar/forms/application_wizard.py @@ -511,7 +511,10 @@ class DotGovDomainForm(RegistrarForm): values = {} requested_domain = getattr(obj, "requested_domain", None) if requested_domain is not None: - values["requested_domain"] = Domain.sld(requested_domain.name) + is_incomplete = requested_domain.is_incomplete + # Only display a preexisting name if the application was completed + domain_name = requested_domain.name if not is_incomplete else "" + values["requested_domain"] = Domain.sld(domain_name) return values def clean_requested_domain(self): diff --git a/src/registrar/migrations/0063_draftdomain_is_incomplete_alter_draftdomain_name.py b/src/registrar/migrations/0063_draftdomain_draft_number_draftdomain_is_incomplete.py similarity index 63% rename from src/registrar/migrations/0063_draftdomain_is_incomplete_alter_draftdomain_name.py rename to src/registrar/migrations/0063_draftdomain_draft_number_draftdomain_is_incomplete.py index fcb462e22..1ea545c70 100644 --- a/src/registrar/migrations/0063_draftdomain_is_incomplete_alter_draftdomain_name.py +++ b/src/registrar/migrations/0063_draftdomain_draft_number_draftdomain_is_incomplete.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.7 on 2024-01-11 19:40 +# Generated by Django 4.2.7 on 2024-01-12 16:17 from django.db import migrations, models @@ -9,14 +9,16 @@ class Migration(migrations.Migration): ] operations = [ + migrations.AddField( + model_name="draftdomain", + name="draft_number", + field=models.IntegerField( + help_text="The draft number in the event a user doesn't save at this stage", null=True + ), + ), migrations.AddField( model_name="draftdomain", name="is_incomplete", field=models.BooleanField(default=False, help_text="Determines if this Draft is complete or not"), ), - migrations.AlterField( - model_name="draftdomain", - name="name", - field=models.CharField(help_text="Fully qualified domain name", max_length=253), - ), ] diff --git a/src/registrar/models/domain_application.py b/src/registrar/models/domain_application.py index 196449bfa..d93c429f9 100644 --- a/src/registrar/models/domain_application.py +++ b/src/registrar/models/domain_application.py @@ -631,6 +631,11 @@ class DomainApplication(TimeStampedModel): # Update submission_date to today self.submission_date = timezone.now().date() + + # Mark the draft domain as complete + if self.requested_domain.is_incomplete: + self.requested_domain.is_incomplete = False + self.save() self._send_status_update_email( diff --git a/src/registrar/models/draft_domain.py b/src/registrar/models/draft_domain.py index 4d79494f5..8a2ced775 100644 --- a/src/registrar/models/draft_domain.py +++ b/src/registrar/models/draft_domain.py @@ -17,10 +17,21 @@ class DraftDomain(TimeStampedModel, DomainHelper): name = models.CharField( max_length=253, blank=False, + default=None, # prevent saving without a value help_text="Fully qualified domain name", ) + draft_number = models.IntegerField( + null=True, + help_text="The draft number in the event a user doesn't save at this stage", + ) + is_incomplete = models.BooleanField( default=False, help_text="Determines if this Draft is complete or not" - ) \ No newline at end of file + ) + + def get_default_request_name(self): + """Returns the draft name that would be used for applications if no name exists""" + return f"New domain request {self.draft_number}" + \ No newline at end of file diff --git a/src/registrar/views/application.py b/src/registrar/views/application.py index deda6d0d2..b03de7774 100644 --- a/src/registrar/views/application.py +++ b/src/registrar/views/application.py @@ -1,5 +1,6 @@ import logging import re +from typing import List from django.db.models import Q from django.http import Http404, HttpResponse, HttpResponseRedirect @@ -172,7 +173,7 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView): incomplete_draft_names = incomplete_drafts.values_list(name_field, flat=True) proposed_draft_number = incomplete_drafts.count() + 1 - draft_name = f"New domain request {proposed_draft_number}" + draft_number = 1 for application in existing_applications: if application.requested_domain is not None and application.requested_domain.name is not None: name = application.requested_domain.name @@ -183,26 +184,26 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView): # and 2 is deleted - meaning we would get two duplicate "3"s if we added another if name in incomplete_draft_names: # Get the last numbered draft - last_draft = incomplete_draft_names.last() - last_draft_number = self._parse_first_number_from_string(last_draft) + last_draft = incomplete_drafts.last() + last_draft_number = last_draft.draft_number - smallest_number = self._find_smallest_missing_number(incomplete_draft_names) + smallest_number = self._find_smallest_missing_number(incomplete_drafts) smallest_name = f"New domain request {smallest_number}" if smallest_name not in incomplete_draft_names: - draft_name = smallest_name + draft_number = smallest_number elif proposed_draft_number == last_draft_number: # If the draft number we are trying to create matches the last draft number, # simply add one to that number - draft_name = f"New domain request {last_draft_number + 1}" - - # Handle edge case if the user has an obscene number of domain drafts - if len(draft_name) > 253: - draft_name = default_draft_text + draft_number = last_draft_number + 1 draft_domain = DraftDomain( - name=draft_name, + # Save a blank string rather then None due to DB requirements + name="", + draft_number=draft_number, is_incomplete=True, ) + # Generate a default name based off of a draft_number + draft_domain.name = draft_domain.get_default_request_name() draft_domain.save() return draft_domain @@ -211,7 +212,7 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView): draft_numbers = [] for draft in incomplete_drafts: # Parse the number out of the text - number = self._parse_first_number_from_string(draft) + number = draft.draft_number if number is not None: draft_numbers.append(number) @@ -224,15 +225,6 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView): break return smallest_missing - def _parse_first_number_from_string(self, string_to_parse: str) -> int | None: - """Given a `string_to_parse`, try to find any number in it and return that. - Returns None if no match is found""" - - # Parse the number out of the text - match = re.search("\d+", string_to_parse) - - number = int(match.group()) if match else None - return number @property def storage(self): # marking session as modified on every access From 6f09c4b6533c5d5e87446adab1e21ab7300a6f62 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 12 Jan 2024 09:42:20 -0700 Subject: [PATCH 039/120] Remove merge weirdnesss --- src/registrar/tests/test_views.py | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index d99d311d0..56f3cb5a5 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -178,18 +178,6 @@ class LoggedInTests(TestWithUser): # clean up application.delete() - def test_home_lists_domains(self): - response = self.client.get("/") - domain, _ = Domain.objects.get_or_create(name="igorville.gov") - self.assertNotContains(response, "igorville.gov") - role, _ = UserDomainRole.objects.get_or_create(user=self.user, domain=domain, role=UserDomainRole.Roles.MANAGER) - response = self.client.get("/") - # count = 2 because it is also in screenreader content - self.assertContains(response, "igorville.gov", count=2) - self.assertContains(response, "Expired") - # clean up - role.delete() - def test_application_form_view(self): response = self.client.get("/register/", follow=True) self.assertContains( From 5c5fcfcf866a469bed6485c033f0939d187c9d8e Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 12 Jan 2024 09:49:36 -0700 Subject: [PATCH 040/120] Update application.py --- src/registrar/views/application.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/views/application.py b/src/registrar/views/application.py index b03de7774..fac722275 100644 --- a/src/registrar/views/application.py +++ b/src/registrar/views/application.py @@ -185,7 +185,7 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView): if name in incomplete_draft_names: # Get the last numbered draft last_draft = incomplete_drafts.last() - last_draft_number = last_draft.draft_number + last_draft_number = last_draft.requested_domain.draft_number smallest_number = self._find_smallest_missing_number(incomplete_drafts) smallest_name = f"New domain request {smallest_number}" From f5fb4b24b88fe0e173d72e84746e2c6718c85993 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 12 Jan 2024 09:57:45 -0700 Subject: [PATCH 041/120] Update application.py --- src/registrar/views/application.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/registrar/views/application.py b/src/registrar/views/application.py index fac722275..c2fe823aa 100644 --- a/src/registrar/views/application.py +++ b/src/registrar/views/application.py @@ -211,8 +211,7 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView): def _find_smallest_missing_number(self, incomplete_drafts): draft_numbers = [] for draft in incomplete_drafts: - # Parse the number out of the text - number = draft.draft_number + number = draft.requested_domain.draft_number if number is not None: draft_numbers.append(number) From cde4ab2fef12f447d7101f78fecc7cd1e2d764e0 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 12 Jan 2024 10:39:01 -0700 Subject: [PATCH 042/120] Remove old check --- src/registrar/views/index.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/registrar/views/index.py b/src/registrar/views/index.py index 44345e72f..bcff0affd 100644 --- a/src/registrar/views/index.py +++ b/src/registrar/views/index.py @@ -12,11 +12,6 @@ def index(request): # domain_applications context will be used to populate # the active applications table applications = DomainApplication.objects.filter(creator=request.user).exclude(status="approved") - - # Adds display logic for empty domain requests - for application in applications: - if not application.requested_domain.name: - application.requested_domain.name = application.requested_domain.draft_name # Pass the final context to the application context["domain_applications"] = applications From 8b2a0c928d05cc8831f157b1c1870a3e30d52480 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 12 Jan 2024 11:18:49 -0700 Subject: [PATCH 043/120] Set is_incomplete flag on post --- src/registrar/views/application.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/registrar/views/application.py b/src/registrar/views/application.py index adde07e96..9aed1fa4d 100644 --- a/src/registrar/views/application.py +++ b/src/registrar/views/application.py @@ -549,6 +549,14 @@ class DotgovDomain(ApplicationWizard): context["organization_type"] = self.application.organization_type context["federal_type"] = self.application.federal_type return context + + def post(self, request, *args, **kwargs): + """Override for the post method to mark the DraftDomain as complete""" + response = super().post(request, *args, **kwargs) + # Set the DraftDomain to "complete" + self.application.requested_domain.is_incomplete = False + self.application.save() + return response class Purpose(ApplicationWizard): From 93107877f32276b6a49629e55d1e9497ff84908c Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 12 Jan 2024 11:42:00 -0700 Subject: [PATCH 044/120] Remove bad role --- src/registrar/templates/home.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index b496aa265..54daa4aa4 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -72,7 +72,7 @@ {% endif %} - + {% if application.status == "started" or application.status == "action needed" or application.status == "withdrawn" %} - + Edit {{ application.requested_domain.name|default:"New domain request" }} {% else %} - + From c9495667440da0397e065b35359c34aca87b8862 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 12 Jan 2024 12:06:44 -0700 Subject: [PATCH 045/120] Fix post bug --- src/registrar/views/application.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/registrar/views/application.py b/src/registrar/views/application.py index 9aed1fa4d..482bc2f10 100644 --- a/src/registrar/views/application.py +++ b/src/registrar/views/application.py @@ -552,10 +552,10 @@ class DotgovDomain(ApplicationWizard): def post(self, request, *args, **kwargs): """Override for the post method to mark the DraftDomain as complete""" - response = super().post(request, *args, **kwargs) # Set the DraftDomain to "complete" + print(f"what is the request at this time? {request}") self.application.requested_domain.is_incomplete = False - self.application.save() + response = super().post(request, *args, **kwargs) return response From 0b503442a8388cf6494520a03063234d28024312 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 12 Jan 2024 15:50:04 -0500 Subject: [PATCH 046/120] Change header to Current websites --- src/registrar/templates/application_status.html | 2 +- .../templates/emails/includes/application_summary.txt | 2 +- src/registrar/tests/test_emails.py | 6 +++--- src/registrar/views/application.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/registrar/templates/application_status.html b/src/registrar/templates/application_status.html index fbabf39a7..590d00b28 100644 --- a/src/registrar/templates/application_status.html +++ b/src/registrar/templates/application_status.html @@ -90,7 +90,7 @@ {% endif %} {% if domainapplication.current_websites.all %} - {% include "includes/summary_item.html" with title='Current website for your organization' value=domainapplication.current_websites.all list='true' heading_level=heading_level %} + {% include "includes/summary_item.html" with title='Current websites' value=domainapplication.current_websites.all list='true' heading_level=heading_level %} {% endif %} {% if domainapplication.requested_domain %} diff --git a/src/registrar/templates/emails/includes/application_summary.txt b/src/registrar/templates/emails/includes/application_summary.txt index c628e1074..ee2564613 100644 --- a/src/registrar/templates/emails/includes/application_summary.txt +++ b/src/registrar/templates/emails/includes/application_summary.txt @@ -17,7 +17,7 @@ About your organization: Authorizing official: {% spaceless %}{% include "emails/includes/contact.txt" with contact=application.authorizing_official %}{% endspaceless %} {% if application.current_websites.exists %}{# if block makes a newline #} -Current website for your organization: {% for site in application.current_websites.all %} +Current websites: {% for site in application.current_websites.all %} {% spaceless %}{{ site.website }}{% endspaceless %} {% endfor %}{% endif %} .gov domain: diff --git a/src/registrar/tests/test_emails.py b/src/registrar/tests/test_emails.py index 61c950255..bc0513a07 100644 --- a/src/registrar/tests/test_emails.py +++ b/src/registrar/tests/test_emails.py @@ -47,7 +47,7 @@ class TestEmails(TestCase): # check for optional things self.assertIn("Other employees from your organization:", body) self.assertIn("Testy2 Tester2", body) - self.assertIn("Current website for your organization:", body) + self.assertIn("Current websites:", body) self.assertIn("city.com", body) self.assertIn("About your organization:", body) self.assertIn("Anything else", body) @@ -61,7 +61,7 @@ class TestEmails(TestCase): application.submit() _, kwargs = self.mock_client.send_email.call_args body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] - self.assertNotIn("Current website for your organization:", body) + self.assertNotIn("Current websites:", body) # spacing should be right between adjacent elements self.assertRegex(body, r"5555\n\n.gov domain:") @@ -74,7 +74,7 @@ class TestEmails(TestCase): application.submit() _, kwargs = self.mock_client.send_email.call_args body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] - self.assertIn("Current website for your organization:", body) + self.assertIn("Current websites:", body) # spacing should be right between adjacent elements self.assertRegex(body, r"5555\n\nCurrent website for") self.assertRegex(body, r"city.com\n\n.gov domain:") diff --git a/src/registrar/views/application.py b/src/registrar/views/application.py index 486964e66..3eabe574f 100644 --- a/src/registrar/views/application.py +++ b/src/registrar/views/application.py @@ -83,7 +83,7 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView): Step.ORGANIZATION_CONTACT: _("Organization name and mailing address"), Step.ABOUT_YOUR_ORGANIZATION: _("About your organization"), Step.AUTHORIZING_OFFICIAL: _("Authorizing official"), - Step.CURRENT_SITES: _("Current website for your organization"), + Step.CURRENT_SITES: _("Current websites"), Step.DOTGOV_DOMAIN: _(".gov domain"), Step.PURPOSE: _("Purpose of your domain"), Step.YOUR_CONTACT: _("Your contact information"), From 9300b06d4c56a19176bb1bf62e56afa88647eef5 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 12 Jan 2024 15:55:56 -0500 Subject: [PATCH 047/120] Fixed test test_submission_confirmation_current_website_spacing --- src/registrar/tests/test_emails.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/tests/test_emails.py b/src/registrar/tests/test_emails.py index bc0513a07..3f5b7fc18 100644 --- a/src/registrar/tests/test_emails.py +++ b/src/registrar/tests/test_emails.py @@ -76,7 +76,7 @@ class TestEmails(TestCase): body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] self.assertIn("Current websites:", body) # spacing should be right between adjacent elements - self.assertRegex(body, r"5555\n\nCurrent website for") + self.assertRegex(body, r"5555\n\nCurrent websites for") self.assertRegex(body, r"city.com\n\n.gov domain:") @boto3_mocking.patching From 938e501a6cafa1d706dbbf4399d87898bda2e0e7 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 12 Jan 2024 15:56:16 -0500 Subject: [PATCH 048/120] Fixed test test_submission_confirmation_current_website_spacing --- src/registrar/tests/test_emails.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/tests/test_emails.py b/src/registrar/tests/test_emails.py index 3f5b7fc18..fcdd46577 100644 --- a/src/registrar/tests/test_emails.py +++ b/src/registrar/tests/test_emails.py @@ -76,7 +76,7 @@ class TestEmails(TestCase): body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] self.assertIn("Current websites:", body) # spacing should be right between adjacent elements - self.assertRegex(body, r"5555\n\nCurrent websites for") + self.assertRegex(body, r"5555\n\nCurrent websites") self.assertRegex(body, r"city.com\n\n.gov domain:") @boto3_mocking.patching From a3f78e2ac7250de3dac401aadb90a3a93ab9575d Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 16 Jan 2024 08:52:03 -0700 Subject: [PATCH 049/120] Linting and fix unit test --- src/registrar/models/draft_domain.py | 6 +----- src/registrar/views/application.py | 20 ++++++++++---------- src/registrar/views/index.py | 1 - 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/registrar/models/draft_domain.py b/src/registrar/models/draft_domain.py index 8a2ced775..5fe00257e 100644 --- a/src/registrar/models/draft_domain.py +++ b/src/registrar/models/draft_domain.py @@ -26,12 +26,8 @@ class DraftDomain(TimeStampedModel, DomainHelper): help_text="The draft number in the event a user doesn't save at this stage", ) - is_incomplete = models.BooleanField( - default=False, - help_text="Determines if this Draft is complete or not" - ) + is_incomplete = models.BooleanField(default=False, help_text="Determines if this Draft is complete or not") def get_default_request_name(self): """Returns the draft name that would be used for applications if no name exists""" return f"New domain request {self.draft_number}" - \ No newline at end of file diff --git a/src/registrar/views/application.py b/src/registrar/views/application.py index 482bc2f10..bef707cb6 100644 --- a/src/registrar/views/application.py +++ b/src/registrar/views/application.py @@ -1,6 +1,4 @@ import logging -import re -from typing import List from django.db.models import Q from django.http import Http404, HttpResponse, HttpResponseRedirect @@ -143,7 +141,7 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView): return self._application except DomainApplication.DoesNotExist: logger.debug("Application id %s did not have a DomainApplication" % id) - + # TODO - revert back to using draft_name draft_domain = self._create_default_draft_domain() self._application = DomainApplication.objects.create( @@ -166,9 +164,11 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView): name_field = "requested_domain__name" - incomplete_drafts = existing_applications.exclude(requested_domain=None).filter( - requested_domain__name__icontains=default_draft_text - ).order_by(name_field) + incomplete_drafts = ( + existing_applications.exclude(requested_domain=None) + .filter(requested_domain__name__icontains=default_draft_text) + .order_by(name_field) + ) incomplete_draft_names = incomplete_drafts.values_list(name_field, flat=True) @@ -178,9 +178,9 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView): if application.requested_domain is not None and application.requested_domain.name is not None: name = application.requested_domain.name - # If we already have a list of draft numbers, base the + # If we already have a list of draft numbers, base the # subsequent number off of the last numbered field. - # This is to avoid a scenario in which drafts 1, 2, 3 and exist + # This is to avoid a scenario in which drafts 1, 2, 3 and exist # and 2 is deleted - meaning we would get two duplicate "3"s if we added another if name in incomplete_draft_names: # Get the last numbered draft @@ -403,7 +403,7 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView): # Build the submit button that we'll pass to the modal. modal_button = '" # Concatenate the modal header that we'll pass to the modal. - if self.application.requested_domain: + if self.application.requested_domain and not self.application.requested_domain.is_incomplete: 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" @@ -549,7 +549,7 @@ class DotgovDomain(ApplicationWizard): context["organization_type"] = self.application.organization_type context["federal_type"] = self.application.federal_type return context - + def post(self, request, *args, **kwargs): """Override for the post method to mark the DraftDomain as complete""" # Set the DraftDomain to "complete" diff --git a/src/registrar/views/index.py b/src/registrar/views/index.py index bcff0affd..f9a658942 100644 --- a/src/registrar/views/index.py +++ b/src/registrar/views/index.py @@ -1,7 +1,6 @@ from django.shortcuts import render from registrar.models import DomainApplication, Domain, UserDomainRole -from registrar.models.draft_domain import DraftDomain def index(request): From 9899b5ae3e24fe9343d83329312ac5eba60975b3 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 16 Jan 2024 09:31:22 -0700 Subject: [PATCH 050/120] Update application.py --- src/registrar/views/application.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/registrar/views/application.py b/src/registrar/views/application.py index bef707cb6..c76d285a7 100644 --- a/src/registrar/views/application.py +++ b/src/registrar/views/application.py @@ -12,6 +12,7 @@ from django.contrib import messages from registrar.forms import application_wizard as forms from registrar.models import DomainApplication from registrar.models.draft_domain import DraftDomain +from registrar.models.user import User from registrar.utility import StrEnum from registrar.views.utility import StepsHelper from registrar.views.utility.permission_views import DomainApplicationPermissionDeleteView @@ -142,12 +143,17 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView): except DomainApplication.DoesNotExist: logger.debug("Application id %s did not have a DomainApplication" % id) - # TODO - revert back to using draft_name draft_domain = self._create_default_draft_domain() - self._application = DomainApplication.objects.create( - creator=self.request.user, - requested_domain=draft_domain, - ) + + # Check added for linting purposes + if self.request.user and isinstance(self.request.user, User): + self._application = DomainApplication.objects.create( + creator=self.request.user, + requested_domain=draft_domain, + ) + else: + # TODO - Need some sort of front end display for this + raise ValueError("Invalid type for user") self.storage["application_id"] = self._application.id return self._application From 9fe1bbaac5c08701f6f59b35a95fb05b489c8a8d Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 16 Jan 2024 14:01:56 -0700 Subject: [PATCH 051/120] Remove numbering --- src/registrar/forms/application_wizard.py | 4 +- ..._draft_number_draftdomain_is_incomplete.py | 24 ----- .../0063_draftdomain_is_complete.py | 17 ++++ src/registrar/models/domain_application.py | 4 - src/registrar/models/draft_domain.py | 12 +-- src/registrar/templates/home.html | 20 ++-- src/registrar/views/application.py | 96 +------------------ src/registrar/views/index.py | 25 ++++- 8 files changed, 57 insertions(+), 145 deletions(-) delete mode 100644 src/registrar/migrations/0063_draftdomain_draft_number_draftdomain_is_incomplete.py create mode 100644 src/registrar/migrations/0063_draftdomain_is_complete.py diff --git a/src/registrar/forms/application_wizard.py b/src/registrar/forms/application_wizard.py index d731c3965..157d4b234 100644 --- a/src/registrar/forms/application_wizard.py +++ b/src/registrar/forms/application_wizard.py @@ -511,9 +511,7 @@ class DotGovDomainForm(RegistrarForm): values = {} requested_domain = getattr(obj, "requested_domain", None) if requested_domain is not None: - is_incomplete = requested_domain.is_incomplete - # Only display a preexisting name if the application was completed - domain_name = requested_domain.name if not is_incomplete else "" + domain_name = requested_domain.name values["requested_domain"] = Domain.sld(domain_name) return values diff --git a/src/registrar/migrations/0063_draftdomain_draft_number_draftdomain_is_incomplete.py b/src/registrar/migrations/0063_draftdomain_draft_number_draftdomain_is_incomplete.py deleted file mode 100644 index 1ea545c70..000000000 --- a/src/registrar/migrations/0063_draftdomain_draft_number_draftdomain_is_incomplete.py +++ /dev/null @@ -1,24 +0,0 @@ -# Generated by Django 4.2.7 on 2024-01-12 16:17 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("registrar", "0062_alter_host_name"), - ] - - operations = [ - migrations.AddField( - model_name="draftdomain", - name="draft_number", - field=models.IntegerField( - help_text="The draft number in the event a user doesn't save at this stage", null=True - ), - ), - migrations.AddField( - model_name="draftdomain", - name="is_incomplete", - field=models.BooleanField(default=False, help_text="Determines if this Draft is complete or not"), - ), - ] diff --git a/src/registrar/migrations/0063_draftdomain_is_complete.py b/src/registrar/migrations/0063_draftdomain_is_complete.py new file mode 100644 index 000000000..d17e59400 --- /dev/null +++ b/src/registrar/migrations/0063_draftdomain_is_complete.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.7 on 2024-01-16 20:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0062_alter_host_name"), + ] + + operations = [ + migrations.AddField( + model_name="draftdomain", + name="is_complete", + field=models.BooleanField(default=True, help_text="Determines if this Draft is complete or not"), + ), + ] diff --git a/src/registrar/models/domain_application.py b/src/registrar/models/domain_application.py index d93c429f9..51c322283 100644 --- a/src/registrar/models/domain_application.py +++ b/src/registrar/models/domain_application.py @@ -632,10 +632,6 @@ class DomainApplication(TimeStampedModel): # Update submission_date to today self.submission_date = timezone.now().date() - # Mark the draft domain as complete - if self.requested_domain.is_incomplete: - self.requested_domain.is_incomplete = False - self.save() self._send_status_update_email( diff --git a/src/registrar/models/draft_domain.py b/src/registrar/models/draft_domain.py index 5fe00257e..a750771b5 100644 --- a/src/registrar/models/draft_domain.py +++ b/src/registrar/models/draft_domain.py @@ -21,13 +21,7 @@ class DraftDomain(TimeStampedModel, DomainHelper): help_text="Fully qualified domain name", ) - draft_number = models.IntegerField( - null=True, - help_text="The draft number in the event a user doesn't save at this stage", + is_complete = models.BooleanField( + default=True, + help_text="Determines if this Draft is complete or not" ) - - is_incomplete = models.BooleanField(default=False, help_text="Determines if this Draft is complete or not") - - def get_default_request_name(self): - """Returns the draft name that would be used for applications if no name exists""" - return f"New domain request {self.draft_number}" diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index 54daa4aa4..1ab4a135e 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -124,7 +124,13 @@ {% for application in domain_applications %}
- {{ application.requested_domain.name|default:"New domain request" }} + {% if application.requested_domain is None or not application.requested_domain.is_complete %} + New domain request +
+ ({{ application.created_at }}) + {% else %} + {{ application.requested_domain.name }} + {% endif %}
{% if application.submission_date %} @@ -140,13 +146,13 @@ - Edit {{ application.requested_domain.name|default:"New domain request" }} + Edit {{ application.requested_domain.name|default:"New domain request ("|add:application.created_at|add:")" }} {% else %} - Manage {{ application.requested_domain.name|default:"New domain request" }} + Manage {{ application.requested_domain.name|default:"New domain request ("|add:application.created_at|add:")" }} {% endif %}
- {% if application.requested_domain is None or not application.requested_domain.is_complete %} + {% if application.requested_domain is None %} New domain request
({{ application.created_at }}) @@ -181,7 +181,26 @@ data-force-action >
- {% include 'includes/modal.html' with modal_heading="Are you sure you want to delete "|add:application.requested_domain.name|add:"?" modal_description="This will remove the domain request from the .gov registrar. This action cannot be undone." modal_button=modal_button|safe %} + {% if application.requested_domain is None %} + {% with prefix="Are you sure you want to delete New domain request " %} + {% if application.created_at %} + {% with formatted_date=application.created_at|date:"DATETIME_FORMAT" %} + {% with modal_heading=prefix|add:formatted_date %} + {% include 'includes/modal.html' with modal_heading=modal_heading modal_description="This will remove the domain request from the .gov registrar. This action cannot be undone." modal_button=modal_button|safe %} + {% endwith %} + {% endwith %} + {% else %} + {# Handle the case when application.created_at is not available or empty #} + {% with modal_heading=prefix %} + {% include 'includes/modal.html' with modal_heading=modal_heading modal_description="This will remove the domain request from the .gov registrar. This action cannot be undone." modal_button=modal_button|safe %} + {% endwith %} + {% endif %} + {% endwith %} + {% else %} + {% with modal_heading="Are you sure you want to delete "|add:application.requested_domain.name|add:"?" %} + {% include 'includes/modal.html' with modal_heading=modal_heading modal_description="This will remove the domain request from the .gov registrar. This action cannot be undone." modal_button=modal_button|safe %} + {% endwith %} + {% endif %}
{% endif %} diff --git a/src/registrar/views/index.py b/src/registrar/views/index.py index f9dcaa253..b98154a23 100644 --- a/src/registrar/views/index.py +++ b/src/registrar/views/index.py @@ -48,13 +48,7 @@ def _get_applications(request): # Create a placeholder DraftDomain for each incomplete draft valid_statuses = [DomainApplication.ApplicationStatus.STARTED, DomainApplication.ApplicationStatus.WITHDRAWN] deletable_applications = applications.filter(status__in=valid_statuses) - for application in applications: - if application in deletable_applications and application.requested_domain is None: - created_at = application.created_at.strftime("%b. %d, %Y, %I:%M %p UTC") - _name = f"New domain request ({created_at})" - default_draft_domain = DraftDomain(name=_name, is_complete=False) - - application.requested_domain = default_draft_domain + return (applications, deletable_applications) From dc032e522234da7ff06bded2dbd381565a1bbaa1 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 17 Jan 2024 14:52:27 -0700 Subject: [PATCH 064/120] Update home.html --- src/registrar/templates/home.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index 5bda944c4..ea70fdbfc 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -182,10 +182,10 @@ >
{% if application.requested_domain is None %} - {% with prefix="Are you sure you want to delete New domain request " %} + {% with prefix="Are you sure you want to delete New domain request (" %} {% if application.created_at %} {% with formatted_date=application.created_at|date:"DATETIME_FORMAT" %} - {% with modal_heading=prefix|add:formatted_date %} + {% with modal_heading=prefix|add:formatted_date|add:")?" %} {% include 'includes/modal.html' with modal_heading=modal_heading modal_description="This will remove the domain request from the .gov registrar. This action cannot be undone." modal_button=modal_button|safe %} {% endwith %} {% endwith %} From 25a3ed44f7954e24ab7c37c093f661ea8adf23b4 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 17 Jan 2024 14:56:27 -0700 Subject: [PATCH 065/120] Linting and tests --- src/registrar/templates/home.html | 2 +- src/registrar/tests/test_views.py | 2 +- src/registrar/views/index.py | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index ea70fdbfc..cccc20f28 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -174,7 +174,7 @@
Date: Wed, 17 Jan 2024 14:59:10 -0700 Subject: [PATCH 066/120] Update index.py --- src/registrar/views/index.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/registrar/views/index.py b/src/registrar/views/index.py index 048bd24b5..a4a1f64d0 100644 --- a/src/registrar/views/index.py +++ b/src/registrar/views/index.py @@ -1,7 +1,6 @@ from django.shortcuts import render from registrar.models import DomainApplication, Domain, UserDomainRole -from registrar.models.draft_domain import DraftDomain def index(request): From 267ba5130867228e609b43204db8adc57f6abc97 Mon Sep 17 00:00:00 2001 From: Erin <121973038+erinysong@users.noreply.github.com> Date: Wed, 17 Jan 2024 15:00:42 -0800 Subject: [PATCH 067/120] Fix linting --- .../commands/disclose_security_emails.py | 8 +++---- src/registrar/tests/test_models_domain.py | 23 +++++++++++++++---- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/registrar/management/commands/disclose_security_emails.py b/src/registrar/management/commands/disclose_security_emails.py index 3e0c06bbc..4e40dda20 100644 --- a/src/registrar/management/commands/disclose_security_emails.py +++ b/src/registrar/management/commands/disclose_security_emails.py @@ -52,19 +52,19 @@ class Command(BaseCommand): # Update EPP contact for domains with a security contact for domain in domains: try: - logger.info(f"Domain {domain.domain_info} security contact: {domain.security_contact.email}") + logger.info(f"Domain {domain.name} security contact: {domain.security_contact.email}") if domain.security_contact.email != "registrar@dotgov.gov": domain._update_epp_contact(contact=domain.security_contact) self.disclosed_domain_contacts_count += 1 else: logger.info( - f"Skipping disclose for {domain.domain_info} security contact {domain.security_contact.email}." + f"Skipping disclose for {domain.name} security contact {domain.security_contact.email}." ) self.skipped_domain_contacts_count += 1 except Exception as err: # error condition if domain not in database - self.domains_with_errors.append(copy.deepcopy(domain.domain_info)) - logger.error(f"error retrieving domain {domain.domain_info} contact {domain.security_contact}: {err}") + self.domains_with_errors.append(copy.deepcopy(domain.name)) + logger.error(f"error retrieving domain {domain.name} contact {domain.security_contact}: {err}") # Inform user how many contacts were disclosed, skipped, and errored logger.info(f"Updated {self.disclosed_domain_contacts_count} contacts to disclosed.") diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index af9c7b053..02346b907 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -983,16 +983,31 @@ class TestRegistrantContacts(MockEppLib): Tests that command disclose_security_emails runs successfully with appropriate EPP calll to UpdateContact. """ - domain, _ = Domain.objects.get_or_create(name="igorville.gov") + domain, _ = Domain.objects.get_or_create(name="testdisclose.gov", state=Domain.State.READY) expectedSecContact = PublicContact.get_default_security() expectedSecContact.domain = domain expectedSecContact.email = "123@mail.gov" + # set domain security email to 123@mail.gov instead of default email domain.security_contact = expectedSecContact self.run_disclose_security_emails() - # running disclose_security_emails makes EPP calls - expectedUpdateCommand = self._convertPublicContactToEpp(expectedSecContact, disclose_email=True) - self.mockedSendFunction.assert_any_call(expectedUpdateCommand, cleaned=True) + # running disclose_security_emails sends EPP call UpdateContact with disclose + self.mockedSendFunction.assert_has_calls( + [ + call( + commands.UpdateContact( + id=domain.security_contact.registry_id, + postal_info=domain._make_epp_contact_postal_info(contact=domain.security_contact), + email=domain.security_contact.email, + voice=domain.security_contact.voice, + fax=domain.security_contact.fax, + auth_info=common.ContactAuthInfo(pw="2fooBAR123fooBaz"), + disclose=domain._disclose_fields(contact=domain.security_contact), + ), + cleaned=True, + ) + ] + ) @skip("not implemented yet") def test_update_is_unsuccessful(self): From 08c6803cf03d1891769c51cd7aca608cdd215e28 Mon Sep 17 00:00:00 2001 From: Erin <121973038+erinysong@users.noreply.github.com> Date: Wed, 17 Jan 2024 15:10:11 -0800 Subject: [PATCH 068/120] Fix whitespace issue in logs --- src/registrar/management/commands/disclose_security_emails.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/management/commands/disclose_security_emails.py b/src/registrar/management/commands/disclose_security_emails.py index 4e40dda20..56f77dc13 100644 --- a/src/registrar/management/commands/disclose_security_emails.py +++ b/src/registrar/management/commands/disclose_security_emails.py @@ -69,7 +69,7 @@ class Command(BaseCommand): # Inform user how many contacts were disclosed, skipped, and errored logger.info(f"Updated {self.disclosed_domain_contacts_count} contacts to disclosed.") logger.info( - f"Skipped disclosing {self.skipped_domain_contacts_count} contacts with security \ + f"Skipped disclosing {self.skipped_domain_contacts_count} contacts with security email registrar@dotgov.gov." ) logger.info( From 595c2f8d80836d6459f24708975e405e78bda9c9 Mon Sep 17 00:00:00 2001 From: Erin <121973038+erinysong@users.noreply.github.com> Date: Wed, 17 Jan 2024 15:27:19 -0800 Subject: [PATCH 069/120] Fix whitespace issue in logs --- src/registrar/management/commands/disclose_security_emails.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/registrar/management/commands/disclose_security_emails.py b/src/registrar/management/commands/disclose_security_emails.py index 56f77dc13..1fdd297a9 100644 --- a/src/registrar/management/commands/disclose_security_emails.py +++ b/src/registrar/management/commands/disclose_security_emails.py @@ -69,8 +69,8 @@ class Command(BaseCommand): # Inform user how many contacts were disclosed, skipped, and errored logger.info(f"Updated {self.disclosed_domain_contacts_count} contacts to disclosed.") logger.info( - f"Skipped disclosing {self.skipped_domain_contacts_count} contacts with security - email registrar@dotgov.gov." + f"Skipped disclosing {self.skipped_domain_contacts_count} contacts with security email " + f"registrar@dotgov.gov." ) logger.info( f"Error disclosing the following {len(self.domains_with_errors)} contacts: {self.domains_with_errors}" From 71f6c8e3ae74decfdde6a8b337952f6bb1cfc255 Mon Sep 17 00:00:00 2001 From: Erin <121973038+erinysong@users.noreply.github.com> Date: Wed, 17 Jan 2024 15:31:57 -0800 Subject: [PATCH 070/120] Fix typo in comment in domain --- src/registrar/models/domain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 4d455d320..1a581a4ec 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -1396,7 +1396,7 @@ class Domain(TimeStampedModel, DomainHelper): def _disclose_fields(self, contact: PublicContact): """creates a disclose object that can be added to a contact Create using .disclose= on the command before sending. - if item is security email then make sure email is visable""" + if item is security email then make sure email is visible""" is_security = contact.contact_type == contact.ContactTypeChoices.SECURITY DF = epp.DiscloseField fields = {DF.EMAIL} From 3ed806c1a1ecaaec1712c0ef0dadcc0c265feebd Mon Sep 17 00:00:00 2001 From: Alysia Broddrick Date: Wed, 17 Jan 2024 21:30:23 -0400 Subject: [PATCH 071/120] removed the hover effect and disable of the start button --- src/registrar/templates/home.html | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index 138f83e04..92ca03343 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -13,22 +13,10 @@

Manage your domains

- {% if IS_PRODUCTION %} - - Start a new domain request - - {% else %} Start a new domain request - {% endif %}

From b140d631f57363ae2e216b20e3dfe36d599f09a7 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Thu, 18 Jan 2024 11:42:17 -0500 Subject: [PATCH 072/120] update all references to beta.get.gov to get.gov in all settings files; update application_org_contact to use _public_site_url rather than hardcoded value --- ops/manifests/manifest-ab.yaml | 2 +- ops/manifests/manifest-backup.yaml | 2 +- ops/manifests/manifest-bl.yaml | 2 +- ops/manifests/manifest-development.yaml | 2 +- ops/manifests/manifest-dk.yaml | 2 +- ops/manifests/manifest-es.yaml | 2 +- ops/manifests/manifest-gd.yaml | 2 +- ops/manifests/manifest-ko.yaml | 2 +- ops/manifests/manifest-ky.yaml | 2 +- ops/manifests/manifest-nl.yaml | 2 +- ops/manifests/manifest-rb.yaml | 2 +- ops/manifests/manifest-rh.yaml | 2 +- ops/manifests/manifest-rjm.yaml | 2 +- ops/manifests/manifest-stable.yaml | 2 +- ops/manifests/manifest-staging.yaml | 2 +- ops/manifests/manifest-za.yaml | 2 +- ops/scripts/manifest-sandbox-template-migrate.yaml | 2 +- ops/scripts/manifest-sandbox-template.yaml | 2 +- src/docker-compose.yml | 2 +- src/registrar/config/settings.py | 2 +- src/registrar/templates/application_org_contact.html | 2 +- 21 files changed, 21 insertions(+), 21 deletions(-) diff --git a/ops/manifests/manifest-ab.yaml b/ops/manifests/manifest-ab.yaml index 38109bdcb..3ca800392 100644 --- a/ops/manifests/manifest-ab.yaml +++ b/ops/manifests/manifest-ab.yaml @@ -22,7 +22,7 @@ applications: # Tell Django how much stuff to log DJANGO_LOG_LEVEL: INFO # default public site location - GETGOV_PUBLIC_SITE_URL: https://beta.get.gov + GETGOV_PUBLIC_SITE_URL: https://get.gov # Flag to disable/enable features in prod environments IS_PRODUCTION: False routes: diff --git a/ops/manifests/manifest-backup.yaml b/ops/manifests/manifest-backup.yaml index c4615d1d5..ab9e36d68 100644 --- a/ops/manifests/manifest-backup.yaml +++ b/ops/manifests/manifest-backup.yaml @@ -22,7 +22,7 @@ applications: # Tell Django how much stuff to log DJANGO_LOG_LEVEL: INFO # default public site location - GETGOV_PUBLIC_SITE_URL: https://beta.get.gov + GETGOV_PUBLIC_SITE_URL: https://get.gov # Flag to disable/enable features in prod environments IS_PRODUCTION: False routes: diff --git a/ops/manifests/manifest-bl.yaml b/ops/manifests/manifest-bl.yaml index 59529278b..ea0617427 100644 --- a/ops/manifests/manifest-bl.yaml +++ b/ops/manifests/manifest-bl.yaml @@ -22,7 +22,7 @@ applications: # Tell Django how much stuff to log DJANGO_LOG_LEVEL: INFO # default public site location - GETGOV_PUBLIC_SITE_URL: https://beta.get.gov + GETGOV_PUBLIC_SITE_URL: https://get.gov # Flag to disable/enable features in prod environments IS_PRODUCTION: False routes: diff --git a/ops/manifests/manifest-development.yaml b/ops/manifests/manifest-development.yaml index 0a1f30ffa..08244cf08 100644 --- a/ops/manifests/manifest-development.yaml +++ b/ops/manifests/manifest-development.yaml @@ -22,7 +22,7 @@ applications: # Tell Django how much stuff to log DJANGO_LOG_LEVEL: INFO # default public site location - GETGOV_PUBLIC_SITE_URL: https://beta.get.gov + GETGOV_PUBLIC_SITE_URL: https://get.gov # Flag to disable/enable features in prod environments IS_PRODUCTION: False routes: diff --git a/ops/manifests/manifest-dk.yaml b/ops/manifests/manifest-dk.yaml index 256beeda2..071efb416 100644 --- a/ops/manifests/manifest-dk.yaml +++ b/ops/manifests/manifest-dk.yaml @@ -22,7 +22,7 @@ applications: # Tell Django how much stuff to log DJANGO_LOG_LEVEL: INFO # default public site location - GETGOV_PUBLIC_SITE_URL: https://beta.get.gov + GETGOV_PUBLIC_SITE_URL: https://get.gov # Flag to disable/enable features in prod environments IS_PRODUCTION: False routes: diff --git a/ops/manifests/manifest-es.yaml b/ops/manifests/manifest-es.yaml index 47c78ce1b..7fd19b7a0 100644 --- a/ops/manifests/manifest-es.yaml +++ b/ops/manifests/manifest-es.yaml @@ -22,7 +22,7 @@ applications: # Tell Django how much stuff to log DJANGO_LOG_LEVEL: INFO # default public site location - GETGOV_PUBLIC_SITE_URL: https://beta.get.gov + GETGOV_PUBLIC_SITE_URL: https://get.gov # Flag to disable/enable features in prod environments IS_PRODUCTION: False routes: diff --git a/ops/manifests/manifest-gd.yaml b/ops/manifests/manifest-gd.yaml index 0c4b2535f..89a7c2169 100644 --- a/ops/manifests/manifest-gd.yaml +++ b/ops/manifests/manifest-gd.yaml @@ -22,7 +22,7 @@ applications: # Tell Django how much stuff to log DJANGO_LOG_LEVEL: INFO # default public site location - GETGOV_PUBLIC_SITE_URL: https://beta.get.gov + GETGOV_PUBLIC_SITE_URL: https://get.gov # Flag to disable/enable features in prod environments IS_PRODUCTION: False routes: diff --git a/ops/manifests/manifest-ko.yaml b/ops/manifests/manifest-ko.yaml index cc6a09337..a69493f9b 100644 --- a/ops/manifests/manifest-ko.yaml +++ b/ops/manifests/manifest-ko.yaml @@ -22,7 +22,7 @@ applications: # Tell Django how much stuff to log DJANGO_LOG_LEVEL: INFO # default public site location - GETGOV_PUBLIC_SITE_URL: https://beta.get.gov + GETGOV_PUBLIC_SITE_URL: https://get.gov # Flag to disable/enable features in prod environments IS_PRODUCTION: False routes: diff --git a/ops/manifests/manifest-ky.yaml b/ops/manifests/manifest-ky.yaml index 31d67cfb3..f416d7385 100644 --- a/ops/manifests/manifest-ky.yaml +++ b/ops/manifests/manifest-ky.yaml @@ -22,7 +22,7 @@ applications: # Tell Django how much stuff to log DJANGO_LOG_LEVEL: INFO # default public site location - GETGOV_PUBLIC_SITE_URL: https://beta.get.gov + GETGOV_PUBLIC_SITE_URL: https://get.gov # Flag to disable/enable features in prod environments IS_PRODUCTION: False routes: diff --git a/ops/manifests/manifest-nl.yaml b/ops/manifests/manifest-nl.yaml index ca6fb4693..d74174e7d 100644 --- a/ops/manifests/manifest-nl.yaml +++ b/ops/manifests/manifest-nl.yaml @@ -22,7 +22,7 @@ applications: # Tell Django how much stuff to log DJANGO_LOG_LEVEL: INFO # default public site location - GETGOV_PUBLIC_SITE_URL: https://beta.get.gov + GETGOV_PUBLIC_SITE_URL: https://get.gov # Flag to disable/enable features in prod environments IS_PRODUCTION: False routes: diff --git a/ops/manifests/manifest-rb.yaml b/ops/manifests/manifest-rb.yaml index 62f243513..570b49dde 100644 --- a/ops/manifests/manifest-rb.yaml +++ b/ops/manifests/manifest-rb.yaml @@ -22,7 +22,7 @@ applications: # Tell Django how much stuff to log DJANGO_LOG_LEVEL: INFO # default public site location - GETGOV_PUBLIC_SITE_URL: https://beta.get.gov + GETGOV_PUBLIC_SITE_URL: https://get.gov # Flag to disable/enable features in prod environments IS_PRODUCTION: False routes: diff --git a/ops/manifests/manifest-rh.yaml b/ops/manifests/manifest-rh.yaml index 4985f3261..f44894ce8 100644 --- a/ops/manifests/manifest-rh.yaml +++ b/ops/manifests/manifest-rh.yaml @@ -22,7 +22,7 @@ applications: # Tell Django how much stuff to log DJANGO_LOG_LEVEL: INFO # default public site location - GETGOV_PUBLIC_SITE_URL: https://beta.get.gov + GETGOV_PUBLIC_SITE_URL: https://get.gov # Flag to disable/enable features in prod environments IS_PRODUCTION: False routes: diff --git a/ops/manifests/manifest-rjm.yaml b/ops/manifests/manifest-rjm.yaml index 7d72e7835..048b44e95 100644 --- a/ops/manifests/manifest-rjm.yaml +++ b/ops/manifests/manifest-rjm.yaml @@ -22,7 +22,7 @@ applications: # Tell Django how much stuff to log DJANGO_LOG_LEVEL: INFO # default public site location - GETGOV_PUBLIC_SITE_URL: https://beta.get.gov + GETGOV_PUBLIC_SITE_URL: https://get.gov # Flag to disable/enable features in prod environments IS_PRODUCTION: False routes: diff --git a/ops/manifests/manifest-stable.yaml b/ops/manifests/manifest-stable.yaml index d8502c625..a70035445 100644 --- a/ops/manifests/manifest-stable.yaml +++ b/ops/manifests/manifest-stable.yaml @@ -22,7 +22,7 @@ applications: # Tell Django how much stuff to log DJANGO_LOG_LEVEL: INFO # default public site location - GETGOV_PUBLIC_SITE_URL: https://beta.get.gov + GETGOV_PUBLIC_SITE_URL: https://get.gov # Which OIDC provider to use OIDC_ACTIVE_PROVIDER: login.gov production # Flag to disable/enable features in prod environments diff --git a/ops/manifests/manifest-staging.yaml b/ops/manifests/manifest-staging.yaml index b616973ac..38099cf17 100644 --- a/ops/manifests/manifest-staging.yaml +++ b/ops/manifests/manifest-staging.yaml @@ -22,7 +22,7 @@ applications: # Tell Django how much stuff to log DJANGO_LOG_LEVEL: INFO # default public site location - GETGOV_PUBLIC_SITE_URL: https://beta.get.gov + GETGOV_PUBLIC_SITE_URL: https://get.gov # Flag to disable/enable features in prod environments IS_PRODUCTION: False routes: diff --git a/ops/manifests/manifest-za.yaml b/ops/manifests/manifest-za.yaml index 1b84a74a1..271f49da9 100644 --- a/ops/manifests/manifest-za.yaml +++ b/ops/manifests/manifest-za.yaml @@ -22,7 +22,7 @@ applications: # Tell Django how much stuff to log DJANGO_LOG_LEVEL: INFO # default public site location - GETGOV_PUBLIC_SITE_URL: https://beta.get.gov + GETGOV_PUBLIC_SITE_URL: https://get.gov # Flag to disable/enable features in prod environments IS_PRODUCTION: False routes: diff --git a/ops/scripts/manifest-sandbox-template-migrate.yaml b/ops/scripts/manifest-sandbox-template-migrate.yaml index dfebed766..9054e9494 100644 --- a/ops/scripts/manifest-sandbox-template-migrate.yaml +++ b/ops/scripts/manifest-sandbox-template-migrate.yaml @@ -22,7 +22,7 @@ applications: # Tell Django how much stuff to log DJANGO_LOG_LEVEL: INFO # default public site location - GETGOV_PUBLIC_SITE_URL: https://beta.get.gov + GETGOV_PUBLIC_SITE_URL: https://get.gov # use a non-default route to avoid conflicts routes: - route: getgov-ENVIRONMENT-migrate.app.cloud.gov diff --git a/ops/scripts/manifest-sandbox-template.yaml b/ops/scripts/manifest-sandbox-template.yaml index 8cdb8d71b..f0aee9664 100644 --- a/ops/scripts/manifest-sandbox-template.yaml +++ b/ops/scripts/manifest-sandbox-template.yaml @@ -22,7 +22,7 @@ applications: # Tell Django how much stuff to log DJANGO_LOG_LEVEL: INFO # default public site location - GETGOV_PUBLIC_SITE_URL: https://beta.get.gov + GETGOV_PUBLIC_SITE_URL: https://get.gov # Flag to disable/enable features in prod environments IS_PRODUCTION: False routes: diff --git a/src/docker-compose.yml b/src/docker-compose.yml index c9b78fd8e..ba6530674 100644 --- a/src/docker-compose.yml +++ b/src/docker-compose.yml @@ -32,7 +32,7 @@ services: # Is this a production environment - IS_PRODUCTION # Public site URL link - - GETGOV_PUBLIC_SITE_URL=https://beta.get.gov + - GETGOV_PUBLIC_SITE_URL=https://get.gov # Set a username for accessing the registry - REGISTRY_CL_ID=nothing # Set a password for accessing the registry diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index 2de7e6eb2..efa512f22 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -335,7 +335,7 @@ CSP_INCLUDE_NONCE_IN = ["script-src-elem"] # Cross-Origin Resource Sharing (CORS) configuration # Sets clients that allow access control to manage.get.gov # TODO: remove :8080 to see if we can have all localhost access -CORS_ALLOWED_ORIGINS = ["http://localhost:8080", "https://beta.get.gov"] +CORS_ALLOWED_ORIGINS = ["http://localhost:8080", "https://beta.get.gov", "https://get.gov"] CORS_ALLOWED_ORIGIN_REGEXES = [r"https://[\w-]+\.sites\.pages\.cloud\.gov"] # Content-Length header is set by django.middleware.common.CommonMiddleware diff --git a/src/registrar/templates/application_org_contact.html b/src/registrar/templates/application_org_contact.html index 01b55d03d..8ddd2e6fd 100644 --- a/src/registrar/templates/application_org_contact.html +++ b/src/registrar/templates/application_org_contact.html @@ -2,7 +2,7 @@ {% load field_helpers %} {% block form_instructions %} -

If your domain request is approved, the name of your organization and your city/state will be listed in .gov’s public data.

+

If your domain request is approved, the name of your organization and your city/state will be listed in .gov’s public data.

What is the name and mailing address of the organization you represent?

From c60c4126f622bd80103694b400c8c539d5f1acbb Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 18 Jan 2024 09:50:14 -0700 Subject: [PATCH 073/120] Change count on test --- src/registrar/tests/test_views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index f878eee80..abb5f0eb1 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -96,7 +96,7 @@ class LoggedInTests(TestWithUser): response = self.client.get("/") # count = 5 because of screenreader content - self.assertContains(response, "igorville.gov", count=4) + self.assertContains(response, "igorville.gov", count=5) # clean up application.delete() From 2afd48a4a105fd70e6b32b8a635dcb9802f1e4f6 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Thu, 18 Jan 2024 12:12:49 -0500 Subject: [PATCH 074/120] fixed application_org_contact --- src/registrar/templates/application_org_contact.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/templates/application_org_contact.html b/src/registrar/templates/application_org_contact.html index 8ddd2e6fd..e8e8f50bf 100644 --- a/src/registrar/templates/application_org_contact.html +++ b/src/registrar/templates/application_org_contact.html @@ -1,5 +1,5 @@ {% extends 'application_form.html' %} -{% load field_helpers %} +{% load field_helpers url_helpers %} {% block form_instructions %}

If your domain request is approved, the name of your organization and your city/state will be listed in .gov’s public data.

From c297ec125d942f041f017e5f651e32de148281eb Mon Sep 17 00:00:00 2001 From: rachidatecs <107004823+rachidatecs@users.noreply.github.com> Date: Thu, 18 Jan 2024 12:15:35 -0500 Subject: [PATCH 075/120] Update src/registrar/tests/test_emails.py Co-authored-by: zandercymatics <141044360+zandercymatics@users.noreply.github.com> --- src/registrar/tests/test_emails.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/tests/test_emails.py b/src/registrar/tests/test_emails.py index fcdd46577..a4f32bfcf 100644 --- a/src/registrar/tests/test_emails.py +++ b/src/registrar/tests/test_emails.py @@ -76,7 +76,7 @@ class TestEmails(TestCase): body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] self.assertIn("Current websites:", body) # spacing should be right between adjacent elements - self.assertRegex(body, r"5555\n\nCurrent websites") + self.assertRegex(body, r"5555\n\nCurrent websites:") self.assertRegex(body, r"city.com\n\n.gov domain:") @boto3_mocking.patching From 573d6d05651e0d995a8d41efac4d869c053cbcf9 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Thu, 18 Jan 2024 12:31:09 -0500 Subject: [PATCH 076/120] merged main and fixed hardcoded urls from another test --- src/registrar/tests/test_forms.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/registrar/tests/test_forms.py b/src/registrar/tests/test_forms.py index 7968c54e0..9d553acc5 100644 --- a/src/registrar/tests/test_forms.py +++ b/src/registrar/tests/test_forms.py @@ -101,7 +101,7 @@ class TestFormValidation(MockEppLib): ( "whitehouse.gov", "That domain isn’t available. Read more about " + "href='https://get.gov/domains/choosing' target='_blank'>Read more about " "choosing your .gov domain.", ), ] @@ -151,7 +151,7 @@ class TestFormValidation(MockEppLib): ( "whitehouse.gov", "That domain isn’t available. Read more about " + "href='https://get.gov/domains/choosing' target='_blank'>Read more about " "choosing your .gov domain.", ), ] From 3c99c0339387d79703f14572f2033ebe7c1f5ced Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 18 Jan 2024 11:21:49 -0700 Subject: [PATCH 077/120] Update home.html --- 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 138f83e04..f0db2d02b 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -58,7 +58,7 @@ {% elif domain.state == domain.State.UNKNOWN or domain.state == domain.State.DNS_NEEDED %} DNS needed {% else %} - {{ domain.state|title }} + {{ domain.state|capfirst }} {% endif %}
From 7194b2748f440e9450ac47da75497c566c910537 Mon Sep 17 00:00:00 2001 From: Erin <121973038+erinysong@users.noreply.github.com> Date: Thu, 18 Jan 2024 11:02:36 -0800 Subject: [PATCH 078/120] Merge iterations of domains in disclose_emails script --- .../management/commands/disclose_security_emails.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/registrar/management/commands/disclose_security_emails.py b/src/registrar/management/commands/disclose_security_emails.py index 1fdd297a9..f44f9bf85 100644 --- a/src/registrar/management/commands/disclose_security_emails.py +++ b/src/registrar/management/commands/disclose_security_emails.py @@ -19,8 +19,6 @@ class Command(BaseCommand): def __init__(self): """Sets global variables for code tidyness""" super().__init__() - # domains and transition domains that must be disclosed to true - self.contacts_saved_count = 0 # domains with errors, which are not successfully updated to disclose self.domains_with_errors: list[str] = [] # domains that are successfully disclosed @@ -42,16 +40,10 @@ class Command(BaseCommand): logger.info(f"Found {len(domains)} domains with status Ready or DNS Needed.") - # Call security_contact on all domains to trigger saving contact information - for domain in domains: - contact = domain.security_contact # noqa on these items as we only want to call security_contact - self.contacts_saved_count += 1 - - logger.info(f"Found {self.contacts_saved_count} security contacts.") - # Update EPP contact for domains with a security contact for domain in domains: try: + contact = domain.security_contact # noqa on these items as we only want to call security_contact logger.info(f"Domain {domain.name} security contact: {domain.security_contact.email}") if domain.security_contact.email != "registrar@dotgov.gov": domain._update_epp_contact(contact=domain.security_contact) From 94d7c351563ae0bc5c9161edb66399e7b31df843 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 18 Jan 2024 12:29:02 -0700 Subject: [PATCH 079/120] PR suggestions --- src/registrar/templates/home.html | 21 +++++++++------------ src/registrar/templates/includes/modal.html | 4 ++++ 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index cccc20f28..b0d510819 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -127,7 +127,7 @@ {% if application.requested_domain is None %} New domain request
- ({{ application.created_at }}) + ({{ application.created_at }}) {% else %} {{ application.requested_domain.name }} {% endif %} @@ -174,31 +174,28 @@
{% if application.requested_domain is None %} - {% with prefix="Are you sure you want to delete New domain request (" %} + {% with prefix="New domain request (" %} {% if application.created_at %} {% with formatted_date=application.created_at|date:"DATETIME_FORMAT" %} - {% with modal_heading=prefix|add:formatted_date|add:")?" %} - {% include 'includes/modal.html' with modal_heading=modal_heading modal_description="This will remove the domain request from the .gov registrar. This action cannot be undone." modal_button=modal_button|safe %} + {% with modal_content=prefix|add:formatted_date|add:")" %} + {% include 'includes/modal.html' with modal_heading="Are you sure you want to delete" heading_value="New domain request?"" modal_description="This will remove "|add:modal_content|add:" from the .gov registrar. This action cannot be undone." modal_button=modal_button|safe %} {% endwith %} {% endwith %} {% else %} - {# Handle the case when application.created_at is not available or empty #} - {% with modal_heading=prefix %} - {% include 'includes/modal.html' with modal_heading=modal_heading modal_description="This will remove the domain request from the .gov registrar. This action cannot be undone." modal_button=modal_button|safe %} - {% endwith %} + {% include 'includes/modal.html' with modal_heading="Are you sure you want to delete New domain request?" modal_description="This will remove the domain request from the .gov registrar. This action cannot be undone." modal_button=modal_button|safe %} {% endif %} {% endwith %} {% else %} - {% with modal_heading="Are you sure you want to delete "|add:application.requested_domain.name|add:"?" %} - {% include 'includes/modal.html' with modal_heading=modal_heading modal_description="This will remove the domain request from the .gov registrar. This action cannot be undone." modal_button=modal_button|safe %} + {% with modal_heading_value=application.requested_domain.name|add:"?" %} + {% include 'includes/modal.html' with modal_heading="Are you sure you want to delete" heading_value=modal_heading_value modal_description="This will remove the domain request from the .gov registrar. This action cannot be undone." modal_button=modal_button|safe %} {% endwith %} {% endif %} diff --git a/src/registrar/templates/includes/modal.html b/src/registrar/templates/includes/modal.html index 1f9fbcfd4..45a6fa3b5 100644 --- a/src/registrar/templates/includes/modal.html +++ b/src/registrar/templates/includes/modal.html @@ -4,6 +4,10 @@

Time to complete the form

If you have all the information you need, completing your domain request might take around 15 minutes.

-

Contact us if you need help with your request.

+ {% block form_buttons %}
@@ -29,6 +29,7 @@ +
Paperwork Reduction Act statement (OMB control number: 1670-0049; expiration date: 10/31/2026)
{% endblock %} From 4c7f954211a549ee8ec271d927af04816f9b1a98 Mon Sep 17 00:00:00 2001 From: Alysia Broddrick Date: Thu, 18 Jan 2024 18:58:53 -0400 Subject: [PATCH 082/120] removed banner --- src/registrar/templates/base.html | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/registrar/templates/base.html b/src/registrar/templates/base.html index a07bf67ee..aed9987c9 100644 --- a/src/registrar/templates/base.html +++ b/src/registrar/templates/base.html @@ -5,14 +5,6 @@ - {% if IS_PRODUCTION %} - - - - {% endif %} - From 94326cd369b38b592f4dc6f5b9170ab34a77d8fa Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 18 Jan 2024 16:00:40 -0700 Subject: [PATCH 083/120] Update src/registrar/templates/home.html Co-authored-by: Alysia Broddrick <109625347+abroddrick@users.noreply.github.com> --- 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 4c87df03b..8d5cb0522 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -141,7 +141,7 @@ </td> <td data-label="Status">{{ application.get_status_display }}</td> <td> - {% if application.status == "started" or application.status == "action needed" or application.status == "withdrawn" %} + {% if application.status == application.ApplicationStatus.STARTED or application.status == application.ApplicationStatus.ACTION_NEEDED or application.status == application.ApplicationStatus.WITHDRAWN %} <a href="{% url 'edit-application' application.pk %}"> <svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24"> <use xlink:href="{%static 'img/sprite.svg'%}#edit"></use> From d5099cd377818d6b66a90a212e0bfb0d986eb86e Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 18 Jan 2024 16:08:04 -0700 Subject: [PATCH 084/120] PR suggestions --- src/registrar/models/domain_application.py | 1 - src/registrar/templates/home.html | 5 ----- src/registrar/views/index.py | 4 +++- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/registrar/models/domain_application.py b/src/registrar/models/domain_application.py index 51c322283..196449bfa 100644 --- a/src/registrar/models/domain_application.py +++ b/src/registrar/models/domain_application.py @@ -631,7 +631,6 @@ class DomainApplication(TimeStampedModel): # Update submission_date to today self.submission_date = timezone.now().date() - self.save() self._send_status_update_email( diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index 4c87df03b..c25d133b8 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -47,11 +47,6 @@ > <span class="usa-sr-only">Action</span> </th> - {% comment %} - #1510 - {% if has_deletable_applications %} - <th></th> - {% endif %} {% endcomment %} </tr> </thead> <tbody> diff --git a/src/registrar/views/index.py b/src/registrar/views/index.py index a4a1f64d0..367e5c315 100644 --- a/src/registrar/views/index.py +++ b/src/registrar/views/index.py @@ -42,7 +42,9 @@ def _get_applications(request): # Let's exclude the approved applications since our # domain_applications context will be used to populate # the active applications table - applications = DomainApplication.objects.filter(creator=request.user).exclude(status="approved") + applications = DomainApplication.objects.filter(creator=request.user).exclude( + status=DomainApplication.ApplicationStatus.APPROVED + ) # Create a placeholder DraftDomain for each incomplete draft valid_statuses = [DomainApplication.ApplicationStatus.STARTED, DomainApplication.ApplicationStatus.WITHDRAWN] From fbfcd7052510c77854d3ea076040df8390de7ffe Mon Sep 17 00:00:00 2001 From: Alysia Broddrick <abroddrick@truss.works> Date: Thu, 18 Jan 2024 19:18:21 -0400 Subject: [PATCH 085/120] removed banner --- src/registrar/templates/base.html | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/src/registrar/templates/base.html b/src/registrar/templates/base.html index aed9987c9..2786cca22 100644 --- a/src/registrar/templates/base.html +++ b/src/registrar/templates/base.html @@ -5,6 +5,14 @@ <html class="no-js" lang="{{ LANGUAGE_CODE }}"> <head> + {% if IS_PRODUCTION %} + <!-- Google tag (gtag.js) --> + <script async src="https://www.googletagmanager.com/gtag/js?id=G-PZ5QSP6QPL"></script> + <script type="text/javascript" nonce="{{request.csp_nonce}}"> + window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'G-PZ5QSP6QPL'); + </script> + {% endif %} + <meta charset="utf-8"> <meta http-equiv="x-ua-compatible" content="ie=edge"> <title> @@ -62,19 +70,6 @@ <script src="{% static 'js/uswds.min.js' %}" defer></script> <a class="usa-skipnav" href="#main-content">Skip to main content</a> - {% if IS_DEMO_SITE %} - <section aria-label="Alert" > - <div class="usa-alert usa-alert--info"> - <div class="usa-alert__body"> - <h4 class="usa-alert__heading">New domain requests are paused</h4> - <p class="usa-alert__text measure-none"> - This is the new registrar for managing .gov domains. Note that we’re not accepting requests for new .gov domains until January 2024. Follow .gov updates at <a href="https://get.gov/updates/" class="usa-link">get.gov/updates/</a>. - </p> - </div> - </div> - </section> - {% endif %} - <section class="usa-banner" aria-label="Official website of the United States government"> <div class="usa-accordion"> <header class="usa-banner__header"> From 7c68f8803f35f4fbfc105b0831f440bf3fbe6ba8 Mon Sep 17 00:00:00 2001 From: Erin <121973038+erinysong@users.noreply.github.com> Date: Thu, 18 Jan 2024 16:28:32 -0800 Subject: [PATCH 086/120] Move disclose script tests to test_management_scripts --- .../tests/test_management_scripts.py | 58 ++++++++++++++++++- src/registrar/tests/test_models_domain.py | 44 -------------- 2 files changed, 57 insertions(+), 45 deletions(-) diff --git a/src/registrar/tests/test_management_scripts.py b/src/registrar/tests/test_management_scripts.py index e557eed45..06886ba66 100644 --- a/src/registrar/tests/test_management_scripts.py +++ b/src/registrar/tests/test_management_scripts.py @@ -11,9 +11,11 @@ from registrar.models import ( DomainInformation, UserDomainRole, ) +from registrar.models.public_contact import PublicContact from django.core.management import call_command -from unittest.mock import patch +from unittest.mock import patch, call +from epplibwrapper import commands, common from .common import MockEppLib @@ -441,3 +443,57 @@ class TestExtendExpirationDates(MockEppLib): # Explicitly test the expiration date - should be the same self.assertEqual(desired_domain.expiration_date, datetime.date(2024, 11, 15)) + + +class TestDiscloseEmails(MockEppLib): + def setUp(self): + super().setUp() + + def tearDown(self): + super().tearDown() + PublicContact.objects.all().delete() + Domain.objects.all().delete() + + def run_disclose_security_emails(self): + """ + This method executes the disclose_security_emails command. + + The 'call_command' function from Django's management framework is then used to + execute the disclose_security_emails command. + """ + with patch( + "registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa + return_value=True, + ): + call_command("disclose_security_emails") + + def test_disclose_security_emails(self): + """ + Tests that command disclose_security_emails runs successfully with + appropriate EPP calll to UpdateContact. + """ + domain, _ = Domain.objects.get_or_create(name="testdisclose.gov", state=Domain.State.READY) + expectedSecContact = PublicContact.get_default_security() + expectedSecContact.domain = domain + expectedSecContact.email = "123@mail.gov" + # set domain security email to 123@mail.gov instead of default email + domain.security_contact = expectedSecContact + self.run_disclose_security_emails() + + # running disclose_security_emails sends EPP call UpdateContact with disclose + self.mockedSendFunction.assert_has_calls( + [ + call( + commands.UpdateContact( + id=domain.security_contact.registry_id, + postal_info=domain._make_epp_contact_postal_info(contact=domain.security_contact), + email=domain.security_contact.email, + voice=domain.security_contact.voice, + fax=domain.security_contact.fax, + auth_info=common.ContactAuthInfo(pw="2fooBAR123fooBaz"), + disclose=domain._disclose_fields(contact=domain.security_contact), + ), + cleaned=True, + ) + ] + ) diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index 02346b907..b54b06589 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -550,19 +550,6 @@ class TestRegistrantContacts(MockEppLib): PublicContact.objects.all().delete() Domain.objects.all().delete() - def run_disclose_security_emails(self): - """ - This method executes the disclose_security_emails command. - - The 'call_command' function from Django's management framework is then used to - execute the disclose_security_emails command. - """ - with patch( - "registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa - return_value=True, - ): - call_command("disclose_security_emails") - def test_no_security_email(self): """ Scenario: Registrant has not added a security contact email @@ -978,37 +965,6 @@ class TestRegistrantContacts(MockEppLib): # Confirm that we are getting the desired email self.assertEqual(domain.security_contact.email, expectedSecContact.email) - def test_disclose_security_emails(self): - """ - Tests that command disclose_security_emails runs successfully with - appropriate EPP calll to UpdateContact. - """ - domain, _ = Domain.objects.get_or_create(name="testdisclose.gov", state=Domain.State.READY) - expectedSecContact = PublicContact.get_default_security() - expectedSecContact.domain = domain - expectedSecContact.email = "123@mail.gov" - # set domain security email to 123@mail.gov instead of default email - domain.security_contact = expectedSecContact - self.run_disclose_security_emails() - - # running disclose_security_emails sends EPP call UpdateContact with disclose - self.mockedSendFunction.assert_has_calls( - [ - call( - commands.UpdateContact( - id=domain.security_contact.registry_id, - postal_info=domain._make_epp_contact_postal_info(contact=domain.security_contact), - email=domain.security_contact.email, - voice=domain.security_contact.voice, - fax=domain.security_contact.fax, - auth_info=common.ContactAuthInfo(pw="2fooBAR123fooBaz"), - disclose=domain._disclose_fields(contact=domain.security_contact), - ), - cleaned=True, - ) - ] - ) - @skip("not implemented yet") def test_update_is_unsuccessful(self): """ From 3cafc9d3f202a06e2caf30103c68988555c2c94d Mon Sep 17 00:00:00 2001 From: Erin <121973038+erinysong@users.noreply.github.com> Date: Thu, 18 Jan 2024 16:29:04 -0800 Subject: [PATCH 087/120] Fix spelling error --- src/registrar/management/commands/disclose_security_emails.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/management/commands/disclose_security_emails.py b/src/registrar/management/commands/disclose_security_emails.py index f44f9bf85..62989e4c0 100644 --- a/src/registrar/management/commands/disclose_security_emails.py +++ b/src/registrar/management/commands/disclose_security_emails.py @@ -17,7 +17,7 @@ class Command(BaseCommand): help = "Disclose all nondefault domain security emails." def __init__(self): - """Sets global variables for code tidyness""" + """Sets global variables for code tidiness""" super().__init__() # domains with errors, which are not successfully updated to disclose self.domains_with_errors: list[str] = [] From 2bbd5c4282bc6ed59ee61224254d71e4616d9100 Mon Sep 17 00:00:00 2001 From: Erin <121973038+erinysong@users.noreply.github.com> Date: Thu, 18 Jan 2024 16:33:52 -0800 Subject: [PATCH 088/120] Remove unused import --- src/registrar/tests/test_management_scripts.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/registrar/tests/test_management_scripts.py b/src/registrar/tests/test_management_scripts.py index 06886ba66..b67e3ca3b 100644 --- a/src/registrar/tests/test_management_scripts.py +++ b/src/registrar/tests/test_management_scripts.py @@ -13,7 +13,6 @@ from registrar.models import ( ) from registrar.models.public_contact import PublicContact -from django.core.management import call_command from unittest.mock import patch, call from epplibwrapper import commands, common From d27cf623f58b1dff930009b6008f5c0750abd6e6 Mon Sep 17 00:00:00 2001 From: Erin <121973038+erinysong@users.noreply.github.com> Date: Thu, 18 Jan 2024 16:37:34 -0800 Subject: [PATCH 089/120] Correct imports --- src/registrar/tests/test_management_scripts.py | 1 + src/registrar/tests/test_models_domain.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/tests/test_management_scripts.py b/src/registrar/tests/test_management_scripts.py index b67e3ca3b..06886ba66 100644 --- a/src/registrar/tests/test_management_scripts.py +++ b/src/registrar/tests/test_management_scripts.py @@ -13,6 +13,7 @@ from registrar.models import ( ) from registrar.models.public_contact import PublicContact +from django.core.management import call_command from unittest.mock import patch, call from epplibwrapper import commands, common diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index b54b06589..9026832cd 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -5,7 +5,6 @@ This file tests the various ways in which the registrar interacts with the regis """ from django.test import TestCase from django.db.utils import IntegrityError -from django.core.management import call_command from unittest.mock import MagicMock, patch, call import datetime from registrar.models import Domain, Host, HostIP From 510da219347f8e267a3422e6d34e0810b5acaee7 Mon Sep 17 00:00:00 2001 From: Rachid Mrad <rachid.mrad@ecstech.com> Date: Thu, 18 Jan 2024 19:58:53 -0500 Subject: [PATCH 090/120] Implement VIP table and fix 401 login bug --- src/djangooidc/exceptions.py | 4 ++ src/djangooidc/oidc.py | 7 ++++ src/djangooidc/tests/test_views.py | 19 ++++++++++ src/djangooidc/views.py | 8 ++++ src/registrar/admin.py | 23 ++++++++++++ .../migrations/0063_veryimportantperson.py | 37 +++++++++++++++++++ src/registrar/models/__init__.py | 3 ++ src/registrar/models/user.py | 5 +++ src/registrar/models/very_important_person.py | 36 ++++++++++++++++++ src/registrar/tests/test_admin.py | 25 +++++++++++++ src/registrar/tests/test_models.py | 9 ++++- 11 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 src/registrar/migrations/0063_veryimportantperson.py create mode 100644 src/registrar/models/very_important_person.py diff --git a/src/djangooidc/exceptions.py b/src/djangooidc/exceptions.py index 260750a4d..226337f54 100644 --- a/src/djangooidc/exceptions.py +++ b/src/djangooidc/exceptions.py @@ -33,6 +33,10 @@ class AuthenticationFailed(OIDCException): friendly_message = "This login attempt didn't work." +class NoStateDefined(OIDCException): + friendly_message = "The session state is None." + + class InternalError(OIDCException): status = status.INTERNAL_SERVER_ERROR friendly_message = "The system broke while trying to log you in." diff --git a/src/djangooidc/oidc.py b/src/djangooidc/oidc.py index 91bfddc66..bff766bb4 100644 --- a/src/djangooidc/oidc.py +++ b/src/djangooidc/oidc.py @@ -183,6 +183,8 @@ class Client(oic.Client): if authn_response["state"] != session.get("state", None): # this most likely means the user's Django session vanished logger.error("Received state not the same as expected for %s" % state) + if session.get("state", None) is None: + raise o_e.NoStateDefined() raise o_e.AuthenticationFailed(locator=state) if self.behaviour.get("response_type") == "code": @@ -272,6 +274,11 @@ class Client(oic.Client): super(Client, self).store_response(resp, info) + def get_default_acr_value(self): + """returns the acr_value from settings + this helper function is called from djangooidc views""" + return self.behaviour.get("acr_value") + def get_step_up_acr_value(self): """returns the step_up_acr_value from settings this helper function is called from djangooidc views""" diff --git a/src/djangooidc/tests/test_views.py b/src/djangooidc/tests/test_views.py index 282e91e1f..63b23df96 100644 --- a/src/djangooidc/tests/test_views.py +++ b/src/djangooidc/tests/test_views.py @@ -3,6 +3,8 @@ from unittest.mock import MagicMock, patch from django.http import HttpResponse from django.test import Client, TestCase, RequestFactory from django.urls import reverse + +from djangooidc.exceptions import NoStateDefined from ..views import login_callback from .common import less_console_noise @@ -17,6 +19,9 @@ class ViewsTest(TestCase): def say_hi(*args): return HttpResponse("Hi") + def create_acr(*args): + return "any string" + def user_info(*args): return { "sub": "TEST", @@ -34,6 +39,7 @@ class ViewsTest(TestCase): callback_url = reverse("openid_login_callback") # mock mock_client.create_authn_request.side_effect = self.say_hi + mock_client.get_default_acr_value.side_effect = self.create_acr # test response = self.client.get(reverse("login"), {"next": callback_url}) # assert @@ -53,6 +59,19 @@ class ViewsTest(TestCase): self.assertTemplateUsed(response, "500.html") self.assertIn("Server error", response.content.decode("utf-8")) + def test_callback_with_no_session_state(self, mock_client): + """If the local session is None (ie the server restarted while user was logged out), + we do not throw an exception. Rather, we attempt to login again.""" + # mock + mock_client.get_default_acr_value.side_effect = self.create_acr + mock_client.callback.side_effect = NoStateDefined() + # test + with less_console_noise(): + response = self.client.get(reverse("openid_login_callback")) + # assert + self.assertEqual(response.status_code, 302) + self.assertEqual(response.url, "/") + def test_login_callback_reads_next(self, mock_client): # setup session = self.client.session diff --git a/src/djangooidc/views.py b/src/djangooidc/views.py index b5905df48..b786ed2e9 100644 --- a/src/djangooidc/views.py +++ b/src/djangooidc/views.py @@ -55,6 +55,10 @@ def error_page(request, error): def openid(request): """Redirect the user to an authentication provider (OP).""" + + # If the session reset because of a server restart, attempt to login again + request.session["acr_value"] = CLIENT.get_default_acr_value() + request.session["next"] = request.GET.get("next", "/") try: @@ -78,9 +82,13 @@ def login_callback(request): if user: login(request, user) logger.info("Successfully logged in user %s" % user) + # Double login bug? return redirect(request.session.get("next", "/")) else: raise o_e.BannedUser() + except o_e.NoStateDefined as nsd_err: + logger.debug(f"No State Defined: {nsd_err}") + return redirect(request.session.get("next", "/")) except Exception as err: return error_page(request, err) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 8d3b1d29f..9ad459a06 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -1239,6 +1239,28 @@ class DraftDomainAdmin(ListHeaderAdmin): search_help_text = "Search by draft domain name." +class VeryImportantPersonAdmin(ListHeaderAdmin): + list_display = ("email", "custom_user_label", "notes", "created_at") + search_fields = ["email"] + search_help_text = "Search by email." + list_filter = [ + "user", + ] + readonly_fields = [ + "user", + ] + + def custom_user_label(self, obj): + return obj.user + + custom_user_label.short_description = "Requestor" # type: ignore + + def save_model(self, request, obj, form, change): + # Set the user field to the current admin user + obj.user = request.user if request.user.is_authenticated else None + super().save_model(request, obj, form, change) + + admin.site.unregister(LogEntry) # Unregister the default registration admin.site.register(LogEntry, CustomLogEntryAdmin) admin.site.register(models.User, MyUserAdmin) @@ -1259,3 +1281,4 @@ admin.site.register(models.Website, WebsiteAdmin) admin.site.register(models.PublicContact, AuditedAdmin) admin.site.register(models.DomainApplication, DomainApplicationAdmin) admin.site.register(models.TransitionDomain, TransitionDomainAdmin) +admin.site.register(models.VeryImportantPerson, VeryImportantPersonAdmin) diff --git a/src/registrar/migrations/0063_veryimportantperson.py b/src/registrar/migrations/0063_veryimportantperson.py new file mode 100644 index 000000000..3d56ae1ac --- /dev/null +++ b/src/registrar/migrations/0063_veryimportantperson.py @@ -0,0 +1,37 @@ +# Generated by Django 4.2.7 on 2024-01-19 00:18 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0062_alter_host_name"), + ] + + operations = [ + migrations.CreateModel( + name="VeryImportantPerson", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("email", models.EmailField(blank=True, db_index=True, help_text="Email", max_length=254, null=True)), + ("notes", models.TextField(blank=True, help_text="Notes", null=True)), + ( + "user", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="verifiedby_user", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "abstract": False, + }, + ), + ] diff --git a/src/registrar/models/__init__.py b/src/registrar/models/__init__.py index 6afad5a5c..90cb2e286 100644 --- a/src/registrar/models/__init__.py +++ b/src/registrar/models/__init__.py @@ -13,6 +13,7 @@ from .user import User from .user_group import UserGroup from .website import Website from .transition_domain import TransitionDomain +from .very_important_person import VeryImportantPerson __all__ = [ "Contact", @@ -29,6 +30,7 @@ __all__ = [ "UserGroup", "Website", "TransitionDomain", + "VeryImportantPerson", ] auditlog.register(Contact) @@ -45,3 +47,4 @@ auditlog.register(User, m2m_fields=["user_permissions", "groups"]) auditlog.register(UserGroup, m2m_fields=["permissions"]) auditlog.register(Website) auditlog.register(TransitionDomain) +auditlog.register(VeryImportantPerson) diff --git a/src/registrar/models/user.py b/src/registrar/models/user.py index d79e4c9ee..269569bfe 100644 --- a/src/registrar/models/user.py +++ b/src/registrar/models/user.py @@ -7,6 +7,7 @@ from registrar.models.user_domain_role import UserDomainRole from .domain_invitation import DomainInvitation from .transition_domain import TransitionDomain +from .very_important_person import VeryImportantPerson from .domain import Domain from phonenumber_field.modelfields import PhoneNumberField # type: ignore @@ -89,6 +90,10 @@ class User(AbstractUser): if TransitionDomain.objects.filter(username=email).exists(): return False + # New users flagged by Staff to bypass ial2 + if VeryImportantPerson.objects.filter(email=email).exists(): + return False + # A new incoming user who is being invited to be a domain manager (that is, # their email address is in DomainInvitation for an invitation that is not yet "retrieved"). invited = DomainInvitation.DomainInvitationStatus.INVITED diff --git a/src/registrar/models/very_important_person.py b/src/registrar/models/very_important_person.py new file mode 100644 index 000000000..42621c64a --- /dev/null +++ b/src/registrar/models/very_important_person.py @@ -0,0 +1,36 @@ +from django.db import models + +from .utility.time_stamped_model import TimeStampedModel + + +class VeryImportantPerson(TimeStampedModel): + + """""" + + email = models.EmailField( + null=True, + blank=True, + help_text="Email", + db_index=True, + ) + + user = models.ForeignKey( + "registrar.User", + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name="verifiedby_user", + ) + + notes = models.TextField( + null=True, + blank=True, + help_text="Notes", + ) + + def __str__(self): + try: + if self.email: + return self.email + except Exception: + return "" diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index f7b1ef06e..175bef11f 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -14,9 +14,11 @@ from registrar.admin import ( ContactAdmin, DomainInformationAdmin, UserDomainRoleAdmin, + VeryImportantPersonAdmin, ) from registrar.models import Domain, DomainApplication, DomainInformation, User, DomainInvitation, Contact, Website from registrar.models.user_domain_role import UserDomainRole +from registrar.models.very_important_person import VeryImportantPerson from .common import ( MockSESClient, AuditedAdminMockData, @@ -1737,3 +1739,26 @@ class ContactAdminTest(TestCase): def tearDown(self): User.objects.all().delete() + + +class VeryImportantPersonAdminTestCase(TestCase): + def setUp(self): + self.superuser = create_superuser() + self.factory = RequestFactory() + + def test_save_model_sets_user_field(self): + # Create an instance of the admin class + admin_instance = VeryImportantPersonAdmin(model=VeryImportantPerson, admin_site=None) + + # Create a VeryImportantPerson instance + vip_instance = VeryImportantPerson(email="test@example.com", notes="Test Notes") + + # Create a request object + request = self.factory.post("/admin/yourapp/veryimportantperson/add/") + request.user = self.superuser + + # Call the save_model method + admin_instance.save_model(request, vip_instance, None, None) + + # Check that the user field is set to the request.user + self.assertEqual(vip_instance.user, self.superuser) diff --git a/src/registrar/tests/test_models.py b/src/registrar/tests/test_models.py index 464d00dc5..ef6522747 100644 --- a/src/registrar/tests/test_models.py +++ b/src/registrar/tests/test_models.py @@ -15,7 +15,8 @@ from registrar.models import ( ) import boto3_mocking -from registrar.models.transition_domain import TransitionDomain # type: ignore +from registrar.models.transition_domain import TransitionDomain +from registrar.models.very_important_person import VeryImportantPerson # type: ignore from .common import MockSESClient, less_console_noise, completed_application from django_fsm import TransitionNotAllowed @@ -652,6 +653,12 @@ class TestUser(TestCase): TransitionDomain.objects.get_or_create(username=self.user.email, domain_name=self.domain_name) self.assertFalse(User.needs_identity_verification(self.user.email, self.user.username)) + def test_identity_verification_with_very_important_person(self): + """A Very Important Person should return False + when tested with class method needs_identity_verification""" + VeryImportantPerson.objects.get_or_create(email=self.user.email) + self.assertFalse(User.needs_identity_verification(self.user.email, self.user.username)) + def test_identity_verification_with_invited_user(self): """An invited user should return False when tested with class method needs_identity_verification""" From 61f2af6ce262e588d99171c33f9b9f6dd3abef9e Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 19 Jan 2024 08:40:38 -0700 Subject: [PATCH 091/120] Add UTC --- src/registrar/templates/home.html | 6 +++--- src/registrar/templates/includes/modal.html | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index d8566a18c..eaafb625e 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -121,8 +121,8 @@ <th th scope="row" role="rowheader" data-label="Domain name"> {% if application.requested_domain is None %} New domain request - <br> - <span class="text-base font-body-xs">({{ application.created_at }})</span> + <br aria-hidden="true"> + <span class="text-base font-body-xs">({{ application.created_at }} UTC)</span> {% else %} {{ application.requested_domain.name }} {% endif %} @@ -141,7 +141,7 @@ <svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24"> <use xlink:href="{%static 'img/sprite.svg'%}#edit"></use> </svg> - Edit <span class="usa-sr-only">{{ application.requested_domain.name|default:"New domain request ("|add:application.created_at|add:")" }}</span> + Edit <span class="usa-sr-only">{{ application.requested_domain.name|default:"New domain request ("|add:application.created_at|add:" UTC)" }}</span> {% else %} <a href="{% url 'application-status' application.pk %}"> <svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24"> diff --git a/src/registrar/templates/includes/modal.html b/src/registrar/templates/includes/modal.html index 45a6fa3b5..0f3150f4c 100644 --- a/src/registrar/templates/includes/modal.html +++ b/src/registrar/templates/includes/modal.html @@ -5,7 +5,7 @@ <h2 class="usa-modal__heading" id="modal-1-heading"> {{ modal_heading }} {% if heading_value is not None %} - <br> + <br aria-hidden="true"> {{ heading_value }} {% endif %} </h2> From ba85517f46b46a871b8c73b5ff87697344422b84 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 19 Jan 2024 08:50:16 -0700 Subject: [PATCH 092/120] Update home.html --- src/registrar/templates/home.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index eaafb625e..3c0e484af 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -147,7 +147,7 @@ <svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24"> <use xlink:href="{%static 'img/sprite.svg'%}#settings"></use> </svg> - Manage <span class="usa-sr-only">{{ application.requested_domain.name|default:"New domain request ("|add:application.created_at|add:")" }}</span> + Manage <span class="usa-sr-only">{{ application.requested_domain.name|default:"New domain request ("|add:application.created_at|add:" UTC)" }}</span> {% endif %} </a> </td> @@ -165,7 +165,7 @@ <svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24"> <use xlink:href="{%static 'img/sprite.svg'%}#delete"></use> </svg> - Delete <span class="usa-sr-only">{{ application.requested_domain.name|default:"New domain request ("|add:application.created_at|add:")" }}</span> + Delete <span class="usa-sr-only">{{ application.requested_domain.name|default:"New domain request ("|add:application.created_at|add:" UTC)" }}</span> </a> <div @@ -180,7 +180,7 @@ {% with prefix="New domain request (" %} {% if application.created_at %} {% with formatted_date=application.created_at|date:"DATETIME_FORMAT" %} - {% with modal_content=prefix|add:formatted_date|add:")" %} + {% with modal_content=prefix|add:formatted_date|add:" UTC)" %} {% include 'includes/modal.html' with modal_heading="Are you sure you want to delete" heading_value="New domain request?" modal_description="This will remove "|add:modal_content|add:" from the .gov registrar. This action cannot be undone." modal_button=modal_button|safe %} {% endwith %} {% endwith %} From e6812afe8bd6f243404cd1875a423f089211bc4c Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 19 Jan 2024 09:13:04 -0700 Subject: [PATCH 093/120] Add default to csv_export, readd content --- src/registrar/templates/domain_security_email.html | 2 +- src/registrar/utility/csv_export.py | 2 +- src/registrar/views/domain.py | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/registrar/templates/domain_security_email.html b/src/registrar/templates/domain_security_email.html index 4054186da..e1755f85e 100644 --- a/src/registrar/templates/domain_security_email.html +++ b/src/registrar/templates/domain_security_email.html @@ -20,7 +20,7 @@ <button type="submit" class="usa-button" - >{% if form.security_email.value is None or form.security_email.value == "dotgov@cisa.dhs.gov"%}Add security email{% else %}Save{% endif %}</button> + >{% if form.security_email.value is None or form.security_email.value == "dotgov@cisa.dhs.gov" or form.security_email.value == "registrar@dotgov.gov"%}Add security email{% else %}Save{% endif %}</button> </form> {% endblock %} {# domain_content #} diff --git a/src/registrar/utility/csv_export.py b/src/registrar/utility/csv_export.py index 52afb218b..3924c03c4 100644 --- a/src/registrar/utility/csv_export.py +++ b/src/registrar/utility/csv_export.py @@ -38,7 +38,7 @@ def write_row(writer, columns, domain_info): if security_contacts: security_email = security_contacts[0].email - invalid_emails = {"registrar@dotgov.gov"} + invalid_emails = {"registrar@dotgov.gov", "dotgov@cisa.dhs.gov"} # These are default emails that should not be displayed in the csv report if security_email is not None and security_email.lower() in invalid_emails: security_email = "(blank)" diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 4d47a6f59..f3e0a3b9d 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -568,7 +568,9 @@ class DomainSecurityEmailView(DomainFormBaseView): """The initial value for the form.""" initial = super().get_initial() security_contact = self.object.security_contact - if security_contact is None or security_contact.email == "dotgov@cisa.dhs.gov": + + invalid_emails = ["dotgov@cisa.dhs.gov", "registrar@dotgov.gov"] + if security_contact is None or security_contact.email in invalid_emails: initial["security_email"] = None return initial initial["security_email"] = security_contact.email From 8bcdbec641835566db4bf0ddfe6b7d2a95c2cfa3 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 19 Jan 2024 09:31:30 -0700 Subject: [PATCH 094/120] Fix unit test --- src/registrar/tests/test_reports.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/tests/test_reports.py b/src/registrar/tests/test_reports.py index b1c631b3d..a85fb5849 100644 --- a/src/registrar/tests/test_reports.py +++ b/src/registrar/tests/test_reports.py @@ -399,7 +399,7 @@ class ExportDataTest(MockEppLib): "adomain10.gov,Federal,Armed Forces Retirement Home,Ready\n" "adomain2.gov,Interstate,(blank),Dns needed\n" "ddomain3.gov,Federal,Armed Forces Retirement Home,123@mail.gov,On hold,2023-05-25\n" - "defaultsecurity.gov,Federal - Executive,World War I Centennial Commission,dotgov@cisa.dhs.gov,Ready" + "defaultsecurity.gov,Federal - Executive,World War I Centennial Commission,(blank),Ready" ) # Normalize line endings and remove commas, From ec5449a833889be91c58021f3981840ceaf8da90 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 19 Jan 2024 10:24:53 -0700 Subject: [PATCH 095/120] Add www. strip --- src/registrar/models/utility/domain_helper.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/registrar/models/utility/domain_helper.py b/src/registrar/models/utility/domain_helper.py index a808ef803..3267f0c93 100644 --- a/src/registrar/models/utility/domain_helper.py +++ b/src/registrar/models/utility/domain_helper.py @@ -57,6 +57,9 @@ class DomainHelper: # If blank ok is true, just return the domain return domain + if domain.startswith("www."): + domain = domain[4:] + if domain.endswith(".gov"): domain = domain[:-4] From 6f4eeb495eb7ee97cf46d1eacaf73772deba92a2 Mon Sep 17 00:00:00 2001 From: Michelle Rago <60157596+michelle-rago@users.noreply.github.com> Date: Fri, 19 Jan 2024 12:29:42 -0500 Subject: [PATCH 096/120] Change HTML page title to "Add a domain manager" (#1661) --- src/registrar/templates/domain_add_user.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/templates/domain_add_user.html b/src/registrar/templates/domain_add_user.html index 4bad529a7..d67c343a6 100644 --- a/src/registrar/templates/domain_add_user.html +++ b/src/registrar/templates/domain_add_user.html @@ -1,7 +1,7 @@ {% extends "domain_base.html" %} {% load static field_helpers %} -{% block title %}Add another user | {% endblock %} +{% block title %}Add a domain manager | {% endblock %} {% block domain_content %} <h1>Add a domain manager</h1> From b491d9f3ea41d5f0e397047992dcf3e1ed0550ab Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 19 Jan 2024 10:50:13 -0700 Subject: [PATCH 097/120] Remove br, change text content for newly created --- src/registrar/templates/home.html | 23 +++++++++++---------- src/registrar/templates/includes/modal.html | 3 ++- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index 3c0e484af..c06a0a43e 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -121,7 +121,8 @@ <th th scope="row" role="rowheader" data-label="Domain name"> {% if application.requested_domain is None %} New domain request - <br aria-hidden="true"> + {# Add a breakpoint #} + <div aria-hidden="true"></div> <span class="text-base font-body-xs">({{ application.created_at }} UTC)</span> {% else %} {{ application.requested_domain.name }} @@ -177,17 +178,17 @@ > <form method="POST" action="{% url "application-delete" pk=application.id %}"> {% if application.requested_domain is None %} - {% with prefix="New domain request (" %} - {% if application.created_at %} - {% with formatted_date=application.created_at|date:"DATETIME_FORMAT" %} - {% with modal_content=prefix|add:formatted_date|add:" UTC)" %} - {% include 'includes/modal.html' with modal_heading="Are you sure you want to delete" heading_value="New domain request?" modal_description="This will remove "|add:modal_content|add:" from the .gov registrar. This action cannot be undone." modal_button=modal_button|safe %} - {% endwith %} + {% if application.created_at %} + {% with prefix="(created " %} + {% with formatted_date=application.created_at|date:"DATETIME_FORMAT" %} + {% with modal_content=prefix|add:formatted_date|add:" UTC)" %} + {% include 'includes/modal.html' with modal_heading="Are you sure you want to delete this domain request?" modal_description="This will remove the domain request "|add:modal_content|add:" from the .gov registrar. This action cannot be undone." modal_button=modal_button|safe %} {% endwith %} - {% else %} - {% include 'includes/modal.html' with modal_heading="Are you sure you want to delete New domain request?" modal_description="This will remove the domain request from the .gov registrar. This action cannot be undone." modal_button=modal_button|safe %} - {% endif %} - {% endwith %} + {% endwith %} + {% endwith %} + {% else %} + {% include 'includes/modal.html' with modal_heading="Are you sure you want to delete New domain request?" modal_description="This will remove the domain request from the .gov registrar. This action cannot be undone." modal_button=modal_button|safe %} + {% endif %} {% else %} {% with modal_heading_value=application.requested_domain.name|add:"?" %} {% include 'includes/modal.html' with modal_heading="Are you sure you want to delete" heading_value=modal_heading_value modal_description="This will remove the domain request from the .gov registrar. This action cannot be undone." modal_button=modal_button|safe %} diff --git a/src/registrar/templates/includes/modal.html b/src/registrar/templates/includes/modal.html index 0f3150f4c..8e6cb66d5 100644 --- a/src/registrar/templates/includes/modal.html +++ b/src/registrar/templates/includes/modal.html @@ -5,7 +5,8 @@ <h2 class="usa-modal__heading" id="modal-1-heading"> {{ modal_heading }} {% if heading_value is not None %} - <br aria-hidden="true"> + {# Add a breakpoint #} + <div aria-hidden="true"></div> {{ heading_value }} {% endif %} </h2> From 928fa9428af4d027f83c6ebe07e98594140373d8 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 19 Jan 2024 12:02:22 -0700 Subject: [PATCH 098/120] Fix screenreader bug --- src/registrar/templates/home.html | 36 ++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index c06a0a43e..06ad1b876 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -137,19 +137,29 @@ </td> <td data-label="Status">{{ application.get_status_display }}</td> <td> - {% if application.status == application.ApplicationStatus.STARTED or application.status == application.ApplicationStatus.ACTION_NEEDED or application.status == application.ApplicationStatus.WITHDRAWN %} - <a href="{% url 'edit-application' application.pk %}"> - <svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24"> - <use xlink:href="{%static 'img/sprite.svg'%}#edit"></use> - </svg> - Edit <span class="usa-sr-only">{{ application.requested_domain.name|default:"New domain request ("|add:application.created_at|add:" UTC)" }}</span> - {% else %} - <a href="{% url 'application-status' application.pk %}"> - <svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24"> - <use xlink:href="{%static 'img/sprite.svg'%}#settings"></use> - </svg> - Manage <span class="usa-sr-only">{{ application.requested_domain.name|default:"New domain request ("|add:application.created_at|add:" UTC)" }}</span> - {% endif %} + {% with prefix="New domain request ("%} + {% with date=application.created_at|date:"DATETIME_FORMAT"%} + {% with name_default=prefix|add:date|add:" UTC)"%} + {% if application.status == application.ApplicationStatus.STARTED or application.status == application.ApplicationStatus.ACTION_NEEDED or application.status == application.ApplicationStatus.WITHDRAWN %} + <a href="{% url 'edit-application' application.pk %}"> + <svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24"> + <use xlink:href="{%static 'img/sprite.svg'%}#edit"></use> + </svg> + {% if application.requested_domain is not None%} + Edit <span class="usa-sr-only">{{ application.requested_domain.name }}</span> + {% else %} + Edit <span class="usa-sr-only">{{ name_default }}</span> + {% endif %} + {% else %} + <a href="{% url 'application-status' application.pk %}"> + <svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24"> + <use xlink:href="{%static 'img/sprite.svg'%}#settings"></use> + </svg> + Manage <span class="usa-sr-only">{{ application.requested_domain.name|default:name_default }}</span> + {% endif %} + {% endwith %} + {% endwith %} + {% endwith %} </a> </td> {% if has_deletable_applications %} From 11b48eb03060d5259d4015006592dcb8f100cb86 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 19 Jan 2024 13:15:30 -0700 Subject: [PATCH 099/120] Fix Vo for delete --- src/registrar/templates/home.html | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index 06ad1b876..32ed8650f 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -176,7 +176,17 @@ <svg class="usa-icon" aria-hidden="true" focusable="false" role="img" width="24"> <use xlink:href="{%static 'img/sprite.svg'%}#delete"></use> </svg> - Delete <span class="usa-sr-only">{{ application.requested_domain.name|default:"New domain request ("|add:application.created_at|add:" UTC)" }}</span> + {% with prefix="New domain request ("%} + {% with date=application.created_at|date:"DATETIME_FORMAT"%} + {% with name_default=prefix|add:date|add:" UTC)"%} + {% if application.requested_domain is not None %} + Delete <span class="usa-sr-only">{{ application.requested_domain.name }}</span> + {% else %} + Delete <span class="usa-sr-only">{{ name_default }}</span> + {% endif %} + {% endwith %} + {% endwith %} + {% endwith %} </a> <div From d1a80d3307d95dfd042ce841b5424ea3ca630ddf Mon Sep 17 00:00:00 2001 From: Rachid Mrad <rachid.mrad@ecstech.com> Date: Fri, 19 Jan 2024 15:22:42 -0500 Subject: [PATCH 100/120] revert column header to user, truncate notes --- src/registrar/admin.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 9ad459a06..125ff2e8a 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -1240,7 +1240,7 @@ class DraftDomainAdmin(ListHeaderAdmin): class VeryImportantPersonAdmin(ListHeaderAdmin): - list_display = ("email", "custom_user_label", "notes", "created_at") + list_display = ("email", "user", "truncated_notes", "created_at") search_fields = ["email"] search_help_text = "Search by email." list_filter = [ @@ -1250,10 +1250,11 @@ class VeryImportantPersonAdmin(ListHeaderAdmin): "user", ] - def custom_user_label(self, obj): - return obj.user + def truncated_notes(self, obj): + # Truncate the 'notes' field to 200 characters + return str(obj.notes)[:50] - custom_user_label.short_description = "Requestor" # type: ignore + truncated_notes.short_description = "Notes (Truncated)" # type: ignore def save_model(self, request, obj, form, change): # Set the user field to the current admin user From d1daefd42c177babbc2e5072ab18a9cb8e6abab6 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 19 Jan 2024 15:54:34 -0700 Subject: [PATCH 101/120] Logic to remove orphaned contacts --- src/registrar/views/application.py | 69 +++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/src/registrar/views/application.py b/src/registrar/views/application.py index 0d006f5ed..ee1b41a75 100644 --- a/src/registrar/views/application.py +++ b/src/registrar/views/application.py @@ -1,5 +1,5 @@ import logging - +from collections import defaultdict from django.http import Http404, HttpResponse, HttpResponseRedirect from django.shortcuts import redirect, render from django.urls import resolve, reverse @@ -10,6 +10,7 @@ from django.contrib import messages from registrar.forms import application_wizard as forms from registrar.models import DomainApplication +from registrar.models.contact import Contact from registrar.models.user import User from registrar.utility import StrEnum from registrar.views.utility import StepsHelper @@ -641,3 +642,69 @@ class DomainApplicationDeleteView(DomainApplicationPermissionDeleteView): def get_success_url(self): """After a delete is successful, redirect to home""" return reverse("home") + + def post(self, request, *args, **kwargs): + # Grab all orphaned contacts + application: DomainApplication = self.get_object() + contacts_to_delete, duplicates = self._get_orphaned_contacts(application) + + # Delete the DomainApplication + response = super().post(request, *args, **kwargs) + + x = Contact.objects.filter(id__in=contacts_to_delete, user=None) + print(f"These contacts will be deleted: {x}") + + # Delete orphaned contacts - but only for if they are not associated with a user + Contact.objects.filter(id__in=contacts_to_delete, user=None).delete() + + # After a delete occurs, do a second sweep on any returned duplicates. + # This determines if any of these three fields share a contact, which is used for + # the edge case where the same user may be an AO, and a submitter, for example. + if len(duplicates) > 0: + duplicates_to_delete, _ = self._get_orphaned_contacts(application) + a = Contact.objects.filter(id__in=duplicates_to_delete, user=None) + print(f"These other contacts will be deleted: {a}") + Contact.objects.filter(id__in=duplicates_to_delete, user=None).delete() + + return response + + def _get_orphaned_contacts(self, application: DomainApplication, check_db=True): + """Collects all orphaned contacts""" + contacts_to_delete = [] + + # Get each contact object on the DomainApplication object + ao = application.authorizing_official + submitter = application.submitter + other_contacts = list(application.other_contacts.all()) + other_contact_ids = application.other_contacts.all().values_list("id", flat=True) + + # Check if the desired item still exists in the DB + if check_db: + ao = self._get_contacts_by_id([ao.id]).first() + submitter = self._get_contacts_by_id([submitter.id]).first() + other_contacts = self._get_contacts_by_id(other_contact_ids) + + # Pair each contact with its related name + checked_contacts = [(ao, "authorizing_official"), (submitter, "submitted_applications")] + checked_contacts.extend((contact, "contact_applications") for contact in other_contacts) + + for contact, related_name in checked_contacts: + if contact is not None and not contact.has_more_than_one_join(related_name): + contacts_to_delete.append(contact.id) + + return (contacts_to_delete, self._get_duplicates(checked_contacts)) + + def _get_contacts_by_id(self, contact_ids): + """Given a list of ids, grab contacts if it exists""" + contacts = Contact.objects.filter(id__in=contact_ids) + return contacts + + def _get_duplicates(self, objects): + """Given a list of objects, return a list of which items were duplicates""" + # Gets the occurence count + object_dict = defaultdict(int) + for contact, _ in objects: + object_dict[contact] += 1 + + duplicates = [item for item, count in object_dict.items() if count > 1] + return duplicates From 5fca4a1065d5e7e861911e980eb75c39a4e69237 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 19 Jan 2024 15:55:46 -0700 Subject: [PATCH 102/120] Remove prints --- src/registrar/views/application.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/registrar/views/application.py b/src/registrar/views/application.py index ee1b41a75..9a96824c7 100644 --- a/src/registrar/views/application.py +++ b/src/registrar/views/application.py @@ -651,9 +651,6 @@ class DomainApplicationDeleteView(DomainApplicationPermissionDeleteView): # Delete the DomainApplication response = super().post(request, *args, **kwargs) - x = Contact.objects.filter(id__in=contacts_to_delete, user=None) - print(f"These contacts will be deleted: {x}") - # Delete orphaned contacts - but only for if they are not associated with a user Contact.objects.filter(id__in=contacts_to_delete, user=None).delete() @@ -662,8 +659,6 @@ class DomainApplicationDeleteView(DomainApplicationPermissionDeleteView): # the edge case where the same user may be an AO, and a submitter, for example. if len(duplicates) > 0: duplicates_to_delete, _ = self._get_orphaned_contacts(application) - a = Contact.objects.filter(id__in=duplicates_to_delete, user=None) - print(f"These other contacts will be deleted: {a}") Contact.objects.filter(id__in=duplicates_to_delete, user=None).delete() return response From c7581d196b4a17cb6ab5467f53c23736ab062e20 Mon Sep 17 00:00:00 2001 From: Cameron Dixon <cameron.dixon@cisa.dhs.gov> Date: Sat, 20 Jan 2024 21:06:43 -0500 Subject: [PATCH 103/120] Add preload content to status_change_approved.txt --- src/registrar/templates/emails/status_change_approved.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/registrar/templates/emails/status_change_approved.txt b/src/registrar/templates/emails/status_change_approved.txt index bc548bfb6..8cfd2a6b1 100644 --- a/src/registrar/templates/emails/status_change_approved.txt +++ b/src/registrar/templates/emails/status_change_approved.txt @@ -32,6 +32,14 @@ Learn more about: - Domain security best practices <https://get.gov/domains/security/> +WE’LL PRELOAD THIS DOMAIN +Each month, we add new .gov domains to the HSTS preload list. This requires browsers to use a secure HTTPS connection to any website at this domain and ensures the content you publish is exactly what your visitors get. It also means you’ll need to support HTTPS anywhere the domain is used for websites – on the internet or internally. We’ll add your domain to the preload list soon. + +Learn more about: +- What preloading is <https://get.gov/domains/security/#preload-your-domain> +- Why we preload new domains <https://get.gov/posts/2021-06-21-an-intent-to-preload/> + + THANK YOU .Gov helps the public identify official, trusted information. Thank you for using a .gov domain. From 4f8216391362d4cf1d72bd63f63bd97805b308de Mon Sep 17 00:00:00 2001 From: Michelle Rago <60157596+michelle-rago@users.noreply.github.com> Date: Mon, 22 Jan 2024 09:06:37 -0500 Subject: [PATCH 104/120] Minor edit --- src/registrar/templates/emails/status_change_approved.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/templates/emails/status_change_approved.txt b/src/registrar/templates/emails/status_change_approved.txt index 8cfd2a6b1..8283f0e67 100644 --- a/src/registrar/templates/emails/status_change_approved.txt +++ b/src/registrar/templates/emails/status_change_approved.txt @@ -33,7 +33,7 @@ Learn more about: WE’LL PRELOAD THIS DOMAIN -Each month, we add new .gov domains to the HSTS preload list. This requires browsers to use a secure HTTPS connection to any website at this domain and ensures the content you publish is exactly what your visitors get. It also means you’ll need to support HTTPS anywhere the domain is used for websites – on the internet or internally. We’ll add your domain to the preload list soon. +We add new .gov domains to the HSTS preload list each month. This requires browsers to use a secure HTTPS connection to any website at this domain and ensures the content you publish is exactly what your visitors get. It also means you’ll need to support HTTPS anywhere the domain is used for websites – on the internet or internally. We’ll add your domain to the preload list soon. Learn more about: - What preloading is <https://get.gov/domains/security/#preload-your-domain> From a119cab680d12067d655cca953902eefd3b84c14 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 22 Jan 2024 09:37:55 -0700 Subject: [PATCH 105/120] Add test case for delete --- src/registrar/tests/test_views.py | 69 +++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index c02f70051..7f47a40f2 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -182,6 +182,75 @@ class LoggedInTests(TestWithUser): # clean up application.delete() + def test_home_deletes_domain_application_and_orphans(self): + """Tests if delete for DomainApplication 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") + application = DomainApplication.objects.create( + creator=self.user, requested_domain=site, status=DomainApplication.ApplicationStatus.WITHDRAWN, + authorizing_official=contact, submitter=contact_user + ) + application.other_contacts.set([contact_2]) + + # Create a second application to attach contacts to + site_2 = DraftDomain.objects.create(name="teaville.gov") + application_2 = DomainApplication.objects.create( + creator=self.user, requested_domain=site_2, status=DomainApplication.ApplicationStatus.STARTED, + authorizing_official=contact_2, submitter=contact_shared + ) + application_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("application-delete", kwargs={"pk": application.pk}), follow=True) + + 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) + + # clean up + application.delete() + def test_application_form_view(self): response = self.client.get("/request/", follow=True) self.assertContains( From 8a75c8fcd6e1946e75d679b9098688d82bea23a2 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 22 Jan 2024 09:52:34 -0700 Subject: [PATCH 106/120] Finish test cases --- src/registrar/tests/test_views.py | 89 +++++++++++++++++++++++++----- src/registrar/views/application.py | 6 +- 2 files changed, 78 insertions(+), 17 deletions(-) diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index 7f47a40f2..28074dd76 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -89,6 +89,10 @@ class LoggedInTests(TestWithUser): super().setUp() self.client.force_login(self.user) + def tearDown(self): + super().tearDown() + Contact.objects.all().delete() + def test_home_lists_domain_applications(self): response = self.client.get("/") self.assertNotContains(response, "igorville.gov") @@ -96,8 +100,8 @@ class LoggedInTests(TestWithUser): application = DomainApplication.objects.create(creator=self.user, requested_domain=site) response = self.client.get("/") - # count = 5 because of screenreader content - self.assertContains(response, "igorville.gov", count=5) + # count = 7 because of screenreader content + self.assertContains(response, "igorville.gov", count=7) # clean up application.delete() @@ -184,7 +188,7 @@ class LoggedInTests(TestWithUser): def test_home_deletes_domain_application_and_orphans(self): """Tests if delete for DomainApplication deletes orphaned Contact objects""" - + # Create the site and contacts to delete (orphaned) contact = Contact.objects.create( first_name="Henry", @@ -202,22 +206,26 @@ class LoggedInTests(TestWithUser): ) # Attach a user object to a contact (should not be deleted) - contact_user, _ = Contact.objects.get_or_create( - user=self.user - ) + contact_user, _ = Contact.objects.get_or_create(user=self.user) site = DraftDomain.objects.create(name="igorville.gov") application = DomainApplication.objects.create( - creator=self.user, requested_domain=site, status=DomainApplication.ApplicationStatus.WITHDRAWN, - authorizing_official=contact, submitter=contact_user + creator=self.user, + requested_domain=site, + status=DomainApplication.ApplicationStatus.WITHDRAWN, + authorizing_official=contact, + submitter=contact_user, ) application.other_contacts.set([contact_2]) - + # Create a second application to attach contacts to site_2 = DraftDomain.objects.create(name="teaville.gov") application_2 = DomainApplication.objects.create( - creator=self.user, requested_domain=site_2, status=DomainApplication.ApplicationStatus.STARTED, - authorizing_official=contact_2, submitter=contact_shared + creator=self.user, + requested_domain=site_2, + status=DomainApplication.ApplicationStatus.STARTED, + authorizing_official=contact_2, + submitter=contact_shared, ) application_2.other_contacts.set([contact_shared]) @@ -239,7 +247,7 @@ class LoggedInTests(TestWithUser): 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() @@ -248,8 +256,61 @@ class LoggedInTests(TestWithUser): self.assertEqual(edge_case, contact_2) - # clean up - application.delete() + def test_home_deletes_domain_application_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") + application = DomainApplication.objects.create( + creator=self.user, + requested_domain=site, + status=DomainApplication.ApplicationStatus.WITHDRAWN, + authorizing_official=contact, + submitter=contact_user, + ) + application.other_contacts.set([contact_2]) + + # Create a second application to attach contacts to + site_2 = DraftDomain.objects.create(name="teaville.gov") + application_2 = DomainApplication.objects.create( + creator=self.user, + requested_domain=site_2, + status=DomainApplication.ApplicationStatus.STARTED, + authorizing_official=contact_2, + submitter=contact_shared, + ) + application_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("application-delete", kwargs={"pk": application_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_application_form_view(self): response = self.client.get("/request/", follow=True) diff --git a/src/registrar/views/application.py b/src/registrar/views/application.py index 9a96824c7..b58deecc4 100644 --- a/src/registrar/views/application.py +++ b/src/registrar/views/application.py @@ -675,8 +675,8 @@ class DomainApplicationDeleteView(DomainApplicationPermissionDeleteView): # Check if the desired item still exists in the DB if check_db: - ao = self._get_contacts_by_id([ao.id]).first() - submitter = self._get_contacts_by_id([submitter.id]).first() + ao = self._get_contacts_by_id([ao.id]).first() if ao is not None else None + submitter = self._get_contacts_by_id([submitter.id]).first() if submitter is not None else None other_contacts = self._get_contacts_by_id(other_contact_ids) # Pair each contact with its related name @@ -698,7 +698,7 @@ class DomainApplicationDeleteView(DomainApplicationPermissionDeleteView): """Given a list of objects, return a list of which items were duplicates""" # Gets the occurence count object_dict = defaultdict(int) - for contact, _ in objects: + for contact, _related in objects: object_dict[contact] += 1 duplicates = [item for item, count in object_dict.items() if count > 1] From c07e83b77dbdd1b0d5abc20bb18690435e8da7dc Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 23 Jan 2024 08:18:20 -0700 Subject: [PATCH 107/120] Update src/registrar/views/application.py Co-authored-by: Alysia Broddrick <109625347+abroddrick@users.noreply.github.com> --- src/registrar/views/application.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/views/application.py b/src/registrar/views/application.py index b58deecc4..3e45778d1 100644 --- a/src/registrar/views/application.py +++ b/src/registrar/views/application.py @@ -679,7 +679,7 @@ class DomainApplicationDeleteView(DomainApplicationPermissionDeleteView): submitter = self._get_contacts_by_id([submitter.id]).first() if submitter is not None else None other_contacts = self._get_contacts_by_id(other_contact_ids) - # Pair each contact with its related name + # Pair each contact with its db related name for use in checking if it has joins checked_contacts = [(ao, "authorizing_official"), (submitter, "submitted_applications")] checked_contacts.extend((contact, "contact_applications") for contact in other_contacts) From e5fbd65847f6afbb47a3d9df0f73ed754f48001d Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 23 Jan 2024 08:20:52 -0700 Subject: [PATCH 108/120] Update src/registrar/tests/test_views.py Co-authored-by: Alysia Broddrick <109625347+abroddrick@users.noreply.github.com> --- src/registrar/tests/test_views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index 28074dd76..b1b112cb8 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -236,6 +236,7 @@ class LoggedInTests(TestWithUser): # Trigger the delete logic response = self.client.post(reverse("application-delete", kwargs={"pk": application.pk}), follow=True) + # igorville is now deleted self.assertNotContains(response, "igorville.gov") # Check if the orphaned contact was deleted From 7c4a0a6de551a27f7ef058cea2af54217c36c838 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 23 Jan 2024 08:26:47 -0700 Subject: [PATCH 109/120] PR suggestions --- src/registrar/models/utility/domain_helper.py | 3 --- src/registrar/views/application.py | 21 ++++++++++++++++--- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/registrar/models/utility/domain_helper.py b/src/registrar/models/utility/domain_helper.py index 3267f0c93..a808ef803 100644 --- a/src/registrar/models/utility/domain_helper.py +++ b/src/registrar/models/utility/domain_helper.py @@ -57,9 +57,6 @@ class DomainHelper: # If blank ok is true, just return the domain return domain - if domain.startswith("www."): - domain = domain[4:] - if domain.endswith(".gov"): domain = domain[:-4] diff --git a/src/registrar/views/application.py b/src/registrar/views/application.py index b58deecc4..56048a23e 100644 --- a/src/registrar/views/application.py +++ b/src/registrar/views/application.py @@ -658,13 +658,28 @@ class DomainApplicationDeleteView(DomainApplicationPermissionDeleteView): # This determines if any of these three fields share a contact, which is used for # the edge case where the same user may be an AO, and a submitter, for example. if len(duplicates) > 0: - duplicates_to_delete, _ = self._get_orphaned_contacts(application) + duplicates_to_delete, _ = self._get_orphaned_contacts(application, check_db=True) Contact.objects.filter(id__in=duplicates_to_delete, user=None).delete() return response - def _get_orphaned_contacts(self, application: DomainApplication, check_db=True): - """Collects all orphaned contacts""" + def _get_orphaned_contacts(self, application: DomainApplication, check_db=False): + """ + Collects all orphaned contacts associated with a given DomainApplication object. + + An orphaned contact is defined as a contact that is associated with the application, + but not with any other application. This includes the authorizing official, the submitter, + and any other contacts linked to the application. + + Parameters: + application (DomainApplication): The DomainApplication object for which to find orphaned contacts. + check_db (bool, optional): A flag indicating whether to check the database for the existence of the contacts. + Defaults to False. + + Returns: + tuple: A tuple containing two lists. The first list contains the IDs of the orphaned contacts. + The second list contains any duplicate contacts found. ([Contacts], [Contacts]) + """ contacts_to_delete = [] # Get each contact object on the DomainApplication object From 688257a71823a8aa37c59bcd5baf6c1b45e19d7d Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 23 Jan 2024 08:44:39 -0700 Subject: [PATCH 110/120] Linting --- src/registrar/views/application.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/registrar/views/application.py b/src/registrar/views/application.py index 1147d93eb..6a9d3f817 100644 --- a/src/registrar/views/application.py +++ b/src/registrar/views/application.py @@ -667,17 +667,17 @@ class DomainApplicationDeleteView(DomainApplicationPermissionDeleteView): """ Collects all orphaned contacts associated with a given DomainApplication object. - An orphaned contact is defined as a contact that is associated with the application, - but not with any other application. This includes the authorizing official, the submitter, + An orphaned contact is defined as a contact that is associated with the application, + but not with any other application. This includes the authorizing official, the submitter, and any other contacts linked to the application. Parameters: application (DomainApplication): The DomainApplication object for which to find orphaned contacts. - check_db (bool, optional): A flag indicating whether to check the database for the existence of the contacts. + check_db (bool, optional): A flag indicating whether to check the database for the existence of the contacts. Defaults to False. Returns: - tuple: A tuple containing two lists. The first list contains the IDs of the orphaned contacts. + tuple: A tuple containing two lists. The first list contains the IDs of the orphaned contacts. The second list contains any duplicate contacts found. ([Contacts], [Contacts]) """ contacts_to_delete = [] From 512d87871a4446b5e9398bd9b161071c0f0e97b2 Mon Sep 17 00:00:00 2001 From: Michelle Rago <60157596+michelle-rago@users.noreply.github.com> Date: Tue, 23 Jan 2024 12:31:28 -0500 Subject: [PATCH 111/120] Make "domain" singular in references to .gov operating requirements (#1667) * Make "domain" singular in references to .gov operating requirements * Make "domain" singular in references to .gov operating requirements * Make "domain" singular in references to .gov operating requirements * Make "domain" singular in references to .gov operating requirements * Update label.html * Change form page title from "Apply for a..." to "Request a..." * Plural to singular --- src/registrar/admin.py | 4 ++-- src/registrar/forms/application_wizard.py | 4 ++-- src/registrar/templates/application_form.html | 2 +- src/registrar/templates/application_requirements.html | 2 +- src/registrar/templates/django/forms/label.html | 2 +- src/registrar/tests/test_forms.py | 4 ++-- src/registrar/views/application.py | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 8d3b1d29f..3c1823f83 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -610,7 +610,7 @@ class DomainInformationAdmin(ListHeaderAdmin): ), ("Anything else?", {"fields": ["anything_else"]}), ( - "Requirements for operating .gov domains", + "Requirements for operating a .gov domain", {"fields": ["is_policy_acknowledged"]}, ), ] @@ -779,7 +779,7 @@ class DomainApplicationAdmin(ListHeaderAdmin): ), ("Anything else?", {"fields": ["anything_else"]}), ( - "Requirements for operating .gov domains", + "Requirements for operating a .gov domain", {"fields": ["is_policy_acknowledged"]}, ), ] diff --git a/src/registrar/forms/application_wizard.py b/src/registrar/forms/application_wizard.py index 36ff408c2..85ce28bb6 100644 --- a/src/registrar/forms/application_wizard.py +++ b/src/registrar/forms/application_wizard.py @@ -838,8 +838,8 @@ class AnythingElseForm(RegistrarForm): class RequirementsForm(RegistrarForm): is_policy_acknowledged = forms.BooleanField( - label="I read and agree to the requirements for operating .gov domains.", + label="I read and agree to the requirements for operating a .gov domain.", error_messages={ - "required": ("Check the box if you read and agree to the requirements for operating .gov domains.") + "required": ("Check the box if you read and agree to the requirements for operating a .gov domain.") }, ) diff --git a/src/registrar/templates/application_form.html b/src/registrar/templates/application_form.html index c34ddf5bc..524045fbe 100644 --- a/src/registrar/templates/application_form.html +++ b/src/registrar/templates/application_form.html @@ -1,7 +1,7 @@ {% extends 'base.html' %} {% load static form_helpers url_helpers %} -{% block title %}Apply for a .gov domain | {{form_titles|get_item:steps.current}} | {% endblock %} +{% block title %}Request a .gov domain | {{form_titles|get_item:steps.current}} | {% endblock %} {% block content %} <div class="grid-container"> <div class="grid-row grid-gap"> diff --git a/src/registrar/templates/application_requirements.html b/src/registrar/templates/application_requirements.html index d16edf963..ef0bf00f2 100644 --- a/src/registrar/templates/application_requirements.html +++ b/src/registrar/templates/application_requirements.html @@ -2,7 +2,7 @@ {% load field_helpers %} {% block form_instructions %} - <p>Please read this page. Check the box at the bottom to show that you agree to the requirements for operating .gov domains.</p> + <p>Please read this page. Check the box at the bottom to show that you agree to the requirements for operating a .gov domain.</p> <p>The .gov domain space exists to support a broad diversity of government missions. Generally, we don’t review or audit how government organizations use their registered domains. However, misuse of a .gov domain can reflect upon the integrity of the entire .gov space. There are categories of misuse that are statutorily prohibited or abusive in nature.</p> diff --git a/src/registrar/templates/django/forms/label.html b/src/registrar/templates/django/forms/label.html index 18d24a7bd..545ccf781 100644 --- a/src/registrar/templates/django/forms/label.html +++ b/src/registrar/templates/django/forms/label.html @@ -10,7 +10,7 @@ {% if widget.attrs.required %} <!--Don't add asterisk to one-field forms --> - {% if field.label == "Is your organization an election office?" or field.label == "What .gov domain do you want?" or field.label == "I read and agree to the requirements for operating .gov domains." or field.label == "Please explain why there are no other employees from your organization we can contact to help us assess your eligibility for a .gov domain." %} + {% if field.label == "Is your organization an election office?" or field.label == "What .gov domain do you want?" or field.label == "I read and agree to the requirements for operating a .gov domain." or field.label == "Please explain why there are no other employees from your organization we can contact to help us assess your eligibility for a .gov domain." %} {% else %} <abbr class="usa-hint usa-hint--required" title="required">*</abbr> {% endif %} diff --git a/src/registrar/tests/test_forms.py b/src/registrar/tests/test_forms.py index 9d553acc5..a8d84ba7b 100644 --- a/src/registrar/tests/test_forms.py +++ b/src/registrar/tests/test_forms.py @@ -338,7 +338,7 @@ class TestFormValidation(MockEppLib): form = RequirementsForm(data={}) self.assertEqual( form.errors["is_policy_acknowledged"], - ["Check the box if you read and agree to the requirements for operating .gov domains."], + ["Check the box if you read and agree to the requirements for operating a .gov domain."], ) def test_requirements_form_unchecked(self): @@ -346,7 +346,7 @@ class TestFormValidation(MockEppLib): form = RequirementsForm(data={"is_policy_acknowledged": False}) self.assertEqual( form.errors["is_policy_acknowledged"], - ["Check the box if you read and agree to the requirements for operating .gov domains."], + ["Check the box if you read and agree to the requirements for operating a .gov domain."], ) def test_tribal_government_unrecognized(self): diff --git a/src/registrar/views/application.py b/src/registrar/views/application.py index 031b93dee..a15f36ccc 100644 --- a/src/registrar/views/application.py +++ b/src/registrar/views/application.py @@ -92,7 +92,7 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView): Step.YOUR_CONTACT: _("Your contact information"), Step.OTHER_CONTACTS: _("Other employees from your organization"), Step.ANYTHING_ELSE: _("Anything else?"), - Step.REQUIREMENTS: _("Requirements for operating .gov domains"), + Step.REQUIREMENTS: _("Requirements for operating a .gov domain"), Step.REVIEW: _("Review and submit your domain request"), } From 52fff3b82c65dd5d7863fcf9bc6ef11eb2bce82f Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 23 Jan 2024 14:58:43 -0700 Subject: [PATCH 112/120] Add verbose names --- src/registrar/models/domain_application.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/registrar/models/domain_application.py b/src/registrar/models/domain_application.py index 196449bfa..30def9cfc 100644 --- a/src/registrar/models/domain_application.py +++ b/src/registrar/models/domain_application.py @@ -431,11 +431,13 @@ class DomainApplication(TimeStampedModel): null=True, blank=True, help_text="Street address", + verbose_name="Address line 1", ) address_line2 = models.TextField( null=True, blank=True, help_text="Street address line 2 (optional)", + verbose_name="Address line 2", ) city = models.TextField( null=True, From 5d97893b92eda69cb618a87328ab05c3cb26ff71 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 23 Jan 2024 15:01:19 -0700 Subject: [PATCH 113/120] Add migration --- ...omainapplication_address_line1_and_more.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/registrar/migrations/0063_alter_domainapplication_address_line1_and_more.py diff --git a/src/registrar/migrations/0063_alter_domainapplication_address_line1_and_more.py b/src/registrar/migrations/0063_alter_domainapplication_address_line1_and_more.py new file mode 100644 index 000000000..99ffac7aa --- /dev/null +++ b/src/registrar/migrations/0063_alter_domainapplication_address_line1_and_more.py @@ -0,0 +1,24 @@ +# Generated by Django 4.2.7 on 2024-01-23 22:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0062_alter_host_name"), + ] + + operations = [ + migrations.AlterField( + model_name="domainapplication", + name="address_line1", + field=models.TextField(blank=True, help_text="Street address", null=True, verbose_name="Address line 1"), + ), + migrations.AlterField( + model_name="domainapplication", + name="address_line2", + field=models.TextField( + blank=True, help_text="Street address line 2 (optional)", null=True, verbose_name="Address line 2" + ), + ), + ] From 3f8e5ce204516eb54fda3b96e7ed6c4631808ebb Mon Sep 17 00:00:00 2001 From: Rachid Mrad <rachid.mrad@ecstech.com> Date: Tue, 23 Jan 2024 18:15:56 -0500 Subject: [PATCH 114/120] Revisions on model --- src/djangooidc/views.py | 4 ++-- src/registrar/admin.py | 8 ++++---- src/registrar/migrations/0063_veryimportantperson.py | 8 ++++---- src/registrar/models/very_important_person.py | 12 ++++-------- src/registrar/tests/test_admin.py | 4 ++-- 5 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/djangooidc/views.py b/src/djangooidc/views.py index b786ed2e9..2fc2a0363 100644 --- a/src/djangooidc/views.py +++ b/src/djangooidc/views.py @@ -82,12 +82,12 @@ def login_callback(request): if user: login(request, user) logger.info("Successfully logged in user %s" % user) - # Double login bug? + # Double login bug (1507)? return redirect(request.session.get("next", "/")) else: raise o_e.BannedUser() except o_e.NoStateDefined as nsd_err: - logger.debug(f"No State Defined: {nsd_err}") + logger.warning(f"No State Defined: {nsd_err}") return redirect(request.session.get("next", "/")) except Exception as err: return error_page(request, err) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 125ff2e8a..eee808691 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -1240,14 +1240,14 @@ class DraftDomainAdmin(ListHeaderAdmin): class VeryImportantPersonAdmin(ListHeaderAdmin): - list_display = ("email", "user", "truncated_notes", "created_at") + list_display = ("email", "requestor", "truncated_notes", "created_at") search_fields = ["email"] search_help_text = "Search by email." list_filter = [ - "user", + "requestor", ] readonly_fields = [ - "user", + "requestor", ] def truncated_notes(self, obj): @@ -1258,7 +1258,7 @@ class VeryImportantPersonAdmin(ListHeaderAdmin): def save_model(self, request, obj, form, change): # Set the user field to the current admin user - obj.user = request.user if request.user.is_authenticated else None + obj.requestor = request.requestor if request.requestor.is_authenticated else None super().save_model(request, obj, form, change) diff --git a/src/registrar/migrations/0063_veryimportantperson.py b/src/registrar/migrations/0063_veryimportantperson.py index 3d56ae1ac..29ea7ffdb 100644 --- a/src/registrar/migrations/0063_veryimportantperson.py +++ b/src/registrar/migrations/0063_veryimportantperson.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.7 on 2024-01-19 00:18 +# Generated by Django 4.2.7 on 2024-01-23 21:02 from django.conf import settings from django.db import migrations, models @@ -17,10 +17,10 @@ class Migration(migrations.Migration): ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), ("created_at", models.DateTimeField(auto_now_add=True)), ("updated_at", models.DateTimeField(auto_now=True)), - ("email", models.EmailField(blank=True, db_index=True, help_text="Email", max_length=254, null=True)), - ("notes", models.TextField(blank=True, help_text="Notes", null=True)), + ("email", models.EmailField(blank=True, db_index=True, help_text="Email", max_length=254)), + ("notes", models.TextField(blank=True, help_text="Notes")), ( - "user", + "requestor", models.ForeignKey( blank=True, null=True, diff --git a/src/registrar/models/very_important_person.py b/src/registrar/models/very_important_person.py index 42621c64a..2fb53ff27 100644 --- a/src/registrar/models/very_important_person.py +++ b/src/registrar/models/very_important_person.py @@ -8,13 +8,13 @@ class VeryImportantPerson(TimeStampedModel): """""" email = models.EmailField( - null=True, + null=False, blank=True, help_text="Email", db_index=True, ) - user = models.ForeignKey( + requestor = models.ForeignKey( "registrar.User", null=True, blank=True, @@ -23,14 +23,10 @@ class VeryImportantPerson(TimeStampedModel): ) notes = models.TextField( - null=True, + null=False, blank=True, help_text="Notes", ) def __str__(self): - try: - if self.email: - return self.email - except Exception: - return "" + return self.email diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index 175bef11f..139e4bc84 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -1755,10 +1755,10 @@ class VeryImportantPersonAdminTestCase(TestCase): # Create a request object request = self.factory.post("/admin/yourapp/veryimportantperson/add/") - request.user = self.superuser + request.requestor = self.superuser # Call the save_model method admin_instance.save_model(request, vip_instance, None, None) # Check that the user field is set to the request.user - self.assertEqual(vip_instance.user, self.superuser) + self.assertEqual(vip_instance.requestor, self.superuser) From 52b76a79981a63b03721664c699bf57c2b6d0e5e Mon Sep 17 00:00:00 2001 From: Rachid Mrad <rachid.mrad@ecstech.com> Date: Tue, 23 Jan 2024 18:40:21 -0500 Subject: [PATCH 115/120] fix error in admin.py --- src/registrar/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index eee808691..dedc6f527 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -1258,7 +1258,7 @@ class VeryImportantPersonAdmin(ListHeaderAdmin): def save_model(self, request, obj, form, change): # Set the user field to the current admin user - obj.requestor = request.requestor if request.requestor.is_authenticated else None + obj.requestor = request.user if request.user.is_authenticated else None super().save_model(request, obj, form, change) From 784b187b09add07857c1975049560be1e520e44f Mon Sep 17 00:00:00 2001 From: Rachid Mrad <rachid.mrad@ecstech.com> Date: Tue, 23 Jan 2024 18:48:51 -0500 Subject: [PATCH 116/120] fix failing test by force logging in user --- src/registrar/tests/test_admin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index 139e4bc84..e0d0a6be5 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -1747,6 +1747,8 @@ class VeryImportantPersonAdminTestCase(TestCase): self.factory = RequestFactory() def test_save_model_sets_user_field(self): + self.client.force_login(self.superuser) + # Create an instance of the admin class admin_instance = VeryImportantPersonAdmin(model=VeryImportantPerson, admin_site=None) From 8570a03600dc8b00ff271e527514376b1ec48370 Mon Sep 17 00:00:00 2001 From: Rachid Mrad <rachid.mrad@ecstech.com> Date: Tue, 23 Jan 2024 18:58:26 -0500 Subject: [PATCH 117/120] Make email and notes required, fix unit test --- src/registrar/migrations/0063_veryimportantperson.py | 6 +++--- src/registrar/models/very_important_person.py | 4 ++-- src/registrar/tests/test_admin.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/registrar/migrations/0063_veryimportantperson.py b/src/registrar/migrations/0063_veryimportantperson.py index 29ea7ffdb..38cfe328e 100644 --- a/src/registrar/migrations/0063_veryimportantperson.py +++ b/src/registrar/migrations/0063_veryimportantperson.py @@ -1,4 +1,4 @@ -# Generated by Django 4.2.7 on 2024-01-23 21:02 +# Generated by Django 4.2.7 on 2024-01-23 23:51 from django.conf import settings from django.db import migrations, models @@ -17,8 +17,8 @@ class Migration(migrations.Migration): ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), ("created_at", models.DateTimeField(auto_now_add=True)), ("updated_at", models.DateTimeField(auto_now=True)), - ("email", models.EmailField(blank=True, db_index=True, help_text="Email", max_length=254)), - ("notes", models.TextField(blank=True, help_text="Notes")), + ("email", models.EmailField(db_index=True, help_text="Email", max_length=254)), + ("notes", models.TextField(help_text="Notes")), ( "requestor", models.ForeignKey( diff --git a/src/registrar/models/very_important_person.py b/src/registrar/models/very_important_person.py index 2fb53ff27..3d6430251 100644 --- a/src/registrar/models/very_important_person.py +++ b/src/registrar/models/very_important_person.py @@ -9,7 +9,7 @@ class VeryImportantPerson(TimeStampedModel): email = models.EmailField( null=False, - blank=True, + blank=False, help_text="Email", db_index=True, ) @@ -24,7 +24,7 @@ class VeryImportantPerson(TimeStampedModel): notes = models.TextField( null=False, - blank=True, + blank=False, help_text="Notes", ) diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index e0d0a6be5..7c9aa8fe4 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -1748,7 +1748,7 @@ class VeryImportantPersonAdminTestCase(TestCase): def test_save_model_sets_user_field(self): self.client.force_login(self.superuser) - + # Create an instance of the admin class admin_instance = VeryImportantPersonAdmin(model=VeryImportantPerson, admin_site=None) @@ -1757,7 +1757,7 @@ class VeryImportantPersonAdminTestCase(TestCase): # Create a request object request = self.factory.post("/admin/yourapp/veryimportantperson/add/") - request.requestor = self.superuser + request.user = self.superuser # Call the save_model method admin_instance.save_model(request, vip_instance, None, None) From e88c50c173a9613fa9cea2ceaf5138d48617e9fb Mon Sep 17 00:00:00 2001 From: Rachid Mrad <rachid.mrad@ecstech.com> Date: Wed, 24 Jan 2024 13:11:00 -0500 Subject: [PATCH 118/120] add class description for vip --- src/registrar/admin.py | 2 +- src/registrar/models/very_important_person.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index dedc6f527..d64296e56 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -1251,7 +1251,7 @@ class VeryImportantPersonAdmin(ListHeaderAdmin): ] def truncated_notes(self, obj): - # Truncate the 'notes' field to 200 characters + # Truncate the 'notes' field to 50 characters return str(obj.notes)[:50] truncated_notes.short_description = "Notes (Truncated)" # type: ignore diff --git a/src/registrar/models/very_important_person.py b/src/registrar/models/very_important_person.py index 3d6430251..9134cb893 100644 --- a/src/registrar/models/very_important_person.py +++ b/src/registrar/models/very_important_person.py @@ -5,7 +5,7 @@ from .utility.time_stamped_model import TimeStampedModel class VeryImportantPerson(TimeStampedModel): - """""" + """emails that get added to this table will bypass ial2 on login.""" email = models.EmailField( null=False, From e9160e67f8d1e510a1c20db6d373ffb829666a9b Mon Sep 17 00:00:00 2001 From: Rachid Mrad <rachid.mrad@ecstech.com> Date: Wed, 24 Jan 2024 13:23:56 -0500 Subject: [PATCH 119/120] Change requester to requestor in code --- .../templates/emails/domain_invitation.txt | 2 +- src/registrar/tests/test_views.py | 12 ++++----- src/registrar/views/domain.py | 26 +++++++++---------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/registrar/templates/emails/domain_invitation.txt b/src/registrar/templates/emails/domain_invitation.txt index b9e4ba853..8896bd85f 100644 --- a/src/registrar/templates/emails/domain_invitation.txt +++ b/src/registrar/templates/emails/domain_invitation.txt @@ -1,7 +1,7 @@ {% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #} Hi. -{{ requester_email }} has added you as a manager on {{ domain.name }}. +{{ requestor_email }} has added you as a manager on {{ domain.name }}. You can manage this domain on the .gov registrar <https://manage.get.gov>. diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index 60ffab416..a6cc3f0f8 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -2590,7 +2590,7 @@ class TestDomainManagers(TestDomainOverview): ) @boto3_mocking.patching - def test_domain_invitation_email_has_email_as_requester_non_existent(self): + def test_domain_invitation_email_has_email_as_requestor_non_existent(self): """Inviting a non existent user sends them an email, with email as the name.""" # make sure there is no user with this email email_address = "mayor@igorville.gov" @@ -2623,13 +2623,13 @@ class TestDomainManagers(TestDomainOverview): email_content = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] self.assertIn("info@example.com", email_content) - # Check that the requesters first/last name do not exist + # Check that the requestors first/last name do not exist self.assertNotIn("First", email_content) self.assertNotIn("Last", email_content) self.assertNotIn("First Last", email_content) @boto3_mocking.patching - def test_domain_invitation_email_has_email_as_requester(self): + def test_domain_invitation_email_has_email_as_requestor(self): """Inviting a user sends them an email, with email as the name.""" # Create a fake user object email_address = "mayor@igorville.gov" @@ -2662,13 +2662,13 @@ class TestDomainManagers(TestDomainOverview): email_content = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] self.assertIn("info@example.com", email_content) - # Check that the requesters first/last name do not exist + # Check that the requestors first/last name do not exist self.assertNotIn("First", email_content) self.assertNotIn("Last", email_content) self.assertNotIn("First Last", email_content) @boto3_mocking.patching - def test_domain_invitation_email_has_email_as_requester_staff(self): + def test_domain_invitation_email_has_email_as_requestor_staff(self): """Inviting a user sends them an email, with email as the name.""" # Create a fake user object email_address = "mayor@igorville.gov" @@ -2705,7 +2705,7 @@ class TestDomainManagers(TestDomainOverview): email_content = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] self.assertIn("help@get.gov", email_content) - # Check that the requesters first/last name do not exist + # Check that the requestors first/last name do not exist self.assertNotIn("First", email_content) self.assertNotIn("Last", email_content) self.assertNotIn("First Last", email_content) diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 4d47a6f59..2d634dbf2 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -646,7 +646,7 @@ class DomainAddUserView(DomainFormBaseView): """Get an absolute URL for this domain.""" return self.request.build_absolute_uri(reverse("domain", kwargs={"pk": self.object.id})) - def _send_domain_invitation_email(self, email: str, requester: User, add_success=True): + def _send_domain_invitation_email(self, email: str, requestor: User, add_success=True): """Performs the sending of the domain invitation email, does not make a domain information object email: string- email to send to @@ -654,16 +654,16 @@ class DomainAddUserView(DomainFormBaseView): adding a success message to the view if the email sending succeeds""" # Set a default email address to send to for staff - requester_email = "help@get.gov" + requestor_email = "help@get.gov" - # Check if the email requester has a valid email address - if not requester.is_staff and requester.email is not None and requester.email.strip() != "": - requester_email = requester.email - elif not requester.is_staff: + # Check if the email requestor has a valid email address + if not requestor.is_staff and requestor.email is not None and requestor.email.strip() != "": + requestor_email = requestor.email + elif not requestor.is_staff: messages.error(self.request, "Can't send invitation email. No email is associated with your account.") logger.error( f"Can't send email to '{email}' on domain '{self.object}'." - f"No email exists for the requester '{requester.username}'.", + f"No email exists for the requestor '{requestor.username}'.", exc_info=True, ) return None @@ -676,7 +676,7 @@ class DomainAddUserView(DomainFormBaseView): context={ "domain_url": self._domain_abs_url(), "domain": self.object, - "requester_email": requester_email, + "requestor_email": requestor_email, }, ) except EmailSendingError: @@ -691,7 +691,7 @@ class DomainAddUserView(DomainFormBaseView): if add_success: messages.success(self.request, f"{email} has been invited to this domain.") - def _make_invitation(self, email_address: str, requester: User): + def _make_invitation(self, email_address: str, requestor: User): """Make a Domain invitation for this email and redirect with a message.""" invitation, created = DomainInvitation.objects.get_or_create(email=email_address, domain=self.object) if not created: @@ -701,22 +701,22 @@ class DomainAddUserView(DomainFormBaseView): f"{email_address} has already been invited to this domain.", ) else: - self._send_domain_invitation_email(email=email_address, requester=requester) + self._send_domain_invitation_email(email=email_address, requestor=requestor) return redirect(self.get_success_url()) def form_valid(self, form): """Add the specified user on this domain.""" requested_email = form.cleaned_data["email"] - requester = self.request.user + requestor = self.request.user # look up a user with that email try: requested_user = User.objects.get(email=requested_email) except User.DoesNotExist: # no matching user, go make an invitation - return self._make_invitation(requested_email, requester) + return self._make_invitation(requested_email, requestor) else: # if user already exists then just send an email - self._send_domain_invitation_email(requested_email, requester, add_success=False) + self._send_domain_invitation_email(requested_email, requestor, add_success=False) try: UserDomainRole.objects.create( From 31dc26476c007a5553565181ee0698ba5270fa75 Mon Sep 17 00:00:00 2001 From: David Kennedy <david.kennedy@associates.cisa.dhs.gov> Date: Thu, 25 Jan 2024 14:50:22 -0500 Subject: [PATCH 120/120] migration fix --- ...y => 0064_alter_domainapplication_address_line1_and_more.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/registrar/migrations/{0063_alter_domainapplication_address_line1_and_more.py => 0064_alter_domainapplication_address_line1_and_more.py} (93%) diff --git a/src/registrar/migrations/0063_alter_domainapplication_address_line1_and_more.py b/src/registrar/migrations/0064_alter_domainapplication_address_line1_and_more.py similarity index 93% rename from src/registrar/migrations/0063_alter_domainapplication_address_line1_and_more.py rename to src/registrar/migrations/0064_alter_domainapplication_address_line1_and_more.py index 99ffac7aa..7241c7164 100644 --- a/src/registrar/migrations/0063_alter_domainapplication_address_line1_and_more.py +++ b/src/registrar/migrations/0064_alter_domainapplication_address_line1_and_more.py @@ -5,7 +5,7 @@ from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ - ("registrar", "0062_alter_host_name"), + ("registrar", "0063_veryimportantperson"), ] operations = [