From 3ec0b51e7269ca0802580537417b356d7cf51b1e Mon Sep 17 00:00:00 2001 From: Matthew Spence Date: Wed, 18 Dec 2024 15:56:00 -0600 Subject: [PATCH 001/252] add db workflow documentation --- docs/developer/cloning-databases.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 docs/developer/cloning-databases.md diff --git a/docs/developer/cloning-databases.md b/docs/developer/cloning-databases.md new file mode 100644 index 000000000..8dc22979a --- /dev/null +++ b/docs/developer/cloning-databases.md @@ -0,0 +1,5 @@ +# Cloning Databases +The clone-db workflow clones a Source database to a Destination database using cloud.gov's cg-manage-rds tool. This document contains additional information needed to understand how the workflow functions. + +## Additional Roles Required +The clone-db workflow functions by temporarily sharing the Destination database with the space of the Source database. This is because cloning databases across spaces is hard. Sharing is done via the `cf share-service` command, but requires that the authenticated user (in this case this will be a user from the Source space) have the `space-developer` role in *both* the Source and Destination spaces. This must be set by someone with permission to edit space roles *before* the workflow runs. The user in question can be found using the `cf space-users [ORG] [SPACE]` command where the SPACE is the Source space, and will appear as a UAA user with a UUID as the name. There is only one such user per space by default (this is a [service account](https://cloud.gov/docs/services/cloud-gov-service-account/) set up by cloud.gov for our Github workflows). This user needs to be provided with the `space-developer` role in the Destination space, which can be accomplished using `cf set-space-role [USER] [ORG] [DESTINATION SPACE] SpaceDeveloper`. From 1f104f0004c1ce07faa48c9f93352ea3c6bf9196 Mon Sep 17 00:00:00 2001 From: Matt-Spence Date: Mon, 23 Dec 2024 11:19:34 -0600 Subject: [PATCH 002/252] Update cloning-databases.md --- docs/developer/cloning-databases.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/developer/cloning-databases.md b/docs/developer/cloning-databases.md index 8dc22979a..cd4b2e9c1 100644 --- a/docs/developer/cloning-databases.md +++ b/docs/developer/cloning-databases.md @@ -3,3 +3,13 @@ The clone-db workflow clones a Source database to a Destination database using c ## Additional Roles Required The clone-db workflow functions by temporarily sharing the Destination database with the space of the Source database. This is because cloning databases across spaces is hard. Sharing is done via the `cf share-service` command, but requires that the authenticated user (in this case this will be a user from the Source space) have the `space-developer` role in *both* the Source and Destination spaces. This must be set by someone with permission to edit space roles *before* the workflow runs. The user in question can be found using the `cf space-users [ORG] [SPACE]` command where the SPACE is the Source space, and will appear as a UAA user with a UUID as the name. There is only one such user per space by default (this is a [service account](https://cloud.gov/docs/services/cloud-gov-service-account/) set up by cloud.gov for our Github workflows). This user needs to be provided with the `space-developer` role in the Destination space, which can be accomplished using `cf set-space-role [USER] [ORG] [DESTINATION SPACE] SpaceDeveloper`. + +## Turning Off DB Cloning Fast (For Emergencies or other Scenarios) +Step 1: +Get the name of the correct service using `cf spaces-users cisa-dotgov stable`. There should only be one user with a name that is a UUID, that is the one you want. + +step 2: +Remove the space develeper role by doing the following command: +`cf unset-space-role [USER] cisa-dotgov staging SpaceDeveloper` + +This will cause the job to fail without requiring pushing anything to main. 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 003/252] 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 9f3fff03fdfaac009377c522f81eb8eb1f0d44c1 Mon Sep 17 00:00:00 2001 From: Matt-Spence Date: Thu, 2 Jan 2025 12:45:28 -0600 Subject: [PATCH 004/252] Update cloning-databases.md --- docs/developer/cloning-databases.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/developer/cloning-databases.md b/docs/developer/cloning-databases.md index cd4b2e9c1..4dcb5f52d 100644 --- a/docs/developer/cloning-databases.md +++ b/docs/developer/cloning-databases.md @@ -5,6 +5,8 @@ The clone-db workflow clones a Source database to a Destination database using c The clone-db workflow functions by temporarily sharing the Destination database with the space of the Source database. This is because cloning databases across spaces is hard. Sharing is done via the `cf share-service` command, but requires that the authenticated user (in this case this will be a user from the Source space) have the `space-developer` role in *both* the Source and Destination spaces. This must be set by someone with permission to edit space roles *before* the workflow runs. The user in question can be found using the `cf space-users [ORG] [SPACE]` command where the SPACE is the Source space, and will appear as a UAA user with a UUID as the name. There is only one such user per space by default (this is a [service account](https://cloud.gov/docs/services/cloud-gov-service-account/) set up by cloud.gov for our Github workflows). This user needs to be provided with the `space-developer` role in the Destination space, which can be accomplished using `cf set-space-role [USER] [ORG] [DESTINATION SPACE] SpaceDeveloper`. ## Turning Off DB Cloning Fast (For Emergencies or other Scenarios) +Note: In less urgent situations it may be better to make a PR removing the scheduled workflow trigger. + Step 1: Get the name of the correct service using `cf spaces-users cisa-dotgov stable`. There should only be one user with a name that is a UUID, that is the one you want. From 2651cd4abaebbe84c3ed762ce3bf99f28ab86122 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Thu, 2 Jan 2025 16:36:36 -0500 Subject: [PATCH 005/252] initial approach, from domain.py, not quite working --- src/registrar/forms/domain_request_wizard.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/registrar/forms/domain_request_wizard.py b/src/registrar/forms/domain_request_wizard.py index 289b3da0b..45bd575f9 100644 --- a/src/registrar/forms/domain_request_wizard.py +++ b/src/registrar/forms/domain_request_wizard.py @@ -296,6 +296,14 @@ class OrganizationContactForm(RegistrarForm): label="Urbanization (required for Puerto Rico only)", ) + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # Set initial value for federal agency combo box and specify combobox template + if self.domain_request and self.domain_request.federal_agency: + self.fields["federal_agency"].initial = self.domain_request.federal_agency + self.fields["federal_agency"].widget.attrs["data-default-value"] = self.domain_request.federal_agency.pk + self.fields["federal_agency"].widget.template_name = "django/forms/widgets/combobox.html", + def clean_federal_agency(self): """Require something to be selected when this is a federal agency.""" federal_agency = self.cleaned_data.get("federal_agency", None) 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 006/252] 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 f359a636d02103c0dd5421a5c25be4314fbc3a27 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Mon, 6 Jan 2025 13:45:37 -0500 Subject: [PATCH 007/252] combobox in domain request organization contact --- src/registrar/forms/domain_request_wizard.py | 17 ++++++++++++++--- src/registrar/forms/utility/combobox.py | 5 +++++ 2 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 src/registrar/forms/utility/combobox.py diff --git a/src/registrar/forms/domain_request_wizard.py b/src/registrar/forms/domain_request_wizard.py index 45bd575f9..f58d62c0e 100644 --- a/src/registrar/forms/domain_request_wizard.py +++ b/src/registrar/forms/domain_request_wizard.py @@ -7,6 +7,7 @@ from django import forms from django.core.validators import RegexValidator, MaxLengthValidator from django.utils.safestring import mark_safe +from registrar.forms.utility.combobox import ComboboxWidget from registrar.forms.utility.wizard_form_helper import ( RegistrarForm, RegistrarFormSet, @@ -257,6 +258,7 @@ class OrganizationContactForm(RegistrarForm): required=False, queryset=FederalAgency.objects.exclude(agency__in=excluded_agencies), empty_label="--Select--", + widget=ComboboxWidget, ) organization_name = forms.CharField( label="Organization name", @@ -298,11 +300,20 @@ class OrganizationContactForm(RegistrarForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - # Set initial value for federal agency combo box and specify combobox template - if self.domain_request and self.domain_request.federal_agency: + + # Initialize federal_agency combobox widget + # Domain requests forms have prefix associated with step + prefix = kwargs.get("prefix", "") + prefixed_name = f"{prefix}-federal_agency" if prefix else "federal_agency" + + # For combobox widget, need to set the data-default-value to selected value + if self.is_bound and self.data.get(prefixed_name): + # If form is bound (from a POST), use submitted value + self.fields["federal_agency"].widget.attrs["data-default-value"] = self.data.get(prefixed_name) + elif self.domain_request and self.domain_request.federal_agency: + # If form is not bound, set initial self.fields["federal_agency"].initial = self.domain_request.federal_agency self.fields["federal_agency"].widget.attrs["data-default-value"] = self.domain_request.federal_agency.pk - self.fields["federal_agency"].widget.template_name = "django/forms/widgets/combobox.html", def clean_federal_agency(self): """Require something to be selected when this is a federal agency.""" diff --git a/src/registrar/forms/utility/combobox.py b/src/registrar/forms/utility/combobox.py new file mode 100644 index 000000000..b7db16ccc --- /dev/null +++ b/src/registrar/forms/utility/combobox.py @@ -0,0 +1,5 @@ +from django.forms import Select + + +class ComboboxWidget(Select): + template_name = "django/forms/widgets/combobox.html" \ No newline at end of file From 4480e3255375120f3ca6612c0f15309ebd57aa9a Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Mon, 6 Jan 2025 14:15:13 -0500 Subject: [PATCH 008/252] updated combobox widget to set proper data-default-value and set domain suborganization form to use new combobox widget --- src/registrar/forms/domain.py | 18 +++--------------- src/registrar/forms/domain_request_wizard.py | 17 ----------------- .../django/forms/widgets/combobox.html | 1 + 3 files changed, 4 insertions(+), 32 deletions(-) diff --git a/src/registrar/forms/domain.py b/src/registrar/forms/domain.py index b43d91a58..1e4068125 100644 --- a/src/registrar/forms/domain.py +++ b/src/registrar/forms/domain.py @@ -4,6 +4,7 @@ import logging from django import forms from django.core.validators import MinValueValidator, MaxValueValidator, RegexValidator, MaxLengthValidator from django.forms import formset_factory +from registrar.forms.utility.combobox import ComboboxWidget from registrar.models import DomainRequest, FederalAgency from phonenumber_field.widgets import RegionalPhoneNumberWidget from registrar.models.suborganization import Suborganization @@ -161,9 +162,10 @@ class DomainSuborganizationForm(forms.ModelForm): """Form for updating the suborganization""" sub_organization = forms.ModelChoiceField( + label = "Suborganization name", queryset=Suborganization.objects.none(), required=False, - widget=forms.Select(), + widget=ComboboxWidget, ) class Meta: @@ -178,20 +180,6 @@ class DomainSuborganizationForm(forms.ModelForm): portfolio = self.instance.portfolio if self.instance else None self.fields["sub_organization"].queryset = Suborganization.objects.filter(portfolio=portfolio) - # Set initial value - if self.instance and self.instance.sub_organization: - self.fields["sub_organization"].initial = self.instance.sub_organization - - # Set custom form label - self.fields["sub_organization"].label = "Suborganization name" - - # Use the combobox rather than the regular select widget - self.fields["sub_organization"].widget.template_name = "django/forms/widgets/combobox.html" - - # Set data-default-value attribute - if self.instance and self.instance.sub_organization: - self.fields["sub_organization"].widget.attrs["data-default-value"] = self.instance.sub_organization.pk - class BaseNameserverFormset(forms.BaseFormSet): def clean(self): diff --git a/src/registrar/forms/domain_request_wizard.py b/src/registrar/forms/domain_request_wizard.py index f58d62c0e..e090ac8d2 100644 --- a/src/registrar/forms/domain_request_wizard.py +++ b/src/registrar/forms/domain_request_wizard.py @@ -298,23 +298,6 @@ class OrganizationContactForm(RegistrarForm): label="Urbanization (required for Puerto Rico only)", ) - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - # Initialize federal_agency combobox widget - # Domain requests forms have prefix associated with step - prefix = kwargs.get("prefix", "") - prefixed_name = f"{prefix}-federal_agency" if prefix else "federal_agency" - - # For combobox widget, need to set the data-default-value to selected value - if self.is_bound and self.data.get(prefixed_name): - # If form is bound (from a POST), use submitted value - self.fields["federal_agency"].widget.attrs["data-default-value"] = self.data.get(prefixed_name) - elif self.domain_request and self.domain_request.federal_agency: - # If form is not bound, set initial - self.fields["federal_agency"].initial = self.domain_request.federal_agency - self.fields["federal_agency"].widget.attrs["data-default-value"] = self.domain_request.federal_agency.pk - def clean_federal_agency(self): """Require something to be selected when this is a federal agency.""" federal_agency = self.cleaned_data.get("federal_agency", None) diff --git a/src/registrar/templates/django/forms/widgets/combobox.html b/src/registrar/templates/django/forms/widgets/combobox.html index 7ff31945b..4fe796347 100644 --- a/src/registrar/templates/django/forms/widgets/combobox.html +++ b/src/registrar/templates/django/forms/widgets/combobox.html @@ -11,6 +11,7 @@ for now we just carry the attribute to both the parent element and the select. {{ name }}="{{ value }}" {% endif %} {% endfor %} +data-default-value="{% for group_name, group_choices, group_index in widget.optgroups %}{% for option in group_choices %}{% if option.selected %}{{ option.value }}{% endif %}{% endfor %}{% endfor %}" > {% include "django/forms/widgets/select.html" %} From 3146dc07fadd3d9b92e5932b7964f57943cb271a Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Mon, 6 Jan 2025 14:22:36 -0500 Subject: [PATCH 009/252] applied widget to state territory --- src/registrar/forms/domain_request_wizard.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/registrar/forms/domain_request_wizard.py b/src/registrar/forms/domain_request_wizard.py index e090ac8d2..fcba68de8 100644 --- a/src/registrar/forms/domain_request_wizard.py +++ b/src/registrar/forms/domain_request_wizard.py @@ -282,6 +282,7 @@ class OrganizationContactForm(RegistrarForm): error_messages={ "required": ("Select the state, territory, or military post where your organization is located.") }, + widget=ComboboxWidget, ) zipcode = forms.CharField( label="Zip code", From 560baed2e1e2eb95aa48364b2e3673084814a741 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Mon, 6 Jan 2025 14:39:44 -0500 Subject: [PATCH 010/252] linted --- src/registrar/forms/domain.py | 2 +- src/registrar/forms/utility/combobox.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/registrar/forms/domain.py b/src/registrar/forms/domain.py index 1e4068125..87a52d142 100644 --- a/src/registrar/forms/domain.py +++ b/src/registrar/forms/domain.py @@ -162,7 +162,7 @@ class DomainSuborganizationForm(forms.ModelForm): """Form for updating the suborganization""" sub_organization = forms.ModelChoiceField( - label = "Suborganization name", + label="Suborganization name", queryset=Suborganization.objects.none(), required=False, widget=ComboboxWidget, diff --git a/src/registrar/forms/utility/combobox.py b/src/registrar/forms/utility/combobox.py index b7db16ccc..277aec4f3 100644 --- a/src/registrar/forms/utility/combobox.py +++ b/src/registrar/forms/utility/combobox.py @@ -2,4 +2,4 @@ from django.forms import Select class ComboboxWidget(Select): - template_name = "django/forms/widgets/combobox.html" \ No newline at end of file + template_name = "django/forms/widgets/combobox.html" 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 011/252] 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 012/252] 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 686fa0bd9f821f9181434d3fe877644e04dc59e0 Mon Sep 17 00:00:00 2001 From: Alysia Broddrick Date: Thu, 9 Jan 2025 09:41:18 -0800 Subject: [PATCH 013/252] removed the ignore on the security check workflow --- .github/workflows/security-check.yaml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/security-check.yaml b/.github/workflows/security-check.yaml index aea700613..bf0498fff 100644 --- a/.github/workflows/security-check.yaml +++ b/.github/workflows/security-check.yaml @@ -2,17 +2,9 @@ name: Security checks on: push: - paths-ignore: - - 'docs/**' - - '**.md' - - '.gitignore' branches: - main pull_request: - paths-ignore: - - 'docs/**' - - '**.md' - - '.gitignore' branches: - main From 7ea7aa3d92b2005c558de56012425b017dbc89be Mon Sep 17 00:00:00 2001 From: Alysia Broddrick Date: Thu, 9 Jan 2025 15:22:14 -0800 Subject: [PATCH 014/252] removed todo so security checks would pass --- src/registrar/models/domain_information.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/registrar/models/domain_information.py b/src/registrar/models/domain_information.py index b1c4fd806..aa933e282 100644 --- a/src/registrar/models/domain_information.py +++ b/src/registrar/models/domain_information.py @@ -101,7 +101,6 @@ class DomainInformation(TimeStampedModel): verbose_name="election office", ) - # TODO - Ticket #1911: stub this data from DomainRequest organization_type = models.CharField( max_length=255, choices=DomainRequest.OrgChoicesElectionOffice.choices, From bb5f61fd989d3ac6588a1f7a4e30f01ac99556fe Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Fri, 10 Jan 2025 06:50:36 -0500 Subject: [PATCH 015/252] updated behavior of combobox, added state territory combobox --- src/registrar/forms/domain_request_wizard.py | 1 + src/registrar/templates/django/forms/widgets/select.html | 1 + 2 files changed, 2 insertions(+) diff --git a/src/registrar/forms/domain_request_wizard.py b/src/registrar/forms/domain_request_wizard.py index fcba68de8..c38eb01a2 100644 --- a/src/registrar/forms/domain_request_wizard.py +++ b/src/registrar/forms/domain_request_wizard.py @@ -57,6 +57,7 @@ class RequestingEntityForm(RegistrarForm): label="State, territory, or military post", required=False, choices=[("", "--Select--")] + DomainRequest.StateTerritoryChoices.choices, + widget=ComboboxWidget, ) def __init__(self, *args, **kwargs): diff --git a/src/registrar/templates/django/forms/widgets/select.html b/src/registrar/templates/django/forms/widgets/select.html index cc62eb91d..86ca4ddcc 100644 --- a/src/registrar/templates/django/forms/widgets/select.html +++ b/src/registrar/templates/django/forms/widgets/select.html @@ -3,6 +3,7 @@ {# hint: spacing in the class string matters #} class="usa-select{% if classes %} {{ classes }}{% endif %}" {% include "django/forms/widgets/attrs.html" %} + data-default-value="{% for group_name, group_choices, group_index in widget.optgroups %}{% for option in group_choices %}{% if option.selected %}{{ option.value }}{% endif %}{% endfor %}{% endfor %}" > {% for group, options, index in widget.optgroups %} {% if group %}{% endif %} From 0e2d62f86f6cee2d63f0813b59f4a4852ce4d729 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Fri, 10 Jan 2025 06:59:26 -0500 Subject: [PATCH 016/252] small improvement --- src/registrar/templates/django/forms/widgets/combobox.html | 2 +- src/registrar/templates/django/forms/widgets/select.html | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/registrar/templates/django/forms/widgets/combobox.html b/src/registrar/templates/django/forms/widgets/combobox.html index 4fe796347..02cd4e35e 100644 --- a/src/registrar/templates/django/forms/widgets/combobox.html +++ b/src/registrar/templates/django/forms/widgets/combobox.html @@ -13,5 +13,5 @@ for now we just carry the attribute to both the parent element and the select. {% endfor %} data-default-value="{% for group_name, group_choices, group_index in widget.optgroups %}{% for option in group_choices %}{% if option.selected %}{{ option.value }}{% endif %}{% endfor %}{% endfor %}" > - {% include "django/forms/widgets/select.html" %} + {% include "django/forms/widgets/select.html" with is_combobox=True %} diff --git a/src/registrar/templates/django/forms/widgets/select.html b/src/registrar/templates/django/forms/widgets/select.html index 86ca4ddcc..db6deafe2 100644 --- a/src/registrar/templates/django/forms/widgets/select.html +++ b/src/registrar/templates/django/forms/widgets/select.html @@ -3,7 +3,9 @@ {# hint: spacing in the class string matters #} class="usa-select{% if classes %} {{ classes }}{% endif %}" {% include "django/forms/widgets/attrs.html" %} - data-default-value="{% for group_name, group_choices, group_index in widget.optgroups %}{% for option in group_choices %}{% if option.selected %}{{ option.value }}{% endif %}{% endfor %}{% endfor %}" + {% if is_combobox %} + data-default-value="{% for group_name, group_choices, group_index in widget.optgroups %}{% for option in group_choices %}{% if option.selected %}{{ option.value }}{% endif %}{% endfor %}{% endfor %}" + {% endif %} > {% for group, options, index in widget.optgroups %} {% if group %}{% endif %} From 6283cad872ef79472ba6dcf60e33cc1479fa90e7 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Fri, 10 Jan 2025 07:22:38 -0500 Subject: [PATCH 017/252] state territory in org name address domain form --- src/registrar/forms/domain.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/registrar/forms/domain.py b/src/registrar/forms/domain.py index 87a52d142..6cf6416c1 100644 --- a/src/registrar/forms/domain.py +++ b/src/registrar/forms/domain.py @@ -457,6 +457,17 @@ class DomainOrgNameAddressForm(forms.ModelForm): }, ) + state_territory = forms.ChoiceField( + label="State, territory, or military post", + required=True, + choices=DomainInformation.StateTerritoryChoices.choices, + widget=ComboboxWidget( + attrs={ + "required": True, + } + ), + ) + class Meta: model = DomainInformation fields = [ @@ -479,20 +490,10 @@ class DomainOrgNameAddressForm(forms.ModelForm): }, } widgets = { - # We need to set the required attributed for State/territory - # because for this fields we are creating an individual - # instance of the Select. For the other fields we use the for loop to set - # the class's required attribute to true. "organization_name": forms.TextInput, "address_line1": forms.TextInput, "address_line2": forms.TextInput, "city": forms.TextInput, - "state_territory": forms.Select( - attrs={ - "required": True, - }, - choices=DomainInformation.StateTerritoryChoices.choices, - ), "urbanization": forms.TextInput, } From d41174547d7ca5fd11bc0929103a58bb40c137bc Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Fri, 10 Jan 2025 07:35:44 -0500 Subject: [PATCH 018/252] fixing test_export again --- src/registrar/tests/test_reports.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/registrar/tests/test_reports.py b/src/registrar/tests/test_reports.py index 4a41238c7..93c07df13 100644 --- a/src/registrar/tests/test_reports.py +++ b/src/registrar/tests/test_reports.py @@ -900,6 +900,7 @@ class MemberExportTest(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) From b4505b38630eb2df71d33d2c50bcc960995cc790 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Fri, 10 Jan 2025 07:38:56 -0500 Subject: [PATCH 019/252] lint --- src/registrar/tests/test_reports.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/registrar/tests/test_reports.py b/src/registrar/tests/test_reports.py index 93c07df13..b11500ea9 100644 --- a/src/registrar/tests/test_reports.py +++ b/src/registrar/tests/test_reports.py @@ -900,7 +900,7 @@ class MemberExportTest(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.maxDiff = None self.assertEqual(csv_content, expected_content) 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 020/252] 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 021/252] 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 022/252] 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 1bc83a1c3e44aeb9d0d78b07aff4eab5db48caa9 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 10 Jan 2025 15:32:00 -0500 Subject: [PATCH 023/252] normalize summary box --- .../assets/src/sass/_theme/_admin.scss | 21 +++++++++++-------- .../assets/src/sass/_theme/_base.scss | 1 - .../assets/src/sass/_theme/_summary-box.scss | 15 +++++++++++++ .../assets/src/sass/_theme/styles.scss | 1 + 4 files changed, 28 insertions(+), 10 deletions(-) create mode 100644 src/registrar/assets/src/sass/_theme/_summary-box.scss diff --git a/src/registrar/assets/src/sass/_theme/_admin.scss b/src/registrar/assets/src/sass/_theme/_admin.scss index a71804d77..afdb39551 100644 --- a/src/registrar/assets/src/sass/_theme/_admin.scss +++ b/src/registrar/assets/src/sass/_theme/_admin.scss @@ -812,16 +812,19 @@ div.dja__model-description{ text-decoration: underline !important; } -//-- Override some styling for the USWDS summary box (per design quidance for ticket #2055 -.usa-summary-box { - background: #{$dhs-blue-10}; - border-color: #{$dhs-blue-30}; - max-width: 72ex; - word-wrap: break-word; -} +//-- Override some styling for the USWDS summary box (per design guidance for ticket #2055) +// Keep it scoped to admin.scss +.dashboard { + .usa-summary-box { + background: #{$dhs-blue-10}; + border-color: #{$dhs-blue-30}; + max-width: 72ex; + word-wrap: break-word; + } -.usa-summary-box h3 { - color: #{$dhs-blue-60}; + .usa-summary-box h3 { + color: #{$dhs-blue-60}; + } } .module caption, .inline-group h2 { diff --git a/src/registrar/assets/src/sass/_theme/_base.scss b/src/registrar/assets/src/sass/_theme/_base.scss index d73becd75..0e726ff37 100644 --- a/src/registrar/assets/src/sass/_theme/_base.scss +++ b/src/registrar/assets/src/sass/_theme/_base.scss @@ -59,7 +59,6 @@ body { } h2 { - color: color('primary-dark'); margin-top: units(2); margin-bottom: units(2); } diff --git a/src/registrar/assets/src/sass/_theme/_summary-box.scss b/src/registrar/assets/src/sass/_theme/_summary-box.scss new file mode 100644 index 000000000..91693ff02 --- /dev/null +++ b/src/registrar/assets/src/sass/_theme/_summary-box.scss @@ -0,0 +1,15 @@ +// USWDS override to basically match the header size to a standard h3 size +// This get complicated because USWDS sets a size on the container then a relative +// size on the header. We'll need to reset the container size, override the header size, +// then 'fix' the content size. +.usa-summary-box { + font-size: 1rem; + .usa-summary-box__heading { + font-size: 1.17em; + } + p, li, dd { + font-size: 1.06rem; + } +} + + \ No newline at end of file diff --git a/src/registrar/assets/src/sass/_theme/styles.scss b/src/registrar/assets/src/sass/_theme/styles.scss index 78d27b2e0..493ebd542 100644 --- a/src/registrar/assets/src/sass/_theme/styles.scss +++ b/src/registrar/assets/src/sass/_theme/styles.scss @@ -17,6 +17,7 @@ @forward "forms"; @forward "search"; @forward "tooltips"; +@forward "summary-box"; @forward "fieldsets"; @forward "alerts"; @forward "tables"; From 7dbb9de3b9925bf955a683132094ca7835e213d7 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Fri, 10 Jan 2025 16:18:11 -0500 Subject: [PATCH 024/252] use typeset mixin instead of hardcoded values in summary-box --- src/registrar/assets/src/sass/_theme/_summary-box.scss | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/registrar/assets/src/sass/_theme/_summary-box.scss b/src/registrar/assets/src/sass/_theme/_summary-box.scss index 91693ff02..fdaf165e2 100644 --- a/src/registrar/assets/src/sass/_theme/_summary-box.scss +++ b/src/registrar/assets/src/sass/_theme/_summary-box.scss @@ -5,10 +5,12 @@ .usa-summary-box { font-size: 1rem; .usa-summary-box__heading { - font-size: 1.17em; + // 1.17em / 18.72px + @include typeset('sans', 'sm', 6); } p, li, dd { - font-size: 1.06rem; + // 1.06rem / 16.96px + @include typeset('sans', 'sm', 5); } } From 8432435c087daa8f1a56a23457b04fbc62aaa57c Mon Sep 17 00:00:00 2001 From: CocoByte Date: Sun, 12 Jan 2025 23:38:48 -0700 Subject: [PATCH 025/252] 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 026/252] 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 027/252] 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 ae6c461ddb33f67c0b7bea042a4ca6594e30e669 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Mon, 13 Jan 2025 08:00:02 -0500 Subject: [PATCH 028/252] added federal agency on domain page, and suborg on requesting entity page --- src/registrar/forms/domain.py | 9 +++++++++ src/registrar/forms/domain_request_wizard.py | 1 + 2 files changed, 10 insertions(+) diff --git a/src/registrar/forms/domain.py b/src/registrar/forms/domain.py index 6cf6416c1..dd8c719cd 100644 --- a/src/registrar/forms/domain.py +++ b/src/registrar/forms/domain.py @@ -444,6 +444,15 @@ class DomainSecurityEmailForm(forms.Form): class DomainOrgNameAddressForm(forms.ModelForm): """Form for updating the organization name and mailing address.""" + # for federal agencies we also want to know the top-level agency. + excluded_agencies = ["gov Administration", "Non-Federal Agency"] + federal_agency = forms.ModelChoiceField( + label="Federal agency", + required=False, + queryset=FederalAgency.objects.exclude(agency__in=excluded_agencies), + empty_label="--Select--", + widget=ComboboxWidget, + ) zipcode = forms.CharField( label="Zip code", validators=[ diff --git a/src/registrar/forms/domain_request_wizard.py b/src/registrar/forms/domain_request_wizard.py index 898a02d58..2365d323d 100644 --- a/src/registrar/forms/domain_request_wizard.py +++ b/src/registrar/forms/domain_request_wizard.py @@ -45,6 +45,7 @@ class RequestingEntityForm(RegistrarForm): required=False, queryset=Suborganization.objects.none(), empty_label="--Select--", + widget=ComboboxWidget, ) requested_suborganization = forms.CharField( label="Requested suborganization", From d54e57b0840b01d0760669e143916ebe522e5a91 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Mon, 13 Jan 2025 08:15:50 -0500 Subject: [PATCH 029/252] update federal agency drop down not to have any exclusions --- src/registrar/forms/domain.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/registrar/forms/domain.py b/src/registrar/forms/domain.py index dd8c719cd..7089409de 100644 --- a/src/registrar/forms/domain.py +++ b/src/registrar/forms/domain.py @@ -445,11 +445,10 @@ class DomainOrgNameAddressForm(forms.ModelForm): """Form for updating the organization name and mailing address.""" # for federal agencies we also want to know the top-level agency. - excluded_agencies = ["gov Administration", "Non-Federal Agency"] federal_agency = forms.ModelChoiceField( label="Federal agency", required=False, - queryset=FederalAgency.objects.exclude(agency__in=excluded_agencies), + queryset=FederalAgency.objects.all(), empty_label="--Select--", widget=ComboboxWidget, ) From 0e9d69a26f8eeab84c0c7a73e3f97b86fc10848d Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Mon, 13 Jan 2025 15:43:37 -0500 Subject: [PATCH 030/252] clean up summary boxes --- .../assets/src/sass/_theme/_admin.scss | 19 ------------ .../assets/src/sass/_theme/_base.scss | 10 ------- .../assets/src/sass/_theme/_summary-box.scss | 25 +++++++--------- .../assets/src/sass/_theme/_typography.scss | 6 ++-- .../admin/domain_delete_confirmation.html | 2 +- .../domain_delete_selected_confirmation.html | 2 +- src/registrar/templates/domain_detail.html | 25 +++++++--------- src/registrar/templates/domain_dnssec.html | 30 ++++++++++--------- .../domain_request_awaiting_review.html | 4 +-- .../includes/profile_information.html | 2 +- .../includes/request_status_manage.html | 24 +++++++-------- 11 files changed, 56 insertions(+), 93 deletions(-) diff --git a/src/registrar/assets/src/sass/_theme/_admin.scss b/src/registrar/assets/src/sass/_theme/_admin.scss index afdb39551..7ffd6d6b1 100644 --- a/src/registrar/assets/src/sass/_theme/_admin.scss +++ b/src/registrar/assets/src/sass/_theme/_admin.scss @@ -516,10 +516,6 @@ input[type=submit].button--dja-toolbar:focus, input[type=submit].button--dja-too max-width: 68ex; } -.usa-summary-box__dhs-color { - color: $dhs-blue-70; -} - details.dja-detail-table { display: inline-table; background-color: var(--body-bg); @@ -812,21 +808,6 @@ div.dja__model-description{ text-decoration: underline !important; } -//-- Override some styling for the USWDS summary box (per design guidance for ticket #2055) -// Keep it scoped to admin.scss -.dashboard { - .usa-summary-box { - background: #{$dhs-blue-10}; - border-color: #{$dhs-blue-30}; - max-width: 72ex; - word-wrap: break-word; - } - - .usa-summary-box h3 { - color: #{$dhs-blue-60}; - } -} - .module caption, .inline-group h2 { text-transform: capitalize; } diff --git a/src/registrar/assets/src/sass/_theme/_base.scss b/src/registrar/assets/src/sass/_theme/_base.scss index 0e726ff37..60018511f 100644 --- a/src/registrar/assets/src/sass/_theme/_base.scss +++ b/src/registrar/assets/src/sass/_theme/_base.scss @@ -129,16 +129,6 @@ grid column to the max-width of the searchbar, which was calculated to be 33rem. word-break: break-word; } -.dotgov-status-box { - background-color: color('primary-lightest'); - border-color: color('accent-cool-lighter'); -} - -.dotgov-status-box--action-need { - background-color: color('warning-lighter'); - border-color: color('warning'); -} - footer { border-top: 1px solid color('primary-darker'); } diff --git a/src/registrar/assets/src/sass/_theme/_summary-box.scss b/src/registrar/assets/src/sass/_theme/_summary-box.scss index fdaf165e2..112829e6c 100644 --- a/src/registrar/assets/src/sass/_theme/_summary-box.scss +++ b/src/registrar/assets/src/sass/_theme/_summary-box.scss @@ -1,17 +1,12 @@ -// USWDS override to basically match the header size to a standard h3 size -// This get complicated because USWDS sets a size on the container then a relative -// size on the header. We'll need to reset the container size, override the header size, -// then 'fix' the content size. -.usa-summary-box { - font-size: 1rem; - .usa-summary-box__heading { - // 1.17em / 18.72px - @include typeset('sans', 'sm', 6); - } - p, li, dd { - // 1.06rem / 16.96px - @include typeset('sans', 'sm', 5); - } -} +@use "uswds-core" as *; +.usa-summary-box { + background-color: color('primary-lightest'); + border-color: color('accent-cool-lighter'); +} + +.usa-summary-box--action-needed { + background-color: color('warning-lighter'); + border-color: color('warning'); +} \ No newline at end of file diff --git a/src/registrar/assets/src/sass/_theme/_typography.scss b/src/registrar/assets/src/sass/_theme/_typography.scss index db19a595b..3fb61ccfd 100644 --- a/src/registrar/assets/src/sass/_theme/_typography.scss +++ b/src/registrar/assets/src/sass/_theme/_typography.scss @@ -10,17 +10,19 @@ address, max-width: measure(5); } +h1, h2, h3, h4, h5, h6 { + color: color('primary-darker'); +} + h1 { @include typeset('sans', '2xl', 2); margin: 0 0 units(2); - color: color('primary-darker'); } h2 { font-weight: font-weight('semibold'); line-height: line-height('heading', 3); margin: units(4) 0 units(1); - color: color('primary-darker'); } .header--body { diff --git a/src/registrar/templates/django/admin/domain_delete_confirmation.html b/src/registrar/templates/django/admin/domain_delete_confirmation.html index 5a9bef5b0..3228188d7 100644 --- a/src/registrar/templates/django/admin/domain_delete_confirmation.html +++ b/src/registrar/templates/django/admin/domain_delete_confirmation.html @@ -8,7 +8,7 @@ aria-labelledby="summary-box-description" >
-

+

When a domain is deleted:

diff --git a/src/registrar/templates/django/admin/domain_delete_selected_confirmation.html b/src/registrar/templates/django/admin/domain_delete_selected_confirmation.html index 3e0a32a4d..68f726708 100644 --- a/src/registrar/templates/django/admin/domain_delete_selected_confirmation.html +++ b/src/registrar/templates/django/admin/domain_delete_selected_confirmation.html @@ -9,7 +9,7 @@ aria-labelledby="summary-box-description" >
-

+

When a domain is deleted:

diff --git a/src/registrar/templates/domain_detail.html b/src/registrar/templates/domain_detail.html index a5b8e52cb..56c46ed0f 100644 --- a/src/registrar/templates/domain_detail.html +++ b/src/registrar/templates/domain_detail.html @@ -23,19 +23,15 @@

{{ domain.name }}

-

- - Status: - - - +

+ Status: {# UNKNOWN domains would not have an expiration date and thus would show 'Expired' #} {% if domain.is_expired and domain.state != domain.State.UNKNOWN %} Expired @@ -46,9 +42,10 @@ {% else %} {{ domain.state|title }} {% endif %} - +

+ {% if domain.get_state_help_text %} -
+

{% if has_domain_renewal_flag and domain.is_expiring and is_domain_manager %} This domain will expire soon. Renew to maintain access. {% elif has_domain_renewal_flag and domain.is_expiring and is_portfolio_user %} @@ -56,13 +53,11 @@ {% else %} {{ domain.get_state_help_text }} {% endif %} -

+

{% endif %} -

+
-
-
- +
{% include "includes/domain_dates.html" %} diff --git a/src/registrar/templates/domain_dnssec.html b/src/registrar/templates/domain_dnssec.html index a795fb2fc..a55bd8d23 100644 --- a/src/registrar/templates/domain_dnssec.html +++ b/src/registrar/templates/domain_dnssec.html @@ -33,23 +33,25 @@
{% csrf_token %} - {% if has_dnssec_records %} +
-

-

To fully disable DNSSEC

-
    -
  • Click “Disable DNSSEC” below.
  • -
  • Wait until the Time to Live (TTL) expires on your DNSSEC records managed by your DNS hosting provider. This is often less than 24 hours, but confirm with your provider.
  • -
  • After the TTL expiration, disable DNSSEC at your DNS hosting provider.
  • -
-

Warning: If you disable DNSSEC at your DNS hosting provider before TTL expiration, this may cause your domain to appear offline.

+

To fully disable DNSSEC

+ +
+
    +
  • Click “Disable DNSSEC” below.
  • +
  • Wait until the Time to Live (TTL) expires on your DNSSEC records managed by your DNS hosting provider. This is often less than 24 hours, but confirm with your provider.
  • +
  • After the TTL expiration, disable DNSSEC at your DNS hosting provider.
  • +
+

Warning: If you disable DNSSEC at your DNS hosting provider before TTL expiration, this may cause your domain to appear offline.

+
+

DNSSEC is enabled on your domain

@@ -60,7 +62,7 @@ data-open-modal >Disable DNSSEC - {% else %} +
@@ -69,7 +71,7 @@
Enable DNSSEC
- {% endif %} +
+

Next steps in this process

We received your .gov domain request. Our next step is to review your request. This usually takes 30 business days. We’ll email you if we have questions and when we complete our review. Contact us with any questions.

{% if show_withdraw_text %} -

+

Need to make changes?

diff --git a/src/registrar/templates/includes/profile_information.html b/src/registrar/templates/includes/profile_information.html index 3e7c827f1..257e8d1dc 100644 --- a/src/registrar/templates/includes/profile_information.html +++ b/src/registrar/templates/includes/profile_information.html @@ -12,7 +12,7 @@ Your contact information
-
    +
    • Full name: {{ user.get_formatted_name }}
    • Organization email: {{ user.email }}
    • Title or role in your organization: {{ user.title }}
    • diff --git a/src/registrar/templates/includes/request_status_manage.html b/src/registrar/templates/includes/request_status_manage.html index b4738d0d0..17083b360 100644 --- a/src/registrar/templates/includes/request_status_manage.html +++ b/src/registrar/templates/includes/request_status_manage.html @@ -39,22 +39,20 @@ {% block status_summary %}
      -
      -

      - - Status: - - {{ DomainRequest.get_status_display|default:"ERROR Please contact technical support/dev" }} -

      +
      +
      +

      + Status: + {{ DomainRequest.get_status_display|default:"ERROR Please contact technical support/dev" }} +

      +
      +
      -
      -
      {% endblock status_summary %} {% block status_metadata %} @@ -142,7 +140,7 @@
      {% block request_summary_header %} -

      Summary of your domain request

      +

      Summary of your domain request

      {% endblock request_summary_header%} {% block request_summary %} From 828c47de4528fc7bec86639087f91cfe628e835a Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Mon, 13 Jan 2025 15:06:44 -0700 Subject: [PATCH 031/252] Better table + simplify get_or_create function --- src/registrar/admin.py | 3 ++ src/registrar/models/domain.py | 68 ++++++++++++++-------------------- 2 files changed, 30 insertions(+), 41 deletions(-) diff --git a/src/registrar/admin.py b/src/registrar/admin.py index 849cb6100..ed0b776ce 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -3835,6 +3835,9 @@ class PublicContactAdmin(ListHeaderAdmin, ImportExportModelAdmin): change_form_template = "django/admin/email_clipboard_change_form.html" autocomplete_fields = ["domain"] + list_display = ("domain", "email", "name", "contact_type", "id") + search_fields = ["email", "name", "id"] + search_help_text = "Search by email, name or id." def changeform_view(self, request, object_id=None, form_url="", extra_context=None): if extra_context is None: diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 6bd8278a1..cb912cb37 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -4,9 +4,9 @@ import ipaddress import re from datetime import date, timedelta from typing import Optional +from django.db import transaction from django_fsm import FSMField, transition, TransitionNotAllowed # type: ignore - -from django.db import models +from django.db import models, IntegrityError from django.utils import timezone from typing import Any from registrar.models.host import Host @@ -2077,49 +2077,35 @@ class Domain(TimeStampedModel, DomainHelper): def _get_or_create_public_contact(self, public_contact: PublicContact): """Tries to find a PublicContact object in our DB. If it can't, it'll create it. Returns PublicContact""" - db_contact = PublicContact.objects.filter( - registry_id=public_contact.registry_id, - contact_type=public_contact.contact_type, - domain=self, - ) - - # If we find duplicates, log it and delete the oldest ones. - if db_contact.count() > 1: - logger.warning("_get_or_create_public_contact() -> Duplicate contacts found. Deleting duplicate.") - - newest_duplicate = db_contact.order_by("-created_at").first() - - duplicates_to_delete = db_contact.exclude(id=newest_duplicate.id) # type: ignore - - # Delete all duplicates - duplicates_to_delete.delete() - - # Do a second filter to grab the latest content - db_contact = PublicContact.objects.filter( + try: + with transaction.atomic(): + contact, _ = PublicContact.objects.get_or_create( + registry_id=public_contact.registry_id, + contact_type=public_contact.contact_type, + domain=self, + defaults={ + "email": public_contact.email, + "voice": public_contact.voice, + "fax": public_contact.fax, + "name": public_contact.name, + "org": public_contact.org, + "pw": public_contact.pw, + "city": public_contact.city, + "pc": public_contact.pc, + "cc": public_contact.cc, + "sp": public_contact.sp, + "street1": public_contact.street1, + "street2": public_contact.street2, + "street3": public_contact.street3, + } + ) + except IntegrityError: + contact = PublicContact.objects.get( registry_id=public_contact.registry_id, contact_type=public_contact.contact_type, domain=self, ) - - # Save to DB if it doesn't exist already. - if db_contact.count() == 0: - # Doesn't run custom save logic, just saves to DB - public_contact.save(skip_epp_save=True) - logger.info(f"Created a new PublicContact: {public_contact}") - # Append the item we just created - return public_contact - - existing_contact = db_contact.get() - - # Does the item we're grabbing match what we have in our DB? - if existing_contact.email != public_contact.email or existing_contact.registry_id != public_contact.registry_id: - existing_contact.delete() - public_contact.save() - logger.warning("Requested PublicContact is out of sync " "with DB.") - return public_contact - - # If it already exists, we can assume that the DB instance was updated during set, so we should just use that. - return existing_contact + return contact def _registrant_to_public_contact(self, registry_id: str): """EPPLib returns the registrant as a string, From e2486c127da6627b4bdcdac15bfeeaf81597d687 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Tue, 14 Jan 2025 08:42:22 -0500 Subject: [PATCH 032/252] organization edit form - added state territory combobox --- src/registrar/forms/portfolio.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/registrar/forms/portfolio.py b/src/registrar/forms/portfolio.py index 0a8c4d623..4e2e7bdf1 100644 --- a/src/registrar/forms/portfolio.py +++ b/src/registrar/forms/portfolio.py @@ -6,6 +6,7 @@ from django.core.validators import RegexValidator from django.core.validators import MaxLengthValidator from django.utils.safestring import mark_safe +from registrar.forms.utility.combobox import ComboboxWidget from registrar.models import ( PortfolioInvitation, UserPortfolioPermission, @@ -33,6 +34,12 @@ class PortfolioOrgAddressForm(forms.ModelForm): "required": "Enter a 5-digit or 9-digit zip code, like 12345 or 12345-6789.", }, ) + state_territory = forms.ChoiceField( + label="State, territory, or military post", + required=True, + choices=DomainInformation.StateTerritoryChoices.choices, + widget=ComboboxWidget, + ) class Meta: model = Portfolio @@ -53,19 +60,9 @@ class PortfolioOrgAddressForm(forms.ModelForm): "zipcode": {"required": "Enter a 5-digit or 9-digit zip code, like 12345 or 12345-6789."}, } widgets = { - # We need to set the required attributed for State/territory - # because for this fields we are creating an individual - # instance of the Select. For the other fields we use the for loop to set - # the class's required attribute to true. "address_line1": forms.TextInput, "address_line2": forms.TextInput, "city": forms.TextInput, - "state_territory": forms.Select( - attrs={ - "required": True, - }, - choices=DomainInformation.StateTerritoryChoices.choices, - ), # "urbanization": forms.TextInput, } From c0c9817c99459a0ecfef76a59fe12a9ea9483bfa Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 14 Jan 2025 10:33:00 -0700 Subject: [PATCH 033/252] Update admin.py --- 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 ed0b776ce..8773f7ef8 100644 --- a/src/registrar/admin.py +++ b/src/registrar/admin.py @@ -3835,9 +3835,9 @@ class PublicContactAdmin(ListHeaderAdmin, ImportExportModelAdmin): change_form_template = "django/admin/email_clipboard_change_form.html" autocomplete_fields = ["domain"] - list_display = ("domain", "email", "name", "contact_type", "id") - search_fields = ["email", "name", "id"] - search_help_text = "Search by email, name or id." + list_display = ("name", "contact_type", "email", "domain", "registry_id") + search_fields = ["email", "name", "registry_id"] + search_help_text = "Search by email, name or registry id." def changeform_view(self, request, object_id=None, form_url="", extra_context=None): if extra_context is None: From f3f1a1fb062aaece1a8b4570b8014f3ddebf5081 Mon Sep 17 00:00:00 2001 From: Matt-Spence Date: Tue, 14 Jan 2025 12:03:36 -0600 Subject: [PATCH 034/252] Update docs/developer/cloning-databases.md Co-authored-by: zandercymatics <141044360+zandercymatics@users.noreply.github.com> --- docs/developer/cloning-databases.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/developer/cloning-databases.md b/docs/developer/cloning-databases.md index 4dcb5f52d..a193e4685 100644 --- a/docs/developer/cloning-databases.md +++ b/docs/developer/cloning-databases.md @@ -11,7 +11,7 @@ Step 1: Get the name of the correct service using `cf spaces-users cisa-dotgov stable`. There should only be one user with a name that is a UUID, that is the one you want. step 2: -Remove the space develeper role by doing the following command: +Remove the space developer role by doing the following command: `cf unset-space-role [USER] cisa-dotgov staging SpaceDeveloper` This will cause the job to fail without requiring pushing anything to main. From 20e8ad5e1e3f276a10aec6507243ffd8a7121597 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Tue, 14 Jan 2025 14:08:28 -0500 Subject: [PATCH 035/252] header revision wip --- .../assets/src/sass/_theme/_admin.scss | 2 +- .../assets/src/sass/_theme/_base.scss | 8 ++- .../src/sass/_theme/_register-form.scss | 18 +---- .../assets/src/sass/_theme/_typography.scss | 6 +- src/registrar/templates/domain_detail.html | 2 +- .../templates/includes/domain_dates.html | 2 +- .../domain_request_awaiting_review.html | 4 +- .../includes/member_domain_management.html | 2 +- .../includes/member_permissions.html | 6 +- .../includes/request_status_manage.html | 69 +++++++------------ .../templates/includes/summary_item.html | 6 +- .../portfolio_member_permissions.html | 8 +-- .../templates/portfolio_members_add_new.html | 9 +-- 13 files changed, 54 insertions(+), 88 deletions(-) diff --git a/src/registrar/assets/src/sass/_theme/_admin.scss b/src/registrar/assets/src/sass/_theme/_admin.scss index 7ffd6d6b1..34ab3024d 100644 --- a/src/registrar/assets/src/sass/_theme/_admin.scss +++ b/src/registrar/assets/src/sass/_theme/_admin.scss @@ -188,7 +188,7 @@ html[data-theme="dark"] { } #branding h1, -h1, h2, h3, +.dashboard h1, .dashboard h2, .dashboard h3, .module h2 { font-weight: font-weight('bold'); } diff --git a/src/registrar/assets/src/sass/_theme/_base.scss b/src/registrar/assets/src/sass/_theme/_base.scss index 60018511f..76c971709 100644 --- a/src/registrar/assets/src/sass/_theme/_base.scss +++ b/src/registrar/assets/src/sass/_theme/_base.scss @@ -270,4 +270,10 @@ abbr[title] { .maxw-fit-content { max-width: fit-content; -} \ No newline at end of file +} + +.summary-item__title { + // match h3 at 18.72px and semibold + font-size: 1.17em; + font-weight: 600; +} diff --git a/src/registrar/assets/src/sass/_theme/_register-form.scss b/src/registrar/assets/src/sass/_theme/_register-form.scss index fcc5b5ae6..ddd162383 100644 --- a/src/registrar/assets/src/sass/_theme/_register-form.scss +++ b/src/registrar/assets/src/sass/_theme/_register-form.scss @@ -64,26 +64,10 @@ margin-top: units(3); } - .summary-item hr, +.summary-item hr, .review__step hr { border: none; //reset border-top: 1px solid color('primary-dark'); margin-top: 0; margin-bottom: units(0.5); } - -.review__step__title a:visited { - color: color('primary'); -} - -.review__step__name { - color: color('primary-dark'); - font-weight: font-weight('semibold'); - margin-bottom: units(0.5); -} - -.review__step__subheading { - color: color('primary-dark'); - font-weight: font-weight('semibold'); - margin-bottom: units(0.5); -} diff --git a/src/registrar/assets/src/sass/_theme/_typography.scss b/src/registrar/assets/src/sass/_theme/_typography.scss index 3fb61ccfd..457c1a367 100644 --- a/src/registrar/assets/src/sass/_theme/_typography.scss +++ b/src/registrar/assets/src/sass/_theme/_typography.scss @@ -20,11 +20,13 @@ h1 { } h2 { - font-weight: font-weight('semibold'); - line-height: line-height('heading', 3); margin: units(4) 0 units(1); } +h3, h4 { + font-weight: font-weight('semibold'); +} + .header--body { margin-top: units(2); font-weight: font-weight('semibold'); diff --git a/src/registrar/templates/domain_detail.html b/src/registrar/templates/domain_detail.html index 56c46ed0f..2776ac40a 100644 --- a/src/registrar/templates/domain_detail.html +++ b/src/registrar/templates/domain_detail.html @@ -30,7 +30,7 @@
      -

      +

      Status: {# UNKNOWN domains would not have an expiration date and thus would show 'Expired' #} {% if domain.is_expired and domain.state != domain.State.UNKNOWN %} diff --git a/src/registrar/templates/includes/domain_dates.html b/src/registrar/templates/includes/domain_dates.html index c05e202e1..b14c091d0 100644 --- a/src/registrar/templates/includes/domain_dates.html +++ b/src/registrar/templates/includes/domain_dates.html @@ -1,5 +1,5 @@ {% if domain.expiration_date or domain.created_at %} -

      +

      {% if domain.expiration_date %} Expires: {{ domain.expiration_date|date }} diff --git a/src/registrar/templates/includes/domain_request_awaiting_review.html b/src/registrar/templates/includes/domain_request_awaiting_review.html index 7d04639aa..852d6bd60 100644 --- a/src/registrar/templates/includes/domain_request_awaiting_review.html +++ b/src/registrar/templates/includes/domain_request_awaiting_review.html @@ -1,12 +1,12 @@ {% load url_helpers %} -

      +

      Next steps in this process

      We received your .gov domain request. Our next step is to review your request. This usually takes 30 business days. We’ll email you if we have questions and when we complete our review. Contact us with any questions.

      {% if show_withdraw_text %} -

      +

      Need to make changes?

      diff --git a/src/registrar/templates/includes/member_domain_management.html b/src/registrar/templates/includes/member_domain_management.html index 6bf3f1320..1e5b29994 100644 --- a/src/registrar/templates/includes/member_domain_management.html +++ b/src/registrar/templates/includes/member_domain_management.html @@ -1,4 +1,4 @@ -

      Assigned domains

      +

      Assigned domains

      {% if domain_count > 0 %}

      {{domain_count}}

      {% else %} diff --git a/src/registrar/templates/includes/member_permissions.html b/src/registrar/templates/includes/member_permissions.html index 8cf75cfbf..4833b5e4b 100644 --- a/src/registrar/templates/includes/member_permissions.html +++ b/src/registrar/templates/includes/member_permissions.html @@ -1,4 +1,4 @@ -

      Member access

      +

      Member access

      {% if permissions.roles and 'organization_admin' in permissions.roles %}

      Admin access

      {% elif permissions.roles and 'organization_member' in permissions.roles %} @@ -7,7 +7,7 @@

      {% endif %} -

      Organization domain requests

      +

      Organization domain requests

      {% if member_has_edit_request_portfolio_permission %}

      View all requests plus create requests

      {% elif member_has_view_all_requests_portfolio_permission %} @@ -16,7 +16,7 @@

      No access

      {% endif %} -

      Organization members

      +

      Organization members

      {% if member_has_edit_members_portfolio_permission %}

      View all members plus manage members

      {% elif member_has_view_members_portfolio_permission %} diff --git a/src/registrar/templates/includes/request_status_manage.html b/src/registrar/templates/includes/request_status_manage.html index 17083b360..f50d31bfc 100644 --- a/src/registrar/templates/includes/request_status_manage.html +++ b/src/registrar/templates/includes/request_status_manage.html @@ -59,12 +59,12 @@ {% if portfolio %} {% if DomainRequest.creator %} -

      - Created by: {{DomainRequest.creator.email|default:DomainRequest.creator.get_formatted_name }} +

      + Created by: {{DomainRequest.creator.email|default:DomainRequest.creator.get_formatted_name }}

      {% else %} -

      - No creator found: this is an error, please email help@get.gov. +

      + No creator found: this is an error, please email help@get.gov.

      {% endif %} {% endif %} @@ -75,49 +75,32 @@ There is some code repetition, but it gives us more flexibility rather than a dense reduction. Leave it this way until we've solidified our requirements. {% endcomment %} - {% if DomainRequest.status == statuses.STARTED %} - {% with first_started_date=DomainRequest.get_first_status_started_date|date:"F j, Y" %} -

      + +

      + {% if DomainRequest.status == statuses.STARTED %} + {% with first_started_date=DomainRequest.get_first_status_started_date|date:"F j, Y" %} {% comment %} A newly created domain request will not have a value for last_status update. This is because the status never really updated. However, if this somehow goes back to started we can default to displaying that new date. {% endcomment %} - Started on: {{last_status_update|default:first_started_date}} -

      - {% endwith %} - {% elif DomainRequest.status == statuses.SUBMITTED %} -

      - Submitted on: {{last_submitted|default:first_submitted }} -

      -

      - Last updated on: {{DomainRequest.updated_at|date:"F j, Y"}} -

      - {% elif DomainRequest.status == statuses.ACTION_NEEDED %} -

      - Submitted on: {{last_submitted|default:first_submitted }} -

      -

      - Last updated on: {{DomainRequest.updated_at|date:"F j, Y"}} -

      - {% elif DomainRequest.status == statuses.REJECTED %} -

      - Submitted on: {{last_submitted|default:first_submitted }} -

      -

      - Rejected on: {{last_status_update}} -

      - {% elif DomainRequest.status == statuses.WITHDRAWN %} -

      - Submitted on: {{last_submitted|default:first_submitted }} -

      -

      - Withdrawn on: {{last_status_update}} -

      - {% else %} - {% comment %} Shown for in_review, approved, ineligible {% endcomment %} -

      - Last updated on: {{DomainRequest.updated_at|date:"F j, Y"}} + Started on: {{last_status_update|default:first_started_date}} + {% endwith %} + {% elif DomainRequest.status == statuses.SUBMITTED %} + Submitted on: {{last_submitted|default:first_submitted }}
      + Last updated on: {{DomainRequest.updated_at|date:"F j, Y"}} + {% elif DomainRequest.status == statuses.ACTION_NEEDED %} + Submitted on: {{last_submitted|default:first_submitted }}
      + Last updated on: {{DomainRequest.updated_at|date:"F j, Y"}} + {% elif DomainRequest.status == statuses.REJECTED %} + Submitted on: {{last_submitted|default:first_submitted }}
      + Rejected on: {{last_status_update}} + {% elif DomainRequest.status == statuses.WITHDRAWN %} + Submitted on: {{last_submitted|default:first_submitted }}
      + Withdrawn on: {{last_status_update}} + {% else %} + {% comment %} Shown for in_review, approved, ineligible {% endcomment %} + Last updated on: {{DomainRequest.updated_at|date:"F j, Y"}}

      {% endif %} {% endwith %} @@ -125,7 +108,7 @@ {% block status_blurb %} {% if DomainRequest.is_awaiting_review %} -

      {% include "includes/domain_request_awaiting_review.html" with show_withdraw_text=DomainRequest.is_withdrawable %}

      + {% include "includes/domain_request_awaiting_review.html" with show_withdraw_text=DomainRequest.is_withdrawable %} {% endif %} {% endblock status_blurb %} diff --git a/src/registrar/templates/includes/summary_item.html b/src/registrar/templates/includes/summary_item.html index bbdfc8dee..0b0cea3c1 100644 --- a/src/registrar/templates/includes/summary_item.html +++ b/src/registrar/templates/includes/summary_item.html @@ -10,8 +10,6 @@

      @@ -41,8 +39,6 @@

      Contact {{forloop.counter}} @@ -143,7 +139,7 @@

      Admin access permissions

      Member permissions available for admin-level acccess.

      -

      Organization domain requests

      {% with group_classes="usa-form-editable usa-form-editable--no-border bg-gray-1 padding-top-0" %} {% input_with_errors form.domain_request_permission_admin %} {% endwith %} -

      Organization members

      {% with group_classes="usa-form-editable usa-form-editable--no-border bg-gray-1 padding-top-0" %} @@ -116,7 +114,7 @@

      Basic member permissions

      Member permissions available for basic-level acccess.

      -

      Organization domain requests

      +

      Organization domain requests

      {% with group_classes="usa-form-editable usa-form-editable--no-border bg-gray-1 padding-top-0" %} {% input_with_errors form.domain_request_permission_member %} {% endwith %} diff --git a/src/registrar/templates/portfolio_members_add_new.html b/src/registrar/templates/portfolio_members_add_new.html index 092a9af31..d44233f49 100644 --- a/src/registrar/templates/portfolio_members_add_new.html +++ b/src/registrar/templates/portfolio_members_add_new.html @@ -68,15 +68,13 @@

      Admin access permissions

      Member permissions available for admin-level acccess.

      -

      Organization domain requests

      {% with group_classes="usa-form-editable usa-form-editable--no-border bg-gray-1 padding-top-0" %} {% input_with_errors form.domain_request_permission_admin %} {% endwith %} -

      Organization members

      {% with group_classes="usa-form-editable usa-form-editable--no-border bg-gray-1 padding-top-0" %} @@ -127,8 +125,7 @@

      Invite this member to the organization?

      -

      Member information and permissions

      +

      Member information and permissions

      Email

      From 3337e5dc9c011df846ec449d201c14961a0b03a9 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Tue, 14 Jan 2025 14:46:47 -0500 Subject: [PATCH 036/252] Header refactor wip --- .../js/getgov/table-edit-member-domains.js | 6 ++-- .../assets/src/sass/_theme/_base.scss | 6 ---- .../assets/src/sass/_theme/_forms.scss | 18 +++++----- .../src/sass/_theme/_register-form.scss | 4 --- .../assets/src/sass/_theme/_typography.scss | 33 +++++++------------ src/registrar/templates/domain_dnssec.html | 6 ++-- .../templates/includes/input_read_only.html | 4 +-- .../portfolio_request_review_steps.html | 2 +- .../includes/request_review_steps.html | 10 +++--- .../includes/request_status_manage.html | 4 +-- .../templates/includes/summary_item.html | 6 ++-- .../portfolio_member_permissions.html | 2 +- .../templates/portfolio_members_add_new.html | 2 +- .../templates/portfolio_organization.html | 8 ++--- src/registrar/tests/test_views_portfolio.py | 12 +++---- 15 files changed, 50 insertions(+), 73 deletions(-) diff --git a/src/registrar/assets/src/js/getgov/table-edit-member-domains.js b/src/registrar/assets/src/js/getgov/table-edit-member-domains.js index 86aa39c37..4f0b1d610 100644 --- a/src/registrar/assets/src/js/getgov/table-edit-member-domains.js +++ b/src/registrar/assets/src/js/getgov/table-edit-member-domains.js @@ -259,7 +259,7 @@ export class EditMemberDomainsTable extends BaseTable { // Append unassigned domains section if (this.removedDomains.length) { const unassignedHeader = document.createElement('h3'); - unassignedHeader.classList.add('header--body', 'text-primary', 'margin-bottom-1'); + unassignedHeader.classList.add('margin-bottom-1'); unassignedHeader.textContent = 'Unassigned domains'; domainAssignmentSummary.appendChild(unassignedHeader); domainAssignmentSummary.appendChild(unassignedDomainsList); @@ -268,7 +268,7 @@ export class EditMemberDomainsTable extends BaseTable { // Append assigned domains section if (this.addedDomains.length) { const assignedHeader = document.createElement('h3'); - assignedHeader.classList.add('header--body', 'text-primary', 'margin-bottom-1'); + assignedHeader.classList.add('margin-bottom-1'); assignedHeader.textContent = 'Assigned domains'; domainAssignmentSummary.appendChild(assignedHeader); domainAssignmentSummary.appendChild(assignedDomainsList); @@ -276,7 +276,7 @@ export class EditMemberDomainsTable extends BaseTable { // Append total assigned domains section const totalHeader = document.createElement('h3'); - totalHeader.classList.add('header--body', 'text-primary', 'margin-bottom-1'); + totalHeader.classList.add('margin-bottom-1'); totalHeader.textContent = 'Total assigned domains'; domainAssignmentSummary.appendChild(totalHeader); const totalCount = document.createElement('p'); diff --git a/src/registrar/assets/src/sass/_theme/_base.scss b/src/registrar/assets/src/sass/_theme/_base.scss index 76c971709..fa4005cfd 100644 --- a/src/registrar/assets/src/sass/_theme/_base.scss +++ b/src/registrar/assets/src/sass/_theme/_base.scss @@ -271,9 +271,3 @@ abbr[title] { .maxw-fit-content { max-width: fit-content; } - -.summary-item__title { - // match h3 at 18.72px and semibold - font-size: 1.17em; - font-weight: 600; -} diff --git a/src/registrar/assets/src/sass/_theme/_forms.scss b/src/registrar/assets/src/sass/_theme/_forms.scss index 4138c5878..10fcd870c 100644 --- a/src/registrar/assets/src/sass/_theme/_forms.scss +++ b/src/registrar/assets/src/sass/_theme/_forms.scss @@ -2,6 +2,14 @@ @use "cisa_colors" as *; @use "typography" as *; +// Normalize typography in forms +.usa-form, +.usa-form fieldset { + font-size: 1rem; + .usa-legend { + font-size: 1rem; + } +} .usa-form .usa-button { margin-top: units(3); } @@ -69,16 +77,6 @@ legend.float-left-tablet + button.float-right-tablet { } } -.read-only-label { - @extend .h4--sm-05; - font-weight: bold; - color: color('primary-dark'); -} - -.read-only-value { - margin-top: units(0); -} - .bg-gray-1 .usa-radio { background: color('gray-1'); } diff --git a/src/registrar/assets/src/sass/_theme/_register-form.scss b/src/registrar/assets/src/sass/_theme/_register-form.scss index ddd162383..9b29029b8 100644 --- a/src/registrar/assets/src/sass/_theme/_register-form.scss +++ b/src/registrar/assets/src/sass/_theme/_register-form.scss @@ -12,11 +12,7 @@ margin-top: units(1); } -// header--body is used on the summary page and -// should not be styled like the register form headers .register-form-step h3 { - color: color('primary-dark'); - letter-spacing: $letter-space--xs; margin-top: units(3); margin-bottom: 0; diff --git a/src/registrar/assets/src/sass/_theme/_typography.scss b/src/registrar/assets/src/sass/_theme/_typography.scss index 457c1a367..b796ad20d 100644 --- a/src/registrar/assets/src/sass/_theme/_typography.scss +++ b/src/registrar/assets/src/sass/_theme/_typography.scss @@ -15,6 +15,7 @@ h1, h2, h3, h4, h5, h6 { } h1 { + font-size: 2.125rem; @include typeset('sans', '2xl', 2); margin: 0 0 units(2); } @@ -23,35 +24,23 @@ h2 { margin: units(4) 0 units(1); } -h3, h4 { +h3, .h3 { + font-size: 1.25rem; font-weight: font-weight('semibold'); } -.header--body { - margin-top: units(2); +h4, .h4 { + font-size: 1.15rem; font-weight: font-weight('semibold'); - // The units mixin can only get us close, so it's between - // hardcoding the value and using in markup - font-size: 16.96px; -} - -.h4--sm-05 { - font-size: size('body', 'sm'); - font-weight: normal; - color: color('primary'); - margin-bottom: units(0.5); -} - -// Normalize typography in forms -.usa-form, -.usa-form fieldset { - font-size: 1rem; - .usa-legend { - font-size: 1rem; - } } .p--blockquote { padding-left: units(1); border-left: 2px solid color('base-lighter'); } + +.summary-item__title { + // match h3 at 18.72px and semibold + font-size: 1.25em; + font-weight: 600; +} diff --git a/src/registrar/templates/domain_dnssec.html b/src/registrar/templates/domain_dnssec.html index a55bd8d23..3beb2548e 100644 --- a/src/registrar/templates/domain_dnssec.html +++ b/src/registrar/templates/domain_dnssec.html @@ -33,7 +33,7 @@
      {% csrf_token %} - + {% if has_dnssec_records %}
      Disable DNSSEC - + {% else %}
      @@ -71,7 +71,7 @@
      Enable DNSSEC
      - + {% endif %}
      {{ field.label }}

      +

      {{ field.label }}

      {% if label_description %}

      {{ label_description }}

      {% endif %} @@ -11,4 +11,4 @@ Template include for read-only form fields This allows us to customize the displayed value. For instance, Select fields will display the id by default. {% endcomment %} -

      {{ value|default:field.value }}

      +

      {{ value|default:field.value }}

      diff --git a/src/registrar/templates/includes/portfolio_request_review_steps.html b/src/registrar/templates/includes/portfolio_request_review_steps.html index eecc5005a..c9bd99607 100644 --- a/src/registrar/templates/includes/portfolio_request_review_steps.html +++ b/src/registrar/templates/includes/portfolio_request_review_steps.html @@ -46,7 +46,7 @@ {% endwith %} {% if domain_request.alternative_domains.all %} -

      Alternative domains

      +

      Alternative domains

        {% for site in domain_request.alternative_domains.all %}
      • {{ site.website }}
      • diff --git a/src/registrar/templates/includes/request_review_steps.html b/src/registrar/templates/includes/request_review_steps.html index dada2dffb..42b5cda46 100644 --- a/src/registrar/templates/includes/request_review_steps.html +++ b/src/registrar/templates/includes/request_review_steps.html @@ -88,7 +88,7 @@ {% endwith %} {% if domain_request.alternative_domains.all %} -

        Alternative domains

        +

        Alternative domains

          {% for site in domain_request.alternative_domains.all %}
        • {{ site.website }}
        • @@ -132,8 +132,8 @@ {% with title=form_titles|get_item:step %} {% if domain_request.has_additional_details %} {% include "includes/summary_item.html" with title="Additional Details" value=" " heading_level=heading_level editable=is_editable edit_link=domain_request_url %} -

          CISA Regional Representative

          -
            +

            CISA Regional Representative

            +
              {% if domain_request.cisa_representative_first_name %}
            • {{domain_request.cisa_representative_first_name}} {{domain_request.cisa_representative_last_name}}
            • {% if domain_request.cisa_representative_email %} @@ -144,8 +144,8 @@ {% endif %}
            -

            Anything else

            -
              +

              Anything else

              +
                {% if domain_request.anything_else %} {{domain_request.anything_else}} {% else %} diff --git a/src/registrar/templates/includes/request_status_manage.html b/src/registrar/templates/includes/request_status_manage.html index f50d31bfc..b1c1a638d 100644 --- a/src/registrar/templates/includes/request_status_manage.html +++ b/src/registrar/templates/includes/request_status_manage.html @@ -198,7 +198,7 @@ {# We always show this field even if None #} {% if DomainRequest %} -

                CISA Regional Representative

                +

                CISA Regional Representative

                  {% if DomainRequest.cisa_representative_first_name %} {{ DomainRequest.get_formatted_cisa_rep_name }} @@ -206,7 +206,7 @@ No {% endif %}
                -

                Anything else

                +

                Anything else

                  {% if DomainRequest.anything_else %} {{DomainRequest.anything_else}} diff --git a/src/registrar/templates/includes/summary_item.html b/src/registrar/templates/includes/summary_item.html index 0b0cea3c1..46ffd2fb0 100644 --- a/src/registrar/templates/includes/summary_item.html +++ b/src/registrar/templates/includes/summary_item.html @@ -20,7 +20,7 @@

      {% endif %} {% if sub_header_text %} -

      {{ sub_header_text }}

      +

      {{ sub_header_text }}

      {% endif %} {% if permissions %} {% include "includes/member_permissions.html" with permissions=value %} @@ -38,7 +38,7 @@ {% for item in value %}
      -

      Contact {{forloop.counter}} @@ -115,7 +115,7 @@ {% endif %} {% endif %} {% if value.invitations.all %} -

      Invited domain managers

      +

      Invited domain managers

        {% for item in value.invitations.all %}
      • {{ item.email }}
      • diff --git a/src/registrar/templates/portfolio_member_permissions.html b/src/registrar/templates/portfolio_member_permissions.html index e4b3e3aa4..21f5593ec 100644 --- a/src/registrar/templates/portfolio_member_permissions.html +++ b/src/registrar/templates/portfolio_member_permissions.html @@ -103,7 +103,7 @@

        Organization members

        + margin-top-4">Organization members {% with group_classes="usa-form-editable usa-form-editable--no-border bg-gray-1 padding-top-0" %} {% input_with_errors form.member_permission_admin %} {% endwith %} diff --git a/src/registrar/templates/portfolio_members_add_new.html b/src/registrar/templates/portfolio_members_add_new.html index d44233f49..16dbded23 100644 --- a/src/registrar/templates/portfolio_members_add_new.html +++ b/src/registrar/templates/portfolio_members_add_new.html @@ -76,7 +76,7 @@

        Organization members

        + margin-top-4">Organization members {% with group_classes="usa-form-editable usa-form-editable--no-border bg-gray-1 padding-top-0" %} {% input_with_errors form.member_permission_admin %} {% endwith %} diff --git a/src/registrar/templates/portfolio_organization.html b/src/registrar/templates/portfolio_organization.html index 55064d902..e6bd19ec2 100644 --- a/src/registrar/templates/portfolio_organization.html +++ b/src/registrar/templates/portfolio_organization.html @@ -37,8 +37,8 @@ {% include "includes/required_fields.html" %}
        {% csrf_token %} -

        Organization name

        -

        +

        Organization name

        +

        {{ portfolio.federal_agency }}

        {% input_with_errors form.address_line1 %} @@ -53,8 +53,8 @@
        {% else %} -

        Organization name

        -

        +

        Organization name

        +

        {{ portfolio.federal_agency }}

        {% if form.address_line1.value is not None %} diff --git a/src/registrar/tests/test_views_portfolio.py b/src/registrar/tests/test_views_portfolio.py index 9bc97874d..76ccbb839 100644 --- a/src/registrar/tests/test_views_portfolio.py +++ b/src/registrar/tests/test_views_portfolio.py @@ -211,11 +211,11 @@ class TestPortfolio(WebTest): # Assert the response is a 200 self.assertEqual(response.status_code, 200) # The label for Federal agency will always be a h4 - self.assertContains(response, '

        Organization name

        ') + self.assertContains(response, '

        Organization name

        ') # The read only label for city will be a h4 - self.assertContains(response, '

        City

        ') + self.assertContains(response, '

        City

        ') self.assertNotContains(response, 'for="id_city"') - self.assertContains(response, '

        Los Angeles

        ') + self.assertContains(response, '

        Los Angeles

        ') @less_console_noise_decorator def test_portfolio_organization_page_edit_access(self): @@ -236,10 +236,10 @@ class TestPortfolio(WebTest): # Assert the response is a 200 self.assertEqual(response.status_code, 200) # The label for Federal agency will always be a h4 - self.assertContains(response, '

        Organization name

        ') + self.assertContains(response, '

        Organization name

        ') # The read only label for city will be a h4 - self.assertNotContains(response, '

        City

        ') - self.assertNotContains(response, '

        Los Angeles

        ') + self.assertNotContains(response, '

        City

        ') + self.assertNotContains(response, '

        Los Angeles

        ') self.assertContains(response, 'for="id_city"') @less_console_noise_decorator 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 037/252] 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 038/252] 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 52ba794d4210be85cf32666039f00e0ef40b5544 Mon Sep 17 00:00:00 2001 From: Rachid Mrad Date: Tue, 14 Jan 2025 16:40:50 -0500 Subject: [PATCH 039/252] cleanup --- src/registrar/assets/src/sass/_theme/_tooltips.scss | 2 +- src/registrar/templates/portfolio_member_domains_edit.html | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/registrar/assets/src/sass/_theme/_tooltips.scss b/src/registrar/assets/src/sass/_theme/_tooltips.scss index 58beb8ae6..65bfbb483 100644 --- a/src/registrar/assets/src/sass/_theme/_tooltips.scss +++ b/src/registrar/assets/src/sass/_theme/_tooltips.scss @@ -66,9 +66,9 @@ text-align: center; font-size: inherit; //inherit tooltip fontsize of .93rem max-width: fit-content; + display: block; @include at-media('desktop') { width: 70vw; } - display: block; } } \ No newline at end of file diff --git a/src/registrar/templates/portfolio_member_domains_edit.html b/src/registrar/templates/portfolio_member_domains_edit.html index 3169076ee..5f2185f84 100644 --- a/src/registrar/templates/portfolio_member_domains_edit.html +++ b/src/registrar/templates/portfolio_member_domains_edit.html @@ -88,13 +88,13 @@
        -

        Unassigned domains

        +

        Unassigned domains

        • item1
        • item2
        -

        Assigned domains

        +

        Assigned domains

        • item1
        • item2
        • From f86a065a607e1bbc0d3705193559ded32891d3d3 Mon Sep 17 00:00:00 2001 From: zandercymatics <141044360+zandercymatics@users.noreply.github.com> Date: Tue, 14 Jan 2025 15:04:56 -0700 Subject: [PATCH 040/252] Update domain.py --- src/registrar/models/domain.py | 85 +++++++++++++++++++++++----------- 1 file changed, 57 insertions(+), 28 deletions(-) diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index cb912cb37..5e186efff 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -1329,7 +1329,7 @@ class Domain(TimeStampedModel, DomainHelper): def get_default_administrative_contact(self): """Gets the default administrative contact.""" - logger.info("get_default_security_contact() -> Adding administrative security contact") + logger.info("get_default_administrative_contact() -> Adding administrative security contact") contact = PublicContact.get_default_administrative() contact.domain = self return contact @@ -1848,7 +1848,6 @@ class Domain(TimeStampedModel, DomainHelper): """ try: self._add_missing_contacts_if_unknown(cleaned) - except Exception as e: logger.error( "%s couldn't _add_missing_contacts_if_unknown, error was %s." @@ -1866,7 +1865,6 @@ class Domain(TimeStampedModel, DomainHelper): is in an UNKNOWN state, that is an error state) Note: The transition state change happens at the end of the function """ - missingAdmin = True missingSecurity = True missingTech = True @@ -1890,6 +1888,11 @@ class Domain(TimeStampedModel, DomainHelper): if missingTech: technical_contact = self.get_default_technical_contact() technical_contact.save() + + logger.info( + "_add_missing_contacts_if_unknown => " + f"missingAdmin: {missingAdmin}, missingSecurity: {missingSecurity}, missingTech: {missingTech}" + ) def _fetch_cache(self, fetch_hosts=False, fetch_contacts=False): """Contact registry for info about a domain.""" @@ -2077,35 +2080,61 @@ class Domain(TimeStampedModel, DomainHelper): def _get_or_create_public_contact(self, public_contact: PublicContact): """Tries to find a PublicContact object in our DB. If it can't, it'll create it. Returns PublicContact""" - try: - with transaction.atomic(): - contact, _ = PublicContact.objects.get_or_create( - registry_id=public_contact.registry_id, - contact_type=public_contact.contact_type, - domain=self, - defaults={ - "email": public_contact.email, - "voice": public_contact.voice, - "fax": public_contact.fax, - "name": public_contact.name, - "org": public_contact.org, - "pw": public_contact.pw, - "city": public_contact.city, - "pc": public_contact.pc, - "cc": public_contact.cc, - "sp": public_contact.sp, - "street1": public_contact.street1, - "street2": public_contact.street2, - "street3": public_contact.street3, - } - ) - except IntegrityError: - contact = PublicContact.objects.get( + db_contact = PublicContact.objects.filter( + registry_id=public_contact.registry_id, + contact_type=public_contact.contact_type, + domain=self, + ) + + # If we find duplicates, log it and delete the oldest ones. + if db_contact.count() > 1: + logger.warning("_get_or_create_public_contact() -> Duplicate contacts found. Deleting duplicate.") + + newest_duplicate = db_contact.order_by("-created_at").first() + + duplicates_to_delete = db_contact.exclude(id=newest_duplicate.id) # type: ignore + + # Delete all duplicates + duplicates_to_delete.delete() + + # Do a second filter to grab the latest content + db_contact = PublicContact.objects.filter( registry_id=public_contact.registry_id, contact_type=public_contact.contact_type, domain=self, ) - return contact + + # Save to DB if it doesn't exist already. + if db_contact.count() == 0: + # Doesn't run custom save logic, just saves to DB + try: + public_contact.save(skip_epp_save=True) + logger.info(f"Created a new PublicContact: {public_contact}") + except IntegrityError as err: + logger.error( + "_get_or_create_public_contact() => tried to create a duplicate public contact: " + f"{err}", exc_info=True + ) + return PublicContact.objects.get( + registry_id=public_contact.registry_id, + contact_type=public_contact.contact_type, + domain=self, + ) + + # Append the item we just created + return public_contact + + existing_contact = db_contact.get() + + # Does the item we're grabbing match what we have in our DB? + if existing_contact.email != public_contact.email or existing_contact.registry_id != public_contact.registry_id: + existing_contact.delete() + public_contact.save() + logger.warning("Requested PublicContact is out of sync " "with DB.") + return public_contact + + # If it already exists, we can assume that the DB instance was updated during set, so we should just use that. + return existing_contact def _registrant_to_public_contact(self, registry_id: str): """EPPLib returns the registrant as a string, From 352e1904313b302eec42599b549c695181912d1b Mon Sep 17 00:00:00 2001 From: CocoByte Date: Tue, 14 Jan 2025 15:43:57 -0700 Subject: [PATCH 041/252] 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 ea509f63efc2880052900ab80b587106200b453b Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Tue, 14 Jan 2025 18:03:24 -0500 Subject: [PATCH 042/252] uswds edits to combobox, default values for comboboxes, cleanup of combobox js --- src/registrar/assets/js/uswds-edited.js | 14 ++++++-- .../assets/src/js/getgov/combobox.js | 36 ------------------- src/registrar/forms/domain.py | 15 ++++---- src/registrar/forms/portfolio.py | 8 ++--- 4 files changed, 21 insertions(+), 52 deletions(-) diff --git a/src/registrar/assets/js/uswds-edited.js b/src/registrar/assets/js/uswds-edited.js index f59417b41..b597f2d2b 100644 --- a/src/registrar/assets/js/uswds-edited.js +++ b/src/registrar/assets/js/uswds-edited.js @@ -29,6 +29,8 @@ * - tooltip dynamic content updated to include nested element (for better sizing control) * - modal exposed to window to be accessible in other js files * - fixed bug in createHeaderButton which added newlines to header button tooltips + * - modified combobox to allow for blank values in list + * - modified aria label for X button in combobox to reflect modified behavior of button */ if ("document" in window.self) { @@ -1218,9 +1220,11 @@ const enhanceComboBox = _comboBoxEl => { input.setAttribute(key, value); })); comboBoxEl.insertAdjacentElement("beforeend", input); + // DOTGOV - modified the aria-label of the clear input button to Reset selection to reflect changed button behavior + // comboBoxEl.insertAdjacentHTML("beforeend", Sanitizer.escapeHTML` - +   @@ -1356,8 +1360,12 @@ const displayList = el => { for (let i = 0, len = selectEl.options.length; i < len; i += 1) { const optionEl = selectEl.options[i]; const optionId = `${listOptionBaseId}${options.length}`; - if (optionEl.value && (disableFiltering || isPristine || !inputValue || regex.test(optionEl.text))) { - if (selectEl.value && optionEl.value === selectEl.value) { + // DOTGOV: modified combobox to allow for options with blank value + //if (optionEl.value && (disableFiltering || isPristine || !inputValue || regex.test(optionEl.text))) { + if ((disableFiltering || isPristine || !inputValue || regex.test(optionEl.text))) { + // DOTGOV: modified combobox to allow blank option value selections to be considered selected + //if (selectEl.value && optionEl.value === selectEl.value) { + if (selectEl.value && optionEl.value === selectEl.value || (!selectEl.value && !optionEl.value)) { selectedItemId = optionId; } if (disableFiltering && !firstFoundId && regex.test(optionEl.text)) { diff --git a/src/registrar/assets/src/js/getgov/combobox.js b/src/registrar/assets/src/js/getgov/combobox.js index 36b7aa0ad..95c5ff7e8 100644 --- a/src/registrar/assets/src/js/getgov/combobox.js +++ b/src/registrar/assets/src/js/getgov/combobox.js @@ -28,19 +28,6 @@ export function loadInitialValuesForComboBoxes() { // Override the default clear button behavior such that it no longer clears the input, // it just resets to the data-initial-value. // Due to the nature of how uswds works, this is slightly hacky. - // Use a MutationObserver to watch for changes in the dropdown list - const dropdownList = comboBox.querySelector(`#${input.id}--list`); - const observer = new MutationObserver(function(mutations) { - mutations.forEach(function(mutation) { - if (mutation.type === "childList") { - addBlankOption(clearInputButton, dropdownList, initialValue); - } - }); - }); - - // Configure the observer to watch for changes in the dropdown list - const config = { childList: true, subtree: true }; - observer.observe(dropdownList, config); // Input event listener to detect typing input.addEventListener("input", () => { @@ -87,27 +74,4 @@ export function loadInitialValuesForComboBoxes() { showElement(clearInputButton) } } - - function addBlankOption(clearInputButton, dropdownList, initialValue) { - if (dropdownList && !dropdownList.querySelector('[data-value=""]') && !isTyping) { - const blankOption = document.createElement("li"); - blankOption.setAttribute("role", "option"); - blankOption.setAttribute("data-value", ""); - blankOption.classList.add("usa-combo-box__list-option"); - if (!initialValue){ - blankOption.classList.add("usa-combo-box__list-option--selected") - } - blankOption.textContent = "⎯"; - - dropdownList.insertBefore(blankOption, dropdownList.firstChild); - blankOption.addEventListener("click", (e) => { - e.preventDefault(); - e.stopPropagation(); - overrideDefaultClearButton = false; - // Trigger the default clear behavior - clearInputButton.click(); - overrideDefaultClearButton = true; - }); - } - } } diff --git a/src/registrar/forms/domain.py b/src/registrar/forms/domain.py index 7089409de..f577ed0ab 100644 --- a/src/registrar/forms/domain.py +++ b/src/registrar/forms/domain.py @@ -164,6 +164,7 @@ class DomainSuborganizationForm(forms.ModelForm): sub_organization = forms.ModelChoiceField( label="Suborganization name", queryset=Suborganization.objects.none(), + empty_label="⎯ (No suborganization)", required=False, widget=ComboboxWidget, ) @@ -468,12 +469,11 @@ class DomainOrgNameAddressForm(forms.ModelForm): state_territory = forms.ChoiceField( label="State, territory, or military post", required=True, - choices=DomainInformation.StateTerritoryChoices.choices, - widget=ComboboxWidget( - attrs={ - "required": True, - } - ), + choices=[("", "--Select--")] + DomainInformation.StateTerritoryChoices.choices, + error_messages={ + "required": ("Select the state, territory, or military post where your organization is located.") + }, + widget=ComboboxWidget(), ) class Meta: @@ -493,9 +493,6 @@ class DomainOrgNameAddressForm(forms.ModelForm): "organization_name": {"required": "Enter the name of your organization."}, "address_line1": {"required": "Enter the street address of your organization."}, "city": {"required": "Enter the city where your organization is located."}, - "state_territory": { - "required": "Select the state, territory, or military post where your organization is located." - }, } widgets = { "organization_name": forms.TextInput, diff --git a/src/registrar/forms/portfolio.py b/src/registrar/forms/portfolio.py index 4e2e7bdf1..13d956fe4 100644 --- a/src/registrar/forms/portfolio.py +++ b/src/registrar/forms/portfolio.py @@ -37,7 +37,10 @@ class PortfolioOrgAddressForm(forms.ModelForm): state_territory = forms.ChoiceField( label="State, territory, or military post", required=True, - choices=DomainInformation.StateTerritoryChoices.choices, + choices=[("", "--Select--")] + DomainInformation.StateTerritoryChoices.choices, + error_messages={ + "required": ("Select the state, territory, or military post where your organization is located.") + }, widget=ComboboxWidget, ) @@ -54,9 +57,6 @@ class PortfolioOrgAddressForm(forms.ModelForm): error_messages = { "address_line1": {"required": "Enter the street address of your organization."}, "city": {"required": "Enter the city where your organization is located."}, - "state_territory": { - "required": "Select the state, territory, or military post where your organization is located." - }, "zipcode": {"required": "Enter a 5-digit or 9-digit zip code, like 12345 or 12345-6789."}, } widgets = { From f858571fbfc7837c259e95a02d978085af78479b Mon Sep 17 00:00:00 2001 From: CocoByte Date: Tue, 14 Jan 2025 17:16:37 -0700 Subject: [PATCH 043/252] 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 044/252] 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 045/252] 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 1ca52b49bbe719f4c4b1cf101df1f20ce038f06e Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Tue, 14 Jan 2025 19:23:29 -0500 Subject: [PATCH 046/252] additional edits to uswds and to combobox.js to accomodate for empty values in options --- src/registrar/assets/js/uswds-edited.js | 53 +++++++++++++------ .../assets/src/js/getgov/combobox.js | 2 +- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/src/registrar/assets/js/uswds-edited.js b/src/registrar/assets/js/uswds-edited.js index b597f2d2b..590033a87 100644 --- a/src/registrar/assets/js/uswds-edited.js +++ b/src/registrar/assets/js/uswds-edited.js @@ -1037,7 +1037,7 @@ const noop = () => {}; * @param {string} value The new value of the element */ const changeElementValue = function (el) { - let value = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ""; + let value = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : ""; const elementToChange = el; elementToChange.value = value; const event = new CustomEvent("change", { @@ -1167,13 +1167,21 @@ const enhanceComboBox = _comboBoxEl => { placeholder }); } - if (defaultValue) { - for (let i = 0, len = selectEl.options.length; i < len; i += 1) { - const optionEl = selectEl.options[i]; - if (optionEl.value === defaultValue) { - selectedOption = optionEl; - break; - } + // DOTGOV - allowing for defaultValue to be empty + //if (defaultValue) { + // for (let i = 0, len = selectEl.options.length; i < len; i += 1) { + // const optionEl = selectEl.options[i]; + // if (optionEl.value === defaultValue) { + // selectedOption = optionEl; + // break; + // } + // } + //} + for (let i = 0, len = selectEl.options.length; i < len; i += 1) { + const optionEl = selectEl.options[i]; + if ((optionEl.value === defaultValue) || (!optionEl.value && !defaultValue)) { + selectedOption = optionEl; + break; } } @@ -1500,16 +1508,27 @@ const resetSelection = el => { } = getComboBoxContext(el); const selectValue = selectEl.value; const inputValue = (inputEl.value || "").toLowerCase(); - if (selectValue) { - for (let i = 0, len = selectEl.options.length; i < len; i += 1) { - const optionEl = selectEl.options[i]; - if (optionEl.value === selectValue) { - if (inputValue !== optionEl.text) { - changeElementValue(inputEl, optionEl.text); - } - comboBoxEl.classList.add(COMBO_BOX_PRISTINE_CLASS); - return; + // DOTGOV - allow for option value to be empty string + //if (selectValue) { + // for (let i = 0, len = selectEl.options.length; i < len; i += 1) { + // const optionEl = selectEl.options[i]; + // if (optionEl.value === selectValue) { + // if (inputValue !== optionEl.text) { + // changeElementValue(inputEl, optionEl.text); + // } + // comboBoxEl.classList.add(COMBO_BOX_PRISTINE_CLASS); + // return; + // } + // } + //} + for (let i = 0, len = selectEl.options.length; i < len; i += 1) { + const optionEl = selectEl.options[i]; + if ((!selectValue && !optionEl.value) || optionEl.value === selectValue) { + if (inputValue !== optionEl.text) { + changeElementValue(inputEl, optionEl.text); } + comboBoxEl.classList.add(COMBO_BOX_PRISTINE_CLASS); + return; } } if (inputValue) { diff --git a/src/registrar/assets/src/js/getgov/combobox.js b/src/registrar/assets/src/js/getgov/combobox.js index 95c5ff7e8..e0ecc92ad 100644 --- a/src/registrar/assets/src/js/getgov/combobox.js +++ b/src/registrar/assets/src/js/getgov/combobox.js @@ -48,7 +48,7 @@ export function loadInitialValuesForComboBoxes() { // Change the default input behaviour - have it reset to the data default instead clearInputButton.addEventListener("click", (e) => { - if (overrideDefaultClearButton && initialValue) { + if (overrideDefaultClearButton) { e.preventDefault(); e.stopPropagation(); input.click(); From 8700f35d35c61eb2129c17ab01d9550680464361 Mon Sep 17 00:00:00 2001 From: CocoByte Date: Tue, 14 Jan 2025 17:27:32 -0700 Subject: [PATCH 047/252] 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 24e0243df3004f91ca2f99722ff90f1385ee4180 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Tue, 14 Jan 2025 20:03:58 -0500 Subject: [PATCH 048/252] fixed bug with portfolio requesting entity --- src/registrar/assets/src/js/getgov/requesting-entity.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/registrar/assets/src/js/getgov/requesting-entity.js b/src/registrar/assets/src/js/getgov/requesting-entity.js index 3bcdcd35c..e784419b4 100644 --- a/src/registrar/assets/src/js/getgov/requesting-entity.js +++ b/src/registrar/assets/src/js/getgov/requesting-entity.js @@ -9,15 +9,16 @@ export function handleRequestingEntityFieldset() { const formPrefix = "portfolio_requesting_entity"; const radioFieldset = document.getElementById(`id_${formPrefix}-requesting_entity_is_suborganization__fieldset`); const radios = radioFieldset?.querySelectorAll(`input[name="${formPrefix}-requesting_entity_is_suborganization"]`); - const select = document.getElementById(`id_${formPrefix}-sub_organization`); - const selectParent = select?.parentElement; + const input = document.getElementById(`id_${formPrefix}-sub_organization`); + const inputGrandParent = input?.parentElement?.parentElement; + const select = input?.previousElementSibling; const suborgContainer = document.getElementById("suborganization-container"); const suborgDetailsContainer = document.getElementById("suborganization-container__details"); const suborgAddtlInstruction = document.getElementById("suborganization-addtl-instruction"); const subOrgCreateNewOption = document.getElementById("option-to-add-suborg")?.value; // Make sure all crucial page elements exist before proceeding. // This more or less ensures that we are on the Requesting Entity page, and not elsewhere. - if (!radios || !select || !selectParent || !suborgContainer || !suborgDetailsContainer) return; + if (!radios || !input || !select || !inputGrandParent || !suborgContainer || !suborgDetailsContainer) return; // requestingSuborganization: This just broadly determines if they're requesting a suborg at all // requestingNewSuborganization: This variable determines if the user is trying to *create* a new suborganization or not. @@ -28,7 +29,7 @@ export function handleRequestingEntityFieldset() { if (radio != null) requestingSuborganization = radio?.checked && radio.value === "True"; requestingSuborganization ? showElement(suborgContainer) : hideElement(suborgContainer); if (select.options.length == 2) { // --Select-- and other are the only options - hideElement(selectParent); // Hide the select drop down and indicate requesting new suborg + hideElement(inputGrandParent); // Hide the combo box and indicate requesting new suborg hideElement(suborgAddtlInstruction); // Hide additional instruction related to the list requestingNewSuborganization.value = "True"; } else { From fabeddaa619a23192c517f831fd80a72c84e066f Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Tue, 14 Jan 2025 20:56:45 -0500 Subject: [PATCH 049/252] modified combobox to handle error class --- src/registrar/assets/js/uswds-edited.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/registrar/assets/js/uswds-edited.js b/src/registrar/assets/js/uswds-edited.js index 590033a87..60502050f 100644 --- a/src/registrar/assets/js/uswds-edited.js +++ b/src/registrar/assets/js/uswds-edited.js @@ -31,6 +31,7 @@ * - fixed bug in createHeaderButton which added newlines to header button tooltips * - modified combobox to allow for blank values in list * - modified aria label for X button in combobox to reflect modified behavior of button + * - modified combobox to handle error class */ if ("document" in window.self) { @@ -1223,6 +1224,11 @@ const enhanceComboBox = _comboBoxEl => { input.setAttribute("class", INPUT_CLASS); input.setAttribute("type", "text"); input.setAttribute("role", "combobox"); + // DOTGOV - handle error class for combobox + // Check if 'usa-input--error' exists in selectEl and add it to input if true + if (selectEl.classList.contains('usa-input--error')) { + input.classList.add('usa-input--error'); + } additionalAttributes.forEach(attr => Object.keys(attr).forEach(key => { const value = Sanitizer.escapeHTML`${attr[key]}`; input.setAttribute(key, value); 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 050/252] 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 051/252] 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 052/252] 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 74ef30b52d222171daa01c1681bdc42c797ace92 Mon Sep 17 00:00:00 2001 From: David Kennedy Date: Wed, 15 Jan 2025 12:44:47 -0500 Subject: [PATCH 053/252] fixed the 'other' problem in requesting entity form --- .../assets/src/js/getgov/requesting-entity.js | 5 ----- src/registrar/forms/domain_request_wizard.py | 14 ++++++++++---- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/registrar/assets/src/js/getgov/requesting-entity.js b/src/registrar/assets/src/js/getgov/requesting-entity.js index e784419b4..5f3be8c79 100644 --- a/src/registrar/assets/src/js/getgov/requesting-entity.js +++ b/src/registrar/assets/src/js/getgov/requesting-entity.js @@ -38,11 +38,6 @@ export function handleRequestingEntityFieldset() { requestingNewSuborganization.value === "True" ? showElement(suborgDetailsContainer) : hideElement(suborgDetailsContainer); } - // Add fake "other" option to sub_organization select - if (select && !Array.from(select.options).some(option => option.value === "other")) { - select.add(new Option(subOrgCreateNewOption, "other")); - } - if (requestingNewSuborganization.value === "True") { select.value = "other"; } diff --git a/src/registrar/forms/domain_request_wizard.py b/src/registrar/forms/domain_request_wizard.py index 2365d323d..636a41760 100644 --- a/src/registrar/forms/domain_request_wizard.py +++ b/src/registrar/forms/domain_request_wizard.py @@ -63,13 +63,19 @@ class RequestingEntityForm(RegistrarForm): ) def __init__(self, *args, **kwargs): - """Override of init to add the suborganization queryset""" + """Override of init to add the suborganization queryset and 'other' option""" super().__init__(*args, **kwargs) if self.domain_request.portfolio: - self.fields["sub_organization"].queryset = Suborganization.objects.filter( - portfolio=self.domain_request.portfolio - ) + # Fetch the queryset for the portfolio + queryset = Suborganization.objects.filter(portfolio=self.domain_request.portfolio) + # set the queryset appropriately so that post can validate against queryset + self.fields["sub_organization"].queryset = queryset + + # Modify the choices to include "other" so that form can display options properly + self.fields["sub_organization"].choices = [("", "--Select--")] + [ + (obj.id, str(obj)) for obj in queryset + ] + [("other", "Other (enter your suborganization manually)")] def clean_sub_organization(self): """On suborganization clean, set the suborganization value to None if the user is requesting 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 054/252] 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 055/252] 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 44681307a6d41fa4e2587836f6578434e29dca9e Mon Sep 17 00:00:00 2001 From: CocoByte Date: Wed, 15 Jan 2025 15:37:16 -0700 Subject: [PATCH 056/252] Resolves #2871 --- src/registrar/forms/domain_request_wizard.py | 3 +++ src/registrar/templates/domain_request_current_sites.html | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/registrar/forms/domain_request_wizard.py b/src/registrar/forms/domain_request_wizard.py index ccdbb17ba..9cc573319 100644 --- a/src/registrar/forms/domain_request_wizard.py +++ b/src/registrar/forms/domain_request_wizard.py @@ -413,6 +413,9 @@ class CurrentSitesForm(RegistrarForm): error_messages={ "invalid": ("Enter your organization's current website in the required format, like example.com.") }, + widget=forms.URLInput(attrs={ + 'aria-labelledby': 'id_current_sites_header id_current_sites_body' + }) ) diff --git a/src/registrar/templates/domain_request_current_sites.html b/src/registrar/templates/domain_request_current_sites.html index 2a2ac6885..99dbdd8aa 100644 --- a/src/registrar/templates/domain_request_current_sites.html +++ b/src/registrar/templates/domain_request_current_sites.html @@ -3,8 +3,8 @@ {% block form_instructions %}

            We can better evaluate your request if we know about domains you’re already using.

            -

            What are the current websites for your organization?

            -

            Enter your organization’s current public websites. If you already have a .gov domain, include that in your list. This question is optional.

            +

            What are the current websites for your organization?

            +

            Enter your organization’s current public websites. If you already have a .gov domain, include that in your list. This question is optional.

            {% endblock %} {% block form_required_fields_help_text %} From f4ff1ecd83dc187fd58d81e2e1fb229fe28a0465 Mon Sep 17 00:00:00 2001 From: CocoByte Date: Wed, 15 Jan 2025 16:39:21 -0700 Subject: [PATCH 057/252] Resolves #2875 --- src/registrar/templates/domain_request_dotgov_domain.html | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/registrar/templates/domain_request_dotgov_domain.html b/src/registrar/templates/domain_request_dotgov_domain.html index 38347ad96..87f55f17a 100644 --- a/src/registrar/templates/domain_request_dotgov_domain.html +++ b/src/registrar/templates/domain_request_dotgov_domain.html @@ -44,7 +44,7 @@

            After you enter your domain, we’ll make sure it’s available and that it meets some of our naming requirements. If your domain passes these initial checks, we’ll verify that it meets all our requirements after you complete the rest of this form.

            - {% with attr_aria_describedby="domain_instructions domain_instructions2" %} + {% with attr_aria_labelledby="domain_instructions domain_instructions2" attr_aria_describedby="id_dotgov_domain-requested_domain--toast" %} {# attr_validate / validate="domain" invokes code in getgov.min.js #} {% with append_gov=True attr_validate="domain" add_label_class="usa-sr-only" %} {% input_with_errors forms.0.requested_domain %} @@ -67,12 +67,14 @@

            Are there other domains you’d like if we can’t give you your first choice?

            - {% with attr_aria_describedby="alt_domain_instructions" %} + {% with attr_aria_labelledby="alt_domain_instructions" %} {# Will probably want to remove blank-ok and do related cleanup when we implement delete #} {% with attr_validate="domain" append_gov=True add_label_class="usa-sr-only" add_class="blank-ok alternate-domain-input" %} {% for form in forms.1 %}
            - {% input_with_errors form.alternative_domain %} + {% with attr_aria_describedby=form.alternative_domain.auto_id|stringformat:"s"|add:"--toast" %} + {% input_with_errors form.alternative_domain %} + {% endwith %}
            {% endfor %} {% endwith %} From 40604a1b2935a98fbd5bfbb7c245217d928d82e9 Mon Sep 17 00:00:00 2001 From: CocoByte Date: Wed, 15 Jan 2025 16:52:44 -0700 Subject: [PATCH 058/252] Resolves #2938 - limited domain name length --- src/registrar/models/utility/domain_helper.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/registrar/models/utility/domain_helper.py b/src/registrar/models/utility/domain_helper.py index 87a885309..0459831e9 100644 --- a/src/registrar/models/utility/domain_helper.py +++ b/src/registrar/models/utility/domain_helper.py @@ -15,9 +15,11 @@ class DomainHelper: # a domain name is alphanumeric or hyphen, up to 63 characters, doesn't # begin or end with a hyphen, followed by a TLD of 2-6 alphabetic characters - DOMAIN_REGEX = re.compile(r"^(?!-)[A-Za-z0-9-]{1,63}(? Date: Thu, 16 Jan 2025 09:22:30 -0800 Subject: [PATCH 059/252] 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 060/252] 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 061/252] 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 062/252] 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 063/252] 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 064/252] 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 @@