diff --git a/docs/developer/README.md b/docs/developer/README.md index 7519da7a9..72f6b9f20 100644 --- a/docs/developer/README.md +++ b/docs/developer/README.md @@ -320,16 +320,6 @@ it may help to resync your laptop with time.nist.gov: sudo sntp -sS time.nist.gov ``` -### Settings -The config for the connection pool exists inside the `settings.py` file. -| Name | Purpose | -| ------------------------ | ------------------------------------------------------------------------------------------------- | -| EPP_CONNECTION_POOL_SIZE | Determines the number of concurrent sockets that should exist in the pool. | -| POOL_KEEP_ALIVE | Determines the interval in which we ping open connections in seconds. Calculated as POOL_KEEP_ALIVE / EPP_CONNECTION_POOL_SIZE | -| POOL_TIMEOUT | Determines how long we try to keep a pool alive for, before restarting it. | - -Consider updating the `POOL_TIMEOUT` or `POOL_KEEP_ALIVE` periods if the pool often restarts. If the pool only restarts after a period of inactivity, update `POOL_KEEP_ALIVE`. If it restarts during the EPP call itself, then `POOL_TIMEOUT` needs to be updated. - ## Adding a S3 instance to your sandbox This can either be done through the CLI, or through the cloud.gov dashboard. Generally, it is better to do it through the dashboard as it handles app binding for you. diff --git a/docs/operations/data_migration.md b/docs/operations/data_migration.md index e4543a28c..472362a79 100644 --- a/docs/operations/data_migration.md +++ b/docs/operations/data_migration.md @@ -668,3 +668,32 @@ Example: `cf ssh getgov-za` #### Step 1: Running the script ```docker-compose exec app ./manage.py populate_verification_type``` + + +## Copy names from contacts to users + +### Running on sandboxes + +#### Step 1: Login to CloudFoundry +```cf login -a api.fr.cloud.gov --sso``` + +#### Step 2: SSH into your environment +```cf ssh getgov-{space}``` + +Example: `cf ssh getgov-za` + +#### Step 3: Create a shell instance +```/tmp/lifecycle/shell``` + +#### Step 4: Running the script +```./manage.py copy_names_from_contacts_to_users --debug``` + +### Running locally + +#### Step 1: Running the script +```docker-compose exec app ./manage.py copy_names_from_contacts_to_users --debug``` + +##### Optional parameters +| | Parameter | Description | +|:-:|:-------------------------- |:----------------------------------------------------------------------------| +| 1 | **debug** | Increases logging detail. Defaults to False. | diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 7bc581c69..79200d910 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -609,7 +609,7 @@ class MyUserAdmin(BaseUserAdmin, ImportExportModelAdmin): None, {"fields": ("username", "password", "status", "verification_type")}, ), - ("Personal Info", {"fields": ("first_name", "middle_name", "last_name", "email", "title")}), + ("Personal Info", {"fields": ("first_name", "middle_name", "last_name", "title", "email", "phone")}), ( "Permissions", { @@ -640,7 +640,7 @@ class MyUserAdmin(BaseUserAdmin, ImportExportModelAdmin): ) }, ), - ("Personal Info", {"fields": ("first_name", "middle_name", "last_name", "email", "title")}), + ("Personal Info", {"fields": ("first_name", "middle_name", "last_name", "title", "email", "phone")}), ( "Permissions", { diff --git a/src/registrar/management/commands/copy_names_from_contacts_to_users.py b/src/registrar/management/commands/copy_names_from_contacts_to_users.py index 50e1bea3d..384029400 100644 --- a/src/registrar/management/commands/copy_names_from_contacts_to_users.py +++ b/src/registrar/management/commands/copy_names_from_contacts_to_users.py @@ -10,6 +10,7 @@ from registrar.management.commands.utility.terminal_helper import ( ) from registrar.models.contact import Contact from registrar.models.user import User +from registrar.models.utility.domain_helper import DomainHelper logger = logging.getLogger(__name__) @@ -110,15 +111,21 @@ class Command(BaseCommand): {TerminalColors.ENDC}""", # noqa ) - # ---- UPDATE THE USER IF IT DOES NOT HAVE A FIRST AND LAST NAMES - # ---- LET'S KEEP A LIGHT TOUCH - if not eligible_user.first_name and not eligible_user.last_name: - # (expression has type "str | None", variable has type "str | int | Combinable") - # so we'll ignore type - eligible_user.first_name = contact.first_name # type: ignore - eligible_user.last_name = contact.last_name # type: ignore - eligible_user.save() - processed_user = eligible_user + # Get the fields that exist on both User and Contact. Excludes id. + common_fields = DomainHelper.get_common_fields(User, Contact) + if "email" in common_fields: + # Don't change the email field. + common_fields.remove("email") + + for field in common_fields: + # Grab the value that contact has stored for this field + new_value = getattr(contact, field) + + # Set it on the user field + setattr(eligible_user, field, new_value) + + eligible_user.save() + processed_user = eligible_user return ( eligible_user, diff --git a/src/registrar/migrations/0097_alter_user_phone.py b/src/registrar/migrations/0097_alter_user_phone.py new file mode 100644 index 000000000..dfa5cfba8 --- /dev/null +++ b/src/registrar/migrations/0097_alter_user_phone.py @@ -0,0 +1,19 @@ +# Generated by Django 4.2.10 on 2024-06-06 18:38 + +from django.db import migrations +import phonenumber_field.modelfields + + +class Migration(migrations.Migration): + + dependencies = [ + ("registrar", "0096_alter_contact_email_alter_contact_first_name_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="user", + name="phone", + field=phonenumber_field.modelfields.PhoneNumberField(blank=True, max_length=128, null=True, region=None), + ), + ] diff --git a/src/registrar/migrations/0098_alter_domainrequest_status.py b/src/registrar/migrations/0098_alter_domainrequest_status.py new file mode 100644 index 000000000..19fa1ded2 --- /dev/null +++ b/src/registrar/migrations/0098_alter_domainrequest_status.py @@ -0,0 +1,32 @@ +# Generated by Django 4.2.10 on 2024-06-07 15:27 + +from django.db import migrations +import django_fsm + + +class Migration(migrations.Migration): + + dependencies = [ + ("registrar", "0097_alter_user_phone"), + ] + + operations = [ + migrations.AlterField( + model_name="domainrequest", + name="status", + field=django_fsm.FSMField( + choices=[ + ("in review", "In review"), + ("action needed", "Action needed"), + ("approved", "Approved"), + ("rejected", "Rejected"), + ("ineligible", "Ineligible"), + ("submitted", "Submitted"), + ("withdrawn", "Withdrawn"), + ("started", "Started"), + ], + default="started", + max_length=50, + ), + ), + ] diff --git a/src/registrar/models/contact.py b/src/registrar/models/contact.py index 91a7515c7..f94938dd1 100644 --- a/src/registrar/models/contact.py +++ b/src/registrar/models/contact.py @@ -123,11 +123,21 @@ class Contact(TimeStampedModel): self.user.last_name = self.last_name updated = True + # Update middle_name if necessary + if not self.user.middle_name: + self.user.middle_name = self.middle_name + updated = True + # Update phone if necessary if not self.user.phone: self.user.phone = self.phone updated = True + # Update title if necessary + if not self.user.title: + self.user.title = self.title + updated = True + # Save user if any updates were made if updated: self.user.save() diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py index d441a6c1b..3f894a181 100644 --- a/src/registrar/models/domain_request.py +++ b/src/registrar/models/domain_request.py @@ -42,14 +42,14 @@ class DomainRequest(TimeStampedModel): # Constants for choice fields class DomainRequestStatus(models.TextChoices): - STARTED = "started", "Started" - SUBMITTED = "submitted", "Submitted" IN_REVIEW = "in review", "In review" ACTION_NEEDED = "action needed", "Action needed" APPROVED = "approved", "Approved" - WITHDRAWN = "withdrawn", "Withdrawn" REJECTED = "rejected", "Rejected" INELIGIBLE = "ineligible", "Ineligible" + SUBMITTED = "submitted", "Submitted" + WITHDRAWN = "withdrawn", "Withdrawn" + STARTED = "started", "Started" class StateTerritoryChoices(models.TextChoices): ALABAMA = "AL", "Alabama (AL)" diff --git a/src/registrar/models/user.py b/src/registrar/models/user.py index 705d2011c..bb0276607 100644 --- a/src/registrar/models/user.py +++ b/src/registrar/models/user.py @@ -87,7 +87,6 @@ class User(AbstractUser): phone = PhoneNumberField( null=True, blank=True, - help_text="Phone", ) middle_name = models.CharField( diff --git a/src/registrar/signals.py b/src/registrar/signals.py index 4e7768ef4..bc0480b2a 100644 --- a/src/registrar/signals.py +++ b/src/registrar/signals.py @@ -24,9 +24,11 @@ def handle_profile(sender, instance, **kwargs): """ first_name = getattr(instance, "first_name", "") + middle_name = getattr(instance, "middle_name", "") last_name = getattr(instance, "last_name", "") email = getattr(instance, "email", "") phone = getattr(instance, "phone", "") + title = getattr(instance, "title", "") is_new_user = kwargs.get("created", False) @@ -39,9 +41,11 @@ def handle_profile(sender, instance, **kwargs): Contact.objects.create( user=instance, first_name=first_name, + middle_name=middle_name, last_name=last_name, email=email, phone=phone, + title=title, ) if len(contacts) >= 1 and is_new_user: # a matching contact diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index 1409b1f76..562c4f82e 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -3590,7 +3590,7 @@ class TestMyUserAdmin(TestCase): ) }, ), - ("Personal Info", {"fields": ("first_name", "middle_name", "last_name", "email", "title")}), + ("Personal Info", {"fields": ("first_name", "middle_name", "last_name", "title", "email", "phone")}), ("Permissions", {"fields": ("is_active", "groups")}), ("Important dates", {"fields": ("last_login", "date_joined")}), ) diff --git a/src/registrar/tests/test_copy_names_from_contacts_to_users.py b/src/registrar/tests/test_copy_names_from_contacts_to_users.py index 032203f4e..7fcbede1e 100644 --- a/src/registrar/tests/test_copy_names_from_contacts_to_users.py +++ b/src/registrar/tests/test_copy_names_from_contacts_to_users.py @@ -23,15 +23,32 @@ class TestDataUpdates(TestCase): self.bs_user = User.objects.create() self.contact1 = Contact.objects.create( - user=self.user1, email="email1@igorville.gov", first_name="first1", last_name="last1" + user=self.user1, + email="email1@igorville.gov", + first_name="first1", + last_name="last1", + middle_name="middle1", + title="title1", ) self.contact2 = Contact.objects.create( - user=self.user2, email="email2@igorville.gov", first_name="first2", last_name="last2" + user=self.user2, + email="email2@igorville.gov", + first_name="first2", + last_name="last2", + middle_name="middle2", + title="title2", ) self.contact3 = Contact.objects.create( - user=self.user3, email="email3@igorville.gov", first_name="first3", last_name="last3" + user=self.user3, + email="email3@igorville.gov", + first_name="first3", + last_name="last3", + middle_name="middle3", + title="title3", + ) + self.contact4 = Contact.objects.create( + email="email4@igorville.gov", first_name="first4", last_name="last4", middle_name="middle4", title="title4" ) - self.contact4 = Contact.objects.create(email="email4@igorville.gov", first_name="first4", last_name="last4") self.command = Command() @@ -42,14 +59,15 @@ class TestDataUpdates(TestCase): Contact.objects.all().delete() def test_script_updates_linked_users(self): - """Test the script that copies contacts' first and last names into associated users that - are eligible (first or last are blank or undefined)""" + """Test the script that copies contact information to the user object""" # Set up the users' first and last names here so # they that they don't get overwritten by Contact's save() # User with no first or last names self.user1.first_name = "" self.user1.last_name = "" + self.user1.title = "dummytitle" + self.user1.middle_name = "dummymiddle" self.user1.save() # User with a first name but no last name @@ -87,12 +105,20 @@ class TestDataUpdates(TestCase): # The user that has no first and last names will get them from the contact self.assertEqual(self.user1.first_name, "first1") self.assertEqual(self.user1.last_name, "last1") - # The user that has a first but no last will be left alone - self.assertEqual(self.user2.first_name, "First name but no last name") - self.assertEqual(self.user2.last_name, "") - # The user that has a first and a last will be left alone - self.assertEqual(self.user3.first_name, "An existing first name") - self.assertEqual(self.user3.last_name, "An existing last name") + self.assertEqual(self.user1.middle_name, "middle1") + self.assertEqual(self.user1.title, "title1") + # The user that has a first but no last will be updated + self.assertEqual(self.user2.first_name, "first2") + self.assertEqual(self.user2.last_name, "last2") + self.assertEqual(self.user2.middle_name, "middle2") + self.assertEqual(self.user2.title, "title2") + # The user that has a first and a last will be updated + self.assertEqual(self.user3.first_name, "first3") + self.assertEqual(self.user3.last_name, "last3") + self.assertEqual(self.user3.middle_name, "middle3") + self.assertEqual(self.user3.title, "title3") # The unlinked user will be left alone self.assertEqual(self.user4.first_name, "") self.assertEqual(self.user4.last_name, "") + self.assertEqual(self.user4.middle_name, None) + self.assertEqual(self.user4.title, None)