mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-21 10:16:13 +02:00
Add test
This commit is contained in:
parent
8b321837f4
commit
70fac1d719
4 changed files with 144 additions and 38 deletions
|
@ -733,6 +733,8 @@ class MockDb(TestCase):
|
||||||
self.domain_request_4 = completed_domain_request(
|
self.domain_request_4 = completed_domain_request(
|
||||||
status=DomainRequest.DomainRequestStatus.STARTED,
|
status=DomainRequest.DomainRequestStatus.STARTED,
|
||||||
name="city4.gov",
|
name="city4.gov",
|
||||||
|
is_election_board=True,
|
||||||
|
generic_org_type="city",
|
||||||
)
|
)
|
||||||
self.domain_request_5 = completed_domain_request(
|
self.domain_request_5 = completed_domain_request(
|
||||||
status=DomainRequest.DomainRequestStatus.APPROVED,
|
status=DomainRequest.DomainRequestStatus.APPROVED,
|
||||||
|
@ -742,8 +744,36 @@ class MockDb(TestCase):
|
||||||
self.domain_request_4.submit()
|
self.domain_request_4.submit()
|
||||||
|
|
||||||
self.domain_request_3.submission_date = get_time_aware_date(datetime(2024, 4, 2))
|
self.domain_request_3.submission_date = get_time_aware_date(datetime(2024, 4, 2))
|
||||||
self.domain_request_4.submission_date = get_time_aware_date(datetime(2024, 4, 2))
|
other, _ = Contact.objects.get_or_create(
|
||||||
|
first_name="Testy1232",
|
||||||
|
last_name="Tester24",
|
||||||
|
title="Another Tester",
|
||||||
|
email="te2@town.com",
|
||||||
|
phone="(555) 555 5557",
|
||||||
|
)
|
||||||
|
other_2, _ = Contact.objects.get_or_create(
|
||||||
|
first_name="Meow",
|
||||||
|
last_name="Tester24",
|
||||||
|
title="Another Tester",
|
||||||
|
email="te2@town.com",
|
||||||
|
phone="(555) 555 5557",
|
||||||
|
)
|
||||||
|
website, _ = Website.objects.get_or_create(website="igorville.gov")
|
||||||
|
website_2, _ = Website.objects.get_or_create(website="cheeseville.gov")
|
||||||
|
website_3, _ = Website.objects.get_or_create(website="https://www.example.com")
|
||||||
|
website_4, _ = Website.objects.get_or_create(website="https://www.example2.com")
|
||||||
|
self.domain_request_3.other_contacts.add(other)
|
||||||
self.domain_request_3.save()
|
self.domain_request_3.save()
|
||||||
|
self.domain_request_3.other_contacts.add(other_2)
|
||||||
|
self.domain_request_3.save()
|
||||||
|
self.domain_request_3.alternative_domains.add(website)
|
||||||
|
self.domain_request_3.alternative_domains.add(website_2)
|
||||||
|
self.domain_request_3.current_websites.add(website_3, website_4)
|
||||||
|
self.domain_request_3.current_websites.add(website_4)
|
||||||
|
self.domain_request_3.cisa_representative_email = "test@igorville.com"
|
||||||
|
self.domain_request_3.save()
|
||||||
|
|
||||||
|
self.domain_request_4.submission_date = get_time_aware_date(datetime(2024, 4, 2))
|
||||||
self.domain_request_4.save()
|
self.domain_request_4.save()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
|
|
@ -4,6 +4,7 @@ from django.test import Client, RequestFactory
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from registrar.models.domain_request import DomainRequest
|
from registrar.models.domain_request import DomainRequest
|
||||||
from registrar.models.domain import Domain
|
from registrar.models.domain import Domain
|
||||||
|
from registrar.models.utility.generic_helper import convert_queryset_to_dict
|
||||||
from registrar.utility.csv_export import (
|
from registrar.utility.csv_export import (
|
||||||
export_data_managed_domains_to_csv,
|
export_data_managed_domains_to_csv,
|
||||||
export_data_unmanaged_domains_to_csv,
|
export_data_unmanaged_domains_to_csv,
|
||||||
|
@ -23,6 +24,7 @@ from botocore.exceptions import ClientError
|
||||||
import boto3_mocking
|
import boto3_mocking
|
||||||
from registrar.utility.s3_bucket import S3ClientError, S3ClientErrorCodes # type: ignore
|
from registrar.utility.s3_bucket import S3ClientError, S3ClientErrorCodes # type: ignore
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from api.tests.common import less_console_noise_decorator
|
||||||
from .common import MockDb, MockEppLib, less_console_noise, get_time_aware_date
|
from .common import MockDb, MockEppLib, less_console_noise, get_time_aware_date
|
||||||
|
|
||||||
|
|
||||||
|
@ -680,11 +682,11 @@ class ExportDataTest(MockDb, MockEppLib):
|
||||||
"submission_date__gte": self.start_date,
|
"submission_date__gte": self.start_date,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
additional_values = ["requested_domain__name"]
|
||||||
all_requests = DomainRequest.objects.filter(**filter_condition).order_by(*sort_fields).distinct()
|
all_requests = DomainRequest.objects.filter(**filter_condition).order_by(*sort_fields).distinct()
|
||||||
extra = all_requests.values("generic_org_type", "federal_type", "submission_date")
|
annotated_requests = DomainRequestExport.annotate_and_retrieve_fields(all_requests, {}, additional_values)
|
||||||
DomainRequestExport.write_csv_for_requests(
|
requests_dict = convert_queryset_to_dict(annotated_requests, is_model=False)
|
||||||
writer, columns, all_requests, extra_request_fields=extra, should_write_header=True
|
DomainRequestExport.write_csv_for_requests(writer, columns, requests_dict)
|
||||||
)
|
|
||||||
# Reset the CSV file's position to the beginning
|
# Reset the CSV file's position to the beginning
|
||||||
csv_file.seek(0)
|
csv_file.seek(0)
|
||||||
# Read the content into a variable
|
# Read the content into a variable
|
||||||
|
@ -692,7 +694,70 @@ class ExportDataTest(MockDb, MockEppLib):
|
||||||
# We expect READY domains first, created between today-2 and today+2, sorted by created_at then name
|
# 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
|
# and DELETED domains deleted between today-2 and today+2, sorted by deleted then name
|
||||||
expected_content = (
|
expected_content = (
|
||||||
"Domain request,Domain type\n" "city3.gov,Federal - Executive\n" "city4.gov,Federal - Executive\n"
|
"Domain request,Domain type,Federal type\n"
|
||||||
|
"city3.gov,Federal,Executive\n"
|
||||||
|
"city4.gov,Federal,Executive\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)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_full_domain_request_report(self):
|
||||||
|
""" """
|
||||||
|
|
||||||
|
# Create a CSV file in memory
|
||||||
|
csv_file = StringIO()
|
||||||
|
writer = csv.writer(csv_file)
|
||||||
|
|
||||||
|
# Call the report. Get existing fields from the report itself.
|
||||||
|
annotations = DomainRequestExport._full_domain_request_annotations()
|
||||||
|
additional_values = [
|
||||||
|
"requested_domain__name",
|
||||||
|
"federal_agency__agency",
|
||||||
|
"authorizing_official__first_name",
|
||||||
|
"authorizing_official__last_name",
|
||||||
|
"authorizing_official__email",
|
||||||
|
"authorizing_official__title",
|
||||||
|
"creator__first_name",
|
||||||
|
"creator__last_name",
|
||||||
|
"creator__email",
|
||||||
|
"investigator__email",
|
||||||
|
]
|
||||||
|
requests = DomainRequest.objects.exclude(status=DomainRequest.DomainRequestStatus.STARTED)
|
||||||
|
annotated_requests = DomainRequestExport.annotate_and_retrieve_fields(requests, annotations, additional_values)
|
||||||
|
requests_dict = convert_queryset_to_dict(annotated_requests, is_model=False)
|
||||||
|
DomainRequestExport.write_csv_for_requests(writer, DomainRequestExport.all_columns, requests_dict)
|
||||||
|
|
||||||
|
# Reset the CSV file's position to the beginning
|
||||||
|
csv_file.seek(0)
|
||||||
|
# Read the content into a variable
|
||||||
|
csv_content = csv_file.read()
|
||||||
|
print(csv_content)
|
||||||
|
expected_content = (
|
||||||
|
# Header
|
||||||
|
"Domain request,Submitted at,Status,Domain type,Federal type,"
|
||||||
|
"Federal agency,Organization name,Election office,City,State/territory,"
|
||||||
|
"Region,Creator first name,Creator last name,Creator email,Creator approved domains count,"
|
||||||
|
"Creator active requests count,Alternative domains,AO first name,AO last name,AO email,"
|
||||||
|
"AO title/role,Request purpose,Request additional details,Other contacts,"
|
||||||
|
"CISA regional representative,Current websites,Investigator\n"
|
||||||
|
# Content
|
||||||
|
"city2.gov,,In review,Federal,Executive,,Testorg,N/A,,NY,2,,,,0,1,city1.gov,Testy,Tester,testy@town.com,"
|
||||||
|
"Chief Tester,Purpose of the site,There is more,Testy Tester testy2@town.com,,city.com,\n"
|
||||||
|
"city3.gov,2024-04-02,Submitted,Federal,Executive,,Testorg,N/A,,NY,2,,,,0,1,"
|
||||||
|
"cheeseville.gov | city1.gov | igorville.gov,Testy,Tester,testy@town.com,Chief Tester,"
|
||||||
|
"Purpose of the site,test@igorville.com | There is more,"
|
||||||
|
"Meow Tester24 te2@town.com | Testy1232 Tester24 te2@town.com "
|
||||||
|
"| Testy Tester testy2@town.com,test@igorville.com,city.com |"
|
||||||
|
" https://www.example2.com | https://www.example.com,\n"
|
||||||
|
"city5.gov,,Approved,Federal,Executive,,Testorg,N/A,,NY,2,,,,1,0,"
|
||||||
|
"city1.gov,Testy,Tester,testy@town.com,Chief Tester,Purpose of the site,There "
|
||||||
|
"is more,Testy Tester testy2@town.com,,city.com,"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Normalize line endings and remove commas,
|
# Normalize line endings and remove commas,
|
||||||
|
|
|
@ -9,7 +9,8 @@ from registrar.models import (
|
||||||
PublicContact,
|
PublicContact,
|
||||||
UserDomainRole,
|
UserDomainRole,
|
||||||
)
|
)
|
||||||
from django.db.models import QuerySet, Value, Case, When, CharField, Count, Q, F
|
from django.db.models import QuerySet, Value, CharField, Count, Q, F
|
||||||
|
from django.db.models import ManyToManyField
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.core.paginator import Paginator
|
from django.core.paginator import Paginator
|
||||||
from django.db.models.functions import Concat, Coalesce
|
from django.db.models.functions import Concat, Coalesce
|
||||||
|
@ -713,9 +714,6 @@ class DomainRequestExport:
|
||||||
A collection of functions which return csv files regarding the DomainRequest model.
|
A collection of functions which return csv files regarding the DomainRequest model.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Get all the field names of the DomainRequest object
|
|
||||||
domain_request_fields = set(field.name for field in DomainRequest._meta.get_fields())
|
|
||||||
|
|
||||||
# Get all columns on the full metadata report
|
# Get all columns on the full metadata report
|
||||||
all_columns = [
|
all_columns = [
|
||||||
"Domain request",
|
"Domain request",
|
||||||
|
@ -816,26 +814,8 @@ class DomainRequestExport:
|
||||||
.distinct()
|
.distinct()
|
||||||
)
|
)
|
||||||
|
|
||||||
# Annotations are custom columns returned to the queryset (AKA: computed in the DB)
|
# Annotations are custom columns returned to the queryset (AKA: computed in the DB).
|
||||||
delimiter = " | "
|
annotations = cls._full_domain_request_annotations()
|
||||||
annotations = {
|
|
||||||
"creator_approved_domains_count": DomainRequestExport.get_creator_approved_domains_count_query(),
|
|
||||||
"creator_active_requests_count": DomainRequestExport.get_creator_active_requests_count_query(),
|
|
||||||
"all_current_websites": StringAgg("current_websites__website", delimiter=delimiter, distinct=True),
|
|
||||||
"all_alternative_domains": StringAgg("alternative_domains__website", delimiter=delimiter, distinct=True),
|
|
||||||
# Coerce the other contacts object to "{first_name} {last_name} {email}"
|
|
||||||
"all_other_contacts": StringAgg(
|
|
||||||
Concat(
|
|
||||||
"other_contacts__first_name",
|
|
||||||
Value(" "),
|
|
||||||
"other_contacts__last_name",
|
|
||||||
Value(" "),
|
|
||||||
"other_contacts__email",
|
|
||||||
),
|
|
||||||
delimiter=delimiter,
|
|
||||||
distinct=True,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
# .values can't go two levels deep (just returns the id) - so we have to include this.
|
# .values can't go two levels deep (just returns the id) - so we have to include this.
|
||||||
additional_values = [
|
additional_values = [
|
||||||
|
@ -858,6 +838,28 @@ class DomainRequestExport:
|
||||||
# Write the csv file
|
# Write the csv file
|
||||||
cls.write_csv_for_requests(writer, cls.all_columns, requests_dict)
|
cls.write_csv_for_requests(writer, cls.all_columns, requests_dict)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _full_domain_request_annotations(cls, delimiter=" | "):
|
||||||
|
"""Returns the annotations for the full domain request report"""
|
||||||
|
return {
|
||||||
|
"creator_approved_domains_count": DomainRequestExport.get_creator_approved_domains_count_query(),
|
||||||
|
"creator_active_requests_count": DomainRequestExport.get_creator_active_requests_count_query(),
|
||||||
|
"all_current_websites": StringAgg("current_websites__website", delimiter=delimiter, distinct=True),
|
||||||
|
"all_alternative_domains": StringAgg("alternative_domains__website", delimiter=delimiter, distinct=True),
|
||||||
|
# Coerce the other contacts object to "{first_name} {last_name} {email}"
|
||||||
|
"all_other_contacts": StringAgg(
|
||||||
|
Concat(
|
||||||
|
"other_contacts__first_name",
|
||||||
|
Value(" "),
|
||||||
|
"other_contacts__last_name",
|
||||||
|
Value(" "),
|
||||||
|
"other_contacts__email",
|
||||||
|
),
|
||||||
|
delimiter=delimiter,
|
||||||
|
distinct=True,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def write_csv_for_requests(
|
def write_csv_for_requests(
|
||||||
writer,
|
writer,
|
||||||
|
@ -912,8 +914,9 @@ class DomainRequestExport:
|
||||||
|
|
||||||
# Handle the election field. N/A if None, "Yes"/"No" if boolean
|
# Handle the election field. N/A if None, "Yes"/"No" if boolean
|
||||||
human_readable_election_board = "N/A"
|
human_readable_election_board = "N/A"
|
||||||
if request.get("is_election_board") is not None:
|
is_election_board = request.get("is_election_board")
|
||||||
human_readable_election_board = "Yes" if request.is_election_board else "No"
|
if is_election_board is not None:
|
||||||
|
human_readable_election_board = "Yes" if is_election_board else "No"
|
||||||
|
|
||||||
# Handle the additional details field. Pipe sep.
|
# Handle the additional details field. Pipe sep.
|
||||||
cisa_rep = request.get("cisa_representative_email")
|
cisa_rep = request.get("cisa_representative_email")
|
||||||
|
@ -960,7 +963,9 @@ class DomainRequestExport:
|
||||||
return row
|
return row
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def annotate_and_retrieve_fields(cls, requests, annotations, additional_values=None) -> QuerySet:
|
def annotate_and_retrieve_fields(
|
||||||
|
cls, requests, annotations, additional_values=None, include_many_to_many=False
|
||||||
|
) -> QuerySet:
|
||||||
"""
|
"""
|
||||||
Applies annotations to a queryset and retrieves specified fields,
|
Applies annotations to a queryset and retrieves specified fields,
|
||||||
including class-defined and annotation-defined.
|
including class-defined and annotation-defined.
|
||||||
|
@ -969,14 +974,12 @@ class DomainRequestExport:
|
||||||
requests (QuerySet): Initial queryset.
|
requests (QuerySet): Initial queryset.
|
||||||
annotations (dict, optional): Fields to compute {field_name: expression}.
|
annotations (dict, optional): Fields to compute {field_name: expression}.
|
||||||
additional_values (list, optional): Extra fields to retrieve; defaults to annotation keys if None.
|
additional_values (list, optional): Extra fields to retrieve; defaults to annotation keys if None.
|
||||||
|
include_many_to_many (bool, optional): Determines if we should include many to many fields or not
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
QuerySet: Contains dictionaries with the specified fields for each record.
|
QuerySet: Contains dictionaries with the specified fields for each record.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if annotations is None:
|
|
||||||
annotations = {}
|
|
||||||
|
|
||||||
if additional_values is None:
|
if additional_values is None:
|
||||||
additional_values = []
|
additional_values = []
|
||||||
|
|
||||||
|
@ -985,7 +988,15 @@ class DomainRequestExport:
|
||||||
if annotations:
|
if annotations:
|
||||||
additional_values.extend(annotations.keys())
|
additional_values.extend(annotations.keys())
|
||||||
|
|
||||||
queryset = requests.annotate(**annotations).values(*cls.domain_request_fields, *additional_values)
|
# Get prexisting fields on DomainRequest
|
||||||
|
domain_request_fields = set()
|
||||||
|
for field in DomainRequest._meta.get_fields():
|
||||||
|
# Exclude many to many fields unless we specify
|
||||||
|
many_to_many = isinstance(field, ManyToManyField) and include_many_to_many
|
||||||
|
if many_to_many or not isinstance(field, ManyToManyField):
|
||||||
|
domain_request_fields.add(field.name)
|
||||||
|
|
||||||
|
queryset = requests.annotate(**annotations).values(*domain_request_fields, *additional_values)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
# ============================================================= #
|
# ============================================================= #
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue