From cf946004c0d11c1637fad2d68f5a042f734b5eda Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Wed, 3 Apr 2024 06:49:29 -0400 Subject: [PATCH 01/80] added user request details below creator in django admin --- src/registrar/models/user.py | 18 ++++++++++++++++++ .../admin/includes/detail_table_fieldset.html | 4 ++++ .../admin/includes/user_detail_list.html | 12 ++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 src/registrar/templates/django/admin/includes/user_detail_list.html diff --git a/src/registrar/models/user.py b/src/registrar/models/user.py index bf904a044..5085c0b94 100644 --- a/src/registrar/models/user.py +++ b/src/registrar/models/user.py @@ -67,6 +67,24 @@ class User(AbstractUser): def is_restricted(self): return self.status == self.RESTRICTED + def get_approved_domains_count(self): + """Return count of approved domains""" + allowed_states = ['unknown', 'dns needed', 'ready', 'on hold'] + approved_domains_count = self.domains.filter(state__in=allowed_states).count() + return approved_domains_count + + def get_active_requests_count(self): + """Return count of active requests""" + allowed_states = ['submitted', 'in review', 'action needed'] + active_requests_count = self.domain_requests_created.filter(status__in=allowed_states).count() + return active_requests_count + + def get_rejected_requests_count(self): + """Return count of rejected or ineligible requests""" + allowed_states = ['rejected', 'ineligible'] + rejected_requests_count = self.domain_requests_created.filter(status__in=allowed_states).count() + return rejected_requests_count + @classmethod def needs_identity_verification(cls, email, uuid): """A method used by our oidc classes to test whether a user needs email/uuid verification diff --git a/src/registrar/templates/django/admin/includes/detail_table_fieldset.html b/src/registrar/templates/django/admin/includes/detail_table_fieldset.html index 47145faf2..2c3b76253 100644 --- a/src/registrar/templates/django/admin/includes/detail_table_fieldset.html +++ b/src/registrar/templates/django/admin/includes/detail_table_fieldset.html @@ -65,6 +65,10 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html) {% include "django/admin/includes/contact_detail_list.html" with user=original.creator no_title_top_padding=field.is_readonly %} +
+ + {% include "django/admin/includes/user_detail_list.html" with user=original.creator no_title_top_padding=field.is_readonly %} +
{% elif field.field.name == "submitter" %}
diff --git a/src/registrar/templates/django/admin/includes/user_detail_list.html b/src/registrar/templates/django/admin/includes/user_detail_list.html new file mode 100644 index 000000000..9daa8a0ee --- /dev/null +++ b/src/registrar/templates/django/admin/includes/user_detail_list.html @@ -0,0 +1,12 @@ +{% load i18n static %} + +
+ + {# Approved domains #} + Approved domains: {{ user.get_approved_domains_count }}
+ {# Active requests #} + Active requests: {{ user.get_active_requests_count }}
+ {# Rejected or ineligible requests #} + Rejected or ineligible: {{ user.get_rejected_requests_count }}
+ +
From 36a86533de363e08b7f4e621e43807fdbf125424 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 3 Apr 2024 09:18:42 -0600 Subject: [PATCH 02/80] Remove website and draftdomain --- src/registrar/models/user_group.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/registrar/models/user_group.py b/src/registrar/models/user_group.py index e8636a462..604004b19 100644 --- a/src/registrar/models/user_group.py +++ b/src/registrar/models/user_group.py @@ -36,11 +36,6 @@ class UserGroup(Group): "model": "domain", "permissions": ["view_domain"], }, - { - "app_label": "registrar", - "model": "draftdomain", - "permissions": ["change_draftdomain"], - }, { "app_label": "registrar", "model": "user", @@ -51,11 +46,6 @@ class UserGroup(Group): "model": "domaininvitation", "permissions": ["add_domaininvitation", "view_domaininvitation"], }, - { - "app_label": "registrar", - "model": "website", - "permissions": ["change_website"], - }, { "app_label": "registrar", "model": "userdomainrole", From a66e873edfcb9f4d011c4cf8a4a6c5834872dd3a Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 3 Apr 2024 09:24:58 -0600 Subject: [PATCH 03/80] Fixtures --- .../migrations/0081_create_groups_v10.py | 2 +- .../migrations/0082_create_groups_v11.py | 37 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 src/registrar/migrations/0082_create_groups_v11.py diff --git a/src/registrar/migrations/0081_create_groups_v10.py b/src/registrar/migrations/0081_create_groups_v10.py index d65b6dbd2..5d8e3dbda 100644 --- a/src/registrar/migrations/0081_create_groups_v10.py +++ b/src/registrar/migrations/0081_create_groups_v10.py @@ -25,7 +25,7 @@ def create_groups(apps, schema_editor) -> Any: class Migration(migrations.Migration): dependencies = [ - ("registrar", "0080_create_groups_v09"), + ("registrar", "0080_create_groups_v10"), ] operations = [ diff --git a/src/registrar/migrations/0082_create_groups_v11.py b/src/registrar/migrations/0082_create_groups_v11.py new file mode 100644 index 000000000..73f54fb2f --- /dev/null +++ b/src/registrar/migrations/0082_create_groups_v11.py @@ -0,0 +1,37 @@ +# This migration creates the create_full_access_group and create_cisa_analyst_group groups +# It is dependent on 0079 (which populates federal agencies) +# If permissions on the groups need changing, edit CISA_ANALYST_GROUP_PERMISSIONS +# in the user_group model then: +# [NOT RECOMMENDED] +# step 1: docker-compose exec app ./manage.py migrate --fake registrar 0035_contenttypes_permissions +# step 2: docker-compose exec app ./manage.py migrate registrar 0036_create_groups +# step 3: fake run the latest migration in the migrations list +# [RECOMMENDED] +# Alternatively: +# step 1: duplicate the migration that loads data +# step 2: docker-compose exec app ./manage.py migrate + +from django.db import migrations +from registrar.models import UserGroup +from typing import Any + + +# For linting: RunPython expects a function reference, +# so let's give it one +def create_groups(apps, schema_editor) -> Any: + UserGroup.create_cisa_analyst_group(apps, schema_editor) + UserGroup.create_full_access_group(apps, schema_editor) + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0081_create_groups_v09"), + ] + + operations = [ + migrations.RunPython( + create_groups, + reverse_code=migrations.RunPython.noop, + atomic=True, + ), + ] From c1ed009a055a3e5a3cc048be5bc25564a0e8dd34 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 3 Apr 2024 10:57:05 -0600 Subject: [PATCH 04/80] Add migrations and selective model view --- src/registrar/admin.py | 40 ++++++++++++++++++- .../migrations/0081_create_groups_v10.py | 2 +- .../migrations/0082_create_groups_v11.py | 2 +- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index e0c98b7c2..7ae7c1e27 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -765,6 +765,41 @@ class WebsiteAdmin(ListHeaderAdmin): "website", ] search_help_text = "Search by website." + + def get_model_perms(self, request): + """ + Return empty perms dict thus hiding the model from admin index. + """ + superuser_perm = request.user.has_perm("registrar.full_access_permission") + analyst_perm = request.user.has_perm("registrar.analyst_access_permission") + if analyst_perm and not superuser_perm: + return {} + return super().get_model_perms(request) + + def has_change_permission(self, request, obj=None): + """ + Allow analysts to access the change form directly via URL. + """ + superuser_perm = request.user.has_perm("registrar.full_access_permission") + analyst_perm = request.user.has_perm("registrar.analyst_access_permission") + if analyst_perm and not superuser_perm: + return True + return super().has_change_permission(request, obj) + + def response_change(self, request, obj): + """ + Override to redirect admins back to the same page after saving. + """ + superuser_perm = request.user.has_perm("registrar.full_access_permission") + analyst_perm = request.user.has_perm("registrar.analyst_access_permission") + + # Don't redirect to the website page on save if the user is an analyst. + # Rather, just redirect back to the same change page. + if analyst_perm and not superuser_perm: + opts = obj._meta + pk_value = obj._get_pk_val() + return HttpResponseRedirect(reverse('admin:%s_%s_change' % (opts.app_label, opts.model_name), args=(pk_value,))) + return super().response_change(request, obj) class UserDomainRoleAdmin(ListHeaderAdmin): @@ -1439,7 +1474,10 @@ class DomainInformationInline(admin.StackedInline): def has_change_permission(self, request, obj=None): """Custom has_change_permission override so that we can specify that analysts can edit this through this inline, but not through the model normally""" - if request.user.has_perm("registrar.analyst_access_permission"): + + superuser_perm = request.user.has_perm("registrar.full_access_permission") + analyst_perm = request.user.has_perm("registrar.analyst_access_permission") + if analyst_perm and not superuser_perm: return True return super().has_change_permission(request, obj) diff --git a/src/registrar/migrations/0081_create_groups_v10.py b/src/registrar/migrations/0081_create_groups_v10.py index 5d8e3dbda..d65b6dbd2 100644 --- a/src/registrar/migrations/0081_create_groups_v10.py +++ b/src/registrar/migrations/0081_create_groups_v10.py @@ -25,7 +25,7 @@ def create_groups(apps, schema_editor) -> Any: class Migration(migrations.Migration): dependencies = [ - ("registrar", "0080_create_groups_v10"), + ("registrar", "0080_create_groups_v09"), ] operations = [ diff --git a/src/registrar/migrations/0082_create_groups_v11.py b/src/registrar/migrations/0082_create_groups_v11.py index 73f54fb2f..8bd0102cd 100644 --- a/src/registrar/migrations/0082_create_groups_v11.py +++ b/src/registrar/migrations/0082_create_groups_v11.py @@ -25,7 +25,7 @@ def create_groups(apps, schema_editor) -> Any: class Migration(migrations.Migration): dependencies = [ - ("registrar", "0081_create_groups_v09"), + ("registrar", "0081_create_groups_v10"), ] operations = [ From 269bb0cab3803cff73361a777d559dd39b203ab6 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 3 Apr 2024 12:19:40 -0600 Subject: [PATCH 05/80] Unit tests --- src/registrar/admin.py | 6 ++- src/registrar/tests/test_admin.py | 78 +++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 7ae7c1e27..73b3e7e2a 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -765,7 +765,7 @@ class WebsiteAdmin(ListHeaderAdmin): "website", ] search_help_text = "Search by website." - + def get_model_perms(self, request): """ Return empty perms dict thus hiding the model from admin index. @@ -798,7 +798,9 @@ class WebsiteAdmin(ListHeaderAdmin): if analyst_perm and not superuser_perm: opts = obj._meta pk_value = obj._get_pk_val() - return HttpResponseRedirect(reverse('admin:%s_%s_change' % (opts.app_label, opts.model_name), args=(pk_value,))) + return HttpResponseRedirect( + reverse("admin:%s_%s_change" % (opts.app_label, opts.model_name), args=(pk_value,)) + ) return super().response_change(request, obj) diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index 368f30721..81bf2736b 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -701,6 +701,84 @@ class TestDomainRequestAdmin(MockEppLib): ) self.mock_client = MockSESClient() + @less_console_noise_decorator + def test_analyst_can_see_alternative_domain(self): + """Tests if an analyst can still see the alternative domain field""" + + # Create fake creator + _creator = User.objects.create( + username="MrMeoward", + first_name="Meoward", + last_name="Jones", + ) + + # Create a fake domain request + _domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=_creator) + + fake_website = Website.objects.create(website="thisisatest.gov") + _domain_request.alternative_domains.add(fake_website) + _domain_request.save() + + p = "userpass" + self.client.login(username="staffuser", password=p) + response = self.client.get( + "/admin/registrar/domainrequest/{}/change/".format(_domain_request.pk), + follow=True, + ) + + # Make sure the page loaded, and that we're on the right page + self.assertEqual(response.status_code, 200) + self.assertContains(response, _domain_request.requested_domain.name) + + # Test if the page has the alternative domain + self.assertContains(response, "thisisatest.gov") + + # Check that the page contains the url we expect + expected_href = reverse("admin:registrar_website_change", args=[fake_website.id]) + self.assertContains(response, expected_href) + + # Navigate to the website to ensure that we can still edit it + response = self.client.get( + "/admin/registrar/website/{}/change/".format(fake_website.pk), + follow=True, + ) + + # Make sure the page loaded, and that we're on the right page + self.assertEqual(response.status_code, 200) + self.assertContains(response, "thisisatest.gov") + + @less_console_noise_decorator + def test_analyst_can_see_current_websites(self): + """Tests if an analyst can still see current website field""" + + # Create fake creator + _creator = User.objects.create( + username="MrMeoward", + first_name="Meoward", + last_name="Jones", + ) + + # Create a fake domain request + _domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=_creator) + + fake_website = Website.objects.create(website="thisisatest.gov") + _domain_request.current_websites.add(fake_website) + _domain_request.save() + + p = "userpass" + self.client.login(username="staffuser", password=p) + response = self.client.get( + "/admin/registrar/domainrequest/{}/change/".format(_domain_request.pk), + follow=True, + ) + + # Make sure the page loaded, and that we're on the right page + self.assertEqual(response.status_code, 200) + self.assertContains(response, _domain_request.requested_domain.name) + + # Test if the page has the current website + self.assertContains(response, "thisisatest.gov") + def test_domain_sortable(self): """Tests if the DomainRequest sorts by domain correctly""" with less_console_noise(): From 02257c4f7846499b51b58010e4e54265d4811ca5 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 3 Apr 2024 12:33:49 -0600 Subject: [PATCH 06/80] fix unit test --- src/registrar/tests/test_migrations.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/registrar/tests/test_migrations.py b/src/registrar/tests/test_migrations.py index add65105a..6d8ff7151 100644 --- a/src/registrar/tests/test_migrations.py +++ b/src/registrar/tests/test_migrations.py @@ -37,7 +37,6 @@ class TestGroups(TestCase): "add_domaininvitation", "view_domaininvitation", "change_domainrequest", - "change_draftdomain", "add_federalagency", "change_federalagency", "delete_federalagency", @@ -48,7 +47,6 @@ class TestGroups(TestCase): "add_verifiedbystaff", "change_verifiedbystaff", "delete_verifiedbystaff", - "change_website", ] # Get the codenames of actual permissions associated with the group From 2f7ca1de6541496abc79b766ee58e0f7abaeef5d Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 3 Apr 2024 12:40:06 -0600 Subject: [PATCH 07/80] Update src/registrar/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 73b3e7e2a..9514c569d 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -788,7 +788,7 @@ class WebsiteAdmin(ListHeaderAdmin): def response_change(self, request, obj): """ - Override to redirect admins back to the same page after saving. + Override to redirect users back to the same page after saving. """ superuser_perm = request.user.has_perm("registrar.full_access_permission") analyst_perm = request.user.has_perm("registrar.analyst_access_permission") From 7303f8055f879f5fa546d78e6be0ce19f820c587 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 3 Apr 2024 13:19:04 -0600 Subject: [PATCH 08/80] DraftDomain --- src/registrar/admin.py | 37 ++++++++++++++++++++ src/registrar/tests/test_admin.py | 57 +++++++++++++++++++++++++++++-- 2 files changed, 91 insertions(+), 3 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 9514c569d..e4990ed60 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -1909,6 +1909,43 @@ class DraftDomainAdmin(ListHeaderAdmin): # in autocomplete_fields for user ordering = ["name"] + def get_model_perms(self, request): + """ + Return empty perms dict thus hiding the model from admin index. + """ + superuser_perm = request.user.has_perm("registrar.full_access_permission") + analyst_perm = request.user.has_perm("registrar.analyst_access_permission") + if analyst_perm and not superuser_perm: + return {} + return super().get_model_perms(request) + + def has_change_permission(self, request, obj=None): + """ + Allow analysts to access the change form directly via URL. + """ + superuser_perm = request.user.has_perm("registrar.full_access_permission") + analyst_perm = request.user.has_perm("registrar.analyst_access_permission") + if analyst_perm and not superuser_perm: + return True + return super().has_change_permission(request, obj) + + def response_change(self, request, obj): + """ + Override to redirect users back to the same page after saving. + """ + superuser_perm = request.user.has_perm("registrar.full_access_permission") + analyst_perm = request.user.has_perm("registrar.analyst_access_permission") + + # Don't redirect to the website page on save if the user is an analyst. + # Rather, just redirect back to the same change page. + if analyst_perm and not superuser_perm: + opts = obj._meta + pk_value = obj._get_pk_val() + return HttpResponseRedirect( + reverse("admin:%s_%s_change" % (opts.app_label, opts.model_name), args=(pk_value,)) + ) + return super().response_change(request, obj) + class VerifiedByStaffAdmin(ListHeaderAdmin): list_display = ("email", "requestor", "truncated_notes", "created_at") diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index 81bf2736b..74f4ba60e 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -19,7 +19,16 @@ from registrar.admin import ( UserDomainRoleAdmin, VerifiedByStaffAdmin, ) -from registrar.models import Domain, DomainRequest, DomainInformation, User, DomainInvitation, Contact, Website +from registrar.models import ( + Domain, + DomainRequest, + DomainInformation, + User, + DomainInvitation, + Contact, + Website, + DraftDomain, +) from registrar.models.user_domain_role import UserDomainRole from registrar.models.verified_by_staff import VerifiedByStaff from .common import ( @@ -702,8 +711,8 @@ class TestDomainRequestAdmin(MockEppLib): self.mock_client = MockSESClient() @less_console_noise_decorator - def test_analyst_can_see_alternative_domain(self): - """Tests if an analyst can still see the alternative domain field""" + def test_analyst_can_see_and_edit_alternative_domain(self): + """Tests if an analyst can still see and edit the alternative domain field""" # Create fake creator _creator = User.objects.create( @@ -747,6 +756,48 @@ class TestDomainRequestAdmin(MockEppLib): self.assertEqual(response.status_code, 200) self.assertContains(response, "thisisatest.gov") + @less_console_noise_decorator + def test_analyst_can_see_and_edit_requested_domain(self): + """Tests if an analyst can still see and edit the requested domain field""" + + # Create fake creator + _creator = User.objects.create( + username="MrMeoward", + first_name="Meoward", + last_name="Jones", + ) + + # Create a fake domain request + _domain_request = completed_domain_request(status=DomainRequest.DomainRequestStatus.IN_REVIEW, user=_creator) + + p = "userpass" + self.client.login(username="staffuser", password=p) + response = self.client.get( + "/admin/registrar/domainrequest/{}/change/".format(_domain_request.pk), + follow=True, + ) + + # Filter to get the latest from the DB (rather than direct assignment) + requested_domain = DraftDomain.objects.filter(name=_domain_request.requested_domain.name).get() + + # Make sure the page loaded, and that we're on the right page + self.assertEqual(response.status_code, 200) + self.assertContains(response, requested_domain.name) + + # Check that the page contains the url we expect + expected_href = reverse("admin:registrar_draftdomain_change", args=[requested_domain.id]) + self.assertContains(response, expected_href) + + # Navigate to the website to ensure that we can still edit it + response = self.client.get( + "/admin/registrar/draftdomain/{}/change/".format(requested_domain.pk), + follow=True, + ) + + # Make sure the page loaded, and that we're on the right page + self.assertEqual(response.status_code, 200) + self.assertContains(response, "city.gov") + @less_console_noise_decorator def test_analyst_can_see_current_websites(self): """Tests if an analyst can still see current website field""" From 784fcbc26a8a146efa7aa7a0802d4ac1eb178b5e Mon Sep 17 00:00:00 2001 From: Rebecca Hsieh Date: Thu, 4 Apr 2024 15:35:15 -0700 Subject: [PATCH 09/80] Update expiration date code and unit test --- src/registrar/admin.py | 8 ++------ src/registrar/tests/test_admin.py | 12 +++--------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index e0c98b7c2..37535f1a2 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -1600,12 +1600,8 @@ class DomainAdmin(ListHeaderAdmin): # No expiration date was found. Return none. extra_context["extended_expiration_date"] = None return super().changeform_view(request, object_id, form_url, extra_context) - - if curr_exp_date < date.today(): - extra_context["extended_expiration_date"] = date.today() + relativedelta(years=years_to_extend_by) - else: - new_date = domain.registry_expiration_date + relativedelta(years=years_to_extend_by) - extra_context["extended_expiration_date"] = new_date + new_date = curr_exp_date + relativedelta(years=years_to_extend_by) + extra_context["extended_expiration_date"] = new_date else: extra_context["extended_expiration_date"] = None diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index 368f30721..4cb66c534 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -74,11 +74,10 @@ class TestDomainAdmin(MockEppLib, WebTest): ) super().setUp() - @skip("TODO for another ticket. This test case is grabbing old db data.") @patch("registrar.admin.DomainAdmin._get_current_date", return_value=date(2024, 1, 1)) def test_extend_expiration_date_button(self, mock_date_today): """ - Tests if extend_expiration_date button extends correctly + Tests if extend_expiration_date modal gives an accurate date """ # Create a ready domain with a preset expiration date @@ -105,17 +104,11 @@ class TestDomainAdmin(MockEppLib, WebTest): # Follow the response response = response.follow() - # refresh_from_db() does not work for objects with protected=True. - # https://github.com/viewflow/django-fsm/issues/89 - new_domain = Domain.objects.get(id=domain.id) - - # Check that the current expiration date is what we expect - self.assertEqual(new_domain.expiration_date, date(2025, 5, 25)) - # Assert that everything on the page looks correct self.assertEqual(response.status_code, 200) self.assertContains(response, domain.name) self.assertContains(response, "Extend expiration date") + self.assertContains(response, "New expiration date: May 25, 2025") # Ensure the message we recieve is in line with what we expect expected_message = "Successfully extended the expiration date." @@ -127,6 +120,7 @@ class TestDomainAdmin(MockEppLib, WebTest): extra_tags="", fail_silently=False, ) + mock_add_message.assert_has_calls([expected_call], 1) @less_console_noise_decorator From 8bea5fd0c474933596f687c1df7469c2a794d187 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Fri, 5 Apr 2024 14:31:16 -0400 Subject: [PATCH 10/80] wip --- src/registrar/models/user.py | 10 ++++--- .../admin/includes/user_detail_list.html | 28 +++++++++++++------ 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/registrar/models/user.py b/src/registrar/models/user.py index 5085c0b94..e2b3840a6 100644 --- a/src/registrar/models/user.py +++ b/src/registrar/models/user.py @@ -80,10 +80,12 @@ class User(AbstractUser): return active_requests_count def get_rejected_requests_count(self): - """Return count of rejected or ineligible requests""" - allowed_states = ['rejected', 'ineligible'] - rejected_requests_count = self.domain_requests_created.filter(status__in=allowed_states).count() - return rejected_requests_count + """Return count of rejected requests""" + return self.domain_requests_created.filter(status='rejected').count() + + def get_ineligible_requests_count(self): + """Return count of ineligible requests""" + return self.domain_requests_created.filter(status='ineligible').count() @classmethod def needs_identity_verification(cls, email, uuid): diff --git a/src/registrar/templates/django/admin/includes/user_detail_list.html b/src/registrar/templates/django/admin/includes/user_detail_list.html index 9daa8a0ee..522dfe3c4 100644 --- a/src/registrar/templates/django/admin/includes/user_detail_list.html +++ b/src/registrar/templates/django/admin/includes/user_detail_list.html @@ -1,12 +1,22 @@ {% load i18n static %} -
+{% with approved_domains_count=user.get_approved_domains_count %} + {% with active_requests_count=user.get_active_requests_count %} + {% with rejected_requests_count=user.get_rejected_requests_count %} + {% with ineligible_requests_count=user.get_ineligible_requests_count %} +
    - {# Approved domains #} - Approved domains: {{ user.get_approved_domains_count }}
    - {# Active requests #} - Active requests: {{ user.get_active_requests_count }}
    - {# Rejected or ineligible requests #} - Rejected or ineligible: {{ user.get_rejected_requests_count }}
    - -
+ {# Approved domains #} +
  • Approved domains: {{ approved_domains_count }}
  • + {# Active requests #} +
  • Active requests: {{ active_requests_count }}
  • + {# Rejected requests #} +
  • Rejected requests: {{ rejected_requests_count }}
  • + {# Ineligible requests #} +
  • Ineligible requests: {{ ineligible_requests_count }}
  • + + + {% endwith %} + {% endwith %} + {% endwith %} +{% endwith %} From 739e24ea3258fba7a985d6b509a7a4eda54d9dc8 Mon Sep 17 00:00:00 2001 From: Kristina Yin <140533113+kristinacyin@users.noreply.github.com> Date: Fri, 5 Apr 2024 12:36:27 -0700 Subject: [PATCH 11/80] Create design-onboarding --- .github/ISSUE_TEMPLATE/design-onboarding | 56 ++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/design-onboarding diff --git a/.github/ISSUE_TEMPLATE/design-onboarding b/.github/ISSUE_TEMPLATE/design-onboarding new file mode 100644 index 000000000..bf7047706 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/design-onboarding @@ -0,0 +1,56 @@ +--- +name: Design Onboarding +about: Onboarding steps for designers. +title: 'Designer Onboarding: GH_HANDLE' +labels: design, onboarding +assignees: katherineosos + +--- + +# Designer Onboarding + +- Onboardee: _GH handle of person being onboarded_ +- Onboarder: _GH handle of onboard buddy_ + +Read the .gov onboarding document +Get access to Slack. Familiar yourself with the channels. Post a hi message in the disc channel. +Get access to Google doc. Navigate through our documents. Find the design folders. +Get access to our project on Github. Look at our project directories. Find our product repos. Find our Product backlog. +Know who to ask for what -- know your team mates. +Make sure you are invited to all our team meetings. +Get an introduction on our application and the design process from Katherine, our Design lead. +Understand the designer tools (Miro, Fisma, etc). +Familiarize with our design guides (e.g., USWDS, 21st Century Integrated Digital Experience Act) +Make a request to the dev lead to get your sandbox created and get access to /admin (added to fixatures). +Look at get.gov. Test out manage.get.gov. +Create your POM and present in the Team coffee. +Schedule a meet-n-greet with Paul, our Product Manager to learn about our application. +Schedule a meet-n-greet with Vicky, our Scrum master to get overview of our Scrum process, our backlog, how our sprint works and how to use your Scrum master to your advantage. +Check up with your manager on your EOD clearance process. + +## Access + +### Steps for the onboardee +- [ ] Setup [commit signing in Github](#setting-up-commit-signing) and with git locally. +- [ ] [Create a cloud.gov account](https://cloud.gov/docs/getting-started/accounts/) +- [ ] Email github@cisa.dhs.gov (cc: Cameron) to add you to the [CISA Github organization](https://github.com/getgov) and [.gov Team](https://github.com/orgs/cisagov/teams/gov). +- [ ] Ensure you can login to your cloud.gov account via the CLI +```bash +cf login -a api.fr.cloud.gov --sso +``` +- [ ] Have an admin add you to cloud.gov org and set up your [sandbox developer space](#setting-up-developer-sandbox). Ensure you can deploy to your sandbox space. +- [ ] Have an admin add you to our login.gov sandbox team (`.gov Registrar`) via the [dashboard](https://dashboard.int.identitysandbox.gov/). + + **Note:** As mentioned in the [Login documentation](https://developers.login.gov/testing/), the sandbox Login account is different account from your regular, production Login account. If you have not created a Login account for the sandbox before, you will need to create a new account first. + +- [ ] Optional- add yourself as a codeowner if desired. See the [Developer readme](https://github.com/cisagov/getgov/blob/main/docs/developer/README.md) for how to do this and what it does. + +### Steps for the onboarder +- [ ] Add the onboardee to cloud.gov org (cisa-dotgov) +- [ ] Setup a [developer specific space for the new developer](#setting-up-developer-sandbox) +- [ ] Add the onboardee to our login.gov sandbox team (`.gov Registrar`) via the [dashboard](https://dashboard.int.identitysandbox.gov/) + + +## Documents to Review + +- [ ] [Team Onboarding](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit?usp=sharing) From 52bfcfa64defc0456d097214dee7ad1829800777 Mon Sep 17 00:00:00 2001 From: Kristina Yin <140533113+kristinacyin@users.noreply.github.com> Date: Fri, 5 Apr 2024 12:50:51 -0700 Subject: [PATCH 12/80] Update design-onboarding organizing steps --- .github/ISSUE_TEMPLATE/design-onboarding | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/design-onboarding b/.github/ISSUE_TEMPLATE/design-onboarding index bf7047706..01f1f8e83 100644 --- a/.github/ISSUE_TEMPLATE/design-onboarding +++ b/.github/ISSUE_TEMPLATE/design-onboarding @@ -29,25 +29,18 @@ Schedule a meet-n-greet with Vicky, our Scrum master to get overview of our Scru Check up with your manager on your EOD clearance process. ## Access +Make sure you have access / been added to the following: +- [] The [.gov team](https://github.com/orgs/cisagov/teams/gov) under cisagov on GitHub +- [] [Slack](dhscisa.enterprise.slack.com), and added to the necessary channels +- [] Google Drive Project folder +- [] Figma +- [] Team meetings ### Steps for the onboardee -- [ ] Setup [commit signing in Github](#setting-up-commit-signing) and with git locally. -- [ ] [Create a cloud.gov account](https://cloud.gov/docs/getting-started/accounts/) -- [ ] Email github@cisa.dhs.gov (cc: Cameron) to add you to the [CISA Github organization](https://github.com/getgov) and [.gov Team](https://github.com/orgs/cisagov/teams/gov). -- [ ] Ensure you can login to your cloud.gov account via the CLI -```bash -cf login -a api.fr.cloud.gov --sso -``` -- [ ] Have an admin add you to cloud.gov org and set up your [sandbox developer space](#setting-up-developer-sandbox). Ensure you can deploy to your sandbox space. -- [ ] Have an admin add you to our login.gov sandbox team (`.gov Registrar`) via the [dashboard](https://dashboard.int.identitysandbox.gov/). - - **Note:** As mentioned in the [Login documentation](https://developers.login.gov/testing/), the sandbox Login account is different account from your regular, production Login account. If you have not created a Login account for the sandbox before, you will need to create a new account first. - -- [ ] Optional- add yourself as a codeowner if desired. See the [Developer readme](https://github.com/cisagov/getgov/blob/main/docs/developer/README.md) for how to do this and what it does. +- [ ] ### Steps for the onboarder -- [ ] Add the onboardee to cloud.gov org (cisa-dotgov) -- [ ] Setup a [developer specific space for the new developer](#setting-up-developer-sandbox) +- [ ] Setup a [sandbox] for the onboardee (#setting-up-developer-sandbox) - [ ] Add the onboardee to our login.gov sandbox team (`.gov Registrar`) via the [dashboard](https://dashboard.int.identitysandbox.gov/) From 6548d3870f432d0e588937e0d098363984b79b9b Mon Sep 17 00:00:00 2001 From: Kristina Yin <140533113+kristinacyin@users.noreply.github.com> Date: Fri, 5 Apr 2024 12:52:09 -0700 Subject: [PATCH 13/80] Rename design-onboarding to design-onboarding.md Forgot to make it an md file --- .../ISSUE_TEMPLATE/{design-onboarding => design-onboarding.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/ISSUE_TEMPLATE/{design-onboarding => design-onboarding.md} (100%) diff --git a/.github/ISSUE_TEMPLATE/design-onboarding b/.github/ISSUE_TEMPLATE/design-onboarding.md similarity index 100% rename from .github/ISSUE_TEMPLATE/design-onboarding rename to .github/ISSUE_TEMPLATE/design-onboarding.md From db65cdba9b794359d6e0073d2463d31612d14b4a Mon Sep 17 00:00:00 2001 From: Kristina Yin <140533113+kristinacyin@users.noreply.github.com> Date: Fri, 5 Apr 2024 12:53:12 -0700 Subject: [PATCH 14/80] Update design-onboarding.md --- .github/ISSUE_TEMPLATE/design-onboarding.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/design-onboarding.md b/.github/ISSUE_TEMPLATE/design-onboarding.md index 01f1f8e83..3cca6e96c 100644 --- a/.github/ISSUE_TEMPLATE/design-onboarding.md +++ b/.github/ISSUE_TEMPLATE/design-onboarding.md @@ -30,11 +30,11 @@ Check up with your manager on your EOD clearance process. ## Access Make sure you have access / been added to the following: -- [] The [.gov team](https://github.com/orgs/cisagov/teams/gov) under cisagov on GitHub -- [] [Slack](dhscisa.enterprise.slack.com), and added to the necessary channels -- [] Google Drive Project folder -- [] Figma -- [] Team meetings +- [ ] The [.gov team](https://github.com/orgs/cisagov/teams/gov) under cisagov on GitHub +- [ ] [Slack](dhscisa.enterprise.slack.com), and added to the necessary channels +- [ ] Google Drive Project folder +- [ ] Figma +- [ ] Team meetings ### Steps for the onboardee - [ ] From 7e70bb848783a530fc2af8c4f1031c13247e5456 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Mon, 8 Apr 2024 11:00:49 -0400 Subject: [PATCH 15/80] conditional display of user detail counts --- .../admin/includes/user_detail_list.html | 32 ++++++++++++------- 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/registrar/templates/django/admin/includes/user_detail_list.html b/src/registrar/templates/django/admin/includes/user_detail_list.html index 522dfe3c4..be233af8e 100644 --- a/src/registrar/templates/django/admin/includes/user_detail_list.html +++ b/src/registrar/templates/django/admin/includes/user_detail_list.html @@ -4,18 +4,26 @@ {% with active_requests_count=user.get_active_requests_count %} {% with rejected_requests_count=user.get_rejected_requests_count %} {% with ineligible_requests_count=user.get_ineligible_requests_count %} -
      - - {# Approved domains #} -
    • Approved domains: {{ approved_domains_count }}
    • - {# Active requests #} -
    • Active requests: {{ active_requests_count }}
    • - {# Rejected requests #} -
    • Rejected requests: {{ rejected_requests_count }}
    • - {# Ineligible requests #} -
    • Ineligible requests: {{ ineligible_requests_count }}
    • - -
    + {% if approved_domains_count|add:active_requests_count|add:rejected_requests_count|add:ineligible_requests_count > 0 %} +
      + {% if approved_domains_count > 0 %} + {# Approved domains #} +
    • Approved domains: {{ approved_domains_count }}
    • + {% endif %} + {% if active_requests_count > 0 %} + {# Active requests #} +
    • Active requests: {{ active_requests_count }}
    • + {% endif %} + {% if rejected_requests_count > 0 %} + {# Rejected requests #} +
    • Rejected requests: {{ rejected_requests_count }}
    • + {% endif %} + {% if ineligible_requests_count > 0 %} + {# Ineligible requests #} +
    • Ineligible requests: {{ ineligible_requests_count }}
    • + {% endif %} +
    + {% endif %} {% endwith %} {% endwith %} {% endwith %} From 2c1844c7381b8a04e690bf8bb00929c46c15ab6f Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Mon, 8 Apr 2024 11:04:48 -0400 Subject: [PATCH 16/80] formatting changes --- src/registrar/models/user.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/registrar/models/user.py b/src/registrar/models/user.py index e2b3840a6..830cac944 100644 --- a/src/registrar/models/user.py +++ b/src/registrar/models/user.py @@ -69,24 +69,24 @@ class User(AbstractUser): def get_approved_domains_count(self): """Return count of approved domains""" - allowed_states = ['unknown', 'dns needed', 'ready', 'on hold'] + allowed_states = ["unknown", "dns needed", "ready", "on hold"] approved_domains_count = self.domains.filter(state__in=allowed_states).count() return approved_domains_count - + def get_active_requests_count(self): """Return count of active requests""" - allowed_states = ['submitted', 'in review', 'action needed'] + allowed_states = ["submitted", "in review", "action needed"] active_requests_count = self.domain_requests_created.filter(status__in=allowed_states).count() return active_requests_count - + def get_rejected_requests_count(self): """Return count of rejected requests""" - return self.domain_requests_created.filter(status='rejected').count() + return self.domain_requests_created.filter(status="rejected").count() def get_ineligible_requests_count(self): """Return count of ineligible requests""" - return self.domain_requests_created.filter(status='ineligible').count() - + return self.domain_requests_created.filter(status="ineligible").count() + @classmethod def needs_identity_verification(cls, email, uuid): """A method used by our oidc classes to test whether a user needs email/uuid verification From d145389d9561baded4c6bab93763725356e2830f Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Mon, 8 Apr 2024 11:51:00 -0400 Subject: [PATCH 17/80] test cases written --- src/registrar/tests/test_models.py | 77 ++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/src/registrar/tests/test_models.py b/src/registrar/tests/test_models.py index c7fe5f94c..3c8d8b05b 100644 --- a/src/registrar/tests/test_models.py +++ b/src/registrar/tests/test_models.py @@ -1004,6 +1004,8 @@ class TestUser(TestCase): Domain.objects.all().delete() DomainInvitation.objects.all().delete() DomainInformation.objects.all().delete() + DomainRequest.objects.all().delete() + DraftDomain.objects.all().delete() TransitionDomain.objects.all().delete() User.objects.all().delete() UserDomainRole.objects.all().delete() @@ -1060,6 +1062,81 @@ class TestUser(TestCase): # Domain Invitation, then save routine should be called exactly once save_mock.assert_called_once() + def test_approved_domains_count(self): + """Test that the correct approved domain count is returned for a user""" + # with no associated approved domains, expect this to return 0 + self.assertEquals(self.user.get_approved_domains_count(), 0) + # with one approved domain, expect this to return 1 + UserDomainRole.objects.get_or_create(user=self.user, domain=self.domain, role=UserDomainRole.Roles.MANAGER) + self.assertEquals(self.user.get_approved_domains_count(), 1) + # with one approved domain, expect this to return 1 (domain2 is deleted, so not considered approved) + domain2, _ = Domain.objects.get_or_create(name="igorville2.gov", state=Domain.State.DELETED) + UserDomainRole.objects.get_or_create(user=self.user, domain=domain2, role=UserDomainRole.Roles.MANAGER) + self.assertEquals(self.user.get_approved_domains_count(), 1) + # with two approved domains, expect this to return 2 + domain3, _ = Domain.objects.get_or_create(name="igorville3.gov", state=Domain.State.DNS_NEEDED) + UserDomainRole.objects.get_or_create(user=self.user, domain=domain3, role=UserDomainRole.Roles.MANAGER) + self.assertEquals(self.user.get_approved_domains_count(), 2) + # with three approved domains, expect this to return 3 + domain4, _ = Domain.objects.get_or_create(name="igorville4.gov", state=Domain.State.ON_HOLD) + UserDomainRole.objects.get_or_create(user=self.user, domain=domain4, role=UserDomainRole.Roles.MANAGER) + self.assertEquals(self.user.get_approved_domains_count(), 3) + # with four approved domains, expect this to return 4 + domain5, _ = Domain.objects.get_or_create(name="igorville5.gov", state=Domain.State.READY) + UserDomainRole.objects.get_or_create(user=self.user, domain=domain5, role=UserDomainRole.Roles.MANAGER) + self.assertEquals(self.user.get_approved_domains_count(), 4) + + def test_active_requests_count(self): + """Test that the correct active domain requests count is returned for a user""" + # with no associated active requests, expect this to return 0 + self.assertEquals(self.user.get_active_requests_count(), 0) + # with one active request, expect this to return 1 + draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville1.gov") + DomainRequest.objects.create( + creator=self.user, requested_domain=draft_domain, status=DomainRequest.DomainRequestStatus.SUBMITTED + ) + self.assertEquals(self.user.get_active_requests_count(), 1) + # with two active requests, expect this to return 2 + draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville2.gov") + DomainRequest.objects.create( + creator=self.user, requested_domain=draft_domain, status=DomainRequest.DomainRequestStatus.IN_REVIEW + ) + self.assertEquals(self.user.get_active_requests_count(), 2) + # with three active requests, expect this to return 3 + draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville3.gov") + DomainRequest.objects.create( + creator=self.user, requested_domain=draft_domain, status=DomainRequest.DomainRequestStatus.ACTION_NEEDED + ) + self.assertEquals(self.user.get_active_requests_count(), 3) + # with three active requests, expect this to return 3 (STARTED is not considered active) + draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville4.gov") + DomainRequest.objects.create( + creator=self.user, requested_domain=draft_domain, status=DomainRequest.DomainRequestStatus.STARTED + ) + self.assertEquals(self.user.get_active_requests_count(), 3) + + def test_rejected_requests_count(self): + """Test that the correct rejected domain requests count is returned for a user""" + # with no associated rejected requests, expect this to return 0 + self.assertEquals(self.user.get_rejected_requests_count(), 0) + # with one rejected request, expect this to return 1 + draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville1.gov") + DomainRequest.objects.create( + creator=self.user, requested_domain=draft_domain, status=DomainRequest.DomainRequestStatus.REJECTED + ) + self.assertEquals(self.user.get_rejected_requests_count(), 1) + + def test_ineligible_requests_count(self): + """Test that the correct ineligible domain requests count is returned for a user""" + # with no associated ineligible requests, expect this to return 0 + self.assertEquals(self.user.get_ineligible_requests_count(), 0) + # with one ineligible request, expect this to return 1 + draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville1.gov") + DomainRequest.objects.create( + creator=self.user, requested_domain=draft_domain, status=DomainRequest.DomainRequestStatus.INELIGIBLE + ) + self.assertEquals(self.user.get_ineligible_requests_count(), 1) + class TestContact(TestCase): def setUp(self): From 80ce98bf9cacb93561f4e8c93b35f5a1dc8e22de Mon Sep 17 00:00:00 2001 From: Kristina Yin <140533113+kristinacyin@users.noreply.github.com> Date: Mon, 8 Apr 2024 10:50:31 -0700 Subject: [PATCH 18/80] Update and rename design-onboarding.md to designer-onboarding.md --- .github/ISSUE_TEMPLATE/design-onboarding.md | 49 ----------- .github/ISSUE_TEMPLATE/designer-onboarding.md | 88 +++++++++++++++++++ 2 files changed, 88 insertions(+), 49 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/design-onboarding.md create mode 100644 .github/ISSUE_TEMPLATE/designer-onboarding.md diff --git a/.github/ISSUE_TEMPLATE/design-onboarding.md b/.github/ISSUE_TEMPLATE/design-onboarding.md deleted file mode 100644 index 3cca6e96c..000000000 --- a/.github/ISSUE_TEMPLATE/design-onboarding.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -name: Design Onboarding -about: Onboarding steps for designers. -title: 'Designer Onboarding: GH_HANDLE' -labels: design, onboarding -assignees: katherineosos - ---- - -# Designer Onboarding - -- Onboardee: _GH handle of person being onboarded_ -- Onboarder: _GH handle of onboard buddy_ - -Read the .gov onboarding document -Get access to Slack. Familiar yourself with the channels. Post a hi message in the disc channel. -Get access to Google doc. Navigate through our documents. Find the design folders. -Get access to our project on Github. Look at our project directories. Find our product repos. Find our Product backlog. -Know who to ask for what -- know your team mates. -Make sure you are invited to all our team meetings. -Get an introduction on our application and the design process from Katherine, our Design lead. -Understand the designer tools (Miro, Fisma, etc). -Familiarize with our design guides (e.g., USWDS, 21st Century Integrated Digital Experience Act) -Make a request to the dev lead to get your sandbox created and get access to /admin (added to fixatures). -Look at get.gov. Test out manage.get.gov. -Create your POM and present in the Team coffee. -Schedule a meet-n-greet with Paul, our Product Manager to learn about our application. -Schedule a meet-n-greet with Vicky, our Scrum master to get overview of our Scrum process, our backlog, how our sprint works and how to use your Scrum master to your advantage. -Check up with your manager on your EOD clearance process. - -## Access -Make sure you have access / been added to the following: -- [ ] The [.gov team](https://github.com/orgs/cisagov/teams/gov) under cisagov on GitHub -- [ ] [Slack](dhscisa.enterprise.slack.com), and added to the necessary channels -- [ ] Google Drive Project folder -- [ ] Figma -- [ ] Team meetings - -### Steps for the onboardee -- [ ] - -### Steps for the onboarder -- [ ] Setup a [sandbox] for the onboardee (#setting-up-developer-sandbox) -- [ ] Add the onboardee to our login.gov sandbox team (`.gov Registrar`) via the [dashboard](https://dashboard.int.identitysandbox.gov/) - - -## Documents to Review - -- [ ] [Team Onboarding](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit?usp=sharing) diff --git a/.github/ISSUE_TEMPLATE/designer-onboarding.md b/.github/ISSUE_TEMPLATE/designer-onboarding.md new file mode 100644 index 000000000..b04ea4314 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/designer-onboarding.md @@ -0,0 +1,88 @@ +--- +name: Designer Onboarding +about: Onboarding steps for designers. +title: 'Designer Onboarding: GH_HANDLE' +labels: design, onboarding +assignees: katherineosos + +--- + +# Designer Onboarding + +- Onboardee: _GH handle of person being onboarded_ +- Onboarder: _GH handle of onboard buddy_ + +Welcome to the .gov team! We're excited to have you here. Please follow the steps below to get everything set up. An onboarding buddy will help grant you access to all the tools and platforms we use. If you haven't been assigned an onboarding buddy, let us know in the #dotgov-disco channel. + +Read the .gov onboarding document +Get access to Slack. Familiar yourself with the channels. Post a hi message in the disc channel. +Get access to Google doc. Navigate through our documents. Find the design folders. +Get access to our project on Github. Look at our project directories. Find our product repos. Find our Product backlog. +Know who to ask for what -- know your team mates. +Make sure you are invited to all our team meetings. +Get an introduction on our application and the design process from Katherine, our Design lead. +Understand the designer tools (Miro, Fisma, etc). +Familiarize with our design guides (e.g., USWDS, 21st Century Integrated Digital Experience Act) +Make a request to the dev lead to get your sandbox created and get access to /admin (added to fixatures). +Look at get.gov. Test out manage.get.gov. +Create your POM and present in the Team coffee. +Schedule a meet-n-greet with Paul, our Product Manager to learn about our application. +Schedule a meet-n-greet with Vicky, our Scrum master to get overview of our Scrum process, our backlog, how our sprint works and how to use your Scrum master to your advantage. +Check up with your manager on your EOD clearance process. + +## Onboardee + +### Steps for the onboardee +- [ ] Read the [.gov onboarding doc](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit?usp=sharing) thoroughly. +- [ ] Accept Slack invitation and fill out your profile. + - [ ] For our Slack profile names, we usually follow the naming convention of `Firstname Lastname (Org, State, pronouns)`. + Example: Katherine Osos (Truss, MN, she/her) + - [ ] Make sure you have been added to the necessary [channels](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit#heading=h.li3lqcygw8ax) and familiarize yourself with them. +- [ ] Get access to our [Project Folder](https://drive.google.com/drive/folders/1qkoFQBlzXA7axi9CZ_OBhlJqRcqlNfpW?usp=drive_link) on Google Drive. + - [ ] Explore the folders and docs. Designers interface with the Product Design, Content, and Research folders most often. +- [ ] Make sure you have been invited to our [team meetings](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit#heading=h.h62kzew057p1) on Google Meet. +- [ ] Get access to our design tools: [Figma](https://www.figma.com/files/1287135731043703282/team/1299882813146449644), Miro +- [ ] Follow the steps in [Preparing for your sandbox](####preparing-for-your-sandbox) section below. + +#### Preparing for your sandbox +- [ ] Create two identity sandbox accounts, this is login.gov’s test environment + - How to make it: + - [ ] Navigate to [identity sandbox](https://idp.int.identitysandbox.gov/) + - [ ] Click create an account + - [ ] Fill in the information with fake data, though you may choose to use your real name. For the social security field, input a SSN that starts with 666 or 900. Instead of uploading a picture of your ID, you may upload any picture (include a cat meme). + - [ ] See login.gov’s [developer section](https://developers.login.gov/testing/#testing-identity-proofing) for testing with identity sandbox if you encounter issues. + - One account should be your preferred work email for the username, the second should be the SAME email address followed by a plus one + - Ex: bob@dhs.gov for the first login account and bob+1@dhs.gov + - One account will represent a normal user while the other will be your way of simulating a “analyst” +- [ ] Sandbox: Have an engineer create a sandbox for you (message in #dotgov-dev Slack channel). This will be used to make content updates in the UI. + - [ ] You will receive a link for Cloud.gov to be added to an organization. Be sure to verify via that link or you may not be able to access any sandbox. + - [ ] Also ask that they add mock data to that sandbox. This will be helpful for testing various scenarios in the UI. + - Note: at any point you can add mock data to your sandbox and reset all test data in you sandbox by running the [reset action on github](https://github.com/cisagov/getgov/actions/workflows/reset-db.yaml) + - Go to the provided link. Click the Run Workflow dropdown + - Select your sandbox environment (your initials). + - Then click the green “Run workflow” button + - [ ] Your sandbox makes the app available online, and everyone should be able to access your sandbox once it is made. See the [sandbox section of the onboarding doc](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit#heading=h.cdlfxamcvus5), your sandbox url will be in this format: https://getgov-.app.cloud.gov/ where ENV refers to your initials. + - [ ] Make sure to check that you can log into your sandbox with the identity sandbox login credentials you made. +Request access to /admin (also known as “add to fixtures”) +Follow the steps in the developer readme for adding yourself to admin +When you get to the last step about editing the code you can instead share the ID you found in the previous step with someone on the development team +OPTIONAL: Request an “analyst” account by using your second identity sandbox credential +This account will allow you to see what an analyst sees when they log in to the application +Use whichever identity sandbox account you did not use when requesting /admin access and follow the developer readme for creating an analyst account +Tip: You can add a ‘+1’ to the end of your gmail account to create a “new” email address that can be used for making a new Login.gov account (example: “orginalemailaddress+1@gmail.com”). Everything will still go to the same email without needing to make a new one. +Just like with /admin, in the last step about editing the code you can instead share the ID you found in the previous step with someone on the development team + + +### Access +By following the steps, you should have access / been added to the following: +- [ ] The [.gov team](https://github.com/orgs/cisagov/teams/gov) under cisagov on GitHub +- [ ] [Slack](dhscisa.enterprise.slack.com), and have been added to the necessary channels +- [ ] [Google Drive Project folder](https://drive.google.com/drive/folders/1qkoFQBlzXA7axi9CZ_OBhlJqRcqlNfpW?usp=drive_link) +- [ ] [.gov team on Figma](https://www.figma.com/files/1287135731043703282/team/1299882813146449644) (as an editor if you have a license) +- [ ] [Team meetings](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit#heading=h.h62kzew057p1) + +## Onboarder + +### Steps for the onboarder +- [ ] Setup a [sandbox] for the onboardee (#setting-up-developer-sandbox) +- [ ] Add the onboardee to our login.gov sandbox team (`.gov Registrar`) via the [dashboard](https://dashboard.int.identitysandbox.gov/) From a257e843af611e17108abc88f33bdea14901d15f Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Mon, 8 Apr 2024 15:27:32 -0400 Subject: [PATCH 19/80] styling --- src/registrar/assets/sass/_theme/_admin.scss | 11 +++++++++++ .../django/admin/includes/user_detail_list.html | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/registrar/assets/sass/_theme/_admin.scss b/src/registrar/assets/sass/_theme/_admin.scss index 658ae5ca8..407163fff 100644 --- a/src/registrar/assets/sass/_theme/_admin.scss +++ b/src/registrar/assets/sass/_theme/_admin.scss @@ -435,3 +435,14 @@ address.dja-address-contact-list { color: var(--link-fg); } } + +.dja-status-list { + border-top: solid 1px var(--border-color); + margin-left: 0 !important; + padding-left: 0 !important; + padding-top: 15px; + li { + line-height: 1.15; + font-family: "Source Sans Pro Web", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif !important; + } +} diff --git a/src/registrar/templates/django/admin/includes/user_detail_list.html b/src/registrar/templates/django/admin/includes/user_detail_list.html index be233af8e..829af933a 100644 --- a/src/registrar/templates/django/admin/includes/user_detail_list.html +++ b/src/registrar/templates/django/admin/includes/user_detail_list.html @@ -5,7 +5,7 @@ {% with rejected_requests_count=user.get_rejected_requests_count %} {% with ineligible_requests_count=user.get_ineligible_requests_count %} {% if approved_domains_count|add:active_requests_count|add:rejected_requests_count|add:ineligible_requests_count > 0 %} -
      +
        {% if approved_domains_count > 0 %} {# Approved domains #}
      • Approved domains: {{ approved_domains_count }}
      • From ca4490e3da8e972c7a9b272c2d500c2942bcba02 Mon Sep 17 00:00:00 2001 From: Kristina Yin <140533113+kristinacyin@users.noreply.github.com> Date: Mon, 8 Apr 2024 20:28:19 -0700 Subject: [PATCH 20/80] Update designer-onboarding.md --- .github/ISSUE_TEMPLATE/designer-onboarding.md | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/designer-onboarding.md b/.github/ISSUE_TEMPLATE/designer-onboarding.md index b04ea4314..6f2374311 100644 --- a/.github/ISSUE_TEMPLATE/designer-onboarding.md +++ b/.github/ISSUE_TEMPLATE/designer-onboarding.md @@ -61,16 +61,15 @@ Check up with your manager on your EOD clearance process. - Go to the provided link. Click the Run Workflow dropdown - Select your sandbox environment (your initials). - Then click the green “Run workflow” button - - [ ] Your sandbox makes the app available online, and everyone should be able to access your sandbox once it is made. See the [sandbox section of the onboarding doc](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit#heading=h.cdlfxamcvus5), your sandbox url will be in this format: https://getgov-.app.cloud.gov/ where ENV refers to your initials. - - [ ] Make sure to check that you can log into your sandbox with the identity sandbox login credentials you made. -Request access to /admin (also known as “add to fixtures”) -Follow the steps in the developer readme for adding yourself to admin -When you get to the last step about editing the code you can instead share the ID you found in the previous step with someone on the development team -OPTIONAL: Request an “analyst” account by using your second identity sandbox credential -This account will allow you to see what an analyst sees when they log in to the application -Use whichever identity sandbox account you did not use when requesting /admin access and follow the developer readme for creating an analyst account -Tip: You can add a ‘+1’ to the end of your gmail account to create a “new” email address that can be used for making a new Login.gov account (example: “orginalemailaddress+1@gmail.com”). Everything will still go to the same email without needing to make a new one. -Just like with /admin, in the last step about editing the code you can instead share the ID you found in the previous step with someone on the development team + - [ ] Your sandbox makes the app available online, and everyone should be able to access your sandbox once it is made. See the [sandbox section of the onboarding doc](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit#heading=h.cdlfxamcvus5), your sandbox url will be in this format: `https://getgov-.app.cloud.gov/` where ENV refers to your initials. + - [ ] Make sure to check that you can log into your sandbox with the identity sandbox login credentials you made. +- [ ] Request access to /admin (also known as “add to fixtures”) + - [ ] Follow the steps in the [developer readme for adding yourself to admin](https://github.com/cisagov/getgov/blob/main/docs/developer/README.md#adding-user-to-admin) + - When you get to the last step about editing the code, you can instead share the ID you found in the previous step with someone on the development team. +- [ ] OPTIONAL: Request an “analyst” account by using your second identity sandbox credential + - This account will allow you to see what an analyst sees when they log in to the application + - Use whichever identity sandbox account you did not use when requesting /admin access and follow the [developer readme for creating an analyst account](https://github.com/cisagov/getgov/blob/main/docs/developer/README.md#adding-an-analyst-to-admin) + - Just like with /admin, in the last step about editing the code you can instead share the ID you found in the previous step with someone on the development team ### Access From f6d95723cea38dea2605ceef6dd883930449ee26 Mon Sep 17 00:00:00 2001 From: Kristina Yin <140533113+kristinacyin@users.noreply.github.com> Date: Mon, 8 Apr 2024 21:48:28 -0700 Subject: [PATCH 21/80] Update designer-onboarding.md --- .github/ISSUE_TEMPLATE/designer-onboarding.md | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/designer-onboarding.md b/.github/ISSUE_TEMPLATE/designer-onboarding.md index 6f2374311..8f36462dc 100644 --- a/.github/ISSUE_TEMPLATE/designer-onboarding.md +++ b/.github/ISSUE_TEMPLATE/designer-onboarding.md @@ -14,35 +14,28 @@ assignees: katherineosos Welcome to the .gov team! We're excited to have you here. Please follow the steps below to get everything set up. An onboarding buddy will help grant you access to all the tools and platforms we use. If you haven't been assigned an onboarding buddy, let us know in the #dotgov-disco channel. -Read the .gov onboarding document -Get access to Slack. Familiar yourself with the channels. Post a hi message in the disc channel. -Get access to Google doc. Navigate through our documents. Find the design folders. -Get access to our project on Github. Look at our project directories. Find our product repos. Find our Product backlog. -Know who to ask for what -- know your team mates. -Make sure you are invited to all our team meetings. -Get an introduction on our application and the design process from Katherine, our Design lead. -Understand the designer tools (Miro, Fisma, etc). -Familiarize with our design guides (e.g., USWDS, 21st Century Integrated Digital Experience Act) -Make a request to the dev lead to get your sandbox created and get access to /admin (added to fixatures). -Look at get.gov. Test out manage.get.gov. -Create your POM and present in the Team coffee. -Schedule a meet-n-greet with Paul, our Product Manager to learn about our application. -Schedule a meet-n-greet with Vicky, our Scrum master to get overview of our Scrum process, our backlog, how our sprint works and how to use your Scrum master to your advantage. -Check up with your manager on your EOD clearance process. - ## Onboardee ### Steps for the onboardee - [ ] Read the [.gov onboarding doc](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit?usp=sharing) thoroughly. -- [ ] Accept Slack invitation and fill out your profile. +- [ ] Accept an invitation to our Slack workspace and fill out your profile. - [ ] For our Slack profile names, we usually follow the naming convention of `Firstname Lastname (Org, State, pronouns)`. Example: Katherine Osos (Truss, MN, she/her) - [ ] Make sure you have been added to the necessary [channels](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit#heading=h.li3lqcygw8ax) and familiarize yourself with them. +- [ ] Create a [Google workspace enterprise account](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit?pli=1#heading=h.xowzg9w0qlis) for CISA. - [ ] Get access to our [Project Folder](https://drive.google.com/drive/folders/1qkoFQBlzXA7axi9CZ_OBhlJqRcqlNfpW?usp=drive_link) on Google Drive. - [ ] Explore the folders and docs. Designers interface with the Product Design, Content, and Research folders most often. - [ ] Make sure you have been invited to our [team meetings](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit#heading=h.h62kzew057p1) on Google Meet. -- [ ] Get access to our design tools: [Figma](https://www.figma.com/files/1287135731043703282/team/1299882813146449644), Miro -- [ ] Follow the steps in [Preparing for your sandbox](####preparing-for-your-sandbox) section below. +- [ ] Review our [design tools](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit?pli=1#heading=h.aprurp3z4gmv). +- [ ] Accept invitation to our [Figma workspace](https://www.figma.com/files/1287135731043703282/team/1299882813146449644). +- [ ] Follow the steps in [Preparing for your sandbox](#preparing-for-your-sandbox) section below. +- [ ] Schedule coffee chats with Design Lead, other designers, scrum master, and product manager ([team directory](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit?pli=1#heading=h.1vq6r8e52e9f)). +- [ ] Look over [recommended reading](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit?pli=1#heading=h.7ox9ee7v5q5n) and [relevant links](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit?pli=1#heading=h.d9pac1gc751t). +- [ ] Fill out your own Personal Operating Manual (POM) on the [team norming board](https://miro.com/app/board/uXjVMxMu1SA=/). Present it on the next team coffee meeting. +- [ ] FOR FEDERAL EMPLOYEES: Check in with your manager on the CISA onboarding process and getting your PIV card. +- [ ] FOR CONTRACTORS: Check with your manager on your EOD clearance process. +- [ ] OPTIONAL: Request access to our [analytics tools](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit?pli=1#heading=h.9q334hs4lbks). + #### Preparing for your sandbox - [ ] Create two identity sandbox accounts, this is login.gov’s test environment @@ -80,8 +73,13 @@ By following the steps, you should have access / been added to the following: - [ ] [.gov team on Figma](https://www.figma.com/files/1287135731043703282/team/1299882813146449644) (as an editor if you have a license) - [ ] [Team meetings](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit#heading=h.h62kzew057p1) + ## Onboarder ### Steps for the onboarder -- [ ] Setup a [sandbox] for the onboardee (#setting-up-developer-sandbox) -- [ ] Add the onboardee to our login.gov sandbox team (`.gov Registrar`) via the [dashboard](https://dashboard.int.identitysandbox.gov/) +- [ ] Make sure onboardee is given an invitation to our Slack workspace +- [ ] Add onboardee to dotgov Slack channels +- [ ] Once onboardee has a Google workspace enterprise account, add onboardee to Project Folder as an editor +- [ ] Once onboardee has been granted a Figma license, add them as an editor to the Figma team workspace +- [ ] Add onboardee to our team meetings +- [ ] If applicable, invite onboardee to Google Analytics, Google Search Console, and Search.gov console From e6106c7946c1a5f93cfd9c6d416104d40c9da272 Mon Sep 17 00:00:00 2001 From: Alysia Broddrick Date: Tue, 9 Apr 2024 07:37:42 -0700 Subject: [PATCH 22/80] wait time changed from 20 to 30 business days --- src/registrar/templates/includes/domain_request.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/templates/includes/domain_request.html b/src/registrar/templates/includes/domain_request.html index a15a8673c..0e377f35c 100644 --- a/src/registrar/templates/includes/domain_request.html +++ b/src/registrar/templates/includes/domain_request.html @@ -3,7 +3,7 @@

        Next steps in this process

        -

        We received your .gov domain request. Our next step is to review your request. This usually takes 20 business days. We’ll email you if we have questions and when we complete our review. Contact us with any questions.

        +

        We received your .gov domain request. Our next step is to review your request. This usually takes 30 business days. We’ll email you if we have questions and when we complete our review. Contact us with any questions.

        Need to make changes? From ccf49040fd4d61ec33d1a945f727c513d476a463 Mon Sep 17 00:00:00 2001 From: Kristina Yin <140533113+kristinacyin@users.noreply.github.com> Date: Tue, 9 Apr 2024 16:56:27 -0700 Subject: [PATCH 23/80] Update designer-onboarding.md --- .github/ISSUE_TEMPLATE/designer-onboarding.md | 42 ++++--------------- 1 file changed, 8 insertions(+), 34 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/designer-onboarding.md b/.github/ISSUE_TEMPLATE/designer-onboarding.md index 8f36462dc..0b810fd5c 100644 --- a/.github/ISSUE_TEMPLATE/designer-onboarding.md +++ b/.github/ISSUE_TEMPLATE/designer-onboarding.md @@ -14,6 +14,7 @@ assignees: katherineosos Welcome to the .gov team! We're excited to have you here. Please follow the steps below to get everything set up. An onboarding buddy will help grant you access to all the tools and platforms we use. If you haven't been assigned an onboarding buddy, let us know in the #dotgov-disco channel. + ## Onboardee ### Steps for the onboardee @@ -22,53 +23,25 @@ Welcome to the .gov team! We're excited to have you here. Please follow the step - [ ] For our Slack profile names, we usually follow the naming convention of `Firstname Lastname (Org, State, pronouns)`. Example: Katherine Osos (Truss, MN, she/her) - [ ] Make sure you have been added to the necessary [channels](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit#heading=h.li3lqcygw8ax) and familiarize yourself with them. -- [ ] Create a [Google workspace enterprise account](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit?pli=1#heading=h.xowzg9w0qlis) for CISA. +- [ ] FOR FEDERAL EMPLOYEES: Create a [Google workspace enterprise account](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit?pli=1#heading=h.xowzg9w0qlis) for CISA. - [ ] Get access to our [Project Folder](https://drive.google.com/drive/folders/1qkoFQBlzXA7axi9CZ_OBhlJqRcqlNfpW?usp=drive_link) on Google Drive. - [ ] Explore the folders and docs. Designers interface with the Product Design, Content, and Research folders most often. - [ ] Make sure you have been invited to our [team meetings](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit#heading=h.h62kzew057p1) on Google Meet. - [ ] Review our [design tools](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit?pli=1#heading=h.aprurp3z4gmv). - [ ] Accept invitation to our [Figma workspace](https://www.figma.com/files/1287135731043703282/team/1299882813146449644). -- [ ] Follow the steps in [Preparing for your sandbox](#preparing-for-your-sandbox) section below. +- [ ] Follow the steps in [preparing for your sandbox](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit?pli=1#heading=h.au66hq5e0l8s) section on the onboarding doc. - [ ] Schedule coffee chats with Design Lead, other designers, scrum master, and product manager ([team directory](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit?pli=1#heading=h.1vq6r8e52e9f)). - [ ] Look over [recommended reading](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit?pli=1#heading=h.7ox9ee7v5q5n) and [relevant links](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit?pli=1#heading=h.d9pac1gc751t). -- [ ] Fill out your own Personal Operating Manual (POM) on the [team norming board](https://miro.com/app/board/uXjVMxMu1SA=/). Present it on the next team coffee meeting. +- [ ] Fill out your own Personal Operating Manual (POM) on the [team norming board](https://miro.com/app/board/uXjVMxMu1SA=/). OPTIONAL: Present it on the next team coffee meeting. - [ ] FOR FEDERAL EMPLOYEES: Check in with your manager on the CISA onboarding process and getting your PIV card. - [ ] FOR CONTRACTORS: Check with your manager on your EOD clearance process. - [ ] OPTIONAL: Request access to our [analytics tools](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit?pli=1#heading=h.9q334hs4lbks). -#### Preparing for your sandbox -- [ ] Create two identity sandbox accounts, this is login.gov’s test environment - - How to make it: - - [ ] Navigate to [identity sandbox](https://idp.int.identitysandbox.gov/) - - [ ] Click create an account - - [ ] Fill in the information with fake data, though you may choose to use your real name. For the social security field, input a SSN that starts with 666 or 900. Instead of uploading a picture of your ID, you may upload any picture (include a cat meme). - - [ ] See login.gov’s [developer section](https://developers.login.gov/testing/#testing-identity-proofing) for testing with identity sandbox if you encounter issues. - - One account should be your preferred work email for the username, the second should be the SAME email address followed by a plus one - - Ex: bob@dhs.gov for the first login account and bob+1@dhs.gov - - One account will represent a normal user while the other will be your way of simulating a “analyst” -- [ ] Sandbox: Have an engineer create a sandbox for you (message in #dotgov-dev Slack channel). This will be used to make content updates in the UI. - - [ ] You will receive a link for Cloud.gov to be added to an organization. Be sure to verify via that link or you may not be able to access any sandbox. - - [ ] Also ask that they add mock data to that sandbox. This will be helpful for testing various scenarios in the UI. - - Note: at any point you can add mock data to your sandbox and reset all test data in you sandbox by running the [reset action on github](https://github.com/cisagov/getgov/actions/workflows/reset-db.yaml) - - Go to the provided link. Click the Run Workflow dropdown - - Select your sandbox environment (your initials). - - Then click the green “Run workflow” button - - [ ] Your sandbox makes the app available online, and everyone should be able to access your sandbox once it is made. See the [sandbox section of the onboarding doc](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit#heading=h.cdlfxamcvus5), your sandbox url will be in this format: `https://getgov-.app.cloud.gov/` where ENV refers to your initials. - - [ ] Make sure to check that you can log into your sandbox with the identity sandbox login credentials you made. -- [ ] Request access to /admin (also known as “add to fixtures”) - - [ ] Follow the steps in the [developer readme for adding yourself to admin](https://github.com/cisagov/getgov/blob/main/docs/developer/README.md#adding-user-to-admin) - - When you get to the last step about editing the code, you can instead share the ID you found in the previous step with someone on the development team. -- [ ] OPTIONAL: Request an “analyst” account by using your second identity sandbox credential - - This account will allow you to see what an analyst sees when they log in to the application - - Use whichever identity sandbox account you did not use when requesting /admin access and follow the [developer readme for creating an analyst account](https://github.com/cisagov/getgov/blob/main/docs/developer/README.md#adding-an-analyst-to-admin) - - Just like with /admin, in the last step about editing the code you can instead share the ID you found in the previous step with someone on the development team - - ### Access By following the steps, you should have access / been added to the following: - [ ] The [.gov team](https://github.com/orgs/cisagov/teams/gov) under cisagov on GitHub -- [ ] [Slack](dhscisa.enterprise.slack.com), and have been added to the necessary channels +- [ ] [Slack](https://dhscisa.enterprise.slack.com), and have been added to the necessary channels - [ ] [Google Drive Project folder](https://drive.google.com/drive/folders/1qkoFQBlzXA7axi9CZ_OBhlJqRcqlNfpW?usp=drive_link) - [ ] [.gov team on Figma](https://www.figma.com/files/1287135731043703282/team/1299882813146449644) (as an editor if you have a license) - [ ] [Team meetings](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit#heading=h.h62kzew057p1) @@ -79,7 +52,8 @@ By following the steps, you should have access / been added to the following: ### Steps for the onboarder - [ ] Make sure onboardee is given an invitation to our Slack workspace - [ ] Add onboardee to dotgov Slack channels -- [ ] Once onboardee has a Google workspace enterprise account, add onboardee to Project Folder as an editor -- [ ] Once onboardee has been granted a Figma license, add them as an editor to the Figma team workspace +- [ ] Add onboardee is added to Project Folder as a "Contributor" (or if you do not have permissions, confirm with Cameron) + - If onboardee if a federal employee, add them as a "Content Manager" instead +- [ ] Once onboardee has been granted a Figma license, add them as an editor to the Figma team workspace, Otherwise, you can add them as a viewer in the meantime. - [ ] Add onboardee to our team meetings - [ ] If applicable, invite onboardee to Google Analytics, Google Search Console, and Search.gov console From 3d21e4912ce66ead90b652e914c18f68713b2349 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 10 Apr 2024 09:22:25 -0600 Subject: [PATCH 24/80] Fix migrations after merge --- .../{0082_create_groups_v11.py => 0084_create_groups_v11.py} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename src/registrar/migrations/{0082_create_groups_v11.py => 0084_create_groups_v11.py} (94%) diff --git a/src/registrar/migrations/0082_create_groups_v11.py b/src/registrar/migrations/0084_create_groups_v11.py similarity index 94% rename from src/registrar/migrations/0082_create_groups_v11.py rename to src/registrar/migrations/0084_create_groups_v11.py index 8bd0102cd..efb587520 100644 --- a/src/registrar/migrations/0082_create_groups_v11.py +++ b/src/registrar/migrations/0084_create_groups_v11.py @@ -25,7 +25,7 @@ def create_groups(apps, schema_editor) -> Any: class Migration(migrations.Migration): dependencies = [ - ("registrar", "0081_create_groups_v10"), + ("registrar", "0083_alter_contact_email_alter_publiccontact_email"), ] operations = [ From 14cfaa7f4c80bddd4def274ce9d8e423165af64d Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Wed, 10 Apr 2024 13:19:19 -0400 Subject: [PATCH 25/80] changed strings to constants --- src/registrar/models/user.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/registrar/models/user.py b/src/registrar/models/user.py index 830cac944..1ca2412ca 100644 --- a/src/registrar/models/user.py +++ b/src/registrar/models/user.py @@ -9,6 +9,7 @@ from .domain_invitation import DomainInvitation from .transition_domain import TransitionDomain from .verified_by_staff import VerifiedByStaff from .domain import Domain +from .domain_request import DomainRequest from phonenumber_field.modelfields import PhoneNumberField # type: ignore @@ -69,23 +70,27 @@ class User(AbstractUser): def get_approved_domains_count(self): """Return count of approved domains""" - allowed_states = ["unknown", "dns needed", "ready", "on hold"] + allowed_states = [Domain.State.UNKNOWN, Domain.State.DNS_NEEDED, Domain.State.READY, Domain.State.ON_HOLD] approved_domains_count = self.domains.filter(state__in=allowed_states).count() return approved_domains_count def get_active_requests_count(self): """Return count of active requests""" - allowed_states = ["submitted", "in review", "action needed"] + allowed_states = [ + DomainRequest.DomainRequestStatus.SUBMITTED, + DomainRequest.DomainRequestStatus.IN_REVIEW, + DomainRequest.DomainRequestStatus.ACTION_NEEDED, + ] active_requests_count = self.domain_requests_created.filter(status__in=allowed_states).count() return active_requests_count def get_rejected_requests_count(self): """Return count of rejected requests""" - return self.domain_requests_created.filter(status="rejected").count() + return self.domain_requests_created.filter(status=DomainRequest.DomainRequestStatus.REJECTED).count() def get_ineligible_requests_count(self): """Return count of ineligible requests""" - return self.domain_requests_created.filter(status="ineligible").count() + return self.domain_requests_created.filter(status=DomainRequest.DomainRequestStatus.INELIGIBLE).count() @classmethod def needs_identity_verification(cls, email, uuid): From 0db398321b08f8f2625dd9823d3976d4949b31a4 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Wed, 10 Apr 2024 13:51:35 -0400 Subject: [PATCH 26/80] added unit test --- src/registrar/tests/test_admin.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index 41700c0ae..73913abc2 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -1553,6 +1553,10 @@ class TestDomainRequestAdmin(MockEppLib): # Test for the copy link self.assertContains(response, "usa-button__clipboard", count=4) + # Test that Creator counts display properly + self.assertNotContains(response, "Approved domains") + self.assertContains(response, "Active requests") + def test_save_model_sets_restricted_status_on_user(self): with less_console_noise(): # make sure there is no user with this email From 6cdba8e995afc8fc6be26329b907647e47d43429 Mon Sep 17 00:00:00 2001 From: Alysia Broddrick Date: Wed, 10 Apr 2024 12:18:44 -0700 Subject: [PATCH 27/80] updated language regarding fixtures --- docs/developer/README.md | 25 ++++++++++++++++++------- docs/django-admin/roles.md | 4 ++-- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/docs/developer/README.md b/docs/developer/README.md index f894955e5..e36cfffab 100644 --- a/docs/developer/README.md +++ b/docs/developer/README.md @@ -73,14 +73,24 @@ cp ./.env-example .env Get the secrets from Cloud.gov by running `cf env getgov-YOURSANDBOX`. More information is available in [rotate_application_secrets.md](../operations/runbooks/rotate_application_secrets.md). -## Adding user to /admin +## Getting access to /admin on all development sandboxes (also referred to as "adding to fixtures") -The endpoint /admin can be used to view and manage site content, including but not limited to user information and the list of current applications in the database. To be able to view and use /admin locally: +The endpoint /admin can be used to view and manage site content, including but not limited to user information and the list of current applications in the database. However, access to this is limited to analysts and full-access users with regular domain requestors and domain managers not being able to see this page. + +While on production (the sandbox referred to as `stable`), an existing analyst or full-access user typically grants access /admin as part of onboarding ([see these instructions](../django-admin/roles.md)), doing this for all development sandboxes is very time consuming. Instead, to get access to /admin on all development sandboxes and when developing code locally, refer to the following sections depending on what level of user access you desire. + +Note: this process where a team member gets analyst/superuser permission on all non-production sandboxes is often called "Adding to fixtures". + +### Adding full-access user to /admin + + To get access to /admin on every non-production sandbox and to use /admin in local development, do the following: 1. Login via login.gov 2. Go to the home page and make sure you can see the part where you can submit a domain request -3. Go to /admin and it will tell you that UUID is not authorized, copy that UUID for use in 4 -4. in src/registrar/fixtures_users.py add to the `ADMINS` list in that file by adding your UUID as your username along with your first and last name. See below: +3. Go to /admin and it will tell you that your UUID is not authorized (it shows a very long string, this is your UUID). Copy that UUID for use in 4. +4. (Designers)- message in #getgov-dev that you need access to admin as a `superuser` and send them this UUID along with your desired email address. Please see the "Adding an Analyst to /admin" section below to complete similiar steps if you also desire an `analyst` user account. Engineers will handle the remaining steps for designers, stop here. + +(Engineers) In src/registrar/fixtures_users.py add to the `ADMINS` list in that file by adding your UUID as your username along with your first and last name. See below: ``` ADMINS = [ @@ -93,10 +103,10 @@ The endpoint /admin can be used to view and manage site content, including but n ] ``` -5. In the browser, navigate to /admin. To verify that all is working correctly, under "domain requests" you should see fake domains with various fake statuses. -6. Add an optional email key/value pair +5. (Engineer only) In the browser, navigate to /admin. To verify that all is working correctly, under "domain requests" you should see fake domains with various fake statuses. +6. (Engineer only) Add an optional email key/value pair -### Adding an Analyst to /admin +### Adding an analyst-level user to /admin Analysts are a variant of the admin role with limited permissions. The process for adding an Analyst is much the same as adding an admin: 1. Login via login.gov (if you already exist as an admin, you will need to create a separate login.gov account for this: i.e. first.last+1@email.com) @@ -119,6 +129,7 @@ Analysts are a variant of the admin role with limited permissions. The process f 6. Add an optional email key/value pair Do note that if you wish to have both an analyst and admin account, append `-Analyst` to your first and last name, or use a completely different first/last name to avoid confusion. Example: `Bob-Analyst` + ## Adding to CODEOWNERS (optional) The CODEOWNERS file sets the tagged individuals as default reviewers on any Pull Request that changes files that they are marked as owners of. diff --git a/docs/django-admin/roles.md b/docs/django-admin/roles.md index c527bbfa5..4e74a857f 100644 --- a/docs/django-admin/roles.md +++ b/docs/django-admin/roles.md @@ -9,7 +9,7 @@ our `user_group` model and run in a migration. For more details, refer to the [user group model](../../src/registrar/models/user_group.py). -## Adding a user as analyst or granting full access via django-admin +## Adding a user as analyst or granting full access via django-admin (/admin) If a new team member has joined, then they will need to be granted analyst (`cisa_analysts_group`) or full access (`full_access_group`) permissions in order to view the admin pages. These admin pages are the ones found at manage.get.gov/admin. To do this, do the following: @@ -21,7 +21,7 @@ To do this, do the following: 5. (Optional) If the user needs access to django admin (such as an analyst), then you will also need to make sure "Staff Status" is checked. This can be found in the same `User Permissions` section right below the checkbox for `Active`. 6. Click `Save` to apply all changes. -## Removing a user group permission via django-admin +## Removing a user group permission via django-admin (/admin) If an employee was given the wrong permissions or has had a change in roles that subsequently requires a permission change, then their permissions should be updated in django-admin. Much like in the previous section you can accomplish this by doing the following: From f7ba4a0404a235e9e737905358307037137cfdd3 Mon Sep 17 00:00:00 2001 From: Alysia Broddrick Date: Wed, 10 Apr 2024 12:35:26 -0700 Subject: [PATCH 28/80] duplicated content from above section --- docs/developer/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/developer/README.md b/docs/developer/README.md index e36cfffab..29bc0a530 100644 --- a/docs/developer/README.md +++ b/docs/developer/README.md @@ -112,7 +112,9 @@ Analysts are a variant of the admin role with limited permissions. The process f 1. Login via login.gov (if you already exist as an admin, you will need to create a separate login.gov account for this: i.e. first.last+1@email.com) 2. Go to the home page and make sure you can see the part where you can submit a domain request 3. Go to /admin and it will tell you that UUID is not authorized, copy that UUID for use in 4 (this will be a different UUID than the one obtained from creating an admin) -4. in src/registrar/fixtures_users.py add to the `STAFF` list in that file by adding your UUID as your username along with your first and last name. See below: +4. (Designers)- message in #getgov-dev that you need access to admin as a `superuser` and send them this UUID along with your desired email address. Please see the "Adding an Analyst to /admin" section below to complete similiar steps if you also desire an `analyst` user account. Engineers will handle the remaining steps for designers, stop here. + +(Engineers) In src/registrar/fixtures_users.py add to the `STAFF` list in that file by adding your UUID as your username along with your first and last name. See below: ``` STAFF = [ @@ -125,8 +127,8 @@ Analysts are a variant of the admin role with limited permissions. The process f ] ``` -5. In the browser, navigate to /admin. To verify that all is working correctly, verify that you can only see a sub-section of the modules and some are set to view-only. -6. Add an optional email key/value pair +5. (Engineer only) In the browser, navigate to /admin. To verify that all is working correctly, verify that you can only see a sub-section of the modules and some are set to view-only. +6. (Engineer only) Add an optional email key/value pair Do note that if you wish to have both an analyst and admin account, append `-Analyst` to your first and last name, or use a completely different first/last name to avoid confusion. Example: `Bob-Analyst` From 536a3f6684b468cc181563a530e263723cf1917d Mon Sep 17 00:00:00 2001 From: Kristina Yin <140533113+kristinacyin@users.noreply.github.com> Date: Wed, 10 Apr 2024 23:55:28 -0700 Subject: [PATCH 29/80] Update designer-onboarding.md --- .github/ISSUE_TEMPLATE/designer-onboarding.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/designer-onboarding.md b/.github/ISSUE_TEMPLATE/designer-onboarding.md index 0b810fd5c..0921728ae 100644 --- a/.github/ISSUE_TEMPLATE/designer-onboarding.md +++ b/.github/ISSUE_TEMPLATE/designer-onboarding.md @@ -35,7 +35,9 @@ Welcome to the .gov team! We're excited to have you here. Please follow the step - [ ] Fill out your own Personal Operating Manual (POM) on the [team norming board](https://miro.com/app/board/uXjVMxMu1SA=/). OPTIONAL: Present it on the next team coffee meeting. - [ ] FOR FEDERAL EMPLOYEES: Check in with your manager on the CISA onboarding process and getting your PIV card. - [ ] FOR CONTRACTORS: Check with your manager on your EOD clearance process. -- [ ] OPTIONAL: Request access to our [analytics tools](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit?pli=1#heading=h.9q334hs4lbks). +- [ ] OPTIONAL: Request access to our [analytics tools](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit?pli=1#heading=h.9q334hs4lbks). +- [ ] OPTIONAL: If you would like to manage content updates by running code locally rather than through GitHub, follow the steps in the [dev onboarding ticket](https://github.com/cisagov/getgov/issues/new?assignees=loganmeetsworld&labels=dev%2C+onboarding&template=developer-onboarding.md&title=Developer+Onboarding%3A+GH_HANDLE). + - [ ] If you would like to run code locally on a CISA laptop, reference the "Setting up a developer environment on CISA laptops" section of the onboarding doc. ### Access From 8302fe1ae1e7704729fd0247ed03e9e37fa4d462 Mon Sep 17 00:00:00 2001 From: Kristina Yin <140533113+kristinacyin@users.noreply.github.com> Date: Wed, 10 Apr 2024 23:58:14 -0700 Subject: [PATCH 30/80] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f457e7e69..12efb2a31 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ The .gov domain helps U.S.-based government organizations gain public trust by b ## Onboarding -For new members of the @cisagov/dotgov team looking to contribute to the registrar, please open an [onboarding ticket](https://github.com/cisagov/getgov/issues/new?assignees=loganmeetsworld&labels=dev%2C+onboarding&template=developer-onboarding.md&title=Developer+Onboarding%3A+GH_HANDLE). +For new members of the @cisagov/dotgov team looking to contribute to the registrar, please open an [dev onboarding ticket](https://github.com/cisagov/getgov/issues/new?assignees=loganmeetsworld&labels=dev%2C+onboarding&template=developer-onboarding.md&title=Developer+Onboarding%3A+GH_HANDLE) or [designer onboarding ticket](https://github.com/cisagov/getgov/issues/new?assignees=loganmeetsworld&labels=design%2C+onboarding&template=designer-onboarding.md&title=Designer+Onboarding%3A+GH_HANDLE). ## Code From f954f3fcf30587e270ad7c3030fba62f4881738c Mon Sep 17 00:00:00 2001 From: Kristina Yin <140533113+kristinacyin@users.noreply.github.com> Date: Wed, 10 Apr 2024 23:59:00 -0700 Subject: [PATCH 31/80] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 12efb2a31..545b64f99 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ The .gov domain helps U.S.-based government organizations gain public trust by b ## Onboarding -For new members of the @cisagov/dotgov team looking to contribute to the registrar, please open an [dev onboarding ticket](https://github.com/cisagov/getgov/issues/new?assignees=loganmeetsworld&labels=dev%2C+onboarding&template=developer-onboarding.md&title=Developer+Onboarding%3A+GH_HANDLE) or [designer onboarding ticket](https://github.com/cisagov/getgov/issues/new?assignees=loganmeetsworld&labels=design%2C+onboarding&template=designer-onboarding.md&title=Designer+Onboarding%3A+GH_HANDLE). +For new members of the @cisagov/dotgov team looking to contribute to the registrar, please open a [dev onboarding ticket](https://github.com/cisagov/getgov/issues/new?assignees=loganmeetsworld&labels=dev%2C+onboarding&template=developer-onboarding.md&title=Developer+Onboarding%3A+GH_HANDLE) or a [designer onboarding ticket](https://github.com/cisagov/getgov/issues/new?assignees=loganmeetsworld&labels=design%2C+onboarding&template=designer-onboarding.md&title=Designer+Onboarding%3A+GH_HANDLE). ## Code From 4297ed0d9d0043156f91a3e6203d8ff2e5197a96 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Thu, 11 Apr 2024 05:41:19 -0400 Subject: [PATCH 32/80] made css changes --- src/registrar/assets/sass/_theme/_admin.scss | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_admin.scss b/src/registrar/assets/sass/_theme/_admin.scss index d4eeaf990..27fce0136 100644 --- a/src/registrar/assets/sass/_theme/_admin.scss +++ b/src/registrar/assets/sass/_theme/_admin.scss @@ -539,10 +539,11 @@ address.dja-address-contact-list { .dja-status-list { border-top: solid 1px var(--border-color); margin-left: 0 !important; + margin-top: 10px; padding-left: 0 !important; - padding-top: 15px; + padding-top: 10px; li { - line-height: 1.15; + line-height: 1.5; font-family: "Source Sans Pro Web", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif !important; } } From 8714c1c4d6a71df889f1c1c3e58223dd8177c284 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 11 Apr 2024 08:08:17 -0600 Subject: [PATCH 33/80] Update docs/operations/data_migration.md Co-authored-by: Alysia Broddrick <109625347+abroddrick@users.noreply.github.com> --- docs/operations/data_migration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/operations/data_migration.md b/docs/operations/data_migration.md index e40a1ee50..59c2ad52c 100644 --- a/docs/operations/data_migration.md +++ b/docs/operations/data_migration.md @@ -588,7 +588,7 @@ Example: `cf ssh getgov-za` | 1 | **debug** | Increases logging detail. Defaults to False. | -## Populate First Ready +## Populate Organization type This section outlines how to run the `populate_organization_type` script. ### Running on sandboxes From c9e3948b04ccfc50e12c2e28adbfa4aee9d807d7 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 11 Apr 2024 09:13:56 -0600 Subject: [PATCH 34/80] PR suggestions --- docs/operations/data_migration.md | 31 +++++++-- .../commands/populate_organization_type.py | 64 +++++++++++++------ 2 files changed, 70 insertions(+), 25 deletions(-) diff --git a/docs/operations/data_migration.md b/docs/operations/data_migration.md index 59c2ad52c..4bfe37174 100644 --- a/docs/operations/data_migration.md +++ b/docs/operations/data_migration.md @@ -589,13 +589,22 @@ Example: `cf ssh getgov-za` ## Populate Organization type -This section outlines how to run the `populate_organization_type` script. +This section outlines how to run the `populate_organization_type` script. +The script is used to update the organization_type field on DomainRequest and DomainInformation when it is None. +That data are synthesized from the generic_org_type field and the is_election_board field ### Running on sandboxes #### Step 1: Login to CloudFoundry ```cf login -a api.fr.cloud.gov --sso``` +#### Step 2: Get the domain_election_board file +The latest domain_election_board csv can be found [here](https://drive.google.com/file/d/1aDeCqwHmBnXBl2arvoFCN0INoZmsEGsQ/view). +After downloading this file, place it in `src/migrationdata` + +#### Step 2: Upload the domain_election_board file to your sandbox +Follow [Step 1: Transfer data to sandboxes](#step-1-transfer-data-to-sandboxes) and [Step 2: Transfer uploaded files to the getgov directory](#step-2-transfer-uploaded-files-to-the-getgov-directory) from the [Set Up Migrations on Sandbox](#set-up-migrations-on-sandbox) portion of this doc. + #### Step 2: SSH into your environment ```cf ssh getgov-{space}``` @@ -605,9 +614,9 @@ Example: `cf ssh getgov-za` ```/tmp/lifecycle/shell``` #### Step 4: Running the script -```./manage.py populate_organization_type {domain_election_office_filename} --debug``` +```./manage.py populate_organization_type {domain_election_board_filename} --debug``` -- The domain_election_office_filename file must adhere to this format: +- The domain_election_board_filename file must adhere to this format: - example.gov\ example2.gov\ example3.gov @@ -616,17 +625,25 @@ Example: `./manage.py populate_organization_type migrationdata/election-domains.csv --debug` ### Running locally -```docker-compose exec app ./manage.py populate_organization_type {domain_election_office_filename} --debug``` + +#### Step 1: Get the domain_election_board file +The latest domain_election_board csv can be found [here](https://drive.google.com/file/d/1aDeCqwHmBnXBl2arvoFCN0INoZmsEGsQ/view). +After downloading this file, place it in `src/migrationdata` + + +#### Step 2: Running the script +```docker-compose exec app ./manage.py populate_organization_type {domain_election_board_filename} --debug``` Example (assuming that this is being ran from src/): `docker-compose exec app ./manage.py populate_organization_type migrationdata/election-domains.csv --debug` -##### Required parameters + +### Required parameters | | Parameter | Description | |:-:|:------------------------------------|:-------------------------------------------------------------------| -| 1 | **domain_election_office_filename** | A file containing every domain that is an election office. +| 1 | **domain_election_board_filename** | A file containing every domain that is an election office. -##### Optional parameters +### Optional parameters | | Parameter | Description | |:-:|:-------------------------- |:----------------------------------------------------------------------------| | 1 | **debug** | Increases logging detail. Defaults to False. \ No newline at end of file diff --git a/src/registrar/management/commands/populate_organization_type.py b/src/registrar/management/commands/populate_organization_type.py index a0a1c8633..67fc9397e 100644 --- a/src/registrar/management/commands/populate_organization_type.py +++ b/src/registrar/management/commands/populate_organization_type.py @@ -11,7 +11,11 @@ logger = logging.getLogger(__name__) class Command(BaseCommand): - help = "Loops through each valid DomainInformation and DomainRequest object and updates its organization_type value" + help = ( + "Loops through each valid DomainInformation and DomainRequest object and updates its organization_type value. " + "A valid DomainInformation/DomainRequest in this sense is one that has the value None for organization_type. " + "In other words, we populate the organization_type field if it is not already populated." + ) def __init__(self): super().__init__() @@ -26,34 +30,28 @@ class Command(BaseCommand): self.di_skipped: List[DomainInformation] = [] # Define a global variable for all domains with election offices - self.domains_with_election_offices_set = set() + self.domains_with_election_boards_set = set() def add_arguments(self, parser): """Adds command line arguments""" parser.add_argument("--debug", action=argparse.BooleanOptionalAction) parser.add_argument( - "domain_election_office_filename", + "domain_election_board_filename", help=("A file that contains" " all the domains that are election offices."), ) - def handle(self, domain_election_office_filename, **kwargs): + def handle(self, domain_election_board_filename, **kwargs): """Loops through each valid Domain object and updates its first_created value""" debug = kwargs.get("debug") # Check if the provided file path is valid - if not os.path.isfile(domain_election_office_filename): - raise argparse.ArgumentTypeError(f"Invalid file path '{domain_election_office_filename}'") + if not os.path.isfile(domain_election_board_filename): + raise argparse.ArgumentTypeError(f"Invalid file path '{domain_election_board_filename}'") - with open(domain_election_office_filename, "r") as file: - for line in file: - # Remove any leading/trailing whitespace - domain = line.strip() - if domain not in self.domains_with_election_offices_set: - self.domains_with_election_offices_set.add(domain) + # Read the election office csv + self.read_election_board_file(domain_election_board_filename) - domain_requests = DomainRequest.objects.filter( - organization_type__isnull=True, requested_domain__name__isnull=False - ) + domain_requests = DomainRequest.objects.filter(organization_type__isnull=True) # Code execution will stop here if the user prompts "N" TerminalHelper.prompt_for_execution( @@ -88,12 +86,37 @@ class Command(BaseCommand): self.update_domain_informations(domain_infos, debug) + def read_election_board_file(self, domain_election_board_filename): + """ + Reads the election board file and adds each parsed domain to self.domains_with_election_boards_set. + As previously implied, this file contains information about Domains which have election boards. + + The file must adhere to this format: + ``` + domain1.gov + domain2.gov + domain3.gov + ``` + (and so on) + """ + with open(domain_election_board_filename, "r") as file: + for line in file: + # Remove any leading/trailing whitespace + domain = line.strip() + if domain not in self.domains_with_election_boards_set: + self.domains_with_election_boards_set.add(domain) + def update_domain_requests(self, domain_requests, debug): + """ + Updates the organization_type for a list of DomainRequest objects using the `sync_organization_type` function. + Results are then logged. + Debug mode provides detailed logging. + """ for request in domain_requests: try: if request.generic_org_type is not None: domain_name = request.requested_domain.name - request.is_election_board = domain_name in self.domains_with_election_offices_set + request.is_election_board = domain_name in self.domains_with_election_boards_set request = self.sync_organization_type(DomainRequest, request) self.request_to_update.append(request) @@ -120,11 +143,16 @@ class Command(BaseCommand): ) def update_domain_informations(self, domain_informations, debug): + """ + Updates the organization_type for a list of DomainInformation objects using the `sync_organization_type` function. + Results are then logged. + Debug mode provides detailed logging. + """ for info in domain_informations: try: if info.generic_org_type is not None: domain_name = info.domain.name - info.is_election_board = domain_name in self.domains_with_election_offices_set + info.is_election_board = domain_name in self.domains_with_election_boards_set info = self.sync_organization_type(DomainInformation, info) self.di_to_update.append(info) if debug: @@ -168,7 +196,7 @@ class Command(BaseCommand): election_org_map = DomainRequest.OrgChoicesElectionOffice.get_org_election_to_org_generic() # Manages the "organization_type" variable and keeps in sync with - # "is_election_office" and "generic_organization_type" + # "is_election_board" and "generic_organization_type" org_type_helper = CreateOrUpdateOrganizationTypeHelper( sender=sender, instance=instance, From c0f83f7696a1057a71fa284b34b081a1c751e47d Mon Sep 17 00:00:00 2001 From: Kristina Yin <140533113+kristinacyin@users.noreply.github.com> Date: Thu, 11 Apr 2024 08:18:55 -0700 Subject: [PATCH 35/80] Update README.md Co-authored-by: Alysia Broddrick <109625347+abroddrick@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 545b64f99..4fa110464 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ The .gov domain helps U.S.-based government organizations gain public trust by b ## Onboarding -For new members of the @cisagov/dotgov team looking to contribute to the registrar, please open a [dev onboarding ticket](https://github.com/cisagov/getgov/issues/new?assignees=loganmeetsworld&labels=dev%2C+onboarding&template=developer-onboarding.md&title=Developer+Onboarding%3A+GH_HANDLE) or a [designer onboarding ticket](https://github.com/cisagov/getgov/issues/new?assignees=loganmeetsworld&labels=design%2C+onboarding&template=designer-onboarding.md&title=Designer+Onboarding%3A+GH_HANDLE). +For new members of the @cisagov/dotgov team looking to contribute to the registrar, please open a [developer onboarding ticket](https://github.com/cisagov/getgov/issues/new?assignees=loganmeetsworld&labels=dev%2C+onboarding&template=developer-onboarding.md&title=Developer+Onboarding%3A+GH_HANDLE) or a [designer onboarding ticket](https://github.com/cisagov/getgov/issues/new?assignees=loganmeetsworld&labels=design%2C+onboarding&template=designer-onboarding.md&title=Designer+Onboarding%3A+GH_HANDLE). ## Code From 09c0ad59cd388cfda86a5b90e18768c6bf7f233c Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 11 Apr 2024 09:25:08 -0600 Subject: [PATCH 36/80] Linting --- .../management/commands/populate_organization_type.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/registrar/management/commands/populate_organization_type.py b/src/registrar/management/commands/populate_organization_type.py index 67fc9397e..8428beb82 100644 --- a/src/registrar/management/commands/populate_organization_type.py +++ b/src/registrar/management/commands/populate_organization_type.py @@ -144,9 +144,9 @@ class Command(BaseCommand): def update_domain_informations(self, domain_informations, debug): """ - Updates the organization_type for a list of DomainInformation objects using the `sync_organization_type` function. + Updates the organization_type for a list of DomainInformation objects + using the `sync_organization_type` function. Results are then logged. - Debug mode provides detailed logging. """ for info in domain_informations: try: From 217604d753931b737e8ecfc863bf63b6138fcf4c Mon Sep 17 00:00:00 2001 From: Kristina Yin <140533113+kristinacyin@users.noreply.github.com> Date: Thu, 11 Apr 2024 09:10:53 -0700 Subject: [PATCH 37/80] Update designer-onboarding.md --- .github/ISSUE_TEMPLATE/designer-onboarding.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/designer-onboarding.md b/.github/ISSUE_TEMPLATE/designer-onboarding.md index 0921728ae..2a620a1f0 100644 --- a/.github/ISSUE_TEMPLATE/designer-onboarding.md +++ b/.github/ISSUE_TEMPLATE/designer-onboarding.md @@ -37,7 +37,7 @@ Welcome to the .gov team! We're excited to have you here. Please follow the step - [ ] FOR CONTRACTORS: Check with your manager on your EOD clearance process. - [ ] OPTIONAL: Request access to our [analytics tools](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit?pli=1#heading=h.9q334hs4lbks). - [ ] OPTIONAL: If you would like to manage content updates by running code locally rather than through GitHub, follow the steps in the [dev onboarding ticket](https://github.com/cisagov/getgov/issues/new?assignees=loganmeetsworld&labels=dev%2C+onboarding&template=developer-onboarding.md&title=Developer+Onboarding%3A+GH_HANDLE). - - [ ] If you would like to run code locally on a CISA laptop, reference the "Setting up a developer environment on CISA laptops" section of the onboarding doc. + - [ ] If you would like to run code locally on a CISA laptop, reference the "[Setting up a developer environment on CISA laptops](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit#heading=h.2ctyba51d1zp)" section of the onboarding doc. ### Access From 0792d68608dffcb1f230f14f130fd29abd08a24c Mon Sep 17 00:00:00 2001 From: Kristina Yin <140533113+kristinacyin@users.noreply.github.com> Date: Thu, 11 Apr 2024 09:20:06 -0700 Subject: [PATCH 38/80] Update designer-onboarding.md --- .github/ISSUE_TEMPLATE/designer-onboarding.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/designer-onboarding.md b/.github/ISSUE_TEMPLATE/designer-onboarding.md index 2a620a1f0..0296b653d 100644 --- a/.github/ISSUE_TEMPLATE/designer-onboarding.md +++ b/.github/ISSUE_TEMPLATE/designer-onboarding.md @@ -26,7 +26,7 @@ Welcome to the .gov team! We're excited to have you here. Please follow the step - [ ] FOR FEDERAL EMPLOYEES: Create a [Google workspace enterprise account](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit?pli=1#heading=h.xowzg9w0qlis) for CISA. - [ ] Get access to our [Project Folder](https://drive.google.com/drive/folders/1qkoFQBlzXA7axi9CZ_OBhlJqRcqlNfpW?usp=drive_link) on Google Drive. - [ ] Explore the folders and docs. Designers interface with the Product Design, Content, and Research folders most often. -- [ ] Make sure you have been invited to our [team meetings](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit#heading=h.h62kzew057p1) on Google Meet. +- [ ] Make sure you have been invited to our [team meetings](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit#heading=h.emgtp2hgvabr) on Google Meet. - [ ] Review our [design tools](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit?pli=1#heading=h.aprurp3z4gmv). - [ ] Accept invitation to our [Figma workspace](https://www.figma.com/files/1287135731043703282/team/1299882813146449644). - [ ] Follow the steps in [preparing for your sandbox](https://docs.google.com/document/d/1ukbpW4LSqkb_CCt8LWfpehP03qqfyYfvK3Fl21NaEq8/edit?pli=1#heading=h.au66hq5e0l8s) section on the onboarding doc. @@ -54,7 +54,7 @@ By following the steps, you should have access / been added to the following: ### Steps for the onboarder - [ ] Make sure onboardee is given an invitation to our Slack workspace - [ ] Add onboardee to dotgov Slack channels -- [ ] Add onboardee is added to Project Folder as a "Contributor" (or if you do not have permissions, confirm with Cameron) +- [ ] Add onboardee to Project Folder as a "Contributor" (or if you do not have permissions, confirm with Cameron) - If onboardee if a federal employee, add them as a "Content Manager" instead - [ ] Once onboardee has been granted a Figma license, add them as an editor to the Figma team workspace, Otherwise, you can add them as a viewer in the meantime. - [ ] Add onboardee to our team meetings From 41406a76ea54364b5cf2dead56425d2e16048dd2 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Thu, 11 Apr 2024 13:41:01 -0400 Subject: [PATCH 39/80] aligned spacing --- src/registrar/assets/sass/_theme/_admin.scss | 3 ++- .../templates/django/admin/includes/contact_detail_list.html | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_admin.scss b/src/registrar/assets/sass/_theme/_admin.scss index 27fce0136..4b69dc8e3 100644 --- a/src/registrar/assets/sass/_theme/_admin.scss +++ b/src/registrar/assets/sass/_theme/_admin.scss @@ -539,12 +539,13 @@ address.dja-address-contact-list { .dja-status-list { border-top: solid 1px var(--border-color); margin-left: 0 !important; - margin-top: 10px; padding-left: 0 !important; padding-top: 10px; li { line-height: 1.5; font-family: "Source Sans Pro Web", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif !important; + padding-top: 0; + padding-bottom: 0; } } diff --git a/src/registrar/templates/django/admin/includes/contact_detail_list.html b/src/registrar/templates/django/admin/includes/contact_detail_list.html index 8ad6fb96d..b5ccb5a42 100644 --- a/src/registrar/templates/django/admin/includes/contact_detail_list.html +++ b/src/registrar/templates/django/admin/includes/contact_detail_list.html @@ -1,6 +1,6 @@ {% load i18n static %} -
        +
        {% if show_formatted_name %} {% if contact.get_formatted_name %} From 1d6c06208d4165aed78016013655561dffc70a2b Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 11 Apr 2024 15:50:34 -0600 Subject: [PATCH 40/80] Fix redirect --- src/registrar/admin.py | 14 ++++++-------- .../admin/includes/detail_table_fieldset.html | 4 +++- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 74c6bc070..8e5d40c05 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -805,15 +805,13 @@ class WebsiteAdmin(ListHeaderAdmin): """ superuser_perm = request.user.has_perm("registrar.full_access_permission") analyst_perm = request.user.has_perm("registrar.analyst_access_permission") - + return_path = request.GET.get('return_path') # Don't redirect to the website page on save if the user is an analyst. - # Rather, just redirect back to the same change page. - if analyst_perm and not superuser_perm: - opts = obj._meta - pk_value = obj._get_pk_val() - return HttpResponseRedirect( - reverse("admin:%s_%s_change" % (opts.app_label, opts.model_name), args=(pk_value,)) - ) + # Rather, just redirect back to the originating page. + if (analyst_perm and not superuser_perm) and return_path: + # Redirect to the return path if it exists + return HttpResponseRedirect(return_path) + return super().response_change(request, obj) diff --git a/src/registrar/templates/django/admin/includes/detail_table_fieldset.html b/src/registrar/templates/django/admin/includes/detail_table_fieldset.html index a0a679290..96d9eb547 100644 --- a/src/registrar/templates/django/admin/includes/detail_table_fieldset.html +++ b/src/registrar/templates/django/admin/includes/detail_table_fieldset.html @@ -49,9 +49,11 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html)

    {% elif field.field.name == "alternative_domains" %}
    + {% with current_path=request.get_full_path %} {% for alt_domain in original.alternative_domains.all %} - {{ alt_domain }}{% if not forloop.last %}, {% endif %} + {{ alt_domain }}{% if not forloop.last %}, {% endif %} {% endfor %} + {% endwith %}
    {% else %}
    {{ field.contents }}
    From 61b71c298ebb230a3fa2996b4b1c3fa2dc6f77b4 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 11 Apr 2024 16:05:42 -0600 Subject: [PATCH 41/80] Fix minor bug --- src/registrar/admin.py | 18 ++++++++---------- .../admin/includes/detail_table_fieldset.html | 4 ++++ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 8e5d40c05..bce9087be 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -801,7 +801,7 @@ class WebsiteAdmin(ListHeaderAdmin): def response_change(self, request, obj): """ - Override to redirect users back to the same page after saving. + Override to redirect users back to the previous page after saving. """ superuser_perm = request.user.has_perm("registrar.full_access_permission") analyst_perm = request.user.has_perm("registrar.analyst_access_permission") @@ -1956,19 +1956,17 @@ class DraftDomainAdmin(ListHeaderAdmin): def response_change(self, request, obj): """ - Override to redirect users back to the same page after saving. + Override to redirect users back to the previous page after saving. """ superuser_perm = request.user.has_perm("registrar.full_access_permission") analyst_perm = request.user.has_perm("registrar.analyst_access_permission") - + return_path = request.GET.get('return_path') # Don't redirect to the website page on save if the user is an analyst. - # Rather, just redirect back to the same change page. - if analyst_perm and not superuser_perm: - opts = obj._meta - pk_value = obj._get_pk_val() - return HttpResponseRedirect( - reverse("admin:%s_%s_change" % (opts.app_label, opts.model_name), args=(pk_value,)) - ) + # Rather, just redirect back to the originating page. + if (analyst_perm and not superuser_perm) and return_path: + # Redirect to the return path if it exists + return HttpResponseRedirect(return_path) + return super().response_change(request, obj) diff --git a/src/registrar/templates/django/admin/includes/detail_table_fieldset.html b/src/registrar/templates/django/admin/includes/detail_table_fieldset.html index 96d9eb547..3e832922d 100644 --- a/src/registrar/templates/django/admin/includes/detail_table_fieldset.html +++ b/src/registrar/templates/django/admin/includes/detail_table_fieldset.html @@ -27,6 +27,10 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html) {% endif %} + {% elif field.field.name == "requested_domain" %} + {% with current_path=request.get_full_path %} + {{ original.requested_domain }} + {% endwith%} {% elif field.field.name == "current_websites" %} {% comment %} The "website" model is essentially just a text field. From 5e15490d2c4a222f104877b62ad8dfdac0f2c788 Mon Sep 17 00:00:00 2001 From: Alysia Broddrick <109625347+abroddrick@users.noreply.github.com> Date: Thu, 11 Apr 2024 16:09:54 -0700 Subject: [PATCH 42/80] Apply suggestions from code review Co-authored-by: zandercymatics <141044360+zandercymatics@users.noreply.github.com> --- docs/developer/README.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/developer/README.md b/docs/developer/README.md index 29bc0a530..9421d5856 100644 --- a/docs/developer/README.md +++ b/docs/developer/README.md @@ -79,7 +79,6 @@ The endpoint /admin can be used to view and manage site content, including but n While on production (the sandbox referred to as `stable`), an existing analyst or full-access user typically grants access /admin as part of onboarding ([see these instructions](../django-admin/roles.md)), doing this for all development sandboxes is very time consuming. Instead, to get access to /admin on all development sandboxes and when developing code locally, refer to the following sections depending on what level of user access you desire. -Note: this process where a team member gets analyst/superuser permission on all non-production sandboxes is often called "Adding to fixtures". ### Adding full-access user to /admin @@ -88,7 +87,7 @@ Note: this process where a team member gets analyst/superuser permission on all 1. Login via login.gov 2. Go to the home page and make sure you can see the part where you can submit a domain request 3. Go to /admin and it will tell you that your UUID is not authorized (it shows a very long string, this is your UUID). Copy that UUID for use in 4. -4. (Designers)- message in #getgov-dev that you need access to admin as a `superuser` and send them this UUID along with your desired email address. Please see the "Adding an Analyst to /admin" section below to complete similiar steps if you also desire an `analyst` user account. Engineers will handle the remaining steps for designers, stop here. +4. (Designers) Message in #getgov-dev that you need access to admin as a `superuser` and send them this UUID along with your desired email address. Please see the "Adding an Analyst to /admin" section below to complete similiar steps if you also desire an `analyst` user account. Engineers will handle the remaining steps for designers, stop here. (Engineers) In src/registrar/fixtures_users.py add to the `ADMINS` list in that file by adding your UUID as your username along with your first and last name. See below: @@ -103,8 +102,8 @@ Note: this process where a team member gets analyst/superuser permission on all ] ``` -5. (Engineer only) In the browser, navigate to /admin. To verify that all is working correctly, under "domain requests" you should see fake domains with various fake statuses. -6. (Engineer only) Add an optional email key/value pair +5. (Engineers) In the browser, navigate to /admin. To verify that all is working correctly, under "domain requests" you should see fake domains with various fake statuses. +6. (Engineers) Add an optional email key/value pair ### Adding an analyst-level user to /admin Analysts are a variant of the admin role with limited permissions. The process for adding an Analyst is much the same as adding an admin: @@ -112,9 +111,9 @@ Analysts are a variant of the admin role with limited permissions. The process f 1. Login via login.gov (if you already exist as an admin, you will need to create a separate login.gov account for this: i.e. first.last+1@email.com) 2. Go to the home page and make sure you can see the part where you can submit a domain request 3. Go to /admin and it will tell you that UUID is not authorized, copy that UUID for use in 4 (this will be a different UUID than the one obtained from creating an admin) -4. (Designers)- message in #getgov-dev that you need access to admin as a `superuser` and send them this UUID along with your desired email address. Please see the "Adding an Analyst to /admin" section below to complete similiar steps if you also desire an `analyst` user account. Engineers will handle the remaining steps for designers, stop here. +4. (Designers) Message in #getgov-dev that you need access to admin as a `superuser` and send them this UUID along with your desired email address. Engineers will handle the remaining steps for designers, stop here. -(Engineers) In src/registrar/fixtures_users.py add to the `STAFF` list in that file by adding your UUID as your username along with your first and last name. See below: +5. (Engineers) In src/registrar/fixtures_users.py add to the `STAFF` list in that file by adding your UUID as your username along with your first and last name. See below: ``` STAFF = [ @@ -127,8 +126,8 @@ Analysts are a variant of the admin role with limited permissions. The process f ] ``` -5. (Engineer only) In the browser, navigate to /admin. To verify that all is working correctly, verify that you can only see a sub-section of the modules and some are set to view-only. -6. (Engineer only) Add an optional email key/value pair +5. (Engineers) In the browser, navigate to /admin. To verify that all is working correctly, verify that you can only see a sub-section of the modules and some are set to view-only. +6. (Engineers) Add an optional email key/value pair Do note that if you wish to have both an analyst and admin account, append `-Analyst` to your first and last name, or use a completely different first/last name to avoid confusion. Example: `Bob-Analyst` From 53d5c3bd61afd0fd48b16c34f5ac23de08221ce9 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 12 Apr 2024 08:39:04 -0600 Subject: [PATCH 43/80] Propagate success message --- src/registrar/admin.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index bce9087be..b3248a96f 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -805,14 +805,19 @@ class WebsiteAdmin(ListHeaderAdmin): """ superuser_perm = request.user.has_perm("registrar.full_access_permission") analyst_perm = request.user.has_perm("registrar.analyst_access_permission") - return_path = request.GET.get('return_path') + return_path = request.GET.get("return_path") + + # First, call the super method to perform the standard operations and capture the response + response = super().response_change(request, obj) + # Don't redirect to the website page on save if the user is an analyst. # Rather, just redirect back to the originating page. if (analyst_perm and not superuser_perm) and return_path: # Redirect to the return path if it exists return HttpResponseRedirect(return_path) - return super().response_change(request, obj) + # If no redirection is needed, return the original response + return response class UserDomainRoleAdmin(ListHeaderAdmin): @@ -1960,14 +1965,19 @@ class DraftDomainAdmin(ListHeaderAdmin): """ superuser_perm = request.user.has_perm("registrar.full_access_permission") analyst_perm = request.user.has_perm("registrar.analyst_access_permission") - return_path = request.GET.get('return_path') + return_path = request.GET.get("return_path") + + # First, call the super method to perform the standard operations and capture the response + response = super().response_change(request, obj) + # Don't redirect to the website page on save if the user is an analyst. # Rather, just redirect back to the originating page. if (analyst_perm and not superuser_perm) and return_path: # Redirect to the return path if it exists return HttpResponseRedirect(return_path) - return super().response_change(request, obj) + # If no redirection is needed, return the original response + return response class PublicContactAdmin(ListHeaderAdmin): From 1fa0e63126eb6f2052a02d2ff1209939f544a9ff Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Fri, 12 Apr 2024 10:47:12 -0400 Subject: [PATCH 44/80] simplified contact detail list template --- src/registrar/models/contact.py | 8 ++++++++ src/registrar/models/user.py | 8 ++++++++ .../django/admin/includes/contact_detail_list.html | 4 ++-- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/registrar/models/contact.py b/src/registrar/models/contact.py index d3de5a293..0b097974e 100644 --- a/src/registrar/models/contact.py +++ b/src/registrar/models/contact.py @@ -93,6 +93,14 @@ class Contact(TimeStampedModel): names = [n for n in [self.first_name, self.middle_name, self.last_name] if n] return " ".join(names) if names else "Unknown" + def has_contact_info(self): + has_contact_info = ( + self.title or + self.email or + self.phone + ) + return has_contact_info + def save(self, *args, **kwargs): # Call the parent class's save method to perform the actual save super().save(*args, **kwargs) diff --git a/src/registrar/models/user.py b/src/registrar/models/user.py index 1ca2412ca..7e9cdf73e 100644 --- a/src/registrar/models/user.py +++ b/src/registrar/models/user.py @@ -92,6 +92,14 @@ class User(AbstractUser): """Return count of ineligible requests""" return self.domain_requests_created.filter(status=DomainRequest.DomainRequestStatus.INELIGIBLE).count() + def has_contact_info(self): + has_contact_info = ( + self.contact.title or + self.email.title or + self.contact.phone + ) + return has_contact_info + @classmethod def needs_identity_verification(cls, email, uuid): """A method used by our oidc classes to test whether a user needs email/uuid verification diff --git a/src/registrar/templates/django/admin/includes/contact_detail_list.html b/src/registrar/templates/django/admin/includes/contact_detail_list.html index b5ccb5a42..0ac9c4c49 100644 --- a/src/registrar/templates/django/admin/includes/contact_detail_list.html +++ b/src/registrar/templates/django/admin/includes/contact_detail_list.html @@ -1,6 +1,6 @@ {% load i18n static %} -
    +
    {% if show_formatted_name %} {% if contact.get_formatted_name %} @@ -10,7 +10,7 @@ {% endif %} {% endif %} - {% if user.title or user.contact.title or user.email or user.contact.email or user.phone or user.contact.phone %} + {% if user.has_contact_info %} {# Title #} {% if user.title or user.contact.title %} {% if user.contact.title %} From ed0ffe2962e837f5853619faafb2ee80c971494e Mon Sep 17 00:00:00 2001 From: Kristina Yin <140533113+kristinacyin@users.noreply.github.com> Date: Fri, 12 Apr 2024 08:22:38 -0700 Subject: [PATCH 45/80] Update designer-onboarding.md --- .github/ISSUE_TEMPLATE/designer-onboarding.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/designer-onboarding.md b/.github/ISSUE_TEMPLATE/designer-onboarding.md index 0296b653d..461850b60 100644 --- a/.github/ISSUE_TEMPLATE/designer-onboarding.md +++ b/.github/ISSUE_TEMPLATE/designer-onboarding.md @@ -56,6 +56,7 @@ By following the steps, you should have access / been added to the following: - [ ] Add onboardee to dotgov Slack channels - [ ] Add onboardee to Project Folder as a "Contributor" (or if you do not have permissions, confirm with Cameron) - If onboardee if a federal employee, add them as a "Content Manager" instead -- [ ] Once onboardee has been granted a Figma license, add them as an editor to the Figma team workspace, Otherwise, you can add them as a viewer in the meantime. +- [ ] Coordinate with Cameron to grant onboardee a Figma license +- [ ] Add onboardee as an editor to the Figma team workspace, If they do not have a license (yet), you can add them as a viewer. - [ ] Add onboardee to our team meetings - [ ] If applicable, invite onboardee to Google Analytics, Google Search Console, and Search.gov console From 673c092ea9f9f8e6fda5b893e2fe679a2ca7b085 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Fri, 12 Apr 2024 11:50:24 -0400 Subject: [PATCH 46/80] updated tests --- src/registrar/models/contact.py | 7 +------ src/registrar/models/user.py | 7 +------ src/registrar/tests/test_models.py | 20 ++++++++++++++++++++ 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/registrar/models/contact.py b/src/registrar/models/contact.py index 0b097974e..c5cb03a6e 100644 --- a/src/registrar/models/contact.py +++ b/src/registrar/models/contact.py @@ -94,12 +94,7 @@ class Contact(TimeStampedModel): return " ".join(names) if names else "Unknown" def has_contact_info(self): - has_contact_info = ( - self.title or - self.email or - self.phone - ) - return has_contact_info + return bool(self.title or self.email or self.phone) def save(self, *args, **kwargs): # Call the parent class's save method to perform the actual save diff --git a/src/registrar/models/user.py b/src/registrar/models/user.py index 7e9cdf73e..2688ef57f 100644 --- a/src/registrar/models/user.py +++ b/src/registrar/models/user.py @@ -93,12 +93,7 @@ class User(AbstractUser): return self.domain_requests_created.filter(status=DomainRequest.DomainRequestStatus.INELIGIBLE).count() def has_contact_info(self): - has_contact_info = ( - self.contact.title or - self.email.title or - self.contact.phone - ) - return has_contact_info + return bool(self.contact.title or self.contact.email or self.contact.phone) @classmethod def needs_identity_verification(cls, email, uuid): diff --git a/src/registrar/tests/test_models.py b/src/registrar/tests/test_models.py index 3c8d8b05b..d6a561c7f 100644 --- a/src/registrar/tests/test_models.py +++ b/src/registrar/tests/test_models.py @@ -1137,6 +1137,16 @@ class TestUser(TestCase): ) self.assertEquals(self.user.get_ineligible_requests_count(), 1) + def test_has_contact_info(self): + """Test that has_contact_info properly returns""" + # test with a user with contact info defined + self.assertTrue(self.user.has_contact_info()) + # test with a user without contact info defined + self.user.contact.title = None + self.user.contact.email = None + self.user.contact.phone = None + self.assertFalse(self.user.has_contact_info()) + class TestContact(TestCase): def setUp(self): @@ -1238,3 +1248,13 @@ class TestContact(TestCase): # test for a contact which is assigned as an authorizing official on a domain request self.assertFalse(self.contact_as_ao.has_more_than_one_join("authorizing_official")) self.assertTrue(self.contact_as_ao.has_more_than_one_join("submitted_domain_requests")) + + def test_has_contact_info(self): + """Test that has_contact_info properly returns""" + # test with a contact with contact info defined + self.assertTrue(self.contact.has_contact_info()) + # test with a contact without contact info defined + self.contact.title = None + self.contact.email = None + self.contact.phone = None + self.assertFalse(self.contact.has_contact_info()) From 9e7f8785490fb0a5c5d0c82e1cdd06fb6127998d Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 12 Apr 2024 15:24:50 -0400 Subject: [PATCH 47/80] allow clanking of first and or second nameserver if enough entries --- src/registrar/forms/domain.py | 36 +++++++++++--- src/registrar/tests/test_views_domain.py | 62 ++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 7 deletions(-) diff --git a/src/registrar/forms/domain.py b/src/registrar/forms/domain.py index 7b0ac2956..5b70d3e9b 100644 --- a/src/registrar/forms/domain.py +++ b/src/registrar/forms/domain.py @@ -83,25 +83,34 @@ class DomainNameserverForm(forms.Form): # after clean_fields. it is used to determine form level errors. # is_valid is typically called from view during a post cleaned_data = super().clean() + self.clean_empty_strings(cleaned_data) + server = cleaned_data.get("server", "") - # remove ANY spaces in the server field - server = server.replace(" ", "") - # lowercase the server - server = server.lower() + server = server.replace(" ", "").lower() cleaned_data["server"] = server - ip = cleaned_data.get("ip", None) - # remove ANY spaces in the ip field + + ip = cleaned_data.get("ip", "") ip = ip.replace(" ", "") cleaned_data["ip"] = ip + domain = cleaned_data.get("domain", "") ip_list = self.extract_ip_list(ip) - # validate if the form has a server or an ip + # Capture the server_value + server_value = self.cleaned_data["server"] + + # Validate if the form has a server or an ip if (ip and ip_list) or server: self.validate_nameserver_ip_combo(domain, server, ip_list) + # Re-set the server value: + # add_error which is called on validate_nameserver_ip_combo will clean-up (delete) any invalid data. + # We need that data because we need to know the total server entries (even if invalid) in the formset + # clean method where we determine whether a blank first and/or second entry should throw a required error. + self.cleaned_data["server"] = server_value + return cleaned_data def clean_empty_strings(self, cleaned_data): @@ -149,6 +158,19 @@ class BaseNameserverFormset(forms.BaseFormSet): """ Check for duplicate entries in the formset. """ + + # Check if there are at least two valid servers + valid_servers_count = sum( + 1 for form in self.forms if form.cleaned_data.get("server") and form.cleaned_data.get("server").strip() + ) + if valid_servers_count >= 2: + # If there are, remove the "At least two name servers are required" error from each form + # This will allow for successful submissions when the first or second entries are blanked + # but there are enough entries total + for form in self.forms: + if form.errors.get("server") == ["At least two name servers are required."]: + form.errors.pop("server") + if any(self.errors): # Don't bother validating the formset unless each form is valid on its own return diff --git a/src/registrar/tests/test_views_domain.py b/src/registrar/tests/test_views_domain.py index 064c5efdb..ba229fc10 100644 --- a/src/registrar/tests/test_views_domain.py +++ b/src/registrar/tests/test_views_domain.py @@ -974,6 +974,68 @@ class TestDomainNameservers(TestDomainOverview): page = result.follow() self.assertContains(page, "The name servers for this domain have been updated") + def test_domain_nameservers_can_blank_out_first_or_second_one_if_enough_entries(self): + """Nameserver form submits successfully with 2 valid inputs, even if the first or + second entries are blanked out. + + Uses self.app WebTest because we need to interact with forms. + """ + + nameserver1 = "" + nameserver2 = "ns2.igorville.gov" + nameserver3 = "ns3.igorville.gov" + valid_ip = "" + valid_ip_2 = "128.0.0.2" + valid_ip_3 = "128.0.0.3" + nameservers_page = self.app.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id})) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + nameservers_page.form["form-0-server"] = nameserver1 + nameservers_page.form["form-0-ip"] = valid_ip + nameservers_page.form["form-1-server"] = nameserver2 + nameservers_page.form["form-1-ip"] = valid_ip_2 + nameservers_page.form["form-2-server"] = nameserver3 + nameservers_page.form["form-2-ip"] = valid_ip_3 + with less_console_noise(): # swallow log warning message + result = nameservers_page.form.submit() + + # form submission was a successful post, response should be a 302 + self.assertEqual(result.status_code, 302) + self.assertEqual( + result["Location"], + reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id}), + ) + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + nameservers_page = result.follow() + self.assertContains(nameservers_page, "The name servers for this domain have been updated") + + nameserver1 = "ns1.igorville.gov" + nameserver2 = "" + nameserver3 = "ns3.igorville.gov" + valid_ip = "128.0.0.1" + valid_ip_2 = "" + valid_ip_3 = "128.0.0.3" + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + nameservers_page.form["form-0-server"] = nameserver1 + nameservers_page.form["form-0-ip"] = valid_ip + nameservers_page.form["form-1-server"] = nameserver2 + nameservers_page.form["form-1-ip"] = valid_ip_2 + nameservers_page.form["form-2-server"] = nameserver3 + nameservers_page.form["form-2-ip"] = valid_ip_3 + with less_console_noise(): # swallow log warning message + result = nameservers_page.form.submit() + + # form submission was a successful post, response should be a 302 + self.assertEqual(result.status_code, 302) + self.assertEqual( + result["Location"], + reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id}), + ) + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + nameservers_page = result.follow() + self.assertContains(nameservers_page, "The name servers for this domain have been updated") + def test_domain_nameservers_form_invalid(self): """Nameserver form does not submit with invalid data. From 0a17f396cb534011518b12f57504bc0c5ea040a5 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 12 Apr 2024 15:44:40 -0400 Subject: [PATCH 48/80] UX bug fixes --- src/registrar/assets/js/get-gov.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js index 587b95305..b2ed71bea 100644 --- a/src/registrar/assets/js/get-gov.js +++ b/src/registrar/assets/js/get-gov.js @@ -530,7 +530,7 @@ function hideDeletedForms() { let isDotgovDomain = document.querySelector(".dotgov-domain-form"); // The Nameservers formset features 2 required and 11 optionals if (isNameserversForm) { - cloneIndex = 2; + // cloneIndex = 2; formLabel = "Name server"; // DNSSEC: DS Data } else if (isDsDataForm) { @@ -766,3 +766,21 @@ function toggleTwoDomElements(ele1, ele2, index) { } })(); +/** + * An IIFE that listens to the other contacts radio form on DAs and toggles the contacts/no other contacts forms + * + */ +(function otherContactsFormListener() { + let isNameserversForm = document.querySelector(".nameservers-form"); + if (isNameserversForm) { + let forms = document.querySelectorAll(".repeatable-form"); + if (forms.length < 3) { + // Hide the delete buttons on the 2 nameservers + forms.forEach((form, index) => { + Array.from(form.querySelectorAll('.delete-record')).forEach((deleteButton) => { + deleteButton.setAttribute("disabled", "true"); + }); + }); + } + } +})(); From 28177030210adbfd40d2ec25e9691c3e4b83fa95 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 12 Apr 2024 15:46:11 -0400 Subject: [PATCH 49/80] update IIFE definition --- src/registrar/assets/js/get-gov.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js index b2ed71bea..f2771e51c 100644 --- a/src/registrar/assets/js/get-gov.js +++ b/src/registrar/assets/js/get-gov.js @@ -767,10 +767,10 @@ function toggleTwoDomElements(ele1, ele2, index) { })(); /** - * An IIFE that listens to the other contacts radio form on DAs and toggles the contacts/no other contacts forms + * An IIFE that disables the delete buttons on nameserver forms on page load if < 3 forms * */ -(function otherContactsFormListener() { +(function nameserversFormListener() { let isNameserversForm = document.querySelector(".nameservers-form"); if (isNameserversForm) { let forms = document.querySelectorAll(".repeatable-form"); From 85bc9c272ede537a52ff283a039dc1b349959223 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 12 Apr 2024 15:47:47 -0400 Subject: [PATCH 50/80] cleanup --- src/registrar/assets/js/get-gov.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/assets/js/get-gov.js b/src/registrar/assets/js/get-gov.js index f2771e51c..b4c41ecf1 100644 --- a/src/registrar/assets/js/get-gov.js +++ b/src/registrar/assets/js/get-gov.js @@ -776,7 +776,7 @@ function toggleTwoDomElements(ele1, ele2, index) { let forms = document.querySelectorAll(".repeatable-form"); if (forms.length < 3) { // Hide the delete buttons on the 2 nameservers - forms.forEach((form, index) => { + forms.forEach((form) => { Array.from(form.querySelectorAll('.delete-record')).forEach((deleteButton) => { deleteButton.setAttribute("disabled", "true"); }); From c22438d2ad360a00c2a2542975e44a279b77d12b Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 12 Apr 2024 13:56:55 -0600 Subject: [PATCH 51/80] Adjust styling --- src/registrar/assets/sass/_theme/_admin.scss | 3 +++ .../templates/django/admin/domain_request_change_form.html | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_admin.scss b/src/registrar/assets/sass/_theme/_admin.scss index 4b69dc8e3..40ab682be 100644 --- a/src/registrar/assets/sass/_theme/_admin.scss +++ b/src/registrar/assets/sass/_theme/_admin.scss @@ -495,6 +495,8 @@ address.dja-address-contact-list { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; + // This has to be set here due to style overrides + font-size: medium; } } @@ -505,6 +507,7 @@ address.dja-address-contact-list { @media screen and (min-width:768px) { .visible-768 { display: block; + padding-top: 0; } } diff --git a/src/registrar/templates/django/admin/domain_request_change_form.html b/src/registrar/templates/django/admin/domain_request_change_form.html index 3b4fa7283..1640f0b68 100644 --- a/src/registrar/templates/django/admin/domain_request_change_form.html +++ b/src/registrar/templates/django/admin/domain_request_change_form.html @@ -116,8 +116,8 @@ -

    - Requested domain: {{ original.requested_domain.name }} +

    + Requested domain: {{ original.requested_domain.name }}

    {{ block.super }} From 34e2e82a0c7c00c4d3d71567647c452514a116cb Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 12 Apr 2024 14:34:48 -0600 Subject: [PATCH 52/80] Update test_admin.py --- src/registrar/tests/test_admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index bf54efe60..45c37ee23 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -1538,7 +1538,7 @@ class TestDomainRequestAdmin(MockEppLib): # Since we're using client to mock the request, we can only test against # non-interpolated values - expected_content = "Requested domain:" + expected_content = "Requested domain:" expected_content2 = '' expected_content3 = '
    ' self.assertContains(request, expected_content) From da149e479225f09cc3eeb59bcade74dbd2ff25dd Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 12 Apr 2024 14:38:48 -0600 Subject: [PATCH 53/80] Add padding --- src/registrar/assets/sass/_theme/_admin.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/registrar/assets/sass/_theme/_admin.scss b/src/registrar/assets/sass/_theme/_admin.scss index 40ab682be..ea49d7b8e 100644 --- a/src/registrar/assets/sass/_theme/_admin.scss +++ b/src/registrar/assets/sass/_theme/_admin.scss @@ -497,6 +497,7 @@ address.dja-address-contact-list { text-overflow: ellipsis; // This has to be set here due to style overrides font-size: medium; + padding-top: 3px; } } From 1b62ad558345ab26361c3227236f1fd3e8681add Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 12 Apr 2024 14:46:33 -0600 Subject: [PATCH 54/80] Fix test (pt.2) --- src/registrar/tests/test_admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index 45c37ee23..a9f9ab557 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -1509,7 +1509,7 @@ class TestDomainRequestAdmin(MockEppLib): # Since we're using client to mock the request, we can only test against # non-interpolated values - expected_content = "Requested domain:" + expected_content = "Requested domain:" expected_content2 = '' expected_content3 = '
    ' not_expected_content = "submit-row-wrapper--analyst-view>" From 27ef823b93d7cd7d3c60e21ea859fca11e21b0fc Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 12 Apr 2024 17:56:57 -0400 Subject: [PATCH 55/80] handle edge case of 0 and 1 empty and duplicate error --- src/registrar/forms/domain.py | 4 +- src/registrar/tests/common.py | 13 ++++++ src/registrar/tests/test_views_domain.py | 55 +++++++++++++++++++++++- 3 files changed, 68 insertions(+), 4 deletions(-) diff --git a/src/registrar/forms/domain.py b/src/registrar/forms/domain.py index 5b70d3e9b..165d32fd8 100644 --- a/src/registrar/forms/domain.py +++ b/src/registrar/forms/domain.py @@ -178,10 +178,10 @@ class BaseNameserverFormset(forms.BaseFormSet): data = [] duplicates = [] - for form in self.forms: + for index, form in enumerate(self.forms): if form.cleaned_data: value = form.cleaned_data["server"] - if value in data: + if value in data and not (form.cleaned_data.get("server", "").strip() == '' and index == 1): form.add_error( "server", NameserverError(code=nsErrorCodes.DUPLICATE_HOST, nameserver=value), diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index a0b0e774f..4b9461b65 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -1152,6 +1152,18 @@ class MockEppLib(TestCase): ], ) + infoDomainFourHosts = fakedEppObject( + "my-nameserver.gov", + cr_date=make_aware(datetime(2023, 5, 25, 19, 45, 35)), + contacts=[], + hosts=[ + "ns1.my-nameserver-1.com", + "ns1.my-nameserver-2.com", + "ns1.cats-are-superior3.com", + "ns1.explosive-chicken-nuggets.com", + ], + ) + infoDomainNoHost = fakedEppObject( "my-nameserver.gov", cr_date=make_aware(datetime(2023, 5, 25, 19, 45, 35)), @@ -1475,6 +1487,7 @@ class MockEppLib(TestCase): "namerserversubdomain.gov": (self.infoDomainCheckHostIPCombo, None), "freeman.gov": (self.InfoDomainWithContacts, None), "threenameserversDomain.gov": (self.infoDomainThreeHosts, None), + "fournameserversDomain.gov": (self.infoDomainFourHosts, None), "defaultsecurity.gov": (self.InfoDomainWithDefaultSecurityContact, None), "adomain2.gov": (self.InfoDomainWithVerisignSecurityContact, None), "defaulttechnical.gov": (self.InfoDomainWithDefaultTechnicalContact, None), diff --git a/src/registrar/tests/test_views_domain.py b/src/registrar/tests/test_views_domain.py index ba229fc10..eeab00ae3 100644 --- a/src/registrar/tests/test_views_domain.py +++ b/src/registrar/tests/test_views_domain.py @@ -5,7 +5,7 @@ from django.conf import settings from django.urls import reverse from django.contrib.auth import get_user_model -from .common import MockSESClient, create_user # type: ignore +from .common import MockEppLib, MockSESClient, create_user # type: ignore from django_webtest import WebTest # type: ignore import boto3_mocking # type: ignore @@ -727,7 +727,7 @@ class TestDomainManagers(TestDomainOverview): self.assertContains(home_page, self.domain.name) -class TestDomainNameservers(TestDomainOverview): +class TestDomainNameservers(TestDomainOverview, MockEppLib): def test_domain_nameservers(self): """Can load domain's nameservers page.""" page = self.client.get(reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id})) @@ -1036,6 +1036,57 @@ class TestDomainNameservers(TestDomainOverview): nameservers_page = result.follow() self.assertContains(nameservers_page, "The name servers for this domain have been updated") + @skip('wip') + def test_domain_nameservers_can_blank_out_first_and_second_one_if_enough_entries(self): + """Nameserver form submits successfully with 2 valid inputs, even if the first and + second entries are blanked out. + + Uses self.app WebTest because we need to interact with forms. + """ + + # Submit a formset with 3 valid forms + # The returned page (after the redirect) will have 4 forms that we can use to test + # our use case. + + + infoDomainFourHosts, _ = Domain.objects.get_or_create(name="fournameserversDomain.gov", state=Domain.State.READY) + UserDomainRole.objects.get_or_create(user=self.user, domain=infoDomainFourHosts) + DomainInformation.objects.get_or_create(creator=self.user, domain=infoDomainFourHosts) + self.client.force_login(self.user) + + nameserver1 = "" + nameserver2 = "" + nameserver3 = "ns3.igorville.gov" + nameserver4 = "ns4.igorville.gov" + valid_ip = "" + valid_ip_2 = "" + valid_ip_3 = "128.0.0.3" + valid_ip_4 = "128.0.0.4" + nameservers_page = self.app.get(reverse("domain-dns-nameservers", kwargs={"pk": infoDomainFourHosts.id})) + print(nameservers_page.content.decode('utf-8')) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + nameservers_page.form["form-0-server"] = nameserver1 + nameservers_page.form["form-0-ip"] = valid_ip + nameservers_page.form["form-1-server"] = nameserver2 + nameservers_page.form["form-1-ip"] = valid_ip_2 + nameservers_page.form["form-2-server"] = nameserver3 + nameservers_page.form["form-2-ip"] = valid_ip_3 + nameservers_page.form["form-3-server"] = nameserver4 + nameservers_page.form["form-3-ip"] = valid_ip_4 + with less_console_noise(): # swallow log warning message + result = nameservers_page.form.submit() + + # form submission was a successful post, response should be a 302 + self.assertEqual(result.status_code, 302) + self.assertEqual( + result["Location"], + reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id}), + ) + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + nameservers_page = result.follow() + self.assertContains(nameservers_page, "The name servers for this domain have been updated") + def test_domain_nameservers_form_invalid(self): """Nameserver form does not submit with invalid data. From d05f016632252e0d1e97b7c27fdfeb5e8bb5edb7 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 12 Apr 2024 18:00:06 -0400 Subject: [PATCH 56/80] comment --- src/registrar/forms/domain.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/registrar/forms/domain.py b/src/registrar/forms/domain.py index 165d32fd8..b7c37277c 100644 --- a/src/registrar/forms/domain.py +++ b/src/registrar/forms/domain.py @@ -181,6 +181,9 @@ class BaseNameserverFormset(forms.BaseFormSet): for index, form in enumerate(self.forms): if form.cleaned_data: value = form.cleaned_data["server"] + # We need to make sure not to trigger the duplicate error in case the first and second nameservers are empty + # If there are enough records in the formset, that error is an unecessary blocker. If there aren't, the required + # error will block the submit. if value in data and not (form.cleaned_data.get("server", "").strip() == '' and index == 1): form.add_error( "server", From 2e86c167b9692b0e0f846f016f9bf2684320ef29 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 12 Apr 2024 21:40:41 -0400 Subject: [PATCH 57/80] Fix unit test for 2 empties --- src/registrar/forms/domain.py | 8 ++--- src/registrar/tests/common.py | 10 ++++--- src/registrar/tests/test_views_domain.py | 38 ++++++++++++++---------- 3 files changed, 32 insertions(+), 24 deletions(-) diff --git a/src/registrar/forms/domain.py b/src/registrar/forms/domain.py index b7c37277c..8fc7a6497 100644 --- a/src/registrar/forms/domain.py +++ b/src/registrar/forms/domain.py @@ -181,10 +181,10 @@ class BaseNameserverFormset(forms.BaseFormSet): for index, form in enumerate(self.forms): if form.cleaned_data: value = form.cleaned_data["server"] - # We need to make sure not to trigger the duplicate error in case the first and second nameservers are empty - # If there are enough records in the formset, that error is an unecessary blocker. If there aren't, the required - # error will block the submit. - if value in data and not (form.cleaned_data.get("server", "").strip() == '' and index == 1): + # We need to make sure not to trigger the duplicate error in case the first and second nameservers + # are empty. If there are enough records in the formset, that error is an unecessary blocker. + # If there aren't, the required error will block the submit. + if value in data and not (form.cleaned_data.get("server", "").strip() == "" and index == 1): form.add_error( "server", NameserverError(code=nsErrorCodes.DUPLICATE_HOST, nameserver=value), diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index 4b9461b65..07dc08f8a 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -1153,7 +1153,7 @@ class MockEppLib(TestCase): ) infoDomainFourHosts = fakedEppObject( - "my-nameserver.gov", + "fournameserversDomain.gov", cr_date=make_aware(datetime(2023, 5, 25, 19, 45, 35)), contacts=[], hosts=[ @@ -1464,7 +1464,9 @@ class MockEppLib(TestCase): ) def mockInfoDomainCommands(self, _request, cleaned): - request_name = getattr(_request, "name", None) + request_name = getattr(_request, "name", None).lower() + + print(request_name) # Define a dictionary to map request names to data and extension values request_mappings = { @@ -1486,8 +1488,8 @@ class MockEppLib(TestCase): "nameserverwithip.gov": (self.infoDomainHasIP, None), "namerserversubdomain.gov": (self.infoDomainCheckHostIPCombo, None), "freeman.gov": (self.InfoDomainWithContacts, None), - "threenameserversDomain.gov": (self.infoDomainThreeHosts, None), - "fournameserversDomain.gov": (self.infoDomainFourHosts, None), + "threenameserversdomain.gov": (self.infoDomainThreeHosts, None), + "fournameserversdomain.gov": (self.infoDomainFourHosts, None), "defaultsecurity.gov": (self.InfoDomainWithDefaultSecurityContact, None), "adomain2.gov": (self.InfoDomainWithVerisignSecurityContact, None), "defaulttechnical.gov": (self.InfoDomainWithDefaultTechnicalContact, None), diff --git a/src/registrar/tests/test_views_domain.py b/src/registrar/tests/test_views_domain.py index eeab00ae3..3a5ce7e7b 100644 --- a/src/registrar/tests/test_views_domain.py +++ b/src/registrar/tests/test_views_domain.py @@ -71,11 +71,14 @@ class TestWithDomainPermissions(TestWithUser): # that inherit this setUp self.domain_dnssec_none, _ = Domain.objects.get_or_create(name="dnssec-none.gov") + self.domain_with_four_nameservers, _ = Domain.objects.get_or_create(name="fournameserversDomain.gov") + self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain) DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_dsdata) DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_multdsdata) DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_dnssec_none) + DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_with_four_nameservers) DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_with_ip) DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_just_nameserver) DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain_on_hold) @@ -98,6 +101,11 @@ class TestWithDomainPermissions(TestWithUser): domain=self.domain_dnssec_none, role=UserDomainRole.Roles.MANAGER, ) + UserDomainRole.objects.get_or_create( + user=self.user, + domain=self.domain_with_four_nameservers, + role=UserDomainRole.Roles.MANAGER, + ) UserDomainRole.objects.get_or_create( user=self.user, domain=self.domain_with_ip, @@ -1036,7 +1044,6 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib): nameservers_page = result.follow() self.assertContains(nameservers_page, "The name servers for this domain have been updated") - @skip('wip') def test_domain_nameservers_can_blank_out_first_and_second_one_if_enough_entries(self): """Nameserver form submits successfully with 2 valid inputs, even if the first and second entries are blanked out. @@ -1044,28 +1051,27 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib): Uses self.app WebTest because we need to interact with forms. """ - # Submit a formset with 3 valid forms - # The returned page (after the redirect) will have 4 forms that we can use to test - # our use case. - - - infoDomainFourHosts, _ = Domain.objects.get_or_create(name="fournameserversDomain.gov", state=Domain.State.READY) - UserDomainRole.objects.get_or_create(user=self.user, domain=infoDomainFourHosts) - DomainInformation.objects.get_or_create(creator=self.user, domain=infoDomainFourHosts) - self.client.force_login(self.user) - + # We need to start with a domain with 4 nameservers otherwise the formset in the test environment + # will only have 3 forms nameserver1 = "" nameserver2 = "" nameserver3 = "ns3.igorville.gov" nameserver4 = "ns4.igorville.gov" valid_ip = "" valid_ip_2 = "" - valid_ip_3 = "128.0.0.3" - valid_ip_4 = "128.0.0.4" - nameservers_page = self.app.get(reverse("domain-dns-nameservers", kwargs={"pk": infoDomainFourHosts.id})) - print(nameservers_page.content.decode('utf-8')) + valid_ip_3 = "" + valid_ip_4 = "" + nameservers_page = self.app.get( + reverse("domain-dns-nameservers", kwargs={"pk": self.domain_with_four_nameservers.id}) + ) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + + # Minimal check to ensure the form is loaded correctly + self.assertEqual(nameservers_page.form["form-0-server"].value, "ns1.my-nameserver-1.com") + self.assertEqual(nameservers_page.form["form-3-server"].value, "ns1.explosive-chicken-nuggets.com") + nameservers_page.form["form-0-server"] = nameserver1 nameservers_page.form["form-0-ip"] = valid_ip nameservers_page.form["form-1-server"] = nameserver2 @@ -1081,7 +1087,7 @@ class TestDomainNameservers(TestDomainOverview, MockEppLib): self.assertEqual(result.status_code, 302) self.assertEqual( result["Location"], - reverse("domain-dns-nameservers", kwargs={"pk": self.domain.id}), + reverse("domain-dns-nameservers", kwargs={"pk": self.domain_with_four_nameservers.id}), ) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) nameservers_page = result.follow() From ffa7b59eb586d433d0aaa72a14be5e39e661949f Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 12 Apr 2024 21:52:16 -0400 Subject: [PATCH 58/80] cleanup --- src/registrar/forms/domain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/forms/domain.py b/src/registrar/forms/domain.py index 8fc7a6497..da1462bdb 100644 --- a/src/registrar/forms/domain.py +++ b/src/registrar/forms/domain.py @@ -99,7 +99,7 @@ class DomainNameserverForm(forms.Form): ip_list = self.extract_ip_list(ip) # Capture the server_value - server_value = self.cleaned_data["server"] + server_value = self.cleaned_data.get("server") # Validate if the form has a server or an ip if (ip and ip_list) or server: From 2ffeda3be7340e9294f9ece9f0cd2d3892a19281 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Mon, 15 Apr 2024 07:46:07 -0400 Subject: [PATCH 59/80] added column for Is user to Contact table in DJA --- src/registrar/admin.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 05bfc06b6..5116888dd 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -663,6 +663,7 @@ class ContactAdmin(ListHeaderAdmin): list_display = [ "contact", "email", + "user_exists", ] # this ordering effects the ordering of results # in autocomplete_fields for user @@ -679,6 +680,12 @@ class ContactAdmin(ListHeaderAdmin): change_form_template = "django/admin/email_clipboard_change_form.html" + def user_exists(self, obj): + """Check if the Contact has a related User""" + return obj.user is not None + user_exists.boolean = True + user_exists.short_description = "Is user" + # We name the custom prop 'contact' because linter # is not allowing a short_description attr on it # This gets around the linter limitation, for now. From 3909ba8212fdb9c7643af59f25d43d45c58defee Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Mon, 15 Apr 2024 07:53:24 -0400 Subject: [PATCH 60/80] formatted for readability --- src/registrar/admin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 5116888dd..b9114f6f5 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -683,6 +683,7 @@ class ContactAdmin(ListHeaderAdmin): def user_exists(self, obj): """Check if the Contact has a related User""" return obj.user is not None + user_exists.boolean = True user_exists.short_description = "Is user" From 77d2c998d3d62d15fdab6c581ef9ad049d7cb4b4 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Mon, 15 Apr 2024 07:57:34 -0400 Subject: [PATCH 61/80] satisfying linter --- src/registrar/admin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index b9114f6f5..fe0730d31 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -684,8 +684,8 @@ class ContactAdmin(ListHeaderAdmin): """Check if the Contact has a related User""" return obj.user is not None - user_exists.boolean = True - user_exists.short_description = "Is user" + user_exists.boolean = True # type: ignore + user_exists.short_description = "Is user" # type: ignore # We name the custom prop 'contact' because linter # is not allowing a short_description attr on it From 5673d5951ef197ee6ef8edd2c4811bf16cecf605 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Mon, 15 Apr 2024 08:50:13 -0400 Subject: [PATCH 62/80] default filter applied when clicking domain requests from editing a domain request --- src/registrar/admin.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 05bfc06b6..efd8c29c9 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -1435,12 +1435,35 @@ class DomainRequestAdmin(ListHeaderAdmin): """ Override changelist_view to set the selected value of status filter. """ + # there are two conditions which should set the default selected filter: + # 1 - there are no query parameters in the request and the request is the + # initial request for this view + # 2 - there are no query parameters in the request and the referring url is + # the change view for a domain request + should_apply_default_filter = False # use http_referer in order to distinguish between request as a link from another page # and request as a removal of all filters http_referer = request.META.get("HTTP_REFERER", "") # if there are no query parameters in the request - # and the request is the initial request for this view - if not bool(request.GET) and request.path not in http_referer: + if not bool(request.GET): + # if the request is the initial request for this view + if request.path not in http_referer: + should_apply_default_filter = True + # elif the request is a referral from changelist view or from + # domain request change view + elif request.path in http_referer: + # find the index to determine the referring url after the path + index = http_referer.find(request.path) + # Check if there is a character following the path in http_referer + if index + len(request.path) < len(http_referer): + next_char = http_referer[index + len(request.path)] + + # Check if the next character is a digit, if so, this indicates + # a change view for domain request + if next_char.isdigit(): + should_apply_default_filter = True + + if should_apply_default_filter: # modify the GET of the request to set the selected filter modified_get = copy.deepcopy(request.GET) modified_get["status__in"] = "submitted,in review,action needed" From 69c665e4fa893e6cb60f580ca1d32a3b49370ead Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Tue, 16 Apr 2024 11:12:17 -0400 Subject: [PATCH 63/80] made is_user column sortable --- src/registrar/admin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index fe0730d31..f17aaf1b2 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -686,6 +686,7 @@ class ContactAdmin(ListHeaderAdmin): user_exists.boolean = True # type: ignore user_exists.short_description = "Is user" # type: ignore + user_exists.admin_order_field = 'user' # type: ignore # We name the custom prop 'contact' because linter # is not allowing a short_description attr on it From 72a816acdafc1f2f0e5f875d62dd8ac97fe2105d Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Tue, 16 Apr 2024 11:13:17 -0400 Subject: [PATCH 64/80] reformatted --- 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 f17aaf1b2..17486e983 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -686,7 +686,7 @@ class ContactAdmin(ListHeaderAdmin): user_exists.boolean = True # type: ignore user_exists.short_description = "Is user" # type: ignore - user_exists.admin_order_field = 'user' # type: ignore + user_exists.admin_order_field = "user" # type: ignore # We name the custom prop 'contact' because linter # is not allowing a short_description attr on it From ee961c9cd801c876e950bdca5bb4d7c90deb4b5f Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Tue, 16 Apr 2024 12:52:48 -0400 Subject: [PATCH 65/80] changed icon to text for column --- src/registrar/admin.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 17486e983..b61b21f87 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -682,9 +682,8 @@ class ContactAdmin(ListHeaderAdmin): def user_exists(self, obj): """Check if the Contact has a related User""" - return obj.user is not None + return "Yes" if obj.user is not None else "No" - user_exists.boolean = True # type: ignore user_exists.short_description = "Is user" # type: ignore user_exists.admin_order_field = "user" # type: ignore From b1a772d1f28a2f2b6ef39861014cb707663f1172 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 16 Apr 2024 12:14:32 -0600 Subject: [PATCH 66/80] Remove comment --- src/registrar/assets/sass/_theme/_admin.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/src/registrar/assets/sass/_theme/_admin.scss b/src/registrar/assets/sass/_theme/_admin.scss index ea49d7b8e..93b437f1d 100644 --- a/src/registrar/assets/sass/_theme/_admin.scss +++ b/src/registrar/assets/sass/_theme/_admin.scss @@ -495,7 +495,6 @@ address.dja-address-contact-list { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; - // This has to be set here due to style overrides font-size: medium; padding-top: 3px; } From 582f35bc07e8c7a5188b8c493a1cbe8970c88587 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 16 Apr 2024 15:24:56 -0600 Subject: [PATCH 67/80] PR suggestions --- docs/operations/data_migration.md | 15 ++-- .../commands/populate_organization_type.py | 89 +++++++++++++------ src/registrar/models/domain_information.py | 2 +- src/registrar/models/domain_request.py | 2 +- .../tests/test_management_scripts.py | 49 ++++++++-- 5 files changed, 110 insertions(+), 47 deletions(-) diff --git a/docs/operations/data_migration.md b/docs/operations/data_migration.md index 4bfe37174..0846208de 100644 --- a/docs/operations/data_migration.md +++ b/docs/operations/data_migration.md @@ -591,7 +591,7 @@ Example: `cf ssh getgov-za` ## Populate Organization type This section outlines how to run the `populate_organization_type` script. The script is used to update the organization_type field on DomainRequest and DomainInformation when it is None. -That data are synthesized from the generic_org_type field and the is_election_board field +That data are synthesized from the generic_org_type field and the is_election_board field by concatenating " - Elections" on the end of generic_org_type string if is_elections_board is True. ### Running on sandboxes @@ -614,7 +614,7 @@ Example: `cf ssh getgov-za` ```/tmp/lifecycle/shell``` #### Step 4: Running the script -```./manage.py populate_organization_type {domain_election_board_filename} --debug``` +```./manage.py populate_organization_type {domain_election_board_filename}``` - The domain_election_board_filename file must adhere to this format: - example.gov\ @@ -622,7 +622,7 @@ Example: `cf ssh getgov-za` example3.gov Example: -`./manage.py populate_organization_type migrationdata/election-domains.csv --debug` +`./manage.py populate_organization_type migrationdata/election-domains.csv` ### Running locally @@ -632,18 +632,13 @@ After downloading this file, place it in `src/migrationdata` #### Step 2: Running the script -```docker-compose exec app ./manage.py populate_organization_type {domain_election_board_filename} --debug``` +```docker-compose exec app ./manage.py populate_organization_type {domain_election_board_filename}``` Example (assuming that this is being ran from src/): -`docker-compose exec app ./manage.py populate_organization_type migrationdata/election-domains.csv --debug` +`docker-compose exec app ./manage.py populate_organization_type migrationdata/election-domains.csv` ### Required parameters | | Parameter | Description | |:-:|:------------------------------------|:-------------------------------------------------------------------| | 1 | **domain_election_board_filename** | A file containing every domain that is an election office. - -### Optional parameters -| | Parameter | Description | -|:-:|:-------------------------- |:----------------------------------------------------------------------------| -| 1 | **debug** | Increases logging detail. Defaults to False. \ No newline at end of file diff --git a/src/registrar/management/commands/populate_organization_type.py b/src/registrar/management/commands/populate_organization_type.py index 8428beb82..f2d9c062d 100644 --- a/src/registrar/management/commands/populate_organization_type.py +++ b/src/registrar/management/commands/populate_organization_type.py @@ -34,7 +34,6 @@ class Command(BaseCommand): def add_arguments(self, parser): """Adds command line arguments""" - parser.add_argument("--debug", action=argparse.BooleanOptionalAction) parser.add_argument( "domain_election_board_filename", help=("A file that contains" " all the domains that are election offices."), @@ -42,7 +41,6 @@ class Command(BaseCommand): def handle(self, domain_election_board_filename, **kwargs): """Loops through each valid Domain object and updates its first_created value""" - debug = kwargs.get("debug") # Check if the provided file path is valid if not os.path.isfile(domain_election_board_filename): @@ -66,7 +64,7 @@ class Command(BaseCommand): ) logger.info("Updating DomainRequest(s)...") - self.update_domain_requests(domain_requests, debug) + self.update_domain_requests(domain_requests) # We should actually be targeting all fields with no value for organization type, # but do have a value for generic_org_type. This is because there is data that we can infer. @@ -84,7 +82,7 @@ class Command(BaseCommand): ) logger.info("Updating DomainInformation(s)...") - self.update_domain_informations(domain_infos, debug) + self.update_domain_informations(domain_infos) def read_election_board_file(self, domain_election_board_filename): """ @@ -106,26 +104,37 @@ class Command(BaseCommand): if domain not in self.domains_with_election_boards_set: self.domains_with_election_boards_set.add(domain) - def update_domain_requests(self, domain_requests, debug): + def update_domain_requests(self, domain_requests): """ Updates the organization_type for a list of DomainRequest objects using the `sync_organization_type` function. Results are then logged. - Debug mode provides detailed logging. + + This function updates the following variables: + - self.request_to_update list is appended to if the field was updated successfully. + - self.request_skipped list is appended to if the field has `None` for `request.generic_org_type`. + - self.request_failed_to_update list is appended to if an exception is caught during update. """ for request in domain_requests: try: if request.generic_org_type is not None: - domain_name = request.requested_domain.name - request.is_election_board = domain_name in self.domains_with_election_boards_set - request = self.sync_organization_type(DomainRequest, request) - self.request_to_update.append(request) + domain_name = None + if ( + request.requested_domain is not None and + request.requested_domain.name is not None + ): + domain_name = request.requested_domain.name - if debug: - logger.info(f"Updating {request} => {request.organization_type}") + request_is_approved = request.state == DomainRequest.DomainRequestStatus.APPROVED + if request_is_approved and domain_name is not None: + request.is_election_board = domain_name in self.domains_with_election_boards_set + + self.sync_organization_type(DomainRequest, request) + + self.request_to_update.append(request) + logger.info(f"Updating {request} => {request.organization_type}") else: self.request_skipped.append(request) - if debug: - logger.warning(f"Skipped updating {request}. No generic_org_type was found.") + logger.warning(f"Skipped updating {request}. No generic_org_type was found.") except Exception as err: self.request_failed_to_update.append(request) logger.error(err) @@ -139,28 +148,44 @@ class Command(BaseCommand): # Log what happened log_header = "============= FINISHED UPDATE FOR DOMAINREQUEST ===============" TerminalHelper.log_script_run_summary( - self.request_to_update, self.request_failed_to_update, self.request_skipped, debug, log_header + self.request_to_update, self.request_failed_to_update, self.request_skipped, True, log_header ) - def update_domain_informations(self, domain_informations, debug): + update_skipped_count = len(self.request_to_update) + if update_skipped_count > 0: + logger.warning( + f"""{TerminalColors.MAGENTA} + Note: Entries are skipped when generic_org_type is None + {TerminalColors.ENDC} + """ + ) + + def update_domain_informations(self, domain_informations): """ Updates the organization_type for a list of DomainInformation objects - using the `sync_organization_type` function. + and updates is_election_board if the domain is in the provided csv. Results are then logged. + + This function updates the following variables: + - self.di_to_update list is appended to if the field was updated successfully. + - self.di_skipped list is appended to if the field has `None` for `request.generic_org_type`. + - self.di_failed_to_update list is appended to if an exception is caught during update. """ for info in domain_informations: try: if info.generic_org_type is not None: domain_name = info.domain.name - info.is_election_board = domain_name in self.domains_with_election_boards_set - info = self.sync_organization_type(DomainInformation, info) + + if not info.is_election_board: + info.is_election_board = domain_name in self.domains_with_election_boards_set + + self.sync_organization_type(DomainInformation, info) + self.di_to_update.append(info) - if debug: - logger.info(f"Updating {info} => {info.organization_type}") + logger.info(f"Updating {info} => {info.organization_type}") else: self.di_skipped.append(info) - if debug: - logger.warning(f"Skipped updating {info}. No generic_org_type was found.") + logger.warning(f"Skipped updating {info}. No generic_org_type was found.") except Exception as err: self.di_failed_to_update.append(info) logger.error(err) @@ -170,13 +195,22 @@ class Command(BaseCommand): ScriptDataHelper.bulk_update_fields( DomainInformation, self.di_to_update, ["organization_type", "is_election_board", "generic_org_type"] ) - + # Log what happened log_header = "============= FINISHED UPDATE FOR DOMAININFORMATION ===============" TerminalHelper.log_script_run_summary( - self.di_to_update, self.di_failed_to_update, self.di_skipped, debug, log_header + self.di_to_update, self.di_failed_to_update, self.di_skipped, True, log_header ) + update_skipped_count = len(self.di_skipped) + if update_skipped_count > 0: + logger.warning( + f"""{TerminalColors.MAGENTA} + Note: Entries are skipped when generic_org_type is None + {TerminalColors.ENDC} + """ + ) + def sync_organization_type(self, sender, instance): """ Updates the organization_type (without saving) to match @@ -187,7 +221,7 @@ class Command(BaseCommand): # These have to be defined here, as you'd get a cyclical import error # otherwise. - # For any given organization type, return the "_election" variant. + # For any given organization type, return the "_ELECTION" enum equivalent. # For example: STATE_OR_TERRITORY => STATE_OR_TERRITORY_ELECTION generic_org_map = DomainRequest.OrgChoicesElectionOffice.get_org_generic_to_org_election() @@ -204,5 +238,4 @@ class Command(BaseCommand): election_org_to_generic_org_map=election_org_map, ) - instance = org_type_helper.create_or_update_organization_type() - return instance + org_type_helper.create_or_update_organization_type() diff --git a/src/registrar/models/domain_information.py b/src/registrar/models/domain_information.py index 6bdc6c00d..b02385d84 100644 --- a/src/registrar/models/domain_information.py +++ b/src/registrar/models/domain_information.py @@ -246,7 +246,7 @@ class DomainInformation(TimeStampedModel): # These have to be defined here, as you'd get a cyclical import error # otherwise. - # For any given organization type, return the "_election" variant. + # For any given organization type, return the "_ELECTION" enum equivalent. # For example: STATE_OR_TERRITORY => STATE_OR_TERRITORY_ELECTION generic_org_map = DomainRequest.OrgChoicesElectionOffice.get_org_generic_to_org_election() diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py index 1b8a519a0..8fb4b78b9 100644 --- a/src/registrar/models/domain_request.py +++ b/src/registrar/models/domain_request.py @@ -675,7 +675,7 @@ class DomainRequest(TimeStampedModel): # These have to be defined here, as you'd get a cyclical import error # otherwise. - # For any given organization type, return the "_election" variant. + # For any given organization type, return the "_ELECTION" enum equivalent. # For example: STATE_OR_TERRITORY => STATE_OR_TERRITORY_ELECTION generic_org_map = self.OrgChoicesElectionOffice.get_org_generic_to_org_election() diff --git a/src/registrar/tests/test_management_scripts.py b/src/registrar/tests/test_management_scripts.py index 26ec6fd1d..7b422e44b 100644 --- a/src/registrar/tests/test_management_scripts.py +++ b/src/registrar/tests/test_management_scripts.py @@ -107,9 +107,23 @@ class TestPopulateOrganizationType(MockEppLib): expected_values: dict, ): """ - This is a a helper function that ensures that: + This is a helper function that tests the following conditions: 1. DomainRequest and DomainInformation (on given objects) are equivalent 2. That generic_org_type, is_election_board, and organization_type are equal to passed in values + + Args: + domain_request (DomainRequest): The DomainRequest object to test + + domain_info (DomainInformation): The DomainInformation object to test + + expected_values (dict): Container for what we expect is_electionboard, generic_org_type, + and organization_type to be on DomainRequest and DomainInformation. + Example: + expected_values = { + "is_election_board": False, + "generic_org_type": DomainRequest.OrganizationChoices.CITY, + "organization_type": DomainRequest.OrgChoicesElectionOffice.CITY, + } """ # Test domain request @@ -124,8 +138,23 @@ class TestPopulateOrganizationType(MockEppLib): self.assertEqual(domain_info.is_election_board, expected_values["is_election_board"]) self.assertEqual(domain_info.organization_type, expected_values["organization_type"]) + def do_nothing(self): + """Does nothing for mocking purposes""" + pass + def test_request_and_info_city_not_in_csv(self): - """Tests what happens to a city domain that is not defined in the CSV""" + """ + Tests what happens to a city domain that is not defined in the CSV. + + Scenario: A domain request (of type city) is made that is not defined in the CSV file. + When a domain request is made for a city that is not listed in the CSV, + Then the `is_election_board` value should remain False, + and the `generic_org_type` and `organization_type` should both be `city`. + + Expected Result: The `is_election_board` and `generic_org_type` attributes should be unchanged. + The `organization_type` field should now be `city`. + """ + city_request = self.domain_request_2 city_info = self.domain_request_2 @@ -149,7 +178,17 @@ class TestPopulateOrganizationType(MockEppLib): self.assert_expected_org_values_on_request_and_info(city_request, city_info, expected_values) def test_request_and_info_federal(self): - """Tests what happens to a federal domain after the script is run (should be unchanged)""" + """ + Tests what happens to a federal domain after the script is run (should be unchanged). + + Scenario: A domain request (of type federal) is processed after running the populate_organization_type script. + When a federal domain request is made, + Then the `is_election_board` value should remain None, + and the `generic_org_type` and `organization_type` fields should both be `federal`. + + Expected Result: The `is_election_board` and `generic_org_type` attributes should be unchanged. + The `organization_type` field should now be `federal`. + """ federal_request = self.domain_request_1 federal_info = self.domain_info_1 @@ -172,10 +211,6 @@ class TestPopulateOrganizationType(MockEppLib): # All values should be the same self.assert_expected_org_values_on_request_and_info(federal_request, federal_info, expected_values) - def do_nothing(self): - """Does nothing for mocking purposes""" - pass - def test_request_and_info_tribal_add_election_office(self): """ Tests if a tribal domain in the election csv changes organization_type to TRIBAL - ELECTION From 50ede0347670d1effc299ef184112b209e6ade2e Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 16 Apr 2024 15:32:33 -0600 Subject: [PATCH 68/80] Linting / fix unit test passing in debug --- .../management/commands/populate_organization_type.py | 7 ++----- src/registrar/tests/test_management_scripts.py | 4 ++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/registrar/management/commands/populate_organization_type.py b/src/registrar/management/commands/populate_organization_type.py index f2d9c062d..cfa44d0a5 100644 --- a/src/registrar/management/commands/populate_organization_type.py +++ b/src/registrar/management/commands/populate_organization_type.py @@ -118,10 +118,7 @@ class Command(BaseCommand): try: if request.generic_org_type is not None: domain_name = None - if ( - request.requested_domain is not None and - request.requested_domain.name is not None - ): + if request.requested_domain is not None and request.requested_domain.name is not None: domain_name = request.requested_domain.name request_is_approved = request.state == DomainRequest.DomainRequestStatus.APPROVED @@ -195,7 +192,7 @@ class Command(BaseCommand): ScriptDataHelper.bulk_update_fields( DomainInformation, self.di_to_update, ["organization_type", "is_election_board", "generic_org_type"] ) - + # Log what happened log_header = "============= FINISHED UPDATE FOR DOMAININFORMATION ===============" TerminalHelper.log_script_run_summary( diff --git a/src/registrar/tests/test_management_scripts.py b/src/registrar/tests/test_management_scripts.py index 7b422e44b..cad9e0ebe 100644 --- a/src/registrar/tests/test_management_scripts.py +++ b/src/registrar/tests/test_management_scripts.py @@ -98,7 +98,7 @@ class TestPopulateOrganizationType(MockEppLib): "registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa return_value=True, ): - call_command("populate_organization_type", "registrar/tests/data/fake_election_domains.csv", debug=True) + call_command("populate_organization_type", "registrar/tests/data/fake_election_domains.csv") def assert_expected_org_values_on_request_and_info( self, @@ -118,7 +118,7 @@ class TestPopulateOrganizationType(MockEppLib): expected_values (dict): Container for what we expect is_electionboard, generic_org_type, and organization_type to be on DomainRequest and DomainInformation. - Example: + Example: expected_values = { "is_election_board": False, "generic_org_type": DomainRequest.OrganizationChoices.CITY, From 40a64a1e868ef61dcce9745b756527ac14b15ceb Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 16 Apr 2024 15:51:10 -0600 Subject: [PATCH 69/80] Fix typo causing test fail --- src/registrar/management/commands/populate_organization_type.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/management/commands/populate_organization_type.py b/src/registrar/management/commands/populate_organization_type.py index cfa44d0a5..cef8e9433 100644 --- a/src/registrar/management/commands/populate_organization_type.py +++ b/src/registrar/management/commands/populate_organization_type.py @@ -121,7 +121,7 @@ class Command(BaseCommand): if request.requested_domain is not None and request.requested_domain.name is not None: domain_name = request.requested_domain.name - request_is_approved = request.state == DomainRequest.DomainRequestStatus.APPROVED + request_is_approved = request.status == DomainRequest.DomainRequestStatus.APPROVED if request_is_approved and domain_name is not None: request.is_election_board = domain_name in self.domains_with_election_boards_set From e4abe28b61be7d523b9e05485b6c4c1ed56f1b36 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 17 Apr 2024 08:33:35 -0600 Subject: [PATCH 70/80] Hotfix --- src/registrar/assets/sass/_theme/_admin.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/assets/sass/_theme/_admin.scss b/src/registrar/assets/sass/_theme/_admin.scss index 93b437f1d..93b3e1581 100644 --- a/src/registrar/assets/sass/_theme/_admin.scss +++ b/src/registrar/assets/sass/_theme/_admin.scss @@ -496,7 +496,7 @@ address.dja-address-contact-list { white-space: nowrap; text-overflow: ellipsis; font-size: medium; - padding-top: 3px; + padding-top: 3px !important; } } From 4b90ff833c1a1686229d2e15bc880f8bd52ccb00 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 17 Apr 2024 09:26:05 -0600 Subject: [PATCH 71/80] Fix unit test --- .../commands/populate_organization_type.py | 5 ++- .../models/utility/generic_helper.py | 33 +++++++++++-------- .../tests/test_management_scripts.py | 19 ++++++----- 3 files changed, 33 insertions(+), 24 deletions(-) diff --git a/src/registrar/management/commands/populate_organization_type.py b/src/registrar/management/commands/populate_organization_type.py index cef8e9433..a7dd98b24 100644 --- a/src/registrar/management/commands/populate_organization_type.py +++ b/src/registrar/management/commands/populate_organization_type.py @@ -122,11 +122,10 @@ class Command(BaseCommand): domain_name = request.requested_domain.name request_is_approved = request.status == DomainRequest.DomainRequestStatus.APPROVED - if request_is_approved and domain_name is not None: + if request_is_approved and domain_name is not None and not request.is_election_board: request.is_election_board = domain_name in self.domains_with_election_boards_set self.sync_organization_type(DomainRequest, request) - self.request_to_update.append(request) logger.info(f"Updating {request} => {request.organization_type}") else: @@ -235,4 +234,4 @@ class Command(BaseCommand): election_org_to_generic_org_map=election_org_map, ) - org_type_helper.create_or_update_organization_type() + org_type_helper.create_or_update_organization_type(force_update=True) diff --git a/src/registrar/models/utility/generic_helper.py b/src/registrar/models/utility/generic_helper.py index 32f767ede..77351b11c 100644 --- a/src/registrar/models/utility/generic_helper.py +++ b/src/registrar/models/utility/generic_helper.py @@ -49,7 +49,7 @@ class CreateOrUpdateOrganizationTypeHelper: self.generic_org_to_org_map = generic_org_to_org_map self.election_org_to_generic_org_map = election_org_to_generic_org_map - def create_or_update_organization_type(self): + def create_or_update_organization_type(self, force_update = False): """The organization_type field on DomainRequest and DomainInformation is consituted from the generic_org_type and is_election_board fields. To keep the organization_type field up to date, we need to update it before save based off of those field @@ -59,6 +59,11 @@ class CreateOrUpdateOrganizationTypeHelper: one of the excluded types (FEDERAL, INTERSTATE, SCHOOL_DISTRICT), the organization_type is set to a corresponding election variant. Otherwise, it directly mirrors the generic_org_type value. + + args: + force_update (bool): If an existing instance has no values to change, + try to update the organization_type field (or related fields) anyway. + This is done by invoking the new instance handler. """ # A new record is added with organization_type not defined. @@ -67,7 +72,7 @@ class CreateOrUpdateOrganizationTypeHelper: if is_new_instance: self._handle_new_instance() else: - self._handle_existing_instance() + self._handle_existing_instance(force_update) return self.instance @@ -92,7 +97,7 @@ class CreateOrUpdateOrganizationTypeHelper: # Update the field self._update_fields(organization_type_needs_update, generic_org_type_needs_update) - def _handle_existing_instance(self): + def _handle_existing_instance(self, force_update_when_no_are_changes_found = False): # == Init variables == # # Instance is already in the database, fetch its current state current_instance = self.sender.objects.get(id=self.instance.id) @@ -109,17 +114,19 @@ class CreateOrUpdateOrganizationTypeHelper: # This will not happen in normal flow as it is not possible otherwise. raise ValueError("Cannot update organization_type and generic_org_type simultaneously.") elif not organization_type_changed and (not generic_org_type_changed and not is_election_board_changed): - # No values to update - do nothing - return None - # == Program flow will halt here if there is no reason to update == # + # No changes found + if force_update_when_no_are_changes_found: + # If we want to force an update anyway, we can treat this record like + # its a new one in that we check for "None" values rather than changes. + self._handle_new_instance() + else: + # == Update the linked values == # + # Find out which field needs updating + organization_type_needs_update = generic_org_type_changed or is_election_board_changed + generic_org_type_needs_update = organization_type_changed - # == Update the linked values == # - # Find out which field needs updating - organization_type_needs_update = generic_org_type_changed or is_election_board_changed - generic_org_type_needs_update = organization_type_changed - - # Update the field - self._update_fields(organization_type_needs_update, generic_org_type_needs_update) + # Update the field + self._update_fields(organization_type_needs_update, generic_org_type_needs_update) def _update_fields(self, organization_type_needs_update, generic_org_type_needs_update): """ diff --git a/src/registrar/tests/test_management_scripts.py b/src/registrar/tests/test_management_scripts.py index cad9e0ebe..26161b272 100644 --- a/src/registrar/tests/test_management_scripts.py +++ b/src/registrar/tests/test_management_scripts.py @@ -251,11 +251,14 @@ class TestPopulateOrganizationType(MockEppLib): self.assert_expected_org_values_on_request_and_info(tribal_request, tribal_info, expected_values) - def test_request_and_info_tribal_remove_election_office(self): + def test_request_and_info_tribal_doesnt_remove_election_office(self): """ - Tests if a tribal domain in the election csv changes organization_type to TRIBAL - when it used to be TRIBAL - ELECTION - for the domain request and the domain info + Tests if a tribal domain in the election csv changes organization_type to TRIBAL_ELECTION + when the is_election_board is True, and generic_org_type is Tribal when it is not + present in the CSV. + + To avoid overwriting data, the script should not set any domain specified as + an election_office (that doesn't exist in the CSV) to false. """ # Set org type fields to none to mimic an environment without this data @@ -287,10 +290,10 @@ class TestPopulateOrganizationType(MockEppLib): except Exception as e: self.fail(f"Could not run populate_organization_type script. Failed with exception: {e}") - # Because we don't define this in the "csv", we expect that is election board will switch to False, - # and organization_type will now be tribal - expected_values["is_election_board"] = False - expected_values["organization_type"] = DomainRequest.OrgChoicesElectionOffice.TRIBAL + # If we don't define this in the "csv", but the value was already true, + # we expect that is election board will stay True, and the org type will be tribal, + # and organization_type will now be tribal_election + expected_values["organization_type"] = DomainRequest.OrgChoicesElectionOffice.TRIBAL_ELECTION tribal_election_request.refresh_from_db() tribal_election_info.refresh_from_db() self.assert_expected_org_values_on_request_and_info( From cdd34936b2c4a33542c0b0a05709fd037291aac7 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 17 Apr 2024 09:29:09 -0600 Subject: [PATCH 72/80] Linter --- src/registrar/models/utility/generic_helper.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/registrar/models/utility/generic_helper.py b/src/registrar/models/utility/generic_helper.py index 77351b11c..991231841 100644 --- a/src/registrar/models/utility/generic_helper.py +++ b/src/registrar/models/utility/generic_helper.py @@ -49,7 +49,7 @@ class CreateOrUpdateOrganizationTypeHelper: self.generic_org_to_org_map = generic_org_to_org_map self.election_org_to_generic_org_map = election_org_to_generic_org_map - def create_or_update_organization_type(self, force_update = False): + def create_or_update_organization_type(self, force_update=False): """The organization_type field on DomainRequest and DomainInformation is consituted from the generic_org_type and is_election_board fields. To keep the organization_type field up to date, we need to update it before save based off of those field @@ -97,7 +97,7 @@ class CreateOrUpdateOrganizationTypeHelper: # Update the field self._update_fields(organization_type_needs_update, generic_org_type_needs_update) - def _handle_existing_instance(self, force_update_when_no_are_changes_found = False): + def _handle_existing_instance(self, force_update_when_no_are_changes_found=False): # == Init variables == # # Instance is already in the database, fetch its current state current_instance = self.sender.objects.get(id=self.instance.id) From c1c428b6c3025fbf87f84cee049a3c15e7facf14 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Wed, 17 Apr 2024 12:40:27 -0400 Subject: [PATCH 73/80] updated code for readability --- src/registrar/admin.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index efd8c29c9..a0d2a0b6b 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -1455,8 +1455,9 @@ class DomainRequestAdmin(ListHeaderAdmin): # find the index to determine the referring url after the path index = http_referer.find(request.path) # Check if there is a character following the path in http_referer - if index + len(request.path) < len(http_referer): - next_char = http_referer[index + len(request.path)] + next_char_index = index + len(request.path) + if index + next_char_index < len(http_referer): + next_char = http_referer[next_char_index] # Check if the next character is a digit, if so, this indicates # a change view for domain request From c0b95f22f9a5f65680f896386a09bffd0a50f251 Mon Sep 17 00:00:00 2001 From: Rebecca Hsieh Date: Wed, 17 Apr 2024 09:52:18 -0700 Subject: [PATCH 74/80] Add documentation on how to access the db and roll back a migration --- docs/developer/database-access.md | 7 +++++++ docs/developer/migration-troubleshooting.md | 15 +++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/docs/developer/database-access.md b/docs/developer/database-access.md index 859ef2fd6..e13f970b3 100644 --- a/docs/developer/database-access.md +++ b/docs/developer/database-access.md @@ -56,6 +56,13 @@ cf ssh getgov-ENVIRONMENT ./manage.py dumpdata ``` +## Access certain table in the database +1. `cf connect-to-service getgov-ENVIRONMENT getgov-ENVIRONMENT-database` gets you into whichever environments database you'd like +2. `\c [table name here that starts cgaws];` connects to the [cgaws...etc] table +3. `\dt` retrieves information about that table and displays it +4. Make sure the table you are looking for exists. For this example, we are looking for `django_migrations` +5. Run `SELECT * FROM django_migrations` to see everything that's in it! + ## Dropping and re-creating the database For your sandbox environment, it might be necessary to start the database over from scratch. diff --git a/docs/developer/migration-troubleshooting.md b/docs/developer/migration-troubleshooting.md index b90c02ae3..e2208f860 100644 --- a/docs/developer/migration-troubleshooting.md +++ b/docs/developer/migration-troubleshooting.md @@ -121,3 +121,18 @@ https://cisa-corp.slack.com/archives/C05BGB4L5NF/p1697810600723069 2. `./manage.py migrate model_name_here file_name_WITH_create` (run the last data creation migration AND ONLY THAT ONE) 3. `./manage.py migrate --fake model_name_here most_recent_file_name` (fake migrate the last migration in the migration list) 4. `./manage.py load` (rerun fixtures) + +### Scenario 9: Inconsistent Migration History +If you see `django.db.migrations.exceptions.InconsistentMigrationHistory` error, or when you run `./manage.py showmigrations` it looks like: + +[x] 0056_example_migration +[ ] 0057_other_migration +[x] 0058_some_other_migration + +1. Go to `database-access.md` to see the commands on how to access a certain table in the database. +2. In this case, we want to remove the migration "history" from the `django_migrations` table +3. Once you are in the `cgaws....` table, select the `django_migrations` table with the command `SELECT * FROM djangomigrations;` +4. Find the id of the "history" you want to delete. In this example, the id would be 58. +5. Run `DELETE FROM django_migrations WHERE id=58;` where 58 is an example id as seen above. +6. Go to your shell and run `./manage.py showmigrations` to make sure your migrations are now back to the right state + From 1bc4f002a9d7a1744e60150479f931bc4d554014 Mon Sep 17 00:00:00 2001 From: Rebecca Hsieh Date: Wed, 17 Apr 2024 09:58:01 -0700 Subject: [PATCH 75/80] Fix table name and add in semicolon for statement --- docs/developer/database-access.md | 4 ++-- docs/developer/migration-troubleshooting.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/developer/database-access.md b/docs/developer/database-access.md index e13f970b3..f77261bbb 100644 --- a/docs/developer/database-access.md +++ b/docs/developer/database-access.md @@ -58,10 +58,10 @@ cf ssh getgov-ENVIRONMENT ## Access certain table in the database 1. `cf connect-to-service getgov-ENVIRONMENT getgov-ENVIRONMENT-database` gets you into whichever environments database you'd like -2. `\c [table name here that starts cgaws];` connects to the [cgaws...etc] table +2. `\c [table name here that starts cgaws...etc];` connects to the [cgaws...etc] table 3. `\dt` retrieves information about that table and displays it 4. Make sure the table you are looking for exists. For this example, we are looking for `django_migrations` -5. Run `SELECT * FROM django_migrations` to see everything that's in it! +5. Run `SELECT * FROM django_migrations;` to see everything that's in it! ## Dropping and re-creating the database diff --git a/docs/developer/migration-troubleshooting.md b/docs/developer/migration-troubleshooting.md index e2208f860..51422d4c4 100644 --- a/docs/developer/migration-troubleshooting.md +++ b/docs/developer/migration-troubleshooting.md @@ -131,7 +131,7 @@ If you see `django.db.migrations.exceptions.InconsistentMigrationHistory` error, 1. Go to `database-access.md` to see the commands on how to access a certain table in the database. 2. In this case, we want to remove the migration "history" from the `django_migrations` table -3. Once you are in the `cgaws....` table, select the `django_migrations` table with the command `SELECT * FROM djangomigrations;` +3. Once you are in the `cgaws....` table, select the `django_migrations` table with the command `SELECT * FROM django_migrations;` 4. Find the id of the "history" you want to delete. In this example, the id would be 58. 5. Run `DELETE FROM django_migrations WHERE id=58;` where 58 is an example id as seen above. 6. Go to your shell and run `./manage.py showmigrations` to make sure your migrations are now back to the right state From e5105d076d1bccc63fbd14e6f3eaa2dcfb2b1468 Mon Sep 17 00:00:00 2001 From: Rebecca Hsieh Date: Wed, 17 Apr 2024 10:12:25 -0700 Subject: [PATCH 76/80] Point to the exact point in db access --- docs/developer/migration-troubleshooting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/developer/migration-troubleshooting.md b/docs/developer/migration-troubleshooting.md index 51422d4c4..b2b8f8662 100644 --- a/docs/developer/migration-troubleshooting.md +++ b/docs/developer/migration-troubleshooting.md @@ -129,7 +129,7 @@ If you see `django.db.migrations.exceptions.InconsistentMigrationHistory` error, [ ] 0057_other_migration [x] 0058_some_other_migration -1. Go to `database-access.md` to see the commands on how to access a certain table in the database. +1. Go to [database-access.md](../database-access.md#access-certain-table-in-the-database) to see the commands on how to access a certain table in the database. 2. In this case, we want to remove the migration "history" from the `django_migrations` table 3. Once you are in the `cgaws....` table, select the `django_migrations` table with the command `SELECT * FROM django_migrations;` 4. Find the id of the "history" you want to delete. In this example, the id would be 58. From 3988ffd653bb29bbcf1245db433f35768d5b57fb Mon Sep 17 00:00:00 2001 From: Rebecca Hsieh Date: Wed, 17 Apr 2024 10:18:15 -0700 Subject: [PATCH 77/80] Address feedback of wording --- docs/developer/migration-troubleshooting.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/developer/migration-troubleshooting.md b/docs/developer/migration-troubleshooting.md index b2b8f8662..f096a876a 100644 --- a/docs/developer/migration-troubleshooting.md +++ b/docs/developer/migration-troubleshooting.md @@ -132,7 +132,8 @@ If you see `django.db.migrations.exceptions.InconsistentMigrationHistory` error, 1. Go to [database-access.md](../database-access.md#access-certain-table-in-the-database) to see the commands on how to access a certain table in the database. 2. In this case, we want to remove the migration "history" from the `django_migrations` table 3. Once you are in the `cgaws....` table, select the `django_migrations` table with the command `SELECT * FROM django_migrations;` -4. Find the id of the "history" you want to delete. In this example, the id would be 58. -5. Run `DELETE FROM django_migrations WHERE id=58;` where 58 is an example id as seen above. -6. Go to your shell and run `./manage.py showmigrations` to make sure your migrations are now back to the right state +4. Find the id of the "history" you want to delete. This will be the one in the far left column. For this example, let's pretend the id is 101. +5. Run `DELETE FROM django_migrations WHERE id=101;` where 101 is an example id as seen above. +6. Go to your shell and run `./manage.py showmigrations` to make sure your migrations are now back to the right state. Most likely you will show several unapplied migrations. +7. If you still have unapplied migrations, run `./manage.py migrate`. If an error occurs saying one has already been applied, fake that particular migration `./manage.py migrate --fake model_name_here migration_number` and then run the normal `./manage.py migrate` command to then apply those migrations that come after the one that threw the error. From 436b87bc9654309de57ab840dcba2505f2a990a6 Mon Sep 17 00:00:00 2001 From: Rebecca Hsieh Date: Wed, 17 Apr 2024 10:20:55 -0700 Subject: [PATCH 78/80] Fix wording --- docs/developer/migration-troubleshooting.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/developer/migration-troubleshooting.md b/docs/developer/migration-troubleshooting.md index f096a876a..22a02503d 100644 --- a/docs/developer/migration-troubleshooting.md +++ b/docs/developer/migration-troubleshooting.md @@ -131,9 +131,9 @@ If you see `django.db.migrations.exceptions.InconsistentMigrationHistory` error, 1. Go to [database-access.md](../database-access.md#access-certain-table-in-the-database) to see the commands on how to access a certain table in the database. 2. In this case, we want to remove the migration "history" from the `django_migrations` table -3. Once you are in the `cgaws....` table, select the `django_migrations` table with the command `SELECT * FROM django_migrations;` +3. Once you are in the `cgaws...` table, select the `django_migrations` table with the command `SELECT * FROM django_migrations;` 4. Find the id of the "history" you want to delete. This will be the one in the far left column. For this example, let's pretend the id is 101. 5. Run `DELETE FROM django_migrations WHERE id=101;` where 101 is an example id as seen above. -6. Go to your shell and run `./manage.py showmigrations` to make sure your migrations are now back to the right state. Most likely you will show several unapplied migrations. +6. Go to your shell and run `./manage.py showmigrations` to make sure your migrations are now back to the right state. Most likely you will see several unapplied migrations. 7. If you still have unapplied migrations, run `./manage.py migrate`. If an error occurs saying one has already been applied, fake that particular migration `./manage.py migrate --fake model_name_here migration_number` and then run the normal `./manage.py migrate` command to then apply those migrations that come after the one that threw the error. From 03a0a60b3a7c762db5de9541287b6c57b3cc08a1 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 17 Apr 2024 14:43:27 -0600 Subject: [PATCH 79/80] Add comment --- src/registrar/models/utility/generic_helper.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/registrar/models/utility/generic_helper.py b/src/registrar/models/utility/generic_helper.py index 991231841..4b08468a4 100644 --- a/src/registrar/models/utility/generic_helper.py +++ b/src/registrar/models/utility/generic_helper.py @@ -64,6 +64,9 @@ class CreateOrUpdateOrganizationTypeHelper: force_update (bool): If an existing instance has no values to change, try to update the organization_type field (or related fields) anyway. This is done by invoking the new instance handler. + + Use to force org type to be updated to the correct value even + if no other changes were made (including is_election). """ # A new record is added with organization_type not defined. From 4467571613f5d1c69665a6a2a9a7df723d5cbb91 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 17 Apr 2024 14:51:02 -0600 Subject: [PATCH 80/80] Its the final lintdown --- src/registrar/models/utility/generic_helper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/models/utility/generic_helper.py b/src/registrar/models/utility/generic_helper.py index 4b08468a4..892298967 100644 --- a/src/registrar/models/utility/generic_helper.py +++ b/src/registrar/models/utility/generic_helper.py @@ -65,7 +65,7 @@ class CreateOrUpdateOrganizationTypeHelper: try to update the organization_type field (or related fields) anyway. This is done by invoking the new instance handler. - Use to force org type to be updated to the correct value even + Use to force org type to be updated to the correct value even if no other changes were made (including is_election). """