From 67d177dc75b5b9881ee67fc796462e95894cea93 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 3 Oct 2023 11:18:38 -0600 Subject: [PATCH 01/53] #1102 - Changed disclose behaviour --- src/registrar/models/domain.py | 15 ++++++--------- src/registrar/tests/common.py | 8 ++------ 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index e45724a9b..744c54a56 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -850,18 +850,15 @@ class Domain(TimeStampedModel, DomainHelper): """creates a disclose object that can be added to a contact Create using .disclose= on the command before sending. if item is security email then make sure email is visable""" - isSecurity = contact.contact_type == contact.ContactTypeChoices.SECURITY + is_security = contact.contact_type == contact.ContactTypeChoices.SECURITY DF = epp.DiscloseField - fields = {DF.FAX, DF.VOICE, DF.ADDR} - - if not isSecurity or ( - isSecurity and contact.email == PublicContact.get_default_security().email - ): - fields.add(DF.EMAIL) + fields = {DF.EMAIL} + disclose = ( + is_security and contact.email != PublicContact.get_default_security().email + ) return epp.Disclose( - flag=False, + flag=disclose, fields=fields, - types={DF.ADDR: "loc"}, ) def _make_epp_contact_postal_info(self, contact: PublicContact): # type: ignore diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index 10c387099..3770bbe09 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -626,15 +626,11 @@ class MockEppLib(TestCase): self, contact: PublicContact, disclose_email=False, createContact=True ): DF = common.DiscloseField - fields = {DF.FAX, DF.VOICE, DF.ADDR} - - if not disclose_email: - fields.add(DF.EMAIL) + fields = {DF.EMAIL} di = common.Disclose( - flag=False, + flag=disclose_email, fields=fields, - types={DF.ADDR: "loc"}, ) # check docs here looks like we may have more than one address field but From 914b2da187e7daa87fd12231363900af74306bb4 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 3 Oct 2023 11:36:56 -0600 Subject: [PATCH 02/53] Update domain.py --- src/registrar/models/domain.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 744c54a56..2d75da42c 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -856,6 +856,7 @@ class Domain(TimeStampedModel, DomainHelper): disclose = ( is_security and contact.email != PublicContact.get_default_security().email ) + # Will only disclose DF.EMAIL if its not the default return epp.Disclose( flag=disclose, fields=fields, From fb5faf35f1c6d7b1a207ccd963e815f746e8f59a Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 4 Oct 2023 10:50:07 -0600 Subject: [PATCH 03/53] Added tests --- src/registrar/tests/test_models_domain.py | 96 +++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index bf258db31..9068863b0 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -629,6 +629,102 @@ class TestRegistrantContacts(MockEppLib): self.mockedSendFunction.assert_has_calls(expected_calls, any_order=True) self.assertEqual(PublicContact.objects.filter(domain=self.domain).count(), 1) + @skip("Dependent on #850") + def test_not_disclosed_on_other_contacts(self): + """ + Scenario: Registrant creates a new domain with multiple contacts + When `domain` has registrant, admin, technical, + and security contacts + Then Domain sends `commands.CreateContact` to the registry + And the field `disclose` is set to false for DF.EMAIL + on all fields except security + """ + # Generates a domain with four existing contacts + domain, _ = Domain.objects.get_or_create(name="igorville.gov") + # Adds default emails to all fields + domain.addAllDefaults() + # Security contact should be disclosed + domain.security_contact.email = "test123@mail.gov" + # TODO - uncomment below when #850 is merged + domain.registrant_contact = domain.get_default_registrant_contact() + + expected_admin = domain.get_default_administrative_contact() + expected_registrant = domain.get_default_registrant_contact() + expected_security = domain.get_default_security_contact() + expected_tech = domain.get_default_technical_contact() + + contacts = [ + expected_admin, + expected_registrant, + expected_security, + expected_tech + ] + + for contact in contacts: + id = PublicContact.objects.get( + domain=self.domain, + contact_type=contact.contact_type_choice, + ).registry_id + contact.registry_id = id + + expectedCreateCommand = self._convertPublicContactToEpp( + contact, disclose_email=False + ) + + self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True) + + @skip("Dependent on #850") + def test_not_disclosed_on_default_security_contact(self): + """ + Scenario: Registrant creates a new domain with no security email + When `domain.security_contact.email` is equal to the default + Then Domain sends `commands.CreateContact` to the registry + And the field `disclose` is set to false for DF.EMAIL + """ + expectedSecContact = PublicContact.get_default_security() + expectedSecContact.domain = self.domain + self.domain.security_contact.email = "test123@mail.gov" + + id = PublicContact.objects.get( + domain=self.domain, + contact_type=PublicContact.ContactTypeChoices.SECURITY, + ).registry_id + + expectedSecContact.registry_id = id + expectedCreateCommand = self._convertPublicContactToEpp( + expectedSecContact, disclose_email=True + ) + + self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True) + # Confirm that we are getting a default object + self.assertEqual(self.domain.security_contact, expectedSecContact) + + @skip("Dependent on #850") + def test_is_disclosed_on_security_contact(self): + """ + Scenario: Registrant creates a new domain with a security email + When `domain.security_contact.email` is set to a valid email + and is not the default + Then Domain sends `commands.CreateContact` to the registry + And the field `disclose` is set to true for DF.EMAIL + """ + expectedSecContact = PublicContact.get_default_security() + expectedSecContact.domain = self.domain + + id = PublicContact.objects.get( + domain=self.domain, + contact_type=PublicContact.ContactTypeChoices.SECURITY, + ).registry_id + + expectedSecContact.registry_id = id + expectedCreateCommand = self._convertPublicContactToEpp( + expectedSecContact, disclose_email=False + ) + + self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True) + # Confirm that we are getting a default object + self.assertEqual(self.domain.security_contact, expectedSecContact) + @skip("not implemented yet") def test_update_is_unsuccessful(self): """ From bb1e7bdf78b883224d9479afb57f6b8d0ebe58fe Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 4 Oct 2023 12:23:08 -0600 Subject: [PATCH 04/53] Test cases --- src/registrar/tests/common.py | 23 +++++++++ src/registrar/tests/test_models_domain.py | 63 +++++++++-------------- 2 files changed, 48 insertions(+), 38 deletions(-) diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index 7a98f224d..1257abe29 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -647,6 +647,25 @@ class MockEppLib(TestCase): registrant="regContact", ) + InfoDomainWithDefaultSecurityContact = fakedEppObject( + "fakepw", + cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), + contacts=[ + common.DomainContact( + contact="defaultSec", + type=PublicContact.ContactTypeChoices.SECURITY, + ) + ], + hosts=["fake.host.com"], + statuses=[ + common.Status(state="serverTransferProhibited", description="", lang="en"), + common.Status(state="inactive", description="", lang="en"), + ], + ) + + mockDefaultSecurityContact = InfoDomainWithContacts.dummyInfoContactResultData( + "defaultSec", "dotgov@cisa.dhs.gov" + ) mockSecurityContact = InfoDomainWithContacts.dummyInfoContactResultData( "securityContact", "security@mail.gov" ) @@ -681,6 +700,8 @@ class MockEppLib(TestCase): return MagicMock(res_data=[self.infoDomainNoContact]) elif getattr(_request, "name", None) == "freeman.gov": return MagicMock(res_data=[self.InfoDomainWithContacts]) + elif getattr(_request, "name", None) == "defaultsecurity.gov": + return MagicMock(res_data=[self.InfoDomainWithDefaultSecurityContact]) else: return MagicMock(res_data=[self.mockDataInfoDomain]) elif isinstance(_request, commands.InfoContact): @@ -696,6 +717,8 @@ class MockEppLib(TestCase): mocked_result = self.mockAdministrativeContact case "regContact": mocked_result = self.mockRegistrantContact + case "defaultSec": + mocked_result = self.mockDefaultSecurityContact case _: # Default contact return mocked_result = self.mockDataInfoContact diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index 51748fdfd..b61e3551e 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -705,7 +705,6 @@ class TestRegistrantContacts(MockEppLib): self.mockedSendFunction.assert_has_calls(expected_calls, any_order=True) self.assertEqual(PublicContact.objects.filter(domain=self.domain).count(), 1) - @skip("Dependent on #850") def test_not_disclosed_on_other_contacts(self): """ Scenario: Registrant creates a new domain with multiple contacts @@ -716,19 +715,19 @@ class TestRegistrantContacts(MockEppLib): on all fields except security """ # Generates a domain with four existing contacts - domain, _ = Domain.objects.get_or_create(name="igorville.gov") - # Adds default emails to all fields - domain.addAllDefaults() - # Security contact should be disclosed - domain.security_contact.email = "test123@mail.gov" - # TODO - uncomment below when #850 is merged - domain.registrant_contact = domain.get_default_registrant_contact() + domain, _ = Domain.objects.get_or_create(name="freeman.gov") expected_admin = domain.get_default_administrative_contact() expected_registrant = domain.get_default_registrant_contact() expected_security = domain.get_default_security_contact() + expected_security.email = "security@mail.gov" expected_tech = domain.get_default_technical_contact() + domain.administrative_contact = expected_admin + domain.registrant_contact = expected_registrant + domain.security_contact = expected_security + domain.technical_contact = expected_tech + contacts = [ expected_admin, expected_registrant, @@ -737,19 +736,14 @@ class TestRegistrantContacts(MockEppLib): ] for contact in contacts: - id = PublicContact.objects.get( - domain=self.domain, - contact_type=contact.contact_type_choice, - ).registry_id - contact.registry_id = id - + is_security = contact.contact_type == "security" expectedCreateCommand = self._convertPublicContactToEpp( - contact, disclose_email=False + contact, + disclose_email=is_security ) self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True) - @skip("Dependent on #850") def test_not_disclosed_on_default_security_contact(self): """ Scenario: Registrant creates a new domain with no security email @@ -757,25 +751,21 @@ class TestRegistrantContacts(MockEppLib): Then Domain sends `commands.CreateContact` to the registry And the field `disclose` is set to false for DF.EMAIL """ + self.maxDiff = None + domain, _ = Domain.objects.get_or_create(name="defaultsecurity.gov") expectedSecContact = PublicContact.get_default_security() - expectedSecContact.domain = self.domain - self.domain.security_contact.email = "test123@mail.gov" + expectedSecContact.domain = domain + expectedSecContact.registry_id="defaultSec" + domain.security_contact = expectedSecContact - id = PublicContact.objects.get( - domain=self.domain, - contact_type=PublicContact.ContactTypeChoices.SECURITY, - ).registry_id - - expectedSecContact.registry_id = id expectedCreateCommand = self._convertPublicContactToEpp( - expectedSecContact, disclose_email=True + expectedSecContact, disclose_email=False ) self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True) - # Confirm that we are getting a default object - self.assertEqual(self.domain.security_contact, expectedSecContact) + # Confirm that we are getting a default email + self.assertEqual(domain.security_contact.email, expectedSecContact.email) - @skip("Dependent on #850") def test_is_disclosed_on_security_contact(self): """ Scenario: Registrant creates a new domain with a security email @@ -784,22 +774,19 @@ class TestRegistrantContacts(MockEppLib): Then Domain sends `commands.CreateContact` to the registry And the field `disclose` is set to true for DF.EMAIL """ + domain, _ = Domain.objects.get_or_create(name="igorville.gov") expectedSecContact = PublicContact.get_default_security() - expectedSecContact.domain = self.domain + expectedSecContact.domain = domain + expectedSecContact.email = "123@mail.gov" + domain.security_contact = expectedSecContact - id = PublicContact.objects.get( - domain=self.domain, - contact_type=PublicContact.ContactTypeChoices.SECURITY, - ).registry_id - - expectedSecContact.registry_id = id expectedCreateCommand = self._convertPublicContactToEpp( - expectedSecContact, disclose_email=False + expectedSecContact, disclose_email=True ) self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True) - # Confirm that we are getting a default object - self.assertEqual(self.domain.security_contact, expectedSecContact) + # Confirm that we are getting the desired email + self.assertEqual(domain.security_contact.email, expectedSecContact.email) @skip("not implemented yet") def test_update_is_unsuccessful(self): From f0acb978b995a307180dbfc50602e98cccdf37ba Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 4 Oct 2023 12:25:16 -0600 Subject: [PATCH 05/53] Running black for formatting --- src/registrar/tests/test_models_domain.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index b61e3551e..666d93c93 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -708,7 +708,7 @@ class TestRegistrantContacts(MockEppLib): def test_not_disclosed_on_other_contacts(self): """ Scenario: Registrant creates a new domain with multiple contacts - When `domain` has registrant, admin, technical, + When `domain` has registrant, admin, technical, and security contacts Then Domain sends `commands.CreateContact` to the registry And the field `disclose` is set to false for DF.EMAIL @@ -732,16 +732,15 @@ class TestRegistrantContacts(MockEppLib): expected_admin, expected_registrant, expected_security, - expected_tech + expected_tech, ] for contact in contacts: is_security = contact.contact_type == "security" expectedCreateCommand = self._convertPublicContactToEpp( - contact, - disclose_email=is_security + contact, disclose_email=is_security ) - + self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True) def test_not_disclosed_on_default_security_contact(self): @@ -755,13 +754,13 @@ class TestRegistrantContacts(MockEppLib): domain, _ = Domain.objects.get_or_create(name="defaultsecurity.gov") expectedSecContact = PublicContact.get_default_security() expectedSecContact.domain = domain - expectedSecContact.registry_id="defaultSec" + expectedSecContact.registry_id = "defaultSec" domain.security_contact = expectedSecContact expectedCreateCommand = self._convertPublicContactToEpp( expectedSecContact, disclose_email=False ) - + self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True) # Confirm that we are getting a default email self.assertEqual(domain.security_contact.email, expectedSecContact.email) @@ -783,7 +782,7 @@ class TestRegistrantContacts(MockEppLib): expectedCreateCommand = self._convertPublicContactToEpp( expectedSecContact, disclose_email=True ) - + self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True) # Confirm that we are getting the desired email self.assertEqual(domain.security_contact.email, expectedSecContact.email) From b824b6725fc7e0731405187ab8bc27dc73a938b4 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 4 Oct 2023 12:46:05 -0600 Subject: [PATCH 06/53] Test for technical contact on its own --- src/registrar/tests/common.py | 23 +++++++++++++++++++++++ src/registrar/tests/test_models_domain.py | 22 +++++++++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index 1257abe29..20c08b9f4 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -663,6 +663,25 @@ class MockEppLib(TestCase): ], ) + InfoDomainWithDefaultTechnicalContact = fakedEppObject( + "fakepw", + cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35), + contacts=[ + common.DomainContact( + contact="defaultTech", + type=PublicContact.ContactTypeChoices.TECHNICAL, + ) + ], + hosts=["fake.host.com"], + statuses=[ + common.Status(state="serverTransferProhibited", description="", lang="en"), + common.Status(state="inactive", description="", lang="en"), + ], + ) + + mockDefaultTechnicalContact = InfoDomainWithContacts.dummyInfoContactResultData( + "defaultTech", "dotgov@cisa.dhs.gov" + ) mockDefaultSecurityContact = InfoDomainWithContacts.dummyInfoContactResultData( "defaultSec", "dotgov@cisa.dhs.gov" ) @@ -702,6 +721,8 @@ class MockEppLib(TestCase): return MagicMock(res_data=[self.InfoDomainWithContacts]) elif getattr(_request, "name", None) == "defaultsecurity.gov": return MagicMock(res_data=[self.InfoDomainWithDefaultSecurityContact]) + elif getattr(_request, "name", None) == "defaulttechnical.gov": + return MagicMock(res_data=[self.InfoDomainWithDefaultTechnicalContact]) else: return MagicMock(res_data=[self.mockDataInfoDomain]) elif isinstance(_request, commands.InfoContact): @@ -719,6 +740,8 @@ class MockEppLib(TestCase): mocked_result = self.mockRegistrantContact case "defaultSec": mocked_result = self.mockDefaultSecurityContact + case "defaultTech": + mocked_result = self.mockDefaultTechnicalContact case _: # Default contact return mocked_result = self.mockDataInfoContact diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index 666d93c93..296389808 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -750,7 +750,6 @@ class TestRegistrantContacts(MockEppLib): Then Domain sends `commands.CreateContact` to the registry And the field `disclose` is set to false for DF.EMAIL """ - self.maxDiff = None domain, _ = Domain.objects.get_or_create(name="defaultsecurity.gov") expectedSecContact = PublicContact.get_default_security() expectedSecContact.domain = domain @@ -765,6 +764,27 @@ class TestRegistrantContacts(MockEppLib): # Confirm that we are getting a default email self.assertEqual(domain.security_contact.email, expectedSecContact.email) + def test_not_disclosed_on_default_technical_contact(self): + """ + Scenario: Registrant creates a new domain with no technical contact + When `domain.technical_contact.email` is equal to the default + Then Domain sends `commands.CreateContact` to the registry + And the field `disclose` is set to false for DF.EMAIL + """ + domain, _ = Domain.objects.get_or_create(name="defaulttechnical.gov") + expectedTechContact = PublicContact.get_default_technical() + expectedTechContact.domain = domain + expectedTechContact.registry_id = "defaultTech" + domain.technical_contact = expectedTechContact + + expectedCreateCommand = self._convertPublicContactToEpp( + expectedTechContact, disclose_email=False + ) + + self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True) + # Confirm that we are getting a default email + self.assertEqual(domain.technical_contact.email, expectedTechContact.email) + def test_is_disclosed_on_security_contact(self): """ Scenario: Registrant creates a new domain with a security email From bb193bd500371832f3eec509fbc767e89a32da6c Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 4 Oct 2023 14:15:36 -0600 Subject: [PATCH 07/53] Added comment --- src/registrar/tests/test_models_domain.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index 296389808..0c75ddb88 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -741,6 +741,7 @@ class TestRegistrantContacts(MockEppLib): contact, disclose_email=is_security ) + # Should only be disclosed if the type is security self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True) def test_not_disclosed_on_default_security_contact(self): From c788200ed3751d05c565083f96cc91dc47568621 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 4 Oct 2023 15:59:13 -0600 Subject: [PATCH 08/53] Update test_models_domain.py --- src/registrar/tests/test_models_domain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index 0c75ddb88..0e0db2810 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -741,7 +741,7 @@ class TestRegistrantContacts(MockEppLib): contact, disclose_email=is_security ) - # Should only be disclosed if the type is security + # Should only be disclosed if the type is security, as the email is valid self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True) def test_not_disclosed_on_default_security_contact(self): From 9ad183712b23d960c60d49f3a27a0bb41af7b916 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 5 Oct 2023 09:59:46 -0600 Subject: [PATCH 09/53] Test on emails --- src/registrar/tests/test_models_domain.py | 28 +++++++++++++++++------ 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index 0e0db2810..7cfc6e4b4 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -717,11 +717,18 @@ class TestRegistrantContacts(MockEppLib): # Generates a domain with four existing contacts domain, _ = Domain.objects.get_or_create(name="freeman.gov") + # Contact setup expected_admin = domain.get_default_administrative_contact() + expected_admin.email = self.mockAdministrativeContact.email + expected_registrant = domain.get_default_registrant_contact() + expected_registrant.email = self.mockRegistrantContact.email + expected_security = domain.get_default_security_contact() - expected_security.email = "security@mail.gov" + expected_security.email = self.mockSecurityContact.email + expected_tech = domain.get_default_technical_contact() + expected_tech.email = self.mockTechnicalContact.email domain.administrative_contact = expected_admin domain.registrant_contact = expected_registrant @@ -729,21 +736,28 @@ class TestRegistrantContacts(MockEppLib): domain.technical_contact = expected_tech contacts = [ - expected_admin, - expected_registrant, - expected_security, - expected_tech, + (expected_admin, domain.administrative_contact), + (expected_registrant, domain.registrant_contact), + (expected_security, domain.security_contact), + (expected_tech, domain.technical_contact), ] + # Test for each contact for contact in contacts: - is_security = contact.contact_type == "security" + expected_contact = contact[0] + actual_contact = contact[1] + is_security = actual_contact.contact_type == "security" + expectedCreateCommand = self._convertPublicContactToEpp( - contact, disclose_email=is_security + expected_contact, disclose_email=is_security ) # Should only be disclosed if the type is security, as the email is valid self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True) + # The emails should match on both items + self.assertEqual(expected_contact.email, actual_contact.email) + def test_not_disclosed_on_default_security_contact(self): """ Scenario: Registrant creates a new domain with no security email From e6e0c2c416857763d83fef8be032613e73fee0a0 Mon Sep 17 00:00:00 2001 From: Cameron Dixon Date: Fri, 6 Oct 2023 12:16:20 -0400 Subject: [PATCH 10/53] Content revision, add :emoji: names --- .github/ISSUE_TEMPLATE/issue-default.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/issue-default.yml b/.github/ISSUE_TEMPLATE/issue-default.yml index 27ec10415..2252845bf 100644 --- a/.github/ISSUE_TEMPLATE/issue-default.yml +++ b/.github/ISSUE_TEMPLATE/issue-default.yml @@ -6,13 +6,13 @@ body: id: title-help attributes: value: | - > Titles should be short, descriptive, and compelling. + > Titles should be short, descriptive, and compelling. Use sentence case. - type: textarea id: issue-description attributes: label: Issue description and context description: | - Describe the issue so that someone who wasn't present for its discovery can understand the problem and why it matters. Use full sentences, plain language, and good [formatting](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax). Share desired outcomes or potential next steps. Images or links to other content/context (like documents or Slack discussions) are welcome. + Describe the issue so that someone who wasn't present for its discovery can understand why it matters. Use full sentences, plain language, and good [formatting](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax). Screenshots and links to documents/discussions are welcome. validations: required: true - type: textarea @@ -20,13 +20,13 @@ body: attributes: label: Acceptance criteria description: "If known, share 1-3 statements that would need to be true for this issue to be considered resolved. Use a [task list](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/about-task-lists#creating-task-lists) if appropriate." - placeholder: "- [ ] The button does the thing." + placeholder: "- [ ]" - type: textarea id: links-to-other-issues attributes: label: Links to other issues description: | - Add the issue #number of other issues this relates to and how (e.g., 🚧 Blocks, ⛔️ Is blocked by, 🔄 Relates to). + "Add issue #numbers this relates to and how (e.g., 🚧 :construction: Blocks, ⛔️ :no_entry: Is blocked by, 🔄 :repeat: Relates to)." placeholder: 🔄 Relates to... - type: markdown id: note From ad607547a0b1bfe289c05860533ab6a7f061006a Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 11 Oct 2023 10:45:59 -0600 Subject: [PATCH 11/53] Fix typo --- src/registrar/tests/test_models_domain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index 7cfc6e4b4..0165000d0 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -746,7 +746,7 @@ class TestRegistrantContacts(MockEppLib): for contact in contacts: expected_contact = contact[0] actual_contact = contact[1] - is_security = actual_contact.contact_type == "security" + is_security = expected_contact.contact_type == "security" expectedCreateCommand = self._convertPublicContactToEpp( expected_contact, disclose_email=is_security From 003db40e58d73a205d027fd8674b89a716370708 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Thu, 12 Oct 2023 15:32:10 -0400 Subject: [PATCH 12/53] use shortened names for orgs in the choicefield which satisfies the admin requirement, replace short name with a long name in the user facing app in the form, summary page, manage app page --- src/registrar/admin.py | 4 +- src/registrar/forms/application_wizard.py | 3 +- ...napplication_organization_type_and_more.py | 52 +++++++++++++++++ src/registrar/models/domain_application.py | 56 ++++++++++++++----- src/registrar/models/domain_information.py | 1 + .../templates/application_review.html | 9 ++- .../templates/application_status.html | 6 +- src/registrar/templatetags/custom_filters.py | 15 +++++ src/registrar/tests/test_admin.py | 17 ++++++ src/registrar/tests/test_views.py | 28 +++++++++- 10 files changed, 171 insertions(+), 20 deletions(-) create mode 100644 src/registrar/migrations/0038_alter_domainapplication_organization_type_and_more.py diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 174500f28..aef56e0b3 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -219,9 +219,9 @@ class MyUserAdmin(BaseUserAdmin): # (which should in theory be the ONLY group) def group(self, obj): if obj.groups.filter(name="full_access_group").exists(): - return "Full access" + return "full_access_group" elif obj.groups.filter(name="cisa_analysts_group").exists(): - return "Analyst" + return "cisa_analysts_group" return "" def get_list_display(self, request): diff --git a/src/registrar/forms/application_wizard.py b/src/registrar/forms/application_wizard.py index 516683247..2fd78cdd8 100644 --- a/src/registrar/forms/application_wizard.py +++ b/src/registrar/forms/application_wizard.py @@ -153,7 +153,8 @@ class RegistrarFormSet(forms.BaseFormSet): class OrganizationTypeForm(RegistrarForm): organization_type = forms.ChoiceField( - choices=DomainApplication.OrganizationChoices.choices, + # use the long names in the application form + choices=DomainApplication.OrganizationChoicesVerbose.choices, widget=forms.RadioSelect, error_messages={"required": "Select the type of organization you represent."}, ) diff --git a/src/registrar/migrations/0038_alter_domainapplication_organization_type_and_more.py b/src/registrar/migrations/0038_alter_domainapplication_organization_type_and_more.py new file mode 100644 index 000000000..bdbca82d8 --- /dev/null +++ b/src/registrar/migrations/0038_alter_domainapplication_organization_type_and_more.py @@ -0,0 +1,52 @@ +# Generated by Django 4.2.1 on 2023-10-12 19:30 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0037_create_groups_v01"), + ] + + operations = [ + migrations.AlterField( + model_name="domainapplication", + name="organization_type", + field=models.CharField( + blank=True, + choices=[ + ("federal", "Federal"), + ("interstate", "Interstate"), + ("state_or_territory", "State or territory"), + ("tribal", "Tribal"), + ("county", "County"), + ("city", "City"), + ("special_district", "Special district"), + ("school_district", "School district"), + ], + help_text="Type of organization", + max_length=255, + null=True, + ), + ), + migrations.AlterField( + model_name="domaininformation", + name="organization_type", + field=models.CharField( + blank=True, + choices=[ + ("federal", "Federal"), + ("interstate", "Interstate"), + ("state_or_territory", "State or territory"), + ("tribal", "Tribal"), + ("county", "County"), + ("city", "City"), + ("special_district", "Special district"), + ("school_district", "School district"), + ], + help_text="Type of Organization", + max_length=255, + null=True, + ), + ), + ] diff --git a/src/registrar/models/domain_application.py b/src/registrar/models/domain_application.py index 68429d381..a4752aa88 100644 --- a/src/registrar/models/domain_application.py +++ b/src/registrar/models/domain_application.py @@ -105,28 +105,57 @@ class DomainApplication(TimeStampedModel): ARMED_FORCES_AP = "AP", "Armed Forces Pacific (AP)" class OrganizationChoices(models.TextChoices): + + """ + Primary organization choices: + For use in django admin + Keys need to match OrganizationChoicesVerbose + """ + + FEDERAL = "federal", "Federal" + INTERSTATE = "interstate", "Interstate" + STATE_OR_TERRITORY = "state_or_territory", "State or territory" + TRIBAL = "tribal", "Tribal" + COUNTY = "county", "County" + CITY = "city", "City" + SPECIAL_DISTRICT = "special_district", "Special district" + SCHOOL_DISTRICT = "school_district", "School district" + + class OrganizationChoicesVerbose(models.TextChoices): + + """ + Secondary organization choices + For use in the application form and on the templates + Keys need to match OrganizationChoices + """ + FEDERAL = ( "federal", - "Federal: an agency of the U.S. government's executive, legislative, " - "or judicial branches", + "Federal: an agency of the U.S. government's executive, " + "legislative, or judicial branches", ) INTERSTATE = "interstate", "Interstate: an organization of two or more states" - STATE_OR_TERRITORY = "state_or_territory", ( - "State or territory: one of the 50 U.S. states, the District of " - "Columbia, American Samoa, Guam, Northern Mariana Islands, " - "Puerto Rico, or the U.S. Virgin Islands" + STATE_OR_TERRITORY = ( + "state_or_territory", + "State or territory: one of the 50 U.S. states, the District of Columbia, " + "American Samoa, Guam, Northern Mariana Islands, Puerto Rico, or the U.S. " + "Virgin Islands", ) - TRIBAL = "tribal", ( - "Tribal: a tribal government recognized by the federal or " - "a state government" + TRIBAL = ( + "tribal", + "Tribal: a tribal government recognized by the federal or a state " + "government", ) COUNTY = "county", "County: a county, parish, or borough" CITY = "city", "City: a city, town, township, village, etc." - SPECIAL_DISTRICT = "special_district", ( - "Special district: an independent organization within a single state" + SPECIAL_DISTRICT = ( + "special_district", + "Special district: an independent organization within a single state", ) - SCHOOL_DISTRICT = "school_district", ( - "School district: a school district that is not part of a local government" + SCHOOL_DISTRICT = ( + "school_district", + "School district: a school district that is not part of a local " + "government", ) class BranchChoices(models.TextChoices): @@ -297,6 +326,7 @@ class DomainApplication(TimeStampedModel): # ##### data fields from the initial form ##### organization_type = models.CharField( max_length=255, + # use the short names in Django admin choices=OrganizationChoices.choices, null=True, blank=True, diff --git a/src/registrar/models/domain_information.py b/src/registrar/models/domain_information.py index 3b93aff48..d2bc5c53d 100644 --- a/src/registrar/models/domain_information.py +++ b/src/registrar/models/domain_information.py @@ -21,6 +21,7 @@ class DomainInformation(TimeStampedModel): StateTerritoryChoices = DomainApplication.StateTerritoryChoices + # use the short names in Django admin OrganizationChoices = DomainApplication.OrganizationChoices BranchChoices = DomainApplication.BranchChoices diff --git a/src/registrar/templates/application_review.html b/src/registrar/templates/application_review.html index be81303b8..6a4dcbffd 100644 --- a/src/registrar/templates/application_review.html +++ b/src/registrar/templates/application_review.html @@ -1,5 +1,6 @@ {% extends 'application_form.html' %} {% load static url_helpers %} +{% load custom_filters %} {% block form_required_fields_help_text %} {# there are no required fields on this page so don't show this #} @@ -26,7 +27,13 @@
{{ form_titles|get_item:step }}
{% if step == Step.ORGANIZATION_TYPE %} - {{ application.get_organization_type_display|default:"Incomplete" }} + {% if application.organization_type is not None %} + {% with long_org_type=application.organization_type|get_organization_long_name %} + {{ long_org_type }} + {% endwith %} + {% else %} + Incomplete + {% endif %} {% endif %} {% if step == Step.TRIBAL_GOVERNMENT %} {{ application.tribe_name|default:"Incomplete" }} diff --git a/src/registrar/templates/application_status.html b/src/registrar/templates/application_status.html index a68c07c8a..79d0f7ff9 100644 --- a/src/registrar/templates/application_status.html +++ b/src/registrar/templates/application_status.html @@ -1,5 +1,7 @@ {% extends 'base.html' %} +{% load custom_filters %} + {% block title %}Domain request status | {{ domainapplication.requested_domain.name }} | {% endblock %} {% load static url_helpers %} @@ -50,7 +52,9 @@

Summary of your domain request

{% with heading_level='h3' %} - {% include "includes/summary_item.html" with title='Type of organization' value=domainapplication.get_organization_type_display heading_level=heading_level %} + {% with long_org_type=domainapplication.organization_type|get_organization_long_name %} + {% include "includes/summary_item.html" with title='Type of organization' value=long_org_type heading_level=heading_level %} + {% endwith %} {% if domainapplication.tribe_name %} {% include "includes/summary_item.html" with title='Tribal government' value=domainapplication.tribe_name heading_level=heading_level %} diff --git a/src/registrar/templatetags/custom_filters.py b/src/registrar/templatetags/custom_filters.py index 3614db18e..e90c3166d 100644 --- a/src/registrar/templatetags/custom_filters.py +++ b/src/registrar/templatetags/custom_filters.py @@ -1,5 +1,6 @@ from django import template import re +from registrar.models.domain_application import DomainApplication register = template.Library() @@ -48,3 +49,17 @@ def contains_checkbox(html_list): if re.search(r']*type="checkbox"', html_string): return True return False + + +@register.filter +def get_organization_long_name(organization_type): + organization_choices_dict = {} + + for name, value in DomainApplication.OrganizationChoicesVerbose.choices: + organization_choices_dict[name] = value + + long_form_type = organization_choices_dict[organization_type] + if long_form_type is not None: + return long_form_type + + return "Error" diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index 51ace34f7..b5827d3e9 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -300,6 +300,23 @@ class TestDomainApplicationAdmin(MockEppLib): self.superuser = create_superuser() self.staffuser = create_user() + def test_short_org_name_in_applications_list(self): + """ + Make sure the short name is displaying in admin on the list page + """ + self.client.force_login(self.superuser) + completed_application() + response = self.client.get("/admin/registrar/domainapplication/") + # There are 3 template references to Federal (3) plus one reference in the table + # for our actual application + self.assertContains(response, "Federal", count=4) + # This may be a bit more robust + self.assertContains( + response, 'Federal', count=1 + ) + # Now let's make sure the long description does not exist + self.assertNotContains(response, "Federal: an agency of the U.S. government") + @boto3_mocking.patching def test_save_model_sends_submitted_email(self): # make sure there is no user with this email diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index 2194b42db..32a22916e 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -141,9 +141,12 @@ class DomainApplicationTests(TestWithUser, WebTest): @boto3_mocking.patching def test_application_form_submission(self): - """Can fill out the entire form and submit. + """ + Can fill out the entire form and submit. As we add additional form pages, we need to include them here to make this test work. + + This test also looks for the long organization name on the summary page. """ num_pages_tested = 0 # elections, type_of_work, tribal_government, no_other_contacts @@ -427,7 +430,8 @@ class DomainApplicationTests(TestWithUser, WebTest): review_form = review_page.form # Review page contains all the previously entered data - self.assertContains(review_page, "Federal") + # Let's make sure the long org name is displayed + self.assertContains(review_page, "Federal: an agency of the U.S. government") self.assertContains(review_page, "Executive") self.assertContains(review_page, "Testorg") self.assertContains(review_page, "address 1") @@ -1065,6 +1069,26 @@ class DomainApplicationTests(TestWithUser, WebTest): # page = self.app.get(url) # self.assertNotContains(page, "VALUE") + def test_long_org_name_in_application(self): + """ + Make sure the long name is displaying in the application form, + org step + """ + request = self.app.get(reverse("application:")).follow() + self.assertContains(request, "Federal: an agency of the U.S. government") + + def test_long_org_name_in_application_manage(self): + """ + Make sure the long name is displaying in the application summary + page (manage your application) + """ + completed_application(status=DomainApplication.SUBMITTED, user=self.user) + home_page = self.app.get("/") + self.assertContains(home_page, "city.gov") + # click the "Edit" link + detail_page = home_page.click("Manage") + self.assertContains(detail_page, "Federal: an agency of the U.S. government") + class TestWithDomainPermissions(TestWithUser): def setUp(self): From 7b13dd4b3a68e973e0d6a204a3ff3398dff47737 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Thu, 12 Oct 2023 16:14:51 -0400 Subject: [PATCH 13/53] trigger PR pipeline --- src/registrar/models/domain_application.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/registrar/models/domain_application.py b/src/registrar/models/domain_application.py index a4752aa88..4da32ad18 100644 --- a/src/registrar/models/domain_application.py +++ b/src/registrar/models/domain_application.py @@ -107,9 +107,9 @@ class DomainApplication(TimeStampedModel): class OrganizationChoices(models.TextChoices): """ - Primary organization choices: - For use in django admin - Keys need to match OrganizationChoicesVerbose + Primary organization choices: + For use in django admin + Keys need to match OrganizationChoicesVerbose """ FEDERAL = "federal", "Federal" @@ -124,9 +124,9 @@ class DomainApplication(TimeStampedModel): class OrganizationChoicesVerbose(models.TextChoices): """ - Secondary organization choices - For use in the application form and on the templates - Keys need to match OrganizationChoices + Secondary organization choices + For use in the application form and on the templates + Keys need to match OrganizationChoices """ FEDERAL = ( From 6709ac9ddb91259f060657970086c05c50cde98e Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 13 Oct 2023 16:20:38 -0400 Subject: [PATCH 14/53] trick the sandbox by deleting a conflicting migration --- .../migrations/0035_alter_user_options.py | 42 ++++++++++++++- ...napplication_organization_type_and_more.py | 52 ------------------- 2 files changed, 41 insertions(+), 53 deletions(-) delete mode 100644 src/registrar/migrations/0038_alter_domainapplication_organization_type_and_more.py diff --git a/src/registrar/migrations/0035_alter_user_options.py b/src/registrar/migrations/0035_alter_user_options.py index 7ed81cdf5..b76f07c5d 100644 --- a/src/registrar/migrations/0035_alter_user_options.py +++ b/src/registrar/migrations/0035_alter_user_options.py @@ -1,6 +1,6 @@ # Generated by Django 4.2.1 on 2023-09-27 18:53 -from django.db import migrations +from django.db import migrations, models class Migration(migrations.Migration): @@ -18,4 +18,44 @@ class Migration(migrations.Migration): ] }, ), + migrations.AlterField( + model_name="domainapplication", + name="organization_type", + field=models.CharField( + blank=True, + choices=[ + ("federal", "Federal"), + ("interstate", "Interstate"), + ("state_or_territory", "State or territory"), + ("tribal", "Tribal"), + ("county", "County"), + ("city", "City"), + ("special_district", "Special district"), + ("school_district", "School district"), + ], + help_text="Type of organization", + max_length=255, + null=True, + ), + ), + migrations.AlterField( + model_name="domaininformation", + name="organization_type", + field=models.CharField( + blank=True, + choices=[ + ("federal", "Federal"), + ("interstate", "Interstate"), + ("state_or_territory", "State or territory"), + ("tribal", "Tribal"), + ("county", "County"), + ("city", "City"), + ("special_district", "Special district"), + ("school_district", "School district"), + ], + help_text="Type of Organization", + max_length=255, + null=True, + ), + ), ] diff --git a/src/registrar/migrations/0038_alter_domainapplication_organization_type_and_more.py b/src/registrar/migrations/0038_alter_domainapplication_organization_type_and_more.py deleted file mode 100644 index bdbca82d8..000000000 --- a/src/registrar/migrations/0038_alter_domainapplication_organization_type_and_more.py +++ /dev/null @@ -1,52 +0,0 @@ -# Generated by Django 4.2.1 on 2023-10-12 19:30 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("registrar", "0037_create_groups_v01"), - ] - - operations = [ - migrations.AlterField( - model_name="domainapplication", - name="organization_type", - field=models.CharField( - blank=True, - choices=[ - ("federal", "Federal"), - ("interstate", "Interstate"), - ("state_or_territory", "State or territory"), - ("tribal", "Tribal"), - ("county", "County"), - ("city", "City"), - ("special_district", "Special district"), - ("school_district", "School district"), - ], - help_text="Type of organization", - max_length=255, - null=True, - ), - ), - migrations.AlterField( - model_name="domaininformation", - name="organization_type", - field=models.CharField( - blank=True, - choices=[ - ("federal", "Federal"), - ("interstate", "Interstate"), - ("state_or_territory", "State or territory"), - ("tribal", "Tribal"), - ("county", "County"), - ("city", "City"), - ("special_district", "Special district"), - ("school_district", "School district"), - ], - help_text="Type of Organization", - max_length=255, - null=True, - ), - ), - ] From a2574124ffaae7eccaf3d94bb3e67aefdffe2ec7 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 13 Oct 2023 16:27:23 -0400 Subject: [PATCH 15/53] return migrations to pervious state --- .../migrations/0035_alter_user_options.py | 42 +-------------- ...napplication_organization_type_and_more.py | 52 +++++++++++++++++++ 2 files changed, 53 insertions(+), 41 deletions(-) create mode 100644 src/registrar/migrations/0038_alter_domainapplication_organization_type_and_more.py diff --git a/src/registrar/migrations/0035_alter_user_options.py b/src/registrar/migrations/0035_alter_user_options.py index b76f07c5d..7ed81cdf5 100644 --- a/src/registrar/migrations/0035_alter_user_options.py +++ b/src/registrar/migrations/0035_alter_user_options.py @@ -1,6 +1,6 @@ # Generated by Django 4.2.1 on 2023-09-27 18:53 -from django.db import migrations, models +from django.db import migrations class Migration(migrations.Migration): @@ -18,44 +18,4 @@ class Migration(migrations.Migration): ] }, ), - migrations.AlterField( - model_name="domainapplication", - name="organization_type", - field=models.CharField( - blank=True, - choices=[ - ("federal", "Federal"), - ("interstate", "Interstate"), - ("state_or_territory", "State or territory"), - ("tribal", "Tribal"), - ("county", "County"), - ("city", "City"), - ("special_district", "Special district"), - ("school_district", "School district"), - ], - help_text="Type of organization", - max_length=255, - null=True, - ), - ), - migrations.AlterField( - model_name="domaininformation", - name="organization_type", - field=models.CharField( - blank=True, - choices=[ - ("federal", "Federal"), - ("interstate", "Interstate"), - ("state_or_territory", "State or territory"), - ("tribal", "Tribal"), - ("county", "County"), - ("city", "City"), - ("special_district", "Special district"), - ("school_district", "School district"), - ], - help_text="Type of Organization", - max_length=255, - null=True, - ), - ), ] diff --git a/src/registrar/migrations/0038_alter_domainapplication_organization_type_and_more.py b/src/registrar/migrations/0038_alter_domainapplication_organization_type_and_more.py new file mode 100644 index 000000000..a06ea0451 --- /dev/null +++ b/src/registrar/migrations/0038_alter_domainapplication_organization_type_and_more.py @@ -0,0 +1,52 @@ +# Generated by Django 4.2.1 on 2023-10-13 20:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0037_create_groups_v01"), + ] + + operations = [ + migrations.AlterField( + model_name="domainapplication", + name="organization_type", + field=models.CharField( + blank=True, + choices=[ + ("federal", "Federal"), + ("interstate", "Interstate"), + ("state_or_territory", "State or territory"), + ("tribal", "Tribal"), + ("county", "County"), + ("city", "City"), + ("special_district", "Special district"), + ("school_district", "School district"), + ], + help_text="Type of organization", + max_length=255, + null=True, + ), + ), + migrations.AlterField( + model_name="domaininformation", + name="organization_type", + field=models.CharField( + blank=True, + choices=[ + ("federal", "Federal"), + ("interstate", "Interstate"), + ("state_or_territory", "State or territory"), + ("tribal", "Tribal"), + ("county", "County"), + ("city", "City"), + ("special_district", "Special district"), + ("school_district", "School district"), + ], + help_text="Type of Organization", + max_length=255, + null=True, + ), + ), + ] From 5e787eeed324f8789c8a1e3e9dcf0694d13d588a Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 13 Oct 2023 16:30:29 -0400 Subject: [PATCH 16/53] merge diverging migrations --- src/registrar/migrations/0039_merge_20231013_2029.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/registrar/migrations/0039_merge_20231013_2029.py diff --git a/src/registrar/migrations/0039_merge_20231013_2029.py b/src/registrar/migrations/0039_merge_20231013_2029.py new file mode 100644 index 000000000..aed231bdc --- /dev/null +++ b/src/registrar/migrations/0039_merge_20231013_2029.py @@ -0,0 +1,12 @@ +# Generated by Django 4.2.1 on 2023-10-13 20:29 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0038_alter_domainapplication_organization_type_and_more"), + ("registrar", "0038_create_groups_v02"), + ] + + operations = [] From 372ead1121372d885b19fc56fce641f1cb6b7541 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 13 Oct 2023 17:05:33 -0400 Subject: [PATCH 17/53] refactor org literal mapping to dict, tweak error handing, lint --- src/registrar/models/domain_application.py | 12 ++++++------ src/registrar/templatetags/custom_filters.py | 17 ++++++++++------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/registrar/models/domain_application.py b/src/registrar/models/domain_application.py index 4da32ad18..a4752aa88 100644 --- a/src/registrar/models/domain_application.py +++ b/src/registrar/models/domain_application.py @@ -107,9 +107,9 @@ class DomainApplication(TimeStampedModel): class OrganizationChoices(models.TextChoices): """ - Primary organization choices: - For use in django admin - Keys need to match OrganizationChoicesVerbose + Primary organization choices: + For use in django admin + Keys need to match OrganizationChoicesVerbose """ FEDERAL = "federal", "Federal" @@ -124,9 +124,9 @@ class DomainApplication(TimeStampedModel): class OrganizationChoicesVerbose(models.TextChoices): """ - Secondary organization choices - For use in the application form and on the templates - Keys need to match OrganizationChoices + Secondary organization choices + For use in the application form and on the templates + Keys need to match OrganizationChoices """ FEDERAL = ( diff --git a/src/registrar/templatetags/custom_filters.py b/src/registrar/templatetags/custom_filters.py index e90c3166d..158b7269e 100644 --- a/src/registrar/templatetags/custom_filters.py +++ b/src/registrar/templatetags/custom_filters.py @@ -1,8 +1,10 @@ +import logging from django import template import re from registrar.models.domain_application import DomainApplication register = template.Library() +logger = logging.getLogger(__name__) @register.filter(name="extract_value") @@ -53,13 +55,14 @@ def contains_checkbox(html_list): @register.filter def get_organization_long_name(organization_type): - organization_choices_dict = {} - - for name, value in DomainApplication.OrganizationChoicesVerbose.choices: - organization_choices_dict[name] = value + # https://gist.github.com/OmenApps/3eef60ba4204f3d1842d9d7477efcce1#file-django_choices-txt-L28 + organization_choices_dict = dict( + DomainApplication.OrganizationChoicesVerbose.choices + ) long_form_type = organization_choices_dict[organization_type] - if long_form_type is not None: - return long_form_type + if long_form_type is None: + logger.error("Organization type error, triggered by a template's custom filter") + return "Error" - return "Error" + return long_form_type From 3ed4c0e4fbbbfdf5b69d7a6d4c1af0d5ec90a61c Mon Sep 17 00:00:00 2001 From: Erin <121973038+erinysong@users.noreply.github.com> Date: Tue, 17 Oct 2023 08:37:03 -0700 Subject: [PATCH 18/53] Update availability API to use EPP availability check --- src/! | 1 + src/api/tests/test_available.py | 78 +++++++++++++++++++++++-------- src/api/views.py | 6 +-- src/registrar/tests/common.py | 37 ++++++++++++++- src/registrar/tests/test_forms.py | 11 ++++- 5 files changed, 108 insertions(+), 25 deletions(-) create mode 100644 src/! diff --git a/src/! b/src/! new file mode 100644 index 000000000..19765bd50 --- /dev/null +++ b/src/! @@ -0,0 +1 @@ +null diff --git a/src/api/tests/test_available.py b/src/api/tests/test_available.py index 0bbe01f03..3d01228c3 100644 --- a/src/api/tests/test_available.py +++ b/src/api/tests/test_available.py @@ -7,21 +7,34 @@ from django.test import TestCase, RequestFactory from ..views import available, _domains, in_domains from .common import less_console_noise +from registrar.tests.common import MockEppLib +from unittest.mock import MagicMock, patch, call + +from epplibwrapper import ( + commands, + common, + extensions, + responses, + RegistryError, + ErrorCode, +) API_BASE_PATH = "/api/v1/available/" +from registrar.models import Domain - -class AvailableViewTest(TestCase): +class AvailableViewTest(MockEppLib): """Test that the view function works as expected.""" def setUp(self): + super().setUp() self.user = get_user_model().objects.create(username="username") self.factory = RequestFactory() def test_view_function(self): request = self.factory.get(API_BASE_PATH + "test.gov") request.user = self.user + response = available(request, domain="test.gov") # has the right text in it self.assertContains(response, "available") @@ -29,28 +42,43 @@ class AvailableViewTest(TestCase): response_object = json.loads(response.content) self.assertIn("available", response_object) - def test_domain_list(self): - """Test the domain list that is returned from Github. + def test_makes_calls(self): + gsa_available = in_domains("gsa.gov") + igorville_available = in_domains("igorvilleremixed.gov") - This does not mock out the external file, it is actually fetched from - the internet. - """ - domains = _domains() - self.assertIn("gsa.gov", domains) - # entries are all lowercase so GSA.GOV is not in the set - self.assertNotIn("GSA.GOV", domains) - self.assertNotIn("igorvilleremixed.gov", domains) - # all the entries have dots - self.assertNotIn("gsa", domains) + self.mockedSendFunction.assert_has_calls( + [ + call( + commands.CheckDomain( + ["gsa.gov"], + ), + cleaned=True, + ), + call( + commands.CheckDomain( + ["igorvilleremixed.gov"], + ), + cleaned=True, + ) + ] + ) def test_in_domains(self): - self.assertTrue(in_domains("gsa.gov")) + gsa_available = in_domains("gsa.gov") + gsa_caps_available = in_domains("GSA.gov") + igorville_available = in_domains("igorvilleremixed.gov") + + self.assertTrue(gsa_available) # input is lowercased so GSA.GOV should be found - self.assertTrue(in_domains("GSA.GOV")) + self.assertTrue(gsa_caps_available) # This domain should not have been registered - self.assertFalse(in_domains("igorvilleremixed.gov")) - + self.assertFalse(igorville_available) + def test_in_domains_dotgov(self): + gsa_available = in_domains("gsa.gov") + gsa_caps_available = in_domains("GSA.gov") + igorville_available = in_domains("igorvilleremixed.gov") + """Domain searches work without trailing .gov""" self.assertTrue(in_domains("gsa")) # input is lowercased so GSA.GOV should be found @@ -58,6 +86,14 @@ class AvailableViewTest(TestCase): # This domain should not have been registered self.assertFalse(in_domains("igorvilleremixed")) + def test_in_domains_capitalized(self): + gsa_available = in_domains("gsa.gov") + capitalized_gsa_available = in_domains("GSA.gov") + + """Domain searches work without case sensitivity""" + self.assertTrue(in_domains("gsa.gov")) + self.assertTrue(in_domains("GSA.gov")) + def test_not_available_domain(self): """gsa.gov is not available""" request = self.factory.get(API_BASE_PATH + "gsa.gov") @@ -86,13 +122,17 @@ class AvailableViewTest(TestCase): request.user = self.user response = available(request, domain=bad_string) self.assertFalse(json.loads(response.content)["available"]) + # domain set to raise error successfully raises error + with self.assertRaises(RegistryError): + error_domain_available = available(request, "errordomain.gov") -class AvailableAPITest(TestCase): +class AvailableAPITest(MockEppLib): """Test that the API can be called as expected.""" def setUp(self): + super().setUp() self.user = get_user_model().objects.create(username="username") def test_available_get(self): diff --git a/src/api/views.py b/src/api/views.py index e19e060ef..02e419a91 100644 --- a/src/api/views.py +++ b/src/api/views.py @@ -59,12 +59,12 @@ def in_domains(domain): given domain doesn't end with .gov, ".gov" is added when looking for a match. """ - domain = domain.lower() + Domain = apps.get_model("registrar.Domain") if domain.endswith(".gov"): - return domain.lower() in _domains() + return Domain.available(domain) else: # domain search string doesn't end with .gov, add it on here - return (domain + ".gov") in _domains() + return Domain.available(domain + ".gov") @require_http_methods(["GET"]) diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index b8fea7f93..0144738e2 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -30,6 +30,7 @@ from epplibwrapper import ( info, RegistryError, ErrorCode, + responses, ) logger = logging.getLogger(__name__) @@ -824,7 +825,41 @@ class MockEppLib(TestCase): raise RegistryError( code=ErrorCode.OBJECT_ASSOCIATION_PROHIBITS_OPERATION ) - + elif isinstance(_request, commands.CheckDomain): + if "gsa.gov" in getattr(_request, "names", None): + return MagicMock( + res_data=[ + responses.check.CheckDomainResultData( + name="gsa.gov", avail=True, reason=None + ), + ] + ) + elif "GSA.gov" in getattr(_request, "names", None): + return MagicMock( + res_data=[ + responses.check.CheckDomainResultData( + name="GSA.gov", avail=True, reason=None + ), + ] + ) + elif "igorvilleremixed.gov" in getattr(_request, "names", None): + return MagicMock( + res_data=[ + responses.check.CheckDomainResultData( + name="igorvilleremixed.gov", avail=False, reason=None + ), + ] + ) + elif "errordomain.gov" in getattr(_request, "names", None): + raise RegistryError("Registry cannot find domain availability.") + else: + return MagicMock( + res_data=[ + responses.check.CheckDomainResultData( + name="domainnotfound.gov", avail=False, reason="In Use" + ) + ], + ) return MagicMock(res_data=[self.mockDataInfoHosts]) def setUp(self): diff --git a/src/registrar/tests/test_forms.py b/src/registrar/tests/test_forms.py index 95be195ba..4b1aeb12c 100644 --- a/src/registrar/tests/test_forms.py +++ b/src/registrar/tests/test_forms.py @@ -1,6 +1,6 @@ """Test form validation requirements.""" -from django.test import TestCase +from django.test import TestCase, RequestFactory from registrar.forms.application_wizard import ( CurrentSitesForm, @@ -16,9 +16,16 @@ from registrar.forms.application_wizard import ( AboutYourOrganizationForm, ) from registrar.forms.domain import ContactForm +from registrar.tests.common import MockEppLib +from django.contrib.auth import get_user_model -class TestFormValidation(TestCase): +class TestFormValidation(MockEppLib): + def setUp(self): + super().setUp() + self.user = get_user_model().objects.create(username="username") + self.factory = RequestFactory() + def test_org_contact_zip_invalid(self): form = OrganizationContactForm(data={"zipcode": "nah"}) self.assertEqual( From dcaaa7099bd8ec1bdb22a18bae46b7e69819974e Mon Sep 17 00:00:00 2001 From: Erin <121973038+erinysong@users.noreply.github.com> Date: Tue, 17 Oct 2023 08:53:04 -0700 Subject: [PATCH 19/53] Delete unknown ! file added --- src/! | 1 - 1 file changed, 1 deletion(-) delete mode 100644 src/! diff --git a/src/! b/src/! deleted file mode 100644 index 19765bd50..000000000 --- a/src/! +++ /dev/null @@ -1 +0,0 @@ -null From 981af109dff15247c62144d779f8dd0838544b5e Mon Sep 17 00:00:00 2001 From: Erin <121973038+erinysong@users.noreply.github.com> Date: Tue, 17 Oct 2023 14:00:34 -0700 Subject: [PATCH 20/53] Fix subset of linter errors --- src/api/tests/test_available.py | 50 ++++++++++------------------- src/registrar/tests/common.py | 57 +++++++++++++-------------------- 2 files changed, 40 insertions(+), 67 deletions(-) diff --git a/src/api/tests/test_available.py b/src/api/tests/test_available.py index 3d01228c3..9eab17bf7 100644 --- a/src/api/tests/test_available.py +++ b/src/api/tests/test_available.py @@ -3,24 +3,20 @@ import json from django.contrib.auth import get_user_model -from django.test import TestCase, RequestFactory +from django.test import RequestFactory -from ..views import available, _domains, in_domains +from ..views import available, in_domains from .common import less_console_noise from registrar.tests.common import MockEppLib -from unittest.mock import MagicMock, patch, call +from unittest.mock import call from epplibwrapper import ( commands, - common, - extensions, - responses, RegistryError, - ErrorCode, ) API_BASE_PATH = "/api/v1/available/" -from registrar.models import Domain + class AvailableViewTest(MockEppLib): @@ -34,7 +30,6 @@ class AvailableViewTest(MockEppLib): def test_view_function(self): request = self.factory.get(API_BASE_PATH + "test.gov") request.user = self.user - response = available(request, domain="test.gov") # has the right text in it self.assertContains(response, "available") @@ -42,10 +37,12 @@ class AvailableViewTest(MockEppLib): response_object = json.loads(response.content) self.assertIn("available", response_object) - def test_makes_calls(self): + def test_in_domains_makes_calls_(self): + """Domain searches successfully make correct mock EPP calls""" gsa_available = in_domains("gsa.gov") igorville_available = in_domains("igorvilleremixed.gov") + """Domain searches successfully make mock EPP calls""" self.mockedSendFunction.assert_has_calls( [ call( @@ -59,26 +56,20 @@ class AvailableViewTest(MockEppLib): ["igorvilleremixed.gov"], ), cleaned=True, - ) + ), ] ) - - def test_in_domains(self): - gsa_available = in_domains("gsa.gov") - gsa_caps_available = in_domains("GSA.gov") - igorville_available = in_domains("igorvilleremixed.gov") - + """Domain searches return correct availability results""" self.assertTrue(gsa_available) - # input is lowercased so GSA.GOV should be found - self.assertTrue(gsa_caps_available) - # This domain should not have been registered self.assertFalse(igorville_available) - - def test_in_domains_dotgov(self): - gsa_available = in_domains("gsa.gov") - gsa_caps_available = in_domains("GSA.gov") - igorville_available = in_domains("igorvilleremixed.gov") + def test_in_domains_capitalized(self): + """Domain searches work without case sensitivity""" + self.assertTrue(in_domains("gsa.gov")) + # input is lowercased so GSA.GOV should be found + self.assertTrue(in_domains("GSA.gov")) + + def test_in_domains_dotgov(self): """Domain searches work without trailing .gov""" self.assertTrue(in_domains("gsa")) # input is lowercased so GSA.GOV should be found @@ -86,14 +77,6 @@ class AvailableViewTest(MockEppLib): # This domain should not have been registered self.assertFalse(in_domains("igorvilleremixed")) - def test_in_domains_capitalized(self): - gsa_available = in_domains("gsa.gov") - capitalized_gsa_available = in_domains("GSA.gov") - - """Domain searches work without case sensitivity""" - self.assertTrue(in_domains("gsa.gov")) - self.assertTrue(in_domains("GSA.gov")) - def test_not_available_domain(self): """gsa.gov is not available""" request = self.factory.get(API_BASE_PATH + "gsa.gov") @@ -125,6 +108,7 @@ class AvailableViewTest(MockEppLib): # domain set to raise error successfully raises error with self.assertRaises(RegistryError): error_domain_available = available(request, "errordomain.gov") + self.assertFalse(json.loads(error_domain_available.content)["available"]) class AvailableAPITest(MockEppLib): diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index 0144738e2..32117e2db 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -761,6 +761,28 @@ class MockEppLib(TestCase): return MagicMock(res_data=[self.infoDomainThreeHosts]) return MagicMock(res_data=[self.mockDataInfoDomain]) + def _mockDomainName(self, _name, _avail=False): + return MagicMock( + res_data=[ + responses.check.CheckDomainResultData( + name=_name, avail=_avail, reason=None + ), + ] + ) + + def _handleCheckDomain(self, _request): + print(getattr(_request, "names", None)) + if "gsa.gov" in getattr(_request, "names", None): + return self._mockDomainName("gsa.gov", True) + elif "GSA.gov" in getattr(_request, "names", None): + return self._mockDomainName("GSA.gov", True) + elif "igorvilleremixed.gov" in getattr(_request, "names", None): + return self._mockDomainName("igorvilleremixed.gov", False) + elif "errordomain.gov" in getattr(_request, "names", None): + raise RegistryError("Registry cannot find domain availability.") + else: + return self._mockDomainName("domainnotfound.gov", False) + def mockSend(self, _request, cleaned): """Mocks the registry.send function used inside of domain.py registry is imported from epplibwrapper @@ -826,40 +848,7 @@ class MockEppLib(TestCase): code=ErrorCode.OBJECT_ASSOCIATION_PROHIBITS_OPERATION ) elif isinstance(_request, commands.CheckDomain): - if "gsa.gov" in getattr(_request, "names", None): - return MagicMock( - res_data=[ - responses.check.CheckDomainResultData( - name="gsa.gov", avail=True, reason=None - ), - ] - ) - elif "GSA.gov" in getattr(_request, "names", None): - return MagicMock( - res_data=[ - responses.check.CheckDomainResultData( - name="GSA.gov", avail=True, reason=None - ), - ] - ) - elif "igorvilleremixed.gov" in getattr(_request, "names", None): - return MagicMock( - res_data=[ - responses.check.CheckDomainResultData( - name="igorvilleremixed.gov", avail=False, reason=None - ), - ] - ) - elif "errordomain.gov" in getattr(_request, "names", None): - raise RegistryError("Registry cannot find domain availability.") - else: - return MagicMock( - res_data=[ - responses.check.CheckDomainResultData( - name="domainnotfound.gov", avail=False, reason="In Use" - ) - ], - ) + return self._handleCheckDomain(_request) return MagicMock(res_data=[self.mockDataInfoHosts]) def setUp(self): From 34294782c3a887f3ef1b5f99d45202bac4a59365 Mon Sep 17 00:00:00 2001 From: Rebecca Hsieh Date: Tue, 17 Oct 2023 15:24:38 -0700 Subject: [PATCH 21/53] Update Staff permissions for contacts, websites, addresses and domain information and application --- src/registrar/admin.py | 26 ++++++++++--- .../migrations/0040_create_groups_v03.py | 37 +++++++++++++++++++ src/registrar/models/user_group.py | 7 +++- 3 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 src/registrar/migrations/0040_create_groups_v03.py diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 8d0ed8c2e..0533929f6 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -294,6 +294,26 @@ class ContactAdmin(ListHeaderAdmin): contact.admin_order_field = "first_name" # type: ignore + # Read only that we'll leverage for CISA Analysts + analyst_readonly_fields = [ + "user", + ] + + def get_readonly_fields(self, request, obj=None): + """Set the read-only state on form elements. + We have 1 conditions that determine which fields are read-only: + admin user permissions. + """ + + readonly_fields = list(self.readonly_fields) + + if request.user.has_perm("registrar.full_access_permission"): + return readonly_fields + # Return restrictive Read-only fields for analysts and + # users who might not belong to groups + readonly_fields.extend([field for field in self.analyst_readonly_fields]) + return readonly_fields # Read-only fields for analysts + class WebsiteAdmin(ListHeaderAdmin): """Custom website admin class.""" @@ -420,9 +440,6 @@ class DomainInformationAdmin(ListHeaderAdmin): "creator", "type_of_work", "more_organization_information", - "address_line1", - "address_line2", - "zipcode", "domain", "submitter", "no_other_contacts_rationale", @@ -557,9 +574,6 @@ class DomainApplicationAdmin(ListHeaderAdmin): analyst_readonly_fields = [ "creator", "about_your_organization", - "address_line1", - "address_line2", - "zipcode", "requested_domain", "alternative_domains", "purpose", diff --git a/src/registrar/migrations/0040_create_groups_v03.py b/src/registrar/migrations/0040_create_groups_v03.py new file mode 100644 index 000000000..6885b9dfc --- /dev/null +++ b/src/registrar/migrations/0040_create_groups_v03.py @@ -0,0 +1,37 @@ +# This migration creates the create_full_access_group and create_cisa_analyst_group groups +# It is dependent on 0035 (which populates ContentType and Permissions) +# 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", "0039_alter_transitiondomain_status"), + ] + + operations = [ + migrations.RunPython( + create_groups, + reverse_code=migrations.RunPython.noop, + atomic=True, + ), + ] \ No newline at end of file diff --git a/src/registrar/models/user_group.py b/src/registrar/models/user_group.py index 5cdb1f2ec..568741786 100644 --- a/src/registrar/models/user_group.py +++ b/src/registrar/models/user_group.py @@ -24,7 +24,7 @@ class UserGroup(Group): { "app_label": "registrar", "model": "contact", - "permissions": ["view_contact"], + "permissions": ["change_contact"], }, { "app_label": "registrar", @@ -56,6 +56,11 @@ class UserGroup(Group): "model": "domaininvitation", "permissions": ["add_domaininvitation", "view_domaininvitation"], }, + { + "app_label": "registrar", + "model": "website", + "permissions": ["change_website"], + }, ] # Avoid error: You can't execute queries until the end From a87ebfc287fef3f886133471a4b6bb8f37adf822 Mon Sep 17 00:00:00 2001 From: Rebecca Hsieh Date: Tue, 17 Oct 2023 15:40:11 -0700 Subject: [PATCH 22/53] Fix linter errors and tests --- src/registrar/migrations/0040_create_groups_v03.py | 2 +- src/registrar/tests/test_admin.py | 3 --- src/registrar/tests/test_migrations.py | 3 ++- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/registrar/migrations/0040_create_groups_v03.py b/src/registrar/migrations/0040_create_groups_v03.py index 6885b9dfc..cad2cadc5 100644 --- a/src/registrar/migrations/0040_create_groups_v03.py +++ b/src/registrar/migrations/0040_create_groups_v03.py @@ -34,4 +34,4 @@ class Migration(migrations.Migration): reverse_code=migrations.RunPython.noop, atomic=True, ), - ] \ No newline at end of file + ] diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index 51ace34f7..7dbc8ff38 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -620,9 +620,6 @@ class TestDomainApplicationAdmin(MockEppLib): expected_fields = [ "creator", "about_your_organization", - "address_line1", - "address_line2", - "zipcode", "requested_domain", "alternative_domains", "purpose", diff --git a/src/registrar/tests/test_migrations.py b/src/registrar/tests/test_migrations.py index 95e5853ff..165ef6f71 100644 --- a/src/registrar/tests/test_migrations.py +++ b/src/registrar/tests/test_migrations.py @@ -36,7 +36,7 @@ class TestGroups(TestCase): # Define the expected permission codenames expected_permissions = [ "view_logentry", - "view_contact", + "change_contact", "view_domain", "change_domainapplication", "change_domaininformation", @@ -45,6 +45,7 @@ class TestGroups(TestCase): "change_draftdomain", "analyst_access_permission", "change_user", + "change_website", ] # Get the codenames of actual permissions associated with the group From 7d6155de1340a0dfba51b87af878200f39502c0f Mon Sep 17 00:00:00 2001 From: Erin <121973038+erinysong@users.noreply.github.com> Date: Tue, 17 Oct 2023 16:06:57 -0700 Subject: [PATCH 23/53] Refactor mockSend to match linter --- src/registrar/tests/common.py | 98 ++++++++++++++++++----------------- 1 file changed, 50 insertions(+), 48 deletions(-) diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index 32117e2db..61444d37f 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -769,9 +769,8 @@ class MockEppLib(TestCase): ), ] ) - + def _handleCheckDomain(self, _request): - print(getattr(_request, "names", None)) if "gsa.gov" in getattr(_request, "names", None): return self._mockDomainName("gsa.gov", True) elif "GSA.gov" in getattr(_request, "names", None): @@ -788,28 +787,8 @@ class MockEppLib(TestCase): registry is imported from epplibwrapper returns objects that simulate what would be in a epp response but only relevant pieces for tests""" - if isinstance(_request, commands.InfoDomain): - return self._getattrInfoDomain(_request) - - elif isinstance(_request, commands.InfoContact): - mocked_result: info.InfoContactResultData - - # For testing contact types - match getattr(_request, "id", None): - case "securityContact": - mocked_result = self.mockSecurityContact - case "technicalContact": - mocked_result = self.mockTechnicalContact - case "adminContact": - mocked_result = self.mockAdministrativeContact - case "regContact": - mocked_result = self.mockRegistrantContact - case _: - # Default contact return - mocked_result = self.mockDataInfoContact - - return MagicMock(res_data=[mocked_result]) - elif ( + print(type(_request) == commands.CheckDomain) + if ( isinstance(_request, commands.CreateContact) and getattr(_request, "id", None) == "fail" and self.mockedSendFunction.call_count == 3 @@ -817,27 +796,8 @@ class MockEppLib(TestCase): # use this for when a contact is being updated # sets the second send() to fail raise RegistryError(code=ErrorCode.OBJECT_EXISTS) - elif isinstance(_request, commands.CreateHost): - return MagicMock( - res_data=[self.mockDataHostChange], - code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY, - ) - elif isinstance(_request, commands.UpdateHost): - return MagicMock( - res_data=[self.mockDataHostChange], - code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY, - ) - elif isinstance(_request, commands.UpdateDomain): - return MagicMock( - res_data=[self.mockDataHostChange], - code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY, - ) - elif isinstance(_request, commands.DeleteHost): - return MagicMock( - res_data=[self.mockDataHostChange], - code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY, - ) - elif ( + + if ( isinstance(_request, commands.DeleteDomain) and getattr(_request, "name", None) == "failDelete.gov" ): @@ -847,9 +807,51 @@ class MockEppLib(TestCase): raise RegistryError( code=ErrorCode.OBJECT_ASSOCIATION_PROHIBITS_OPERATION ) - elif isinstance(_request, commands.CheckDomain): - return self._handleCheckDomain(_request) - return MagicMock(res_data=[self.mockDataInfoHosts]) + + match type(_request): + case commands.InfoDomain: + return self._getattrInfoDomain(_request) + case commands.InfoContact: + mocked_result: info.InfoContactResultData + + # For testing contact types + match getattr(_request, "id", None): + case "securityContact": + mocked_result = self.mockSecurityContact + case "technicalContact": + mocked_result = self.mockTechnicalContact + case "adminContact": + mocked_result = self.mockAdministrativeContact + case "regContact": + mocked_result = self.mockRegistrantContact + case _: + # Default contact return + mocked_result = self.mockDataInfoContact + return MagicMock(res_data=[mocked_result]) + case commands.CreateHost: + return MagicMock( + res_data=[self.mockDataHostChange], + code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY, + ) + case commands.UpdateHost: + return MagicMock( + res_data=[self.mockDataHostChange], + code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY, + ) + case commands.UpdateDomain: + return MagicMock( + res_data=[self.mockDataHostChange], + code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY, + ) + case commands.DeleteHost: + return MagicMock( + res_data=[self.mockDataHostChange], + code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY, + ) + case commands.CheckDomain: + return self._handleCheckDomain(_request) + case _: + return MagicMock(res_data=[self.mockDataInfoHosts]) def setUp(self): """mock epp send function as this will fail locally""" From 02a9c98a570f0c1d58a5d47f53e947990065a081 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Wed, 18 Oct 2023 17:49:49 -0400 Subject: [PATCH 24/53] fix org in domain table and write a unit test for it --- src/registrar/admin.py | 2 +- src/registrar/tests/test_admin.py | 25 ++++++++++++++++++++++++- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 88f0de869..417d23708 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -721,7 +721,7 @@ class DomainAdmin(ListHeaderAdmin): ] def organization_type(self, obj): - return obj.domain_info.organization_type + return obj.domain_info.get_organization_type_display() organization_type.admin_order_field = ( # type: ignore "domain_info__organization_type" diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index b5827d3e9..805e97171 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -52,6 +52,26 @@ class TestDomainAdmin(MockEppLib): self.factory = RequestFactory() super().setUp() + def test_short_org_name_in_domains_list(self): + """ + Make sure the short name is displaying in admin on the list page + """ + self.client.force_login(self.superuser) + application = completed_application(status=DomainApplication.IN_REVIEW) + application.approve() + + response = self.client.get("/admin/registrar/domain/") + + # There are 3 template references to Federal (3) plus one reference in the table + # for our actual application + self.assertContains(response, "Federal", count=4) + # This may be a bit more robust + self.assertContains( + response, 'Federal', count=1 + ) + # Now let's make sure the long description does not exist + self.assertNotContains(response, "Federal: an agency of the U.S. government") + @skip("Why did this test stop working, and is is a good test") def test_place_and_remove_hold(self): domain = create_ready_domain() @@ -243,8 +263,11 @@ class TestDomainAdmin(MockEppLib): raise def tearDown(self): - User.objects.all().delete() super().tearDown() + Domain.objects.all().delete() + DomainInformation.objects.all().delete() + DomainApplication.objects.all().delete() + User.objects.all().delete() class TestDomainApplicationAdminForm(TestCase): From d01bebec4110dc247fd3a882a86cdd07c9a9af4f Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Wed, 18 Oct 2023 18:03:22 -0400 Subject: [PATCH 25/53] merge conflicting migrations --- src/registrar/migrations/0040_merge_20231018_2203.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/registrar/migrations/0040_merge_20231018_2203.py diff --git a/src/registrar/migrations/0040_merge_20231018_2203.py b/src/registrar/migrations/0040_merge_20231018_2203.py new file mode 100644 index 000000000..fad679098 --- /dev/null +++ b/src/registrar/migrations/0040_merge_20231018_2203.py @@ -0,0 +1,12 @@ +# Generated by Django 4.2.1 on 2023-10-18 22:03 + +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0039_alter_transitiondomain_status"), + ("registrar", "0039_merge_20231013_2029"), + ] + + operations = [] From 1791db42035c19a85bef66bed4a99fdcc0f99a52 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Wed, 18 Oct 2023 18:57:58 -0400 Subject: [PATCH 26/53] add more specificity to CSS selectors causing a btn color bug --- src/registrar/assets/sass/_theme/_buttons.scss | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/registrar/assets/sass/_theme/_buttons.scss b/src/registrar/assets/sass/_theme/_buttons.scss index 718bd5792..0857ec603 100644 --- a/src/registrar/assets/sass/_theme/_buttons.scss +++ b/src/registrar/assets/sass/_theme/_buttons.scss @@ -22,15 +22,15 @@ a.breadcrumb__back { } } -a.usa-button { +a.usa-button:not(.usa-button--unstyled, .usa-button--outline) { text-decoration: none; color: color('white'); } -a.usa-button:visited, -a.usa-button:hover, -a.usa-button:focus, -a.usa-button:active { +a.usa-button:not(.usa-button--unstyled, .usa-button--outline):visited, +a.usa-button:not(.usa-button--unstyled, .usa-button--outline):hover, +a.usa-button:not(.usa-button--unstyled, .usa-button--outline):focus, +a.usa-button:not(.usa-button--unstyled, .usa-button--outline):active { color: color('white'); } From eebe6e117efb9f2ef9e5cdf1fe81e9882a45959c Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 19 Oct 2023 10:50:18 -0600 Subject: [PATCH 27/53] Fix merge issue --- src/registrar/tests/common.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index 94e3e2d3e..f6c6c1f2d 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -900,11 +900,10 @@ class MockEppLib(TestCase): "namerserversubdomain.gov": (self.infoDomainCheckHostIPCombo, None), "freeman.gov": (self.InfoDomainWithContacts, None), "threenameserversDomain.gov": (self.infoDomainThreeHosts, None), + "defaultsecurity.gov": (self.InfoDomainWithDefaultSecurityContact, None), + "defaulttechnical.gov": (self.InfoDomainWithDefaultTechnicalContact, None) } - TODO = elif getattr(_request, "name", None) == "defaultsecurity.gov": - return MagicMock(res_data=[self.InfoDomainWithDefaultSecurityContact]) - elif getattr(_request, "name", None) == "defaulttechnical.gov": - return MagicMock(res_data=[self.InfoDomainWithDefaultTechnicalContact]) + # Retrieve the corresponding values from the dictionary res_data, extensions = request_mappings.get( request_name, (self.mockDataInfoDomain, None) From e26db14a29b441d632f083ffcbc0acb62233c186 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 19 Oct 2023 10:53:54 -0600 Subject: [PATCH 28/53] Fix merge weirdness --- src/registrar/tests/common.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index f6c6c1f2d..acda90a75 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -917,23 +917,23 @@ class MockEppLib(TestCase): def mockInfoContactCommands(self, _request, cleaned): mocked_result: info.InfoContactResultData - # For testing contact types - match getattr(_request, "id", None): - case "securityContact": - mocked_result = self.mockSecurityContact - case "technicalContact": - mocked_result = self.mockTechnicalContact - case "adminContact": - mocked_result = self.mockAdministrativeContact - case "regContact": - mocked_result = self.mockRegistrantContact - case "defaultSec": - mocked_result = self.mockDefaultSecurityContact - case "defaultTech": - mocked_result = self.mockDefaultTechnicalContact - case _: - # Default contact return - mocked_result = self.mockDataInfoContact + # For testing contact types + match getattr(_request, "id", None): + case "securityContact": + mocked_result = self.mockSecurityContact + case "technicalContact": + mocked_result = self.mockTechnicalContact + case "adminContact": + mocked_result = self.mockAdministrativeContact + case "regContact": + mocked_result = self.mockRegistrantContact + case "defaultSec": + mocked_result = self.mockDefaultSecurityContact + case "defaultTech": + mocked_result = self.mockDefaultTechnicalContact + case _: + # Default contact return + mocked_result = self.mockDataInfoContact return MagicMock(res_data=[mocked_result]) From afd7b1c3f332f619677fa13fc1fd23fb1bc9f4e5 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 19 Oct 2023 11:51:52 -0600 Subject: [PATCH 29/53] Reformat + add test case for eppdisclose --- src/registrar/tests/common.py | 2 +- src/registrar/tests/test_models_domain.py | 69 ++++++++++++++++++++++- 2 files changed, 69 insertions(+), 2 deletions(-) diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index acda90a75..bd6a6336b 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -901,7 +901,7 @@ class MockEppLib(TestCase): "freeman.gov": (self.InfoDomainWithContacts, None), "threenameserversDomain.gov": (self.infoDomainThreeHosts, None), "defaultsecurity.gov": (self.InfoDomainWithDefaultSecurityContact, None), - "defaulttechnical.gov": (self.InfoDomainWithDefaultTechnicalContact, None) + "defaulttechnical.gov": (self.InfoDomainWithDefaultTechnicalContact, None), } # Retrieve the corresponding values from the dictionary diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index 7a66c1106..76f8f1d2a 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -19,7 +19,7 @@ from registrar.utility.errors import ActionNotAllowed, NameserverError from registrar.models.utility.contact_error import ContactError, ContactErrorCodes -from .common import MockEppLib + from django_fsm import TransitionNotAllowed # type: ignore from epplibwrapper import ( commands, @@ -29,6 +29,7 @@ from epplibwrapper import ( RegistryError, ErrorCode, ) +from .common import MockEppLib import logging logger = logging.getLogger(__name__) @@ -813,6 +814,72 @@ class TestRegistrantContacts(MockEppLib): # The emails should match on both items self.assertEqual(expected_contact.email, actual_contact.email) + def test_convert_public_contact_to_epp(self): + self.maxDiff = None + domain, _ = Domain.objects.get_or_create(name="freeman.gov") + dummy_contact = domain.get_default_security_contact() + test_disclose = self._convertPublicContactToEpp( + dummy_contact, disclose_email=True + ).__dict__ + test_not_disclose = self._convertPublicContactToEpp( + dummy_contact, disclose_email=False + ).__dict__ + expected_disclose = { + "auth_info": common.ContactAuthInfo(pw='2fooBAR123fooBaz'), + "disclose": common.Disclose(flag=True, fields={common.DiscloseField.EMAIL}, types=None), + "email": "dotgov@cisa.dhs.gov", + "extensions": [], + "fax": None, + "id": "ThIq2NcRIDN7PauO", + "ident": None, + "notify_email": None, + "postal_info": common.PostalInfo( + name='Registry Customer Service', + addr=common.ContactAddr( + street=['4200 Wilson Blvd.', None, None], + city='Arlington', + pc='22201', + cc='US', + sp='VA' + ), + org='Cybersecurity and Infrastructure Security Agency', + type='loc' + ), + "vat": None, + "voice": "+1.8882820870" + } + expected_not_disclose = { + "auth_info": common.ContactAuthInfo(pw='2fooBAR123fooBaz'), + "disclose": common.Disclose(flag=False, fields={common.DiscloseField.EMAIL}, types=None), + "email": "dotgov@cisa.dhs.gov", + "extensions": [], + "fax": None, + "id": "ThrECENCHI76PGLh", + "ident": None, + "notify_email": None, + "postal_info": common.PostalInfo( + name='Registry Customer Service', + addr=common.ContactAddr( + street=['4200 Wilson Blvd.', None, None], + city='Arlington', + pc='22201', + cc='US', + sp='VA' + ), + org='Cybersecurity and Infrastructure Security Agency', + type='loc' + ), + "vat": None, + "voice": "+1.8882820870" + } + + # Set the ids equal, since this value changes + test_disclose["id"] = expected_disclose["id"] + test_not_disclose["id"] = expected_not_disclose["id"] + + self.assertEqual(test_disclose, expected_disclose) + self.assertEqual(test_not_disclose, expected_not_disclose) + def test_not_disclosed_on_default_security_contact(self): """ Scenario: Registrant creates a new domain with no security email From 81b58167a61b7ceee66230af314f73dba3da0be1 Mon Sep 17 00:00:00 2001 From: Erin <121973038+erinysong@users.noreply.github.com> Date: Thu, 19 Oct 2023 11:13:55 -0700 Subject: [PATCH 30/53] Fix lint errors --- src/registrar/tests/common.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index c61b827fc..f6539466d 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -851,7 +851,7 @@ class MockEppLib(TestCase): res_data=[self.mockDataHostChange], code=ErrorCode.COMMAND_COMPLETED_SUCCESSFULLY, ) - + def mockDeleteDomainCommands(self, _request, cleaned): if getattr(_request, "name", None) == "failDelete.gov": name = getattr(_request, "name", None) @@ -862,7 +862,6 @@ class MockEppLib(TestCase): ) return None - def mockInfoDomainCommands(self, _request, cleaned): request_name = getattr(_request, "name", None) From 257bc0f0ba2def600a45eb94be0d8486781ca3a8 Mon Sep 17 00:00:00 2001 From: Rebecca Hsieh Date: Thu, 19 Oct 2023 17:36:22 -0700 Subject: [PATCH 31/53] Add tests --- src/registrar/tests/test_admin.py | 36 +++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index 7dbc8ff38..4737909fe 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -11,6 +11,7 @@ from registrar.admin import ( ListHeaderAdmin, MyUserAdmin, AuditedAdmin, + ContactAdmin, ) from registrar.models import ( Domain, @@ -1310,3 +1311,38 @@ class DomainSessionVariableTest(TestCase): {"_edit_domain": "true"}, follow=True, ) + + +class ContactAdminTest(TestCase): + def setUp(self): + self.site = AdminSite() + self.factory = RequestFactory() + self.client = Client(HTTP_HOST="localhost:8080") + self.admin = ContactAdmin(model=get_user_model(), admin_site=None) + self.superuser = create_superuser() + self.staffuser = create_user() + + def test_readonly_when_restricted_staffuser(self): + request = self.factory.get("/") + request.user = self.staffuser + + readonly_fields = self.admin.get_readonly_fields(request) + + expected_fields = [ + "user", + ] + + self.assertEqual(readonly_fields, expected_fields) + + def test_readonly_when_restricted_superuser(self): + request = self.factory.get("/") + request.user = self.superuser + + readonly_fields = self.admin.get_readonly_fields(request) + + expected_fields = [] + + self.assertEqual(readonly_fields, expected_fields) + + def tearDown(self): + User.objects.all().delete() From 15ecaa83e70f0c40f8a5a06dc0f506a54967cbdc Mon Sep 17 00:00:00 2001 From: Cameron Dixon Date: Fri, 20 Oct 2023 06:33:11 -0400 Subject: [PATCH 32/53] Separate additional context from the description --- .github/ISSUE_TEMPLATE/issue-default.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/issue-default.yml b/.github/ISSUE_TEMPLATE/issue-default.yml index 2252845bf..701742f72 100644 --- a/.github/ISSUE_TEMPLATE/issue-default.yml +++ b/.github/ISSUE_TEMPLATE/issue-default.yml @@ -10,9 +10,9 @@ body: - type: textarea id: issue-description attributes: - label: Issue description and context + label: Issue description description: | - Describe the issue so that someone who wasn't present for its discovery can understand why it matters. Use full sentences, plain language, and good [formatting](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax). Screenshots and links to documents/discussions are welcome. + Describe the issue so that someone who wasn't present for its discovery can understand why it matters. Use full sentences, plain language, and good [formatting](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax). validations: required: true - type: textarea @@ -21,6 +21,11 @@ body: label: Acceptance criteria description: "If known, share 1-3 statements that would need to be true for this issue to be considered resolved. Use a [task list](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/about-task-lists#creating-task-lists) if appropriate." placeholder: "- [ ]" + - type: textarea + id: additional-context + attributes: + label: Additional context + description: "Share any other thoughts, like how this might be implemented or fixed. Screenshots and links to documents/discussions are welcome." - type: textarea id: links-to-other-issues attributes: @@ -32,4 +37,5 @@ body: id: note attributes: value: | - > We may edit this issue's text to document our understanding and clarify the product work. + > We may edit the text in this issue to document our understanding and clarify the product work. + From 0b087a81517c4e2a66e14844de18adeb2441ad41 Mon Sep 17 00:00:00 2001 From: Cameron Dixon Date: Fri, 20 Oct 2023 06:38:34 -0400 Subject: [PATCH 33/53] fix spacing --- .github/ISSUE_TEMPLATE/issue-default.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/issue-default.yml b/.github/ISSUE_TEMPLATE/issue-default.yml index 701742f72..26384ceda 100644 --- a/.github/ISSUE_TEMPLATE/issue-default.yml +++ b/.github/ISSUE_TEMPLATE/issue-default.yml @@ -24,8 +24,8 @@ body: - type: textarea id: additional-context attributes: - label: Additional context - description: "Share any other thoughts, like how this might be implemented or fixed. Screenshots and links to documents/discussions are welcome." + label: Additional context + description: "Share any other thoughts, like how this might be implemented or fixed. Screenshots and links to documents/discussions are welcome." - type: textarea id: links-to-other-issues attributes: From 94a8bf7793c3b5b4ffccde707027488c9ee430c2 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 20 Oct 2023 07:56:00 -0600 Subject: [PATCH 34/53] Linting --- src/registrar/tests/test_models_domain.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index 76f8f1d2a..939d765b6 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -824,9 +824,13 @@ class TestRegistrantContacts(MockEppLib): test_not_disclose = self._convertPublicContactToEpp( dummy_contact, disclose_email=False ).__dict__ + + # Separated for linter + disclose_email_field = {common.DiscloseField.EMAIL} + disclose = common.Disclose(flag=True, fields=disclose_email_field, types=None), expected_disclose = { "auth_info": common.ContactAuthInfo(pw='2fooBAR123fooBaz'), - "disclose": common.Disclose(flag=True, fields={common.DiscloseField.EMAIL}, types=None), + "disclose": disclose, "email": "dotgov@cisa.dhs.gov", "extensions": [], "fax": None, @@ -848,9 +852,12 @@ class TestRegistrantContacts(MockEppLib): "vat": None, "voice": "+1.8882820870" } + + # Separated for linter + not_disclose = common.Disclose(flag=False, fields=disclose_email_field, types=None) expected_not_disclose = { "auth_info": common.ContactAuthInfo(pw='2fooBAR123fooBaz'), - "disclose": common.Disclose(flag=False, fields={common.DiscloseField.EMAIL}, types=None), + "disclose": not_disclose, "email": "dotgov@cisa.dhs.gov", "extensions": [], "fax": None, From d5b47a22df78eaa0e8ec22620ab68eae933abcf3 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 20 Oct 2023 08:04:21 -0600 Subject: [PATCH 35/53] Update test_models_domain.py --- src/registrar/tests/test_models_domain.py | 50 ++++++++++++----------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index 939d765b6..5759df1be 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -827,10 +827,11 @@ class TestRegistrantContacts(MockEppLib): # Separated for linter disclose_email_field = {common.DiscloseField.EMAIL} - disclose = common.Disclose(flag=True, fields=disclose_email_field, types=None), expected_disclose = { - "auth_info": common.ContactAuthInfo(pw='2fooBAR123fooBaz'), - "disclose": disclose, + "auth_info": common.ContactAuthInfo(pw="2fooBAR123fooBaz"), + "disclose": common.Disclose( + flag=True, fields=disclose_email_field, types=None + ), "email": "dotgov@cisa.dhs.gov", "extensions": [], "fax": None, @@ -838,26 +839,27 @@ class TestRegistrantContacts(MockEppLib): "ident": None, "notify_email": None, "postal_info": common.PostalInfo( - name='Registry Customer Service', + name="Registry Customer Service", addr=common.ContactAddr( - street=['4200 Wilson Blvd.', None, None], - city='Arlington', - pc='22201', - cc='US', - sp='VA' + street=["4200 Wilson Blvd.", None, None], + city="Arlington", + pc="22201", + cc="US", + sp="VA", ), - org='Cybersecurity and Infrastructure Security Agency', - type='loc' + org="Cybersecurity and Infrastructure Security Agency", + type="loc", ), "vat": None, - "voice": "+1.8882820870" + "voice": "+1.8882820870", } # Separated for linter - not_disclose = common.Disclose(flag=False, fields=disclose_email_field, types=None) expected_not_disclose = { - "auth_info": common.ContactAuthInfo(pw='2fooBAR123fooBaz'), - "disclose": not_disclose, + "auth_info": common.ContactAuthInfo(pw="2fooBAR123fooBaz"), + "disclose": common.Disclose( + flag=False, fields=disclose_email_field, types=None + ), "email": "dotgov@cisa.dhs.gov", "extensions": [], "fax": None, @@ -865,19 +867,19 @@ class TestRegistrantContacts(MockEppLib): "ident": None, "notify_email": None, "postal_info": common.PostalInfo( - name='Registry Customer Service', + name="Registry Customer Service", addr=common.ContactAddr( - street=['4200 Wilson Blvd.', None, None], - city='Arlington', - pc='22201', - cc='US', - sp='VA' + street=["4200 Wilson Blvd.", None, None], + city="Arlington", + pc="22201", + cc="US", + sp="VA", ), - org='Cybersecurity and Infrastructure Security Agency', - type='loc' + org="Cybersecurity and Infrastructure Security Agency", + type="loc", ), "vat": None, - "voice": "+1.8882820870" + "voice": "+1.8882820870", } # Set the ids equal, since this value changes From 485a05e45e133b508cdd0cf64d18078eb9d0e828 Mon Sep 17 00:00:00 2001 From: Alysia Broddrick Date: Fri, 20 Oct 2023 08:44:12 -0700 Subject: [PATCH 36/53] Fixed manager role still showing as admin --- .../0040_alter_userdomainrole_role.py | 17 +++++++++++++++++ src/registrar/models/user_domain_role.py | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 src/registrar/migrations/0040_alter_userdomainrole_role.py diff --git a/src/registrar/migrations/0040_alter_userdomainrole_role.py b/src/registrar/migrations/0040_alter_userdomainrole_role.py new file mode 100644 index 000000000..39e539f55 --- /dev/null +++ b/src/registrar/migrations/0040_alter_userdomainrole_role.py @@ -0,0 +1,17 @@ +# Generated by Django 4.2.1 on 2023-10-20 15:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0039_alter_transitiondomain_status"), + ] + + operations = [ + migrations.AlterField( + model_name="userdomainrole", + name="role", + field=models.TextField(choices=[("manager", "Manager")]), + ), + ] diff --git a/src/registrar/models/user_domain_role.py b/src/registrar/models/user_domain_role.py index e5cb01cc1..7b1f550d3 100644 --- a/src/registrar/models/user_domain_role.py +++ b/src/registrar/models/user_domain_role.py @@ -15,7 +15,7 @@ class UserDomainRole(TimeStampedModel): elsewhere. """ - ADMIN = "manager" + MANAGER = "manager" user = models.ForeignKey( "registrar.User", From 34e0ce955ab54384b2404fac4ef4adafc493f49d Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 20 Oct 2023 11:51:11 -0400 Subject: [PATCH 37/53] clean up a bad comment --- src/registrar/templatetags/custom_filters.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/registrar/templatetags/custom_filters.py b/src/registrar/templatetags/custom_filters.py index 158b7269e..14e2c9e3e 100644 --- a/src/registrar/templatetags/custom_filters.py +++ b/src/registrar/templatetags/custom_filters.py @@ -55,11 +55,9 @@ def contains_checkbox(html_list): @register.filter def get_organization_long_name(organization_type): - # https://gist.github.com/OmenApps/3eef60ba4204f3d1842d9d7477efcce1#file-django_choices-txt-L28 organization_choices_dict = dict( DomainApplication.OrganizationChoicesVerbose.choices ) - long_form_type = organization_choices_dict[organization_type] if long_form_type is None: logger.error("Organization type error, triggered by a template's custom filter") From 649a22456232f77f75f98bd6ecedc09d3ac62529 Mon Sep 17 00:00:00 2001 From: Alysia Broddrick Date: Fri, 20 Oct 2023 09:15:46 -0700 Subject: [PATCH 38/53] updated related mentions to admin to be Manager --- docs/developer/user-permissions.md | 2 +- src/registrar/models/domain_application.py | 2 +- src/registrar/models/domain_invitation.py | 2 +- src/registrar/tests/test_models.py | 2 +- src/registrar/tests/test_views.py | 12 ++++++------ src/registrar/views/domain.py | 2 +- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/developer/user-permissions.md b/docs/developer/user-permissions.md index 31b69d3b3..f7c41492d 100644 --- a/docs/developer/user-permissions.md +++ b/docs/developer/user-permissions.md @@ -42,7 +42,7 @@ as health checks used by our platform). ## Adding roles The current MVP design uses only a single role called -`UserDomainRole.Roles.ADMIN` that has all access on a domain. As such, the +`UserDomainRole.Roles.MANAGER` that has all access on a domain. As such, the permission mixin doesn't need to examine the `role` field carefully. In the future, as we add additional roles that our product vision calls for (read-only? editing only some information?), we need to add conditional diff --git a/src/registrar/models/domain_application.py b/src/registrar/models/domain_application.py index 68429d381..f15474117 100644 --- a/src/registrar/models/domain_application.py +++ b/src/registrar/models/domain_application.py @@ -582,7 +582,7 @@ class DomainApplication(TimeStampedModel): # create the permission for the user UserDomainRole = apps.get_model("registrar.UserDomainRole") UserDomainRole.objects.get_or_create( - user=self.creator, domain=created_domain, role=UserDomainRole.Roles.ADMIN + user=self.creator, domain=created_domain, role=UserDomainRole.Roles.MANAGER ) self._send_status_update_email( diff --git a/src/registrar/models/domain_invitation.py b/src/registrar/models/domain_invitation.py index 7cc2a5432..dff03fb87 100644 --- a/src/registrar/models/domain_invitation.py +++ b/src/registrar/models/domain_invitation.py @@ -63,7 +63,7 @@ class DomainInvitation(TimeStampedModel): # and create a role for that user on this domain _, created = UserDomainRole.objects.get_or_create( - user=user, domain=self.domain, role=UserDomainRole.Roles.ADMIN + user=user, domain=self.domain, role=UserDomainRole.Roles.MANAGER ) if not created: # something strange happened and this role already existed when diff --git a/src/registrar/tests/test_models.py b/src/registrar/tests/test_models.py index 2c6f78ef5..e76dea035 100644 --- a/src/registrar/tests/test_models.py +++ b/src/registrar/tests/test_models.py @@ -601,7 +601,7 @@ class TestInvitations(TestCase): def test_retrieve_existing_role_no_error(self): # make the overlapping role UserDomainRole.objects.get_or_create( - user=self.user, domain=self.domain, role=UserDomainRole.Roles.ADMIN + user=self.user, domain=self.domain, role=UserDomainRole.Roles.MANAGER ) # this is not an error but does produce a console warning with less_console_noise(): diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index 0e8f895af..2a14f3466 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -89,7 +89,7 @@ class LoggedInTests(TestWithUser): domain, _ = Domain.objects.get_or_create(name="igorville.gov") self.assertNotContains(response, "igorville.gov") role, _ = UserDomainRole.objects.get_or_create( - user=self.user, domain=domain, role=UserDomainRole.Roles.ADMIN + user=self.user, domain=domain, role=UserDomainRole.Roles.MANAGER ) response = self.client.get("/") # count = 2 because it is also in screenreader content @@ -1097,23 +1097,23 @@ class TestWithDomainPermissions(TestWithUser): creator=self.user, domain=self.domain_dnssec_none ) self.role, _ = UserDomainRole.objects.get_or_create( - user=self.user, domain=self.domain, role=UserDomainRole.Roles.ADMIN + user=self.user, domain=self.domain, role=UserDomainRole.Roles.MANAGER ) UserDomainRole.objects.get_or_create( - user=self.user, domain=self.domain_dsdata, role=UserDomainRole.Roles.ADMIN + user=self.user, domain=self.domain_dsdata, role=UserDomainRole.Roles.MANAGER ) UserDomainRole.objects.get_or_create( user=self.user, domain=self.domain_multdsdata, - role=UserDomainRole.Roles.ADMIN, + role=UserDomainRole.Roles.MANAGER, ) UserDomainRole.objects.get_or_create( - user=self.user, domain=self.domain_keydata, role=UserDomainRole.Roles.ADMIN + user=self.user, domain=self.domain_keydata, role=UserDomainRole.Roles.MANAGER ) UserDomainRole.objects.get_or_create( user=self.user, domain=self.domain_dnssec_none, - role=UserDomainRole.Roles.ADMIN, + role=UserDomainRole.Roles.MANAGER, ) def tearDown(self): diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index aa71a7551..5590698be 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -736,7 +736,7 @@ class DomainAddUserView(DomainFormBaseView): try: UserDomainRole.objects.create( - user=requested_user, domain=self.object, role=UserDomainRole.Roles.ADMIN + user=requested_user, domain=self.object, role=UserDomainRole.Roles.MANAGER ) except IntegrityError: # User already has the desired role! Do nothing?? From 555a1e7ae95a8e8ea6f3debce1ab65c94ca34c29 Mon Sep 17 00:00:00 2001 From: Alysia Broddrick Date: Fri, 20 Oct 2023 09:21:12 -0700 Subject: [PATCH 39/53] ran linter --- src/registrar/tests/test_views.py | 4 +++- src/registrar/views/domain.py | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index 2a14f3466..7cc616889 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -1108,7 +1108,9 @@ class TestWithDomainPermissions(TestWithUser): role=UserDomainRole.Roles.MANAGER, ) UserDomainRole.objects.get_or_create( - user=self.user, domain=self.domain_keydata, role=UserDomainRole.Roles.MANAGER + user=self.user, + domain=self.domain_keydata, + role=UserDomainRole.Roles.MANAGER, ) UserDomainRole.objects.get_or_create( user=self.user, diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 5590698be..fce94f175 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -736,7 +736,9 @@ class DomainAddUserView(DomainFormBaseView): try: UserDomainRole.objects.create( - user=requested_user, domain=self.object, role=UserDomainRole.Roles.MANAGER + user=requested_user, + domain=self.object, + role=UserDomainRole.Roles.MANAGER, ) except IntegrityError: # User already has the desired role! Do nothing?? From 8c0d88df2ac1a97b3f889b4437fa41904224c578 Mon Sep 17 00:00:00 2001 From: Erin <121973038+erinysong@users.noreply.github.com> Date: Fri, 20 Oct 2023 09:35:28 -0700 Subject: [PATCH 40/53] Remove login requirement on domain availability API --- src/api/views.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/api/views.py b/src/api/views.py index 02e419a91..e8b8431de 100644 --- a/src/api/views.py +++ b/src/api/views.py @@ -3,8 +3,6 @@ from django.apps import apps from django.views.decorators.http import require_http_methods from django.http import JsonResponse -from django.contrib.auth.decorators import login_required - import requests from cachetools.func import ttl_cache @@ -68,7 +66,6 @@ def in_domains(domain): @require_http_methods(["GET"]) -@login_required def available(request, domain=""): """Is a given domain available or not. From 3cfdacccfc3bc5aaf7aa71eb97da959b2cf1fc44 Mon Sep 17 00:00:00 2001 From: Neil Martinsen-Burrell Date: Fri, 20 Oct 2023 12:00:17 -0500 Subject: [PATCH 41/53] Change User management to Domain managers --- src/registrar/templates/domain_detail.html | 2 +- src/registrar/templates/domain_sidebar.html | 2 +- src/registrar/templates/domain_users.html | 17 +++++++++++++++-- src/registrar/tests/test_views.py | 2 +- src/registrar/views/domain.py | 2 +- 5 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/registrar/templates/domain_detail.html b/src/registrar/templates/domain_detail.html index e0d672093..4ddbd673a 100644 --- a/src/registrar/templates/domain_detail.html +++ b/src/registrar/templates/domain_detail.html @@ -52,7 +52,7 @@ {% include "includes/summary_item.html" with title='Security email' value='None provided' edit_link=url %} {% endif %} {% url 'domain-users' pk=domain.id as url %} - {% include "includes/summary_item.html" with title='User management' users='true' list=True value=domain.permissions.all edit_link=url %} + {% include "includes/summary_item.html" with title='Domain managers' users='true' list=True value=domain.permissions.all edit_link=url %}
{% endblock %} {# domain_content #} diff --git a/src/registrar/templates/domain_sidebar.html b/src/registrar/templates/domain_sidebar.html index 1acd87eeb..ac45ad04c 100644 --- a/src/registrar/templates/domain_sidebar.html +++ b/src/registrar/templates/domain_sidebar.html @@ -100,7 +100,7 @@ - User management + Domain managers diff --git a/src/registrar/templates/domain_users.html b/src/registrar/templates/domain_users.html index 22b9d18d1..5cb7acffd 100644 --- a/src/registrar/templates/domain_users.html +++ b/src/registrar/templates/domain_users.html @@ -1,10 +1,23 @@ {% extends "domain_base.html" %} {% load static %} -{% block title %}User management | {{ domain.name }} | {% endblock %} +{% block title %}Domain managers | {{ domain.name }} | {% endblock %} {% block domain_content %} -

User management

+

Domain managers

+ +

+ Domain managers can update all information related to a domain within the + .gov registrar, including contact details, authorizing official, security + email, and DNS name servers. +

+ +
    +
  • There is no limit to the number of domain managers you can add.
  • +
  • After adding a domain manager, an email invitation will be sent to that user with + instructions on how to set up an account.
  • +
  • To remove a domain manager, contact us for assistance. +
{% if domain.permissions %}
diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index 0e8f895af..8ad855433 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -1204,7 +1204,7 @@ class TestDomainUserManagement(TestDomainOverview): response = self.client.get( reverse("domain-users", kwargs={"pk": self.domain.id}) ) - self.assertContains(response, "User management") + self.assertContains(response, "Domain managers") def test_domain_user_management_add_link(self): """Button to get to user add page works.""" diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index aa71a7551..d9b671a65 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -656,7 +656,7 @@ class DomainSecurityEmailView(DomainFormBaseView): class DomainUsersView(DomainBaseView): - """User management page in the domain details.""" + """Domain managers page in the domain details.""" template_name = "domain_users.html" From 8e753796ec166e1dc467a4db2b6429623602425a Mon Sep 17 00:00:00 2001 From: Rebecca Hsieh Date: Fri, 20 Oct 2023 10:10:14 -0700 Subject: [PATCH 42/53] Update migration to match main --- .../migrations/0041_create_groups_v03.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/registrar/migrations/0041_create_groups_v03.py diff --git a/src/registrar/migrations/0041_create_groups_v03.py b/src/registrar/migrations/0041_create_groups_v03.py new file mode 100644 index 000000000..4ddbb651a --- /dev/null +++ b/src/registrar/migrations/0041_create_groups_v03.py @@ -0,0 +1,37 @@ +# This migration creates the create_full_access_group and create_cisa_analyst_group groups +# It is dependent on 0035 (which populates ContentType and Permissions) +# 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", "0040_alter_user_domainrole_role"), + ] + + operations = [ + migrations.RunPython( + create_groups, + reverse_code=migrations.RunPython.noop, + atomic=True, + ), + ] From 070631b2160200850be76cd932af996aed677de6 Mon Sep 17 00:00:00 2001 From: Rebecca Hsieh Date: Fri, 20 Oct 2023 10:19:57 -0700 Subject: [PATCH 43/53] Fix naming for migration --- src/registrar/migrations/0041_create_groups_v03.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/migrations/0041_create_groups_v03.py b/src/registrar/migrations/0041_create_groups_v03.py index 4ddbb651a..d6ec5e433 100644 --- a/src/registrar/migrations/0041_create_groups_v03.py +++ b/src/registrar/migrations/0041_create_groups_v03.py @@ -25,7 +25,7 @@ def create_groups(apps, schema_editor) -> Any: class Migration(migrations.Migration): dependencies = [ - ("registrar", "0040_alter_user_domainrole_role"), + ("registrar", "0040_alter_userdomainrole_role"), ] operations = [ From a0911b46f84e7b16df9c0d38de591799a99c91e0 Mon Sep 17 00:00:00 2001 From: Neil Martinsen-Burrell Date: Fri, 20 Oct 2023 13:28:36 -0500 Subject: [PATCH 44/53] load public_site_url helper --- src/registrar/templates/domain_users.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/templates/domain_users.html b/src/registrar/templates/domain_users.html index 5cb7acffd..f66eef5a6 100644 --- a/src/registrar/templates/domain_users.html +++ b/src/registrar/templates/domain_users.html @@ -1,5 +1,5 @@ {% extends "domain_base.html" %} -{% load static %} +{% load static url_helpers %} {% block title %}Domain managers | {{ domain.name }} | {% endblock %} From 7f153f77ed70147c39e021ab02d85498c1cb4bf2 Mon Sep 17 00:00:00 2001 From: Neil Martinsen-Burrell Date: Fri, 20 Oct 2023 13:46:51 -0500 Subject: [PATCH 45/53] Review feedback: rename tests to match page name --- src/registrar/tests/test_views.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index 8ad855433..1262347a1 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -1199,14 +1199,14 @@ class TestDomainOverview(TestWithDomainPermissions, WebTest): self.assertEqual(response.status_code, 403) -class TestDomainUserManagement(TestDomainOverview): - def test_domain_user_management(self): +class TestDomainManagers(TestDomainOverview): + def test_domain_managers(self): response = self.client.get( reverse("domain-users", kwargs={"pk": self.domain.id}) ) self.assertContains(response, "Domain managers") - def test_domain_user_management_add_link(self): + def test_domain_managers_add_link(self): """Button to get to user add page works.""" management_page = self.app.get( reverse("domain-users", kwargs={"pk": self.domain.id}) From 020f2614d0e7c727d0261a4f9bf3da0c22fbff3e Mon Sep 17 00:00:00 2001 From: Cameron Dixon Date: Fri, 20 Oct 2023 15:46:51 -0400 Subject: [PATCH 46/53] Update issue-default.yml --- .github/ISSUE_TEMPLATE/issue-default.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/issue-default.yml b/.github/ISSUE_TEMPLATE/issue-default.yml index 26384ceda..3a34b2943 100644 --- a/.github/ISSUE_TEMPLATE/issue-default.yml +++ b/.github/ISSUE_TEMPLATE/issue-default.yml @@ -21,9 +21,9 @@ body: label: Acceptance criteria description: "If known, share 1-3 statements that would need to be true for this issue to be considered resolved. Use a [task list](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/about-task-lists#creating-task-lists) if appropriate." placeholder: "- [ ]" - - type: textarea - id: additional-context - attributes: + - type: textarea + id: additional-context + attributes: label: Additional context description: "Share any other thoughts, like how this might be implemented or fixed. Screenshots and links to documents/discussions are welcome." - type: textarea From b8b12bad6ea2f1330ef840cdf1318795d7876fae Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 20 Oct 2023 17:09:50 -0400 Subject: [PATCH 47/53] Clean up migrations --- ...napplication_organization_type_and_more.py | 52 ------------------- .../migrations/0039_merge_20231013_2029.py | 12 ----- .../migrations/0040_merge_20231018_2203.py | 12 ----- 3 files changed, 76 deletions(-) delete mode 100644 src/registrar/migrations/0038_alter_domainapplication_organization_type_and_more.py delete mode 100644 src/registrar/migrations/0039_merge_20231013_2029.py delete mode 100644 src/registrar/migrations/0040_merge_20231018_2203.py diff --git a/src/registrar/migrations/0038_alter_domainapplication_organization_type_and_more.py b/src/registrar/migrations/0038_alter_domainapplication_organization_type_and_more.py deleted file mode 100644 index a06ea0451..000000000 --- a/src/registrar/migrations/0038_alter_domainapplication_organization_type_and_more.py +++ /dev/null @@ -1,52 +0,0 @@ -# Generated by Django 4.2.1 on 2023-10-13 20:26 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("registrar", "0037_create_groups_v01"), - ] - - operations = [ - migrations.AlterField( - model_name="domainapplication", - name="organization_type", - field=models.CharField( - blank=True, - choices=[ - ("federal", "Federal"), - ("interstate", "Interstate"), - ("state_or_territory", "State or territory"), - ("tribal", "Tribal"), - ("county", "County"), - ("city", "City"), - ("special_district", "Special district"), - ("school_district", "School district"), - ], - help_text="Type of organization", - max_length=255, - null=True, - ), - ), - migrations.AlterField( - model_name="domaininformation", - name="organization_type", - field=models.CharField( - blank=True, - choices=[ - ("federal", "Federal"), - ("interstate", "Interstate"), - ("state_or_territory", "State or territory"), - ("tribal", "Tribal"), - ("county", "County"), - ("city", "City"), - ("special_district", "Special district"), - ("school_district", "School district"), - ], - help_text="Type of Organization", - max_length=255, - null=True, - ), - ), - ] diff --git a/src/registrar/migrations/0039_merge_20231013_2029.py b/src/registrar/migrations/0039_merge_20231013_2029.py deleted file mode 100644 index aed231bdc..000000000 --- a/src/registrar/migrations/0039_merge_20231013_2029.py +++ /dev/null @@ -1,12 +0,0 @@ -# Generated by Django 4.2.1 on 2023-10-13 20:29 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ("registrar", "0038_alter_domainapplication_organization_type_and_more"), - ("registrar", "0038_create_groups_v02"), - ] - - operations = [] diff --git a/src/registrar/migrations/0040_merge_20231018_2203.py b/src/registrar/migrations/0040_merge_20231018_2203.py deleted file mode 100644 index fad679098..000000000 --- a/src/registrar/migrations/0040_merge_20231018_2203.py +++ /dev/null @@ -1,12 +0,0 @@ -# Generated by Django 4.2.1 on 2023-10-18 22:03 - -from django.db import migrations - - -class Migration(migrations.Migration): - dependencies = [ - ("registrar", "0039_alter_transitiondomain_status"), - ("registrar", "0039_merge_20231013_2029"), - ] - - operations = [] From d5b21435ddea84afd8fb821c065a3606a37f1726 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 20 Oct 2023 17:11:00 -0400 Subject: [PATCH 48/53] re-create org names migration --- ...napplication_organization_type_and_more.py | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/registrar/migrations/0041_alter_domainapplication_organization_type_and_more.py diff --git a/src/registrar/migrations/0041_alter_domainapplication_organization_type_and_more.py b/src/registrar/migrations/0041_alter_domainapplication_organization_type_and_more.py new file mode 100644 index 000000000..07cfe0e77 --- /dev/null +++ b/src/registrar/migrations/0041_alter_domainapplication_organization_type_and_more.py @@ -0,0 +1,52 @@ +# Generated by Django 4.2.1 on 2023-10-20 21:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0040_alter_userdomainrole_role"), + ] + + operations = [ + migrations.AlterField( + model_name="domainapplication", + name="organization_type", + field=models.CharField( + blank=True, + choices=[ + ("federal", "Federal"), + ("interstate", "Interstate"), + ("state_or_territory", "State or territory"), + ("tribal", "Tribal"), + ("county", "County"), + ("city", "City"), + ("special_district", "Special district"), + ("school_district", "School district"), + ], + help_text="Type of organization", + max_length=255, + null=True, + ), + ), + migrations.AlterField( + model_name="domaininformation", + name="organization_type", + field=models.CharField( + blank=True, + choices=[ + ("federal", "Federal"), + ("interstate", "Interstate"), + ("state_or_territory", "State or territory"), + ("tribal", "Tribal"), + ("county", "County"), + ("city", "City"), + ("special_district", "Special district"), + ("school_district", "School district"), + ], + help_text="Type of Organization", + max_length=255, + null=True, + ), + ), + ] From 62b6514b0732d3e6713dbc296cd2adb14dce01d1 Mon Sep 17 00:00:00 2001 From: Rebecca Hsieh Date: Fri, 20 Oct 2023 14:50:02 -0700 Subject: [PATCH 49/53] Remove 0041 so I can match main --- .../migrations/0041_create_groups_v03.py | 37 ------------------- 1 file changed, 37 deletions(-) delete mode 100644 src/registrar/migrations/0041_create_groups_v03.py diff --git a/src/registrar/migrations/0041_create_groups_v03.py b/src/registrar/migrations/0041_create_groups_v03.py deleted file mode 100644 index d6ec5e433..000000000 --- a/src/registrar/migrations/0041_create_groups_v03.py +++ /dev/null @@ -1,37 +0,0 @@ -# This migration creates the create_full_access_group and create_cisa_analyst_group groups -# It is dependent on 0035 (which populates ContentType and Permissions) -# 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", "0040_alter_userdomainrole_role"), - ] - - operations = [ - migrations.RunPython( - create_groups, - reverse_code=migrations.RunPython.noop, - atomic=True, - ), - ] From 797d896e0a2362f20c221e10dd45e9e041c42ffd Mon Sep 17 00:00:00 2001 From: Rebecca Hsieh Date: Fri, 20 Oct 2023 14:51:48 -0700 Subject: [PATCH 50/53] Now updating correct migration after new main additions --- .../migrations/0042_create_groups_v03.py | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/registrar/migrations/0042_create_groups_v03.py diff --git a/src/registrar/migrations/0042_create_groups_v03.py b/src/registrar/migrations/0042_create_groups_v03.py new file mode 100644 index 000000000..e7039294b --- /dev/null +++ b/src/registrar/migrations/0042_create_groups_v03.py @@ -0,0 +1,37 @@ +# This migration creates the create_full_access_group and create_cisa_analyst_group groups +# It is dependent on 0035 (which populates ContentType and Permissions) +# 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", "0041_alter_domainapplication_organization_type_and_more"), + ] + + operations = [ + migrations.RunPython( + create_groups, + reverse_code=migrations.RunPython.noop, + atomic=True, + ), + ] \ No newline at end of file From a25a38f94832036d8189d693b53b120032ebf7af Mon Sep 17 00:00:00 2001 From: Rebecca Hsieh Date: Fri, 20 Oct 2023 14:57:04 -0700 Subject: [PATCH 51/53] Linter wins as always --- src/registrar/migrations/0042_create_groups_v03.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/migrations/0042_create_groups_v03.py b/src/registrar/migrations/0042_create_groups_v03.py index e7039294b..01b7985bf 100644 --- a/src/registrar/migrations/0042_create_groups_v03.py +++ b/src/registrar/migrations/0042_create_groups_v03.py @@ -34,4 +34,4 @@ class Migration(migrations.Migration): reverse_code=migrations.RunPython.noop, atomic=True, ), - ] \ No newline at end of file + ] From fb5cc4e5c670235c3f94d9702b6cafaac20e57d1 Mon Sep 17 00:00:00 2001 From: Neil Martinsen-Burrell Date: Mon, 23 Oct 2023 10:32:40 -0500 Subject: [PATCH 52/53] Allow connections from manage.get.gov --- src/registrar/config/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/config/settings.py b/src/registrar/config/settings.py index 7b96af5ee..59f00fe61 100644 --- a/src/registrar/config/settings.py +++ b/src/registrar/config/settings.py @@ -581,7 +581,7 @@ ALLOWED_HOSTS = [ "getgov-bl.app.cloud.gov", "getgov-rjm.app.cloud.gov", "getgov-dk.app.cloud.gov", - "get.gov", + "manage.get.gov", ] # Extend ALLOWED_HOSTS. From 5319f58191bb0c9a0bdd97f5e2cbe8ff395ffeeb Mon Sep 17 00:00:00 2001 From: Neil Martinsen-Burrell Date: Mon, 23 Oct 2023 10:57:58 -0500 Subject: [PATCH 53/53] Use manage.get.gov as the BASE_URL --- ops/manifests/manifest-stable.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ops/manifests/manifest-stable.yaml b/ops/manifests/manifest-stable.yaml index bc5e933f6..6295fa63b 100644 --- a/ops/manifests/manifest-stable.yaml +++ b/ops/manifests/manifest-stable.yaml @@ -18,7 +18,7 @@ applications: # Tell Django where to find its configuration DJANGO_SETTINGS_MODULE: registrar.config.settings # Tell Django where it is being hosted - DJANGO_BASE_URL: https://getgov-stable.app.cloud.gov + DJANGO_BASE_URL: https://manage.get.gov # Tell Django how much stuff to log DJANGO_LOG_LEVEL: INFO # default public site location