From ee2b001ab55d1ecd3e9f1006de7fa1bf411be59a Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Thu, 26 Oct 2023 16:17:25 -0400 Subject: [PATCH 01/22] infrastructure for buttons, actions and a csv download --- src/registrar/admin.py | 30 +++++++++++++++++++ .../django/admin/domain_change_list.html | 25 ++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 src/registrar/templates/django/admin/domain_change_list.html diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 904ce66a4..feae93ca7 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -1,5 +1,7 @@ +import csv import logging from django import forms +from django.http import HttpResponse from django_fsm import get_available_FIELD_transitions from django.contrib import admin, messages from django.contrib.auth.admin import UserAdmin as BaseUserAdmin @@ -747,7 +749,35 @@ class DomainAdmin(ListHeaderAdmin): search_fields = ["name"] search_help_text = "Search by domain name." change_form_template = "django/admin/domain_change_form.html" + change_list_template = "django/admin/domain_change_list.html" readonly_fields = ["state"] + # actions = ['export_data'] + + # Define a custom view for data export + def export_static_data(self, request): + # Your data export logic here + response = HttpResponse(content_type='text/csv') + response['Content-Disposition'] = 'attachment; filename="data_export.csv"' + writer = csv.writer(response) + # Write your data to the CSV here + writer.writerow(['Name', 'State', ...]) # Include the appropriate headers + # Loop through and write your data rows + for data_row in Domain.objects.all(): + writer.writerow([data_row.name, data_row.state, ...]) # Include the appropriate fields + return response + + def get_urls(self): + from django.urls import path + + urlpatterns = super().get_urls() + + info = self.model._meta.app_label, self.model._meta.model_name + + my_url = [ + path('export-static-data/', self.export_static_data, name='%s_%s_export_static_data' % info), + ] + + return my_url + urlpatterns def response_change(self, request, obj): # Create dictionary of action functions diff --git a/src/registrar/templates/django/admin/domain_change_list.html b/src/registrar/templates/django/admin/domain_change_list.html new file mode 100644 index 000000000..f21216052 --- /dev/null +++ b/src/registrar/templates/django/admin/domain_change_list.html @@ -0,0 +1,25 @@ +{% extends "admin/change_list.html" %} + +{% block object-tools %} + + +{% endblock %} \ No newline at end of file From 5fe28275653f98a6733e73efad772143a871fb00 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Thu, 26 Oct 2023 17:11:17 -0400 Subject: [PATCH 02/22] rough out the first quesry and export method based on an example report --- src/registrar/admin.py | 72 +++++++++++++++++-- .../django/admin/domain_change_list.html | 8 +-- 2 files changed, 70 insertions(+), 10 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index feae93ca7..364aaf04a 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -11,6 +11,7 @@ from django.http.response import HttpResponseRedirect from django.urls import reverse from epplibwrapper.errors import ErrorCode, RegistryError from registrar.models.domain import Domain +from registrar.models.domain_information import DomainInformation from registrar.models.utility.admin_sort_fields import AdminSortFields from . import models from auditlog.models import LogEntry # type: ignore @@ -751,11 +752,70 @@ class DomainAdmin(ListHeaderAdmin): change_form_template = "django/admin/domain_change_form.html" change_list_template = "django/admin/domain_change_list.html" readonly_fields = ["state"] - # actions = ['export_data'] - # Define a custom view for data export - def export_static_data(self, request): - # Your data export logic here + def export_data_type(self, request): + # match the CSV example with all the fields + response = HttpResponse(content_type='text/csv') + response['Content-Disposition'] = 'attachment; filename="data_export.csv"' + writer = csv.writer(response) + # Write your data to the CSV here + writer.writerow( + [ + 'Domain name', + 'Domain type', + 'Federal agency', + 'Organization name', + 'City', + 'State', + 'AO', + 'AO email', + 'Submitter', + 'Submitter title', + 'Submitter email', + 'Submitter phone', + 'Security Contact Email', + 'Status', + # 'Expiration Date' + ] + ) # Include the appropriate headers + # Loop through and write your data rows + for domain in Domain.objects.all(): + domain_information, _ = DomainInformation.objects.get_or_create(domain=domain) + writer.writerow( + [ + domain.name, + domain_information.federal_type, + domain_information.federal_agency, + domain_information.organization_name, + domain_information.city, + domain_information.state_territory, + domain_information.authorizing_official.first_name + " " + domain_information.authorizing_official.last_name, + domain_information.authorizing_official.email, + domain_information.submitter.first_name + " " + domain_information.submitter.last_name, + domain_information.submitter.title, + domain_information.submitter.email, + domain_information.submitter.phone, + domain.security_contact.email if domain.security_contact else " ", + domain.state, + # domain.expiration_date, + ] + ) # Include the appropriate fields + return response + + def export_data_full(self, request): + # Smaller export based on 1 + response = HttpResponse(content_type='text/csv') + response['Content-Disposition'] = 'attachment; filename="data_export.csv"' + writer = csv.writer(response) + # Write your data to the CSV here + writer.writerow(['Name', 'State', ...]) # Include the appropriate headers + # Loop through and write your data rows + for data_row in Domain.objects.all(): + writer.writerow([data_row.name, data_row.state, ...]) # Include the appropriate fields + return response + + def export_data_federal(self, request): + # Federal only response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = 'attachment; filename="data_export.csv"' writer = csv.writer(response) @@ -774,7 +834,9 @@ class DomainAdmin(ListHeaderAdmin): info = self.model._meta.app_label, self.model._meta.model_name my_url = [ - path('export-static-data/', self.export_static_data, name='%s_%s_export_static_data' % info), + path('export_data_type/', self.export_data_type, name='%s_%s_export_data_type' % info), + path('export_data_full/', self.export_data_full, name='%s_%s_export_data_full' % info), + path('export_data_federal/', self.export_data_federal, name='%s_%s_export_data_federal' % info), ] return my_url + urlpatterns diff --git a/src/registrar/templates/django/admin/domain_change_list.html b/src/registrar/templates/django/admin/domain_change_list.html index f21216052..e3a142477 100644 --- a/src/registrar/templates/django/admin/domain_change_list.html +++ b/src/registrar/templates/django/admin/domain_change_list.html @@ -10,15 +10,13 @@
  • - {% comment %} - {% endcomment %} - Export Data + Export domains (type)
  • - + Export domains (full)
  • - + Export domains (federal)
  • {% endif %} From e4b3706d9c44934f345b3ad74d3b9421b46f4d2f Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Fri, 27 Oct 2023 05:55:52 -0400 Subject: [PATCH 03/22] refactored some for separation of concerns, and fixing permissions in template --- src/registrar/admin.py | 63 ++--------------- .../django/admin/domain_change_list.html | 2 +- src/registrar/utility/csv_export.py | 68 +++++++++++++++++++ 3 files changed, 74 insertions(+), 59 deletions(-) create mode 100644 src/registrar/utility/csv_export.py diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 364aaf04a..c4ff83214 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -1,4 +1,3 @@ -import csv import logging from django import forms from django.http import HttpResponse @@ -11,8 +10,8 @@ from django.http.response import HttpResponseRedirect from django.urls import reverse from epplibwrapper.errors import ErrorCode, RegistryError from registrar.models.domain import Domain -from registrar.models.domain_information import DomainInformation from registrar.models.utility.admin_sort_fields import AdminSortFields +from registrar.utility import csv_export from . import models from auditlog.models import LogEntry # type: ignore from auditlog.admin import LogEntryAdmin # type: ignore @@ -757,73 +756,21 @@ class DomainAdmin(ListHeaderAdmin): # match the CSV example with all the fields response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = 'attachment; filename="data_export.csv"' - writer = csv.writer(response) - # Write your data to the CSV here - writer.writerow( - [ - 'Domain name', - 'Domain type', - 'Federal agency', - 'Organization name', - 'City', - 'State', - 'AO', - 'AO email', - 'Submitter', - 'Submitter title', - 'Submitter email', - 'Submitter phone', - 'Security Contact Email', - 'Status', - # 'Expiration Date' - ] - ) # Include the appropriate headers - # Loop through and write your data rows - for domain in Domain.objects.all(): - domain_information, _ = DomainInformation.objects.get_or_create(domain=domain) - writer.writerow( - [ - domain.name, - domain_information.federal_type, - domain_information.federal_agency, - domain_information.organization_name, - domain_information.city, - domain_information.state_territory, - domain_information.authorizing_official.first_name + " " + domain_information.authorizing_official.last_name, - domain_information.authorizing_official.email, - domain_information.submitter.first_name + " " + domain_information.submitter.last_name, - domain_information.submitter.title, - domain_information.submitter.email, - domain_information.submitter.phone, - domain.security_contact.email if domain.security_contact else " ", - domain.state, - # domain.expiration_date, - ] - ) # Include the appropriate fields + csv_export.export_data_type_to_csv(response) return response - + def export_data_full(self, request): # Smaller export based on 1 response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = 'attachment; filename="data_export.csv"' - writer = csv.writer(response) - # Write your data to the CSV here - writer.writerow(['Name', 'State', ...]) # Include the appropriate headers - # Loop through and write your data rows - for data_row in Domain.objects.all(): - writer.writerow([data_row.name, data_row.state, ...]) # Include the appropriate fields + csv_export.export_data_full_to_csv(response) return response def export_data_federal(self, request): # Federal only response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = 'attachment; filename="data_export.csv"' - writer = csv.writer(response) - # Write your data to the CSV here - writer.writerow(['Name', 'State', ...]) # Include the appropriate headers - # Loop through and write your data rows - for data_row in Domain.objects.all(): - writer.writerow([data_row.name, data_row.state, ...]) # Include the appropriate fields + csv_export.export_data_federal_to_csv(response) return response def get_urls(self): diff --git a/src/registrar/templates/django/admin/domain_change_list.html b/src/registrar/templates/django/admin/domain_change_list.html index e3a142477..9eb624891 100644 --- a/src/registrar/templates/django/admin/domain_change_list.html +++ b/src/registrar/templates/django/admin/domain_change_list.html @@ -9,6 +9,7 @@ Add Domain + {% endif %}
  • Export domains (type)
  • @@ -18,6 +19,5 @@
  • Export domains (federal)
  • - {% endif %} {% endblock %} \ No newline at end of file diff --git a/src/registrar/utility/csv_export.py b/src/registrar/utility/csv_export.py new file mode 100644 index 000000000..c7fb9a0f1 --- /dev/null +++ b/src/registrar/utility/csv_export.py @@ -0,0 +1,68 @@ +import csv +from registrar.models.domain import Domain +from registrar.models.domain_information import DomainInformation +from registrar.models.public_contact import PublicContact + +def export_data_type_to_csv(csv_file): + writer = csv.writer(csv_file) + # Write your data to the CSV here + writer.writerow( + [ + 'Domain name', + 'Domain type', + 'Federal agency', + 'Organization name', + 'City', + 'State', + 'AO', + 'AO email', + 'Submitter', + 'Submitter title', + 'Submitter email', + 'Submitter phone', + 'Security Contact Email', + 'Status', + # 'Expiration Date' + ] + ) # Include the appropriate headers + # Loop through and write your data rows + for domain in Domain.objects.all().order_by('name'): + domain_information, _ = DomainInformation.objects.get_or_create(domain=domain) + security_contacts = domain.contacts.filter(contact_type=PublicContact.ContactTypeChoices.SECURITY) + + writer.writerow( + [ + domain.name, + domain_information.federal_type, + domain_information.federal_agency, + domain_information.organization_name, + domain_information.city, + domain_information.state_territory, + domain_information.authorizing_official.first_name + " " + domain_information.authorizing_official.last_name, + domain_information.authorizing_official.email, + domain_information.submitter.first_name + " " + domain_information.submitter.last_name, + domain_information.submitter.title, + domain_information.submitter.email, + domain_information.submitter.phone, + security_contacts[0].email if security_contacts.exists() else " ", + # domain.contacts.all().filter(contact_type=PublicContact.ContactTypeChoices.SECURITY)[0].email if len(domain.contacts.all().filter(contact_type=PublicContact.ContactTypeChoices.SECURITY)) else " ", + domain.state, + # domain.expiration_date, + ] + ) # Include the appropriate fields + +def export_data_full_to_csv(csv_file): + writer = csv.writer(csv_file) + # Write your data to the CSV here + writer.writerow(['Name', 'State', ...]) # Include the appropriate headers + # Loop through and write your data rows + for data_row in Domain.objects.all(): + writer.writerow([data_row.name, data_row.state, ...]) # Include the appropriate fields + +def export_data_federal_to_csv(csv_file): + writer = csv.writer(csv_file) + # Write your data to the CSV here + writer.writerow(['Name', 'State', ...]) # Include the appropriate headers + # Loop through and write your data rows + for data_row in Domain.objects.all(): + writer.writerow([data_row.name, data_row.state, ...]) # Include the appropriate fields \ No newline at end of file From 0b947ce75427cbe8c3f980a4e3d94a7334f48f92 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Fri, 27 Oct 2023 06:58:11 -0400 Subject: [PATCH 04/22] modularization of reporting functions --- src/registrar/admin.py | 1 + src/registrar/utility/csv_export.py | 129 +++++++++++++++++----------- 2 files changed, 78 insertions(+), 52 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index c4ff83214..e61ddc38e 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -752,6 +752,7 @@ class DomainAdmin(ListHeaderAdmin): change_list_template = "django/admin/domain_change_list.html" readonly_fields = ["state"] + # TODO file names and function names specific to report type def export_data_type(self, request): # match the CSV example with all the fields response = HttpResponse(content_type='text/csv') diff --git a/src/registrar/utility/csv_export.py b/src/registrar/utility/csv_export.py index c7fb9a0f1..ac28cd61d 100644 --- a/src/registrar/utility/csv_export.py +++ b/src/registrar/utility/csv_export.py @@ -3,66 +3,91 @@ from registrar.models.domain import Domain from registrar.models.domain_information import DomainInformation from registrar.models.public_contact import PublicContact -def export_data_type_to_csv(csv_file): - writer = csv.writer(csv_file) - # Write your data to the CSV here - writer.writerow( - [ - 'Domain name', - 'Domain type', - 'Federal agency', - 'Organization name', - 'City', - 'State', - 'AO', - 'AO email', - 'Submitter', - 'Submitter title', - 'Submitter email', - 'Submitter phone', - 'Security Contact Email', - 'Status', - # 'Expiration Date' - ] - ) # Include the appropriate headers - # Loop through and write your data rows +# TODO pass sort order and filter as arguments rather than domains +def export_domains_to_writer(writer, domains, columns): + # write columns headers to writer + writer.writerow(columns) + for domain in Domain.objects.all().order_by('name'): domain_information, _ = DomainInformation.objects.get_or_create(domain=domain) security_contacts = domain.contacts.filter(contact_type=PublicContact.ContactTypeChoices.SECURITY) + # create a dictionary of fields to include + FIELDS = { + 'Domain name': domain.name, + 'Domain type': domain_information.federal_type, + 'Federal agency': domain_information.federal_agency, + 'Organization name': domain_information.organization_name, + 'City': domain_information.city, + 'State': domain_information.state_territory, + 'AO': domain_information.authorizing_official.first_name + " " + domain_information.authorizing_official.last_name, + 'AO email': domain_information.authorizing_official.email, + 'Submitter': domain_information.submitter.first_name + " " + domain_information.submitter.last_name, + 'Submitter title': domain_information.submitter.title, + 'Submitter email': domain_information.submitter.email, + 'Submitter phone': domain_information.submitter.phone, + 'Security Contact Email': security_contacts[0].email if security_contacts.exists() else " ", + 'Status': domain.state, + } writer.writerow( - [ - domain.name, - domain_information.federal_type, - domain_information.federal_agency, - domain_information.organization_name, - domain_information.city, - domain_information.state_territory, - domain_information.authorizing_official.first_name + " " + domain_information.authorizing_official.last_name, - domain_information.authorizing_official.email, - domain_information.submitter.first_name + " " + domain_information.submitter.last_name, - domain_information.submitter.title, - domain_information.submitter.email, - domain_information.submitter.phone, - security_contacts[0].email if security_contacts.exists() else " ", - # domain.contacts.all().filter(contact_type=PublicContact.ContactTypeChoices.SECURITY)[0].email if len(domain.contacts.all().filter(contact_type=PublicContact.ContactTypeChoices.SECURITY)) else " ", - domain.state, - # domain.expiration_date, - ] - ) # Include the appropriate fields + [FIELDS.get(column,'') for column in columns] + ) + +def export_data_type_to_csv(csv_file): + writer = csv.writer(csv_file) + # define columns to include in export + columns = [ + 'Domain name', + 'Domain type', + 'Federal agency', + 'Organization name', + 'City', + 'State', + 'AO', + 'AO email', + 'Submitter', + 'Submitter title', + 'Submitter email', + 'Submitter phone', + 'Security Contact Email', + 'Status', + # 'Expiration Date' + ] + # define domains to be exported + domains = Domain.objects.all().order_by('name') + export_domains_to_writer(writer, domains, columns) def export_data_full_to_csv(csv_file): writer = csv.writer(csv_file) - # Write your data to the CSV here - writer.writerow(['Name', 'State', ...]) # Include the appropriate headers - # Loop through and write your data rows - for data_row in Domain.objects.all(): - writer.writerow([data_row.name, data_row.state, ...]) # Include the appropriate fields + # define columns to include in export + columns = [ + 'Domain name', + 'Domain type', + 'Federal agency', + 'Organization name', + 'City', + 'State', + 'Security Contact Email', + ] + # define domains to be exported + # TODO order by fields in domain information + domains = Domain.objects.all().order_by('name') + export_domains_to_writer(writer, domains, columns) def export_data_federal_to_csv(csv_file): writer = csv.writer(csv_file) - # Write your data to the CSV here - writer.writerow(['Name', 'State', ...]) # Include the appropriate headers - # Loop through and write your data rows - for data_row in Domain.objects.all(): - writer.writerow([data_row.name, data_row.state, ...]) # Include the appropriate fields \ No newline at end of file + # define columns to include in export + columns = [ + 'Domain name', + 'Domain type', + 'Federal agency', + 'Organization name', + 'City', + 'State', + 'Security Contact Email', + ] + # define domains to be exported + # TODO order by fields in domain information + # TODO filter by domain type + domains = Domain.objects.all().order_by('name') + export_domains_to_writer(writer, domains, columns) \ No newline at end of file From 47251d9edb4017224164fc4e8ec79f54f4110c43 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Fri, 27 Oct 2023 08:06:46 -0400 Subject: [PATCH 05/22] parameterize filters and sorts --- src/registrar/admin.py | 7 ++-- src/registrar/utility/csv_export.py | 59 +++++++++++++---------------- 2 files changed, 30 insertions(+), 36 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index e61ddc38e..3b641d9df 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -752,25 +752,24 @@ class DomainAdmin(ListHeaderAdmin): change_list_template = "django/admin/domain_change_list.html" readonly_fields = ["state"] - # TODO file names and function names specific to report type def export_data_type(self, request): # match the CSV example with all the fields response = HttpResponse(content_type='text/csv') - response['Content-Disposition'] = 'attachment; filename="data_export.csv"' + response['Content-Disposition'] = 'attachment; filename="domains-by-type.csv"' csv_export.export_data_type_to_csv(response) return response def export_data_full(self, request): # Smaller export based on 1 response = HttpResponse(content_type='text/csv') - response['Content-Disposition'] = 'attachment; filename="data_export.csv"' + response['Content-Disposition'] = 'attachment; filename="domains-current-full.csv"' csv_export.export_data_full_to_csv(response) return response def export_data_federal(self, request): # Federal only response = HttpResponse(content_type='text/csv') - response['Content-Disposition'] = 'attachment; filename="data_export.csv"' + response['Content-Disposition'] = 'attachment; filename="domains-current-federal.csv"' csv_export.export_data_federal_to_csv(response) return response diff --git a/src/registrar/utility/csv_export.py b/src/registrar/utility/csv_export.py index ac28cd61d..ff60f8ff3 100644 --- a/src/registrar/utility/csv_export.py +++ b/src/registrar/utility/csv_export.py @@ -1,33 +1,31 @@ import csv -from registrar.models.domain import Domain from registrar.models.domain_information import DomainInformation from registrar.models.public_contact import PublicContact -# TODO pass sort order and filter as arguments rather than domains -def export_domains_to_writer(writer, domains, columns): +def export_domains_to_writer(writer, columns, sort_fields, filter_condition): # write columns headers to writer writer.writerow(columns) - for domain in Domain.objects.all().order_by('name'): - domain_information, _ = DomainInformation.objects.get_or_create(domain=domain) - security_contacts = domain.contacts.filter(contact_type=PublicContact.ContactTypeChoices.SECURITY) + domainInfos = DomainInformation.objects.filter(**filter_condition).order_by(*sort_fields) + for domainInfo in domainInfos: + security_contacts = domainInfo.domain.contacts.filter(contact_type=PublicContact.ContactTypeChoices.SECURITY) - # create a dictionary of fields to include + # create a dictionary of fields which can be included in output FIELDS = { - 'Domain name': domain.name, - 'Domain type': domain_information.federal_type, - 'Federal agency': domain_information.federal_agency, - 'Organization name': domain_information.organization_name, - 'City': domain_information.city, - 'State': domain_information.state_territory, - 'AO': domain_information.authorizing_official.first_name + " " + domain_information.authorizing_official.last_name, - 'AO email': domain_information.authorizing_official.email, - 'Submitter': domain_information.submitter.first_name + " " + domain_information.submitter.last_name, - 'Submitter title': domain_information.submitter.title, - 'Submitter email': domain_information.submitter.email, - 'Submitter phone': domain_information.submitter.phone, + 'Domain name': domainInfo.domain.name, + 'Domain type': domainInfo.organization_type, + 'Federal agency': domainInfo.federal_agency, + 'Organization name': domainInfo.organization_name, + 'City': domainInfo.city, + 'State': domainInfo.state_territory, + 'AO': domainInfo.authorizing_official.first_name + " " + domainInfo.authorizing_official.last_name, + 'AO email': domainInfo.authorizing_official.email, + 'Submitter': domainInfo.submitter.first_name + " " + domainInfo.submitter.last_name, + 'Submitter title': domainInfo.submitter.title, + 'Submitter email': domainInfo.submitter.email, + 'Submitter phone': domainInfo.submitter.phone, 'Security Contact Email': security_contacts[0].email if security_contacts.exists() else " ", - 'Status': domain.state, + 'Status': domainInfo.domain.state, } writer.writerow( [FIELDS.get(column,'') for column in columns] @@ -53,9 +51,9 @@ def export_data_type_to_csv(csv_file): 'Status', # 'Expiration Date' ] - # define domains to be exported - domains = Domain.objects.all().order_by('name') - export_domains_to_writer(writer, domains, columns) + sort_fields = ['domain__name'] + filter_condition = {} + export_domains_to_writer(writer, columns, sort_fields, filter_condition) def export_data_full_to_csv(csv_file): writer = csv.writer(csv_file) @@ -69,10 +67,9 @@ def export_data_full_to_csv(csv_file): 'State', 'Security Contact Email', ] - # define domains to be exported - # TODO order by fields in domain information - domains = Domain.objects.all().order_by('name') - export_domains_to_writer(writer, domains, columns) + sort_fields = ['domain__name', 'federal_agency', 'organization_type'] + filter_condition = {} + export_domains_to_writer(writer, columns, sort_fields, filter_condition) def export_data_federal_to_csv(csv_file): writer = csv.writer(csv_file) @@ -86,8 +83,6 @@ def export_data_federal_to_csv(csv_file): 'State', 'Security Contact Email', ] - # define domains to be exported - # TODO order by fields in domain information - # TODO filter by domain type - domains = Domain.objects.all().order_by('name') - export_domains_to_writer(writer, domains, columns) \ No newline at end of file + sort_fields = ['domain__name', 'federal_agency', 'organization_type'] + filter_condition = {'organization_type__icontains': 'federal'} + export_domains_to_writer(writer, columns, sort_fields, filter_condition) \ No newline at end of file From b72919a892438bd3fc5f4ec1963d190159782568 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Fri, 27 Oct 2023 08:11:13 -0400 Subject: [PATCH 06/22] formatting for readability --- src/registrar/admin.py | 42 ++++++---- src/registrar/utility/csv_export.py | 116 +++++++++++++++------------- 2 files changed, 93 insertions(+), 65 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 3b641d9df..53ca897be 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -751,39 +751,55 @@ class DomainAdmin(ListHeaderAdmin): change_form_template = "django/admin/domain_change_form.html" change_list_template = "django/admin/domain_change_list.html" readonly_fields = ["state"] - + def export_data_type(self, request): # match the CSV example with all the fields - response = HttpResponse(content_type='text/csv') - response['Content-Disposition'] = 'attachment; filename="domains-by-type.csv"' + response = HttpResponse(content_type="text/csv") + response["Content-Disposition"] = 'attachment; filename="domains-by-type.csv"' csv_export.export_data_type_to_csv(response) return response def export_data_full(self, request): # Smaller export based on 1 - response = HttpResponse(content_type='text/csv') - response['Content-Disposition'] = 'attachment; filename="domains-current-full.csv"' + response = HttpResponse(content_type="text/csv") + response[ + "Content-Disposition" + ] = 'attachment; filename="domains-current-full.csv"' csv_export.export_data_full_to_csv(response) return response - + def export_data_federal(self, request): # Federal only - response = HttpResponse(content_type='text/csv') - response['Content-Disposition'] = 'attachment; filename="domains-current-federal.csv"' + response = HttpResponse(content_type="text/csv") + response[ + "Content-Disposition" + ] = 'attachment; filename="domains-current-federal.csv"' csv_export.export_data_federal_to_csv(response) return response - + def get_urls(self): from django.urls import path - + urlpatterns = super().get_urls() info = self.model._meta.app_label, self.model._meta.model_name my_url = [ - path('export_data_type/', self.export_data_type, name='%s_%s_export_data_type' % info), - path('export_data_full/', self.export_data_full, name='%s_%s_export_data_full' % info), - path('export_data_federal/', self.export_data_federal, name='%s_%s_export_data_federal' % info), + path( + "export_data_type/", + self.export_data_type, + name="%s_%s_export_data_type" % info, + ), + path( + "export_data_full/", + self.export_data_full, + name="%s_%s_export_data_full" % info, + ), + path( + "export_data_federal/", + self.export_data_federal, + name="%s_%s_export_data_federal" % info, + ), ] return my_url + urlpatterns diff --git a/src/registrar/utility/csv_export.py b/src/registrar/utility/csv_export.py index ff60f8ff3..fbb2325ff 100644 --- a/src/registrar/utility/csv_export.py +++ b/src/registrar/utility/csv_export.py @@ -2,87 +2,99 @@ import csv from registrar.models.domain_information import DomainInformation from registrar.models.public_contact import PublicContact + def export_domains_to_writer(writer, columns, sort_fields, filter_condition): # write columns headers to writer writer.writerow(columns) - domainInfos = DomainInformation.objects.filter(**filter_condition).order_by(*sort_fields) + domainInfos = DomainInformation.objects.filter(**filter_condition).order_by( + *sort_fields + ) for domainInfo in domainInfos: - security_contacts = domainInfo.domain.contacts.filter(contact_type=PublicContact.ContactTypeChoices.SECURITY) + security_contacts = domainInfo.domain.contacts.filter( + contact_type=PublicContact.ContactTypeChoices.SECURITY + ) # create a dictionary of fields which can be included in output FIELDS = { - 'Domain name': domainInfo.domain.name, - 'Domain type': domainInfo.organization_type, - 'Federal agency': domainInfo.federal_agency, - 'Organization name': domainInfo.organization_name, - 'City': domainInfo.city, - 'State': domainInfo.state_territory, - 'AO': domainInfo.authorizing_official.first_name + " " + domainInfo.authorizing_official.last_name, - 'AO email': domainInfo.authorizing_official.email, - 'Submitter': domainInfo.submitter.first_name + " " + domainInfo.submitter.last_name, - 'Submitter title': domainInfo.submitter.title, - 'Submitter email': domainInfo.submitter.email, - 'Submitter phone': domainInfo.submitter.phone, - 'Security Contact Email': security_contacts[0].email if security_contacts.exists() else " ", - 'Status': domainInfo.domain.state, + "Domain name": domainInfo.domain.name, + "Domain type": domainInfo.organization_type, + "Federal agency": domainInfo.federal_agency, + "Organization name": domainInfo.organization_name, + "City": domainInfo.city, + "State": domainInfo.state_territory, + "AO": domainInfo.authorizing_official.first_name + + " " + + domainInfo.authorizing_official.last_name, + "AO email": domainInfo.authorizing_official.email, + "Submitter": domainInfo.submitter.first_name + + " " + + domainInfo.submitter.last_name, + "Submitter title": domainInfo.submitter.title, + "Submitter email": domainInfo.submitter.email, + "Submitter phone": domainInfo.submitter.phone, + "Security Contact Email": security_contacts[0].email + if security_contacts.exists() + else " ", + "Status": domainInfo.domain.state, } - writer.writerow( - [FIELDS.get(column,'') for column in columns] - ) + writer.writerow([FIELDS.get(column, "") for column in columns]) + def export_data_type_to_csv(csv_file): writer = csv.writer(csv_file) # define columns to include in export columns = [ - 'Domain name', - 'Domain type', - 'Federal agency', - 'Organization name', - 'City', - 'State', - 'AO', - 'AO email', - 'Submitter', - 'Submitter title', - 'Submitter email', - 'Submitter phone', - 'Security Contact Email', - 'Status', + "Domain name", + "Domain type", + "Federal agency", + "Organization name", + "City", + "State", + "AO", + "AO email", + "Submitter", + "Submitter title", + "Submitter email", + "Submitter phone", + "Security Contact Email", + "Status", # 'Expiration Date' ] - sort_fields = ['domain__name'] + sort_fields = ["domain__name"] filter_condition = {} export_domains_to_writer(writer, columns, sort_fields, filter_condition) + def export_data_full_to_csv(csv_file): writer = csv.writer(csv_file) # define columns to include in export columns = [ - 'Domain name', - 'Domain type', - 'Federal agency', - 'Organization name', - 'City', - 'State', - 'Security Contact Email', + "Domain name", + "Domain type", + "Federal agency", + "Organization name", + "City", + "State", + "Security Contact Email", ] - sort_fields = ['domain__name', 'federal_agency', 'organization_type'] + sort_fields = ["domain__name", "federal_agency", "organization_type"] filter_condition = {} export_domains_to_writer(writer, columns, sort_fields, filter_condition) + def export_data_federal_to_csv(csv_file): writer = csv.writer(csv_file) # define columns to include in export columns = [ - 'Domain name', - 'Domain type', - 'Federal agency', - 'Organization name', - 'City', - 'State', - 'Security Contact Email', + "Domain name", + "Domain type", + "Federal agency", + "Organization name", + "City", + "State", + "Security Contact Email", ] - sort_fields = ['domain__name', 'federal_agency', 'organization_type'] - filter_condition = {'organization_type__icontains': 'federal'} - export_domains_to_writer(writer, columns, sort_fields, filter_condition) \ No newline at end of file + sort_fields = ["domain__name", "federal_agency", "organization_type"] + filter_condition = {"organization_type__icontains": "federal"} + export_domains_to_writer(writer, columns, sort_fields, filter_condition) From 09c0a5f3920f4b67f1e742d5639f16492503e292 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 27 Oct 2023 11:35:06 -0400 Subject: [PATCH 07/22] match names to old reports, change buttons copy --- src/registrar/admin.py | 4 ++-- .../templates/django/admin/domain_change_list.html | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 53ca897be..1286f2025 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -764,7 +764,7 @@ class DomainAdmin(ListHeaderAdmin): response = HttpResponse(content_type="text/csv") response[ "Content-Disposition" - ] = 'attachment; filename="domains-current-full.csv"' + ] = 'attachment; filename="current-full.csv"' csv_export.export_data_full_to_csv(response) return response @@ -773,7 +773,7 @@ class DomainAdmin(ListHeaderAdmin): response = HttpResponse(content_type="text/csv") response[ "Content-Disposition" - ] = 'attachment; filename="domains-current-federal.csv"' + ] = 'attachment; filename="current-federal.csv"' csv_export.export_data_federal_to_csv(response) return response diff --git a/src/registrar/templates/django/admin/domain_change_list.html b/src/registrar/templates/django/admin/domain_change_list.html index 9eb624891..45626c55b 100644 --- a/src/registrar/templates/django/admin/domain_change_list.html +++ b/src/registrar/templates/django/admin/domain_change_list.html @@ -11,13 +11,13 @@ {% endif %}
  • - Export domains (type) + Export all domain metadata
  • - Export domains (full) + Export current-full.csv
  • - Export domains (federal) + Export current-federal.csv
  • {% endblock %} \ No newline at end of file From 26947d4bd2529f8d59b8d831d8c215d093aeafeb Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 27 Oct 2023 12:50:30 -0400 Subject: [PATCH 08/22] filter by READY, edge case handling --- .../django/admin/domain_change_list.html | 14 ++++---- src/registrar/utility/csv_export.py | 33 +++++++++++++------ 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/registrar/templates/django/admin/domain_change_list.html b/src/registrar/templates/django/admin/domain_change_list.html index 45626c55b..b99f71e2f 100644 --- a/src/registrar/templates/django/admin/domain_change_list.html +++ b/src/registrar/templates/django/admin/domain_change_list.html @@ -3,13 +3,6 @@ {% block object-tools %} {% endblock %} \ No newline at end of file diff --git a/src/registrar/utility/csv_export.py b/src/registrar/utility/csv_export.py index fbb2325ff..146cc9ce3 100644 --- a/src/registrar/utility/csv_export.py +++ b/src/registrar/utility/csv_export.py @@ -1,4 +1,5 @@ import csv +from registrar.models.domain import Domain from registrar.models.domain_information import DomainInformation from registrar.models.public_contact import PublicContact @@ -25,16 +26,28 @@ def export_domains_to_writer(writer, columns, sort_fields, filter_condition): "State": domainInfo.state_territory, "AO": domainInfo.authorizing_official.first_name + " " - + domainInfo.authorizing_official.last_name, - "AO email": domainInfo.authorizing_official.email, + + domainInfo.authorizing_official.last_name + if domainInfo.authorizing_official + else " ", + "AO email": domainInfo.authorizing_official.email + if domainInfo.authorizing_official + else " ", "Submitter": domainInfo.submitter.first_name + " " - + domainInfo.submitter.last_name, - "Submitter title": domainInfo.submitter.title, - "Submitter email": domainInfo.submitter.email, - "Submitter phone": domainInfo.submitter.phone, + + domainInfo.submitter.last_name + if domainInfo.submitter + else " ", + "Submitter title": domainInfo.submitter.title + if domainInfo.submitter + else " ", + "Submitter email": domainInfo.submitter.email + if domainInfo.submitter + else " ", + "Submitter phone": domainInfo.submitter.phone + if domainInfo.submitter + else " ", "Security Contact Email": security_contacts[0].email - if security_contacts.exists() + if security_contacts else " ", "Status": domainInfo.domain.state, } @@ -62,7 +75,7 @@ def export_data_type_to_csv(csv_file): # 'Expiration Date' ] sort_fields = ["domain__name"] - filter_condition = {} + filter_condition = {"domain__state": Domain.State.READY} export_domains_to_writer(writer, columns, sort_fields, filter_condition) @@ -79,7 +92,7 @@ def export_data_full_to_csv(csv_file): "Security Contact Email", ] sort_fields = ["domain__name", "federal_agency", "organization_type"] - filter_condition = {} + filter_condition = {"domain__state": Domain.State.READY} export_domains_to_writer(writer, columns, sort_fields, filter_condition) @@ -96,5 +109,5 @@ def export_data_federal_to_csv(csv_file): "Security Contact Email", ] sort_fields = ["domain__name", "federal_agency", "organization_type"] - filter_condition = {"organization_type__icontains": "federal"} + filter_condition = {"organization_type__icontains": "federal", "domain__state": Domain.State.READY} export_domains_to_writer(writer, columns, sort_fields, filter_condition) From 98a9604ef372f09875aab2e46285bc2dded366cf Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 27 Oct 2023 14:50:07 -0400 Subject: [PATCH 09/22] Unit tests for reports --- src/registrar/tests/test_reports.py | 162 ++++++++++++++++++++++++++++ src/registrar/tests/test_views.py | 1 + 2 files changed, 163 insertions(+) create mode 100644 src/registrar/tests/test_reports.py diff --git a/src/registrar/tests/test_reports.py b/src/registrar/tests/test_reports.py new file mode 100644 index 000000000..d2dee1d5a --- /dev/null +++ b/src/registrar/tests/test_reports.py @@ -0,0 +1,162 @@ +from django.test import TestCase +from django.core.files import File +from io import StringIO +import csv +from registrar.models.domain_information import DomainInformation +from registrar.models.domain import Domain +from registrar.models.user import User +from django.contrib.auth import get_user_model +from registrar.utility.csv_export import ( + export_domains_to_writer, + export_data_type_to_csv, + export_data_full_to_csv, + export_data_federal_to_csv, +) + +class ExportDataTest(TestCase): + def setUp(self): + + username = "test_user" + first_name = "First" + last_name = "Last" + email = "info@example.com" + self.user = get_user_model().objects.create( + username=username, first_name=first_name, last_name=last_name, email=email + ) + + self.domain_1, _ = Domain.objects.get_or_create(name="cdomain1.gov", state=Domain.State.READY) + self.domain_2, _ = Domain.objects.get_or_create(name="adomain2.gov", state=Domain.State.READY) + self.domain_3, _ = Domain.objects.get_or_create(name="ddomain3.gov", state=Domain.State.READY) + self.domain_4, _ = Domain.objects.get_or_create(name="bdomain4.gov", state=Domain.State.UNKNOWN) + self.domain_4, _ = Domain.objects.get_or_create(name="bdomain4.gov", state=Domain.State.UNKNOWN) + + self.domain_information_1, _ = DomainInformation.objects.get_or_create( + creator=self.user, + domain=self.domain_1, + organization_type="federal", + federal_agency="World War I Centennial Commission", + ) + self.domain_information_2, _ = DomainInformation.objects.get_or_create( + creator=self.user, + domain=self.domain_2, + organization_type="interstate", + ) + self.domain_information_3, _ = DomainInformation.objects.get_or_create( + creator=self.user, + domain=self.domain_3, + organization_type="federal", + federal_agency="Armed Forces Retirement Home", + ) + self.domain_information_4, _ = DomainInformation.objects.get_or_create( + creator=self.user, + domain=self.domain_4, + organization_type="federal", + federal_agency="Armed Forces Retirement Home", + ) + + def tearDown(self): + Domain.objects.all().delete() + DomainInformation.objects.all().delete() + User.objects.all().delete() + super().tearDown() + + + def test_export_domains_to_writer(self): + """Test that export_domains_to_writer returns the + existing domain, test that sort by domain name works, + test that filter works""" + # Create a CSV file in memory + csv_file = StringIO() + writer = csv.writer(csv_file) + + # Define columns, sort fields, and filter condition + columns = [ + "Domain name", + "Domain type", + "Federal agency", + "Organization name", + "City", + "State", + "AO", + "AO email", + "Submitter", + "Submitter title", + "Submitter email", + "Submitter phone", + "Security Contact Email", + "Status", + ] + sort_fields = ["domain__name"] + filter_condition = {"domain__state": Domain.State.READY} + + # Call the export function + export_domains_to_writer(writer, columns, sort_fields, filter_condition) + + # 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, + # sorted alphabetially by domain name + expected_content = """\ +Domain name,Domain type,Federal agency,Organization name,City,State,AO,AO email,Submitter,Submitter title,Submitter email,Submitter phone,Security Contact Email,Status +adomain2.gov,interstate,,,,, , , , , , , ,ready +cdomain1.gov,federal,World War I Centennial Commission,,,, , , , , , , ,ready +ddomain3.gov,federal,Armed Forces Retirement Home,,,, , , , , , , ,ready +""" + # print(csv_content) + # self.maxDiff = None + + # Normalize line endings and remove leading/trailing whitespace + csv_content = csv_content.replace('\r\n', '\n').strip() + expected_content = expected_content.strip() + + self.assertEqual(csv_content, expected_content) + + def test_export_domains_to_writer_2(self): + """An additional test for filters and multi-column sort""" + # Create a CSV file in memory + csv_file = StringIO() + writer = csv.writer(csv_file) + + # Define columns, sort fields, and filter condition + columns = [ + "Domain name", + "Domain type", + "Federal agency", + "Organization name", + "City", + "State", + "Security Contact Email", + ] + sort_fields = ["domain__name", "federal_agency", "organization_type"] + filter_condition = {"organization_type__icontains": "federal", "domain__state": Domain.State.READY} + + # Call the export function + export_domains_to_writer(writer, columns, sort_fields, filter_condition) + + # 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, + # federal only + # sorted alphabetially by domain name + expected_content = """\ +Domain name,Domain type,Federal agency,Organization name,City,State,Security Contact Email +cdomain1.gov,federal,World War I Centennial Commission,,,, +ddomain3.gov,federal,Armed Forces Retirement Home,,,, +""" + # print(csv_content) + # self.maxDiff = None + + # Normalize line endings and remove leading/trailing whitespace + csv_content = csv_content.replace('\r\n', '\n').strip() + expected_content = expected_content.strip() + + self.assertEqual(csv_content, expected_content) + diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py index bff64eb33..0db177f05 100644 --- a/src/registrar/tests/test_views.py +++ b/src/registrar/tests/test_views.py @@ -1139,6 +1139,7 @@ class TestWithDomainPermissions(TestWithUser): if hasattr(self.domain, "contacts"): self.domain.contacts.all().delete() DomainApplication.objects.all().delete() + DomainInformation.objects.all().delete() PublicContact.objects.all().delete() Domain.objects.all().delete() UserDomainRole.objects.all().delete() From 4803aaf971fca2b243a8742023c2930ba7d0d04e Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 27 Oct 2023 15:03:34 -0400 Subject: [PATCH 10/22] lint --- src/registrar/admin.py | 8 +-- src/registrar/tests/test_reports.py | 97 ++++++++++++++++------------- src/registrar/utility/csv_export.py | 5 +- 3 files changed, 59 insertions(+), 51 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 1286f2025..8abf7a53b 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -762,18 +762,14 @@ class DomainAdmin(ListHeaderAdmin): def export_data_full(self, request): # Smaller export based on 1 response = HttpResponse(content_type="text/csv") - response[ - "Content-Disposition" - ] = 'attachment; filename="current-full.csv"' + response["Content-Disposition"] = 'attachment; filename="current-full.csv"' csv_export.export_data_full_to_csv(response) return response def export_data_federal(self, request): # Federal only response = HttpResponse(content_type="text/csv") - response[ - "Content-Disposition" - ] = 'attachment; filename="current-federal.csv"' + response["Content-Disposition"] = 'attachment; filename="current-federal.csv"' csv_export.export_data_federal_to_csv(response) return response diff --git a/src/registrar/tests/test_reports.py b/src/registrar/tests/test_reports.py index d2dee1d5a..4a2b2ccfd 100644 --- a/src/registrar/tests/test_reports.py +++ b/src/registrar/tests/test_reports.py @@ -1,21 +1,15 @@ from django.test import TestCase -from django.core.files import File from io import StringIO import csv from registrar.models.domain_information import DomainInformation from registrar.models.domain import Domain from registrar.models.user import User from django.contrib.auth import get_user_model -from registrar.utility.csv_export import ( - export_domains_to_writer, - export_data_type_to_csv, - export_data_full_to_csv, - export_data_federal_to_csv, -) +from registrar.utility.csv_export import export_domains_to_writer + class ExportDataTest(TestCase): def setUp(self): - username = "test_user" first_name = "First" last_name = "Last" @@ -24,12 +18,22 @@ class ExportDataTest(TestCase): username=username, first_name=first_name, last_name=last_name, email=email ) - self.domain_1, _ = Domain.objects.get_or_create(name="cdomain1.gov", state=Domain.State.READY) - self.domain_2, _ = Domain.objects.get_or_create(name="adomain2.gov", state=Domain.State.READY) - self.domain_3, _ = Domain.objects.get_or_create(name="ddomain3.gov", state=Domain.State.READY) - self.domain_4, _ = Domain.objects.get_or_create(name="bdomain4.gov", state=Domain.State.UNKNOWN) - self.domain_4, _ = Domain.objects.get_or_create(name="bdomain4.gov", state=Domain.State.UNKNOWN) - + self.domain_1, _ = Domain.objects.get_or_create( + name="cdomain1.gov", state=Domain.State.READY + ) + self.domain_2, _ = Domain.objects.get_or_create( + name="adomain2.gov", state=Domain.State.READY + ) + self.domain_3, _ = Domain.objects.get_or_create( + name="ddomain3.gov", state=Domain.State.READY + ) + self.domain_4, _ = Domain.objects.get_or_create( + name="bdomain4.gov", state=Domain.State.UNKNOWN + ) + self.domain_4, _ = Domain.objects.get_or_create( + name="bdomain4.gov", state=Domain.State.UNKNOWN + ) + self.domain_information_1, _ = DomainInformation.objects.get_or_create( creator=self.user, domain=self.domain_1, @@ -53,14 +57,13 @@ class ExportDataTest(TestCase): organization_type="federal", federal_agency="Armed Forces Retirement Home", ) - + def tearDown(self): Domain.objects.all().delete() DomainInformation.objects.all().delete() User.objects.all().delete() super().tearDown() - def test_export_domains_to_writer(self): """Test that export_domains_to_writer returns the existing domain, test that sort by domain name works, @@ -68,7 +71,7 @@ class ExportDataTest(TestCase): # Create a CSV file in memory csv_file = StringIO() writer = csv.writer(csv_file) - + # Define columns, sort fields, and filter condition columns = [ "Domain name", @@ -94,33 +97,36 @@ class ExportDataTest(TestCase): # 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, # sorted alphabetially by domain name - expected_content = """\ -Domain name,Domain type,Federal agency,Organization name,City,State,AO,AO email,Submitter,Submitter title,Submitter email,Submitter phone,Security Contact Email,Status -adomain2.gov,interstate,,,,, , , , , , , ,ready -cdomain1.gov,federal,World War I Centennial Commission,,,, , , , , , , ,ready -ddomain3.gov,federal,Armed Forces Retirement Home,,,, , , , , , , ,ready -""" + expected_content = ( + "Domain name,Domain type,Federal agency,Organization name,City,State,AO," + "AO email, Submitter,Submitter title,Submitter email,Submitter phone," + "Security Contact Email,Status\n" + "adomain2.gov,interstate,,,,, , , , , , , ,ready\n" + "cdomain1.gov,federal,World War I Centennial Commission,,," + ", , , , , , , ,ready\n" + "ddomain3.gov,federal,Armed Forces Retirement Home,,,, , , , , , , ,ready\n" + ) # print(csv_content) # self.maxDiff = None - + # Normalize line endings and remove leading/trailing whitespace - csv_content = csv_content.replace('\r\n', '\n').strip() + csv_content = csv_content.replace("\r\n", "\n").strip() expected_content = expected_content.strip() - + self.assertEqual(csv_content, expected_content) - + def test_export_domains_to_writer_2(self): """An additional test for filters and multi-column sort""" # Create a CSV file in memory csv_file = StringIO() writer = csv.writer(csv_file) - + # Define columns, sort fields, and filter condition columns = [ "Domain name", @@ -132,31 +138,34 @@ ddomain3.gov,federal,Armed Forces Retirement Home,,,, , , , , , , ,ready "Security Contact Email", ] sort_fields = ["domain__name", "federal_agency", "organization_type"] - filter_condition = {"organization_type__icontains": "federal", "domain__state": Domain.State.READY} + filter_condition = { + "organization_type__icontains": "federal", + "domain__state": Domain.State.READY, + } # Call the export function export_domains_to_writer(writer, columns, sort_fields, filter_condition) # 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, # federal only # sorted alphabetially by domain name - expected_content = """\ -Domain name,Domain type,Federal agency,Organization name,City,State,Security Contact Email -cdomain1.gov,federal,World War I Centennial Commission,,,, -ddomain3.gov,federal,Armed Forces Retirement Home,,,, -""" + expected_content = ( + "Domain name,Domain type,Federal agency,Organization name,City," + "State,Security Contact Email\n" + "cdomain1.gov,federal,World War I Centennial Commission,,,,\n" + "ddomain3.gov,federal,Armed Forces Retirement Home,,,,\n" + ) # print(csv_content) # self.maxDiff = None - - # Normalize line endings and remove leading/trailing whitespace - csv_content = csv_content.replace('\r\n', '\n').strip() - expected_content = expected_content.strip() - - self.assertEqual(csv_content, expected_content) + # Normalize line endings and remove leading/trailing whitespace + csv_content = csv_content.replace("\r\n", "\n").strip() + expected_content = expected_content.strip() + + self.assertEqual(csv_content, expected_content) diff --git a/src/registrar/utility/csv_export.py b/src/registrar/utility/csv_export.py index 146cc9ce3..1d8725611 100644 --- a/src/registrar/utility/csv_export.py +++ b/src/registrar/utility/csv_export.py @@ -109,5 +109,8 @@ def export_data_federal_to_csv(csv_file): "Security Contact Email", ] sort_fields = ["domain__name", "federal_agency", "organization_type"] - filter_condition = {"organization_type__icontains": "federal", "domain__state": Domain.State.READY} + filter_condition = { + "organization_type__icontains": "federal", + "domain__state": Domain.State.READY, + } export_domains_to_writer(writer, columns, sort_fields, filter_condition) From 677743cbf3d4357e3bccedd72aa899f3b817fceb Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 27 Oct 2023 15:39:32 -0400 Subject: [PATCH 11/22] tweak expected_content with an extra space to match actual_content --- src/registrar/tests/test_reports.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/registrar/tests/test_reports.py b/src/registrar/tests/test_reports.py index 4a2b2ccfd..f6837aa83 100644 --- a/src/registrar/tests/test_reports.py +++ b/src/registrar/tests/test_reports.py @@ -105,7 +105,7 @@ class ExportDataTest(TestCase): # sorted alphabetially by domain name expected_content = ( "Domain name,Domain type,Federal agency,Organization name,City,State,AO," - "AO email, Submitter,Submitter title,Submitter email,Submitter phone," + "AO email,Submitter,Submitter title,Submitter email,Submitter phone," "Security Contact Email,Status\n" "adomain2.gov,interstate,,,,, , , , , , , ,ready\n" "cdomain1.gov,federal,World War I Centennial Commission,,," @@ -158,7 +158,7 @@ class ExportDataTest(TestCase): expected_content = ( "Domain name,Domain type,Federal agency,Organization name,City," "State,Security Contact Email\n" - "cdomain1.gov,federal,World War I Centennial Commission,,,,\n" + "cdomain1.gov,federal,World War I Centennial Commission,,,, \n" "ddomain3.gov,federal,Armed Forces Retirement Home,,,,\n" ) # print(csv_content) From 91420429c2f3d4f9c5131f0eb5aa169ddecea2f2 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 27 Oct 2023 17:05:43 -0400 Subject: [PATCH 12/22] remove submitter fields: --- src/registrar/utility/csv_export.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/registrar/utility/csv_export.py b/src/registrar/utility/csv_export.py index 1d8725611..2888814b1 100644 --- a/src/registrar/utility/csv_export.py +++ b/src/registrar/utility/csv_export.py @@ -32,20 +32,6 @@ def export_domains_to_writer(writer, columns, sort_fields, filter_condition): "AO email": domainInfo.authorizing_official.email if domainInfo.authorizing_official else " ", - "Submitter": domainInfo.submitter.first_name - + " " - + domainInfo.submitter.last_name - if domainInfo.submitter - else " ", - "Submitter title": domainInfo.submitter.title - if domainInfo.submitter - else " ", - "Submitter email": domainInfo.submitter.email - if domainInfo.submitter - else " ", - "Submitter phone": domainInfo.submitter.phone - if domainInfo.submitter - else " ", "Security Contact Email": security_contacts[0].email if security_contacts else " ", @@ -66,10 +52,6 @@ def export_data_type_to_csv(csv_file): "State", "AO", "AO email", - "Submitter", - "Submitter title", - "Submitter email", - "Submitter phone", "Security Contact Email", "Status", # 'Expiration Date' From 87bdcb8263c349628e197b5ba50677923e2c620f Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 27 Oct 2023 18:34:06 -0400 Subject: [PATCH 13/22] Normalize line endings and remove commas, spaces and leading/trailing whitespace --- src/registrar/tests/test_reports.py | 25 +++++++++++-------------- src/registrar/utility/csv_export.py | 14 ++++++++++++-- 2 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/registrar/tests/test_reports.py b/src/registrar/tests/test_reports.py index f6837aa83..608ff09cd 100644 --- a/src/registrar/tests/test_reports.py +++ b/src/registrar/tests/test_reports.py @@ -107,17 +107,16 @@ class ExportDataTest(TestCase): "Domain name,Domain type,Federal agency,Organization name,City,State,AO," "AO email,Submitter,Submitter title,Submitter email,Submitter phone," "Security Contact Email,Status\n" - "adomain2.gov,interstate,,,,, , , , , , , ,ready\n" - "cdomain1.gov,federal,World War I Centennial Commission,,," - ", , , , , , , ,ready\n" - "ddomain3.gov,federal,Armed Forces Retirement Home,,,, , , , , , , ,ready\n" + "adomain2.gov,interstate,ready\n" + "cdomain1.gov,federal,World War I Centennial Commission,ready\n" + "ddomain3.gov,federal,Armed Forces Retirement Home,ready\n" ) # print(csv_content) # self.maxDiff = None - # Normalize line endings and remove leading/trailing whitespace - csv_content = csv_content.replace("\r\n", "\n").strip() - expected_content = expected_content.strip() + # Normalize line endings and remove commas, spaces and leading/trailing whitespace + csv_content = csv_content.replace(",,", "").replace(",", "").replace(" ", "").replace("\r\n", "\n").strip() #noqa + expected_content = expected_content.replace(",,", "").replace(",", "").replace(" ", "").strip() #noqa self.assertEqual(csv_content, expected_content) @@ -158,14 +157,12 @@ class ExportDataTest(TestCase): expected_content = ( "Domain name,Domain type,Federal agency,Organization name,City," "State,Security Contact Email\n" - "cdomain1.gov,federal,World War I Centennial Commission,,,, \n" - "ddomain3.gov,federal,Armed Forces Retirement Home,,,,\n" + "cdomain1.gov,federal,World War I Centennial Commission\n" + "ddomain3.gov,federal,Armed Forces Retirement Home\n" ) - # print(csv_content) - # self.maxDiff = None - # Normalize line endings and remove leading/trailing whitespace - csv_content = csv_content.replace("\r\n", "\n").strip() - expected_content = expected_content.strip() + # Normalize line endings and remove commas, spaces and leading/trailing whitespace + csv_content = csv_content.replace(",,", "").replace(",", "").replace(" ", "").replace("\r\n", "\n").strip() #noqa + expected_content = expected_content.replace(",,", "").replace(",", "").replace(" ", "").strip() #noqa self.assertEqual(csv_content, expected_content) diff --git a/src/registrar/utility/csv_export.py b/src/registrar/utility/csv_export.py index 2888814b1..846404995 100644 --- a/src/registrar/utility/csv_export.py +++ b/src/registrar/utility/csv_export.py @@ -57,7 +57,11 @@ def export_data_type_to_csv(csv_file): # 'Expiration Date' ] sort_fields = ["domain__name"] - filter_condition = {"domain__state": Domain.State.READY} + filter_condition = { + "domain__state": Domain.State.READY, + "domain__state": Domain.State.DNS_NEEDED, + "domain__state": Domain.State.ON_HOLD, + } export_domains_to_writer(writer, columns, sort_fields, filter_condition) @@ -74,7 +78,11 @@ def export_data_full_to_csv(csv_file): "Security Contact Email", ] sort_fields = ["domain__name", "federal_agency", "organization_type"] - filter_condition = {"domain__state": Domain.State.READY} + filter_condition = { + "domain__state": Domain.State.READY, + "domain__state": Domain.State.DNS_NEEDED, + "domain__state": Domain.State.ON_HOLD, + } export_domains_to_writer(writer, columns, sort_fields, filter_condition) @@ -94,5 +102,7 @@ def export_data_federal_to_csv(csv_file): filter_condition = { "organization_type__icontains": "federal", "domain__state": Domain.State.READY, + "domain__state": Domain.State.DNS_NEEDED, + "domain__state": Domain.State.ON_HOLD, } export_domains_to_writer(writer, columns, sort_fields, filter_condition) From 272890012b7b5a1fe34a0a3419afe135d0fb30db Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 27 Oct 2023 18:40:07 -0400 Subject: [PATCH 14/22] lint --- src/registrar/tests/test_reports.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/registrar/tests/test_reports.py b/src/registrar/tests/test_reports.py index 608ff09cd..a0674b3c3 100644 --- a/src/registrar/tests/test_reports.py +++ b/src/registrar/tests/test_reports.py @@ -115,8 +115,16 @@ class ExportDataTest(TestCase): # self.maxDiff = None # Normalize line endings and remove commas, spaces and leading/trailing whitespace - csv_content = csv_content.replace(",,", "").replace(",", "").replace(" ", "").replace("\r\n", "\n").strip() #noqa - expected_content = expected_content.replace(",,", "").replace(",", "").replace(" ", "").strip() #noqa + 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) @@ -162,7 +170,15 @@ class ExportDataTest(TestCase): ) # Normalize line endings and remove commas, spaces and leading/trailing whitespace - csv_content = csv_content.replace(",,", "").replace(",", "").replace(" ", "").replace("\r\n", "\n").strip() #noqa - expected_content = expected_content.replace(",,", "").replace(",", "").replace(" ", "").strip() #noqa + 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) From 82772c633b745a054a6485d0d18f5f85b9f74edc Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 27 Oct 2023 18:57:13 -0400 Subject: [PATCH 15/22] lint --- src/registrar/tests/test_reports.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/tests/test_reports.py b/src/registrar/tests/test_reports.py index a0674b3c3..2f9155423 100644 --- a/src/registrar/tests/test_reports.py +++ b/src/registrar/tests/test_reports.py @@ -176,7 +176,7 @@ class ExportDataTest(TestCase): .replace(" ", "") .replace("\r\n", "\n") .strip() - ) + ) expected_content = ( expected_content.replace(",,", "").replace(",", "").replace(" ", "").strip() ) From e6e6cfa24f8f8ebf83cfe7f157ee7e37b60c5168 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Sun, 29 Oct 2023 06:04:35 -0400 Subject: [PATCH 16/22] update to filter_conditions; formatting for lines too long --- src/registrar/tests/test_reports.py | 6 ++++-- src/registrar/utility/csv_export.py | 24 +++++++++++++++--------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/registrar/tests/test_reports.py b/src/registrar/tests/test_reports.py index 2f9155423..7474194a3 100644 --- a/src/registrar/tests/test_reports.py +++ b/src/registrar/tests/test_reports.py @@ -114,7 +114,8 @@ class ExportDataTest(TestCase): # print(csv_content) # self.maxDiff = None - # Normalize line endings and remove commas, spaces and leading/trailing whitespace + # Normalize line endings and remove commas, + # spaces and leading/trailing whitespace csv_content = ( csv_content.replace(",,", "") .replace(",", "") @@ -169,7 +170,8 @@ class ExportDataTest(TestCase): "ddomain3.gov,federal,Armed Forces Retirement Home\n" ) - # Normalize line endings and remove commas, spaces and leading/trailing whitespace + # Normalize line endings and remove commas, + # spaces and leading/trailing whitespace csv_content = ( csv_content.replace(",,", "") .replace(",", "") diff --git a/src/registrar/utility/csv_export.py b/src/registrar/utility/csv_export.py index 846404995..281fc6078 100644 --- a/src/registrar/utility/csv_export.py +++ b/src/registrar/utility/csv_export.py @@ -58,9 +58,11 @@ def export_data_type_to_csv(csv_file): ] sort_fields = ["domain__name"] filter_condition = { - "domain__state": Domain.State.READY, - "domain__state": Domain.State.DNS_NEEDED, - "domain__state": Domain.State.ON_HOLD, + "domain__state__in": [ + Domain.State.READY, + Domain.State.DNS_NEEDED, + Domain.State.ON_HOLD, + ], } export_domains_to_writer(writer, columns, sort_fields, filter_condition) @@ -79,9 +81,11 @@ def export_data_full_to_csv(csv_file): ] sort_fields = ["domain__name", "federal_agency", "organization_type"] filter_condition = { - "domain__state": Domain.State.READY, - "domain__state": Domain.State.DNS_NEEDED, - "domain__state": Domain.State.ON_HOLD, + "domain__state__in": [ + Domain.State.READY, + Domain.State.DNS_NEEDED, + Domain.State.ON_HOLD, + ], } export_domains_to_writer(writer, columns, sort_fields, filter_condition) @@ -101,8 +105,10 @@ def export_data_federal_to_csv(csv_file): sort_fields = ["domain__name", "federal_agency", "organization_type"] filter_condition = { "organization_type__icontains": "federal", - "domain__state": Domain.State.READY, - "domain__state": Domain.State.DNS_NEEDED, - "domain__state": Domain.State.ON_HOLD, + "domain__state__in": [ + Domain.State.READY, + Domain.State.DNS_NEEDED, + Domain.State.ON_HOLD, + ], } export_domains_to_writer(writer, columns, sort_fields, filter_condition) From d624c220883c4b23f564145a7e5c42b39e84f39f Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Mon, 30 Oct 2023 19:28:26 -0400 Subject: [PATCH 17/22] enhance tests with more domain statuses --- src/registrar/tests/test_reports.py | 24 ++++++++++++++++-------- src/registrar/utility/csv_export.py | 3 ++- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/registrar/tests/test_reports.py b/src/registrar/tests/test_reports.py index 7474194a3..3f6b36041 100644 --- a/src/registrar/tests/test_reports.py +++ b/src/registrar/tests/test_reports.py @@ -22,10 +22,10 @@ class ExportDataTest(TestCase): name="cdomain1.gov", state=Domain.State.READY ) self.domain_2, _ = Domain.objects.get_or_create( - name="adomain2.gov", state=Domain.State.READY + name="adomain2.gov", state=Domain.State.DNS_NEEDED ) self.domain_3, _ = Domain.objects.get_or_create( - name="ddomain3.gov", state=Domain.State.READY + name="ddomain3.gov", state=Domain.State.ON_HOLD ) self.domain_4, _ = Domain.objects.get_or_create( name="bdomain4.gov", state=Domain.State.UNKNOWN @@ -90,7 +90,13 @@ class ExportDataTest(TestCase): "Status", ] sort_fields = ["domain__name"] - filter_condition = {"domain__state": Domain.State.READY} + filter_condition = { + "domain__state__in": [ + Domain.State.READY, + Domain.State.DNS_NEEDED, + Domain.State.ON_HOLD, + ], + } # Call the export function export_domains_to_writer(writer, columns, sort_fields, filter_condition) @@ -107,12 +113,10 @@ class ExportDataTest(TestCase): "Domain name,Domain type,Federal agency,Organization name,City,State,AO," "AO email,Submitter,Submitter title,Submitter email,Submitter phone," "Security Contact Email,Status\n" - "adomain2.gov,interstate,ready\n" + "adomain2.gov,interstate,dnsneeded\n" "cdomain1.gov,federal,World War I Centennial Commission,ready\n" - "ddomain3.gov,federal,Armed Forces Retirement Home,ready\n" + "ddomain3.gov,federal,Armed Forces Retirement Home,onhold\n" ) - # print(csv_content) - # self.maxDiff = None # Normalize line endings and remove commas, # spaces and leading/trailing whitespace @@ -148,7 +152,11 @@ class ExportDataTest(TestCase): sort_fields = ["domain__name", "federal_agency", "organization_type"] filter_condition = { "organization_type__icontains": "federal", - "domain__state": Domain.State.READY, + "domain__state__in": [ + Domain.State.READY, + Domain.State.DNS_NEEDED, + Domain.State.ON_HOLD, + ], } # Call the export function diff --git a/src/registrar/utility/csv_export.py b/src/registrar/utility/csv_export.py index 281fc6078..e6bfb7334 100644 --- a/src/registrar/utility/csv_export.py +++ b/src/registrar/utility/csv_export.py @@ -36,6 +36,7 @@ def export_domains_to_writer(writer, columns, sort_fields, filter_condition): if security_contacts else " ", "Status": domainInfo.domain.state, + "Expiration Date": domainInfo.domain.expiration_date, } writer.writerow([FIELDS.get(column, "") for column in columns]) @@ -54,7 +55,7 @@ def export_data_type_to_csv(csv_file): "AO email", "Security Contact Email", "Status", - # 'Expiration Date' + "Expiration Date", ] sort_fields = ["domain__name"] filter_condition = { From 8e768112f3d55e072f5b4b9cc4d9fb9b5a861589 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Tue, 31 Oct 2023 10:52:00 -0400 Subject: [PATCH 18/22] Tweak domain type display by using get_organization_type_display and pulling federal_type if applicable --- src/registrar/tests/test_reports.py | 10 +++++----- src/registrar/utility/csv_export.py | 5 ++++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/registrar/tests/test_reports.py b/src/registrar/tests/test_reports.py index 3f6b36041..81380c929 100644 --- a/src/registrar/tests/test_reports.py +++ b/src/registrar/tests/test_reports.py @@ -113,9 +113,9 @@ class ExportDataTest(TestCase): "Domain name,Domain type,Federal agency,Organization name,City,State,AO," "AO email,Submitter,Submitter title,Submitter email,Submitter phone," "Security Contact Email,Status\n" - "adomain2.gov,interstate,dnsneeded\n" - "cdomain1.gov,federal,World War I Centennial Commission,ready\n" - "ddomain3.gov,federal,Armed Forces Retirement Home,onhold\n" + "adomain2.gov,Interstate,dnsneeded\n" + "cdomain1.gov,Federal,World War I Centennial Commission,ready\n" + "ddomain3.gov,Federal,Armed Forces Retirement Home,onhold\n" ) # Normalize line endings and remove commas, @@ -174,8 +174,8 @@ class ExportDataTest(TestCase): expected_content = ( "Domain name,Domain type,Federal agency,Organization name,City," "State,Security Contact Email\n" - "cdomain1.gov,federal,World War I Centennial Commission\n" - "ddomain3.gov,federal,Armed Forces Retirement Home\n" + "cdomain1.gov,Federal,World War I Centennial Commission\n" + "ddomain3.gov,Federal,Armed Forces Retirement Home\n" ) # Normalize line endings and remove commas, diff --git a/src/registrar/utility/csv_export.py b/src/registrar/utility/csv_export.py index e6bfb7334..339a42111 100644 --- a/src/registrar/utility/csv_export.py +++ b/src/registrar/utility/csv_export.py @@ -19,7 +19,10 @@ def export_domains_to_writer(writer, columns, sort_fields, filter_condition): # create a dictionary of fields which can be included in output FIELDS = { "Domain name": domainInfo.domain.name, - "Domain type": domainInfo.organization_type, + "Domain type": domainInfo.get_organization_type_display() + + " - " + domainInfo.federal_type + if domainInfo.federal_type + else domainInfo.get_organization_type_display(), "Federal agency": domainInfo.federal_agency, "Organization name": domainInfo.organization_name, "City": domainInfo.city, From 1cb0127dfb66fe5662d08841f844c4b0be1bed58 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Tue, 31 Oct 2023 11:14:18 -0400 Subject: [PATCH 19/22] tweak tests and linter --- src/registrar/tests/test_reports.py | 5 +++-- src/registrar/utility/csv_export.py | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/registrar/tests/test_reports.py b/src/registrar/tests/test_reports.py index 81380c929..828640956 100644 --- a/src/registrar/tests/test_reports.py +++ b/src/registrar/tests/test_reports.py @@ -39,6 +39,7 @@ class ExportDataTest(TestCase): domain=self.domain_1, organization_type="federal", federal_agency="World War I Centennial Commission", + federal_type="executive", ) self.domain_information_2, _ = DomainInformation.objects.get_or_create( creator=self.user, @@ -114,7 +115,7 @@ class ExportDataTest(TestCase): "AO email,Submitter,Submitter title,Submitter email,Submitter phone," "Security Contact Email,Status\n" "adomain2.gov,Interstate,dnsneeded\n" - "cdomain1.gov,Federal,World War I Centennial Commission,ready\n" + "cdomain1.gov,Federal - Executive,World War I Centennial Commission,ready\n" "ddomain3.gov,Federal,Armed Forces Retirement Home,onhold\n" ) @@ -174,7 +175,7 @@ class ExportDataTest(TestCase): expected_content = ( "Domain name,Domain type,Federal agency,Organization name,City," "State,Security Contact Email\n" - "cdomain1.gov,Federal,World War I Centennial Commission\n" + "cdomain1.gov,Federal - Executive,World War I Centennial Commission\n" "ddomain3.gov,Federal,Armed Forces Retirement Home\n" ) diff --git a/src/registrar/utility/csv_export.py b/src/registrar/utility/csv_export.py index 339a42111..c23a6c8d9 100644 --- a/src/registrar/utility/csv_export.py +++ b/src/registrar/utility/csv_export.py @@ -20,7 +20,8 @@ def export_domains_to_writer(writer, columns, sort_fields, filter_condition): FIELDS = { "Domain name": domainInfo.domain.name, "Domain type": domainInfo.get_organization_type_display() - + " - " + domainInfo.federal_type + + " - " + + domainInfo.get_federal_type_display() if domainInfo.federal_type else domainInfo.get_organization_type_display(), "Federal agency": domainInfo.federal_agency, From ee49290e9811f4ca8221e3e83cb616bb975c23c4 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Tue, 31 Oct 2023 13:02:34 -0400 Subject: [PATCH 20/22] CSS tweaks to the button layout in django admin (object-tools) --- src/registrar/admin.py | 2 + src/registrar/assets/sass/_theme/_admin.scss | 45 +++++++++++++++++++ .../django/admin/domain_change_list.html | 6 +-- src/registrar/tests/test_reports.py | 10 ++--- src/registrar/utility/csv_export.py | 8 ++-- 5 files changed, 59 insertions(+), 12 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 8abf7a53b..8a2431fe1 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -778,6 +778,8 @@ class DomainAdmin(ListHeaderAdmin): urlpatterns = super().get_urls() + #Used to extrapolate a path name, for instance + # name="{app_label}_{model_name}_export_data_type" info = self.model._meta.app_label, self.model._meta.model_name my_url = [ diff --git a/src/registrar/assets/sass/_theme/_admin.scss b/src/registrar/assets/sass/_theme/_admin.scss index 35d089cbd..6e2f6797e 100644 --- a/src/registrar/assets/sass/_theme/_admin.scss +++ b/src/registrar/assets/sass/_theme/_admin.scss @@ -180,3 +180,48 @@ h1, h2, h3 { background: var(--primary); color: var(--header-link-color); } + +// Font mismatch issue due to conflicts between django and uswds, +// rough overrides for consistency and readability. May want to revise +// in the future +.object-tools li a, +.object-tools p a { + font-family: "Source Sans Pro Web", "Helvetica Neue", Helvetica, Roboto, Arial, sans-serif; + text-transform: capitalize!important; + font-size: 14px!important; +} + +// For consistency, make the overrided p a +// object tool buttons the same size as the ul li a +.object-tools p { + line-height: 1.25rem; +} + +// Fix margins in mobile view +@media (max-width: 767px) { + .object-tools li { + // our CSS is read before django's, so need !important + // to override + margin-left: 0!important; + margin-right: 15px; + } +} + +// Fix height of buttons +.object-tools li { + height: auto; +} + +// Fixing height of buttons breaks layout because +// object-tools and changelist are siblings with +// flexbox positioning +#changelist { + clear: both; +} + +// Account for the h2, roughly 90px +@include at-media(tablet) { + .object-tools { + padding-left: 90px; + } +} \ No newline at end of file diff --git a/src/registrar/templates/django/admin/domain_change_list.html b/src/registrar/templates/django/admin/domain_change_list.html index b99f71e2f..68fdbe7aa 100644 --- a/src/registrar/templates/django/admin/domain_change_list.html +++ b/src/registrar/templates/django/admin/domain_change_list.html @@ -14,9 +14,9 @@ {% if has_add_permission %}
  • - - Add Domain - + + Add Domain +
  • {% endif %} diff --git a/src/registrar/tests/test_reports.py b/src/registrar/tests/test_reports.py index 828640956..404ed358c 100644 --- a/src/registrar/tests/test_reports.py +++ b/src/registrar/tests/test_reports.py @@ -77,7 +77,7 @@ class ExportDataTest(TestCase): columns = [ "Domain name", "Domain type", - "Federal agency", + "Agency", "Organization name", "City", "State", @@ -111,7 +111,7 @@ class ExportDataTest(TestCase): # We expect READY domains, # sorted alphabetially by domain name expected_content = ( - "Domain name,Domain type,Federal agency,Organization name,City,State,AO," + "Domain name,Domain type,Agency,Organization name,City,State,AO," "AO email,Submitter,Submitter title,Submitter email,Submitter phone," "Security Contact Email,Status\n" "adomain2.gov,Interstate,dnsneeded\n" @@ -134,7 +134,7 @@ class ExportDataTest(TestCase): self.assertEqual(csv_content, expected_content) - def test_export_domains_to_writer_2(self): + def test_export_domains_to_writer_additional(self): """An additional test for filters and multi-column sort""" # Create a CSV file in memory csv_file = StringIO() @@ -144,7 +144,7 @@ class ExportDataTest(TestCase): columns = [ "Domain name", "Domain type", - "Federal agency", + "Agency", "Organization name", "City", "State", @@ -173,7 +173,7 @@ class ExportDataTest(TestCase): # federal only # sorted alphabetially by domain name expected_content = ( - "Domain name,Domain type,Federal agency,Organization name,City," + "Domain name,Domain type,Agency,Organization name,City," "State,Security Contact Email\n" "cdomain1.gov,Federal - Executive,World War I Centennial Commission\n" "ddomain3.gov,Federal,Armed Forces Retirement Home\n" diff --git a/src/registrar/utility/csv_export.py b/src/registrar/utility/csv_export.py index c23a6c8d9..ffada0a0b 100644 --- a/src/registrar/utility/csv_export.py +++ b/src/registrar/utility/csv_export.py @@ -24,7 +24,7 @@ def export_domains_to_writer(writer, columns, sort_fields, filter_condition): + domainInfo.get_federal_type_display() if domainInfo.federal_type else domainInfo.get_organization_type_display(), - "Federal agency": domainInfo.federal_agency, + "Agency": domainInfo.federal_agency, "Organization name": domainInfo.organization_name, "City": domainInfo.city, "State": domainInfo.state_territory, @@ -51,7 +51,7 @@ def export_data_type_to_csv(csv_file): columns = [ "Domain name", "Domain type", - "Federal agency", + "Agency", "Organization name", "City", "State", @@ -78,7 +78,7 @@ def export_data_full_to_csv(csv_file): columns = [ "Domain name", "Domain type", - "Federal agency", + "Agency", "Organization name", "City", "State", @@ -101,7 +101,7 @@ def export_data_federal_to_csv(csv_file): columns = [ "Domain name", "Domain type", - "Federal agency", + "Agency", "Organization name", "City", "State", From a1c351277e03b6f77aaf784d49449c1c0c18c9f3 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Tue, 31 Oct 2023 13:48:07 -0400 Subject: [PATCH 21/22] lint --- src/registrar/admin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 8a2431fe1..8914e5c87 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -778,7 +778,7 @@ class DomainAdmin(ListHeaderAdmin): urlpatterns = super().get_urls() - #Used to extrapolate a path name, for instance + # Used to extrapolate a path name, for instance # name="{app_label}_{model_name}_export_data_type" info = self.model._meta.app_label, self.model._meta.model_name From 0d504c742f788cf25106017c3697825e685d0d2d Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Tue, 31 Oct 2023 13:53:34 -0400 Subject: [PATCH 22/22] End of line on scss admin --- src/registrar/assets/sass/_theme/_admin.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/assets/sass/_theme/_admin.scss b/src/registrar/assets/sass/_theme/_admin.scss index 6e2f6797e..68ff51597 100644 --- a/src/registrar/assets/sass/_theme/_admin.scss +++ b/src/registrar/assets/sass/_theme/_admin.scss @@ -224,4 +224,4 @@ h1, h2, h3 { .object-tools { padding-left: 90px; } -} \ No newline at end of file +}