diff --git a/src/registrar/admin.py b/src/registrar/admin.py index f0ec5199f..cfc64cf3a 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -1841,6 +1841,13 @@ class VerifiedByStaffAdmin(ListHeaderAdmin): super().save_model(request, obj, form, change) +class FederalAgencyAdmin(ListHeaderAdmin): + list_display = ["agency"] + search_fields = ["agency"] + search_help_text = "Search by agency name." + ordering = ["agency"] + + admin.site.unregister(LogEntry) # Unregister the default registration admin.site.register(LogEntry, CustomLogEntryAdmin) admin.site.register(models.User, MyUserAdmin) @@ -1854,6 +1861,7 @@ admin.site.register(models.DomainInvitation, DomainInvitationAdmin) admin.site.register(models.DomainInformation, DomainInformationAdmin) admin.site.register(models.Domain, DomainAdmin) admin.site.register(models.DraftDomain, DraftDomainAdmin) +admin.site.register(models.FederalAgency, FederalAgencyAdmin) # Host and HostIP removed from django admin because changes in admin # do not propagate to registry and logic not applied admin.site.register(models.Host, MyHostAdmin) diff --git a/src/registrar/migrations/0079_create_federal_agencies_v01.py b/src/registrar/migrations/0079_create_federal_agencies_v01.py new file mode 100644 index 000000000..2f42e3382 --- /dev/null +++ b/src/registrar/migrations/0079_create_federal_agencies_v01.py @@ -0,0 +1,38 @@ +# Generated by Django 4.2.10 on 2024-03-22 22:18 + +from django.db import migrations, models +from registrar.models import FederalAgency +from typing import Any + + +# For linting: RunPython expects a function reference. +def create_federal_agencies(apps, schema_editor) -> Any: + FederalAgency.create_federal_agencies(apps, schema_editor) + + +class Migration(migrations.Migration): + + dependencies = [ + ("registrar", "0078_rename_organization_type_domaininformation_generic_org_type_and_more"), + ] + + operations = [ + migrations.CreateModel( + name="FederalAgency", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("agency", models.CharField(blank=True, help_text="Federal agency", null=True)), + ], + options={ + "verbose_name": "Federal agency", + "verbose_name_plural": "Federal agencies", + }, + ), + migrations.RunPython( + create_federal_agencies, + reverse_code=migrations.RunPython.noop, + atomic=True, + ), + ] diff --git a/src/registrar/migrations/0080_create_groups_v09.py b/src/registrar/migrations/0080_create_groups_v09.py new file mode 100644 index 000000000..342404aa1 --- /dev/null +++ b/src/registrar/migrations/0080_create_groups_v09.py @@ -0,0 +1,37 @@ +# This migration creates the create_full_access_group and create_cisa_analyst_group groups +# It is dependent on 0079 (which populates federal agencies) +# If permissions on the groups need changing, edit CISA_ANALYST_GROUP_PERMISSIONS +# in the user_group model then: +# [NOT RECOMMENDED] +# step 1: docker-compose exec app ./manage.py migrate --fake registrar 0035_contenttypes_permissions +# step 2: docker-compose exec app ./manage.py migrate registrar 0036_create_groups +# step 3: fake run the latest migration in the migrations list +# [RECOMMENDED] +# Alternatively: +# step 1: duplicate the migration that loads data +# step 2: docker-compose exec app ./manage.py migrate + +from django.db import migrations +from registrar.models import UserGroup +from typing import Any + + +# For linting: RunPython expects a function reference, +# so let's give it one +def create_groups(apps, schema_editor) -> Any: + UserGroup.create_cisa_analyst_group(apps, schema_editor) + UserGroup.create_full_access_group(apps, schema_editor) + + +class Migration(migrations.Migration): + dependencies = [ + ("registrar", "0079_create_federal_agencies_v01"), + ] + + operations = [ + migrations.RunPython( + create_groups, + reverse_code=migrations.RunPython.noop, + atomic=True, + ), + ] diff --git a/src/registrar/models/__init__.py b/src/registrar/models/__init__.py index d203421ac..d3bbb3ae5 100644 --- a/src/registrar/models/__init__.py +++ b/src/registrar/models/__init__.py @@ -4,6 +4,7 @@ from .domain_request import DomainRequest from .domain_information import DomainInformation from .domain import Domain from .draft_domain import DraftDomain +from .federal_agency import FederalAgency from .host_ip import HostIP from .host import Host from .domain_invitation import DomainInvitation @@ -22,6 +23,7 @@ __all__ = [ "Domain", "DraftDomain", "DomainInvitation", + "FederalAgency", "HostIP", "Host", "UserDomainRole", @@ -39,6 +41,7 @@ auditlog.register(Domain) auditlog.register(DraftDomain) auditlog.register(DomainInvitation) auditlog.register(DomainInformation) +auditlog.register(FederalAgency) auditlog.register(HostIP) auditlog.register(Host) auditlog.register(UserDomainRole) diff --git a/src/registrar/models/federal_agency.py b/src/registrar/models/federal_agency.py new file mode 100644 index 000000000..89b15ab56 --- /dev/null +++ b/src/registrar/models/federal_agency.py @@ -0,0 +1,223 @@ +from .utility.time_stamped_model import TimeStampedModel +from django.db import models +import logging + +logger = logging.getLogger(__name__) + + +class FederalAgency(TimeStampedModel): + class Meta: + verbose_name = "Federal agency" + verbose_name_plural = "Federal agencies" + + agency = models.CharField( + null=True, + blank=True, + help_text="Federal agency", + ) + + def __str__(self) -> str: + return f"{self.agency}" + + def create_federal_agencies(apps, schema_editor): + """This method gets run from a data migration to prepopulate data + regarding federal agencies.""" + + # Hard to pass self to these methods as the calls from migrations + # are only expecting apps and schema_editor, so we'll just define + # apps, schema_editor in the local scope instead + + AGENCIES = [ + "Administrative Conference of the United States", + "Advisory Council on Historic Preservation", + "American Battle Monuments Commission", + "AMTRAK", + "Appalachian Regional Commission", + ("Appraisal Subcommittee of the Federal Financial " "Institutions Examination Council"), + "Architect of the Capitol", + "Armed Forces Retirement Home", + "Barry Goldwater Scholarship and Excellence in Education Foundation", + "Central Intelligence Agency", + "Christopher Columbus Fellowship Foundation", + "Civil Rights Cold Case Records Review Board", + "Commission for the Preservation of America's Heritage Abroad", + "Commission of Fine Arts", + "Committee for Purchase From People Who Are Blind or Severely Disabled", + "Commodity Futures Trading Commission", + "Congressional Budget Office", + "Consumer Financial Protection Bureau", + "Consumer Product Safety Commission", + "Corporation for National and Community Service", + "Council of Inspectors General on Integrity and Efficiency", + "Court Services and Offender Supervision", + "Cyberspace Solarium Commission", + "DC Court Services and Offender Supervision Agency", + "DC Pre-trial Services", + "Defense Nuclear Facilities Safety Board", + "Delta Regional Authority", + "Denali Commission", + "Department of Agriculture", + "Department of Commerce", + "Department of Defense", + "Department of Education", + "Department of Energy", + "Department of Health and Human Services", + "Department of Homeland Security", + "Department of Housing and Urban Development", + "Department of Justice", + "Department of Labor", + "Department of State", + "Department of the Interior", + "Department of the Treasury", + "Department of Transportation", + "Department of Veterans Affairs", + "Director of National Intelligence", + "Dwight D. Eisenhower Memorial Commission", + "Election Assistance Commission", + "Environmental Protection Agency", + "Equal Employment Opportunity Commission", + "Executive Office of the President", + "Export-Import Bank of the United States", + "Farm Credit Administration", + "Farm Credit System Insurance Corporation", + "Federal Communications Commission", + "Federal Deposit Insurance Corporation", + "Federal Election Commission", + "Federal Energy Regulatory Commission", + "Federal Financial Institutions Examination Council", + "Federal Housing Finance Agency", + "Federal Judiciary", + "Federal Labor Relations Authority", + "Federal Maritime Commission", + "Federal Mediation and Conciliation Service", + "Federal Mine Safety and Health Review Commission", + "Federal Permitting Improvement Steering Council", + "Federal Reserve Board of Governors", + "Federal Trade Commission", + "General Services Administration", + "gov Administration", + "Government Accountability Office", + "Government Publishing Office", + "Gulf Coast Ecosystem Restoration Council", + "Harry S. Truman Scholarship Foundation", + "Institute of Museum and Library Services", + "Institute of Peace", + "Inter-American Foundation", + "International Boundary and Water Commission: United States and Mexico", + "International Boundary Commission: United States and Canada", + "International Joint Commission: United States and Canada", + "James Madison Memorial Fellowship Foundation", + "Japan-U.S. Friendship Commission", + "John F. Kennedy Center for the Performing Arts", + "Legal Services Corporation", + "Legislative Branch", + "Library of Congress", + "Marine Mammal Commission", + "Medicaid and CHIP Payment and Access Commission", + "Medicare Payment Advisory Commission", + "Merit Systems Protection Board", + "Millennium Challenge Corporation", + "Morris K. Udall and Stewart L. Udall Foundation", + "National Aeronautics and Space Administration", + "National Archives and Records Administration", + "National Capital Planning Commission", + "National Council on Disability", + "National Credit Union Administration", + "National Endowment for the Arts", + "National Endowment for the Humanities", + "National Foundation on the Arts and the Humanities", + "National Gallery of Art", + "National Indian Gaming Commission", + "National Labor Relations Board", + "National Mediation Board", + "National Science Foundation", + "National Security Commission on Artificial Intelligence", + "National Transportation Safety Board", + "Networking Information Technology Research and Development", + "Non-Federal Agency", + "Northern Border Regional Commission", + "Nuclear Regulatory Commission", + "Nuclear Safety Oversight Committee", + "Occupational Safety and Health Review Commission", + "Office of Compliance", + "Office of Congressional Workplace Rights", + "Office of Government Ethics", + "Office of Navajo and Hopi Indian Relocation", + "Office of Personnel Management", + "Open World Leadership Center", + "Overseas Private Investment Corporation", + "Peace Corps", + "Pension Benefit Guaranty Corporation", + "Postal Regulatory Commission", + "Presidio Trust", + "Privacy and Civil Liberties Oversight Board", + "Public Buildings Reform Board", + "Public Defender Service for the District of Columbia", + "Railroad Retirement Board", + "Securities and Exchange Commission", + "Selective Service System", + "Small Business Administration", + "Smithsonian Institution", + "Social Security Administration", + "Social Security Advisory Board", + "Southeast Crescent Regional Commission", + "Southwest Border Regional Commission", + "State Justice Institute", + "Stennis Center for Public Service", + "Surface Transportation Board", + "Tennessee Valley Authority", + "The Executive Office of the President", + "The Intelligence Community", + "The Legislative Branch", + "The Supreme Court", + "The United States World War One Centennial Commission", + "U.S. Access Board", + "U.S. Agency for Global Media", + "U.S. Agency for International Development", + "U.S. Capitol Police", + "U.S. Chemical Safety Board", + "U.S. China Economic and Security Review Commission", + "U.S. Commission for the Preservation of Americas Heritage Abroad", + "U.S. Commission of Fine Arts", + "U.S. Commission on Civil Rights", + "U.S. Commission on International Religious Freedom", + "U.S. Courts", + "U.S. Department of Agriculture", + "U.S. Interagency Council on Homelessness", + "U.S. International Trade Commission", + "U.S. Nuclear Waste Technical Review Board", + "U.S. Office of Special Counsel", + "U.S. Postal Service", + "U.S. Semiquincentennial Commission", + "U.S. Trade and Development Agency", + "U.S.-China Economic and Security Review Commission", + "Udall Foundation", + "United States AbilityOne", + "United States Access Board", + "United States African Development Foundation", + "United States Agency for Global Media", + "United States Arctic Research Commission", + "United States Global Change Research Program", + "United States Holocaust Memorial Museum", + "United States Institute of Peace", + "United States Interagency Council on Homelessness", + "United States International Development Finance Corporation", + "United States International Trade Commission", + "United States Postal Service", + "United States Senate", + "United States Trade and Development Agency", + "Utah Reclamation Mitigation and Conservation Commission", + "Vietnam Education Foundation", + "Western Hemisphere Drug Policy Commission", + "Woodrow Wilson International Center for Scholars", + "World War I Centennial Commission", + ] + + FederalAgency = apps.get_model("registrar", "FederalAgency") + logger.info("Creating federal agency table.") + + try: + agencies = [FederalAgency(agency=agency) for agency in AGENCIES] + FederalAgency.objects.bulk_create(agencies) + except Exception as e: + logger.error(f"Error creating federal agencies: {e}") diff --git a/src/registrar/models/user_group.py b/src/registrar/models/user_group.py index a84da798a..2aa2f642e 100644 --- a/src/registrar/models/user_group.py +++ b/src/registrar/models/user_group.py @@ -71,6 +71,11 @@ class UserGroup(Group): "model": "verifiedbystaff", "permissions": ["add_verifiedbystaff", "change_verifiedbystaff", "delete_verifiedbystaff"], }, + { + "app_label": "registrar", + "model": "federalagency", + "permissions": ["add_federalagency", "change_federalagency", "delete_federalagency"], + }, ] # Avoid error: You can't execute queries until the end diff --git a/src/registrar/tests/common.py b/src/registrar/tests/common.py index 5349a3b83..87af54669 100644 --- a/src/registrar/tests/common.py +++ b/src/registrar/tests/common.py @@ -548,6 +548,12 @@ class MockDb(TestCase): state=Domain.State.READY, first_ready=timezone.make_aware(datetime.combine(date.today() + timedelta(days=1), datetime.min.time())), ) + self.domain_11, _ = Domain.objects.get_or_create( + name="cdomain11.gov", state=Domain.State.READY, first_ready=timezone.now() + ) + self.domain_12, _ = Domain.objects.get_or_create( + name="zdomain12.gov", state=Domain.State.READY, first_ready=timezone.now() + ) self.domain_information_1, _ = DomainInformation.objects.get_or_create( creator=self.user, @@ -616,6 +622,20 @@ class MockDb(TestCase): federal_agency="Armed Forces Retirement Home", is_election_board=False, ) + self.domain_information_11, _ = DomainInformation.objects.get_or_create( + creator=self.user, + domain=self.domain_11, + generic_org_type="federal", + federal_agency="World War I Centennial Commission", + federal_type="executive", + is_election_board=True, + ) + self.domain_information_12, _ = DomainInformation.objects.get_or_create( + creator=self.user, + domain=self.domain_12, + generic_org_type="interstate", + is_election_board=False, + ) meoward_user = get_user_model().objects.create( username="meoward_username", first_name="first_meoward", last_name="last_meoward", email="meoward@rocks.com" @@ -641,6 +661,14 @@ class MockDb(TestCase): user=meoward_user, domain=self.domain_2, role=UserDomainRole.Roles.MANAGER ) + _, created = UserDomainRole.objects.get_or_create( + user=meoward_user, domain=self.domain_11, role=UserDomainRole.Roles.MANAGER + ) + + _, created = UserDomainRole.objects.get_or_create( + user=meoward_user, domain=self.domain_12, role=UserDomainRole.Roles.MANAGER + ) + with less_console_noise(): self.domain_request_1 = completed_domain_request( status=DomainRequest.DomainRequestStatus.STARTED, name="city1.gov" diff --git a/src/registrar/tests/test_admin.py b/src/registrar/tests/test_admin.py index 267fb4af4..5165e4def 100644 --- a/src/registrar/tests/test_admin.py +++ b/src/registrar/tests/test_admin.py @@ -284,7 +284,7 @@ class TestDomainAdmin(MockEppLib, WebTest): # There are 4 template references to Federal (4) plus four references in the table # for our actual domain_request - self.assertContains(response, "Federal", count=8) + self.assertContains(response, "Federal", count=36) # This may be a bit more robust self.assertContains(response, 'Federal', count=1) # Now let's make sure the long description does not exist @@ -708,7 +708,7 @@ class TestDomainRequestAdmin(MockEppLib): response = self.client.get("/admin/registrar/domainrequest/?generic_org_type__exact=federal") # There are 2 template references to Federal (4) and two in the results data # of the request - self.assertContains(response, "Federal", count=6) + self.assertContains(response, "Federal", count=34) # This may be a bit more robust self.assertContains(response, 'Federal', count=1) # Now let's make sure the long description does not exist @@ -722,7 +722,7 @@ class TestDomainRequestAdmin(MockEppLib): self.client.force_login(self.superuser) completed_domain_request() response = self.client.get("/admin/registrar/domainrequest/") - # The results are filtered by "status in [submitted]" + # The results are filtered by "status in [submitted,in review,action needed]" self.assertContains(response, "status in [submitted,in review,action needed]", count=1) def transition_state_and_send_email(self, domain_request, status, rejection_reason=None): diff --git a/src/registrar/tests/test_migrations.py b/src/registrar/tests/test_migrations.py index 50861d97f..bf3b09d0d 100644 --- a/src/registrar/tests/test_migrations.py +++ b/src/registrar/tests/test_migrations.py @@ -39,6 +39,9 @@ class TestGroups(TestCase): "view_domaininvitation", "change_domainrequest", "change_draftdomain", + "add_federalagency", + "change_federalagency", + "delete_federalagency", "analyst_access_permission", "change_user", "delete_userdomainrole", diff --git a/src/registrar/tests/test_reports.py b/src/registrar/tests/test_reports.py index 029fe873a..5bd594a15 100644 --- a/src/registrar/tests/test_reports.py +++ b/src/registrar/tests/test_reports.py @@ -44,6 +44,7 @@ class CsvReportsTest(MockDb): fake_open = mock_open() expected_file_content = [ call("Domain name,Domain type,Agency,Organization name,City,State,Security contact email\r\n"), + call("cdomain11.gov,Federal - Executive,World War I Centennial Commission,,,, \r\n"), call("cdomain1.gov,Federal - Executive,World War I Centennial Commission,,,, \r\n"), call("adomain10.gov,Federal,Armed Forces Retirement Home,,,, \r\n"), call("ddomain3.gov,Federal,Armed Forces Retirement Home,,,, \r\n"), @@ -65,6 +66,7 @@ class CsvReportsTest(MockDb): fake_open = mock_open() expected_file_content = [ call("Domain name,Domain type,Agency,Organization name,City,State,Security contact email\r\n"), + call("cdomain11.gov,Federal - Executive,World War I Centennial Commission,,,, \r\n"), call("cdomain1.gov,Federal - Executive,World War I Centennial Commission,,,, \r\n"), call("adomain10.gov,Federal,Armed Forces Retirement Home,,,, \r\n"), call("ddomain3.gov,Federal,Armed Forces Retirement Home,,,, \r\n"), @@ -255,8 +257,10 @@ class ExportDataTest(MockDb, MockEppLib): "AO email,Security contact email,Status,Expiration date\n" "adomain10.gov,Federal,Armed Forces Retirement Home,Ready\n" "adomain2.gov,Interstate,(blank),Dns needed\n" + "cdomain11.govFederal-ExecutiveWorldWarICentennialCommissionReady\n" "ddomain3.gov,Federal,Armed Forces Retirement Home,123@mail.gov,On hold,2023-05-25\n" - "defaultsecurity.gov,Federal - Executive,World War I Centennial Commission,(blank),Ready" + "defaultsecurity.gov,Federal - Executive,World War I Centennial Commission,(blank),Ready\n" + "zdomain12.govInterstateReady\n" ) # Normalize line endings and remove commas, # spaces and leading/trailing whitespace @@ -315,8 +319,10 @@ class ExportDataTest(MockDb, MockEppLib): "Security contact email,Status\n" "adomain10.gov,Federal,Armed Forces Retirement Home,Ready\n" "adomain2.gov,Interstate,Dns needed\n" + "cdomain11.govFederal-ExecutiveWorldWarICentennialCommissionReady\n" "cdomain1.gov,Federal - Executive,World War I Centennial Commission,Ready\n" "ddomain3.gov,Federal,Armed Forces Retirement Home,On hold\n" + "zdomain12.govInterstateReady\n" ) # Normalize line endings and remove commas, # spaces and leading/trailing whitespace @@ -365,6 +371,7 @@ class ExportDataTest(MockDb, MockEppLib): "Domain name,Domain type,Agency,Organization name,City," "State,Security contact email\n" "adomain10.gov,Federal,Armed Forces Retirement Home\n" + "cdomain11.govFederal-ExecutiveWorldWarICentennialCommission\n" "cdomain1.gov,Federal - Executive,World War I Centennial Commission\n" "ddomain3.gov,Federal,Armed Forces Retirement Home\n" ) @@ -455,6 +462,8 @@ class ExportDataTest(MockDb, MockEppLib): "State,Status,Expiration date\n" "cdomain1.gov,Federal-Executive,World War I Centennial Commission,,,,Ready,\n" "adomain10.gov,Federal,Armed Forces Retirement Home,,,,Ready,\n" + "cdomain11.govFederal-ExecutiveWorldWarICentennialCommissionReady\n" + "zdomain12.govInterstateReady\n" "zdomain9.gov,Federal,Armed Forces Retirement Home,,,,Deleted,\n" "sdomain8.gov,Federal,Armed Forces Retirement Home,,,,Deleted,\n" "xdomain7.gov,Federal,Armed Forces Retirement Home,,,,Deleted,\n" @@ -515,9 +524,11 @@ class ExportDataTest(MockDb, MockEppLib): "Security contact email,Domain manager email 1,Domain manager email 2,Domain manager email 3\n" "adomain10.gov,Ready,,Federal,Armed Forces Retirement Home,,,, , ,\n" "adomain2.gov,Dns needed,,Interstate,,,,, , , ,meoward@rocks.com\n" + "cdomain11.govReadyFederal-ExecutiveWorldWarICentennialCommissionmeoward@rocks.com\n" "cdomain1.gov,Ready,,Federal - Executive,World War I Centennial Commission,,," ", , , ,meoward@rocks.com,info@example.com,big_lebowski@dude.co\n" "ddomain3.gov,On hold,,Federal,Armed Forces Retirement Home,,,, , , ,,\n" + "zdomain12.govReadyInterstatemeoward@rocks.com\n" ) # Normalize line endings and remove commas, # spaces and leading/trailing whitespace @@ -551,10 +562,12 @@ class ExportDataTest(MockDb, MockEppLib): "MANAGED DOMAINS COUNTS AT END DATE\n" "Total,Federal,Interstate,State or territory,Tribal,County,City," "Special district,School district,Election office\n" - "1,1,0,0,0,0,0,0,0,1\n" + "3,2,1,0,0,0,0,0,0,2\n" "\n" "Domain name,Domain type,Domain manager email 1,Domain manager email 2,Domain manager email 3\n" + "cdomain11.govFederal-Executivemeoward@rocks.com\n" "cdomain1.gov,Federal - Executive,meoward@rocks.com,info@example.com,big_lebowski@dude.co\n" + "zdomain12.govInterstatemeoward@rocks.com\n" ) # Normalize line endings and remove commas, @@ -674,12 +687,12 @@ class HelperFunctions(MockDb): } # Test with distinct managed_domains_sliced_at_end_date = get_sliced_domains(filter_condition, True) - expected_content = [1, 1, 0, 0, 0, 0, 0, 0, 0, 1] + expected_content = [3, 2, 1, 0, 0, 0, 0, 0, 0, 2] self.assertEqual(managed_domains_sliced_at_end_date, expected_content) # Test without distinct managed_domains_sliced_at_end_date = get_sliced_domains(filter_condition) - expected_content = [1, 3, 0, 0, 0, 0, 0, 0, 0, 1] + expected_content = [3, 4, 1, 0, 0, 0, 0, 0, 0, 2] self.assertEqual(managed_domains_sliced_at_end_date, expected_content) def test_get_sliced_requests(self): diff --git a/src/registrar/utility/csv_export.py b/src/registrar/utility/csv_export.py index 5022fcd10..7fc710827 100644 --- a/src/registrar/utility/csv_export.py +++ b/src/registrar/utility/csv_export.py @@ -80,7 +80,7 @@ def parse_domain_row(columns, domain_info: DomainInformation, security_emails_di if security_email.lower() in invalid_emails: security_email = "(blank)" - if domain_info.federal_type: + if domain_info.federal_type and domain_info.generic_org_type == DomainRequest.OrganizationChoices.FEDERAL: domain_type = f"{domain_info.get_generic_org_type_display()} - {domain_info.get_federal_type_display()}" else: domain_type = domain_info.get_generic_org_type_display() @@ -469,16 +469,43 @@ def get_sliced_domains(filter_condition, distinct=False): domains_count = DomainInformation.objects.filter(**filter_condition).distinct().count() # Round trip 2: Get counts for other slices + # This will require either 8 filterd and distinct DB round trips, + # or 2 DB round trips plus iteration on domain_permissions for each domain if distinct: - generic_org_types_query = ( - DomainInformation.objects.filter(**filter_condition).values_list("generic_org_type", flat=True).distinct() + generic_org_types_query = DomainInformation.objects.filter(**filter_condition).values_list( + "domain_id", "generic_org_type" ) + # Initialize Counter to store counts for each generic_org_type + generic_org_type_counts = Counter() + + # Keep track of domains already counted + domains_counted = set() + + # Iterate over distinct domains + for domain_id, generic_org_type in generic_org_types_query: + # Check if the domain has already been counted + if domain_id in domains_counted: + continue + + # Get all permissions for the current domain + domain_permissions = DomainInformation.objects.filter(domain_id=domain_id, **filter_condition).values_list( + "domain__permissions", flat=True + ) + + # Check if the domain has multiple permissions + if len(domain_permissions) > 0: + # Mark the domain as counted + domains_counted.add(domain_id) + + # Increment the count for the corresponding generic_org_type + generic_org_type_counts[generic_org_type] += 1 else: generic_org_types_query = DomainInformation.objects.filter(**filter_condition).values_list( "generic_org_type", flat=True ) - generic_org_type_counts = Counter(generic_org_types_query) + generic_org_type_counts = Counter(generic_org_types_query) + # Extract counts for each generic_org_type federal = generic_org_type_counts.get(DomainRequest.OrganizationChoices.FEDERAL, 0) interstate = generic_org_type_counts.get(DomainRequest.OrganizationChoices.INTERSTATE, 0) state_or_territory = generic_org_type_counts.get(DomainRequest.OrganizationChoices.STATE_OR_TERRITORY, 0) @@ -505,21 +532,16 @@ def get_sliced_domains(filter_condition, distinct=False): ] -def get_sliced_requests(filter_condition, distinct=False): +def get_sliced_requests(filter_condition): """Get filtered requests counts sliced by org type and election office.""" # Round trip 1: Get distinct requests based on filter condition requests_count = DomainRequest.objects.filter(**filter_condition).distinct().count() # Round trip 2: Get counts for other slices - if distinct: - generic_org_types_query = ( - DomainRequest.objects.filter(**filter_condition).values_list("generic_org_type", flat=True).distinct() - ) - else: - generic_org_types_query = DomainRequest.objects.filter(**filter_condition).values_list( - "generic_org_type", flat=True - ) + generic_org_types_query = DomainRequest.objects.filter(**filter_condition).values_list( + "generic_org_type", flat=True + ) generic_org_type_counts = Counter(generic_org_types_query) federal = generic_org_type_counts.get(DomainRequest.OrganizationChoices.FEDERAL, 0)