diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 343624915..b7172953b 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -4147,11 +4147,13 @@ class PublicContactResource(resources.ModelResource): class PublicContactAdmin(ListHeaderAdmin, ImportExportModelAdmin): """Custom PublicContact admin class.""" - resource_classes = [PublicContactResource] change_form_template = "django/admin/email_clipboard_change_form.html" autocomplete_fields = ["domain"] + list_display = ("registry_id", "contact_type", "domain", "name") + search_fields = ["registry_id", "domain", "name"] + search_help_text = "Search by registry id, domain, or name." def changeform_view(self, request, object_id=None, form_url="", extra_context=None): if extra_context is None: diff --git a/src/registrar/management/commands/update_default_public_contacts.py b/src/registrar/management/commands/update_default_public_contacts.py index ed23a3c6e..1af0c1a45 100644 --- a/src/registrar/management/commands/update_default_public_contacts.py +++ b/src/registrar/management/commands/update_default_public_contacts.py @@ -2,9 +2,8 @@ import logging import argparse from django.core.management import BaseCommand from registrar.management.commands.utility.terminal_helper import PopulateScriptTemplate, TerminalHelper -from registrar.models import PublicContact, Domain -from django.db.models import Q - +from registrar.models import PublicContact +from django.db import transaction from registrar.models.utility.generic_helper import normalize_string from registrar.utility.enums import DefaultEmail @@ -33,6 +32,8 @@ class Command(BaseCommand, PopulateScriptTemplate): ), ) + # print to file setting! + def handle(self, **kwargs): """Loops through each valid User object and updates its verification_type value""" overwrite_updated_contacts = kwargs.get("overwrite_updated_contacts") @@ -57,12 +58,21 @@ class Command(BaseCommand, PopulateScriptTemplate): "pc": {"22201", "20598-0645"}, "email": default_emails, } - + # 16 if not target_domain: filter_condition = {"email__in": default_emails} else: filter_condition = {"email__in": default_emails, "domain__name": target_domain} - self.mass_update_records(PublicContact, filter_condition, [], skip_bulk_update=True) + fields_to_update = ["name", "street1", "pc", "email"] + self.mass_update_records(PublicContact, filter_condition, fields_to_update) + + def bulk_update_fields(self, object_class, to_update, fields_to_update): + with transaction.atomic(): + super().bulk_update_fields(object_class, to_update, fields_to_update) + TerminalHelper.colorful_logger("INFO", "MAGENTA", f"Updating records in EPP...") + for record in to_update: + record.add_to_domain_in_epp() + TerminalHelper.colorful_logger("INFO", "OKCYAN", f"Updated '{record}' in EPP.") def update_record(self, record: PublicContact): """Defines how we update the verification_type field""" @@ -71,16 +81,22 @@ class Command(BaseCommand, PopulateScriptTemplate): record.pc = "22201" record.email = DefaultEmail.PUBLIC_CONTACT_DEFAULT TerminalHelper.colorful_logger("INFO", "OKCYAN", f"Updating default values for '{record}'.") - record.save() - TerminalHelper.colorful_logger("INFO", "OKCYAN", f"Updated record in EPP.") def should_skip_record(self, record) -> bool: # noqa """Skips updating a public contact if it contains different default info.""" + if record.registry_id and len(record.registry_id) < 16: + message = ( + f"Skipping legacy verisign contact '{record}'. " + f"The registry_id field has a length less than 16 characters." + ) + TerminalHelper.colorful_logger("WARNING", "YELLOW", message) + return True + for key, expected_values in self.old_and_new_default_contact_values.items(): record_field = normalize_string(getattr(record, key)) if record_field not in expected_values: message = ( - f"Skipping '{record}' to avoid data corruption. " + f"Skipping '{record}' to avoid potential data corruption. " f"The field '{key}' does not match the default.\n" f"Details: DB value - {record_field}, expected value(s) - {expected_values}" ) diff --git a/src/registrar/management/commands/utility/terminal_helper.py b/src/registrar/management/commands/utility/terminal_helper.py index fec21fd4d..197a44ed3 100644 --- a/src/registrar/management/commands/utility/terminal_helper.py +++ b/src/registrar/management/commands/utility/terminal_helper.py @@ -87,7 +87,7 @@ class PopulateScriptTemplate(ABC): raise NotImplementedError def mass_update_records( - self, object_class, filter_conditions, fields_to_update, debug=True, verbose=False, skip_bulk_update=False + self, object_class, filter_conditions, fields_to_update, debug=True, verbose=False ): """Loops through each valid "object_class" object - specified by filter_conditions - and updates fields defined by fields_to_update using update_record. @@ -108,11 +108,6 @@ class PopulateScriptTemplate(ABC): verbose: Whether to print a detailed run summary *before* run confirmation. Default: False. - skip_bulk_update: Whether to avoid doing a bulk update or not. - This setting assumes that you are doing a save in the update_record class. - IMPORANT: this setting invalidates 'fields_to_update'. - Default: False - Raises: NotImplementedError: If you do not define update_record before using this function. TypeError: If custom_filter is not Callable. @@ -162,8 +157,7 @@ class PopulateScriptTemplate(ABC): logger.error(fail_message) # Do a bulk update on the desired field - if not skip_bulk_update: - ScriptDataHelper.bulk_update_fields(object_class, to_update, fields_to_update) + self.bulk_update_fields(object_class, to_update, fields_to_update) # Log what happened TerminalHelper.log_script_run_summary( @@ -175,6 +169,9 @@ class PopulateScriptTemplate(ABC): display_as_str=True, ) + def bulk_update_fields(self, object_class, to_update, fields_to_update): + ScriptDataHelper.bulk_update_fields(object_class, to_update, fields_to_update) + def get_class_name(self, sender) -> str: """Returns the class name that we want to display for the terminal prompt. Example: DomainRequest => "Domain Request" @@ -472,4 +469,4 @@ class TerminalHelper: terminal_color = color colored_message = f"{terminal_color}{message}{TerminalColors.ENDC}" - log_method(colored_message, exc_info=exc_info) + return log_method(colored_message, exc_info=exc_info) diff --git a/src/registrar/models/public_contact.py b/src/registrar/models/public_contact.py index d3167960d..6ca2267f0 100644 --- a/src/registrar/models/public_contact.py +++ b/src/registrar/models/public_contact.py @@ -41,21 +41,6 @@ class PublicContact(TimeStampedModel): TECHNICAL = "tech", "Technical" SECURITY = "security", "Security" - def save(self, *args, **kwargs): - """Save to the registry and also locally in the registrar database.""" - skip_epp_save = kwargs.pop("skip_epp_save", False) - if hasattr(self, "domain") and not skip_epp_save: - match self.contact_type: - case PublicContact.ContactTypeChoices.REGISTRANT: - self.domain.registrant_contact = self - case PublicContact.ContactTypeChoices.ADMINISTRATIVE: - self.domain.administrative_contact = self - case PublicContact.ContactTypeChoices.TECHNICAL: - self.domain.technical_contact = self - case PublicContact.ContactTypeChoices.SECURITY: - self.domain.security_contact = self - super().save(*args, **kwargs) - contact_type = models.CharField( max_length=14, choices=ContactTypeChoices.choices, @@ -91,6 +76,25 @@ class PublicContact(TimeStampedModel): ) pw = models.CharField(null=False, help_text="Contact's authorization code. 16 characters minimum.") + def save(self, *args, **kwargs): + """Save to the registry and also locally in the registrar database.""" + skip_epp_save = kwargs.pop("skip_epp_save", False) + if hasattr(self, "domain") and not skip_epp_save: + self.add_to_domain_in_epp() + super().save(*args, **kwargs) + + def add_to_domain_in_epp(self): + """Adds the current contact to the underlying domain in EPP.""" + match self.contact_type: + case PublicContact.ContactTypeChoices.REGISTRANT: + self.domain.registrant_contact = self + case PublicContact.ContactTypeChoices.ADMINISTRATIVE: + self.domain.administrative_contact = self + case PublicContact.ContactTypeChoices.TECHNICAL: + self.domain.technical_contact = self + case PublicContact.ContactTypeChoices.SECURITY: + self.domain.security_contact = self + def print_contact_info_epp(self): """Prints registry data for this PublicContact for easier debugging""" results = self.domain._request_contact_info(self, get_result_as_dict=True) @@ -172,4 +176,4 @@ class PublicContact(TimeStampedModel): return cls._meta.get_field("registry_id").max_length def __str__(self): - return f"{self.name} <{self.email}>" f"id: {self.registry_id} " f"type: {self.contact_type}" + return self.registry_id