diff --git a/src/api/tests/test_available.py b/src/api/tests/test_available.py index 66f7de13f..fa9dadcd4 100644 --- a/src/api/tests/test_available.py +++ b/src/api/tests/test_available.py @@ -69,8 +69,8 @@ class AvailableViewTest(MockEppLib): self.assertTrue(check_domain_available("igorville.gov")) # input is lowercased so GSA.GOV should also not be available self.assertFalse(check_domain_available("GSA.gov")) - # input is lowercased so IGORVILLE.GOV should also not be available - self.assertFalse(check_domain_available("IGORVILLE.gov")) + # input is lowercased so IGORVILLE.GOV should also be available + self.assertTrue(check_domain_available("IGORVILLE.gov")) def test_domain_available_dotgov(self): """Domain searches work without trailing .gov""" diff --git a/src/api/views.py b/src/api/views.py index 700a8b1d5..068844919 100644 --- a/src/api/views.py +++ b/src/api/views.py @@ -32,7 +32,9 @@ DOMAIN_API_MESSAGES = { "Read more about choosing your .gov domain.".format(public_site_url("domains/choosing")) ), "invalid": "Enter a domain using only letters, numbers, or hyphens (though we don't recommend using hyphens).", - "success": "That domain is available!", + "success": "That domain is available! We’ll try to give you the domain you want, \ + but it's not guaranteed. After you complete this form, we’ll \ + evaluate whether your request meets our requirements.", "error": GenericError.get_error_message(GenericErrorCodes.CANNOT_CONTACT_REGISTRY), } diff --git a/src/epplibwrapper/client.py b/src/epplibwrapper/client.py index cfb41f4ea..9ed437aef 100644 --- a/src/epplibwrapper/client.py +++ b/src/epplibwrapper/client.py @@ -17,7 +17,7 @@ except ImportError: from django.conf import settings from .cert import Cert, Key -from .errors import LoginError, RegistryError +from .errors import ErrorCode, LoginError, RegistryError from .socket import Socket from .utility.pool import EPPConnectionPool @@ -115,7 +115,7 @@ class EPPLibWrapper: except TransportError as err: message = f"{cmd_type} failed to execute due to a connection error." logger.error(f"{message} Error: {err}", exc_info=True) - raise RegistryError(message) from err + raise RegistryError(message, code=ErrorCode.TRANSPORT_ERROR) from err except LoginError as err: # For linter due to it not liking this line length text = "failed to execute due to a registry login error." @@ -163,7 +163,8 @@ class EPPLibWrapper: try: return self._send(command) except RegistryError as err: - if err.should_retry() and counter < 3: + if counter < 3 and (err.should_retry() or err.is_transport_error()): + logger.info(f"Retrying transport error. Attempt #{counter+1} of 3.") counter += 1 sleep((counter * 50) / 1000) # sleep 50 ms to 150 ms else: # don't try again diff --git a/src/epplibwrapper/errors.py b/src/epplibwrapper/errors.py index d34ed5e91..2b7bdd255 100644 --- a/src/epplibwrapper/errors.py +++ b/src/epplibwrapper/errors.py @@ -4,13 +4,15 @@ from enum import IntEnum class ErrorCode(IntEnum): """ Overview of registry response codes from RFC 5730. See RFC 5730 for full text. - + - 0 System connection error - 1000 - 1500 Success - 2000 - 2308 Registrar did something silly - 2400 - 2500 Registry did something silly - 2501 - 2502 Something malicious or abusive may have occurred """ + TRANSPORT_ERROR = 0 + COMMAND_COMPLETED_SUCCESSFULLY = 1000 COMMAND_COMPLETED_SUCCESSFULLY_ACTION_PENDING = 1001 COMMAND_COMPLETED_SUCCESSFULLY_NO_MESSAGES = 1300 @@ -67,6 +69,9 @@ class RegistryError(Exception): def should_retry(self): return self.code == ErrorCode.COMMAND_FAILED + def is_transport_error(self): + return self.code == ErrorCode.TRANSPORT_ERROR + # connection errors have error code of None and [Errno 99] in the err message def is_connection_error(self): return self.code is None diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 429bd762f..def7c64b1 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -527,14 +527,14 @@ class DomainApplicationAdminForm(forms.ModelForm): current_state = application.status # first option in status transitions is current state - available_transitions = [(current_state, current_state)] + available_transitions = [(current_state, application.get_status_display())] transitions = get_available_FIELD_transitions( application, models.DomainApplication._meta.get_field("status") ) for transition in transitions: - available_transitions.append((transition.target, transition.target)) + available_transitions.append((transition.target, transition.target.label)) # only set the available transitions if the user is not restricted # from editing the domain application; otherwise, the form will be @@ -650,10 +650,10 @@ class DomainApplicationAdmin(ListHeaderAdmin): if ( obj - and original_obj.status == models.DomainApplication.APPROVED + and original_obj.status == models.DomainApplication.ApplicationStatus.APPROVED and ( - obj.status == models.DomainApplication.REJECTED - or obj.status == models.DomainApplication.INELIGIBLE + obj.status == models.DomainApplication.ApplicationStatus.REJECTED + or obj.status == models.DomainApplication.ApplicationStatus.INELIGIBLE ) and not obj.domain_is_not_active() ): @@ -675,14 +675,14 @@ class DomainApplicationAdmin(ListHeaderAdmin): else: if obj.status != original_obj.status: status_method_mapping = { - models.DomainApplication.STARTED: None, - models.DomainApplication.SUBMITTED: obj.submit, - models.DomainApplication.IN_REVIEW: obj.in_review, - models.DomainApplication.ACTION_NEEDED: obj.action_needed, - models.DomainApplication.APPROVED: obj.approve, - models.DomainApplication.WITHDRAWN: obj.withdraw, - models.DomainApplication.REJECTED: obj.reject, - models.DomainApplication.INELIGIBLE: (obj.reject_with_prejudice), + models.DomainApplication.ApplicationStatus.STARTED: None, + models.DomainApplication.ApplicationStatus.SUBMITTED: obj.submit, + models.DomainApplication.ApplicationStatus.IN_REVIEW: obj.in_review, + models.DomainApplication.ApplicationStatus.ACTION_NEEDED: obj.action_needed, + models.DomainApplication.ApplicationStatus.APPROVED: obj.approve, + models.DomainApplication.ApplicationStatus.WITHDRAWN: obj.withdraw, + models.DomainApplication.ApplicationStatus.REJECTED: obj.reject, + models.DomainApplication.ApplicationStatus.INELIGIBLE: (obj.reject_with_prejudice), } selected_method = status_method_mapping.get(obj.status) if selected_method is None: @@ -752,6 +752,7 @@ class TransitionDomainAdmin(ListHeaderAdmin): "domain_name", "status", "email_sent", + "processed", ] search_fields = ["username", "domain_name"] diff --git a/src/registrar/fixtures_applications.py b/src/registrar/fixtures_applications.py index aea153aef..ad3ae0820 100644 --- a/src/registrar/fixtures_applications.py +++ b/src/registrar/fixtures_applications.py @@ -49,28 +49,28 @@ class DomainApplicationFixture: # }, DA = [ { - "status": "started", - "organization_name": "Example - Finished but not Submitted", + "status": DomainApplication.ApplicationStatus.STARTED, + "organization_name": "Example - Finished but not submitted", }, { - "status": "submitted", - "organization_name": "Example - Submitted but pending Investigation", + "status": DomainApplication.ApplicationStatus.SUBMITTED, + "organization_name": "Example - Submitted but pending investigation", }, { - "status": "in review", - "organization_name": "Example - In Investigation", + "status": DomainApplication.ApplicationStatus.IN_REVIEW, + "organization_name": "Example - In investigation", }, { - "status": "in review", + "status": DomainApplication.ApplicationStatus.IN_REVIEW, "organization_name": "Example - Approved", }, { - "status": "withdrawn", + "status": DomainApplication.ApplicationStatus.WITHDRAWN, "organization_name": "Example - Withdrawn", }, { - "status": "action needed", - "organization_name": "Example - Action Needed", + "status": DomainApplication.ApplicationStatus.ACTION_NEEDED, + "organization_name": "Example - Action needed", }, { "status": "rejected", @@ -214,7 +214,9 @@ class DomainFixture(DomainApplicationFixture): for user in users: # approve one of each users in review status domains - application = DomainApplication.objects.filter(creator=user, status=DomainApplication.IN_REVIEW).last() + application = DomainApplication.objects.filter( + creator=user, status=DomainApplication.ApplicationStatus.IN_REVIEW + ).last() logger.debug(f"Approving {application} for {user}") application.approve() application.save() diff --git a/src/registrar/forms/application_wizard.py b/src/registrar/forms/application_wizard.py index 9a8899e2b..5310c4610 100644 --- a/src/registrar/forms/application_wizard.py +++ b/src/registrar/forms/application_wizard.py @@ -262,7 +262,7 @@ class OrganizationContactForm(RegistrarForm): validators=[ RegexValidator( "^[0-9]{5}(?:-[0-9]{4})?$|^$", - message="Enter a zip code in the form of 12345 or 12345-6789.", + message="Enter a zip code in the required format, like 12345 or 12345-6789.", ) ], ) diff --git a/src/registrar/forms/common.py b/src/registrar/forms/common.py index 3ab36cf6f..e2a234e71 100644 --- a/src/registrar/forms/common.py +++ b/src/registrar/forms/common.py @@ -1,6 +1,6 @@ # common.py # -# ALGORITHM_CHOICES are options for alg attribute in DS Data +# ALGORITHM_CHOICES are options for alg attribute in DS data # reference: # https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml ALGORITHM_CHOICES = [ @@ -18,7 +18,7 @@ ALGORITHM_CHOICES = [ (15, "(15) Ed25519"), (16, "(16) Ed448"), ] -# DIGEST_TYPE_CHOICES are options for digestType attribute in DS Data +# DIGEST_TYPE_CHOICES are options for digestType attribute in DS data # reference: https://datatracker.ietf.org/doc/html/rfc4034#appendix-A.2 DIGEST_TYPE_CHOICES = [ (1, "(1) SHA-1"), diff --git a/src/registrar/forms/domain.py b/src/registrar/forms/domain.py index ff41b9268..ac96393b4 100644 --- a/src/registrar/forms/domain.py +++ b/src/registrar/forms/domain.py @@ -28,6 +28,17 @@ class DomainAddUserForm(forms.Form): email = forms.EmailField(label="Email") + def clean(self): + """clean form data by lowercasing email""" + cleaned_data = super().clean() + + # Lowercase the value of the 'email' field + email_value = cleaned_data.get("email") + if email_value: + cleaned_data["email"] = email_value.lower() + + return cleaned_data + class DomainNameserverForm(forms.Form): """Form for changing nameservers.""" @@ -239,7 +250,7 @@ class DomainOrgNameAddressForm(forms.ModelForm): validators=[ RegexValidator( "^[0-9]{5}(?:-[0-9]{4})?$|^$", - message="Enter a zip code in the form of 12345 or 12345-6789.", + message="Enter a zip code in the required format, like 12345 or 12345-6789.", ) ], ) @@ -302,7 +313,7 @@ class DomainDnssecForm(forms.Form): class DomainDsdataForm(forms.Form): - """Form for adding or editing DNSSEC DS Data to a domain.""" + """Form for adding or editing DNSSEC DS data to a domain.""" def validate_hexadecimal(value): """ diff --git a/src/registrar/management/commands/load_domain_invitations.py b/src/registrar/management/commands/load_domain_invitations.py index 28eb09def..32a63d860 100644 --- a/src/registrar/management/commands/load_domain_invitations.py +++ b/src/registrar/management/commands/load_domain_invitations.py @@ -62,7 +62,7 @@ class Command(BaseCommand): DomainInvitation( email=email_address.lower(), domain=domain, - status=DomainInvitation.INVITED, + status=DomainInvitation.DomainInvitationStatus.INVITED, ) ) logger.info("Creating %d invitations", len(to_create)) diff --git a/src/registrar/management/commands/load_transition_domain.py b/src/registrar/management/commands/load_transition_domain.py index e1165bf9f..4132096c8 100644 --- a/src/registrar/management/commands/load_transition_domain.py +++ b/src/registrar/management/commands/load_transition_domain.py @@ -536,19 +536,27 @@ class Command(BaseCommand): domain_name=new_entry_domain_name, ) - if existing_entry.status != new_entry_status: - # DEBUG: + if not existing_entry.processed: + if existing_entry.status != new_entry_status: + TerminalHelper.print_conditional( + debug_on, + f"{TerminalColors.OKCYAN}" + f"Updating entry: {existing_entry}" + f"Status: {existing_entry.status} > {new_entry_status}" # noqa + f"Email Sent: {existing_entry.email_sent} > {new_entry_emailSent}" # noqa + f"{TerminalColors.ENDC}", + ) + existing_entry.status = new_entry_status + existing_entry.email_sent = new_entry_emailSent + existing_entry.save() + else: TerminalHelper.print_conditional( debug_on, - f"{TerminalColors.OKCYAN}" - f"Updating entry: {existing_entry}" - f"Status: {existing_entry.status} > {new_entry_status}" # noqa - f"Email Sent: {existing_entry.email_sent} > {new_entry_emailSent}" # noqa + f"{TerminalColors.YELLOW}" + f"Skipping update on processed domain: {existing_entry}" f"{TerminalColors.ENDC}", ) - existing_entry.status = new_entry_status - existing_entry.email_sent = new_entry_emailSent - existing_entry.save() + except TransitionDomain.MultipleObjectsReturned: logger.info( f"{TerminalColors.FAIL}" @@ -558,6 +566,7 @@ class Command(BaseCommand): f"----------TERMINATING----------" ) sys.exit() + else: # no matching entry, make one new_entry = TransitionDomain( @@ -565,6 +574,7 @@ class Command(BaseCommand): domain_name=new_entry_domain_name, status=new_entry_status, email_sent=new_entry_emailSent, + processed=False, ) to_create.append(new_entry) total_new_entries += 1 diff --git a/src/registrar/management/commands/transfer_transition_domains_to_domains.py b/src/registrar/management/commands/transfer_transition_domains_to_domains.py index d0d6ff363..15cd7376d 100644 --- a/src/registrar/management/commands/transfer_transition_domains_to_domains.py +++ b/src/registrar/management/commands/transfer_transition_domains_to_domains.py @@ -559,7 +559,8 @@ class Command(BaseCommand): debug_max_entries_to_parse, total_rows_parsed, ): - for transition_domain in TransitionDomain.objects.all(): + changed_transition_domains = TransitionDomain.objects.filter(processed=False) + for transition_domain in changed_transition_domains: ( target_domain_information, associated_domain, @@ -644,7 +645,8 @@ class Command(BaseCommand): debug_max_entries_to_parse, total_rows_parsed, ): - for transition_domain in TransitionDomain.objects.all(): + changed_transition_domains = TransitionDomain.objects.filter(processed=False) + for transition_domain in changed_transition_domains: # Create some local variables to make data tracing easier transition_domain_name = transition_domain.domain_name transition_domain_status = transition_domain.status @@ -796,6 +798,7 @@ class Command(BaseCommand): # First, save all Domain objects to the database Domain.objects.bulk_create(domains_to_create) + # DomainInvitation.objects.bulk_create(domain_invitations_to_create) # TODO: this is to resolve an error where bulk_create @@ -847,6 +850,15 @@ class Command(BaseCommand): ) DomainInformation.objects.bulk_create(domain_information_to_create) + # Loop through the list of everything created, and mark it as processed + for domain in domains_to_create: + name = domain.name + TransitionDomain.objects.filter(domain_name=name).update(processed=True) + + # Loop through the list of everything updated, and mark it as processed + for name in updated_domain_entries: + TransitionDomain.objects.filter(domain_name=name).update(processed=True) + self.print_summary_of_findings( domains_to_create, updated_domain_entries, diff --git a/src/registrar/management/commands/utility/extra_transition_domain_helper.py b/src/registrar/management/commands/utility/extra_transition_domain_helper.py index 04170811f..54f68d5c8 100644 --- a/src/registrar/management/commands/utility/extra_transition_domain_helper.py +++ b/src/registrar/management/commands/utility/extra_transition_domain_helper.py @@ -155,13 +155,13 @@ class LoadExtraTransitionDomain: def update_transition_domain_models(self): """Updates TransitionDomain objects based off the file content given in self.parsed_data_container""" - all_transition_domains = TransitionDomain.objects.all() - if not all_transition_domains.exists(): - raise ValueError("No TransitionDomain objects exist.") + valid_transition_domains = TransitionDomain.objects.filter(processed=False) + if not valid_transition_domains.exists(): + raise ValueError("No updatable TransitionDomain objects exist.") updated_transition_domains = [] failed_transition_domains = [] - for transition_domain in all_transition_domains: + for transition_domain in valid_transition_domains: domain_name = transition_domain.domain_name updated_transition_domain = transition_domain try: @@ -228,7 +228,7 @@ class LoadExtraTransitionDomain: # DATA INTEGRITY CHECK # Make sure every Transition Domain got updated total_transition_domains = len(updated_transition_domains) - total_updates_made = TransitionDomain.objects.all().count() + total_updates_made = TransitionDomain.objects.filter(processed=False).count() if total_transition_domains != total_updates_made: # noqa here for line length logger.error( @@ -787,7 +787,7 @@ class OrganizationDataLoader: self.tds_to_update: List[TransitionDomain] = [] def update_organization_data_for_all(self): - """Updates org address data for all TransitionDomains""" + """Updates org address data for valid TransitionDomains""" all_transition_domains = TransitionDomain.objects.all() if len(all_transition_domains) == 0: raise LoadOrganizationError(code=LoadOrganizationErrorCodes.EMPTY_TRANSITION_DOMAIN_TABLE) diff --git a/src/registrar/migrations/0055_transitiondomain_processed.py b/src/registrar/migrations/0055_transitiondomain_processed.py new file mode 100644 index 000000000..a2fb78edd --- /dev/null +++ b/src/registrar/migrations/0055_transitiondomain_processed.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.7 on 2023-12-12 21:46 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0054_alter_domainapplication_federal_agency_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="transitiondomain", + name="processed", + field=models.BooleanField( + default=True, + help_text="Indicates whether this TransitionDomain was already processed", + verbose_name="Processed", + ), + ), + ] diff --git a/src/registrar/migrations/0056_alter_domain_state_alter_domainapplication_status_and_more.py b/src/registrar/migrations/0056_alter_domain_state_alter_domainapplication_status_and_more.py new file mode 100644 index 000000000..097cddf8a --- /dev/null +++ b/src/registrar/migrations/0056_alter_domain_state_alter_domainapplication_status_and_more.py @@ -0,0 +1,70 @@ +# Generated by Django 4.2.7 on 2023-12-06 16:16 + +from django.db import migrations, models +import django_fsm + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0055_transitiondomain_processed"), + ] + + operations = [ + migrations.AlterField( + model_name="domain", + name="state", + field=django_fsm.FSMField( + choices=[ + ("unknown", "Unknown"), + ("dns needed", "Dns needed"), + ("ready", "Ready"), + ("on hold", "On hold"), + ("deleted", "Deleted"), + ], + default="unknown", + help_text="Very basic info about the lifecycle of this domain object", + max_length=21, + protected=True, + ), + ), + migrations.AlterField( + model_name="domainapplication", + name="status", + field=django_fsm.FSMField( + choices=[ + ("started", "Started"), + ("submitted", "Submitted"), + ("in review", "In review"), + ("action needed", "Action needed"), + ("approved", "Approved"), + ("withdrawn", "Withdrawn"), + ("rejected", "Rejected"), + ("ineligible", "Ineligible"), + ], + default="started", + max_length=50, + ), + ), + migrations.AlterField( + model_name="domaininvitation", + name="status", + field=django_fsm.FSMField( + choices=[("invited", "Invited"), ("retrieved", "Retrieved")], + default="invited", + max_length=50, + protected=True, + ), + ), + migrations.AlterField( + model_name="transitiondomain", + name="status", + field=models.CharField( + blank=True, + choices=[("ready", "Ready"), ("on hold", "On hold"), ("unknown", "Unknown")], + default="ready", + help_text="domain status during the transfer", + max_length=255, + verbose_name="Status", + ), + ), + ] diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 94430fb36..c92f540f1 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -122,20 +122,20 @@ class Domain(TimeStampedModel, DomainHelper): """These capture (some of) the states a domain object can be in.""" # the state is indeterminate - UNKNOWN = "unknown" + UNKNOWN = "unknown", "Unknown" # The domain object exists in the registry # but nameservers don't exist for it yet - DNS_NEEDED = "dns needed" + DNS_NEEDED = "dns needed", "Dns needed" # Domain has had nameservers set, may or may not be active - READY = "ready" + READY = "ready", "Ready" # Registrar manually changed state to client hold - ON_HOLD = "on hold" + ON_HOLD = "on hold", "On hold" # previously existed but has been deleted from the registry - DELETED = "deleted" + DELETED = "deleted", "Deleted" class Cache(property): """ @@ -174,7 +174,8 @@ class Domain(TimeStampedModel, DomainHelper): """Check if a domain is available.""" if not cls.string_could_be_domain(domain): raise ValueError("Not a valid domain: %s" % str(domain)) - req = commands.CheckDomain([domain]) + domain_name = domain.lower() + req = commands.CheckDomain([domain_name]) return registry.send(req, cleaned=True).res_data[0].avail @classmethod diff --git a/src/registrar/models/domain_application.py b/src/registrar/models/domain_application.py index 2eb2075b7..12eda4caf 100644 --- a/src/registrar/models/domain_application.py +++ b/src/registrar/models/domain_application.py @@ -19,25 +19,16 @@ class DomainApplication(TimeStampedModel): """A registrant's application for a new domain.""" - # #### Constants for choice fields #### - STARTED = "started" - SUBMITTED = "submitted" - IN_REVIEW = "in review" - ACTION_NEEDED = "action needed" - APPROVED = "approved" - WITHDRAWN = "withdrawn" - REJECTED = "rejected" - INELIGIBLE = "ineligible" - STATUS_CHOICES = [ - (STARTED, STARTED), - (SUBMITTED, SUBMITTED), - (IN_REVIEW, IN_REVIEW), - (ACTION_NEEDED, ACTION_NEEDED), - (APPROVED, APPROVED), - (WITHDRAWN, WITHDRAWN), - (REJECTED, REJECTED), - (INELIGIBLE, INELIGIBLE), - ] + # Constants for choice fields + class ApplicationStatus(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" class StateTerritoryChoices(models.TextChoices): ALABAMA = "AL", "Alabama (AL)" @@ -363,8 +354,8 @@ class DomainApplication(TimeStampedModel): # #### Internal fields about the application ##### status = FSMField( - choices=STATUS_CHOICES, # possible states as an array of constants - default=STARTED, # sensible default + choices=ApplicationStatus.choices, # possible states as an array of constants + default=ApplicationStatus.STARTED, # sensible default protected=False, # can change state directly, particularly in Django admin ) # This is the application user who created this application. The contact @@ -592,7 +583,11 @@ class DomainApplication(TimeStampedModel): except EmailSendingError: logger.warning("Failed to send confirmation email", exc_info=True) - @transition(field="status", source=[STARTED, ACTION_NEEDED, WITHDRAWN], target=SUBMITTED) + @transition( + field="status", + source=[ApplicationStatus.STARTED, ApplicationStatus.ACTION_NEEDED, ApplicationStatus.WITHDRAWN], + target=ApplicationStatus.SUBMITTED, + ) def submit(self): """Submit an application that is started. @@ -618,7 +613,7 @@ class DomainApplication(TimeStampedModel): "emails/submission_confirmation_subject.txt", ) - @transition(field="status", source=SUBMITTED, target=IN_REVIEW) + @transition(field="status", source=ApplicationStatus.SUBMITTED, target=ApplicationStatus.IN_REVIEW) def in_review(self): """Investigate an application that has been submitted. @@ -630,7 +625,11 @@ class DomainApplication(TimeStampedModel): "emails/status_change_in_review_subject.txt", ) - @transition(field="status", source=[IN_REVIEW, REJECTED], target=ACTION_NEEDED) + @transition( + field="status", + source=[ApplicationStatus.IN_REVIEW, ApplicationStatus.REJECTED], + target=ApplicationStatus.ACTION_NEEDED, + ) def action_needed(self): """Send back an application that is under investigation or rejected. @@ -644,8 +643,13 @@ class DomainApplication(TimeStampedModel): @transition( field="status", - source=[SUBMITTED, IN_REVIEW, REJECTED, INELIGIBLE], - target=APPROVED, + source=[ + ApplicationStatus.SUBMITTED, + ApplicationStatus.IN_REVIEW, + ApplicationStatus.REJECTED, + ApplicationStatus.INELIGIBLE, + ], + target=ApplicationStatus.APPROVED, ) def approve(self): """Approve an application that has been submitted. @@ -678,7 +682,11 @@ class DomainApplication(TimeStampedModel): "emails/status_change_approved_subject.txt", ) - @transition(field="status", source=[SUBMITTED, IN_REVIEW], target=WITHDRAWN) + @transition( + field="status", + source=[ApplicationStatus.SUBMITTED, ApplicationStatus.IN_REVIEW], + target=ApplicationStatus.WITHDRAWN, + ) def withdraw(self): """Withdraw an application that has been submitted.""" self._send_status_update_email( @@ -689,8 +697,8 @@ class DomainApplication(TimeStampedModel): @transition( field="status", - source=[IN_REVIEW, APPROVED], - target=REJECTED, + source=[ApplicationStatus.IN_REVIEW, ApplicationStatus.APPROVED], + target=ApplicationStatus.REJECTED, conditions=[domain_is_not_active], ) def reject(self): @@ -698,7 +706,7 @@ class DomainApplication(TimeStampedModel): As side effects this will delete the domain and domain_information (will cascade), and send an email notification.""" - if self.status == self.APPROVED: + if self.status == self.ApplicationStatus.APPROVED: domain_state = self.approved_domain.state # Only reject if it exists on EPP if domain_state != Domain.State.UNKNOWN: @@ -714,8 +722,8 @@ class DomainApplication(TimeStampedModel): @transition( field="status", - source=[IN_REVIEW, APPROVED], - target=INELIGIBLE, + source=[ApplicationStatus.IN_REVIEW, ApplicationStatus.APPROVED], + target=ApplicationStatus.INELIGIBLE, conditions=[domain_is_not_active], ) def reject_with_prejudice(self): @@ -727,7 +735,7 @@ class DomainApplication(TimeStampedModel): permissions classes test against. This will also delete the domain and domain_information (will cascade) when they exist.""" - if self.status == self.APPROVED: + if self.status == self.ApplicationStatus.APPROVED: domain_state = self.approved_domain.state # Only reject if it exists on EPP if domain_state != Domain.State.UNKNOWN: diff --git a/src/registrar/models/domain_invitation.py b/src/registrar/models/domain_invitation.py index 1e0b7fec8..12082142d 100644 --- a/src/registrar/models/domain_invitation.py +++ b/src/registrar/models/domain_invitation.py @@ -15,8 +15,10 @@ logger = logging.getLogger(__name__) class DomainInvitation(TimeStampedModel): - INVITED = "invited" - RETRIEVED = "retrieved" + # Constants for status field + class DomainInvitationStatus(models.TextChoices): + INVITED = "invited", "Invited" + RETRIEVED = "retrieved", "Retrieved" email = models.EmailField( null=False, @@ -31,18 +33,15 @@ class DomainInvitation(TimeStampedModel): ) status = FSMField( - choices=[ - (INVITED, INVITED), - (RETRIEVED, RETRIEVED), - ], - default=INVITED, + choices=DomainInvitationStatus.choices, + default=DomainInvitationStatus.INVITED, protected=True, # can't alter state except through transition methods! ) def __str__(self): return f"Invitation for {self.email} on {self.domain} is {self.status}" - @transition(field="status", source=INVITED, target=RETRIEVED) + @transition(field="status", source=DomainInvitationStatus.INVITED, target=DomainInvitationStatus.RETRIEVED) def retrieve(self): """When an invitation is retrieved, create the corresponding permission. diff --git a/src/registrar/models/transition_domain.py b/src/registrar/models/transition_domain.py index 28bdc4fc7..915b97d56 100644 --- a/src/registrar/models/transition_domain.py +++ b/src/registrar/models/transition_domain.py @@ -4,7 +4,7 @@ from .utility.time_stamped_model import TimeStampedModel class StatusChoices(models.TextChoices): READY = "ready", "Ready" - ON_HOLD = "on hold", "On Hold" + ON_HOLD = "on hold", "On hold" UNKNOWN = "unknown", "Unknown" @@ -43,6 +43,12 @@ class TransitionDomain(TimeStampedModel): verbose_name="email sent", help_text="indicates whether email was sent", ) + processed = models.BooleanField( + null=False, + default=True, + verbose_name="Processed", + help_text="Indicates whether this TransitionDomain was already processed", + ) organization_type = models.TextField( max_length=255, null=True, diff --git a/src/registrar/models/user.py b/src/registrar/models/user.py index 42f427ea5..d79e4c9ee 100644 --- a/src/registrar/models/user.py +++ b/src/registrar/models/user.py @@ -91,7 +91,8 @@ class User(AbstractUser): # A new incoming user who is being invited to be a domain manager (that is, # their email address is in DomainInvitation for an invitation that is not yet "retrieved"). - if DomainInvitation.objects.filter(email=email, status=DomainInvitation.INVITED).exists(): + invited = DomainInvitation.DomainInvitationStatus.INVITED + if DomainInvitation.objects.filter(email=email, status=invited).exists(): return False return True @@ -99,7 +100,9 @@ class User(AbstractUser): def check_domain_invitations_on_login(self): """When a user first arrives on the site, we need to retrieve any domain invitations that match their email address.""" - for invitation in DomainInvitation.objects.filter(email=self.email, status=DomainInvitation.INVITED): + for invitation in DomainInvitation.objects.filter( + email__iexact=self.email, status=DomainInvitation.DomainInvitationStatus.INVITED + ): try: invitation.retrieve() invitation.save() diff --git a/src/registrar/templates/application_done.html b/src/registrar/templates/application_done.html index a9ee55b47..91f9588b7 100644 --- a/src/registrar/templates/application_done.html +++ b/src/registrar/templates/application_done.html @@ -1,7 +1,7 @@ {% extends 'base.html' %} {% load static %} -{% block title %}Thanks for your domain request!{% endblock %} +{% block title %}Thanks for your domain request! | {% endblock %} {% block content %}
diff --git a/src/registrar/templates/application_status.html b/src/registrar/templates/application_status.html index 3bf02f0e0..fbabf39a7 100644 --- a/src/registrar/templates/application_status.html +++ b/src/registrar/templates/application_status.html @@ -31,7 +31,7 @@ Status: {% if domainapplication.status == 'approved' %} Approved - {% elif domainapplication.status == 'in review' %} In Review + {% elif domainapplication.status == 'in review' %} In review {% elif domainapplication.status == 'rejected' %} Rejected {% elif domainapplication.status == 'submitted' %} Submitted {% elif domainapplication.status == 'ineligible' %} Ineligible diff --git a/src/registrar/templates/application_withdraw_confirmation.html b/src/registrar/templates/application_withdraw_confirmation.html index 1406d224d..c8a3c6732 100644 --- a/src/registrar/templates/application_withdraw_confirmation.html +++ b/src/registrar/templates/application_withdraw_confirmation.html @@ -1,6 +1,6 @@ {% extends 'base.html' %} -{% block title %}Withdraw request for {{ domainapplication.requested_domain.name }}{% endblock %} +{% block title %}Withdraw request for {{ domainapplication.requested_domain.name }} | {% endblock %} {% load static url_helpers %} {% block content %} diff --git a/src/registrar/templates/base.html b/src/registrar/templates/base.html index b4aeba704..a07bf67ee 100644 --- a/src/registrar/templates/base.html +++ b/src/registrar/templates/base.html @@ -149,7 +149,7 @@ {% block logo %} {% endblock %} diff --git a/src/registrar/templates/domain_add_user.html b/src/registrar/templates/domain_add_user.html index d8cae576a..4bad529a7 100644 --- a/src/registrar/templates/domain_add_user.html +++ b/src/registrar/templates/domain_add_user.html @@ -1,7 +1,7 @@ {% extends "domain_base.html" %} {% load static field_helpers %} -{% block title %}Add another user{% endblock %} +{% block title %}Add another user | {% endblock %} {% block domain_content %}

Add a domain manager

diff --git a/src/registrar/templates/domain_detail.html b/src/registrar/templates/domain_detail.html index 81a350f82..470df7537 100644 --- a/src/registrar/templates/domain_detail.html +++ b/src/registrar/templates/domain_detail.html @@ -18,7 +18,7 @@ Status: {% if domain.state == domain.State.UNKNOWN or domain.state == domain.State.DNS_NEEDED%} - DNS Needed + DNS needed {% else %} {{ domain.state|title }} {% endif %} diff --git a/src/registrar/templates/domain_dns.html b/src/registrar/templates/domain_dns.html index 85ba02e16..0f625e0e3 100644 --- a/src/registrar/templates/domain_dns.html +++ b/src/registrar/templates/domain_dns.html @@ -12,9 +12,11 @@

You can enter your name servers, as well as other DNS-related information, in the following sections:

{% url 'domain-dns-nameservers' pk=domain.id as url %} -

DNS name servers

+ {% endblock %} {# domain_content #} diff --git a/src/registrar/templates/domain_dsdata.html b/src/registrar/templates/domain_dsdata.html index d049675fc..15343413b 100644 --- a/src/registrar/templates/domain_dsdata.html +++ b/src/registrar/templates/domain_dsdata.html @@ -1,13 +1,13 @@ {% extends "domain_base.html" %} {% load static field_helpers url_helpers %} -{% block title %}DS Data | {{ domain.name }} | {% endblock %} +{% block title %}DS data | {{ domain.name }} | {% endblock %} {% block domain_content %} {% if domain.dnssecdata is None %}
- You have no DS Data added. Enable DNSSEC by adding DS Data. + You have no DS data added. Enable DNSSEC by adding DS data.
{% endif %} @@ -16,11 +16,11 @@ {% include "includes/form_errors.html" with form=form %} {% endfor %} -

DS Data

+

DS data

In order to enable DNSSEC, you must first configure it with your DNS hosting service.

-

Enter the values given by your DNS provider for DS Data.

+

Enter the values given by your DNS provider for DS data.

{% include "includes/required_fields.html" %} @@ -31,9 +31,9 @@ {% for form in formset %}
- DS Data record {{forloop.counter}} + DS data record {{forloop.counter}} -

DS Data record {{forloop.counter}}

+

DS data record {{forloop.counter}}

diff --git a/src/registrar/templates/domain_sidebar.html b/src/registrar/templates/domain_sidebar.html index c224d60c1..9e00fafa9 100644 --- a/src/registrar/templates/domain_sidebar.html +++ b/src/registrar/templates/domain_sidebar.html @@ -44,7 +44,7 @@ - DS Data + DS data diff --git a/src/registrar/templates/emails/status_change_approved.txt b/src/registrar/templates/emails/status_change_approved.txt index 80bf78842..25abe6e69 100644 --- a/src/registrar/templates/emails/status_change_approved.txt +++ b/src/registrar/templates/emails/status_change_approved.txt @@ -14,7 +14,7 @@ Now that your .gov domain has been approved, there are a few more things to do b YOU MUST ADD DOMAIN NAME SERVER INFORMATION Before your .gov domain can be used, you have to connect it to your Domain Name System (DNS) hosting service. At this time, we don’t provide DNS hosting services. -Go to the domain management page to add your domain name server information . +Go to the domain management page to add your domain name server information . Get help with adding your domain name server information . @@ -23,7 +23,7 @@ ADD DOMAIN MANAGERS, SECURITY EMAIL We strongly recommend that you add other points of contact who will help manage your domain. We also recommend that you provide a security email. This email will allow the public to report security issues on your domain. Security emails are made public. -Go to the domain management page to add domain contacts and a security email . +Go to the domain management page to add domain contacts and a security email . Get help with managing your .gov domain . diff --git a/src/registrar/templates/emails/status_change_in_review.txt b/src/registrar/templates/emails/status_change_in_review.txt index 63df669eb..b45b0d244 100644 --- a/src/registrar/templates/emails/status_change_in_review.txt +++ b/src/registrar/templates/emails/status_change_in_review.txt @@ -22,7 +22,7 @@ NEXT STEPS - We’re reviewing your request. This usually takes 20 business days. - You can check the status of your request at any time. - + - We’ll email you with questions or when we complete our review. diff --git a/src/registrar/templates/emails/submission_confirmation.txt b/src/registrar/templates/emails/submission_confirmation.txt index 41fab8005..2b0c0e86e 100644 --- a/src/registrar/templates/emails/submission_confirmation.txt +++ b/src/registrar/templates/emails/submission_confirmation.txt @@ -21,7 +21,7 @@ NEXT STEPS - We’ll review your request. This usually takes 20 business days. - You can check the status of your request at any time. - + - We’ll email you with questions or when we complete our review. diff --git a/src/registrar/templates/home.html b/src/registrar/templates/home.html index 0bc792cef..38c3e35b4 100644 --- a/src/registrar/templates/home.html +++ b/src/registrar/templates/home.html @@ -2,7 +2,7 @@ {% load static %} -{% block title %} Hello {% endblock %} +{% block title %} Home | {% endblock %} {% block content %}
@@ -53,7 +53,7 @@ {{ domain.created_time|date }} {% if domain.state == "unknown" or domain.state == "dns needed"%} - DNS Needed + DNS needed {% else %} {{ domain.state|title }} {% endif %} @@ -87,7 +87,7 @@ aria-live="polite" >
{% else %} -

You don't have any registered domains yet

+

You don't have any registered domains.

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

Domain requests

{% if domain_applications %} - + @@ -111,7 +111,7 @@ {{ application.requested_domain.name|default:"New domain request" }} - +
Your domain applicationsYour domain requests
Domain name {{ application.created_at|date }}{{ application.status|title }}{{ application.get_status_display }} {% if application.status == "started" or application.status == "action needed" or application.status == "withdrawn" %} @@ -138,7 +138,7 @@ aria-live="polite" > {% else %} -

You don't have any active domain requests right now

+

You haven't requested any domains.

{% endif %} diff --git a/src/registrar/templates/includes/ao_example.html b/src/registrar/templates/includes/ao_example.html index dfdbb3775..85df174aa 100644 --- a/src/registrar/templates/includes/ao_example.html +++ b/src/registrar/templates/includes/ao_example.html @@ -1,50 +1,62 @@ {% if is_federal %} {% if federal_type == 'executive' %} -

Domain requests from executive branch agencies must be authorized by Chief Information Officers or agency heads.

-

Domain requests from executive branch agencies are subject to guidance issued by the U.S. Office of Management and Budget.

+

Executive branch federal agencies

+

Domain requests from executive branch federal agencies must be authorized by the agency's CIO or the head of the agency.

+

See OMB Memorandum M-23-10 for more information.

{% elif federal_type == 'judicial' %} -

Domain requests from the U.S. Supreme Court must be authorized by the director of information technology for the U.S. Supreme Court.

-

Domain requests from other judicial branch agencies must be authorized by the director or Chief Information Officer of the Administrative Office (AO) of the United States Courts. -

+

Judicial branch federal agencies

+

Domain requests for judicial branch federal agencies, except the U.S. Supreme Court, must be authorized by the director or CIO of the Administrative Office (AO) of the United States Courts.

+

Domain requests from the U.S. Supreme Court must be authorized by the director of information technology for the U.S. Supreme Court.

{% elif federal_type == 'legislative' %} -

U.S. Senate

-

Domain requests from the U.S. Senate must come from the Senate Sergeant at Arms.

+

Legislative branch federal agencies

-

U.S. House of Representatives

-

Domain requests from the U.S. House of Representatives must come from the House Chief Administrative Officer. +

U.S. Senate

+

Domain requests from the U.S. Senate must come from the Senate Sergeant at Arms.

-

Other legislative branch agencies

-

Domain requests from legislative branch agencies must come from the agency’s head or Chief Information Officer.

-

Domain requests from legislative commissions must come from the head of the commission, or the head or Chief Information Officer of the parent agency, if there is one. -

+

U.S. House of Representatives

+

Domain requests from the U.S. House of Representatives must come from the House Chief Administrative Officer.

+ +

Other legislative branch agencies

+

Domain requests from legislative branch agencies must come from the agency’s head or CIO.

+

Domain requests from legislative commissions must come from the head of the commission, or the head or CIO of the parent agency, if there is one.

{% endif %} {% elif organization_type == 'city' %} -

Domain requests from cities must be authorized by the mayor or the equivalent highest-elected official.

+

Cities

+

Domain requests from cities must be authorized by someone in a role of significant, executive responsibility within the city (mayor, council president, city manager, township/village supervisor, select board chairperson, chief, senior technology officer, or equivalent).

{% elif organization_type == 'county' %} -

Domain requests from counties must be authorized by the chair of the county commission or the equivalent highest-elected official.

+

Counties

+

Domain requests from counties must be authorized by the commission chair or someone in a role of significant, executive responsibility within the county (county judge, county mayor, parish/borough president, senior technology officer, or equivalent). Other county-level offices (county clerk, sheriff, county auditor, comptroller) may qualify, as well, in some instances.

{% elif organization_type == 'interstate' %} -

Domain requests from interstate organizations must be authorized by the highest-ranking executive (president, director, chair, or equivalent) or one of the state’s governors or Chief Information Officers.

+

Interstate organizations

+

Domain requests from interstate organizations must be authorized by someone in a role of significant, executive responsibility within the organization (president, director, chair, senior technology officer, or equivalent) or one of the state’s governors or CIOs.

{% elif organization_type == 'school_district' %} -

Domain requests from school district governments must be authorized by the highest-ranking executive (the chair of a school district’s board or a superintendent).

+

School districts

+

Domain requests from school district governments must be authorized by someone in a role of significant, executive responsibility within the district (board chair, superintendent, senior technology officer, or equivalent).

{% elif organization_type == 'special_district' %} -

Domain requests from special districts must be authorized by the highest-ranking executive (president, director, chair, or equivalent) or state Chief Information Officers for state-based organizations.

+

Special districts

+

Domain requests from special districts must be authorized by someone in a role of significant, executive responsibility within the district (CEO, chair, executive director, senior technology officer, or equivalent). +

{% elif organization_type == 'state_or_territory' %} -

States and territories: executive branch

-

Domain requests from states and territories must be authorized by the governor or the state Chief Information Officer.

-

States and territories: judicial and legislative branches

-

Domain requests from state legislatures and courts must be authorized by an agency’s Chief Information Officer or highest-ranking executive.

+

U.S. states and territories

+ +

States and territories: executive branch

+

Domain requests from states and territories must be authorized by the governor or someone in a role of significant, executive responsibility within the agency (department secretary, senior technology officer, or equivalent).

+ +

States and territories: judicial and legislative branches

+

Domain requests from state legislatures and courts must be authorized by an agency’s CIO or someone in a role of significant, executive responsibility within the agency.

{% elif organization_type == 'tribal' %} -

Domain requests from federally-recognized tribal governments must be authorized by the leader of the tribe, as recognized by the Bureau of Indian Affairs.

-

Domain requests from state-recognized tribal governments must be authorized by the leader of the tribe, as determined by the state’s tribal recognition initiative.

+

Tribal governments

+

Domain requests from federally-recognized tribal governments must be authorized by the tribal leader the Bureau of Indian Affairs recognizes.

+

Domain requests from state-recognized tribal governments must be authorized by the tribal leader the individual state recognizes.

{% endif %} diff --git a/src/registrar/templates/profile.html b/src/registrar/templates/profile.html index 9b8305922..6a051bfe4 100644 --- a/src/registrar/templates/profile.html +++ b/src/registrar/templates/profile.html @@ -1,7 +1,7 @@ {% extends 'base.html' %} {% block title %} -Edit your User Profile +Edit your User Profile | {% endblock title %} {% block content %} diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index ec09b45d0..7ba64ffd4 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -295,7 +295,7 @@ class AuditedAdminMockData: self, domain_type, item_name, - status=DomainApplication.STARTED, + status=DomainApplication.ApplicationStatus.STARTED, org_type="federal", federal_type="executive", purpose="Purpose of the site", @@ -312,7 +312,7 @@ class AuditedAdminMockData: title, email, and username. status (str - optional): Defines the status for DomainApplication, - e.g. DomainApplication.STARTED + e.g. DomainApplication.ApplicationStatus.STARTED org_type (str - optional): Sets a domains org_type @@ -345,23 +345,23 @@ class AuditedAdminMockData: full_arg_dict = dict( email="test_mail@mail.com", domain=self.dummy_domain(item_name, True), - status=DomainInvitation.INVITED, + status=DomainInvitation.DomainInvitationStatus.INVITED, ) return full_arg_dict - def create_full_dummy_domain_application(self, item_name, status=DomainApplication.STARTED): + def create_full_dummy_domain_application(self, item_name, status=DomainApplication.ApplicationStatus.STARTED): """Creates a dummy domain application object""" domain_application_kwargs = self.dummy_kwarg_boilerplate(self.APPLICATION, item_name, status) application = DomainApplication.objects.get_or_create(**domain_application_kwargs)[0] return application - def create_full_dummy_domain_information(self, item_name, status=DomainApplication.STARTED): + def create_full_dummy_domain_information(self, item_name, status=DomainApplication.ApplicationStatus.STARTED): """Creates a dummy domain information object""" domain_application_kwargs = self.dummy_kwarg_boilerplate(self.INFORMATION, item_name, status) application = DomainInformation.objects.get_or_create(**domain_application_kwargs)[0] return application - def create_full_dummy_domain_invitation(self, item_name, status=DomainApplication.STARTED): + def create_full_dummy_domain_invitation(self, item_name, status=DomainApplication.ApplicationStatus.STARTED): """Creates a dummy domain invitation object""" domain_application_kwargs = self.dummy_kwarg_boilerplate(self.INVITATION, item_name, status) application = DomainInvitation.objects.get_or_create(**domain_application_kwargs)[0] @@ -375,7 +375,7 @@ class AuditedAdminMockData: has_other_contacts=True, has_current_website=True, has_alternative_gov_domain=True, - status=DomainApplication.STARTED, + status=DomainApplication.ApplicationStatus.STARTED, ): """A helper to create a dummy domain application object""" application = None @@ -456,7 +456,7 @@ def completed_application( has_alternative_gov_domain=True, has_about_your_organization=True, has_anything_else=True, - status=DomainApplication.STARTED, + status=DomainApplication.ApplicationStatus.STARTED, user=False, name="city.gov", ): @@ -830,10 +830,8 @@ class MockEppLib(TestCase): def mockCheckDomainCommand(self, _request, cleaned): if "gsa.gov" in getattr(_request, "names", None): return self._mockDomainName("gsa.gov", False) - elif "GSA.gov" in getattr(_request, "names", None): - return self._mockDomainName("GSA.gov", False) elif "igorville.gov" in getattr(_request, "names", None): - return self._mockDomainName("igorvilleremixed.gov", True) + return self._mockDomainName("igorville.gov", True) elif "top-level-agency.gov" in getattr(_request, "names", None): return self._mockDomainName("top-level-agency.gov", True) elif "city.gov" in getattr(_request, "names", None): diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index 8cca67d1e..9d6add249 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -61,7 +61,7 @@ class TestDomainAdmin(MockEppLib): 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 = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW) application.approve() response = self.client.get("/admin/registrar/domain/") @@ -282,7 +282,7 @@ class TestDomainApplicationAdminForm(TestCase): form = DomainApplicationAdminForm(instance=self.application) # Verify that the form choices match the available transitions for started - expected_choices = [("started", "started"), ("submitted", "submitted")] + expected_choices = [("started", "Started"), ("submitted", "Submitted")] self.assertEqual(form.fields["status"].widget.choices, expected_choices) def test_form_choices_when_no_instance(self): @@ -355,7 +355,7 @@ class TestDomainApplicationAdmin(MockEppLib): request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk)) # Modify the application's property - application.status = DomainApplication.SUBMITTED + application.status = DomainApplication.ApplicationStatus.SUBMITTED # Use the model admin's save_model method self.admin.save_model(request, application, form=None, change=True) @@ -390,13 +390,13 @@ class TestDomainApplicationAdmin(MockEppLib): with boto3_mocking.clients.handler_for("sesv2", mock_client): # Create a sample application - application = completed_application(status=DomainApplication.SUBMITTED) + application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED) # Create a mock request request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk)) # Modify the application's property - application.status = DomainApplication.IN_REVIEW + application.status = DomainApplication.ApplicationStatus.IN_REVIEW # Use the model admin's save_model method self.admin.save_model(request, application, form=None, change=True) @@ -431,13 +431,13 @@ class TestDomainApplicationAdmin(MockEppLib): with boto3_mocking.clients.handler_for("sesv2", mock_client): # Create a sample application - application = completed_application(status=DomainApplication.IN_REVIEW) + application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW) # Create a mock request request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk)) # Modify the application's property - application.status = DomainApplication.APPROVED + application.status = DomainApplication.ApplicationStatus.APPROVED # Use the model admin's save_model method self.admin.save_model(request, application, form=None, change=True) @@ -467,13 +467,13 @@ class TestDomainApplicationAdmin(MockEppLib): User.objects.filter(email=EMAIL).delete() # Create a sample application - application = completed_application(status=DomainApplication.IN_REVIEW) + application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW) # Create a mock request request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk)) # Modify the application's property - application.status = DomainApplication.APPROVED + application.status = DomainApplication.ApplicationStatus.APPROVED # Use the model admin's save_model method self.admin.save_model(request, application, form=None, change=True) @@ -492,13 +492,13 @@ class TestDomainApplicationAdmin(MockEppLib): with boto3_mocking.clients.handler_for("sesv2", mock_client): # Create a sample application - application = completed_application(status=DomainApplication.IN_REVIEW) + application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW) # Create a mock request request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk)) # Modify the application's property - application.status = DomainApplication.ACTION_NEEDED + application.status = DomainApplication.ApplicationStatus.ACTION_NEEDED # Use the model admin's save_model method self.admin.save_model(request, application, form=None, change=True) @@ -533,13 +533,13 @@ class TestDomainApplicationAdmin(MockEppLib): with boto3_mocking.clients.handler_for("sesv2", mock_client): # Create a sample application - application = completed_application(status=DomainApplication.IN_REVIEW) + application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW) # Create a mock request request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk)) # Modify the application's property - application.status = DomainApplication.REJECTED + application.status = DomainApplication.ApplicationStatus.REJECTED # Use the model admin's save_model method self.admin.save_model(request, application, form=None, change=True) @@ -569,13 +569,13 @@ class TestDomainApplicationAdmin(MockEppLib): User.objects.filter(email=EMAIL).delete() # Create a sample application - application = completed_application(status=DomainApplication.IN_REVIEW) + application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW) # Create a mock request request = self.factory.post("/admin/registrar/domainapplication/{}/change/".format(application.pk)) # Modify the application's property - application.status = DomainApplication.INELIGIBLE + application.status = DomainApplication.ApplicationStatus.INELIGIBLE # Use the model admin's save_model method self.admin.save_model(request, application, form=None, change=True) @@ -584,7 +584,7 @@ class TestDomainApplicationAdmin(MockEppLib): self.assertEqual(application.creator.status, "restricted") def test_readonly_when_restricted_creator(self): - application = completed_application(status=DomainApplication.IN_REVIEW) + application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW) application.creator.status = User.RESTRICTED application.creator.save() @@ -662,7 +662,7 @@ class TestDomainApplicationAdmin(MockEppLib): def test_saving_when_restricted_creator(self): # Create an instance of the model - application = completed_application(status=DomainApplication.IN_REVIEW) + application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW) application.creator.status = User.RESTRICTED application.creator.save() @@ -681,11 +681,11 @@ class TestDomainApplicationAdmin(MockEppLib): ) # Assert that the status has not changed - self.assertEqual(application.status, DomainApplication.IN_REVIEW) + self.assertEqual(application.status, DomainApplication.ApplicationStatus.IN_REVIEW) def test_change_view_with_restricted_creator(self): # Create an instance of the model - application = completed_application(status=DomainApplication.IN_REVIEW) + application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW) application.creator.status = User.RESTRICTED application.creator.save() @@ -704,7 +704,7 @@ class TestDomainApplicationAdmin(MockEppLib): def test_error_when_saving_approved_to_rejected_and_domain_is_active(self): # Create an instance of the model - application = completed_application(status=DomainApplication.APPROVED) + application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED) domain = Domain.objects.create(name=application.requested_domain.name) application.approved_domain = domain application.save() @@ -724,7 +724,7 @@ class TestDomainApplicationAdmin(MockEppLib): stack.enter_context(patch.object(messages, "error")) # Simulate saving the model - application.status = DomainApplication.REJECTED + application.status = DomainApplication.ApplicationStatus.REJECTED self.admin.save_model(request, application, None, True) # Assert that the error message was called with the correct argument @@ -735,7 +735,7 @@ class TestDomainApplicationAdmin(MockEppLib): def test_side_effects_when_saving_approved_to_rejected(self): # Create an instance of the model - application = completed_application(status=DomainApplication.APPROVED) + application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED) domain = Domain.objects.create(name=application.requested_domain.name) domain_information = DomainInformation.objects.create(creator=self.superuser, domain=domain) application.approved_domain = domain @@ -756,7 +756,7 @@ class TestDomainApplicationAdmin(MockEppLib): stack.enter_context(patch.object(messages, "error")) # Simulate saving the model - application.status = DomainApplication.REJECTED + application.status = DomainApplication.ApplicationStatus.REJECTED self.admin.save_model(request, application, None, True) # Assert that the error message was never called @@ -774,7 +774,7 @@ class TestDomainApplicationAdmin(MockEppLib): def test_error_when_saving_approved_to_ineligible_and_domain_is_active(self): # Create an instance of the model - application = completed_application(status=DomainApplication.APPROVED) + application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED) domain = Domain.objects.create(name=application.requested_domain.name) application.approved_domain = domain application.save() @@ -794,7 +794,7 @@ class TestDomainApplicationAdmin(MockEppLib): stack.enter_context(patch.object(messages, "error")) # Simulate saving the model - application.status = DomainApplication.INELIGIBLE + application.status = DomainApplication.ApplicationStatus.INELIGIBLE self.admin.save_model(request, application, None, True) # Assert that the error message was called with the correct argument @@ -805,7 +805,7 @@ class TestDomainApplicationAdmin(MockEppLib): def test_side_effects_when_saving_approved_to_ineligible(self): # Create an instance of the model - application = completed_application(status=DomainApplication.APPROVED) + application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED) domain = Domain.objects.create(name=application.requested_domain.name) domain_information = DomainInformation.objects.create(creator=self.superuser, domain=domain) application.approved_domain = domain @@ -826,7 +826,7 @@ class TestDomainApplicationAdmin(MockEppLib): stack.enter_context(patch.object(messages, "error")) # Simulate saving the model - application.status = DomainApplication.INELIGIBLE + application.status = DomainApplication.ApplicationStatus.INELIGIBLE self.admin.save_model(request, application, None, True) # Assert that the error message was never called @@ -877,12 +877,14 @@ class DomainInvitationAdminTest(TestCase): ) # Assert that the filters are added - self.assertContains(response, "invited", count=4) - self.assertContains(response, "retrieved", count=4) + self.assertContains(response, "invited", count=2) + self.assertContains(response, "Invited", count=2) + self.assertContains(response, "retrieved", count=2) + self.assertContains(response, "Retrieved", count=2) # Check for the HTML context specificially - invited_html = 'invited' - retrieved_html = 'retrieved' + invited_html = 'Invited' + retrieved_html = 'Retrieved' self.assertContains(response, invited_html, count=1) self.assertContains(response, retrieved_html, count=1) diff --git a/src/registrar/tests/test_forms.py b/src/registrar/tests/test_forms.py index 035e1c8c5..00bb7ce61 100644 --- a/src/registrar/tests/test_forms.py +++ b/src/registrar/tests/test_forms.py @@ -30,7 +30,7 @@ class TestFormValidation(MockEppLib): form = OrganizationContactForm(data={"zipcode": "nah"}) self.assertEqual( form.errors["zipcode"], - ["Enter a zip code in the form of 12345 or 12345-6789."], + ["Enter a zip code in the required format, like 12345 or 12345-6789."], ) def test_org_contact_zip_valid(self): diff --git a/src/registrar/tests/test_models.py b/src/registrar/tests/test_models.py index d9090c287..3854528b4 100644 --- a/src/registrar/tests/test_models.py +++ b/src/registrar/tests/test_models.py @@ -35,7 +35,7 @@ class TestDomainApplication(TestCase): """Can create with just a creator.""" user, _ = User.objects.get_or_create() application = DomainApplication.objects.create(creator=user) - self.assertEqual(application.status, DomainApplication.STARTED) + self.assertEqual(application.status, DomainApplication.ApplicationStatus.STARTED) def test_full_create(self): """Can create with all fields.""" @@ -108,7 +108,7 @@ class TestDomainApplication(TestCase): # no submitter email so this emits a log warning with less_console_noise(): application.submit() - self.assertEqual(application.status, application.SUBMITTED) + self.assertEqual(application.status, application.ApplicationStatus.SUBMITTED) def test_submit_sends_email(self): """Create an application and submit it and see if email was sent.""" @@ -139,7 +139,7 @@ class TestDomainApplication(TestCase): """Create an application with status submitted and call submit against transition rules""" - application = completed_application(status=DomainApplication.SUBMITTED) + application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED) with self.assertRaises(TransitionNotAllowed): application.submit() @@ -148,7 +148,7 @@ class TestDomainApplication(TestCase): """Create an application with status in review and call submit against transition rules""" - application = completed_application(status=DomainApplication.IN_REVIEW) + application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW) with self.assertRaises(TransitionNotAllowed): application.submit() @@ -157,7 +157,7 @@ class TestDomainApplication(TestCase): """Create an application with status approved and call submit against transition rules""" - application = completed_application(status=DomainApplication.APPROVED) + application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED) with self.assertRaises(TransitionNotAllowed): application.submit() @@ -166,7 +166,7 @@ class TestDomainApplication(TestCase): """Create an application with status rejected and call submit against transition rules""" - application = completed_application(status=DomainApplication.REJECTED) + application = completed_application(status=DomainApplication.ApplicationStatus.REJECTED) with self.assertRaises(TransitionNotAllowed): application.submit() @@ -175,7 +175,7 @@ class TestDomainApplication(TestCase): """Create an application with status ineligible and call submit against transition rules""" - application = completed_application(status=DomainApplication.INELIGIBLE) + application = completed_application(status=DomainApplication.ApplicationStatus.INELIGIBLE) with self.assertRaises(TransitionNotAllowed): application.submit() @@ -184,7 +184,7 @@ class TestDomainApplication(TestCase): """Create an application with status started and call in_review against transition rules""" - application = completed_application(status=DomainApplication.STARTED) + application = completed_application(status=DomainApplication.ApplicationStatus.STARTED) with self.assertRaises(TransitionNotAllowed): application.in_review() @@ -193,7 +193,7 @@ class TestDomainApplication(TestCase): """Create an application with status in review and call in_review against transition rules""" - application = completed_application(status=DomainApplication.IN_REVIEW) + application = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW) with self.assertRaises(TransitionNotAllowed): application.in_review() @@ -202,7 +202,7 @@ class TestDomainApplication(TestCase): """Create an application with status approved and call in_review against transition rules""" - application = completed_application(status=DomainApplication.APPROVED) + application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED) with self.assertRaises(TransitionNotAllowed): application.in_review() @@ -211,7 +211,7 @@ class TestDomainApplication(TestCase): """Create an application with status action needed and call in_review against transition rules""" - application = completed_application(status=DomainApplication.ACTION_NEEDED) + application = completed_application(status=DomainApplication.ApplicationStatus.ACTION_NEEDED) with self.assertRaises(TransitionNotAllowed): application.in_review() @@ -220,7 +220,7 @@ class TestDomainApplication(TestCase): """Create an application with status rejected and call in_review against transition rules""" - application = completed_application(status=DomainApplication.REJECTED) + application = completed_application(status=DomainApplication.ApplicationStatus.REJECTED) with self.assertRaises(TransitionNotAllowed): application.in_review() @@ -229,7 +229,7 @@ class TestDomainApplication(TestCase): """Create an application with status withdrawn and call in_review against transition rules""" - application = completed_application(status=DomainApplication.WITHDRAWN) + application = completed_application(status=DomainApplication.ApplicationStatus.WITHDRAWN) with self.assertRaises(TransitionNotAllowed): application.in_review() @@ -238,7 +238,7 @@ class TestDomainApplication(TestCase): """Create an application with status ineligible and call in_review against transition rules""" - application = completed_application(status=DomainApplication.INELIGIBLE) + application = completed_application(status=DomainApplication.ApplicationStatus.INELIGIBLE) with self.assertRaises(TransitionNotAllowed): application.in_review() @@ -247,7 +247,7 @@ class TestDomainApplication(TestCase): """Create an application with status started and call action_needed against transition rules""" - application = completed_application(status=DomainApplication.STARTED) + application = completed_application(status=DomainApplication.ApplicationStatus.STARTED) with self.assertRaises(TransitionNotAllowed): application.action_needed() @@ -256,7 +256,7 @@ class TestDomainApplication(TestCase): """Create an application with status submitted and call action_needed against transition rules""" - application = completed_application(status=DomainApplication.SUBMITTED) + application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED) with self.assertRaises(TransitionNotAllowed): application.action_needed() @@ -265,7 +265,7 @@ class TestDomainApplication(TestCase): """Create an application with status action needed and call action_needed against transition rules""" - application = completed_application(status=DomainApplication.ACTION_NEEDED) + application = completed_application(status=DomainApplication.ApplicationStatus.ACTION_NEEDED) with self.assertRaises(TransitionNotAllowed): application.action_needed() @@ -274,7 +274,7 @@ class TestDomainApplication(TestCase): """Create an application with status approved and call action_needed against transition rules""" - application = completed_application(status=DomainApplication.APPROVED) + application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED) with self.assertRaises(TransitionNotAllowed): application.action_needed() @@ -283,7 +283,7 @@ class TestDomainApplication(TestCase): """Create an application with status withdrawn and call action_needed against transition rules""" - application = completed_application(status=DomainApplication.WITHDRAWN) + application = completed_application(status=DomainApplication.ApplicationStatus.WITHDRAWN) with self.assertRaises(TransitionNotAllowed): application.action_needed() @@ -292,7 +292,7 @@ class TestDomainApplication(TestCase): """Create an application with status ineligible and call action_needed against transition rules""" - application = completed_application(status=DomainApplication.INELIGIBLE) + application = completed_application(status=DomainApplication.ApplicationStatus.INELIGIBLE) with self.assertRaises(TransitionNotAllowed): application.action_needed() @@ -301,7 +301,7 @@ class TestDomainApplication(TestCase): """Create an application with status started and call approve against transition rules""" - application = completed_application(status=DomainApplication.STARTED) + application = completed_application(status=DomainApplication.ApplicationStatus.STARTED) with self.assertRaises(TransitionNotAllowed): application.approve() @@ -310,7 +310,7 @@ class TestDomainApplication(TestCase): """Create an application with status approved and call approve against transition rules""" - application = completed_application(status=DomainApplication.APPROVED) + application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED) with self.assertRaises(TransitionNotAllowed): application.approve() @@ -319,7 +319,7 @@ class TestDomainApplication(TestCase): """Create an application with status action needed and call approve against transition rules""" - application = completed_application(status=DomainApplication.ACTION_NEEDED) + application = completed_application(status=DomainApplication.ApplicationStatus.ACTION_NEEDED) with self.assertRaises(TransitionNotAllowed): application.approve() @@ -328,7 +328,7 @@ class TestDomainApplication(TestCase): """Create an application with status withdrawn and call approve against transition rules""" - application = completed_application(status=DomainApplication.WITHDRAWN) + application = completed_application(status=DomainApplication.ApplicationStatus.WITHDRAWN) with self.assertRaises(TransitionNotAllowed): application.approve() @@ -337,7 +337,7 @@ class TestDomainApplication(TestCase): """Create an application with status started and call withdraw against transition rules""" - application = completed_application(status=DomainApplication.STARTED) + application = completed_application(status=DomainApplication.ApplicationStatus.STARTED) with self.assertRaises(TransitionNotAllowed): application.withdraw() @@ -346,7 +346,7 @@ class TestDomainApplication(TestCase): """Create an application with status approved and call withdraw against transition rules""" - application = completed_application(status=DomainApplication.APPROVED) + application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED) with self.assertRaises(TransitionNotAllowed): application.withdraw() @@ -355,7 +355,7 @@ class TestDomainApplication(TestCase): """Create an application with status action needed and call withdraw against transition rules""" - application = completed_application(status=DomainApplication.ACTION_NEEDED) + application = completed_application(status=DomainApplication.ApplicationStatus.ACTION_NEEDED) with self.assertRaises(TransitionNotAllowed): application.withdraw() @@ -364,7 +364,7 @@ class TestDomainApplication(TestCase): """Create an application with status rejected and call withdraw against transition rules""" - application = completed_application(status=DomainApplication.REJECTED) + application = completed_application(status=DomainApplication.ApplicationStatus.REJECTED) with self.assertRaises(TransitionNotAllowed): application.withdraw() @@ -373,7 +373,7 @@ class TestDomainApplication(TestCase): """Create an application with status withdrawn and call withdraw against transition rules""" - application = completed_application(status=DomainApplication.WITHDRAWN) + application = completed_application(status=DomainApplication.ApplicationStatus.WITHDRAWN) with self.assertRaises(TransitionNotAllowed): application.withdraw() @@ -382,7 +382,7 @@ class TestDomainApplication(TestCase): """Create an application with status ineligible and call withdraw against transition rules""" - application = completed_application(status=DomainApplication.INELIGIBLE) + application = completed_application(status=DomainApplication.ApplicationStatus.INELIGIBLE) with self.assertRaises(TransitionNotAllowed): application.withdraw() @@ -391,7 +391,7 @@ class TestDomainApplication(TestCase): """Create an application with status started and call reject against transition rules""" - application = completed_application(status=DomainApplication.STARTED) + application = completed_application(status=DomainApplication.ApplicationStatus.STARTED) with self.assertRaises(TransitionNotAllowed): application.reject() @@ -400,7 +400,7 @@ class TestDomainApplication(TestCase): """Create an application with status submitted and call reject against transition rules""" - application = completed_application(status=DomainApplication.SUBMITTED) + application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED) with self.assertRaises(TransitionNotAllowed): application.reject() @@ -409,7 +409,7 @@ class TestDomainApplication(TestCase): """Create an application with status action needed and call reject against transition rules""" - application = completed_application(status=DomainApplication.ACTION_NEEDED) + application = completed_application(status=DomainApplication.ApplicationStatus.ACTION_NEEDED) with self.assertRaises(TransitionNotAllowed): application.reject() @@ -418,7 +418,7 @@ class TestDomainApplication(TestCase): """Create an application with status withdrawn and call reject against transition rules""" - application = completed_application(status=DomainApplication.WITHDRAWN) + application = completed_application(status=DomainApplication.ApplicationStatus.WITHDRAWN) with self.assertRaises(TransitionNotAllowed): application.reject() @@ -427,7 +427,7 @@ class TestDomainApplication(TestCase): """Create an application with status rejected and call reject against transition rules""" - application = completed_application(status=DomainApplication.REJECTED) + application = completed_application(status=DomainApplication.ApplicationStatus.REJECTED) with self.assertRaises(TransitionNotAllowed): application.reject() @@ -436,7 +436,7 @@ class TestDomainApplication(TestCase): """Create an application with status ineligible and call reject against transition rules""" - application = completed_application(status=DomainApplication.INELIGIBLE) + application = completed_application(status=DomainApplication.ApplicationStatus.INELIGIBLE) with self.assertRaises(TransitionNotAllowed): application.reject() @@ -445,7 +445,7 @@ class TestDomainApplication(TestCase): """Create an application with status approved, create a matching domain that is active, and call reject against transition rules""" - application = completed_application(status=DomainApplication.APPROVED) + application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED) domain = Domain.objects.create(name=application.requested_domain.name) application.approved_domain = domain application.save() @@ -464,7 +464,7 @@ class TestDomainApplication(TestCase): """Create an application with status started and call reject against transition rules""" - application = completed_application(status=DomainApplication.STARTED) + application = completed_application(status=DomainApplication.ApplicationStatus.STARTED) with self.assertRaises(TransitionNotAllowed): application.reject_with_prejudice() @@ -473,7 +473,7 @@ class TestDomainApplication(TestCase): """Create an application with status submitted and call reject against transition rules""" - application = completed_application(status=DomainApplication.SUBMITTED) + application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED) with self.assertRaises(TransitionNotAllowed): application.reject_with_prejudice() @@ -482,7 +482,7 @@ class TestDomainApplication(TestCase): """Create an application with status action needed and call reject against transition rules""" - application = completed_application(status=DomainApplication.ACTION_NEEDED) + application = completed_application(status=DomainApplication.ApplicationStatus.ACTION_NEEDED) with self.assertRaises(TransitionNotAllowed): application.reject_with_prejudice() @@ -491,7 +491,7 @@ class TestDomainApplication(TestCase): """Create an application with status withdrawn and call reject against transition rules""" - application = completed_application(status=DomainApplication.WITHDRAWN) + application = completed_application(status=DomainApplication.ApplicationStatus.WITHDRAWN) with self.assertRaises(TransitionNotAllowed): application.reject_with_prejudice() @@ -500,7 +500,7 @@ class TestDomainApplication(TestCase): """Create an application with status rejected and call reject against transition rules""" - application = completed_application(status=DomainApplication.REJECTED) + application = completed_application(status=DomainApplication.ApplicationStatus.REJECTED) with self.assertRaises(TransitionNotAllowed): application.reject_with_prejudice() @@ -509,7 +509,7 @@ class TestDomainApplication(TestCase): """Create an application with status ineligible and call reject against transition rules""" - application = completed_application(status=DomainApplication.INELIGIBLE) + application = completed_application(status=DomainApplication.ApplicationStatus.INELIGIBLE) with self.assertRaises(TransitionNotAllowed): application.reject_with_prejudice() @@ -518,7 +518,7 @@ class TestDomainApplication(TestCase): """Create an application with status approved, create a matching domain that is active, and call reject_with_prejudice against transition rules""" - application = completed_application(status=DomainApplication.APPROVED) + application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED) domain = Domain.objects.create(name=application.requested_domain.name) application.approved_domain = domain application.save() @@ -543,7 +543,7 @@ class TestPermissions(TestCase): user, _ = User.objects.get_or_create() application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain) # skip using the submit method - application.status = DomainApplication.SUBMITTED + application.status = DomainApplication.ApplicationStatus.SUBMITTED application.approve() # should be a role for this user @@ -560,7 +560,7 @@ class TestDomainInfo(TestCase): user, _ = User.objects.get_or_create() application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain) # skip using the submit method - application.status = DomainApplication.SUBMITTED + application.status = DomainApplication.ApplicationStatus.SUBMITTED application.approve() # should be an information present for this domain @@ -597,7 +597,7 @@ class TestInvitations(TestCase): # this is not an error but does produce a console warning with less_console_noise(): self.invitation.retrieve() - self.assertEqual(self.invitation.status, DomainInvitation.RETRIEVED) + self.assertEqual(self.invitation.status, DomainInvitation.DomainInvitationStatus.RETRIEVED) def test_retrieve_on_each_login(self): """A user's authenticate on_each_login callback retrieves their invitations.""" @@ -699,3 +699,17 @@ class TestContact(TestCase): # Updating the contact's email does not propagate self.assertEqual(self.contact.email, "joey.baloney@diaperville.com") self.assertEqual(self.user.email, "mayor@igorville.gov") + + def test_check_domain_invitations_on_login_caps_email(self): + """A DomainInvitation with an email address with capital letters should match + a User record whose email address is not in caps""" + # create DomainInvitation with CAPS email that matches User email + # on a case-insensitive match + caps_email = "MAYOR@igorville.gov" + # mock the domain invitation save routine + with patch("registrar.models.DomainInvitation.save") as save_mock: + DomainInvitation.objects.get_or_create(email=caps_email, domain=self.domain) + self.user.check_domain_invitations_on_login() + # if check_domain_invitations_on_login properly matches exactly one + # Domain Invitation, then save routine should be called exactly once + save_mock.assert_called_once() diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index 4a2023243..39f63c942 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -261,7 +261,7 @@ class TestDomainCreation(MockEppLib): user, _ = User.objects.get_or_create() application = DomainApplication.objects.create(creator=user, requested_domain=draft_domain) # skip using the submit method - application.status = DomainApplication.SUBMITTED + application.status = DomainApplication.ApplicationStatus.SUBMITTED # transition to approve state application.approve() # should have information present for this domain @@ -1506,7 +1506,7 @@ class TestRegistrantNameservers(MockEppLib): ] def test_setting_not_allowed(self): - """Scenario: A domain state is not Ready or DNS Needed + """Scenario: A domain state is not Ready or DNS needed then setting nameservers is not allowed""" domain, _ = Domain.objects.get_or_create(name="onholdDomain.gov", state=Domain.State.ON_HOLD) with self.assertRaises(ActionNotAllowed): diff --git a/src/registrar/tests/test_reports.py b/src/registrar/tests/test_reports.py index b94316248..112b2ba34 100644 --- a/src/registrar/tests/test_reports.py +++ b/src/registrar/tests/test_reports.py @@ -258,7 +258,6 @@ class ExportDataTest(TestCase): ) def tearDown(self): - # Dummy push - will remove Domain.objects.all().delete() DomainInformation.objects.all().delete() User.objects.all().delete() diff --git a/src/registrar/tests/test_transition_domain_migrations.py b/src/registrar/tests/test_transition_domain_migrations.py index 4e549bdd6..cfee68fea 100644 --- a/src/registrar/tests/test_transition_domain_migrations.py +++ b/src/registrar/tests/test_transition_domain_migrations.py @@ -21,6 +21,155 @@ from registrar.models.contact import Contact from .common import less_console_noise +class TestProcessedMigrations(TestCase): + """This test case class is designed to verify the idempotency of migrations + related to domain transitions in the application.""" + + def setUp(self): + """Defines the file name of migration_json and the folder its contained in""" + self.test_data_file_location = "registrar/tests/data" + self.migration_json_filename = "test_migrationFilepaths.json" + self.user, _ = User.objects.get_or_create(username="igorvillian") + + def tearDown(self): + """Deletes all DB objects related to migrations""" + # Delete domain information + Domain.objects.all().delete() + DomainInformation.objects.all().delete() + DomainInvitation.objects.all().delete() + TransitionDomain.objects.all().delete() + + # Delete users + User.objects.all().delete() + UserDomainRole.objects.all().delete() + + def run_load_domains(self): + """ + This method executes the load_transition_domain command. + + It uses 'unittest.mock.patch' to mock the TerminalHelper.query_yes_no_exit method, + which is a user prompt in the terminal. The mock function always returns True, + allowing the test to proceed without manual user input. + + The 'call_command' function from Django's management framework is then used to + execute the load_transition_domain command with the specified arguments. + """ + # noqa here because splitting this up makes it confusing. + # ES501 + with patch( + "registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit", # noqa + return_value=True, + ): + call_command( + "load_transition_domain", + self.migration_json_filename, + directory=self.test_data_file_location, + ) + + def run_transfer_domains(self): + """ + This method executes the transfer_transition_domains_to_domains command. + + The 'call_command' function from Django's management framework is then used to + execute the load_transition_domain command with the specified arguments. + """ + call_command("transfer_transition_domains_to_domains") + + def test_domain_idempotent(self): + """ + This test ensures that the domain transfer process + is idempotent on Domain and DomainInformation. + """ + unchanged_domain, _ = Domain.objects.get_or_create( + name="testdomain.gov", + state=Domain.State.READY, + expiration_date=datetime.date(2000, 1, 1), + ) + unchanged_domain_information, _ = DomainInformation.objects.get_or_create( + domain=unchanged_domain, organization_name="test org name", creator=self.user + ) + self.run_load_domains() + + # Test that a given TransitionDomain isn't set to "processed" + transition_domain_object = TransitionDomain.objects.get(domain_name="fakewebsite3.gov") + self.assertFalse(transition_domain_object.processed) + + self.run_transfer_domains() + + # Test that old data isn't corrupted + actual_unchanged = Domain.objects.filter(name="testdomain.gov").get() + actual_unchanged_information = DomainInformation.objects.filter(domain=actual_unchanged).get() + self.assertEqual(unchanged_domain, actual_unchanged) + self.assertEqual(unchanged_domain_information, actual_unchanged_information) + + # Test that a given TransitionDomain is set to "processed" after we transfer domains + transition_domain_object = TransitionDomain.objects.get(domain_name="fakewebsite3.gov") + self.assertTrue(transition_domain_object.processed) + + # Manually change Domain/DomainInformation objects + changed_domain = Domain.objects.filter(name="fakewebsite3.gov").get() + changed_domain.expiration_date = datetime.date(1999, 1, 1) + + changed_domain.save() + + changed_domain_information = DomainInformation.objects.filter(domain=changed_domain).get() + changed_domain_information.organization_name = "changed" + + changed_domain_information.save() + + # Rerun transfer domains + self.run_transfer_domains() + + # Test that old data isn't corrupted after running this twice + actual_unchanged = Domain.objects.filter(name="testdomain.gov").get() + actual_unchanged_information = DomainInformation.objects.filter(domain=actual_unchanged).get() + self.assertEqual(unchanged_domain, actual_unchanged) + self.assertEqual(unchanged_domain_information, actual_unchanged_information) + + # Ensure that domain hasn't changed + actual_domain = Domain.objects.filter(name="fakewebsite3.gov").get() + self.assertEqual(changed_domain, actual_domain) + + # Ensure that DomainInformation hasn't changed + actual_domain_information = DomainInformation.objects.filter(domain=changed_domain).get() + self.assertEqual(changed_domain_information, actual_domain_information) + + def test_transition_domain_is_processed(self): + """ + This test checks if a domain is correctly marked as processed in the transition. + """ + old_transition_domain, _ = TransitionDomain.objects.get_or_create(domain_name="testdomain.gov") + # Asser that old records default to 'True' + self.assertTrue(old_transition_domain.processed) + + unchanged_domain, _ = Domain.objects.get_or_create( + name="testdomain.gov", + state=Domain.State.READY, + expiration_date=datetime.date(2000, 1, 1), + ) + unchanged_domain_information, _ = DomainInformation.objects.get_or_create( + domain=unchanged_domain, organization_name="test org name", creator=self.user + ) + self.run_load_domains() + + # Test that a given TransitionDomain isn't set to "processed" + transition_domain_object = TransitionDomain.objects.get(domain_name="fakewebsite3.gov") + self.assertFalse(transition_domain_object.processed) + + self.run_transfer_domains() + + # Test that old data isn't corrupted + actual_unchanged = Domain.objects.filter(name="testdomain.gov").get() + actual_unchanged_information = DomainInformation.objects.filter(domain=actual_unchanged).get() + self.assertEqual(unchanged_domain, actual_unchanged) + self.assertTrue(old_transition_domain.processed) + self.assertEqual(unchanged_domain_information, actual_unchanged_information) + + # Test that a given TransitionDomain is set to "processed" after we transfer domains + transition_domain_object = TransitionDomain.objects.get(domain_name="fakewebsite3.gov") + self.assertTrue(transition_domain_object.processed) + + class TestOrganizationMigration(TestCase): def setUp(self): """Defines the file name of migration_json and the folder its contained in""" diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index a71f85697..17641636e 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -100,7 +100,7 @@ class LoggedInTests(TestWithUser): response = self.client.get("/") # count = 2 because it is also in screenreader content self.assertContains(response, "igorville.gov", count=2) - self.assertContains(response, "DNS Needed") + self.assertContains(response, "DNS needed") # clean up role.delete() @@ -804,7 +804,7 @@ class DomainApplicationTests(TestWithUser, WebTest): # ---- AO CONTACT PAGE ---- self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) ao_page = org_contact_result.follow() - self.assertContains(ao_page, "Domain requests from executive branch agencies") + self.assertContains(ao_page, "Executive branch federal agencies") # Go back to organization type page and change type self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) @@ -1079,7 +1079,7 @@ class DomainApplicationTests(TestWithUser, WebTest): Make sure the long name is displaying in the application summary page (manage your application) """ - completed_application(status=DomainApplication.SUBMITTED, user=self.user) + completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED, user=self.user) home_page = self.app.get("/") self.assertContains(home_page, "city.gov") # click the "Edit" link @@ -1355,29 +1355,55 @@ class TestDomainManagers(TestDomainOverview): out the boto3 SES email sending here. """ # make sure there is no user with this email - EMAIL = "mayor@igorville.gov" - User.objects.filter(email=EMAIL).delete() + email_address = "mayor@igorville.gov" + User.objects.filter(email=email_address).delete() self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain) add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - add_page.form["email"] = EMAIL + add_page.form["email"] = email_address self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) success_result = add_page.form.submit() self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) success_page = success_result.follow() - self.assertContains(success_page, EMAIL) + self.assertContains(success_page, email_address) self.assertContains(success_page, "Cancel") # link to cancel invitation - self.assertTrue(DomainInvitation.objects.filter(email=EMAIL).exists()) + self.assertTrue(DomainInvitation.objects.filter(email=email_address).exists()) + + @boto3_mocking.patching + def test_domain_invitation_created_for_caps_email(self): + """Add user on a nonexistent email with CAPS creates an invitation to lowercase email. + + Adding a non-existent user sends an email as a side-effect, so mock + out the boto3 SES email sending here. + """ + # make sure there is no user with this email + email_address = "mayor@igorville.gov" + caps_email_address = "MAYOR@igorville.gov" + User.objects.filter(email=email_address).delete() + + self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain) + + add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) + session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] + add_page.form["email"] = caps_email_address + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + success_result = add_page.form.submit() + self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) + success_page = success_result.follow() + + self.assertContains(success_page, email_address) + self.assertContains(success_page, "Cancel") # link to cancel invitation + self.assertTrue(DomainInvitation.objects.filter(email=email_address).exists()) @boto3_mocking.patching def test_domain_invitation_email_sent(self): """Inviting a non-existent user sends them an email.""" # make sure there is no user with this email - EMAIL = "mayor@igorville.gov" - User.objects.filter(email=EMAIL).delete() + email_address = "mayor@igorville.gov" + User.objects.filter(email=email_address).delete() self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain) @@ -1386,28 +1412,28 @@ class TestDomainManagers(TestDomainOverview): with boto3_mocking.clients.handler_for("sesv2", mock_client): add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - add_page.form["email"] = EMAIL + add_page.form["email"] = email_address self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) add_page.form.submit() # check the mock instance to see if `send_email` was called right mock_client_instance.send_email.assert_called_once_with( FromEmailAddress=settings.DEFAULT_FROM_EMAIL, - Destination={"ToAddresses": [EMAIL]}, + Destination={"ToAddresses": [email_address]}, Content=ANY, ) def test_domain_invitation_cancel(self): """Posting to the delete view deletes an invitation.""" - EMAIL = "mayor@igorville.gov" - invitation, _ = DomainInvitation.objects.get_or_create(domain=self.domain, email=EMAIL) + email_address = "mayor@igorville.gov" + invitation, _ = DomainInvitation.objects.get_or_create(domain=self.domain, email=email_address) self.client.post(reverse("invitation-delete", kwargs={"pk": invitation.id})) with self.assertRaises(DomainInvitation.DoesNotExist): DomainInvitation.objects.get(id=invitation.id) def test_domain_invitation_cancel_no_permissions(self): """Posting to the delete view as a different user should fail.""" - EMAIL = "mayor@igorville.gov" - invitation, _ = DomainInvitation.objects.get_or_create(domain=self.domain, email=EMAIL) + email_address = "mayor@igorville.gov" + invitation, _ = DomainInvitation.objects.get_or_create(domain=self.domain, email=email_address) other_user = User() other_user.save() @@ -1419,20 +1445,20 @@ class TestDomainManagers(TestDomainOverview): @boto3_mocking.patching def test_domain_invitation_flow(self): """Send an invitation to a new user, log in and load the dashboard.""" - EMAIL = "mayor@igorville.gov" - User.objects.filter(email=EMAIL).delete() + email_address = "mayor@igorville.gov" + User.objects.filter(email=email_address).delete() add_page = self.app.get(reverse("domain-users-add", kwargs={"pk": self.domain.id})) self.domain_information, _ = DomainInformation.objects.get_or_create(creator=self.user, domain=self.domain) session_id = self.app.cookies[settings.SESSION_COOKIE_NAME] - add_page.form["email"] = EMAIL + add_page.form["email"] = email_address self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) add_page.form.submit() # user was invited, create them - new_user = User.objects.create(username=EMAIL, email=EMAIL) + new_user = User.objects.create(username=email_address, email=email_address) # log them in to `self.app` self.app.set_user(new_user.username) # and manually call the on each login callback @@ -1941,19 +1967,19 @@ class TestDomainDNSSEC(TestDomainOverview): self.assertContains(updated_page, "Enable DNSSEC") def test_ds_form_loads_with_no_domain_data(self): - """DNSSEC Add DS Data page loads when there is no + """DNSSEC Add DS data page loads when there is no domain DNSSEC data and shows a button to Add new record""" page = self.client.get(reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dnssec_none.id})) - self.assertContains(page, "You have no DS Data added") + self.assertContains(page, "You have no DS data added") self.assertContains(page, "Add new record") def test_ds_form_loads_with_ds_data(self): - """DNSSEC Add DS Data page loads when there is + """DNSSEC Add DS data page loads when there is domain DNSSEC DS data and shows the data""" page = self.client.get(reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.domain_dsdata.id})) - self.assertContains(page, "DS Data record 1") + self.assertContains(page, "DS data record 1") def test_ds_data_form_modal(self): """When user clicks on save, a modal pops up.""" @@ -1974,7 +2000,7 @@ class TestDomainDNSSEC(TestDomainOverview): self.assertContains(response, "Trigger Disable DNSSEC Modal") def test_ds_data_form_submits(self): - """DS Data form submits successfully + """DS data form submits successfully Uses self.app WebTest because we need to interact with forms. """ @@ -1991,10 +2017,10 @@ class TestDomainDNSSEC(TestDomainOverview): ) self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id) page = result.follow() - self.assertContains(page, "The DS Data records for this domain have been updated.") + self.assertContains(page, "The DS data records for this domain have been updated.") def test_ds_data_form_invalid(self): - """DS Data form errors with invalid data (missing required fields) + """DS data form errors with invalid data (missing required fields) Uses self.app WebTest because we need to interact with forms. """ @@ -2017,7 +2043,7 @@ class TestDomainDNSSEC(TestDomainOverview): self.assertContains(result, "Digest is required", count=2, status_code=200) def test_ds_data_form_invalid_keytag(self): - """DS Data form errors with invalid data (key tag too large) + """DS data form errors with invalid data (key tag too large) Uses self.app WebTest because we need to interact with forms. """ @@ -2040,7 +2066,7 @@ class TestDomainDNSSEC(TestDomainOverview): ) def test_ds_data_form_invalid_digest_chars(self): - """DS Data form errors with invalid data (digest contains non hexadecimal chars) + """DS data form errors with invalid data (digest contains non hexadecimal chars) Uses self.app WebTest because we need to interact with forms. """ @@ -2063,7 +2089,7 @@ class TestDomainDNSSEC(TestDomainOverview): ) def test_ds_data_form_invalid_digest_sha1(self): - """DS Data form errors with invalid data (digest is invalid sha-1) + """DS data form errors with invalid data (digest is invalid sha-1) Uses self.app WebTest because we need to interact with forms. """ @@ -2086,7 +2112,7 @@ class TestDomainDNSSEC(TestDomainOverview): ) def test_ds_data_form_invalid_digest_sha256(self): - """DS Data form errors with invalid data (digest is invalid sha-256) + """DS data form errors with invalid data (digest is invalid sha-256) Uses self.app WebTest because we need to interact with forms. """ @@ -2117,7 +2143,7 @@ class TestApplicationStatus(TestWithUser, WebTest): def test_application_status(self): """Checking application status page""" - application = completed_application(status=DomainApplication.SUBMITTED, user=self.user) + application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED, user=self.user) application.save() home_page = self.app.get("/") @@ -2137,7 +2163,7 @@ class TestApplicationStatus(TestWithUser, WebTest): self.user.status = "ineligible" self.user.save() - application = completed_application(status=DomainApplication.SUBMITTED, user=self.user) + application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED, user=self.user) application.save() home_page = self.app.get("/") @@ -2152,7 +2178,7 @@ class TestApplicationStatus(TestWithUser, WebTest): def test_application_withdraw(self): """Checking application status page""" - application = completed_application(status=DomainApplication.SUBMITTED, user=self.user) + application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED, user=self.user) application.save() home_page = self.app.get("/") @@ -2182,7 +2208,7 @@ class TestApplicationStatus(TestWithUser, WebTest): def test_application_status_no_permissions(self): """Can't access applications without being the creator.""" - application = completed_application(status=DomainApplication.SUBMITTED, user=self.user) + application = completed_application(status=DomainApplication.ApplicationStatus.SUBMITTED, user=self.user) other_user = User() other_user.save() application.creator = other_user @@ -2202,7 +2228,7 @@ class TestApplicationStatus(TestWithUser, WebTest): def test_approved_application_not_in_active_requests(self): """An approved application is not shown in the Active Requests table on home.html.""" - application = completed_application(status=DomainApplication.APPROVED, user=self.user) + application = completed_application(status=DomainApplication.ApplicationStatus.APPROVED, user=self.user) application.save() home_page = self.app.get("/") diff --git a/src/registrar/views/application.py b/src/registrar/views/application.py index 32633a89b..bb1b3aee6 100644 --- a/src/registrar/views/application.py +++ b/src/registrar/views/application.py @@ -293,9 +293,9 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView): return self.pending_applications() def approved_applications_exist(self): - """Checks if user is creator of applications with APPROVED status""" + """Checks if user is creator of applications with ApplicationStatus.APPROVED status""" approved_application_count = DomainApplication.objects.filter( - creator=self.request.user, status=DomainApplication.APPROVED + creator=self.request.user, status=DomainApplication.ApplicationStatus.APPROVED ).count() return approved_application_count > 0 @@ -308,11 +308,15 @@ class ApplicationWizard(ApplicationWizardPermissionView, TemplateView): def pending_applications(self): """Returns a List of user's applications with one of the following states: - SUBMITTED, IN_REVIEW, ACTION_NEEDED""" - # if the current application has ACTION_NEEDED status, this check should not be performed - if self.application.status == DomainApplication.ACTION_NEEDED: + ApplicationStatus.SUBMITTED, ApplicationStatus.IN_REVIEW, ApplicationStatus.ACTION_NEEDED""" + # if the current application has ApplicationStatus.ACTION_NEEDED status, this check should not be performed + if self.application.status == DomainApplication.ApplicationStatus.ACTION_NEEDED: return [] - check_statuses = [DomainApplication.SUBMITTED, DomainApplication.IN_REVIEW, DomainApplication.ACTION_NEEDED] + check_statuses = [ + DomainApplication.ApplicationStatus.SUBMITTED, + DomainApplication.ApplicationStatus.IN_REVIEW, + DomainApplication.ApplicationStatus.ACTION_NEEDED, + ] return DomainApplication.objects.filter(creator=self.request.user, status__in=check_statuses) def get_context_data(self): diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 81f1d0bb7..5ec4433f7 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -434,7 +434,7 @@ class DomainDsDataView(DomainFormBaseView): return initial_data def get_success_url(self): - """Redirect to the DS Data page for the domain.""" + """Redirect to the DS data page for the domain.""" return reverse("domain-dns-dnssec-dsdata", kwargs={"pk": self.object.pk}) def get_context_data(self, **kwargs): @@ -473,7 +473,7 @@ class DomainDsDataView(DomainFormBaseView): modal_button = ( '' + 'name="disable-override-click">Remove all DS data' ) # context to back out of a broken form on all fields delete @@ -523,7 +523,7 @@ class DomainDsDataView(DomainFormBaseView): logger.error(f"Registry error: {err}") return self.form_invalid(formset) else: - messages.success(self.request, "The DS Data records for this domain have been updated.") + messages.success(self.request, "The DS data records for this domain have been updated.") # superclass has the redirect return super().form_valid(formset) diff --git a/src/registrar/views/utility/mixins.py b/src/registrar/views/utility/mixins.py index aaa2e849c..37c0f6e98 100644 --- a/src/registrar/views/utility/mixins.py +++ b/src/registrar/views/utility/mixins.py @@ -101,10 +101,10 @@ class DomainPermission(PermissionsLoginMixin): # Analysts may manage domains, when they are in these statuses: valid_domain_statuses = [ - DomainApplication.APPROVED, - DomainApplication.IN_REVIEW, - DomainApplication.REJECTED, - DomainApplication.ACTION_NEEDED, + DomainApplication.ApplicationStatus.APPROVED, + DomainApplication.ApplicationStatus.IN_REVIEW, + DomainApplication.ApplicationStatus.REJECTED, + DomainApplication.ApplicationStatus.ACTION_NEEDED, # Edge case - some domains do not have # a status or DomainInformation... aka a status of 'None'. # It is necessary to access those to correct errors.