diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 5bf41777b..78f85f0f9 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -1091,7 +1091,7 @@ class DomainApplicationAdmin(ListHeaderAdmin): search_help_text = "Search by domain or submitter." fieldsets = [ - (None, {"fields": ["status", "rejection_reason", "submission_date", "investigator", "creator", "approved_domain", "notes"]}), + (None, {"fields": ["status", "rejection_reason", "investigator", "creator", "approved_domain", "notes"]}), ( "Type of organization", { @@ -1443,7 +1443,7 @@ class DomainAdmin(ListHeaderAdmin): search_fields = ["name"] search_help_text = "Search by domain name." change_form_template = "django/admin/domain_change_form.html" - readonly_fields = ["state", "expiration_date", "deleted"] + readonly_fields = ["state", "expiration_date", "first_ready", "deleted"] # Table ordering ordering = ["name"] diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 3b18ac8b6..449c4c4bb 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -1022,7 +1022,7 @@ class Domain(TimeStampedModel, DomainHelper): first_ready = DateField( null=True, - editable=True, + editable=False, help_text="The last time this domain moved into the READY state", ) diff --git a/src/registrar/signals.py b/src/registrar/signals.py index 74dc8a063..4e7768ef4 100644 --- a/src/registrar/signals.py +++ b/src/registrar/signals.py @@ -27,7 +27,6 @@ def handle_profile(sender, instance, **kwargs): last_name = getattr(instance, "last_name", "") email = getattr(instance, "email", "") phone = getattr(instance, "phone", "") - logger.info(f"in handle_profile first {instance}") is_new_user = kwargs.get("created", False) @@ -37,7 +36,6 @@ def handle_profile(sender, instance, **kwargs): contacts = Contact.objects.filter(user=instance) if len(contacts) == 0: # no matching contact - logger.info(f"inside no matching contacts for first {first_name} last {last_name} email {email}") Contact.objects.create( user=instance, first_name=first_name, diff --git a/src/registrar/tests/data/mocks.py b/src/registrar/tests/data/mocks.py index e6dccb14f..25f56f247 100644 --- a/src/registrar/tests/data/mocks.py +++ b/src/registrar/tests/data/mocks.py @@ -1,5 +1,6 @@ from django.test import TestCase from django.contrib.auth import get_user_model +from api.tests.common import less_console_noise from registrar.models.domain_application import DomainApplication from registrar.models.domain_information import DomainInformation from registrar.models.domain import Domain @@ -8,7 +9,7 @@ from registrar.models.public_contact import PublicContact from registrar.models.user import User from datetime import date, datetime, timedelta from django.utils import timezone -from registrar.tests.common import MockEppLib +from registrar.tests.common import MockEppLib, completed_application class MockDb(MockEppLib): def setUp(self): @@ -152,81 +153,22 @@ class MockDb(MockEppLib): user=meoward_user, domain=self.domain_2, role=UserDomainRole.Roles.MANAGER ) - # self.domain_request_1, _ = DomainApplication.objects.get_or_create( - # creator=self.user, - # requested_domain=self.domain_1.name, - # organization_type="federal", - # federal_agency="World War I Centennial Commission", - # federal_type="executive", - # is_election_board=True - # ) - # self.domain_request_2, _ = DomainApplication.objects.get_or_create( - # creator=self.user, - # domain=self.domain_2, - # organization_type="interstate", - # is_election_board=True - # ) - # self.domain_request_3, _ = DomainApplication.objects.get_or_create( - # creator=self.user, - # domain=self.domain_3, - # organization_type="federal", - # federal_agency="Armed Forces Retirement Home", - # is_election_board=True - # ) - # self.domain_request_4, _ = DomainApplication.objects.get_or_create( - # creator=self.user, - # domain=self.domain_4, - # organization_type="federal", - # federal_agency="Armed Forces Retirement Home", - # is_election_board=True - # ) - # self.domain_request_5, _ = DomainApplication.objects.get_or_create( - # creator=self.user, - # domain=self.domain_5, - # organization_type="federal", - # federal_agency="Armed Forces Retirement Home", - # is_election_board=False - # ) - # self.domain_request_6, _ = DomainApplication.objects.get_or_create( - # creator=self.user, - # domain=self.domain_6, - # organization_type="federal", - # federal_agency="Armed Forces Retirement Home", - # is_election_board=False - # ) - # self.domain_request_7, _ = DomainApplication.objects.get_or_create( - # creator=self.user, - # domain=self.domain_7, - # organization_type="federal", - # federal_agency="Armed Forces Retirement Home", - # is_election_board=False - # ) - # self.domain_request_8, _ = DomainApplication.objects.get_or_create( - # creator=self.user, - # domain=self.domain_8, - # organization_type="federal", - # federal_agency="Armed Forces Retirement Home", - # is_election_board=False - # ) - # self.domain_information_9, _ = DomainApplication.objects.get_or_create( - # creator=self.user, - # domain=self.domain_9, - # organization_type="federal", - # federal_agency="Armed Forces Retirement Home", - # is_election_board=False - # ) - # self.domain_information_10, _ = DomainApplication.objects.get_or_create( - # creator=self.user, - # domain=self.domain_10, - # organization_type="federal", - # federal_agency="Armed Forces Retirement Home", - # is_election_board=False - # ) + with less_console_noise(): + self.domain_request_1 = completed_application(status=DomainApplication.ApplicationStatus.STARTED, name="city1.gov") + self.domain_request_2 = completed_application(status=DomainApplication.ApplicationStatus.IN_REVIEW, name="city2.gov") + self.domain_request_3 = completed_application(status=DomainApplication.ApplicationStatus.STARTED, name="city3.gov") + self.domain_request_4 = completed_application(status=DomainApplication.ApplicationStatus.STARTED, name="city4.gov") + self.domain_request_5 = completed_application(status=DomainApplication.ApplicationStatus.APPROVED, name="city5.gov") + self.domain_request_3.submit() + self.domain_request_3.save() + self.domain_request_4.submit() + self.domain_request_4.save() def tearDown(self): PublicContact.objects.all().delete() Domain.objects.all().delete() DomainInformation.objects.all().delete() + DomainApplication.objects.all().delete() User.objects.all().delete() UserDomainRole.objects.all().delete() super().tearDown() \ No newline at end of file diff --git a/src/registrar/tests/test_reports.py b/src/registrar/tests/test_reports.py index 43efb3128..cc7cc7991 100644 --- a/src/registrar/tests/test_reports.py +++ b/src/registrar/tests/test_reports.py @@ -2,6 +2,7 @@ import csv import io from django.test import Client, RequestFactory, TestCase from io import StringIO +from registrar.models.domain_application import DomainApplication from registrar.models.domain_information import DomainInformation from registrar.models.domain import Domain from registrar.models.public_contact import PublicContact @@ -13,9 +14,11 @@ from registrar.utility.csv_export import ( format_end_date, format_start_date, get_sliced_domains, + get_sliced_requests, write_domains_csv, get_default_start_date, get_default_end_date, + write_requests_csv, ) from django.core.management import call_command @@ -27,7 +30,7 @@ import boto3_mocking from registrar.utility.s3_bucket import S3ClientError, S3ClientErrorCodes # type: ignore from datetime import date, datetime, timedelta from django.utils import timezone -from .common import less_console_noise +from .common import completed_application, less_console_noise class CsvReportsTest(TestCase): @@ -771,9 +774,58 @@ class ExportDataTest(MockDb): Test that requests are sorted by requested domain name. """ - pass + with less_console_noise(): + # Create a CSV file in memory + csv_file = StringIO() + writer = csv.writer(csv_file) + # We use timezone.make_aware to sync to server time a datetime object with the current date + # (using date.today()) and a specific time (using datetime.min.time()). + + # Create a time-aware current date + current_datetime = timezone.now() + # Extract the date part + current_date = current_datetime.date() + # Create start and end dates using timedelta + end_date = current_date + timedelta(days=2) + start_date = current_date - timedelta(days=2) -class HelperFunctions(TestCase): + # Define columns, sort fields, and filter condition + columns = [ + "Requested domain", + "Organization type", + "Submission date", + ] + sort_fields = [ + "requested_domain__name", + ] + filter_condition = { + "status": DomainApplication.ApplicationStatus.SUBMITTED, + "submission_date__lte": end_date, + "submission_date__gte": start_date, + } + write_requests_csv(writer, columns, sort_fields, filter_condition, should_write_header=True) + # Reset the CSV file's position to the beginning + csv_file.seek(0) + + # Read the content into a variable + csv_content = csv_file.read() + + # We expect READY domains first, created between today-2 and today+2, sorted by created_at then name + # and DELETED domains deleted between today-2 and today+2, sorted by deleted then name + expected_content = ( + "Requested domain,Organization type,Submission date\n" + "city3.gov,Federal - Executive,2024-03-05\n" + "city4.gov,Federal - Executive,2024-03-05\n" + ) + + # Normalize line endings and remove commas, + # spaces and leading/trailing whitespace + csv_content = csv_content.replace(",,", "").replace(",", "").replace(" ", "").replace("\r\n", "\n").strip() + expected_content = expected_content.replace(",,", "").replace(",", "").replace(" ", "").strip() + + self.assertEqual(csv_content, expected_content) + +class HelperFunctions(MockDb): """This asserts that 1=1. Its limited usefulness lies in making sure the helper methods stay healthy.""" def test_get_default_start_date(self): @@ -787,10 +839,52 @@ class HelperFunctions(TestCase): actual_date = get_default_end_date() self.assertEqual(actual_date.date(), expected_date.date()) - def get_sliced_domains(self): + def test_get_sliced_domains(self): """Should get fitered domains counts sliced by org type and election office.""" - pass + with less_console_noise(): + # Create a time-aware current date + current_datetime = timezone.now() + # Extract the date part + current_date = current_datetime.date() + # Create start and end dates using timedelta + end_date = current_date + timedelta(days=2) + start_date = current_date - timedelta(days=2) + + filter_condition = { + "domain__permissions__isnull": False, + "domain__first_ready__lte": end_date, + } + managed_domains_sliced_at_end_date = get_sliced_domains(filter_condition) + + expected_content = ( + [1, 1, 0, 0, 0, 0, 0, 0, 0, 1] + ) + + self.assertEqual(managed_domains_sliced_at_end_date, expected_content) + + def test_get_sliced_requests(self): """Should get fitered requests counts sliced by org type and election office.""" - pass \ No newline at end of file + with less_console_noise(): + # Create a time-aware current date + current_datetime = timezone.now() + # Extract the date part + current_date = current_datetime.date() + # Create start and end dates using timedelta + end_date = current_date + timedelta(days=2) + start_date = current_date - timedelta(days=2) + + filter_condition = { + "status": DomainApplication.ApplicationStatus.SUBMITTED, + "submission_date__lte": end_date, + } + submitted_requests_sliced_at_end_date = get_sliced_requests(filter_condition) + + print(f'managed_domains_sliced_at_end_date {submitted_requests_sliced_at_end_date}') + + expected_content = ( + [2, 2, 0, 0, 0, 0, 0, 0, 0, 0] + ) + + self.assertEqual(submitted_requests_sliced_at_end_date, expected_content) \ No newline at end of file diff --git a/src/registrar/utility/csv_export.py b/src/registrar/utility/csv_export.py index cbdbfddb3..e09258022 100644 --- a/src/registrar/utility/csv_export.py +++ b/src/registrar/utility/csv_export.py @@ -188,7 +188,7 @@ def write_domains_csv( def get_requests(filter_condition, sort_fields): - requests = DomainApplication.objects.all().filter(**filter_condition).order_by(*sort_fields) + requests = DomainApplication.objects.all().filter(**filter_condition).order_by(*sort_fields).distinct() return requests @@ -197,12 +197,8 @@ def parse_request_row(columns, request: DomainApplication): requested_domain_name = "No requested domain" - # Domain should never be none when parsing this information if request.requested_domain is not None: - domain = request.requested_domain - requested_domain_name = domain.name - - domain = request.requested_domain # type: ignore + requested_domain_name = request.requested_domain.name if request.federal_type: request_type = f"{request.get_organization_type_display()} - {request.get_federal_type_display()}"