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 001/107] 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 002/107] 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 003/107] 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 004/107] 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 5c127ed586d8f51519f5bca212131a93a2504d5b Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 10 Jan 2025 08:43:05 -0700 Subject: [PATCH 005/107] Change admin tag color, weight, and casing --- src/registrar/assets/src/js/getgov/table-members.js | 2 +- src/registrar/assets/src/sass/_theme/_admin.scss | 4 ++++ src/registrar/assets/src/sass/_theme/_tables.scss | 4 ++++ src/registrar/assets/src/sass/_theme/_uswds-theme.scss | 1 + .../admin/includes/portfolio/portfolio_members_table.html | 2 +- src/registrar/templates/domain_users.html | 4 ++-- 6 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/registrar/assets/src/js/getgov/table-members.js b/src/registrar/assets/src/js/getgov/table-members.js index d7348441f..e0bd52125 100644 --- a/src/registrar/assets/src/js/getgov/table-members.js +++ b/src/registrar/assets/src/js/getgov/table-members.js @@ -84,7 +84,7 @@ export class MembersTable extends BaseTable { let admin_tagHTML = ``; if (member.is_admin) - admin_tagHTML = `Admin` + admin_tagHTML = `Admin` // generate html blocks for domains and permissions for the member let domainsHTML = this.generateDomainsHTML(num_domains, member.domain_names, member.domain_urls, member.action_url); diff --git a/src/registrar/assets/src/sass/_theme/_admin.scss b/src/registrar/assets/src/sass/_theme/_admin.scss index a71804d77..98bb8f22f 100644 --- a/src/registrar/assets/src/sass/_theme/_admin.scss +++ b/src/registrar/assets/src/sass/_theme/_admin.scss @@ -948,3 +948,7 @@ ul.add-list-reset { background-color: transparent !important; } } + +.dja-detail-table .usa-tag { + text-transform: none; +} diff --git a/src/registrar/assets/src/sass/_theme/_tables.scss b/src/registrar/assets/src/sass/_theme/_tables.scss index ea160396e..e61d9c545 100644 --- a/src/registrar/assets/src/sass/_theme/_tables.scss +++ b/src/registrar/assets/src/sass/_theme/_tables.scss @@ -87,6 +87,10 @@ th { } } + .usa-tag { + text-transform: none; + } + @include at-media(tablet-lg) { th[data-sortable] .usa-table__header__button { right: auto; diff --git a/src/registrar/assets/src/sass/_theme/_uswds-theme.scss b/src/registrar/assets/src/sass/_theme/_uswds-theme.scss index 1661a6388..5df79e7c9 100644 --- a/src/registrar/assets/src/sass/_theme/_uswds-theme.scss +++ b/src/registrar/assets/src/sass/_theme/_uswds-theme.scss @@ -68,6 +68,7 @@ in the form $setting: value, /*--------------------------- ## Font weights ----------------------------*/ + $theme-font-weight-medium: 500, $theme-font-weight-semibold: 600, /*--------------------------- diff --git a/src/registrar/templates/django/admin/includes/portfolio/portfolio_members_table.html b/src/registrar/templates/django/admin/includes/portfolio/portfolio_members_table.html index fe62f268b..31bc6231f 100644 --- a/src/registrar/templates/django/admin/includes/portfolio/portfolio_members_table.html +++ b/src/registrar/templates/django/admin/includes/portfolio/portfolio_members_table.html @@ -30,7 +30,7 @@ {{ member.user.phone }} {% for role in member.user|portfolio_role_summary:original %} - {{ role }} + {{ role }} {% endfor %} diff --git a/src/registrar/templates/domain_users.html b/src/registrar/templates/domain_users.html index f42e738e1..0b5ec41df 100644 --- a/src/registrar/templates/domain_users.html +++ b/src/registrar/templates/domain_users.html @@ -65,7 +65,7 @@ {{ item.permission.user.email }} - {% if item.has_admin_flag %}Admin{% endif %} + {% if item.has_admin_flag %}Admin{% endif %} {% if not portfolio %}{{ item.permission.role|title }}{% endif %} @@ -160,7 +160,7 @@ {{ invitation.domain_invitation.email }} - {% if invitation.has_admin_flag %}Admin{% endif %} + {% if invitation.has_admin_flag %}Admin{% endif %} {{ invitation.domain_invitation.created_at|date }} {% if not portfolio %}{{ invitation.domain_invitation.status|title }}{% endif %} From ac027554d0e96d76f3ad8c584f2cbccd0b952301 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Fri, 10 Jan 2025 08:59:19 -0700 Subject: [PATCH 006/107] Always show the "view assigned domains" link --- src/registrar/assets/src/js/getgov/table-members.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/registrar/assets/src/js/getgov/table-members.js b/src/registrar/assets/src/js/getgov/table-members.js index e0bd52125..665201333 100644 --- a/src/registrar/assets/src/js/getgov/table-members.js +++ b/src/registrar/assets/src/js/getgov/table-members.js @@ -257,9 +257,7 @@ export class MembersTable extends BaseTable { domainsHTML += ""; // If there are more than 6 domains, display a "View assigned domains" link - if (num_domains >= 6) { - domainsHTML += `

View assigned domains

`; - } + domainsHTML += `

View assigned domains

`; domainsHTML += ""; } 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 007/107] 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 008/107] 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 009/107] 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 010/107] 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 011/107] 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 012/107] 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 013/107] 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 014/107] 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 015/107] 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 016/107] 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 017/107] 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 fc2c6b5aa654d7879290cae2477e355a74f91a99 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 15 Jan 2025 09:11:57 -0700 Subject: [PATCH 018/107] Last active, small text, base-dark, "domain" vs "domains" Addresses these: https://cisa-corp.slack.com/archives/C087NN9UW5C/p1736918173911589 https://cisa-corp.slack.com/archives/C087NN9UW5C/p1736918609387289 --- .../assets/src/js/getgov/table-members.js | 27 ++++++++++--------- .../templates/includes/members_table.html | 2 +- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/registrar/assets/src/js/getgov/table-members.js b/src/registrar/assets/src/js/getgov/table-members.js index 665201333..edab96ec6 100644 --- a/src/registrar/assets/src/js/getgov/table-members.js +++ b/src/registrar/assets/src/js/getgov/table-members.js @@ -245,19 +245,19 @@ export class MembersTable extends BaseTable { // Only generate HTML if the member has one or more assigned domains if (num_domains > 0) { domainsHTML += "
"; - domainsHTML += "

Domains assigned

"; - domainsHTML += `

This member is assigned to ${num_domains} domains:

`; + domainsHTML += "

Domains assigned

"; + domainsHTML += `

This member is assigned to ${num_domains} domain${num_domains > 1 ? 's' : ''}:

`; domainsHTML += "
    "; // Display up to 6 domains with their URLs for (let i = 0; i < num_domains && i < 6; i++) { - domainsHTML += `
  • ${domain_names[i]}
  • `; + domainsHTML += `
  • ${domain_names[i]}
  • `; } domainsHTML += "
"; // If there are more than 6 domains, display a "View assigned domains" link - domainsHTML += `

View assigned domains

`; + domainsHTML += `

View assigned domains

`; domainsHTML += "
"; } @@ -376,34 +376,37 @@ export class MembersTable extends BaseTable { generatePermissionsHTML(member_permissions, UserPortfolioPermissionChoices) { let permissionsHTML = ''; + // Define shared classes across elements for easier refactoring + let sharedParagraphClasses = "font-body-xs text-base-dark margin-top-1 p--blockquote"; + // Check domain-related permissions if (member_permissions.includes(UserPortfolioPermissionChoices.VIEW_ALL_DOMAINS)) { - permissionsHTML += "

Domains: Can view all organization domains. Can manage domains they are assigned to and edit information about the domain (including DNS settings).

"; + permissionsHTML += `

Domains: Can view all organization domains. Can manage domains they are assigned to and edit information about the domain (including DNS settings).

`; } else if (member_permissions.includes(UserPortfolioPermissionChoices.VIEW_MANAGED_DOMAINS)) { - permissionsHTML += "

Domains: Can manage domains they are assigned to and edit information about the domain (including DNS settings).

"; + permissionsHTML += `

Domains: Can manage domains they are assigned to and edit information about the domain (including DNS settings).

`; } // Check request-related permissions if (member_permissions.includes(UserPortfolioPermissionChoices.EDIT_REQUESTS)) { - permissionsHTML += "

Domain requests: Can view all organization domain requests. Can create domain requests and modify their own requests.

"; + permissionsHTML += `

Domain requests: Can view all organization domain requests. Can create domain requests and modify their own requests.

`; } else if (member_permissions.includes(UserPortfolioPermissionChoices.VIEW_ALL_REQUESTS)) { - permissionsHTML += "

Domain requests (view-only): Can view all organization domain requests. Can't create or modify any domain requests.

"; + permissionsHTML += `

Domain requests (view-only): Can view all organization domain requests. Can't create or modify any domain requests.

`; } // Check member-related permissions if (member_permissions.includes(UserPortfolioPermissionChoices.EDIT_MEMBERS)) { - permissionsHTML += "

Members: Can manage members including inviting new members, removing current members, and assigning domains to members.

"; + permissionsHTML += `

Members: Can manage members including inviting new members, removing current members, and assigning domains to members.

`; } else if (member_permissions.includes(UserPortfolioPermissionChoices.VIEW_MEMBERS)) { - permissionsHTML += "

Members (view-only): Can view all organizational members. Can't manage any members.

"; + permissionsHTML += `

Members (view-only): Can view all organizational members. Can't manage any members.

`; } // If no specific permissions are assigned, display a message indicating no additional permissions if (!permissionsHTML) { - permissionsHTML += "

No additional permissions: There are no additional permissions for this member.

"; + permissionsHTML += `

No additional permissions: There are no additional permissions for this member.

`; } // Add a permissions header and wrap the entire output in a container - permissionsHTML = "

Additional permissions for this member

" + permissionsHTML + "
"; + permissionsHTML = "

Additional permissions for this member

" + permissionsHTML + "
"; return permissionsHTML; } diff --git a/src/registrar/templates/includes/members_table.html b/src/registrar/templates/includes/members_table.html index 19320a4ae..cc308619a 100644 --- a/src/registrar/templates/includes/members_table.html +++ b/src/registrar/templates/includes/members_table.html @@ -54,7 +54,7 @@ Member - Last Active + Last active Date: Wed, 15 Jan 2025 09:16:07 -0700 Subject: [PATCH 019/107] Update table-members.js --- src/registrar/assets/src/js/getgov/table-members.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/assets/src/js/getgov/table-members.js b/src/registrar/assets/src/js/getgov/table-members.js index edab96ec6..87bc6622e 100644 --- a/src/registrar/assets/src/js/getgov/table-members.js +++ b/src/registrar/assets/src/js/getgov/table-members.js @@ -246,7 +246,7 @@ export class MembersTable extends BaseTable { if (num_domains > 0) { domainsHTML += "
"; domainsHTML += "

Domains assigned

"; - domainsHTML += `

This member is assigned to ${num_domains} domain${num_domains > 1 ? 's' : ''}:

`; + domainsHTML += `

This member is assigned to ${num_domains} domain${num_domains > 1 ? 's' : ''}:

`; domainsHTML += "
    "; // Display up to 6 domains with their URLs From 32e5cfebf7df8d89a6b2ff0f288131d227764493 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 15 Jan 2025 10:42:26 -0700 Subject: [PATCH 020/107] fix bug --- src/registrar/assets/src/js/getgov/portfolio-member-page.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/assets/src/js/getgov/portfolio-member-page.js b/src/registrar/assets/src/js/getgov/portfolio-member-page.js index cfb83badc..aa2a8dbfe 100644 --- a/src/registrar/assets/src/js/getgov/portfolio-member-page.js +++ b/src/registrar/assets/src/js/getgov/portfolio-member-page.js @@ -22,7 +22,7 @@ export function initPortfolioNewMemberPageToggle() { // This easter egg is only for fixtures that dont have names as we are displaying their emails // All prod users will have emails linked to their account - MembersTable.addMemberModal(num_domains, member_email || "Samwise Gamgee", member_delete_url, unique_id, wrapperDeleteAction); + MembersTable.addMemberDeleteModal(num_domains, member_email || "Samwise Gamgee", member_delete_url, unique_id, wrapperDeleteAction); uswdsInitializeModals(); From 036c10e53d0fd2e1abbc469f0fe3a19c7b9818e6 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 15 Jan 2025 10:58:24 -0700 Subject: [PATCH 021/107] Fix last table row + modal changes Resolves these: https://cisa-corp.slack.com/archives/C087NN9UW5C/p1736918783309229 https://cisa-corp.slack.com/archives/C087NN9UW5C/p1736919499797869 --- src/registrar/assets/src/js/getgov/table-members.js | 2 +- src/registrar/assets/src/sass/_theme/_admin.scss | 2 +- src/registrar/assets/src/sass/_theme/_tables.scss | 2 +- src/registrar/assets/src/sass/_theme/_usa-modal.scss | 5 +++++ src/registrar/assets/src/sass/_theme/styles.scss | 1 + 5 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 src/registrar/assets/src/sass/_theme/_usa-modal.scss diff --git a/src/registrar/assets/src/js/getgov/table-members.js b/src/registrar/assets/src/js/getgov/table-members.js index 87bc6622e..439589f8a 100644 --- a/src/registrar/assets/src/js/getgov/table-members.js +++ b/src/registrar/assets/src/js/getgov/table-members.js @@ -424,7 +424,7 @@ export class MembersTable extends BaseTable { let modalDescription = ``; if (num_domains >= 0){ - modalHeading = `Are you sure you want to delete ${member_email}?`; + modalHeading = `Are you sure you want to remove ${member_email} from the organization?`; modalDescription = `They will no longer be able to access this organization. This action cannot be undone.`; if (num_domains >= 1) diff --git a/src/registrar/assets/src/sass/_theme/_admin.scss b/src/registrar/assets/src/sass/_theme/_admin.scss index 98bb8f22f..3caa553e2 100644 --- a/src/registrar/assets/src/sass/_theme/_admin.scss +++ b/src/registrar/assets/src/sass/_theme/_admin.scss @@ -8,7 +8,7 @@ :root, html[data-theme="light"] { --primary: #{$theme-color-primary}; - --secondary: #{$theme-color-primary-darkest}; + --secondary: #{$theme-color-error}; --accent: #{$theme-color-accent-cool}; // --primary-fg: #fff; diff --git a/src/registrar/assets/src/sass/_theme/_tables.scss b/src/registrar/assets/src/sass/_theme/_tables.scss index e61d9c545..8ee944ac3 100644 --- a/src/registrar/assets/src/sass/_theme/_tables.scss +++ b/src/registrar/assets/src/sass/_theme/_tables.scss @@ -56,7 +56,7 @@ th { border: none; } - tr:not(.hide-td-borders) { + tr:not(.hide-td-borders):not(:last-of-type) { td, th { border-bottom: 1px solid color('base-lighter'); } diff --git a/src/registrar/assets/src/sass/_theme/_usa-modal.scss b/src/registrar/assets/src/sass/_theme/_usa-modal.scss new file mode 100644 index 000000000..44107790d --- /dev/null +++ b/src/registrar/assets/src/sass/_theme/_usa-modal.scss @@ -0,0 +1,5 @@ +@use "uswds-core" as *; + +.usa-modal__main { + padding: 0 2rem 2rem; +} diff --git a/src/registrar/assets/src/sass/_theme/styles.scss b/src/registrar/assets/src/sass/_theme/styles.scss index 78d27b2e0..e3c94e3df 100644 --- a/src/registrar/assets/src/sass/_theme/styles.scss +++ b/src/registrar/assets/src/sass/_theme/styles.scss @@ -25,6 +25,7 @@ @forward "header"; @forward "register-form"; @forward "containers"; +@forward "usa-modal"; /*-------------------------------------------------- --- Admin ---------------------------------*/ From 3aa493d15efa032c00afe6b0349c7466c764ecbd Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Wed, 15 Jan 2025 12:05:58 -0700 Subject: [PATCH 022/107] Fix bugs --- src/registrar/assets/src/js/getgov/table-base.js | 11 +++++++---- src/registrar/assets/src/js/getgov/table-members.js | 12 +++++++++++- src/registrar/assets/src/sass/_theme/_admin.scss | 2 +- src/registrar/assets/src/sass/_theme/_buttons.scss | 4 ++++ src/registrar/assets/src/sass/_theme/_tables.scss | 10 +++++++++- 5 files changed, 32 insertions(+), 7 deletions(-) diff --git a/src/registrar/assets/src/js/getgov/table-base.js b/src/registrar/assets/src/js/getgov/table-base.js index e1d5c11ce..bcd1abd61 100644 --- a/src/registrar/assets/src/js/getgov/table-base.js +++ b/src/registrar/assets/src/js/getgov/table-base.js @@ -462,10 +462,7 @@ export class BaseTable { let dataObjects = this.getDataObjects(data); let customTableOptions = this.customizeTable(data); - - dataObjects.forEach(dataObject => { - this.addRow(dataObject, tbody, customTableOptions); - }); + this.loadRows(dataObjects, tbody, customTableOptions) this.initShowMoreButtons(); this.initCheckboxListeners(); @@ -492,6 +489,12 @@ export class BaseTable { .catch(error => console.error('Error fetching objects:', error)); } + loadRows(dataObjects, tbody, customTableOptions) { + dataObjects.forEach(dataObject => { + this.addRow(dataObject, tbody, customTableOptions); + }); + } + // Add event listeners to table headers for sorting initializeTableHeaders() { this.tableHeaders.forEach(header => { diff --git a/src/registrar/assets/src/js/getgov/table-members.js b/src/registrar/assets/src/js/getgov/table-members.js index 439589f8a..123ec0205 100644 --- a/src/registrar/assets/src/js/getgov/table-members.js +++ b/src/registrar/assets/src/js/getgov/table-members.js @@ -66,7 +66,14 @@ export class MembersTable extends BaseTable { }; } - addRow(dataObject, tbody, customTableOptions) { + loadRows(dataObjects, tbody, customTableOptions) { + dataObjects.forEach((dataObject, index) => { + const isLastRow = index === dataObjects.length - 1; + this.addRow(dataObject, tbody, customTableOptions, isLastRow); + }); + } + + addRow(dataObject, tbody, customTableOptions, isLastRow = false) { const member = dataObject; // member is based on either a UserPortfolioPermission or a PortfolioInvitation // and also includes information from related domains; the 'id' of the org_member @@ -81,6 +88,9 @@ export class MembersTable extends BaseTable { const kebabHTML = customTableOptions.needsAdditionalColumn ? generateKebabHTML('remove-member', unique_id, cancelInvitationButton, `for ${member.name}`): ''; const row = document.createElement('tr'); + if (isLastRow) { + row.classList.add("hide-td-borders"); + } let admin_tagHTML = ``; if (member.is_admin) diff --git a/src/registrar/assets/src/sass/_theme/_admin.scss b/src/registrar/assets/src/sass/_theme/_admin.scss index 3caa553e2..98bb8f22f 100644 --- a/src/registrar/assets/src/sass/_theme/_admin.scss +++ b/src/registrar/assets/src/sass/_theme/_admin.scss @@ -8,7 +8,7 @@ :root, html[data-theme="light"] { --primary: #{$theme-color-primary}; - --secondary: #{$theme-color-error}; + --secondary: #{$theme-color-primary-darkest}; --accent: #{$theme-color-accent-cool}; // --primary-fg: #fff; diff --git a/src/registrar/assets/src/sass/_theme/_buttons.scss b/src/registrar/assets/src/sass/_theme/_buttons.scss index 3342f5f7d..bd5e30b7a 100644 --- a/src/registrar/assets/src/sass/_theme/_buttons.scss +++ b/src/registrar/assets/src/sass/_theme/_buttons.scss @@ -253,6 +253,10 @@ a.text-secondary:hover { color: $theme-color-error; } +button.usa-button.usa-button--secondary, a.usa-button.usa-button--secondary { + background-color: $theme-color-error; +} + .usa-button--show-more-button { font-size: size('ui', 'xs'); text-decoration: none; diff --git a/src/registrar/assets/src/sass/_theme/_tables.scss b/src/registrar/assets/src/sass/_theme/_tables.scss index 8ee944ac3..4102f0bdf 100644 --- a/src/registrar/assets/src/sass/_theme/_tables.scss +++ b/src/registrar/assets/src/sass/_theme/_tables.scss @@ -41,6 +41,14 @@ th { } } +.members__table-wrapper .dotgov-table { + tr:not(.hide-td-borders):not(:last-of-type) { + td, th { + border-bottom: 1px solid color('base-lighter'); + } + } +} + .dotgov-table { width: 100%; @@ -56,7 +64,7 @@ th { border: none; } - tr:not(.hide-td-borders):not(:last-of-type) { + tr:not(.hide-td-borders) { td, th { border-bottom: 1px solid color('base-lighter'); } 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 023/107] 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 024/107] 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 025/107] 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 f7758181ec4c1e0a1f6249f12e9975d56b145167 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 16 Jan 2025 11:22:45 -0700 Subject: [PATCH 026/107] change kebab style --- src/registrar/assets/src/sass/_theme/_accordions.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/registrar/assets/src/sass/_theme/_accordions.scss b/src/registrar/assets/src/sass/_theme/_accordions.scss index 762618415..e00da3771 100644 --- a/src/registrar/assets/src/sass/_theme/_accordions.scss +++ b/src/registrar/assets/src/sass/_theme/_accordions.scss @@ -40,6 +40,12 @@ top: 30px; } +@media (min-width: 1030px) { + .usa-accordion--more-actions .usa-accordion__content { + right: auto; + } +} + // Special positioning for the kabob menu popup in the last row on a given page // This won't work on the Members table rows because that table has show-more rows // Currently, that's not an issue since that Members table is not wrapped in the From ee4629b897adab2ae77f9b5748eb91aba8a9fb6f Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 16 Jan 2025 11:35:18 -0700 Subject: [PATCH 027/107] Revert "change kebab style" This reverts commit f7758181ec4c1e0a1f6249f12e9975d56b145167. --- src/registrar/assets/src/sass/_theme/_accordions.scss | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/registrar/assets/src/sass/_theme/_accordions.scss b/src/registrar/assets/src/sass/_theme/_accordions.scss index e00da3771..762618415 100644 --- a/src/registrar/assets/src/sass/_theme/_accordions.scss +++ b/src/registrar/assets/src/sass/_theme/_accordions.scss @@ -40,12 +40,6 @@ top: 30px; } -@media (min-width: 1030px) { - .usa-accordion--more-actions .usa-accordion__content { - right: auto; - } -} - // Special positioning for the kabob menu popup in the last row on a given page // This won't work on the Members table rows because that table has show-more rows // Currently, that's not an issue since that Members table is not wrapped in the From c8ac9c4a075fe58f9e5bb46bb8e5ad26f2cb0bb5 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Thu, 16 Jan 2025 11:41:56 -0700 Subject: [PATCH 028/107] Fix error styling on member page --- src/registrar/templates/portfolio_members.html | 2 +- src/registrar/views/portfolios.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/registrar/templates/portfolio_members.html b/src/registrar/templates/portfolio_members.html index 720d60e59..43d5c0672 100644 --- a/src/registrar/templates/portfolio_members.html +++ b/src/registrar/templates/portfolio_members.html @@ -17,7 +17,7 @@