From 853a083a9b7b296208b1d95ec50d871448acbe66 Mon Sep 17 00:00:00 2001 From: Erin Song <121973038+erinysong@users.noreply.github.com> Date: Mon, 30 Dec 2024 10:56:35 -0800 Subject: [PATCH 01/46] Revert changes to fixtures domains --- src/registrar/fixtures/fixtures_domains.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/registrar/fixtures/fixtures_domains.py b/src/registrar/fixtures/fixtures_domains.py index 2b79f6963..4606024d0 100644 --- a/src/registrar/fixtures/fixtures_domains.py +++ b/src/registrar/fixtures/fixtures_domains.py @@ -39,11 +39,12 @@ class DomainFixture(DomainRequestFixture): except Exception as e: logger.warning(e) return + # Approve each user associated with `in review` status domains cls._approve_domain_requests(users) @staticmethod - def _generate_fake_expiration_date(days_in_future=100): + def _generate_fake_expiration_date(days_in_future=365): """Generates a fake expiration date between 1 and 365 days in the future.""" current_date = timezone.now().date() # nosec return current_date + timedelta(days=random.randint(1, days_in_future)) # nosec From 1e64233bf948f5085208678fe7fb916d709fc390 Mon Sep 17 00:00:00 2001 From: Erin Song <121973038+erinysong@users.noreply.github.com> Date: Mon, 6 Jan 2025 10:25:21 -0800 Subject: [PATCH 02/46] Add requested by field to submission email --- src/registrar/templates/emails/submission_confirmation.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/registrar/templates/emails/submission_confirmation.txt b/src/registrar/templates/emails/submission_confirmation.txt index aa1c207ce..027063721 100644 --- a/src/registrar/templates/emails/submission_confirmation.txt +++ b/src/registrar/templates/emails/submission_confirmation.txt @@ -4,6 +4,7 @@ Hi, {{ recipient.first_name }}. We received your .gov domain request. DOMAIN REQUESTED: {{ domain_request.requested_domain.name }} +REQUESTED BY: {{ domain_request.creator.first_name domain_request.creator.last_name }} REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }} STATUS: Submitted From 627cc189a9e1f7057a915b08ac109b82ba5b6fc0 Mon Sep 17 00:00:00 2001 From: Erin Song <121973038+erinysong@users.noreply.github.com> Date: Mon, 6 Jan 2025 12:37:17 -0800 Subject: [PATCH 03/46] Add requested by info to domain request email template --- .../emails/action_needed_reasons/already_has_a_domain.txt | 1 + .../templates/emails/action_needed_reasons/bad_name.txt | 1 + .../emails/action_needed_reasons/eligibility_unclear.txt | 1 + .../action_needed_reasons/questionable_senior_official.txt | 1 + src/registrar/templates/emails/domain_request_withdrawn.txt | 1 + src/registrar/templates/emails/status_change_approved.txt | 1 + src/registrar/templates/emails/status_change_rejected.txt | 1 + src/registrar/templates/emails/submission_confirmation.txt | 2 +- 8 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/registrar/templates/emails/action_needed_reasons/already_has_a_domain.txt b/src/registrar/templates/emails/action_needed_reasons/already_has_a_domain.txt index 2e3012c91..0f190f475 100644 --- a/src/registrar/templates/emails/action_needed_reasons/already_has_a_domain.txt +++ b/src/registrar/templates/emails/action_needed_reasons/already_has_a_domain.txt @@ -4,6 +4,7 @@ Hi, {{ recipient.first_name }}. We've identified an action that you’ll need to complete before we continue reviewing your .gov domain request. DOMAIN REQUESTED: {{ domain_request.requested_domain.name }} +REQUESTED BY: {{ domain_request.creator.first_name }} {{ domain_request.creator.last_name }} REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }} STATUS: Action needed diff --git a/src/registrar/templates/emails/action_needed_reasons/bad_name.txt b/src/registrar/templates/emails/action_needed_reasons/bad_name.txt index 9481a1e63..abeec88fa 100644 --- a/src/registrar/templates/emails/action_needed_reasons/bad_name.txt +++ b/src/registrar/templates/emails/action_needed_reasons/bad_name.txt @@ -4,6 +4,7 @@ Hi, {{ recipient.first_name }}. We've identified an action that you’ll need to complete before we continue reviewing your .gov domain request. DOMAIN REQUESTED: {{ domain_request.requested_domain.name }} +REQUESTED BY: {{ domain_request.creator.first_name }} {{ domain_request.creator.last_name }} REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }} STATUS: Action needed diff --git a/src/registrar/templates/emails/action_needed_reasons/eligibility_unclear.txt b/src/registrar/templates/emails/action_needed_reasons/eligibility_unclear.txt index 705805998..59713bd81 100644 --- a/src/registrar/templates/emails/action_needed_reasons/eligibility_unclear.txt +++ b/src/registrar/templates/emails/action_needed_reasons/eligibility_unclear.txt @@ -4,6 +4,7 @@ Hi, {{ recipient.first_name }}. We've identified an action that you’ll need to complete before we continue reviewing your .gov domain request. DOMAIN REQUESTED: {{ domain_request.requested_domain.name }} +REQUESTED BY: {{ domain_request.creator.first_name }} {{ domain_request.creator.last_name }} REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }} STATUS: Action needed diff --git a/src/registrar/templates/emails/action_needed_reasons/questionable_senior_official.txt b/src/registrar/templates/emails/action_needed_reasons/questionable_senior_official.txt index 5967d7089..f0824b06d 100644 --- a/src/registrar/templates/emails/action_needed_reasons/questionable_senior_official.txt +++ b/src/registrar/templates/emails/action_needed_reasons/questionable_senior_official.txt @@ -4,6 +4,7 @@ Hi, {{ recipient.first_name }}. We've identified an action that you’ll need to complete before we continue reviewing your .gov domain request. DOMAIN REQUESTED: {{ domain_request.requested_domain.name }} +REQUESTED BY: {{ domain_request.creator.first_name }} {{ domain_request.creator.last_name }} REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }} STATUS: Action needed diff --git a/src/registrar/templates/emails/domain_request_withdrawn.txt b/src/registrar/templates/emails/domain_request_withdrawn.txt index 0db00feea..68d52761b 100644 --- a/src/registrar/templates/emails/domain_request_withdrawn.txt +++ b/src/registrar/templates/emails/domain_request_withdrawn.txt @@ -4,6 +4,7 @@ Hi, {{ recipient.first_name }}. Your .gov domain request has been withdrawn and will not be reviewed by our team. DOMAIN REQUESTED: {{ domain_request.requested_domain.name }} +REQUESTED BY: {{ domain_request.creator.first_name }} {{ domain_request.creator.last_name }} REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }} STATUS: Withdrawn diff --git a/src/registrar/templates/emails/status_change_approved.txt b/src/registrar/templates/emails/status_change_approved.txt index 66f8f8b6c..9aedcd25f 100644 --- a/src/registrar/templates/emails/status_change_approved.txt +++ b/src/registrar/templates/emails/status_change_approved.txt @@ -4,6 +4,7 @@ Hi, {{ recipient.first_name }}. Congratulations! Your .gov domain request has been approved. DOMAIN REQUESTED: {{ domain_request.requested_domain.name }} +REQUESTED BY: {{ domain_request.creator.first_name }} {{ domain_request.creator.last_name }} REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }} STATUS: Approved diff --git a/src/registrar/templates/emails/status_change_rejected.txt b/src/registrar/templates/emails/status_change_rejected.txt index b1d989bf1..d963e39d0 100644 --- a/src/registrar/templates/emails/status_change_rejected.txt +++ b/src/registrar/templates/emails/status_change_rejected.txt @@ -4,6 +4,7 @@ Hi, {{ recipient.first_name }}. Your .gov domain request has been rejected. DOMAIN REQUESTED: {{ domain_request.requested_domain.name }} +REQUESTED BY: {{ domain_request.creator.first_name }} {{ domain_request.creator.last_name }} REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }} STATUS: Rejected diff --git a/src/registrar/templates/emails/submission_confirmation.txt b/src/registrar/templates/emails/submission_confirmation.txt index 027063721..800d0dfad 100644 --- a/src/registrar/templates/emails/submission_confirmation.txt +++ b/src/registrar/templates/emails/submission_confirmation.txt @@ -4,7 +4,7 @@ Hi, {{ recipient.first_name }}. We received your .gov domain request. DOMAIN REQUESTED: {{ domain_request.requested_domain.name }} -REQUESTED BY: {{ domain_request.creator.first_name domain_request.creator.last_name }} +REQUESTED BY: {{ domain_request.creator.first_name }} {{ domain_request.creator.last_name }} REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }} STATUS: Submitted From ac931faa06eb75134e983317616a12cecd952194 Mon Sep 17 00:00:00 2001 From: Erin Song <121973038+erinysong@users.noreply.github.com> Date: Mon, 6 Jan 2025 12:58:07 -0800 Subject: [PATCH 04/46] Modify copy on submission email from org and non org model --- src/registrar/templates/emails/submission_confirmation.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/registrar/templates/emails/submission_confirmation.txt b/src/registrar/templates/emails/submission_confirmation.txt index 800d0dfad..ed4c1e00c 100644 --- a/src/registrar/templates/emails/submission_confirmation.txt +++ b/src/registrar/templates/emails/submission_confirmation.txt @@ -12,12 +12,14 @@ STATUS: Submitted NEXT STEPS We’ll review your request. This review period can take 30 business days. Due to the volume of requests, the wait time is longer than usual. We appreciate your patience. - +{% if has_organization_feature_flag %} +During our review we’ll verify that your requested domain meets our naming requirements. +{% else %} During our review, we’ll verify that: - Your organization is eligible for a .gov domain - You work at the organization and/or can make requests on its behalf - Your requested domain meets our naming requirements - +{% endif %} We’ll email you if we have questions. We’ll also email you as soon as we complete our review. You can check the status of your request at any time on the registrar. From 30986c546d0e1b6ce36d511f44e4da6fcc579027 Mon Sep 17 00:00:00 2001 From: Erin Song <121973038+erinysong@users.noreply.github.com> Date: Fri, 10 Jan 2025 10:10:10 -0800 Subject: [PATCH 05/46] Add organization feature flag check to email template --- src/registrar/templates/emails/submission_confirmation.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/registrar/templates/emails/submission_confirmation.txt b/src/registrar/templates/emails/submission_confirmation.txt index ed4c1e00c..fbc7a5ad3 100644 --- a/src/registrar/templates/emails/submission_confirmation.txt +++ b/src/registrar/templates/emails/submission_confirmation.txt @@ -12,9 +12,10 @@ STATUS: Submitted NEXT STEPS We’ll review your request. This review period can take 30 business days. Due to the volume of requests, the wait time is longer than usual. We appreciate your patience. -{% if has_organization_feature_flag %} +{% if has_organization_feature_flag %} During our review we’ll verify that your requested domain meets our naming requirements. {% else %} +has feature flag: {{has_organization_feature_flag}} During our review, we’ll verify that: - Your organization is eligible for a .gov domain - You work at the organization and/or can make requests on its behalf From 8432435c087daa8f1a56a23457b04fbc62aaa57c Mon Sep 17 00:00:00 2001 From: CocoByte Date: Sun, 12 Jan 2025 23:38:48 -0700 Subject: [PATCH 06/46] Adjusted fixtures to allow specification of number of domain requests to load --- src/registrar/fixtures/fixtures_requests.py | 48 +++++++++++++++------ 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/src/registrar/fixtures/fixtures_requests.py b/src/registrar/fixtures/fixtures_requests.py index bff49ff6b..f8371cc2b 100644 --- a/src/registrar/fixtures/fixtures_requests.py +++ b/src/registrar/fixtures/fixtures_requests.py @@ -325,20 +325,42 @@ class DomainRequestFixture: @classmethod def _create_domain_requests(cls, users): """Creates DomainRequests given a list of users.""" + total_domain_requests_to_make = 100000 + domain_requests_already_made = DomainRequest.objects.count() + domain_requests_to_create = [] - for user in users: - for request_data in cls.DOMAINREQUESTS: - # Prepare DomainRequest objects - try: - domain_request = DomainRequest( - creator=user, - organization_name=request_data["organization_name"], - ) - cls._set_non_foreign_key_fields(domain_request, request_data) - cls._set_foreign_key_fields(domain_request, request_data, user) - domain_requests_to_create.append(domain_request) - except Exception as e: - logger.warning(e) + if domain_requests_already_made < total_domain_requests_to_make: + for user in users: + for request_data in cls.DOMAINREQUESTS: + # Prepare DomainRequest objects + try: + domain_request = DomainRequest( + creator=user, + organization_name=request_data["organization_name"], + ) + cls._set_non_foreign_key_fields(domain_request, request_data) + cls._set_foreign_key_fields(domain_request, request_data, user) + domain_requests_to_create.append(domain_request) + except Exception as e: + logger.warning(e) + + num_additional_requests_to_make = total_domain_requests_to_make-domain_requests_already_made-len(domain_requests_to_create) + if num_additional_requests_to_make > 0: + for _ in range(num_additional_requests_to_make): + random_user = random.choice(users) + try: + random_request_data = random.choice(cls.DOMAINREQUESTS) + # Prepare DomainRequest objects + domain_request = DomainRequest( + creator=random_user, + organization_name=random_request_data["organization_name"], + ) + cls._set_non_foreign_key_fields(domain_request, random_request_data) + cls._set_foreign_key_fields(domain_request, random_request_data, random_user) + domain_requests_to_create.append(domain_request) + except Exception as e: + logger.warning(f"Error creating random domain request: {e}") + # Bulk create domain requests cls._bulk_create_requests(domain_requests_to_create) From 0a05039ac7a8c8e51c9333f3cd77a88c6c5a51a8 Mon Sep 17 00:00:00 2001 From: CocoByte Date: Sun, 12 Jan 2025 23:39:04 -0700 Subject: [PATCH 07/46] Fixed slowness in DomainRequest, Domain, and DomainInformation admin tables --- src/registrar/admin.py | 158 +++++++++++++++++++---------------------- 1 file changed, 74 insertions(+), 84 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 849cb6100..910a90092 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -1625,28 +1625,27 @@ class DomainInformationAdmin(ListHeaderAdmin, ImportExportModelAdmin): class GenericOrgFilter(admin.SimpleListFilter): """Custom Generic Organization filter that accomodates portfolio feature. If we have a portfolio, use the portfolio's organization. If not, use the - organization in the Domain Information object.""" + organization in the Domain Request object.""" title = "generic organization" parameter_name = "converted_generic_orgs" def lookups(self, request, model_admin): - converted_generic_orgs = set() + # Annotate the queryset to avoid Python-side iteration + queryset = DomainRequest.objects.annotate( + converted_generic_org=Case( + When(portfolio__organization_type__isnull=False, then="portfolio__organization_type"), + When(portfolio__isnull=True, generic_org_type__isnull=False, then="generic_org_type"), + default=Value(''), + output_field=CharField() + ) + ).values_list('converted_generic_org', flat=True).distinct() - # Populate the set with tuples of (value, display value) - for domain_info in DomainInformation.objects.all(): - converted_generic_org = domain_info.converted_generic_org_type # Actual value - converted_generic_org_display = domain_info.converted_generic_org_type_display # Display value + # Filter out empty results and return sorted list of unique values + return sorted([(org, org) for org in queryset if org]) - if converted_generic_org: - converted_generic_orgs.add((converted_generic_org, converted_generic_org_display)) # Value, Display - - # Sort the set by display value - return sorted(converted_generic_orgs, key=lambda x: x[1]) # x[1] is the display value - - # Filter queryset def queryset(self, request, queryset): - if self.value(): # Check if a generic org is selected in the filter + if self.value(): return queryset.filter( Q(portfolio__organization_type=self.value()) | Q(portfolio__isnull=True, generic_org_type=self.value()) @@ -1984,22 +1983,21 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): parameter_name = "converted_generic_orgs" def lookups(self, request, model_admin): - converted_generic_orgs = set() + # Annotate the queryset to avoid Python-side iteration + queryset = DomainRequest.objects.annotate( + converted_generic_org=Case( + When(portfolio__organization_type__isnull=False, then="portfolio__organization_type"), + When(portfolio__isnull=True, generic_org_type__isnull=False, then="generic_org_type"), + default=Value(''), + output_field=CharField() + ) + ).values_list('converted_generic_org', flat=True).distinct() - # Populate the set with tuples of (value, display value) - for domain_request in DomainRequest.objects.all(): - converted_generic_org = domain_request.converted_generic_org_type # Actual value - converted_generic_org_display = domain_request.converted_generic_org_type_display # Display value + # Filter out empty results and return sorted list of unique values + return sorted([(org, org) for org in queryset if org]) - if converted_generic_org: - converted_generic_orgs.add((converted_generic_org, converted_generic_org_display)) # Value, Display - - # Sort the set by display value - return sorted(converted_generic_orgs, key=lambda x: x[1]) # x[1] is the display value - - # Filter queryset def queryset(self, request, queryset): - if self.value(): # Check if a generic org is selected in the filter + if self.value(): return queryset.filter( Q(portfolio__organization_type=self.value()) | Q(portfolio__isnull=True, generic_org_type=self.value()) @@ -2015,26 +2013,23 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): parameter_name = "converted_federal_types" def lookups(self, request, model_admin): - converted_federal_types = set() + # Annotate the queryset for efficient filtering + queryset = DomainRequest.objects.annotate( + converted_federal_type=Case( + When(portfolio__isnull=False, portfolio__federal_type__isnull=False, then="portfolio__federal_type"), + When(portfolio__isnull=True, federal_type__isnull=False, then="federal_type"), + default=Value(''), + output_field=CharField() + ) + ).values_list('converted_federal_type', flat=True).distinct() - # Populate the set with tuples of (value, display value) - for domain_request in DomainRequest.objects.all(): - converted_federal_type = domain_request.converted_federal_type # Actual value - converted_federal_type_display = domain_request.converted_federal_type_display # Display value + # Filter out empty values and return sorted unique entries + return sorted([(federal_type, federal_type) for federal_type in queryset if federal_type]) - if converted_federal_type: - converted_federal_types.add( - (converted_federal_type, converted_federal_type_display) # Value, Display - ) - - # Sort the set by display value - return sorted(converted_federal_types, key=lambda x: x[1]) # x[1] is the display value - - # Filter queryset def queryset(self, request, queryset): - if self.value(): # Check if a federal type is selected in the filter + if self.value(): return queryset.filter( - Q(portfolio__federal_agency__federal_type=self.value()) + Q(portfolio__federal_type=self.value()) | Q(portfolio__isnull=True, federal_type=self.value()) ) return queryset @@ -3167,72 +3162,67 @@ class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin): return queryset.filter(domain_info__is_election_board=True) if self.value() == "0": return queryset.filter(Q(domain_info__is_election_board=False) | Q(domain_info__is_election_board=None)) - + class GenericOrgFilter(admin.SimpleListFilter): """Custom Generic Organization filter that accomodates portfolio feature. If we have a portfolio, use the portfolio's organization. If not, use the - organization in the Domain Information object.""" + organization in the Domain Request object.""" title = "generic organization" parameter_name = "converted_generic_orgs" def lookups(self, request, model_admin): - converted_generic_orgs = set() - - # Populate the set with tuples of (value, display value) - for domain_info in DomainInformation.objects.all(): - converted_generic_org = domain_info.converted_generic_org_type # Actual value - converted_generic_org_display = domain_info.converted_generic_org_type_display # Display value - - if converted_generic_org: - converted_generic_orgs.add((converted_generic_org, converted_generic_org_display)) # Value, Display - - # Sort the set by display value - return sorted(converted_generic_orgs, key=lambda x: x[1]) # x[1] is the display value - - # Filter queryset - def queryset(self, request, queryset): - if self.value(): # Check if a generic org is selected in the filter - return queryset.filter( - Q(domain_info__portfolio__organization_type=self.value()) - | Q(domain_info__portfolio__isnull=True, domain_info__generic_org_type=self.value()) + # Annotate the queryset to avoid Python-side iteration + queryset = DomainRequest.objects.annotate( + converted_generic_org=Case( + When(portfolio__organization_type__isnull=False, then="portfolio__organization_type"), + When(portfolio__isnull=True, generic_org_type__isnull=False, then="generic_org_type"), + default=Value(''), + output_field=CharField() ) + ).values_list('converted_generic_org', flat=True).distinct() + # Filter out empty results and return sorted list of unique values + return sorted([(org, org) for org in queryset if org]) + + def queryset(self, request, queryset): + if self.value(): + return queryset.filter( + Q(portfolio__organization_type=self.value()) + | Q(portfolio__isnull=True, generic_org_type=self.value()) + ) return queryset class FederalTypeFilter(admin.SimpleListFilter): """Custom Federal Type filter that accomodates portfolio feature. If we have a portfolio, use the portfolio's federal type. If not, use the - federal type in the Domain Information object.""" + organization in the Domain Request object.""" title = "federal type" parameter_name = "converted_federal_types" def lookups(self, request, model_admin): - converted_federal_types = set() + # Annotate the queryset for efficient filtering + queryset = DomainRequest.objects.annotate( + converted_federal_type=Case( + When(portfolio__isnull=False, portfolio__federal_type__isnull=False, then="portfolio__federal_type"), + When(portfolio__isnull=True, federal_type__isnull=False, then="federal_type"), + default=Value(''), + output_field=CharField() + ) + ).values_list('converted_federal_type', flat=True).distinct() - # Populate the set with tuples of (value, display value) - for domain_info in DomainInformation.objects.all(): - converted_federal_type = domain_info.converted_federal_type # Actual value - converted_federal_type_display = domain_info.converted_federal_type_display # Display value + # Filter out empty values and return sorted unique entries + return sorted([(federal_type, federal_type) for federal_type in queryset if federal_type]) - if converted_federal_type: - converted_federal_types.add( - (converted_federal_type, converted_federal_type_display) # Value, Display - ) - - # Sort the set by display value - return sorted(converted_federal_types, key=lambda x: x[1]) # x[1] is the display value - - # Filter queryset def queryset(self, request, queryset): - if self.value(): # Check if a federal type is selected in the filter + if self.value(): return queryset.filter( - Q(domain_info__portfolio__federal_agency__federal_type=self.value()) - | Q(domain_info__portfolio__isnull=True, domain_info__federal_agency__federal_type=self.value()) + Q(portfolio__federal_type=self.value()) + | Q(portfolio__isnull=True, federal_type=self.value()) ) return queryset - + def get_annotated_queryset(self, queryset): return queryset.annotate( converted_generic_org_type=Case( @@ -3254,7 +3244,7 @@ class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin): # When portfolio is present, use its value instead When( Q(domain_info__portfolio__isnull=False) & Q(domain_info__portfolio__federal_agency__isnull=False), - then=F("domain_info__portfolio__federal_agency__federal_type"), + then=F("domain_info__portfolio__federal_type"), ), # Otherwise, return the natively assigned value default=F("domain_info__federal_agency__federal_type"), From 18317021a4a09c44ef76fa8616c634ad939c6b1c Mon Sep 17 00:00:00 2001 From: CocoByte Date: Sun, 12 Jan 2025 23:40:25 -0700 Subject: [PATCH 08/46] reducing domain requests fixtures to load only 1000 domain requests (so we don't all end up with 100000 --- src/registrar/fixtures/fixtures_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/fixtures/fixtures_requests.py b/src/registrar/fixtures/fixtures_requests.py index f8371cc2b..a2a8d72c6 100644 --- a/src/registrar/fixtures/fixtures_requests.py +++ b/src/registrar/fixtures/fixtures_requests.py @@ -325,7 +325,7 @@ class DomainRequestFixture: @classmethod def _create_domain_requests(cls, users): """Creates DomainRequests given a list of users.""" - total_domain_requests_to_make = 100000 + total_domain_requests_to_make = 1000 domain_requests_already_made = DomainRequest.objects.count() domain_requests_to_create = [] From 51e3fc8c3d0d52bae181eb9ad9631c30ff92d025 Mon Sep 17 00:00:00 2001 From: Erin Song <121973038+erinysong@users.noreply.github.com> Date: Tue, 14 Jan 2025 12:57:38 -0800 Subject: [PATCH 09/46] CC view requests users to submission emails --- src/registrar/models/domain_request.py | 11 +++++++++++ src/registrar/models/portfolio.py | 9 +++++++++ src/registrar/utility/email.py | 2 +- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py index 3d3aac769..f5c0e9561 100644 --- a/src/registrar/models/domain_request.py +++ b/src/registrar/models/domain_request.py @@ -9,6 +9,7 @@ from django.utils import timezone from registrar.models.domain import Domain from registrar.models.federal_agency import FederalAgency from registrar.models.utility.generic_helper import CreateOrUpdateOrganizationTypeHelper +from registrar.models.utility.portfolio_helper import UserPortfolioPermissionChoices from registrar.utility.errors import FSMDomainRequestError, FSMErrorCodes from registrar.utility.constants import BranchChoices from auditlog.models import LogEntry @@ -903,6 +904,7 @@ class DomainRequest(TimeStampedModel): email_template, email_template_subject, bcc_address="", + cc_addresses: list[str] = [], context=None, send_email=True, wrap_email=False, @@ -961,6 +963,7 @@ class DomainRequest(TimeStampedModel): recipient.email, context=context, bcc_address=bcc_address, + cc_addresses=cc_addresses, wrap_email=wrap_email, ) logger.info(f"The {new_status} email sent to: {recipient.email}") @@ -1015,6 +1018,13 @@ class DomainRequest(TimeStampedModel): if settings.IS_PRODUCTION: bcc_address = settings.DEFAULT_FROM_EMAIL + cc_addresses: list[str] = [] + if self.requesting_entity_is_portfolio: + portfolio_view_requests_users = self.portfolio.portfolio_users_with_permissions(permissions=[UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS]) + cc_users = self.portfolio.portfolio_admin_users | portfolio_view_requests_users + cc_addresses = list(cc_users.values_list("email", flat=True)) + print("cc addresses: ", cc_addresses) + if self.status in limited_statuses: self._send_status_update_email( "submission confirmation", @@ -1022,6 +1032,7 @@ class DomainRequest(TimeStampedModel): "emails/submission_confirmation_subject.txt", send_email=True, bcc_address=bcc_address, + cc_addresses=cc_addresses ) @transition( diff --git a/src/registrar/models/portfolio.py b/src/registrar/models/portfolio.py index 82afcd4d6..27501921a 100644 --- a/src/registrar/models/portfolio.py +++ b/src/registrar/models/portfolio.py @@ -144,6 +144,15 @@ class Portfolio(TimeStampedModel): ).values_list("user__id", flat=True) return User.objects.filter(id__in=admin_ids) + def portfolio_users_with_permissions(self, permissions=[]): + """Gets all users with specified additional permissions for this particular portfolio. + Returns a queryset of User.""" + portfolio_users = self.portfolio_users + if permissions: + portfolio_users = portfolio_users.filter(additional_permissions__overlap=permissions) + user_ids = portfolio_users.values_list("user__id", flat=True) + return User.objects.filter(id__in=user_ids) + # == Getters for domains == # def get_domains(self, order_by=None): """Returns all DomainInformations associated with this portfolio""" diff --git a/src/registrar/utility/email.py b/src/registrar/utility/email.py index 2a99267a5..40601cdc7 100644 --- a/src/registrar/utility/email.py +++ b/src/registrar/utility/email.py @@ -36,7 +36,7 @@ def send_templated_email( # noqa to_address and bcc_address currently only support single addresses. - cc_address is a list and can contain many addresses. Emails not in the + cc_addresses is a list and can contain many addresses. Emails not in the whitelist (if applicable) will be filtered out before sending. template_name and subject_template_name are relative to the same template From 48454e192161a59290a29d411d8c88bebca6e546 Mon Sep 17 00:00:00 2001 From: Erin Song <121973038+erinysong@users.noreply.github.com> Date: Tue, 14 Jan 2025 12:57:58 -0800 Subject: [PATCH 10/46] Remove print --- src/registrar/models/domain_request.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py index f5c0e9561..700a6bb8a 100644 --- a/src/registrar/models/domain_request.py +++ b/src/registrar/models/domain_request.py @@ -1023,7 +1023,6 @@ class DomainRequest(TimeStampedModel): portfolio_view_requests_users = self.portfolio.portfolio_users_with_permissions(permissions=[UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS]) cc_users = self.portfolio.portfolio_admin_users | portfolio_view_requests_users cc_addresses = list(cc_users.values_list("email", flat=True)) - print("cc addresses: ", cc_addresses) if self.status in limited_statuses: self._send_status_update_email( From 352e1904313b302eec42599b549c695181912d1b Mon Sep 17 00:00:00 2001 From: CocoByte Date: Tue, 14 Jan 2025 15:43:57 -0700 Subject: [PATCH 11/46] fixes --- src/registrar/admin.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index ad683d424..090ac56a8 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -1632,7 +1632,7 @@ class DomainInformationAdmin(ListHeaderAdmin, ImportExportModelAdmin): def lookups(self, request, model_admin): # Annotate the queryset to avoid Python-side iteration - queryset = DomainRequest.objects.annotate( + queryset = DomainInformation.objects.annotate( converted_generic_org=Case( When(portfolio__organization_type__isnull=False, then="portfolio__organization_type"), When(portfolio__isnull=True, generic_org_type__isnull=False, then="generic_org_type"), @@ -2016,8 +2016,8 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): # Annotate the queryset for efficient filtering queryset = DomainRequest.objects.annotate( converted_federal_type=Case( - When(portfolio__isnull=False, portfolio__federal_type__isnull=False, then="portfolio__federal_type"), - When(portfolio__isnull=True, federal_type__isnull=False, then="federal_type"), + When(portfolio__isnull=False, portfolio__federal_agency__federal_type__isnull=False, then="portfolio__federal_agency__federal_type"), + When(portfolio__isnull=True, federal_agency__federal_type__isnull=False, then="federal_agency__federal_type"), default=Value(''), output_field=CharField() ) @@ -3175,10 +3175,10 @@ class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin): def lookups(self, request, model_admin): # Annotate the queryset to avoid Python-side iteration - queryset = DomainRequest.objects.annotate( + queryset = Domain.objects.annotate( converted_generic_org=Case( - When(portfolio__organization_type__isnull=False, then="portfolio__organization_type"), - When(portfolio__isnull=True, generic_org_type__isnull=False, then="generic_org_type"), + When(domain_info__isnull=False, domain_info__portfolio__organization_type__isnull=False, then="domain_info__portfolio__organization_type"), + When(domain_info__isnull=False, domain_info__portfolio__isnull=True, domain_info__generic_org_type__isnull=False, then="domain_info__generic_org_type"), default=Value(''), output_field=CharField() ) @@ -3190,8 +3190,8 @@ class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin): def queryset(self, request, queryset): if self.value(): return queryset.filter( - Q(portfolio__organization_type=self.value()) - | Q(portfolio__isnull=True, generic_org_type=self.value()) + Q(domain_info__portfolio__organization_type=self.value()) + | Q(domain_info__portfolio__isnull=True, domain_info__generic_org_type=self.value()) ) return queryset @@ -3205,10 +3205,10 @@ class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin): def lookups(self, request, model_admin): # Annotate the queryset for efficient filtering - queryset = DomainRequest.objects.annotate( + queryset = Domain.objects.annotate( converted_federal_type=Case( - When(portfolio__isnull=False, portfolio__federal_type__isnull=False, then="portfolio__federal_type"), - When(portfolio__isnull=True, federal_type__isnull=False, then="federal_type"), + When(domain_info__isnull=False, domain_info__portfolio__isnull=False, then=F("domain_info__portfolio__organization_type")), + When(domain_info__isnull=False, domain_info__portfolio__isnull=True, domain_info__federal_type__isnull=False, then="domain_info__federal_agency__federal_type"), default=Value(''), output_field=CharField() ) @@ -3220,8 +3220,8 @@ class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin): def queryset(self, request, queryset): if self.value(): return queryset.filter( - Q(portfolio__federal_type=self.value()) - | Q(portfolio__isnull=True, federal_type=self.value()) + Q(domain_info__portfolio__federal_type=self.value()) + | Q(domain_info__portfolio__isnull=True, domain_info__federal_type=self.value()) ) return queryset @@ -3246,7 +3246,7 @@ class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin): # When portfolio is present, use its value instead When( Q(domain_info__portfolio__isnull=False) & Q(domain_info__portfolio__federal_agency__isnull=False), - then=F("domain_info__portfolio__federal_type"), + then=F("domain_info__portfolio__federal_agency__federal_type"), ), # Otherwise, return the natively assigned value default=F("domain_info__federal_agency__federal_type"), From f858571fbfc7837c259e95a02d978085af78479b Mon Sep 17 00:00:00 2001 From: CocoByte Date: Tue, 14 Jan 2025 17:16:37 -0700 Subject: [PATCH 12/46] uni test updates to accommodate lack of _display values --- src/registrar/tests/test_admin_domain.py | 3 ++- src/registrar/tests/test_admin_request.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/registrar/tests/test_admin_domain.py b/src/registrar/tests/test_admin_domain.py index 072bc1f7f..a86cdde4d 100644 --- a/src/registrar/tests/test_admin_domain.py +++ b/src/registrar/tests/test_admin_domain.py @@ -779,7 +779,8 @@ class TestDomainAdminWithClient(TestCase): response = self.client.get("/admin/registrar/domain/") # There are 4 template references to Federal (4) plus four references in the table # for our actual domain_request - self.assertContains(response, "Federal", count=57) + self.assertContains(response, "Federal", count=54) + self.assertContains(response, "federal", count=225) # This may be a bit more robust self.assertContains(response, 'Federal', count=1) # Now let's make sure the long description does not exist diff --git a/src/registrar/tests/test_admin_request.py b/src/registrar/tests/test_admin_request.py index 968de0d65..9708e6439 100644 --- a/src/registrar/tests/test_admin_request.py +++ b/src/registrar/tests/test_admin_request.py @@ -662,7 +662,8 @@ 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=55) + self.assertContains(response, "Federal", count=52) + self.assertContains(response, "federal", count=383) # This may be a bit more robust self.assertContains(response, 'Federal', count=1) # Now let's make sure the long description does not exist From e276f45f1d1f822be8cad426968874173e85e186 Mon Sep 17 00:00:00 2001 From: CocoByte Date: Tue, 14 Jan 2025 17:16:47 -0700 Subject: [PATCH 13/46] cleanup in fixtures --- src/registrar/fixtures/fixtures_requests.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/registrar/fixtures/fixtures_requests.py b/src/registrar/fixtures/fixtures_requests.py index a2a8d72c6..6fd03c410 100644 --- a/src/registrar/fixtures/fixtures_requests.py +++ b/src/registrar/fixtures/fixtures_requests.py @@ -326,6 +326,11 @@ class DomainRequestFixture: def _create_domain_requests(cls, users): """Creates DomainRequests given a list of users.""" total_domain_requests_to_make = 1000 + + # Check if the database is already populated with the desired + # number of entries. + # (Prevents re-adding more entries to an already populated database, + # which happens when restarting Docker src) domain_requests_already_made = DomainRequest.objects.count() domain_requests_to_create = [] @@ -349,14 +354,14 @@ class DomainRequestFixture: for _ in range(num_additional_requests_to_make): random_user = random.choice(users) try: - random_request_data = random.choice(cls.DOMAINREQUESTS) + random_request_type = random.choice(cls.DOMAINREQUESTS) # Prepare DomainRequest objects domain_request = DomainRequest( creator=random_user, - organization_name=random_request_data["organization_name"], + organization_name=random_request_type["organization_name"], ) - cls._set_non_foreign_key_fields(domain_request, random_request_data) - cls._set_foreign_key_fields(domain_request, random_request_data, random_user) + cls._set_non_foreign_key_fields(domain_request, random_request_type) + cls._set_foreign_key_fields(domain_request, random_request_type, random_user) domain_requests_to_create.append(domain_request) except Exception as e: logger.warning(f"Error creating random domain request: {e}") From 604004d89711c67eb749b0eea937f2ac6073ec73 Mon Sep 17 00:00:00 2001 From: CocoByte Date: Tue, 14 Jan 2025 17:19:45 -0700 Subject: [PATCH 14/46] linted --- src/registrar/admin.py | 123 +++++++++++++------- src/registrar/fixtures/fixtures_requests.py | 9 +- 2 files changed, 89 insertions(+), 43 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 090ac56a8..1da5ee6ba 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -1632,14 +1632,18 @@ class DomainInformationAdmin(ListHeaderAdmin, ImportExportModelAdmin): def lookups(self, request, model_admin): # Annotate the queryset to avoid Python-side iteration - queryset = DomainInformation.objects.annotate( - converted_generic_org=Case( - When(portfolio__organization_type__isnull=False, then="portfolio__organization_type"), - When(portfolio__isnull=True, generic_org_type__isnull=False, then="generic_org_type"), - default=Value(''), - output_field=CharField() + queryset = ( + DomainInformation.objects.annotate( + converted_generic_org=Case( + When(portfolio__organization_type__isnull=False, then="portfolio__organization_type"), + When(portfolio__isnull=True, generic_org_type__isnull=False, then="generic_org_type"), + default=Value(""), + output_field=CharField(), + ) ) - ).values_list('converted_generic_org', flat=True).distinct() + .values_list("converted_generic_org", flat=True) + .distinct() + ) # Filter out empty results and return sorted list of unique values return sorted([(org, org) for org in queryset if org]) @@ -1984,14 +1988,18 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): def lookups(self, request, model_admin): # Annotate the queryset to avoid Python-side iteration - queryset = DomainRequest.objects.annotate( - converted_generic_org=Case( - When(portfolio__organization_type__isnull=False, then="portfolio__organization_type"), - When(portfolio__isnull=True, generic_org_type__isnull=False, then="generic_org_type"), - default=Value(''), - output_field=CharField() + queryset = ( + DomainRequest.objects.annotate( + converted_generic_org=Case( + When(portfolio__organization_type__isnull=False, then="portfolio__organization_type"), + When(portfolio__isnull=True, generic_org_type__isnull=False, then="generic_org_type"), + default=Value(""), + output_field=CharField(), + ) ) - ).values_list('converted_generic_org', flat=True).distinct() + .values_list("converted_generic_org", flat=True) + .distinct() + ) # Filter out empty results and return sorted list of unique values return sorted([(org, org) for org in queryset if org]) @@ -2014,14 +2022,26 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): def lookups(self, request, model_admin): # Annotate the queryset for efficient filtering - queryset = DomainRequest.objects.annotate( - converted_federal_type=Case( - When(portfolio__isnull=False, portfolio__federal_agency__federal_type__isnull=False, then="portfolio__federal_agency__federal_type"), - When(portfolio__isnull=True, federal_agency__federal_type__isnull=False, then="federal_agency__federal_type"), - default=Value(''), - output_field=CharField() + queryset = ( + DomainRequest.objects.annotate( + converted_federal_type=Case( + When( + portfolio__isnull=False, + portfolio__federal_agency__federal_type__isnull=False, + then="portfolio__federal_agency__federal_type", + ), + When( + portfolio__isnull=True, + federal_agency__federal_type__isnull=False, + then="federal_agency__federal_type", + ), + default=Value(""), + output_field=CharField(), + ) ) - ).values_list('converted_federal_type', flat=True).distinct() + .values_list("converted_federal_type", flat=True) + .distinct() + ) # Filter out empty values and return sorted unique entries return sorted([(federal_type, federal_type) for federal_type in queryset if federal_type]) @@ -2029,8 +2049,7 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): def queryset(self, request, queryset): if self.value(): return queryset.filter( - Q(portfolio__federal_type=self.value()) - | Q(portfolio__isnull=True, federal_type=self.value()) + Q(portfolio__federal_type=self.value()) | Q(portfolio__isnull=True, federal_type=self.value()) ) return queryset @@ -3164,7 +3183,7 @@ class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin): return queryset.filter(domain_info__is_election_board=True) if self.value() == "0": return queryset.filter(Q(domain_info__is_election_board=False) | Q(domain_info__is_election_board=None)) - + class GenericOrgFilter(admin.SimpleListFilter): """Custom Generic Organization filter that accomodates portfolio feature. If we have a portfolio, use the portfolio's organization. If not, use the @@ -3175,14 +3194,27 @@ class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin): def lookups(self, request, model_admin): # Annotate the queryset to avoid Python-side iteration - queryset = Domain.objects.annotate( - converted_generic_org=Case( - When(domain_info__isnull=False, domain_info__portfolio__organization_type__isnull=False, then="domain_info__portfolio__organization_type"), - When(domain_info__isnull=False, domain_info__portfolio__isnull=True, domain_info__generic_org_type__isnull=False, then="domain_info__generic_org_type"), - default=Value(''), - output_field=CharField() + queryset = ( + Domain.objects.annotate( + converted_generic_org=Case( + When( + domain_info__isnull=False, + domain_info__portfolio__organization_type__isnull=False, + then="domain_info__portfolio__organization_type", + ), + When( + domain_info__isnull=False, + domain_info__portfolio__isnull=True, + domain_info__generic_org_type__isnull=False, + then="domain_info__generic_org_type", + ), + default=Value(""), + output_field=CharField(), + ) ) - ).values_list('converted_generic_org', flat=True).distinct() + .values_list("converted_generic_org", flat=True) + .distinct() + ) # Filter out empty results and return sorted list of unique values return sorted([(org, org) for org in queryset if org]) @@ -3205,14 +3237,27 @@ class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin): def lookups(self, request, model_admin): # Annotate the queryset for efficient filtering - queryset = Domain.objects.annotate( - converted_federal_type=Case( - When(domain_info__isnull=False, domain_info__portfolio__isnull=False, then=F("domain_info__portfolio__organization_type")), - When(domain_info__isnull=False, domain_info__portfolio__isnull=True, domain_info__federal_type__isnull=False, then="domain_info__federal_agency__federal_type"), - default=Value(''), - output_field=CharField() + queryset = ( + Domain.objects.annotate( + converted_federal_type=Case( + When( + domain_info__isnull=False, + domain_info__portfolio__isnull=False, + then=F("domain_info__portfolio__organization_type"), + ), + When( + domain_info__isnull=False, + domain_info__portfolio__isnull=True, + domain_info__federal_type__isnull=False, + then="domain_info__federal_agency__federal_type", + ), + default=Value(""), + output_field=CharField(), + ) ) - ).values_list('converted_federal_type', flat=True).distinct() + .values_list("converted_federal_type", flat=True) + .distinct() + ) # Filter out empty values and return sorted unique entries return sorted([(federal_type, federal_type) for federal_type in queryset if federal_type]) @@ -3224,7 +3269,7 @@ class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin): | Q(domain_info__portfolio__isnull=True, domain_info__federal_type=self.value()) ) return queryset - + def get_annotated_queryset(self, queryset): return queryset.annotate( converted_generic_org_type=Case( diff --git a/src/registrar/fixtures/fixtures_requests.py b/src/registrar/fixtures/fixtures_requests.py index 6fd03c410..56653b3b2 100644 --- a/src/registrar/fixtures/fixtures_requests.py +++ b/src/registrar/fixtures/fixtures_requests.py @@ -323,10 +323,10 @@ class DomainRequestFixture: cls._create_domain_requests(users) @classmethod - def _create_domain_requests(cls, users): + def _create_domain_requests(cls, users): # noqa: C901 """Creates DomainRequests given a list of users.""" total_domain_requests_to_make = 1000 - + # Check if the database is already populated with the desired # number of entries. # (Prevents re-adding more entries to an already populated database, @@ -349,7 +349,9 @@ class DomainRequestFixture: except Exception as e: logger.warning(e) - num_additional_requests_to_make = total_domain_requests_to_make-domain_requests_already_made-len(domain_requests_to_create) + num_additional_requests_to_make = ( + total_domain_requests_to_make - domain_requests_already_made - len(domain_requests_to_create) + ) if num_additional_requests_to_make > 0: for _ in range(num_additional_requests_to_make): random_user = random.choice(users) @@ -366,7 +368,6 @@ class DomainRequestFixture: except Exception as e: logger.warning(f"Error creating random domain request: {e}") - # Bulk create domain requests cls._bulk_create_requests(domain_requests_to_create) From 8700f35d35c61eb2129c17ab01d9550680464361 Mon Sep 17 00:00:00 2001 From: CocoByte Date: Tue, 14 Jan 2025 17:27:32 -0700 Subject: [PATCH 15/46] linter errors resolved --- src/registrar/fixtures/fixtures_requests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/registrar/fixtures/fixtures_requests.py b/src/registrar/fixtures/fixtures_requests.py index 56653b3b2..eb19649b2 100644 --- a/src/registrar/fixtures/fixtures_requests.py +++ b/src/registrar/fixtures/fixtures_requests.py @@ -354,9 +354,9 @@ class DomainRequestFixture: ) if num_additional_requests_to_make > 0: for _ in range(num_additional_requests_to_make): - random_user = random.choice(users) + random_user = random.choice(users) # nosec try: - random_request_type = random.choice(cls.DOMAINREQUESTS) + random_request_type = random.choice(cls.DOMAINREQUESTS) # nosec # Prepare DomainRequest objects domain_request = DomainRequest( creator=random_user, From a403dc2922868053a68512212de04cc6e9a6bfb6 Mon Sep 17 00:00:00 2001 From: Erin Song <121973038+erinysong@users.noreply.github.com> Date: Thu, 16 Jan 2025 09:22:30 -0800 Subject: [PATCH 16/46] Add conditional org model email formatting --- src/registrar/models/domain_request.py | 1 - src/registrar/templates/emails/submission_confirmation.txt | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py index 700a6bb8a..0a7955d7f 100644 --- a/src/registrar/models/domain_request.py +++ b/src/registrar/models/domain_request.py @@ -954,7 +954,6 @@ class DomainRequest(TimeStampedModel): "recipient": recipient, "is_org_user": is_org_user, } - if custom_email_content: context["custom_email_content"] = custom_email_content send_templated_email( diff --git a/src/registrar/templates/emails/submission_confirmation.txt b/src/registrar/templates/emails/submission_confirmation.txt index fbc7a5ad3..cb284342b 100644 --- a/src/registrar/templates/emails/submission_confirmation.txt +++ b/src/registrar/templates/emails/submission_confirmation.txt @@ -12,10 +12,9 @@ STATUS: Submitted NEXT STEPS We’ll review your request. This review period can take 30 business days. Due to the volume of requests, the wait time is longer than usual. We appreciate your patience. -{% if has_organization_feature_flag %} +{% if is_org_user %} During our review we’ll verify that your requested domain meets our naming requirements. {% else %} -has feature flag: {{has_organization_feature_flag}} During our review, we’ll verify that: - Your organization is eligible for a .gov domain - You work at the organization and/or can make requests on its behalf From 8c4d726045072b99657da47db2c9c7e5ff6525d1 Mon Sep 17 00:00:00 2001 From: Erin Song <121973038+erinysong@users.noreply.github.com> Date: Thu, 16 Jan 2025 09:25:53 -0800 Subject: [PATCH 17/46] Readd newline --- src/registrar/models/domain_request.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py index 0a7955d7f..700a6bb8a 100644 --- a/src/registrar/models/domain_request.py +++ b/src/registrar/models/domain_request.py @@ -954,6 +954,7 @@ class DomainRequest(TimeStampedModel): "recipient": recipient, "is_org_user": is_org_user, } + if custom_email_content: context["custom_email_content"] = custom_email_content send_templated_email( From 493aaa7bfad80b38eefd58a9ccb4780b32678e94 Mon Sep 17 00:00:00 2001 From: Erin Song <121973038+erinysong@users.noreply.github.com> Date: Thu, 16 Jan 2025 09:58:40 -0800 Subject: [PATCH 18/46] Fix linting --- src/registrar/models/domain_request.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py index 700a6bb8a..546ccf0dd 100644 --- a/src/registrar/models/domain_request.py +++ b/src/registrar/models/domain_request.py @@ -1020,7 +1020,9 @@ class DomainRequest(TimeStampedModel): cc_addresses: list[str] = [] if self.requesting_entity_is_portfolio: - portfolio_view_requests_users = self.portfolio.portfolio_users_with_permissions(permissions=[UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS]) + portfolio_view_requests_users = self.portfolio.portfolio_users_with_permissions( + permissions=[UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS] + ) cc_users = self.portfolio.portfolio_admin_users | portfolio_view_requests_users cc_addresses = list(cc_users.values_list("email", flat=True)) @@ -1031,7 +1033,7 @@ class DomainRequest(TimeStampedModel): "emails/submission_confirmation_subject.txt", send_email=True, bcc_address=bcc_address, - cc_addresses=cc_addresses + cc_addresses=cc_addresses, ) @transition( From 0c9122e794af0575ebe9a83892183a8bb2cc8e68 Mon Sep 17 00:00:00 2001 From: Erin Song <121973038+erinysong@users.noreply.github.com> Date: Thu, 16 Jan 2025 17:38:33 -0800 Subject: [PATCH 19/46] Fixed bug in portfolio getter --- src/registrar/models/domain_request.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py index 546ccf0dd..12346c43e 100644 --- a/src/registrar/models/domain_request.py +++ b/src/registrar/models/domain_request.py @@ -1019,7 +1019,7 @@ class DomainRequest(TimeStampedModel): bcc_address = settings.DEFAULT_FROM_EMAIL cc_addresses: list[str] = [] - if self.requesting_entity_is_portfolio: + if self.requesting_entity_is_portfolio(): portfolio_view_requests_users = self.portfolio.portfolio_users_with_permissions( permissions=[UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS] ) From 43a1344c9e437eeb7a961c983a1fda56be4bb386 Mon Sep 17 00:00:00 2001 From: CocoByte Date: Fri, 17 Jan 2025 13:11:21 -0700 Subject: [PATCH 20/46] temporarily commit fixture for loading 100000 entries --- src/registrar/fixtures/fixtures_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/fixtures/fixtures_requests.py b/src/registrar/fixtures/fixtures_requests.py index eb19649b2..d18192533 100644 --- a/src/registrar/fixtures/fixtures_requests.py +++ b/src/registrar/fixtures/fixtures_requests.py @@ -325,7 +325,7 @@ class DomainRequestFixture: @classmethod def _create_domain_requests(cls, users): # noqa: C901 """Creates DomainRequests given a list of users.""" - total_domain_requests_to_make = 1000 + total_domain_requests_to_make = 100000 # Check if the database is already populated with the desired # number of entries. From f9fca1b906d5257802504f0363b1121806228fbd Mon Sep 17 00:00:00 2001 From: CocoByte Date: Fri, 17 Jan 2025 15:57:16 -0700 Subject: [PATCH 21/46] adjust fixtures for domain request objects to load only 10,000 entries instead of 100,000 --- src/registrar/fixtures/fixtures_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/fixtures/fixtures_requests.py b/src/registrar/fixtures/fixtures_requests.py index d18192533..c42b83ea8 100644 --- a/src/registrar/fixtures/fixtures_requests.py +++ b/src/registrar/fixtures/fixtures_requests.py @@ -325,7 +325,7 @@ class DomainRequestFixture: @classmethod def _create_domain_requests(cls, users): # noqa: C901 """Creates DomainRequests given a list of users.""" - total_domain_requests_to_make = 100000 + total_domain_requests_to_make = 10000 # Check if the database is already populated with the desired # number of entries. From 0cee2ecbc03e1d7788eb28c4ba27237fb267a286 Mon Sep 17 00:00:00 2001 From: CocoByte Date: Fri, 17 Jan 2025 15:57:26 -0700 Subject: [PATCH 22/46] Corrected comments --- src/registrar/admin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 82e3392a4..dd97e4252 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -1670,7 +1670,7 @@ class DomainInformationAdmin(ListHeaderAdmin, ImportExportModelAdmin): class GenericOrgFilter(admin.SimpleListFilter): """Custom Generic Organization filter that accomodates portfolio feature. If we have a portfolio, use the portfolio's organization. If not, use the - organization in the Domain Request object.""" + organization in the Domain Information object.""" title = "generic organization" parameter_name = "converted_generic_orgs" @@ -3232,7 +3232,7 @@ class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin): class GenericOrgFilter(admin.SimpleListFilter): """Custom Generic Organization filter that accomodates portfolio feature. If we have a portfolio, use the portfolio's organization. If not, use the - organization in the Domain Request object.""" + organization in the Domain Information object.""" title = "generic organization" parameter_name = "converted_generic_orgs" From eb19038fd4dd178148b94a6a8f0e0b7432259ada Mon Sep 17 00:00:00 2001 From: Erin Song <121973038+erinysong@users.noreply.github.com> Date: Thu, 23 Jan 2025 11:12:51 -0800 Subject: [PATCH 23/46] CC portfolio request viewers on all domain status updates --- src/registrar/models/domain_request.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py index 12346c43e..256ecbe80 100644 --- a/src/registrar/models/domain_request.py +++ b/src/registrar/models/domain_request.py @@ -957,6 +957,14 @@ class DomainRequest(TimeStampedModel): if custom_email_content: context["custom_email_content"] = custom_email_content + + if self.requesting_entity_is_portfolio() or self.requesting_entity_is_suborganization(): + portfolio_view_requests_users = self.portfolio.portfolio_users_with_permissions( + permissions=[UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS] + ) + cc_users = self.portfolio.portfolio_admin_users | portfolio_view_requests_users + cc_addresses = list(cc_users.values_list("email", flat=True)) + send_templated_email( email_template, email_template_subject, @@ -1018,14 +1026,6 @@ class DomainRequest(TimeStampedModel): if settings.IS_PRODUCTION: bcc_address = settings.DEFAULT_FROM_EMAIL - cc_addresses: list[str] = [] - if self.requesting_entity_is_portfolio(): - portfolio_view_requests_users = self.portfolio.portfolio_users_with_permissions( - permissions=[UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS] - ) - cc_users = self.portfolio.portfolio_admin_users | portfolio_view_requests_users - cc_addresses = list(cc_users.values_list("email", flat=True)) - if self.status in limited_statuses: self._send_status_update_email( "submission confirmation", @@ -1033,7 +1033,6 @@ class DomainRequest(TimeStampedModel): "emails/submission_confirmation_subject.txt", send_email=True, bcc_address=bcc_address, - cc_addresses=cc_addresses, ) @transition( From 607336406f377ce6e28d5460096be796ecdf05f0 Mon Sep 17 00:00:00 2001 From: Erin Song <121973038+erinysong@users.noreply.github.com> Date: Thu, 23 Jan 2025 11:18:05 -0800 Subject: [PATCH 24/46] Add period --- src/registrar/templates/emails/submission_confirmation.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/templates/emails/submission_confirmation.txt b/src/registrar/templates/emails/submission_confirmation.txt index cb284342b..589bdf618 100644 --- a/src/registrar/templates/emails/submission_confirmation.txt +++ b/src/registrar/templates/emails/submission_confirmation.txt @@ -20,7 +20,7 @@ During our review, we’ll verify that: - You work at the organization and/or can make requests on its behalf - Your requested domain meets our naming requirements {% endif %} -We’ll email you if we have questions. We’ll also email you as soon as we complete our review. You can check the status of your request at any time on the registrar. +We’ll email you if we have questions. We’ll also email you as soon as we complete our review. You can check the status of your request at any time on the registrar. . NEED TO MAKE CHANGES? From 0522138cc4986f90a79926e4b00013b1c5bb1c62 Mon Sep 17 00:00:00 2001 From: Erin Song <121973038+erinysong@users.noreply.github.com> Date: Thu, 23 Jan 2025 11:48:07 -0800 Subject: [PATCH 25/46] Fix linting --- src/registrar/models/domain_request.py | 9 +++++---- src/registrar/models/portfolio.py | 13 +++++++++++-- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py index 256ecbe80..9f1a3539d 100644 --- a/src/registrar/models/domain_request.py +++ b/src/registrar/models/domain_request.py @@ -959,11 +959,12 @@ class DomainRequest(TimeStampedModel): context["custom_email_content"] = custom_email_content if self.requesting_entity_is_portfolio() or self.requesting_entity_is_suborganization(): - portfolio_view_requests_users = self.portfolio.portfolio_users_with_permissions( - permissions=[UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS] + portfolio_view_requests_users = self.portfolio.portfolio_users_with_permissions( # type: ignore + permissions=[UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS], + include_admin=True ) - cc_users = self.portfolio.portfolio_admin_users | portfolio_view_requests_users - cc_addresses = list(cc_users.values_list("email", flat=True)) + cc_addresses = list(portfolio_view_requests_users.values_list("email", flat=True)) + print("cc addresses: ", cc_addresses) send_templated_email( email_template, diff --git a/src/registrar/models/portfolio.py b/src/registrar/models/portfolio.py index 27501921a..9dd4d3f1d 100644 --- a/src/registrar/models/portfolio.py +++ b/src/registrar/models/portfolio.py @@ -4,6 +4,7 @@ from registrar.models.domain_request import DomainRequest from registrar.models.federal_agency import FederalAgency from registrar.models.user import User from registrar.models.utility.portfolio_helper import UserPortfolioRoleChoices +from django.db.models import Q from .utility.time_stamped_model import TimeStampedModel @@ -144,12 +145,20 @@ class Portfolio(TimeStampedModel): ).values_list("user__id", flat=True) return User.objects.filter(id__in=admin_ids) - def portfolio_users_with_permissions(self, permissions=[]): + def portfolio_users_with_permissions(self, permissions=[], include_admin=False): """Gets all users with specified additional permissions for this particular portfolio. Returns a queryset of User.""" portfolio_users = self.portfolio_users if permissions: - portfolio_users = portfolio_users.filter(additional_permissions__overlap=permissions) + if include_admin: + portfolio_users = portfolio_users.filter( + Q(additional_permissions__overlap=permissions) | + Q(roles__overlap=[ + UserPortfolioRoleChoices.ORGANIZATION_ADMIN, + ]), + ) + else: + portfolio_users = portfolio_users.filter(additional_permissions__overlap=permissions) user_ids = portfolio_users.values_list("user__id", flat=True) return User.objects.filter(id__in=user_ids) From f0d58c753c6d4ecbc1736aab0b2c134895c81a3a Mon Sep 17 00:00:00 2001 From: Erin Song <121973038+erinysong@users.noreply.github.com> Date: Thu, 23 Jan 2025 11:57:47 -0800 Subject: [PATCH 26/46] Fix linting --- src/registrar/models/domain_request.py | 5 ++--- src/registrar/models/portfolio.py | 10 ++++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py index 9f1a3539d..aae845cf2 100644 --- a/src/registrar/models/domain_request.py +++ b/src/registrar/models/domain_request.py @@ -959,9 +959,8 @@ class DomainRequest(TimeStampedModel): context["custom_email_content"] = custom_email_content if self.requesting_entity_is_portfolio() or self.requesting_entity_is_suborganization(): - portfolio_view_requests_users = self.portfolio.portfolio_users_with_permissions( # type: ignore - permissions=[UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS], - include_admin=True + portfolio_view_requests_users = self.portfolio.portfolio_users_with_permissions( # type: ignore + permissions=[UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS], include_admin=True ) cc_addresses = list(portfolio_view_requests_users.values_list("email", flat=True)) print("cc addresses: ", cc_addresses) diff --git a/src/registrar/models/portfolio.py b/src/registrar/models/portfolio.py index 9dd4d3f1d..2f88e9043 100644 --- a/src/registrar/models/portfolio.py +++ b/src/registrar/models/portfolio.py @@ -152,10 +152,12 @@ class Portfolio(TimeStampedModel): if permissions: if include_admin: portfolio_users = portfolio_users.filter( - Q(additional_permissions__overlap=permissions) | - Q(roles__overlap=[ - UserPortfolioRoleChoices.ORGANIZATION_ADMIN, - ]), + Q(additional_permissions__overlap=permissions) + | Q( + roles__overlap=[ + UserPortfolioRoleChoices.ORGANIZATION_ADMIN, + ] + ), ) else: portfolio_users = portfolio_users.filter(additional_permissions__overlap=permissions) From f4670fed52dd134515b7223c2772b3dfac411e71 Mon Sep 17 00:00:00 2001 From: CocoByte Date: Fri, 24 Jan 2025 14:56:11 -0700 Subject: [PATCH 27/46] restored labels --- src/registrar/admin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 5807c52f3..acefd349e 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -1693,7 +1693,7 @@ class DomainInformationAdmin(ListHeaderAdmin, ImportExportModelAdmin): ) # Filter out empty results and return sorted list of unique values - return sorted([(org, org) for org in queryset if org]) + return sorted([(org, DomainRequest.OrganizationChoices.get_org_label(org)) for org in queryset if org]) def queryset(self, request, queryset): if self.value(): @@ -2049,7 +2049,7 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): ) # Filter out empty results and return sorted list of unique values - return sorted([(org, org) for org in queryset if org]) + return sorted([(org, DomainRequest.OrganizationChoices.get_org_label(org)) for org in queryset if org]) def queryset(self, request, queryset): if self.value(): @@ -3264,7 +3264,7 @@ class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin): ) # Filter out empty results and return sorted list of unique values - return sorted([(org, org) for org in queryset if org]) + return sorted([(org, DomainRequest.OrganizationChoices.get_org_label(org)) for org in queryset if org]) def queryset(self, request, queryset): if self.value(): From ec351b055ae82908b6d6a89654ceb2b7faa4952d Mon Sep 17 00:00:00 2001 From: CocoByte Date: Fri, 24 Jan 2025 15:06:10 -0700 Subject: [PATCH 28/46] updated unit tests --- src/registrar/tests/test_admin_domain.py | 3 +-- src/registrar/tests/test_admin_request.py | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/registrar/tests/test_admin_domain.py b/src/registrar/tests/test_admin_domain.py index a86cdde4d..867bf1b82 100644 --- a/src/registrar/tests/test_admin_domain.py +++ b/src/registrar/tests/test_admin_domain.py @@ -779,8 +779,7 @@ class TestDomainAdminWithClient(TestCase): response = self.client.get("/admin/registrar/domain/") # There are 4 template references to Federal (4) plus four references in the table # for our actual domain_request - self.assertContains(response, "Federal", count=54) - self.assertContains(response, "federal", count=225) + self.assertContains(response, "Federal", count=56) # This may be a bit more robust self.assertContains(response, 'Federal', count=1) # Now let's make sure the long description does not exist diff --git a/src/registrar/tests/test_admin_request.py b/src/registrar/tests/test_admin_request.py index 9708e6439..f7dfed108 100644 --- a/src/registrar/tests/test_admin_request.py +++ b/src/registrar/tests/test_admin_request.py @@ -662,8 +662,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=52) - self.assertContains(response, "federal", count=383) + self.assertContains(response, "Federal", count=54) # This may be a bit more robust self.assertContains(response, 'Federal', count=1) # Now let's make sure the long description does not exist From 74182b9a52a7cbfb57ed9db08bf5da6ca538d92c Mon Sep 17 00:00:00 2001 From: CocoByte Date: Fri, 24 Jan 2025 15:06:46 -0700 Subject: [PATCH 29/46] use users.Count() for domain Request fixtures --- src/registrar/fixtures/fixtures_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/fixtures/fixtures_requests.py b/src/registrar/fixtures/fixtures_requests.py index c42b83ea8..d55b1b008 100644 --- a/src/registrar/fixtures/fixtures_requests.py +++ b/src/registrar/fixtures/fixtures_requests.py @@ -325,7 +325,7 @@ class DomainRequestFixture: @classmethod def _create_domain_requests(cls, users): # noqa: C901 """Creates DomainRequests given a list of users.""" - total_domain_requests_to_make = 10000 + total_domain_requests_to_make = users.count() # 100000 # Check if the database is already populated with the desired # number of entries. From c33ce78a2bdafef972518ba9d38ef5d03ae29a0a Mon Sep 17 00:00:00 2001 From: CocoByte Date: Fri, 24 Jan 2025 15:07:43 -0700 Subject: [PATCH 30/46] linted --- src/registrar/fixtures/fixtures_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/fixtures/fixtures_requests.py b/src/registrar/fixtures/fixtures_requests.py index d55b1b008..87b56c168 100644 --- a/src/registrar/fixtures/fixtures_requests.py +++ b/src/registrar/fixtures/fixtures_requests.py @@ -325,7 +325,7 @@ class DomainRequestFixture: @classmethod def _create_domain_requests(cls, users): # noqa: C901 """Creates DomainRequests given a list of users.""" - total_domain_requests_to_make = users.count() # 100000 + total_domain_requests_to_make = users.count() # 100000 # Check if the database is already populated with the desired # number of entries. From 074ecf6f669cb6f2c946d8f1bc4f17987aab2cba Mon Sep 17 00:00:00 2001 From: Erin Song <121973038+erinysong@users.noreply.github.com> Date: Mon, 27 Jan 2025 09:39:30 -0800 Subject: [PATCH 31/46] Remove print statement --- src/registrar/models/domain_request.py | 1 - .../emails/domain_manager_removed.txt | 31 +++++++++++++++++++ .../emails/domain_manager_removed_subject.txt | 1 + src/registrar/views/domain.py | 3 ++ 4 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 src/registrar/templates/emails/domain_manager_removed.txt create mode 100644 src/registrar/templates/emails/domain_manager_removed_subject.txt diff --git a/src/registrar/models/domain_request.py b/src/registrar/models/domain_request.py index aae845cf2..c5a0926ad 100644 --- a/src/registrar/models/domain_request.py +++ b/src/registrar/models/domain_request.py @@ -963,7 +963,6 @@ class DomainRequest(TimeStampedModel): permissions=[UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS], include_admin=True ) cc_addresses = list(portfolio_view_requests_users.values_list("email", flat=True)) - print("cc addresses: ", cc_addresses) send_templated_email( email_template, diff --git a/src/registrar/templates/emails/domain_manager_removed.txt b/src/registrar/templates/emails/domain_manager_removed.txt new file mode 100644 index 000000000..01bdd66d6 --- /dev/null +++ b/src/registrar/templates/emails/domain_manager_removed.txt @@ -0,0 +1,31 @@ +{% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #} +Hi, {{ recipient.first_name }}. + +A domain manager was removed from {{ domain.name }}. + +REMOVED BY: {{ "populate" }} +REMOVED ON: {{date}} +MANAGER REMOVED: {{ "populate" }} + + +---------------------------------------------------------------- + + +WHY DID YOU RECEIVE THIS EMAIL? + +You’re listed as a domain manager for {{ domain.name }}, so you’ll receive a notification whenever a domain manager is removed from that domain. +If you have questions or concerns, reach out to the person who removed the domain manager or reply to this email. + + +THANK YOU + +.Gov helps the public identify official, trusted information. Thank you for using a .gov domain. + +---------------------------------------------------------------- + +The .gov team +Contact us +Learn about .gov + +The .gov registry is a part of the Cybersecurity and Infrastructure Security Agency (CISA) . +{% endautoescape %} diff --git a/src/registrar/templates/emails/domain_manager_removed_subject.txt b/src/registrar/templates/emails/domain_manager_removed_subject.txt new file mode 100644 index 000000000..c84a20f18 --- /dev/null +++ b/src/registrar/templates/emails/domain_manager_removed_subject.txt @@ -0,0 +1 @@ +A domain manager was removed from {{ domain.name }} \ No newline at end of file diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index f82d7005d..1c21f54b7 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -1336,6 +1336,9 @@ class DomainDeleteUserView(UserDomainRolePermissionDeleteView): # Is the user deleting themselves? If so, display a different message delete_self = self.request.user == self.object.user + # Email domain managers + + # Add a success message messages.success(self.request, self.get_success_message(delete_self)) return redirect(self.get_success_url()) From ae70f0396727d7e617476b22617dfffd499f7961 Mon Sep 17 00:00:00 2001 From: Erin Song <121973038+erinysong@users.noreply.github.com> Date: Mon, 27 Jan 2025 09:40:43 -0800 Subject: [PATCH 32/46] Remove unused email templates --- .../emails/domain_manager_removed.txt | 31 ------------------- .../emails/domain_manager_removed_subject.txt | 1 - 2 files changed, 32 deletions(-) delete mode 100644 src/registrar/templates/emails/domain_manager_removed.txt delete mode 100644 src/registrar/templates/emails/domain_manager_removed_subject.txt diff --git a/src/registrar/templates/emails/domain_manager_removed.txt b/src/registrar/templates/emails/domain_manager_removed.txt deleted file mode 100644 index 01bdd66d6..000000000 --- a/src/registrar/templates/emails/domain_manager_removed.txt +++ /dev/null @@ -1,31 +0,0 @@ -{% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #} -Hi, {{ recipient.first_name }}. - -A domain manager was removed from {{ domain.name }}. - -REMOVED BY: {{ "populate" }} -REMOVED ON: {{date}} -MANAGER REMOVED: {{ "populate" }} - - ----------------------------------------------------------------- - - -WHY DID YOU RECEIVE THIS EMAIL? - -You’re listed as a domain manager for {{ domain.name }}, so you’ll receive a notification whenever a domain manager is removed from that domain. -If you have questions or concerns, reach out to the person who removed the domain manager or reply to this email. - - -THANK YOU - -.Gov helps the public identify official, trusted information. Thank you for using a .gov domain. - ----------------------------------------------------------------- - -The .gov team -Contact us -Learn about .gov - -The .gov registry is a part of the Cybersecurity and Infrastructure Security Agency (CISA) . -{% endautoescape %} diff --git a/src/registrar/templates/emails/domain_manager_removed_subject.txt b/src/registrar/templates/emails/domain_manager_removed_subject.txt deleted file mode 100644 index c84a20f18..000000000 --- a/src/registrar/templates/emails/domain_manager_removed_subject.txt +++ /dev/null @@ -1 +0,0 @@ -A domain manager was removed from {{ domain.name }} \ No newline at end of file From d64a48934c1e681ae06b0d55a7a640135277b978 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Mon, 27 Jan 2025 15:31:48 -0500 Subject: [PATCH 33/46] update to portfolio model and associated tests --- src/registrar/models/portfolio.py | 10 ++++++++++ src/registrar/tests/test_models.py | 32 ++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/registrar/models/portfolio.py b/src/registrar/models/portfolio.py index 82afcd4d6..3803f8e5b 100644 --- a/src/registrar/models/portfolio.py +++ b/src/registrar/models/portfolio.py @@ -122,6 +122,16 @@ class Portfolio(TimeStampedModel): if self.state_territory != self.StateTerritoryChoices.PUERTO_RICO and self.urbanization: self.urbanization = None + # If the org type is federal, and org federal agency is not blank, and is a federal agency + # overwrite the organization name with the federal agency's agency + if ( + self.organization_type == self.OrganizationChoices.FEDERAL + and self.federal_agency + and self.federal_agency != FederalAgency.get_non_federal_agency() + and self.federal_agency.agency + ): + self.organization_name = self.federal_agency.agency + super().save(*args, **kwargs) @property diff --git a/src/registrar/tests/test_models.py b/src/registrar/tests/test_models.py index d8db0f043..ef811e083 100644 --- a/src/registrar/tests/test_models.py +++ b/src/registrar/tests/test_models.py @@ -2073,13 +2073,18 @@ class TestPortfolio(TestCase): self.user, _ = User.objects.get_or_create( username="intern@igorville.com", email="intern@igorville.com", first_name="Lava", last_name="World" ) + self.non_federal_agency, _ = FederalAgency.objects.get_or_create(agency="Non-Federal Agency") + self.federal_agency, _ = FederalAgency.objects.get_or_create(agency="Federal Agency") super().setUp() def tearDown(self): super().tearDown() Portfolio.objects.all().delete() + self.federal_agency.delete() + # not deleting non_federal_agency so as not to interfere potentially with other tests User.objects.all().delete() + @less_console_noise_decorator def test_urbanization_field_resets_when_not_puetro_rico(self): """The urbanization field should only be populated when the state is puetro rico. Otherwise, this field should be empty.""" @@ -2100,6 +2105,7 @@ class TestPortfolio(TestCase): self.assertEqual(portfolio.urbanization, None) self.assertEqual(portfolio.state_territory, DomainRequest.StateTerritoryChoices.ALABAMA) + @less_console_noise_decorator def test_can_add_urbanization_field(self): """Ensures that you can populate the urbanization field when conditions are right""" # Create a portfolio that cannot have this field @@ -2121,6 +2127,32 @@ class TestPortfolio(TestCase): self.assertEqual(portfolio.urbanization, "test123") self.assertEqual(portfolio.state_territory, DomainRequest.StateTerritoryChoices.PUERTO_RICO) + @less_console_noise_decorator + def test_organization_name_updates_for_federal_agency(self): + # Create a Portfolio instance with a federal agency + portfolio = Portfolio( + creator=self.user, + organization_type=DomainRequest.OrganizationChoices.FEDERAL, + federal_agency=self.federal_agency, + ) + portfolio.save() + + # Assert that organization_name is updated to the federal agency's name + self.assertEqual(portfolio.organization_name, "Federal Agency") + + @less_console_noise_decorator + def test_organization_name_does_not_update_for_non_federal_agency(self): + # Create a Portfolio instance with a non-federal agency + portfolio = Portfolio( + creator=self.user, + organization_type=DomainRequest.OrganizationChoices.FEDERAL, + federal_agency=self.non_federal_agency, + ) + portfolio.save() + + # Assert that organization_name remains None + self.assertIsNone(portfolio.organization_name) + class TestAllowedEmail(TestCase): """Tests our allowed email whitelist""" From cd0fbe4cda94e634ca40331a3a68fcbc6ad5496c Mon Sep 17 00:00:00 2001 From: CocoByte Date: Mon, 27 Jan 2025 15:34:36 -0700 Subject: [PATCH 34/46] PR feedback --- src/registrar/admin.py | 6 +++--- src/registrar/fixtures/fixtures_requests.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index acefd349e..bc79cab60 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -2091,12 +2091,12 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): ) # Filter out empty values and return sorted unique entries - return sorted([(federal_type, federal_type) for federal_type in queryset if federal_type]) + return sorted([(federal_type, DomainRequest.BranchChoices.get_branch_label(federal_type)) for federal_type in queryset if federal_type]) def queryset(self, request, queryset): if self.value(): return queryset.filter( - Q(portfolio__federal_type=self.value()) | Q(portfolio__isnull=True, federal_type=self.value()) + Q(portfolio__federal_agency__federal_type=self.value()) | Q(portfolio__isnull=True, federal_type=self.value()) ) return queryset @@ -3307,7 +3307,7 @@ class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin): ) # Filter out empty values and return sorted unique entries - return sorted([(federal_type, federal_type) for federal_type in queryset if federal_type]) + return sorted([(federal_type, DomainRequest.BranchChoices.get_branch_label(federal_type)) for federal_type in queryset if federal_type]) def queryset(self, request, queryset): if self.value(): diff --git a/src/registrar/fixtures/fixtures_requests.py b/src/registrar/fixtures/fixtures_requests.py index 87b56c168..c4d824b37 100644 --- a/src/registrar/fixtures/fixtures_requests.py +++ b/src/registrar/fixtures/fixtures_requests.py @@ -325,7 +325,7 @@ class DomainRequestFixture: @classmethod def _create_domain_requests(cls, users): # noqa: C901 """Creates DomainRequests given a list of users.""" - total_domain_requests_to_make = users.count() # 100000 + total_domain_requests_to_make = len(users) # 100000 # Check if the database is already populated with the desired # number of entries. From 4f217e49708f68a50d43067b0230a2c9c047f6c6 Mon Sep 17 00:00:00 2001 From: CocoByte Date: Mon, 27 Jan 2025 15:47:53 -0700 Subject: [PATCH 35/46] Sneaking in fix for portfolio unit test --- src/registrar/tests/test_management_scripts.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/registrar/tests/test_management_scripts.py b/src/registrar/tests/test_management_scripts.py index 334d7d83c..965f98f51 100644 --- a/src/registrar/tests/test_management_scripts.py +++ b/src/registrar/tests/test_management_scripts.py @@ -2217,6 +2217,11 @@ class TestRemovePortfolios(TestCase): def tearDown(self): self.logger_patcher.stop() + DomainInformation.objects.all().delete() + DomainRequest.objects.all().delete() + Suborganization.objects.all().delete() + Portfolio.objects.all().delete() + User.objects.all().delete() @patch("registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no") def test_delete_unlisted_portfolios(self, mock_query_yes_no): From 346af8605d772c6d688c90f8ef7bff048efe4381 Mon Sep 17 00:00:00 2001 From: Erin Song <121973038+erinysong@users.noreply.github.com> Date: Mon, 27 Jan 2025 16:07:23 -0800 Subject: [PATCH 36/46] Add cc tests --- src/registrar/tests/test_models_requests.py | 44 ++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/registrar/tests/test_models_requests.py b/src/registrar/tests/test_models_requests.py index 983a12b3c..ad7cf996b 100644 --- a/src/registrar/tests/test_models_requests.py +++ b/src/registrar/tests/test_models_requests.py @@ -5,6 +5,7 @@ from unittest.mock import patch from registrar.models import ( + AllowedEmail, Contact, DomainRequest, DomainInformation, @@ -16,7 +17,9 @@ from registrar.models import ( AllowedEmail, Portfolio, Suborganization, + UserPortfolioPermission ) +from registrar.models.utility.portfolio_helper import UserPortfolioRoleChoices import boto3_mocking from registrar.utility.constants import BranchChoices @@ -46,6 +49,11 @@ class TestDomainRequest(TestCase): self.dummy_user_2, _ = User.objects.get_or_create( username="intern@igorville.com", email="intern@igorville.com", first_name="Lava", last_name="World" ) + + self.dummy_user_3, _ = User.objects.get_or_create( + username="portfolioadmin@igorville.com", email="portfolioadmin@igorville.com", first_name="Portfolio", last_name="Admin" + ) + self.started_domain_request = completed_domain_request( status=DomainRequest.DomainRequestStatus.STARTED, name="started.gov", @@ -273,7 +281,7 @@ class TestDomainRequest(TestCase): self.assertEqual(domain_request.status, domain_request.DomainRequestStatus.SUBMITTED) def check_email_sent( - self, domain_request, msg, action, expected_count, expected_content=None, expected_email="mayor@igorville.com" + self, domain_request, msg, action, expected_count, expected_content=None, expected_email="mayor@igorville.com", expected_cc=[] ): """Check if an email was sent after performing an action.""" email_allowed, _ = AllowedEmail.objects.get_or_create(email=expected_email) @@ -292,6 +300,11 @@ class TestDomainRequest(TestCase): ] self.assertEqual(len(sent_emails), expected_count) + if expected_cc: + sent_cc_adddresses = sent_emails[0]["kwargs"]["Destination"]['CcAddresses'] + for cc_address in expected_cc: + self.assertIn(cc_address, sent_cc_adddresses) + if expected_content: email_content = sent_emails[0]["kwargs"]["Content"]["Simple"]["Body"]["Text"]["Data"] self.assertIn(expected_content, email_content) @@ -1074,6 +1087,35 @@ class TestDomainRequest(TestCase): self.assertEqual(domain_request2.generic_org_type, domain_request2.converted_generic_org_type) self.assertEqual(domain_request2.federal_agency, domain_request2.converted_federal_agency) + @less_console_noise_decorator + def test_portfolio_domain_requests_cc_requests_viewers(self): + """test that portfolio domain request emails cc portfolio members who have read requests access""" + fed_agency = FederalAgency.objects.filter(agency="Non-Federal Agency").first() + portfolio = Portfolio.objects.create( + organization_name="Test Portfolio", + creator=self.dummy_user_2, + federal_agency=fed_agency, + organization_type=DomainRequest.OrganizationChoices.FEDERAL, + ) + user_portfolio_permission = UserPortfolioPermission.objects.create( + user=self.dummy_user_3, + portfolio=portfolio, + roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN] + ) + # Adds cc'ed email in this test's allow list + AllowedEmail.objects.create(email="portfolioadmin@igorville.com") + + msg = "Create a domain request and submit it and see if email cc's portfolio admin and members who can view requests." + domain_request = completed_domain_request( + name="test.gov", + user=self.dummy_user_2, + portfolio=portfolio, + organization_name="Test Portfolio" + ) + self.check_email_sent( + domain_request, msg, "submit", 1, expected_email="intern@igorville.com", expected_cc=['portfolioadmin@igorville.com'] + ) + class TestDomainRequestSuborganization(TestCase): """Tests for the suborganization fields on domain requests""" From 84c6d192e897f0c98f9c7e9192b5ab7781ed5095 Mon Sep 17 00:00:00 2001 From: Erin Song <121973038+erinysong@users.noreply.github.com> Date: Mon, 27 Jan 2025 16:09:53 -0800 Subject: [PATCH 37/46] Fix linting --- src/registrar/tests/test_models_requests.py | 34 +++++++++++++-------- src/registrar/views/domain.py | 1 - 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/registrar/tests/test_models_requests.py b/src/registrar/tests/test_models_requests.py index ad7cf996b..b1300a1e9 100644 --- a/src/registrar/tests/test_models_requests.py +++ b/src/registrar/tests/test_models_requests.py @@ -17,7 +17,7 @@ from registrar.models import ( AllowedEmail, Portfolio, Suborganization, - UserPortfolioPermission + UserPortfolioPermission, ) from registrar.models.utility.portfolio_helper import UserPortfolioRoleChoices @@ -51,7 +51,10 @@ class TestDomainRequest(TestCase): ) self.dummy_user_3, _ = User.objects.get_or_create( - username="portfolioadmin@igorville.com", email="portfolioadmin@igorville.com", first_name="Portfolio", last_name="Admin" + username="portfolioadmin@igorville.com", + email="portfolioadmin@igorville.com", + first_name="Portfolio", + last_name="Admin", ) self.started_domain_request = completed_domain_request( @@ -281,7 +284,14 @@ class TestDomainRequest(TestCase): self.assertEqual(domain_request.status, domain_request.DomainRequestStatus.SUBMITTED) def check_email_sent( - self, domain_request, msg, action, expected_count, expected_content=None, expected_email="mayor@igorville.com", expected_cc=[] + self, + domain_request, + msg, + action, + expected_count, + expected_content=None, + expected_email="mayor@igorville.com", + expected_cc=[], ): """Check if an email was sent after performing an action.""" email_allowed, _ = AllowedEmail.objects.get_or_create(email=expected_email) @@ -301,7 +311,7 @@ class TestDomainRequest(TestCase): self.assertEqual(len(sent_emails), expected_count) if expected_cc: - sent_cc_adddresses = sent_emails[0]["kwargs"]["Destination"]['CcAddresses'] + sent_cc_adddresses = sent_emails[0]["kwargs"]["Destination"]["CcAddresses"] for cc_address in expected_cc: self.assertIn(cc_address, sent_cc_adddresses) @@ -1098,22 +1108,22 @@ class TestDomainRequest(TestCase): organization_type=DomainRequest.OrganizationChoices.FEDERAL, ) user_portfolio_permission = UserPortfolioPermission.objects.create( - user=self.dummy_user_3, - portfolio=portfolio, - roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN] + user=self.dummy_user_3, portfolio=portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN] ) # Adds cc'ed email in this test's allow list AllowedEmail.objects.create(email="portfolioadmin@igorville.com") msg = "Create a domain request and submit it and see if email cc's portfolio admin and members who can view requests." domain_request = completed_domain_request( - name="test.gov", - user=self.dummy_user_2, - portfolio=portfolio, - organization_name="Test Portfolio" + name="test.gov", user=self.dummy_user_2, portfolio=portfolio, organization_name="Test Portfolio" ) self.check_email_sent( - domain_request, msg, "submit", 1, expected_email="intern@igorville.com", expected_cc=['portfolioadmin@igorville.com'] + domain_request, + msg, + "submit", + 1, + expected_email="intern@igorville.com", + expected_cc=["portfolioadmin@igorville.com"], ) diff --git a/src/registrar/views/domain.py b/src/registrar/views/domain.py index 1c21f54b7..5cac3c667 100644 --- a/src/registrar/views/domain.py +++ b/src/registrar/views/domain.py @@ -1337,7 +1337,6 @@ class DomainDeleteUserView(UserDomainRolePermissionDeleteView): delete_self = self.request.user == self.object.user # Email domain managers - # Add a success message messages.success(self.request, self.get_success_message(delete_self)) From 007506404ca51aa5ffb484ae96bdfbf4e06ae620 Mon Sep 17 00:00:00 2001 From: Erin Song <121973038+erinysong@users.noreply.github.com> Date: Mon, 27 Jan 2025 16:18:57 -0800 Subject: [PATCH 38/46] Fix linting --- src/registrar/tests/test_models_requests.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/registrar/tests/test_models_requests.py b/src/registrar/tests/test_models_requests.py index b1300a1e9..0da09fd45 100644 --- a/src/registrar/tests/test_models_requests.py +++ b/src/registrar/tests/test_models_requests.py @@ -5,7 +5,6 @@ from unittest.mock import patch from registrar.models import ( - AllowedEmail, Contact, DomainRequest, DomainInformation, @@ -1107,13 +1106,14 @@ class TestDomainRequest(TestCase): federal_agency=fed_agency, organization_type=DomainRequest.OrganizationChoices.FEDERAL, ) - user_portfolio_permission = UserPortfolioPermission.objects.create( + user_portfolio_permission = UserPortfolioPermission.objects.create( # ignore user=self.dummy_user_3, portfolio=portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN] ) # Adds cc'ed email in this test's allow list AllowedEmail.objects.create(email="portfolioadmin@igorville.com") - msg = "Create a domain request and submit it and see if email cc's portfolio admin and members who can view requests." + msg = "Create a domain request and submit it and see if email cc's portfolio admin and members who can view \ + requests." domain_request = completed_domain_request( name="test.gov", user=self.dummy_user_2, portfolio=portfolio, organization_name="Test Portfolio" ) From eefb64996329565a9928b83be46582a3358de557 Mon Sep 17 00:00:00 2001 From: Erin Song <121973038+erinysong@users.noreply.github.com> Date: Mon, 27 Jan 2025 16:19:48 -0800 Subject: [PATCH 39/46] Fix linting --- src/registrar/tests/test_models_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/tests/test_models_requests.py b/src/registrar/tests/test_models_requests.py index 0da09fd45..20264f056 100644 --- a/src/registrar/tests/test_models_requests.py +++ b/src/registrar/tests/test_models_requests.py @@ -1106,7 +1106,7 @@ class TestDomainRequest(TestCase): federal_agency=fed_agency, organization_type=DomainRequest.OrganizationChoices.FEDERAL, ) - user_portfolio_permission = UserPortfolioPermission.objects.create( # ignore + user_portfolio_permission = UserPortfolioPermission.objects.create( # type: ignore user=self.dummy_user_3, portfolio=portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN] ) # Adds cc'ed email in this test's allow list From c6eb4432f083bd5a01f08c0fc3c796ffab85b867 Mon Sep 17 00:00:00 2001 From: Erin Song <121973038+erinysong@users.noreply.github.com> Date: Mon, 27 Jan 2025 16:21:34 -0800 Subject: [PATCH 40/46] Fix linting --- src/registrar/tests/test_models_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/tests/test_models_requests.py b/src/registrar/tests/test_models_requests.py index 20264f056..c3528311d 100644 --- a/src/registrar/tests/test_models_requests.py +++ b/src/registrar/tests/test_models_requests.py @@ -1106,7 +1106,7 @@ class TestDomainRequest(TestCase): federal_agency=fed_agency, organization_type=DomainRequest.OrganizationChoices.FEDERAL, ) - user_portfolio_permission = UserPortfolioPermission.objects.create( # type: ignore + user_portfolio_permission = UserPortfolioPermission.objects.create( # noqa: F841 user=self.dummy_user_3, portfolio=portfolio, roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN] ) # Adds cc'ed email in this test's allow list From 3c9b51bc30ab59665a3c1e08e88ffb494834ec2e Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Tue, 28 Jan 2025 06:24:26 -0500 Subject: [PATCH 41/46] fixed export tests --- src/registrar/tests/test_reports.py | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/registrar/tests/test_reports.py b/src/registrar/tests/test_reports.py index 9d410e430..ae48143d2 100644 --- a/src/registrar/tests/test_reports.py +++ b/src/registrar/tests/test_reports.py @@ -255,10 +255,10 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib): "Organization name,City,State,SO,SO email," "Security contact email,Domain managers,Invited domain managers\n" "adomain2.gov,Dns needed,(blank),(blank),Federal - Executive," - "Portfolio 1 Federal Agency,,,, ,,(blank)," + "Portfolio 1 Federal Agency,Portfolio 1 Federal Agency,,, ,,(blank)," "meoward@rocks.com,squeaker@rocks.com\n" "defaultsecurity.gov,Ready,2023-11-01,(blank),Federal - Executive," - "Portfolio 1 Federal Agency,,,, ,,(blank)," + "Portfolio 1 Federal Agency,Portfolio 1 Federal Agency,,, ,,(blank)," '"big_lebowski@dude.co, info@example.com, meoward@rocks.com",woofwardthethird@rocks.com\n' "cdomain11.gov,Ready,2024-04-02,(blank),Federal - Executive," "World War I Centennial Commission,,,, ,,(blank)," @@ -280,6 +280,7 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib): # spaces and leading/trailing whitespace csv_content = csv_content.replace(",,", "").replace(",", "").replace(" ", "").replace("\r\n", "\n").strip() expected_content = expected_content.replace(",,", "").replace(",", "").replace(" ", "").strip() + self.maxDiff = None self.assertEqual(csv_content, expected_content) @less_console_noise_decorator @@ -316,9 +317,11 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib): expected_content = ( "Domain name,Status,First ready on,Expiration date,Domain type,Agency,Organization name," "City,State,SO,SO email,Security contact email,Domain managers,Invited domain managers\n" - "adomain2.gov,Dns needed,(blank),(blank),Federal - Executive,Portfolio 1 Federal Agency,,,, ,,(blank)," + "adomain2.gov,Dns needed,(blank),(blank),Federal - Executive,Portfolio 1 Federal Agency," + "Portfolio 1 Federal Agency,,, ,,(blank)," '"info@example.com, meoward@rocks.com",squeaker@rocks.com\n' - "defaultsecurity.gov,Ready,2023-11-01,(blank),Federal - Executive,Portfolio 1 Federal Agency,,,, ,,(blank)," + "defaultsecurity.gov,Ready,2023-11-01,(blank),Federal - Executive,Portfolio 1 Federal Agency," + "Portfolio 1 Federal Agency,,, ,,(blank)," '"big_lebowski@dude.co, info@example.com, meoward@rocks.com",woofwardthethird@rocks.com\n' ) @@ -326,6 +329,7 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib): # spaces and leading/trailing whitespace csv_content = csv_content.replace(",,", "").replace(",", "").replace(" ", "").replace("\r\n", "\n").strip() expected_content = expected_content.replace(",,", "").replace(",", "").replace(" ", "").strip() + self.maxDiff = None self.assertEqual(csv_content, expected_content) @less_console_noise_decorator @@ -587,7 +591,7 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib): expected_content = ( "Domain name,Domain type,Agency,Organization name,City," "State,Status,Expiration date, Deleted\n" - "cdomain1.gov,Federal-Executive,Portfolio1FederalAgency,Ready,(blank)\n" + "cdomain1.gov,Federal-Executive,Portfolio1FederalAgency,Portfolio1FederalAgency,Ready,(blank)\n" "adomain10.gov,Federal,ArmedForcesRetirementHome,Ready,(blank)\n" "cdomain11.gov,Federal-Executive,WorldWarICentennialCommission,Ready,(blank)\n" "zdomain12.gov,Interstate,Ready,(blank)\n" @@ -601,6 +605,7 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib): csv_content.replace(",,", "").replace(",", "").replace(" ", "").replace("\r\n", "\n").strip() ) expected_content = expected_content.replace(",,", "").replace(",", "").replace(" ", "").strip() + self.maxDiff = None self.assertEqual(csv_content, expected_content) @less_console_noise_decorator @@ -780,9 +785,11 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib): "city5.gov,Approved,Federal,No,Executive,,Testorg,N/A,,NY,2,requested_suborg,SanFran,CA,,,,,1,0," "city1.gov,Testy,Tester,testy@town.com,Chief Tester,Purpose of the site,There is more," "Testy Tester testy2@town.com,,city.com,\n" - "city2.gov,In review,Federal,Yes,Executive,Portfolio 1 Federal Agency,,N/A,,,2,SubOrg 1,,,,,,,0," - "1,city1.gov,,,,,Purpose of the site,There is more,Testy Tester testy2@town.com,,city.com,\n" - "city3.gov,Submitted,Federal,Yes,Executive,Portfolio 1 Federal Agency,,N/A,,,2,,,,,,,,0,1," + "city2.gov,In review,Federal,Yes,Executive,Portfolio 1 Federal Agency,Portfolio 1 Federal Agency," + "N/A,,,2,SubOrg 1,,,,,,,0,1,city1.gov,,,,,Purpose of the site,There is more," + "Testy Tester testy2@town.com,,city.com,\n" + "city3.gov,Submitted,Federal,Yes,Executive,Portfolio 1 Federal Agency,Portfolio 1 Federal Agency," + "N/A,,,2,,,,,,,,0,1," '"cheeseville.gov, city1.gov, igorville.gov",,,,,Purpose of the site,CISA-first-name CISA-last-name | ' 'There is more,"Meow Tester24 te2@town.com, Testy1232 Tester24 te2@town.com, ' 'Testy Tester testy2@town.com",' @@ -792,9 +799,9 @@ class ExportDataTest(MockDbForIndividualTests, MockEppLib): "Chief Tester,Purpose of the site,CISA-first-name CISA-last-name | There is more," "Testy Tester testy2@town.com," "cisaRep@igorville.gov,city.com,\n" - "city6.gov,Submitted,Federal,Yes,Executive,Portfolio 1 Federal Agency,,N/A,,,2,,,,,,,,0,1,city1.gov," - ",,,,Purpose of the site,CISA-first-name CISA-last-name | There is more,Testy Tester testy2@town.com," - "cisaRep@igorville.gov,city.com,\n" + "city6.gov,Submitted,Federal,Yes,Executive,Portfolio 1 Federal Agency,Portfolio 1 Federal Agency,N/A," + ",,2,,,,,,,,0,1,city1.gov,,,,,Purpose of the site,CISA-first-name CISA-last-name | There is more," + "Testy Tester testy2@town.com,cisaRep@igorville.gov,city.com,\n" ) # Normalize line endings and remove commas, From c496d0102ef234b82f7e8e2131acd2d1690d8842 Mon Sep 17 00:00:00 2001 From: CuriousX Date: Tue, 28 Jan 2025 10:45:32 -0700 Subject: [PATCH 42/46] Update src/registrar/admin.py Co-authored-by: zandercymatics <141044360+zandercymatics@users.noreply.github.com> --- 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 1ca628d7c..f37b9645d 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -3307,7 +3307,7 @@ class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin): ) # Filter out empty values and return sorted unique entries - return sorted([(federal_type, DomainRequest.BranchChoices.get_branch_label(federal_type)) for federal_type in queryset if federal_type]) + return sorted([(federal_type, BranchChoices.get_branch_label(federal_type)) for federal_type in queryset if federal_type]) def queryset(self, request, queryset): if self.value(): From b124c1bb11d16aca21f596c280fc1e1153c0ed32 Mon Sep 17 00:00:00 2001 From: CuriousX Date: Tue, 28 Jan 2025 10:45:41 -0700 Subject: [PATCH 43/46] Update src/registrar/admin.py Co-authored-by: zandercymatics <141044360+zandercymatics@users.noreply.github.com> --- 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 f37b9645d..3ebd452ec 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -2091,7 +2091,7 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): ) # Filter out empty values and return sorted unique entries - return sorted([(federal_type, DomainRequest.BranchChoices.get_branch_label(federal_type)) for federal_type in queryset if federal_type]) + return sorted([(federal_type, BranchChoices.get_branch_label(federal_type)) for federal_type in queryset if federal_type]) def queryset(self, request, queryset): if self.value(): From bc2ffb3f708523be495b10bc9a6a4b5ce3feabe7 Mon Sep 17 00:00:00 2001 From: CocoByte Date: Tue, 28 Jan 2025 10:47:24 -0700 Subject: [PATCH 44/46] linted --- src/registrar/admin.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 3ebd452ec..4e38baac2 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -2091,12 +2091,19 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin): ) # Filter out empty values and return sorted unique entries - return sorted([(federal_type, BranchChoices.get_branch_label(federal_type)) for federal_type in queryset if federal_type]) + return sorted( + [ + (federal_type, BranchChoices.get_branch_label(federal_type)) + for federal_type in queryset + if federal_type + ] + ) def queryset(self, request, queryset): if self.value(): return queryset.filter( - Q(portfolio__federal_agency__federal_type=self.value()) | Q(portfolio__isnull=True, federal_type=self.value()) + Q(portfolio__federal_agency__federal_type=self.value()) + | Q(portfolio__isnull=True, federal_type=self.value()) ) return queryset @@ -3307,7 +3314,13 @@ class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin): ) # Filter out empty values and return sorted unique entries - return sorted([(federal_type, BranchChoices.get_branch_label(federal_type)) for federal_type in queryset if federal_type]) + return sorted( + [ + (federal_type, BranchChoices.get_branch_label(federal_type)) + for federal_type in queryset + if federal_type + ] + ) def queryset(self, request, queryset): if self.value(): From ab634516e90ec3c60d8c4fc5620c10b177cf2272 Mon Sep 17 00:00:00 2001 From: CocoByte Date: Tue, 28 Jan 2025 11:27:33 -0700 Subject: [PATCH 45/46] fix --- 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 4e38baac2..8ecf36f52 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -3297,7 +3297,7 @@ class DomainAdmin(ListHeaderAdmin, ImportExportModelAdmin): When( domain_info__isnull=False, domain_info__portfolio__isnull=False, - then=F("domain_info__portfolio__organization_type"), + then=F("domain_info__portfolio__federal_agency__federal_type"), ), When( domain_info__isnull=False, From 587621380c43298ca9e6df83359127df1ab55359 Mon Sep 17 00:00:00 2001 From: Erin Song <121973038+erinysong@users.noreply.github.com> Date: Tue, 28 Jan 2025 10:36:59 -0800 Subject: [PATCH 46/46] Change Requested by field from name to email --- .../emails/action_needed_reasons/already_has_a_domain.txt | 2 +- .../templates/emails/action_needed_reasons/bad_name.txt | 2 +- .../emails/action_needed_reasons/eligibility_unclear.txt | 2 +- .../action_needed_reasons/questionable_senior_official.txt | 2 +- src/registrar/templates/emails/domain_request_withdrawn.txt | 2 +- src/registrar/templates/emails/status_change_approved.txt | 2 +- src/registrar/templates/emails/status_change_rejected.txt | 2 +- src/registrar/templates/emails/submission_confirmation.txt | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/registrar/templates/emails/action_needed_reasons/already_has_a_domain.txt b/src/registrar/templates/emails/action_needed_reasons/already_has_a_domain.txt index 0f190f475..0f87ef60e 100644 --- a/src/registrar/templates/emails/action_needed_reasons/already_has_a_domain.txt +++ b/src/registrar/templates/emails/action_needed_reasons/already_has_a_domain.txt @@ -4,7 +4,7 @@ Hi, {{ recipient.first_name }}. We've identified an action that you’ll need to complete before we continue reviewing your .gov domain request. DOMAIN REQUESTED: {{ domain_request.requested_domain.name }} -REQUESTED BY: {{ domain_request.creator.first_name }} {{ domain_request.creator.last_name }} +REQUESTED BY: {{ domain_request.creator.email }} REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }} STATUS: Action needed diff --git a/src/registrar/templates/emails/action_needed_reasons/bad_name.txt b/src/registrar/templates/emails/action_needed_reasons/bad_name.txt index abeec88fa..ac563b549 100644 --- a/src/registrar/templates/emails/action_needed_reasons/bad_name.txt +++ b/src/registrar/templates/emails/action_needed_reasons/bad_name.txt @@ -4,7 +4,7 @@ Hi, {{ recipient.first_name }}. We've identified an action that you’ll need to complete before we continue reviewing your .gov domain request. DOMAIN REQUESTED: {{ domain_request.requested_domain.name }} -REQUESTED BY: {{ domain_request.creator.first_name }} {{ domain_request.creator.last_name }} +REQUESTED BY: {{ domain_request.creator.email }} REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }} STATUS: Action needed diff --git a/src/registrar/templates/emails/action_needed_reasons/eligibility_unclear.txt b/src/registrar/templates/emails/action_needed_reasons/eligibility_unclear.txt index 59713bd81..649dd76fb 100644 --- a/src/registrar/templates/emails/action_needed_reasons/eligibility_unclear.txt +++ b/src/registrar/templates/emails/action_needed_reasons/eligibility_unclear.txt @@ -4,7 +4,7 @@ Hi, {{ recipient.first_name }}. We've identified an action that you’ll need to complete before we continue reviewing your .gov domain request. DOMAIN REQUESTED: {{ domain_request.requested_domain.name }} -REQUESTED BY: {{ domain_request.creator.first_name }} {{ domain_request.creator.last_name }} +REQUESTED BY: {{ domain_request.creator.email }} REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }} STATUS: Action needed diff --git a/src/registrar/templates/emails/action_needed_reasons/questionable_senior_official.txt b/src/registrar/templates/emails/action_needed_reasons/questionable_senior_official.txt index f0824b06d..ef05e17d7 100644 --- a/src/registrar/templates/emails/action_needed_reasons/questionable_senior_official.txt +++ b/src/registrar/templates/emails/action_needed_reasons/questionable_senior_official.txt @@ -4,7 +4,7 @@ Hi, {{ recipient.first_name }}. We've identified an action that you’ll need to complete before we continue reviewing your .gov domain request. DOMAIN REQUESTED: {{ domain_request.requested_domain.name }} -REQUESTED BY: {{ domain_request.creator.first_name }} {{ domain_request.creator.last_name }} +REQUESTED BY: {{ domain_request.creator.email }} REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }} STATUS: Action needed diff --git a/src/registrar/templates/emails/domain_request_withdrawn.txt b/src/registrar/templates/emails/domain_request_withdrawn.txt index 68d52761b..fbdf5b4f1 100644 --- a/src/registrar/templates/emails/domain_request_withdrawn.txt +++ b/src/registrar/templates/emails/domain_request_withdrawn.txt @@ -4,7 +4,7 @@ Hi, {{ recipient.first_name }}. Your .gov domain request has been withdrawn and will not be reviewed by our team. DOMAIN REQUESTED: {{ domain_request.requested_domain.name }} -REQUESTED BY: {{ domain_request.creator.first_name }} {{ domain_request.creator.last_name }} +REQUESTED BY: {{ domain_request.creator.email }} REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }} STATUS: Withdrawn diff --git a/src/registrar/templates/emails/status_change_approved.txt b/src/registrar/templates/emails/status_change_approved.txt index 9aedcd25f..821e89e42 100644 --- a/src/registrar/templates/emails/status_change_approved.txt +++ b/src/registrar/templates/emails/status_change_approved.txt @@ -4,7 +4,7 @@ Hi, {{ recipient.first_name }}. Congratulations! Your .gov domain request has been approved. DOMAIN REQUESTED: {{ domain_request.requested_domain.name }} -REQUESTED BY: {{ domain_request.creator.first_name }} {{ domain_request.creator.last_name }} +REQUESTED BY: {{ domain_request.creator.email }} REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }} STATUS: Approved diff --git a/src/registrar/templates/emails/status_change_rejected.txt b/src/registrar/templates/emails/status_change_rejected.txt index d963e39d0..e56d46a1f 100644 --- a/src/registrar/templates/emails/status_change_rejected.txt +++ b/src/registrar/templates/emails/status_change_rejected.txt @@ -4,7 +4,7 @@ Hi, {{ recipient.first_name }}. Your .gov domain request has been rejected. DOMAIN REQUESTED: {{ domain_request.requested_domain.name }} -REQUESTED BY: {{ domain_request.creator.first_name }} {{ domain_request.creator.last_name }} +REQUESTED BY: {{ domain_request.creator.email }} REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }} STATUS: Rejected diff --git a/src/registrar/templates/emails/submission_confirmation.txt b/src/registrar/templates/emails/submission_confirmation.txt index 589bdf618..d9d01ec3e 100644 --- a/src/registrar/templates/emails/submission_confirmation.txt +++ b/src/registrar/templates/emails/submission_confirmation.txt @@ -4,7 +4,7 @@ Hi, {{ recipient.first_name }}. We received your .gov domain request. DOMAIN REQUESTED: {{ domain_request.requested_domain.name }} -REQUESTED BY: {{ domain_request.creator.first_name }} {{ domain_request.creator.last_name }} +REQUESTED BY: {{ domain_request.creator.email }} REQUEST RECEIVED ON: {{ domain_request.last_submitted_date|date }} STATUS: Submitted