+ {% if form.city.value is not None %}
+ {% include "includes/input_read_only.html" with field=form.title %}
+ {% endif %}
+ {% if form.state_territory.value is not None %}
+ {% include "includes/input_read_only.html" with field=form.email %}
+ {% endif %}
+ {% endif %}
+
+
+
+{% endblock %}
diff --git a/src/registrar/views/portfolios.py b/src/registrar/views/portfolios.py
index 63ebbaa01..0903b2e58 100644
--- a/src/registrar/views/portfolios.py
+++ b/src/registrar/views/portfolios.py
@@ -3,7 +3,7 @@ from django.http import Http404
from django.shortcuts import render
from django.urls import reverse
from django.contrib import messages
-from registrar.forms.portfolio import PortfolioOrgAddressForm
+from registrar.forms.portfolio import PortfolioOrgAddressForm, PortfolioSeniorOfficialForm
from registrar.models.portfolio import Portfolio
from registrar.views.utility.permission_views import (
PortfolioDomainRequestsPermissionView,
@@ -94,3 +94,65 @@ class PortfolioOrganizationView(PortfolioBasePermissionView, FormMixin):
def get_success_url(self):
"""Redirect to the overview page for the portfolio."""
return reverse("organization")
+
+
+class PortfolioSeniorOfficialView(PortfolioBasePermissionView, FormMixin):
+ """
+ View to handle displaying and updating the portfolio's senior official details.
+ """
+
+ model = Portfolio
+ template_name = "portfolio_senior_official.html"
+ form_class = PortfolioSeniorOfficialForm
+ context_object_name = "portfolio"
+
+ def get_context_data(self, **kwargs):
+ """Add additional context data to the template."""
+ context = super().get_context_data(**kwargs)
+ context["has_edit_org_portfolio_permission"] = self.request.user.has_edit_org_portfolio_permission()
+ return context
+
+ def get_object(self, queryset=None):
+ """Get the portfolio object based on the request user."""
+ portfolio = self.request.user.portfolio
+ if portfolio is None:
+ raise Http404("No organization found for this user")
+ return portfolio
+
+ def get_form_kwargs(self):
+ """Include the instance in the form kwargs."""
+ kwargs = super().get_form_kwargs()
+ kwargs["instance"] = self.get_object().senior_official
+ return kwargs
+
+ def get(self, request, *args, **kwargs):
+ """Handle GET requests to display the form."""
+ self.object = self.get_object()
+ form = self.get_form()
+ return self.render_to_response(self.get_context_data(form=form))
+
+ def post(self, request, *args, **kwargs):
+ """Handle POST requests to process form submission."""
+ self.object = self.get_object()
+ form = self.get_form()
+ if form.is_valid():
+ return self.form_valid(form)
+ else:
+ return self.form_invalid(form)
+
+ def form_valid(self, form):
+ """Handle the case when the form is valid."""
+ senior_official = form.save()
+ portfolio = self.get_object()
+ portfolio.senior_official = senior_official
+ portfolio.save()
+ messages.success(self.request, "The Senior Official for this portfolio has been updated.")
+ return super().form_valid(form)
+
+ def form_invalid(self, form):
+ """Handle the case when the form is invalid."""
+ return self.render_to_response(self.get_context_data(form=form))
+
+ def get_success_url(self):
+ """Redirect to the overview page for the portfolio."""
+ return reverse("senior-official")
\ No newline at end of file
From a9a93df5cdc2d2138f5883d1baff15ab71f0164d Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Fri, 9 Aug 2024 10:00:44 -0600
Subject: [PATCH 02/37] Readonly logic
---
src/registrar/forms/portfolio.py | 20 +++++++-------
.../templates/portfolio_senior_official.html | 27 ++++++++++---------
src/registrar/views/portfolios.py | 8 +++---
3 files changed, 29 insertions(+), 26 deletions(-)
diff --git a/src/registrar/forms/portfolio.py b/src/registrar/forms/portfolio.py
index 88ec8e3f7..936953686 100644
--- a/src/registrar/forms/portfolio.py
+++ b/src/registrar/forms/portfolio.py
@@ -70,27 +70,22 @@ class PortfolioOrgAddressForm(forms.ModelForm):
class PortfolioSeniorOfficialForm(forms.ModelForm):
- """Form for updating the portfolio senior official."""
+ """
+ Form for updating the portfolio senior official.
+ This form is readonly for now.
+ """
JOIN = "senior_official"
+ full_name = forms.CharField(label="Full name", required=False)
class Meta:
model = SeniorOfficial
- # TODO - add full name
fields = [
"first_name",
"last_name",
"title",
"email",
]
-
- # 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."
- # },
- # }
widgets = {
# We need to set the required attributed for State/territory
# because for this fields we are creating an individual
@@ -100,14 +95,17 @@ class PortfolioSeniorOfficialForm(forms.ModelForm):
"last_name": forms.TextInput,
"title": forms.TextInput,
"email": forms.TextInput,
+ "full_name": forms.TextInput(attrs={"readonly": "readonly"})
}
# the database fields have blank=True so ModelForm doesn't create
# required fields by default. Use this list in __init__ to mark each
# of these fields as required
required = ["first_name", "last_name", "title", "email"]
-
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field_name in self.required:
self.fields[field_name].required = True
+
+ if self.instance:
+ self.fields["full_name"].initial = self.instance.get_formatted_name()
diff --git a/src/registrar/templates/portfolio_senior_official.html b/src/registrar/templates/portfolio_senior_official.html
index e22237ac4..9566736a0 100644
--- a/src/registrar/templates/portfolio_senior_official.html
+++ b/src/registrar/templates/portfolio_senior_official.html
@@ -23,9 +23,7 @@
Your senior official is a person within your organization who can authorize domain requests.
-
The senior official for your organization can’t be updated here. To suggest an update, email help@get.gov
-
- {% if has_edit_org_portfolio_permission %}
+ {% if santa_exists %}
{% include "includes/form_errors.html" with form=form %}
{% include "includes/required_fields.html" %}
{% else %}
-
- {% if form.city.value is not None %}
- {% include "includes/input_read_only.html" with field=form.title %}
- {% endif %}
- {% if form.state_territory.value is not None %}
- {% include "includes/input_read_only.html" with field=form.email %}
+
The senior official for your organization can’t be updated here. To suggest an update, email help@get.gov
+
+ {% if not senior_official %}
+
No senior official was found for this portfolio.
+ {% else %}
+ {% if form.full_name.value is not None %}
+ {% include "includes/input_read_only.html" with field=form.full_name %}
+ {% endif %}
+ {% if form.title.value is not None %}
+ {% include "includes/input_read_only.html" with field=form.title %}
+ {% endif %}
+ {% if form.email.value is not None %}
+ {% include "includes/input_read_only.html" with field=form.email %}
+ {% endif %}
{% endif %}
{% endif %}
diff --git a/src/registrar/views/portfolios.py b/src/registrar/views/portfolios.py
index 0903b2e58..3c851841a 100644
--- a/src/registrar/views/portfolios.py
+++ b/src/registrar/views/portfolios.py
@@ -48,7 +48,7 @@ class PortfolioOrganizationView(PortfolioBasePermissionView, FormMixin):
def get_context_data(self, **kwargs):
"""Add additional context data to the template."""
context = super().get_context_data(**kwargs)
- context["has_edit_org_portfolio_permission"] = self.request.user.has_edit_org_portfolio_permission()
+ # context["has_edit_org_portfolio_permission"]
return context
def get_object(self, queryset=None):
@@ -109,7 +109,7 @@ class PortfolioSeniorOfficialView(PortfolioBasePermissionView, FormMixin):
def get_context_data(self, **kwargs):
"""Add additional context data to the template."""
context = super().get_context_data(**kwargs)
- context["has_edit_org_portfolio_permission"] = self.request.user.has_edit_org_portfolio_permission()
+ context["senior_official"] = self.get_object().senior_official
return context
def get_object(self, queryset=None):
@@ -131,6 +131,8 @@ class PortfolioSeniorOfficialView(PortfolioBasePermissionView, FormMixin):
form = self.get_form()
return self.render_to_response(self.get_context_data(form=form))
+ # These functions are included for future compatibility, but for now
+ # we do not offer an edit mode for senior officials.
def post(self, request, *args, **kwargs):
"""Handle POST requests to process form submission."""
self.object = self.get_object()
@@ -155,4 +157,4 @@ class PortfolioSeniorOfficialView(PortfolioBasePermissionView, FormMixin):
def get_success_url(self):
"""Redirect to the overview page for the portfolio."""
- return reverse("senior-official")
\ No newline at end of file
+ return reverse("senior-official")
From 3837359977df8425b16742bacde154f2a52a0e14 Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Fri, 9 Aug 2024 10:09:17 -0600
Subject: [PATCH 03/37] Fix bug with load senior official table
---
src/registrar/forms/portfolio.py | 2 +-
.../commands/load_senior_official_table.py | 15 ++++++---------
2 files changed, 7 insertions(+), 10 deletions(-)
diff --git a/src/registrar/forms/portfolio.py b/src/registrar/forms/portfolio.py
index 936953686..d807b4184 100644
--- a/src/registrar/forms/portfolio.py
+++ b/src/registrar/forms/portfolio.py
@@ -106,6 +106,6 @@ class PortfolioSeniorOfficialForm(forms.ModelForm):
super().__init__(*args, **kwargs)
for field_name in self.required:
self.fields[field_name].required = True
-
+
if self.instance:
self.fields["full_name"].initial = self.instance.get_formatted_name()
diff --git a/src/registrar/management/commands/load_senior_official_table.py b/src/registrar/management/commands/load_senior_official_table.py
index cbfe479ea..43f61d57a 100644
--- a/src/registrar/management/commands/load_senior_official_table.py
+++ b/src/registrar/management/commands/load_senior_official_table.py
@@ -35,7 +35,6 @@ class Command(BaseCommand):
Note:
- If the row is missing SO data - it will not be added.
- - Given we can add the row, any blank first_name will be replaced with "-".
""", # noqa: W291
prompt_title="Do you wish to load records into the SeniorOfficial table?",
)
@@ -64,7 +63,11 @@ class Command(BaseCommand):
# Clean the returned data
for key, value in so_kwargs.items():
if isinstance(value, str):
- so_kwargs[key] = value.strip()
+ clean_string = value.strip()
+ if clean_string:
+ so_kwargs[key] = clean_string
+ else:
+ so_kwargs[key] = None
# Handle the federal_agency record seperately (db call)
agency_name = row.get("Agency").strip() if row.get("Agency") else None
@@ -95,17 +98,11 @@ class Command(BaseCommand):
def create_senior_official(self, so_kwargs):
"""Creates a senior official object from kwargs but does not add it to the DB"""
- # WORKAROUND: Placeholder value for first name,
- # as not having these makes it impossible to access through DJA.
- old_first_name = so_kwargs["first_name"]
- if not so_kwargs["first_name"]:
- so_kwargs["first_name"] = "-"
-
# Create a new SeniorOfficial object
new_so = SeniorOfficial(**so_kwargs)
# Store a variable for the console logger
- if all([old_first_name, new_so.last_name]):
+ if all([new_so.first_name, new_so.last_name]):
record_display = new_so
else:
record_display = so_kwargs
From 0218da50d8a449ad624753f10c17fe04ac818e7b Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Fri, 9 Aug 2024 10:22:41 -0600
Subject: [PATCH 04/37] Cleanup unused content
---
src/registrar/forms/portfolio.py | 6 ---
.../templates/portfolio_senior_official.html | 39 ++++++-------------
src/registrar/views/portfolios.py | 29 +-------------
3 files changed, 13 insertions(+), 61 deletions(-)
diff --git a/src/registrar/forms/portfolio.py b/src/registrar/forms/portfolio.py
index d807b4184..92b68c86a 100644
--- a/src/registrar/forms/portfolio.py
+++ b/src/registrar/forms/portfolio.py
@@ -98,14 +98,8 @@ class PortfolioSeniorOfficialForm(forms.ModelForm):
"full_name": forms.TextInput(attrs={"readonly": "readonly"})
}
- # the database fields have blank=True so ModelForm doesn't create
- # required fields by default. Use this list in __init__ to mark each
- # of these fields as required
- required = ["first_name", "last_name", "title", "email"]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
- for field_name in self.required:
- self.fields[field_name].required = True
if self.instance:
self.fields["full_name"].initial = self.instance.get_formatted_name()
diff --git a/src/registrar/templates/portfolio_senior_official.html b/src/registrar/templates/portfolio_senior_official.html
index 9566736a0..12ddab908 100644
--- a/src/registrar/templates/portfolio_senior_official.html
+++ b/src/registrar/templates/portfolio_senior_official.html
@@ -23,34 +23,19 @@
Your senior official is a person within your organization who can authorize domain requests.
- {% if santa_exists %}
- {% include "includes/form_errors.html" with form=form %}
- {% include "includes/required_fields.html" %}
-
+
The senior official for your organization can’t be updated here. To suggest an update, email help@get.gov
+
+ {% if not senior_official %}
+
No senior official was found for this portfolio.
{% else %}
-
The senior official for your organization can’t be updated here. To suggest an update, email help@get.gov
-
- {% if not senior_official %}
-
No senior official was found for this portfolio.
- {% else %}
- {% if form.full_name.value is not None %}
- {% include "includes/input_read_only.html" with field=form.full_name %}
- {% endif %}
- {% if form.title.value is not None %}
- {% include "includes/input_read_only.html" with field=form.title %}
- {% endif %}
- {% if form.email.value is not None %}
- {% include "includes/input_read_only.html" with field=form.email %}
- {% endif %}
+ {% if form.full_name.value is not None %}
+ {% include "includes/input_read_only.html" with field=form.full_name %}
+ {% endif %}
+ {% if form.title.value is not None %}
+ {% include "includes/input_read_only.html" with field=form.title %}
+ {% endif %}
+ {% if form.email.value is not None %}
+ {% include "includes/input_read_only.html" with field=form.email %}
{% endif %}
{% endif %}
diff --git a/src/registrar/views/portfolios.py b/src/registrar/views/portfolios.py
index 3c851841a..8879f020c 100644
--- a/src/registrar/views/portfolios.py
+++ b/src/registrar/views/portfolios.py
@@ -99,6 +99,7 @@ class PortfolioOrganizationView(PortfolioBasePermissionView, FormMixin):
class PortfolioSeniorOfficialView(PortfolioBasePermissionView, FormMixin):
"""
View to handle displaying and updating the portfolio's senior official details.
+ For now, this view is readonly.
"""
model = Portfolio
@@ -130,31 +131,3 @@ class PortfolioSeniorOfficialView(PortfolioBasePermissionView, FormMixin):
self.object = self.get_object()
form = self.get_form()
return self.render_to_response(self.get_context_data(form=form))
-
- # These functions are included for future compatibility, but for now
- # we do not offer an edit mode for senior officials.
- def post(self, request, *args, **kwargs):
- """Handle POST requests to process form submission."""
- self.object = self.get_object()
- form = self.get_form()
- if form.is_valid():
- return self.form_valid(form)
- else:
- return self.form_invalid(form)
-
- def form_valid(self, form):
- """Handle the case when the form is valid."""
- senior_official = form.save()
- portfolio = self.get_object()
- portfolio.senior_official = senior_official
- portfolio.save()
- messages.success(self.request, "The Senior Official for this portfolio has been updated.")
- return super().form_valid(form)
-
- def form_invalid(self, form):
- """Handle the case when the form is invalid."""
- return self.render_to_response(self.get_context_data(form=form))
-
- def get_success_url(self):
- """Redirect to the overview page for the portfolio."""
- return reverse("senior-official")
From 956e27cc77894f097fd673a942032b917e2f7b34 Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Fri, 9 Aug 2024 10:24:45 -0600
Subject: [PATCH 05/37] Update portfolio.py
---
src/registrar/forms/portfolio.py | 17 +----------------
1 file changed, 1 insertion(+), 16 deletions(-)
diff --git a/src/registrar/forms/portfolio.py b/src/registrar/forms/portfolio.py
index 92b68c86a..ab9459588 100644
--- a/src/registrar/forms/portfolio.py
+++ b/src/registrar/forms/portfolio.py
@@ -76,30 +76,15 @@ class PortfolioSeniorOfficialForm(forms.ModelForm):
"""
JOIN = "senior_official"
- full_name = forms.CharField(label="Full name", required=False)
-
+ full_name = forms.CharField(label="Full name")
class Meta:
model = SeniorOfficial
fields = [
- "first_name",
- "last_name",
"title",
"email",
]
- 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.
- "first_name": forms.TextInput,
- "last_name": forms.TextInput,
- "title": forms.TextInput,
- "email": forms.TextInput,
- "full_name": forms.TextInput(attrs={"readonly": "readonly"})
- }
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
-
if self.instance:
self.fields["full_name"].initial = self.instance.get_formatted_name()
From e017315638e790c1dbf725d8af8c98a3b13025a4 Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Fri, 9 Aug 2024 13:11:48 -0600
Subject: [PATCH 06/37] Modify existing
---
.../templates/domain_senior_official.html | 24 ++++++-------------
.../includes/readonly_senior_official.html | 11 +++++++++
.../templates/portfolio_senior_official.html | 10 +-------
src/registrar/views/portfolios.py | 2 +-
4 files changed, 20 insertions(+), 27 deletions(-)
create mode 100644 src/registrar/templates/includes/readonly_senior_official.html
diff --git a/src/registrar/templates/domain_senior_official.html b/src/registrar/templates/domain_senior_official.html
index 5d13e28e7..c911af1b9 100644
--- a/src/registrar/templates/domain_senior_official.html
+++ b/src/registrar/templates/domain_senior_official.html
@@ -26,23 +26,13 @@
{% csrf_token %}
{% if generic_org_type == "federal" or generic_org_type == "tribal" %}
- {# If all fields are disabled, add SR content #}
-
{{ form.first_name.value }}
-
{{ form.last_name.value }}
-
{{ form.title.value }}
-
{{ form.email.value }}
- {% endif %}
-
- {% input_with_errors form.first_name %}
-
- {% input_with_errors form.last_name %}
-
- {% input_with_errors form.title %}
-
- {% input_with_errors form.email %}
-
- {% if generic_org_type != "federal" and generic_org_type != "tribal" %}
-
+ {% include "includes/readonly_senior_official.html" with form=form senior_official=senior_officialn%}
+ {% else %}
+ {% input_with_errors form.first_name %}
+ {% input_with_errors form.last_name %}
+ {% input_with_errors form.title %}
+ {% input_with_errors form.email %}
+
{% endif %}
{% endblock %} {# domain_content #}
diff --git a/src/registrar/templates/includes/readonly_senior_official.html b/src/registrar/templates/includes/readonly_senior_official.html
new file mode 100644
index 000000000..73e7bfd77
--- /dev/null
+++ b/src/registrar/templates/includes/readonly_senior_official.html
@@ -0,0 +1,11 @@
+{% if form.full_name.value is not None %}
+ {% include "includes/input_read_only.html" with field=form.full_name %}
+{% endif %}
+
+{% if form.title.value is not None %}
+ {% include "includes/input_read_only.html" with field=form.title %}
+{% endif %}
+
+{% if form.email.value is not None %}
+ {% include "includes/input_read_only.html" with field=form.email %}
+{% endif %}
\ No newline at end of file
diff --git a/src/registrar/templates/portfolio_senior_official.html b/src/registrar/templates/portfolio_senior_official.html
index 12ddab908..976cf9639 100644
--- a/src/registrar/templates/portfolio_senior_official.html
+++ b/src/registrar/templates/portfolio_senior_official.html
@@ -28,15 +28,7 @@
{% if not senior_official %}
No senior official was found for this portfolio.
{% else %}
- {% if form.full_name.value is not None %}
- {% include "includes/input_read_only.html" with field=form.full_name %}
- {% endif %}
- {% if form.title.value is not None %}
- {% include "includes/input_read_only.html" with field=form.title %}
- {% endif %}
- {% if form.email.value is not None %}
- {% include "includes/input_read_only.html" with field=form.email %}
- {% endif %}
+ {% include "includes/readonly_senior_official.html" with form=form %}
{% endif %}
diff --git a/src/registrar/views/portfolios.py b/src/registrar/views/portfolios.py
index 8879f020c..a7c7d1356 100644
--- a/src/registrar/views/portfolios.py
+++ b/src/registrar/views/portfolios.py
@@ -48,7 +48,7 @@ class PortfolioOrganizationView(PortfolioBasePermissionView, FormMixin):
def get_context_data(self, **kwargs):
"""Add additional context data to the template."""
context = super().get_context_data(**kwargs)
- # context["has_edit_org_portfolio_permission"]
+ context["has_edit_org_portfolio_permission"] = self.request.user.has_edit_org_portfolio_permission()
return context
def get_object(self, queryset=None):
From cdcd90bb89706776399384e6430b2e5f7bc89a73 Mon Sep 17 00:00:00 2001
From: matthewswspence
Date: Fri, 9 Aug 2024 14:37:49 -0500
Subject: [PATCH 07/37] add dnssec summary item to overview page
---
src/registrar/templates/domain_detail.html | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/src/registrar/templates/domain_detail.html b/src/registrar/templates/domain_detail.html
index 0b6f47481..ad187aa4e 100644
--- a/src/registrar/templates/domain_detail.html
+++ b/src/registrar/templates/domain_detail.html
@@ -64,6 +64,13 @@
{% endif %}
{% endif %}
+ {% url 'domain-dns-dnssec' pk=domain.id as url %}
+ {% if domain.dnssecdata is not None %}
+ {% include "includes/summary_item.html" with title='DNSSEC' value='Enabled' edit_link=url editable=is_editable %}
+ {% else %}
+ {% include "includes/summary_item.html" with title='DNSSEC' value='Disabled' edit_link=url editable=is_editable %}
+ {% endif %}
+
{% if portfolio %}
{% comment %} TODO - uncomment in #2352 and add to edit_link
{% url 'domain-suborganization' pk=domain.id as url %}
From 2863e29d96892dd076ffa572ada9eae8162821d0 Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Fri, 9 Aug 2024 13:40:16 -0600
Subject: [PATCH 08/37] Fix bug with senior_official in django admin, add
readonly
---
src/registrar/admin.py | 2 --
src/registrar/forms/domain.py | 5 ++++-
src/registrar/forms/portfolio.py | 2 +-
3 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/src/registrar/admin.py b/src/registrar/admin.py
index ca4038d51..138183082 100644
--- a/src/registrar/admin.py
+++ b/src/registrar/admin.py
@@ -504,8 +504,6 @@ class AdminSortFields:
# == Contact == #
"other_contacts": (Contact, _name_sort),
"submitter": (Contact, _name_sort),
- # == Senior Official == #
- "senior_official": (SeniorOfficial, _name_sort),
# == User == #
"creator": (User, _name_sort),
"user": (User, _name_sort),
diff --git a/src/registrar/forms/domain.py b/src/registrar/forms/domain.py
index 02a0724d1..53f340aee 100644
--- a/src/registrar/forms/domain.py
+++ b/src/registrar/forms/domain.py
@@ -321,9 +321,12 @@ class SeniorOfficialContactForm(ContactForm):
"""Form for updating senior official contacts."""
JOIN = "senior_official"
-
+ full_name = forms.CharField(label="Full name", required=False)
def __init__(self, disable_fields=False, *args, **kwargs):
super().__init__(*args, **kwargs)
+
+ if self.instance:
+ self.fields["full_name"].initial = self.instance.get_formatted_name()
# Overriding bc phone not required in this form
self.fields["phone"] = forms.IntegerField(required=False)
diff --git a/src/registrar/forms/portfolio.py b/src/registrar/forms/portfolio.py
index ab9459588..3a9e65b7f 100644
--- a/src/registrar/forms/portfolio.py
+++ b/src/registrar/forms/portfolio.py
@@ -76,7 +76,7 @@ class PortfolioSeniorOfficialForm(forms.ModelForm):
"""
JOIN = "senior_official"
- full_name = forms.CharField(label="Full name")
+ full_name = forms.CharField(label="Full name", required=False)
class Meta:
model = SeniorOfficial
fields = [
From 4be1e3e29e2140ebecfd3c08885de00ad7a95d7e Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Fri, 9 Aug 2024 13:53:54 -0600
Subject: [PATCH 09/37] Linting and fix existing unit test
---
src/registrar/admin.py | 2 +-
src/registrar/forms/domain.py | 3 +-
src/registrar/forms/portfolio.py | 1 +
.../includes/readonly_senior_official.html | 2 +-
src/registrar/tests/test_views_domain.py | 45 +++----------------
5 files changed, 11 insertions(+), 42 deletions(-)
diff --git a/src/registrar/admin.py b/src/registrar/admin.py
index 138183082..ae06a8d96 100644
--- a/src/registrar/admin.py
+++ b/src/registrar/admin.py
@@ -22,7 +22,7 @@ from epplibwrapper.errors import ErrorCode, RegistryError
from registrar.models.user_domain_role import UserDomainRole
from waffle.admin import FlagAdmin
from waffle.models import Sample, Switch
-from registrar.models import Contact, Domain, DomainRequest, DraftDomain, User, Website, SeniorOfficial
+from registrar.models import Contact, Domain, DomainRequest, DraftDomain, User, Website
from registrar.utility.errors import FSMDomainRequestError, FSMErrorCodes
from registrar.views.utility.mixins import OrderableFieldsMixin
from django.contrib.admin.views.main import ORDER_VAR
diff --git a/src/registrar/forms/domain.py b/src/registrar/forms/domain.py
index 53f340aee..4a1e95431 100644
--- a/src/registrar/forms/domain.py
+++ b/src/registrar/forms/domain.py
@@ -322,9 +322,10 @@ class SeniorOfficialContactForm(ContactForm):
JOIN = "senior_official"
full_name = forms.CharField(label="Full name", required=False)
+
def __init__(self, disable_fields=False, *args, **kwargs):
super().__init__(*args, **kwargs)
-
+
if self.instance:
self.fields["full_name"].initial = self.instance.get_formatted_name()
diff --git a/src/registrar/forms/portfolio.py b/src/registrar/forms/portfolio.py
index 3a9e65b7f..44d195dd6 100644
--- a/src/registrar/forms/portfolio.py
+++ b/src/registrar/forms/portfolio.py
@@ -77,6 +77,7 @@ class PortfolioSeniorOfficialForm(forms.ModelForm):
JOIN = "senior_official"
full_name = forms.CharField(label="Full name", required=False)
+
class Meta:
model = SeniorOfficial
fields = [
diff --git a/src/registrar/templates/includes/readonly_senior_official.html b/src/registrar/templates/includes/readonly_senior_official.html
index 73e7bfd77..7a7908726 100644
--- a/src/registrar/templates/includes/readonly_senior_official.html
+++ b/src/registrar/templates/includes/readonly_senior_official.html
@@ -8,4 +8,4 @@
{% if form.email.value is not None %}
{% include "includes/input_read_only.html" with field=form.email %}
-{% endif %}
\ No newline at end of file
+{% endif %}
diff --git a/src/registrar/tests/test_views_domain.py b/src/registrar/tests/test_views_domain.py
index 31ca776e2..efbc1296c 100644
--- a/src/registrar/tests/test_views_domain.py
+++ b/src/registrar/tests/test_views_domain.py
@@ -1192,14 +1192,14 @@ class TestDomainSeniorOfficial(TestDomainOverview):
self.assertTrue("disabled" in form[field_name].attrs)
@less_console_noise_decorator
- def test_domain_edit_senior_official_federal(self):
+ def test_domain_cannot_edit_senior_official_when_federal(self):
"""Tests that no edit can occur when the underlying domain is federal"""
# Set the org type to federal
self.domain_information.generic_org_type = DomainInformation.OrganizationChoices.FEDERAL
self.domain_information.save()
- # Add an SO. We can do this at the model level, just not the form level.
+ # Add an SO
self.domain_information.senior_official = Contact(
first_name="Apple", last_name="Tester", title="CIO", email="nobody@igorville.gov"
)
@@ -1210,43 +1210,10 @@ class TestDomainSeniorOfficial(TestDomainOverview):
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
- # Test if the form is populating data correctly
- so_form = so_page.forms[0]
-
- test_cases = [
- ("first_name", "Apple"),
- ("last_name", "Tester"),
- ("title", "CIO"),
- ("email", "nobody@igorville.gov"),
- ]
- self.assert_all_form_fields_have_expected_values(so_form, test_cases, test_for_disabled=True)
-
- # Attempt to change data on each field. Because this domain is federal,
- # this should not succeed.
- so_form["first_name"] = "Orange"
- so_form["last_name"] = "Smoothie"
- so_form["title"] = "Cat"
- so_form["email"] = "somebody@igorville.gov"
-
- submission = so_form.submit()
-
- # A 302 indicates this page underwent a redirect.
- self.assertEqual(submission.status_code, 302)
-
- followed_submission = submission.follow()
-
- # Test the returned form for data accuracy. These values should be unchanged.
- new_form = followed_submission.forms[0]
- self.assert_all_form_fields_have_expected_values(new_form, test_cases, test_for_disabled=True)
-
- # refresh domain information. Test these values in the DB.
- self.domain_information.refresh_from_db()
-
- # All values should be unchanged. These are defined manually for code clarity.
- self.assertEqual("Apple", self.domain_information.senior_official.first_name)
- self.assertEqual("Tester", self.domain_information.senior_official.last_name)
- self.assertEqual("CIO", self.domain_information.senior_official.title)
- self.assertEqual("nobody@igorville.gov", self.domain_information.senior_official.email)
+ self.assertContains(so_page, "Apple Tester")
+ self.assertContains(so_page, "CIO")
+ self.assertContains(so_page, "nobody@igorville.gov")
+ self.assertNotContains(so_page, "Save")
@less_console_noise_decorator
def test_domain_edit_senior_official_tribal(self):
From 3005158b82fc563f3e94dc7e94e1351772971241 Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Fri, 9 Aug 2024 14:05:11 -0600
Subject: [PATCH 10/37] Add unit test
---
src/registrar/tests/test_views_portfolio.py | 31 ++++++++++++++++++++-
1 file changed, 30 insertions(+), 1 deletion(-)
diff --git a/src/registrar/tests/test_views_portfolio.py b/src/registrar/tests/test_views_portfolio.py
index f72130d94..d725c95ad 100644
--- a/src/registrar/tests/test_views_portfolio.py
+++ b/src/registrar/tests/test_views_portfolio.py
@@ -1,7 +1,7 @@
from django.urls import reverse
from api.tests.common import less_console_noise_decorator
from registrar.config import settings
-from registrar.models.portfolio import Portfolio
+from registrar.models import Portfolio, SeniorOfficial
from django_webtest import WebTest # type: ignore
from registrar.models import (
DomainRequest,
@@ -38,6 +38,35 @@ class TestPortfolio(WebTest):
User.objects.all().delete()
super().tearDown()
+ @less_console_noise_decorator
+ @override_flag("organization_feature", active=True)
+ def test_portfolio_senior_official(self):
+ """Tests the senior official page on portfolio"""
+ self.app.set_user(self.user.username)
+
+ so = SeniorOfficial.objects.create(
+ first_name="Saturn", last_name="Enceladus", title="Planet/Moon", email="spacedivision@igorville.com"
+ )
+
+ self.portfolio.senior_official = so
+ self.portfolio.save()
+ self.portfolio.refresh_from_db()
+
+ self.user.portfolio = self.portfolio
+ self.user.portfolio_additional_permissions = [UserPortfolioPermissionChoices.VIEW_PORTFOLIO]
+ self.user.save()
+ self.user.refresh_from_db()
+
+ so_portfolio_page = self.app.get(reverse("senior-official"))
+ # Assert that we're on the right page
+ self.assertContains(so_portfolio_page, "Senior official")
+ self.assertContains(so_portfolio_page, "Saturn Enceladus")
+ self.assertContains(so_portfolio_page, "Planet/Moon")
+ self.assertContains(so_portfolio_page, "spacedivision@igorville.com")
+
+ self.portfolio.delete()
+ so.delete()
+
@less_console_noise_decorator
def test_middleware_does_not_redirect_if_no_permission(self):
"""Test that user with no portfolio permission is not redirected when attempting to access home"""
From 38e1c79e751d0d8266c3531b41383d8fce6379d0 Mon Sep 17 00:00:00 2001
From: matthewswspence
Date: Fri, 9 Aug 2024 15:10:47 -0500
Subject: [PATCH 11/37] Additional required changes
---
src/registrar/templates/domain_base.html | 2 +-
src/registrar/templates/domain_detail.html | 8 ++++----
src/registrar/templates/includes/summary_item.html | 4 ++--
3 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/src/registrar/templates/domain_base.html b/src/registrar/templates/domain_base.html
index 9a869ef42..b99b12740 100644
--- a/src/registrar/templates/domain_base.html
+++ b/src/registrar/templates/domain_base.html
@@ -54,7 +54,7 @@
{% block domain_content %}
-
{% endif %}
{% if address %}
{% include "includes/organization_address.html" with organization=value %}
From 727362014161e50e96bff0637f540e7efd3c4293 Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Fri, 9 Aug 2024 14:28:27 -0600
Subject: [PATCH 13/37] Use same template for both views
---
.../templates/domain_senior_official.html | 32 ++-----------
.../includes/readonly_senior_official.html | 11 -----
.../templates/includes/senior_official.html | 47 +++++++++++++++++++
.../templates/portfolio_senior_official.html | 14 +-----
src/registrar/views/portfolios.py | 6 ---
5 files changed, 51 insertions(+), 59 deletions(-)
delete mode 100644 src/registrar/templates/includes/readonly_senior_official.html
create mode 100644 src/registrar/templates/includes/senior_official.html
diff --git a/src/registrar/templates/domain_senior_official.html b/src/registrar/templates/domain_senior_official.html
index c911af1b9..e51bf255c 100644
--- a/src/registrar/templates/domain_senior_official.html
+++ b/src/registrar/templates/domain_senior_official.html
@@ -4,35 +4,9 @@
{% block title %}Senior official | {{ domain.name }} | {% endblock %}
{% block domain_content %}
- {# this is right after the messages block in the parent template #}
- {% include "includes/form_errors.html" with form=form %}
-
-
Senior official
-
-
Your senior official is a person within your organization who can
- authorize domain requests. This person must be in a role of significant, executive responsibility within the organization. Read more about who can serve as a senior official.
-
{% if generic_org_type == "federal" or generic_org_type == "tribal" %}
-
- The senior official for your organization can’t be updated here.
- To suggest an update, email help@get.gov.
-
+ {% include "includes/senior_official.html" with can_edit=False %}
{% else %}
- {% include "includes/required_fields.html" %}
+ {% include "includes/senior_official.html" with can_edit=True %}
{% endif %}
-
-
-
-{% endblock %} {# domain_content #}
+{% endblock %}
diff --git a/src/registrar/templates/includes/readonly_senior_official.html b/src/registrar/templates/includes/readonly_senior_official.html
deleted file mode 100644
index 7a7908726..000000000
--- a/src/registrar/templates/includes/readonly_senior_official.html
+++ /dev/null
@@ -1,11 +0,0 @@
-{% if form.full_name.value is not None %}
- {% include "includes/input_read_only.html" with field=form.full_name %}
-{% endif %}
-
-{% if form.title.value is not None %}
- {% include "includes/input_read_only.html" with field=form.title %}
-{% endif %}
-
-{% if form.email.value is not None %}
- {% include "includes/input_read_only.html" with field=form.email %}
-{% endif %}
diff --git a/src/registrar/templates/includes/senior_official.html b/src/registrar/templates/includes/senior_official.html
new file mode 100644
index 000000000..c63493ae8
--- /dev/null
+++ b/src/registrar/templates/includes/senior_official.html
@@ -0,0 +1,47 @@
+{% load static field_helpers url_helpers %}
+
+{% if can_edit %}
+ {% include "includes/form_errors.html" with form=form %}
+{% endif %}
+
+
Senior Official
+
+
+ Your senior official is a person within your organization who can authorize domain requests.
+ This person must be in a role of significant, executive responsibility within the organization.
+ Read more about who can serve as a senior official.
+
+
+{% if can_edit %}
+ {% include "includes/required_fields.html" %}
+{% else %}
+
+ The senior official for your organization can’t be updated here.
+ To suggest an update, email help@get.gov
+
+{% endif %}
+
+{% if can_edit %}
+
+{% elif not form.full_name.value and not form.title.value and not form.email.value %}
+
No senior official was found.
+{% else %}
+ {% if form.full_name.value is not None %}
+ {% include "includes/input_read_only.html" with field=form.full_name %}
+ {% endif %}
+
+ {% if form.title.value is not None %}
+ {% include "includes/input_read_only.html" with field=form.title %}
+ {% endif %}
+
+ {% if form.email.value is not None %}
+ {% include "includes/input_read_only.html" with field=form.email %}
+ {% endif %}
+{% endif %}
\ No newline at end of file
diff --git a/src/registrar/templates/portfolio_senior_official.html b/src/registrar/templates/portfolio_senior_official.html
index 976cf9639..5c47e1645 100644
--- a/src/registrar/templates/portfolio_senior_official.html
+++ b/src/registrar/templates/portfolio_senior_official.html
@@ -18,19 +18,7 @@
-
-
Senior Official
-
-
Your senior official is a person within your organization who can authorize domain requests.
-
-
The senior official for your organization can’t be updated here. To suggest an update, email help@get.gov
-
- {% if not senior_official %}
-
No senior official was found for this portfolio.
- {% else %}
- {% include "includes/readonly_senior_official.html" with form=form %}
- {% endif %}
-
+ {% include "includes/senior_official.html" with can_edit=False %}
{% endblock %}
diff --git a/src/registrar/views/portfolios.py b/src/registrar/views/portfolios.py
index b9042d617..8a5321cc9 100644
--- a/src/registrar/views/portfolios.py
+++ b/src/registrar/views/portfolios.py
@@ -110,12 +110,6 @@ class PortfolioSeniorOfficialView(PortfolioBasePermissionView, FormMixin):
form_class = PortfolioSeniorOfficialForm
context_object_name = "portfolio"
- def get_context_data(self, **kwargs):
- """Add additional context data to the template."""
- context = super().get_context_data(**kwargs)
- context["senior_official"] = self.get_object().senior_official
- return context
-
def get_object(self, queryset=None):
"""Get the portfolio object based on the request user."""
portfolio = self.request.user.portfolio
From de50972ff1e29c835ee81cfe3f93100a5edd7baa Mon Sep 17 00:00:00 2001
From: Cameron Dixon
Date: Mon, 12 Aug 2024 11:40:31 -0400
Subject: [PATCH 14/37] Update domain_invitation.txt
---
src/registrar/templates/emails/domain_invitation.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/registrar/templates/emails/domain_invitation.txt b/src/registrar/templates/emails/domain_invitation.txt
index e426ae6ef..068040205 100644
--- a/src/registrar/templates/emails/domain_invitation.txt
+++ b/src/registrar/templates/emails/domain_invitation.txt
@@ -38,5 +38,5 @@ The .gov team
Contact us:
Learn about .gov
-The .gov registry is a part of the Cybersecurity and Infrastructure Security Agency (CISA)
+The .gov registry is a part of the Cybersecurity and Infrastructure Security Agency (CISA)
{% endautoescape %}
From d24c7f64604c03493aa76204da11de743efdf4f2 Mon Sep 17 00:00:00 2001
From: Cameron Dixon
Date: Mon, 12 Aug 2024 11:42:11 -0400
Subject: [PATCH 15/37] Update transition_domain_invitation.txt
---
src/registrar/templates/emails/transition_domain_invitation.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/registrar/templates/emails/transition_domain_invitation.txt b/src/registrar/templates/emails/transition_domain_invitation.txt
index bdce7d107..b6773d9e9 100644
--- a/src/registrar/templates/emails/transition_domain_invitation.txt
+++ b/src/registrar/templates/emails/transition_domain_invitation.txt
@@ -60,5 +60,5 @@ The .gov team
Domain management
Get.gov
-The .gov registry is a part of the Cybersecurity and Infrastructure Security Agency (CISA)
+The .gov registry is a part of the Cybersecurity and Infrastructure Security Agency (CISA)
{% endautoescape %}
From 5cd0900fe1e2dea415bb79b0e340c14270351706 Mon Sep 17 00:00:00 2001
From: Cameron Dixon
Date: Mon, 12 Aug 2024 11:42:32 -0400
Subject: [PATCH 16/37] Update questionable_senior_official.txt
---
.../action_needed_reasons/questionable_senior_official.txt | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
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 94e15b78c..e20e4cb60 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
@@ -32,5 +32,5 @@ The .gov team
Contact us:
Learn about .gov
-The .gov registry is a part of the Cybersecurity and Infrastructure Security Agency (CISA)
-{% endautoescape %}
\ No newline at end of file
+The .gov registry is a part of the Cybersecurity and Infrastructure Security Agency (CISA)
+{% endautoescape %}
From 411b1b655739d1226d78278fc49b5dc717e366e4 Mon Sep 17 00:00:00 2001
From: Cameron Dixon
Date: Mon, 12 Aug 2024 11:44:58 -0400
Subject: [PATCH 17/37] Update status_change_rejected.txt
---
src/registrar/templates/emails/status_change_rejected.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/registrar/templates/emails/status_change_rejected.txt b/src/registrar/templates/emails/status_change_rejected.txt
index 12693deb9..2fcbb1d83 100644
--- a/src/registrar/templates/emails/status_change_rejected.txt
+++ b/src/registrar/templates/emails/status_change_rejected.txt
@@ -77,5 +77,5 @@ The .gov team
Contact us:
Learn about .gov
-The .gov registry is a part of the Cybersecurity and Infrastructure Security Agency (CISA)
+The .gov registry is a part of the Cybersecurity and Infrastructure Security Agency (CISA)
{% endautoescape %}
From 5ce926f84badebeb3af5efe562ee42a215b1ec2c Mon Sep 17 00:00:00 2001
From: Cameron Dixon
Date: Mon, 12 Aug 2024 11:45:56 -0400
Subject: [PATCH 18/37] Update eligibility_unclear.txt
---
.../emails/action_needed_reasons/eligibility_unclear.txt | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
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 280321045..d3a986183 100644
--- a/src/registrar/templates/emails/action_needed_reasons/eligibility_unclear.txt
+++ b/src/registrar/templates/emails/action_needed_reasons/eligibility_unclear.txt
@@ -31,5 +31,5 @@ The .gov team
Contact us:
Learn about .gov
-The .gov registry is a part of the Cybersecurity and Infrastructure Security Agency (CISA)
-{% endautoescape %}
\ No newline at end of file
+The .gov registry is a part of the Cybersecurity and Infrastructure Security Agency (CISA)
+{% endautoescape %}
From 77499cdc384fe6cc3292ec901e6aa798da5f577d Mon Sep 17 00:00:00 2001
From: Cameron Dixon
Date: Mon, 12 Aug 2024 11:47:53 -0400
Subject: [PATCH 19/37] Update already_has_domains.txt
---
.../emails/action_needed_reasons/already_has_domains.txt | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/registrar/templates/emails/action_needed_reasons/already_has_domains.txt b/src/registrar/templates/emails/action_needed_reasons/already_has_domains.txt
index 264fe265b..b1b3b0a1c 100644
--- a/src/registrar/templates/emails/action_needed_reasons/already_has_domains.txt
+++ b/src/registrar/templates/emails/action_needed_reasons/already_has_domains.txt
@@ -47,5 +47,5 @@ The .gov team
Contact us:
Learn about .gov
-The .gov registry is a part of the Cybersecurity and Infrastructure Security Agency (CISA)
-{% endautoescape %}
\ No newline at end of file
+The .gov registry is a part of the Cybersecurity and Infrastructure Security Agency (CISA)
+{% endautoescape %}
From f8c20946d0b5b6f86da5db1e16f3f22754f99eab Mon Sep 17 00:00:00 2001
From: Cameron Dixon
Date: Mon, 12 Aug 2024 11:48:41 -0400
Subject: [PATCH 20/37] Update submission_confirmation.txt
---
src/registrar/templates/emails/submission_confirmation.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/registrar/templates/emails/submission_confirmation.txt b/src/registrar/templates/emails/submission_confirmation.txt
index 2dd4387a4..740e6f393 100644
--- a/src/registrar/templates/emails/submission_confirmation.txt
+++ b/src/registrar/templates/emails/submission_confirmation.txt
@@ -38,5 +38,5 @@ The .gov team
Contact us:
Learn about .gov
-The .gov registry is a part of the Cybersecurity and Infrastructure Security Agency (CISA)
+The .gov registry is a part of the Cybersecurity and Infrastructure Security Agency (CISA)
{% endautoescape %}
From c1c89f1c57b37b28982073c0361318f3b79d9881 Mon Sep 17 00:00:00 2001
From: Cameron Dixon
Date: Mon, 12 Aug 2024 11:49:03 -0400
Subject: [PATCH 21/37] Update bad_name.txt
---
.../templates/emails/action_needed_reasons/bad_name.txt | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
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 95967639c..7d088aa4e 100644
--- a/src/registrar/templates/emails/action_needed_reasons/bad_name.txt
+++ b/src/registrar/templates/emails/action_needed_reasons/bad_name.txt
@@ -30,5 +30,5 @@ The .gov team
Contact us:
Learn about .gov
-The .gov registry is a part of the Cybersecurity and Infrastructure Security Agency (CISA)
-{% endautoescape %}
\ No newline at end of file
+The .gov registry is a part of the Cybersecurity and Infrastructure Security Agency (CISA)
+{% endautoescape %}
From 6c73175c802e382b63f71ac5af1ee761b06390a4 Mon Sep 17 00:00:00 2001
From: Cameron Dixon
Date: Mon, 12 Aug 2024 11:49:22 -0400
Subject: [PATCH 22/37] Update status_change_approved.txt
---
src/registrar/templates/emails/status_change_approved.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/registrar/templates/emails/status_change_approved.txt b/src/registrar/templates/emails/status_change_approved.txt
index bbef8c81a..70f813599 100644
--- a/src/registrar/templates/emails/status_change_approved.txt
+++ b/src/registrar/templates/emails/status_change_approved.txt
@@ -49,5 +49,5 @@ The .gov team
Contact us:
Learn about .gov
-The .gov registry is a part of the Cybersecurity and Infrastructure Security Agency (CISA)
+The .gov registry is a part of the Cybersecurity and Infrastructure Security Agency (CISA)
{% endautoescape %}
From 7463e356f948fe7aa72813b467224787b408e4f1 Mon Sep 17 00:00:00 2001
From: Cameron Dixon
Date: Mon, 12 Aug 2024 11:54:13 -0400
Subject: [PATCH 23/37] Update domain_request_withdrawn.txt
---
src/registrar/templates/emails/domain_request_withdrawn.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/registrar/templates/emails/domain_request_withdrawn.txt b/src/registrar/templates/emails/domain_request_withdrawn.txt
index 0c061c53c..6efa92d64 100644
--- a/src/registrar/templates/emails/domain_request_withdrawn.txt
+++ b/src/registrar/templates/emails/domain_request_withdrawn.txt
@@ -26,5 +26,5 @@ The .gov team
Contact us:
Learn about .gov
-The .gov registry is a part of the Cybersecurity and Infrastructure Security Agency (CISA)
+The .gov registry is a part of the Cybersecurity and Infrastructure Security Agency (CISA)
{% endautoescape %}
From ad674e646b7e31d8538f28e46c105e6f10a131ea Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Mon, 12 Aug 2024 09:59:10 -0600
Subject: [PATCH 24/37] Fix pre-existing bug with sortfields
The AdminSortFields helper is incorrectly sorting the senior_official contact object.
Added a workaround as this ultimately needs a refactor
---
src/registrar/admin.py | 42 ++++++++++++++++---
src/registrar/forms/domain.py | 2 +-
src/registrar/forms/portfolio.py | 2 +-
.../templates/includes/senior_official.html | 2 +-
src/registrar/tests/test_views_portfolio.py | 3 +-
5 files changed, 41 insertions(+), 10 deletions(-)
diff --git a/src/registrar/admin.py b/src/registrar/admin.py
index ae06a8d96..31cbdc38d 100644
--- a/src/registrar/admin.py
+++ b/src/registrar/admin.py
@@ -22,7 +22,7 @@ from epplibwrapper.errors import ErrorCode, RegistryError
from registrar.models.user_domain_role import UserDomainRole
from waffle.admin import FlagAdmin
from waffle.models import Sample, Switch
-from registrar.models import Contact, Domain, DomainRequest, DraftDomain, User, Website
+from registrar.models import Contact, Domain, DomainRequest, DraftDomain, User, Website, SeniorOfficial
from registrar.utility.errors import FSMDomainRequestError, FSMErrorCodes
from registrar.views.utility.mixins import OrderableFieldsMixin
from django.contrib.admin.views.main import ORDER_VAR
@@ -493,6 +493,8 @@ class CustomLogEntryAdmin(LogEntryAdmin):
# return super().change_view(request, object_id, form_url, extra_context=extra_context)
+# TODO - this should be refactored. This is shared among every class that inherits this,
+# and it breaks the senior_official field because it exists both as model "Contact" and "SeniorOfficial".
class AdminSortFields:
_name_sort = ["first_name", "last_name", "email"]
@@ -504,6 +506,8 @@ class AdminSortFields:
# == Contact == #
"other_contacts": (Contact, _name_sort),
"submitter": (Contact, _name_sort),
+ # == Senior Official == #
+ "senior_official": (SeniorOfficial, _name_sort),
# == User == #
"creator": (User, _name_sort),
"user": (User, _name_sort),
@@ -553,15 +557,16 @@ class AuditedAdmin(admin.ModelAdmin):
)
)
- def formfield_for_manytomany(self, db_field, request, **kwargs):
+ def formfield_for_manytomany(self, db_field, request, use_admin_sort_fields=True, **kwargs):
"""customize the behavior of formfields with manytomany relationships. the customized
behavior includes sorting of objects in lists as well as customizing helper text"""
# Define a queryset. Note that in the super of this,
# a new queryset will only be generated if one does not exist.
# Thus, the order in which we define queryset matters.
+
queryset = AdminSortFields.get_queryset(db_field)
- if queryset:
+ if queryset and use_admin_sort_fields:
kwargs["queryset"] = queryset
formfield = super().formfield_for_manytomany(db_field, request, **kwargs)
@@ -572,7 +577,7 @@ class AuditedAdmin(admin.ModelAdmin):
)
return formfield
- def formfield_for_foreignkey(self, db_field, request, **kwargs):
+ def formfield_for_foreignkey(self, db_field, request, use_admin_sort_fields=True, **kwargs):
"""Customize the behavior of formfields with foreign key relationships. This will customize
the behavior of selects. Customized behavior includes sorting of objects in list."""
@@ -580,7 +585,7 @@ class AuditedAdmin(admin.ModelAdmin):
# a new queryset will only be generated if one does not exist.
# Thus, the order in which we define queryset matters.
queryset = AdminSortFields.get_queryset(db_field)
- if queryset:
+ if queryset and use_admin_sort_fields:
kwargs["queryset"] = queryset
return super().formfield_for_foreignkey(db_field, request, **kwargs)
@@ -1542,6 +1547,16 @@ class DomainInformationAdmin(ListHeaderAdmin, ImportExportModelAdmin):
# Get the filtered values
return super().changelist_view(request, extra_context=extra_context)
+ def formfield_for_foreignkey(self, db_field, request, **kwargs):
+ """Customize the behavior of formfields with foreign key relationships. This will customize
+ the behavior of selects. Customized behavior includes sorting of objects in list."""
+ # Remove this check on senior_official if this underlying model changes from
+ # "Contact" to "SeniorOfficial" or if we refactor AdminSortFields.
+ # Removing this will cause the list on django admin to return SeniorOffical
+ # objects rather than Contact objects.
+ use_sort = db_field.name != "senior_official"
+ return super().formfield_for_foreignkey(db_field, request, use_admin_sort_fields=use_sort, **kwargs)
+
class DomainRequestResource(FsmModelResource):
"""defines how each field in the referenced model should be mapped to the corresponding fields in the
@@ -2209,6 +2224,16 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
return None
+ def formfield_for_foreignkey(self, db_field, request, **kwargs):
+ """Customize the behavior of formfields with foreign key relationships. This will customize
+ the behavior of selects. Customized behavior includes sorting of objects in list."""
+ # Remove this check on senior_official if this underlying model changes from
+ # "Contact" to "SeniorOfficial" or if we refactor AdminSortFields.
+ # Removing this will cause the list on django admin to return SeniorOffical
+ # objects rather than Contact objects.
+ use_sort = db_field.name != "senior_official"
+ return super().formfield_for_foreignkey(db_field, request, use_admin_sort_fields=use_sort, **kwargs)
+
class TransitionDomainAdmin(ListHeaderAdmin):
"""Custom transition domain admin class."""
@@ -2258,6 +2283,7 @@ class DomainInformationInline(admin.StackedInline):
def formfield_for_manytomany(self, db_field, request, **kwargs):
"""customize the behavior of formfields with manytomany relationships. the customized
behavior includes sorting of objects in lists as well as customizing helper text"""
+
queryset = AdminSortFields.get_queryset(db_field)
if queryset:
kwargs["queryset"] = queryset
@@ -2272,8 +2298,12 @@ class DomainInformationInline(admin.StackedInline):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
"""Customize the behavior of formfields with foreign key relationships. This will customize
the behavior of selects. Customized behavior includes sorting of objects in list."""
+ # Remove this check on senior_official if this underlying model changes from
+ # "Contact" to "SeniorOfficial" or if we refactor AdminSortFields.
+ # Removing this will cause the list on django admin to return SeniorOffical
+ # objects rather than Contact objects.
queryset = AdminSortFields.get_queryset(db_field)
- if queryset:
+ if queryset and db_field.name != "senior_official":
kwargs["queryset"] = queryset
return super().formfield_for_foreignkey(db_field, request, **kwargs)
diff --git a/src/registrar/forms/domain.py b/src/registrar/forms/domain.py
index 4a1e95431..a46a4d3e8 100644
--- a/src/registrar/forms/domain.py
+++ b/src/registrar/forms/domain.py
@@ -326,7 +326,7 @@ class SeniorOfficialContactForm(ContactForm):
def __init__(self, disable_fields=False, *args, **kwargs):
super().__init__(*args, **kwargs)
- if self.instance:
+ if self.instance and self.instance.id:
self.fields["full_name"].initial = self.instance.get_formatted_name()
# Overriding bc phone not required in this form
diff --git a/src/registrar/forms/portfolio.py b/src/registrar/forms/portfolio.py
index 44d195dd6..67ccf2464 100644
--- a/src/registrar/forms/portfolio.py
+++ b/src/registrar/forms/portfolio.py
@@ -87,5 +87,5 @@ class PortfolioSeniorOfficialForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
- if self.instance:
+ if self.instance and self.instance.id:
self.fields["full_name"].initial = self.instance.get_formatted_name()
diff --git a/src/registrar/templates/includes/senior_official.html b/src/registrar/templates/includes/senior_official.html
index c63493ae8..b080127f4 100644
--- a/src/registrar/templates/includes/senior_official.html
+++ b/src/registrar/templates/includes/senior_official.html
@@ -44,4 +44,4 @@
{% if form.email.value is not None %}
{% include "includes/input_read_only.html" with field=form.email %}
{% endif %}
-{% endif %}
\ No newline at end of file
+{% endif %}
diff --git a/src/registrar/tests/test_views_portfolio.py b/src/registrar/tests/test_views_portfolio.py
index d725c95ad..60764cf1c 100644
--- a/src/registrar/tests/test_views_portfolio.py
+++ b/src/registrar/tests/test_views_portfolio.py
@@ -41,7 +41,7 @@ class TestPortfolio(WebTest):
@less_console_noise_decorator
@override_flag("organization_feature", active=True)
def test_portfolio_senior_official(self):
- """Tests the senior official page on portfolio"""
+ """Tests that the senior official page on portfolio contains the content we expect"""
self.app.set_user(self.user.username)
so = SeniorOfficial.objects.create(
@@ -63,6 +63,7 @@ class TestPortfolio(WebTest):
self.assertContains(so_portfolio_page, "Saturn Enceladus")
self.assertContains(so_portfolio_page, "Planet/Moon")
self.assertContains(so_portfolio_page, "spacedivision@igorville.com")
+ self.assertNotContains(so_portfolio_page, "Save")
self.portfolio.delete()
so.delete()
From 6c4e10cab14a9896ffbdda55dfddddabdd06ba07 Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Mon, 12 Aug 2024 10:10:38 -0600
Subject: [PATCH 25/37] Fix unit tests
---
src/registrar/forms/domain.py | 6 +++
src/registrar/forms/portfolio.py | 6 +++
src/registrar/tests/test_views_domain.py | 51 +++---------------------
3 files changed, 18 insertions(+), 45 deletions(-)
diff --git a/src/registrar/forms/domain.py b/src/registrar/forms/domain.py
index a46a4d3e8..c724ce383 100644
--- a/src/registrar/forms/domain.py
+++ b/src/registrar/forms/domain.py
@@ -351,6 +351,12 @@ class SeniorOfficialContactForm(ContactForm):
if disable_fields:
DomainHelper.mass_disable_fields(fields=self.fields, disable_required=True, disable_maxlength=True)
+ def clean(self):
+ """Clean override to remove unused fields"""
+ cleaned_data = super().clean()
+ cleaned_data.pop("full_name", None)
+ return cleaned_data
+
def save(self, commit=True):
"""
Override the save() method of the BaseModelForm.
diff --git a/src/registrar/forms/portfolio.py b/src/registrar/forms/portfolio.py
index 67ccf2464..cfd23c630 100644
--- a/src/registrar/forms/portfolio.py
+++ b/src/registrar/forms/portfolio.py
@@ -89,3 +89,9 @@ class PortfolioSeniorOfficialForm(forms.ModelForm):
super().__init__(*args, **kwargs)
if self.instance and self.instance.id:
self.fields["full_name"].initial = self.instance.get_formatted_name()
+
+ def clean(self):
+ """Clean override to remove unused fields"""
+ cleaned_data = super().clean()
+ cleaned_data.pop("full_name", None)
+ return cleaned_data
diff --git a/src/registrar/tests/test_views_domain.py b/src/registrar/tests/test_views_domain.py
index efbc1296c..8c79c92aa 100644
--- a/src/registrar/tests/test_views_domain.py
+++ b/src/registrar/tests/test_views_domain.py
@@ -1128,7 +1128,7 @@ class TestDomainSeniorOfficial(TestDomainOverview):
def test_domain_senior_official(self):
"""Can load domain's senior official page."""
page = self.client.get(reverse("domain-senior-official", kwargs={"pk": self.domain.id}))
- self.assertContains(page, "Senior official", count=14)
+ self.assertContains(page, "Senior official", count=3)
@less_console_noise_decorator
def test_domain_senior_official_content(self):
@@ -1207,16 +1207,13 @@ class TestDomainSeniorOfficial(TestDomainOverview):
self.domain_information.save()
so_page = self.app.get(reverse("domain-senior-official", kwargs={"pk": self.domain.id}))
- session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
- self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
-
self.assertContains(so_page, "Apple Tester")
self.assertContains(so_page, "CIO")
self.assertContains(so_page, "nobody@igorville.gov")
self.assertNotContains(so_page, "Save")
@less_console_noise_decorator
- def test_domain_edit_senior_official_tribal(self):
+ def test_domain_cannot_edit_senior_official_tribal(self):
"""Tests that no edit can occur when the underlying domain is tribal"""
# Set the org type to federal
@@ -1231,46 +1228,10 @@ class TestDomainSeniorOfficial(TestDomainOverview):
self.domain_information.save()
so_page = self.app.get(reverse("domain-senior-official", kwargs={"pk": self.domain.id}))
- session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
- self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
-
- # Test if the form is populating data correctly
- so_form = so_page.forms[0]
-
- test_cases = [
- ("first_name", "Apple"),
- ("last_name", "Tester"),
- ("title", "CIO"),
- ("email", "nobody@igorville.gov"),
- ]
- self.assert_all_form_fields_have_expected_values(so_form, test_cases, test_for_disabled=True)
-
- # Attempt to change data on each field. Because this domain is federal,
- # this should not succeed.
- so_form["first_name"] = "Orange"
- so_form["last_name"] = "Smoothie"
- so_form["title"] = "Cat"
- so_form["email"] = "somebody@igorville.gov"
-
- submission = so_form.submit()
-
- # A 302 indicates this page underwent a redirect.
- self.assertEqual(submission.status_code, 302)
-
- followed_submission = submission.follow()
-
- # Test the returned form for data accuracy. These values should be unchanged.
- new_form = followed_submission.forms[0]
- self.assert_all_form_fields_have_expected_values(new_form, test_cases, test_for_disabled=True)
-
- # refresh domain information. Test these values in the DB.
- self.domain_information.refresh_from_db()
-
- # All values should be unchanged. These are defined manually for code clarity.
- self.assertEqual("Apple", self.domain_information.senior_official.first_name)
- self.assertEqual("Tester", self.domain_information.senior_official.last_name)
- self.assertEqual("CIO", self.domain_information.senior_official.title)
- self.assertEqual("nobody@igorville.gov", self.domain_information.senior_official.email)
+ self.assertContains(so_page, "Apple Tester")
+ self.assertContains(so_page, "CIO")
+ self.assertContains(so_page, "nobody@igorville.gov")
+ self.assertNotContains(so_page, "Save")
@less_console_noise_decorator
def test_domain_edit_senior_official_creates_new(self):
From c2af76b6dcd34996ea3ed03d8b7bb41966f5e94e Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Mon, 12 Aug 2024 11:21:16 -0600
Subject: [PATCH 26/37] lint
---
src/registrar/forms/portfolio.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/registrar/forms/portfolio.py b/src/registrar/forms/portfolio.py
index cfd23c630..14a45f6ae 100644
--- a/src/registrar/forms/portfolio.py
+++ b/src/registrar/forms/portfolio.py
@@ -89,7 +89,7 @@ class PortfolioSeniorOfficialForm(forms.ModelForm):
super().__init__(*args, **kwargs)
if self.instance and self.instance.id:
self.fields["full_name"].initial = self.instance.get_formatted_name()
-
+
def clean(self):
"""Clean override to remove unused fields"""
cleaned_data = super().clean()
From 6b9ec52921361e7ac264069b207ad3de202ff507 Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Mon, 12 Aug 2024 12:19:50 -0600
Subject: [PATCH 27/37] Update admin.py
---
src/registrar/admin.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/registrar/admin.py b/src/registrar/admin.py
index 31cbdc38d..590ccbaac 100644
--- a/src/registrar/admin.py
+++ b/src/registrar/admin.py
@@ -2876,6 +2876,7 @@ class PortfolioAdmin(ListHeaderAdmin):
autocomplete_fields = [
"creator",
"federal_agency",
+ "senior_official",
]
def change_view(self, request, object_id, form_url="", extra_context=None):
From 3691a9ee58751761070f5cd44c7ebf054cd93bc2 Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Mon, 12 Aug 2024 12:51:08 -0600
Subject: [PATCH 28/37] Text toggle
---
src/registrar/templates/domain_senior_official.html | 4 ++--
src/registrar/templates/includes/senior_official.html | 4 +++-
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/src/registrar/templates/domain_senior_official.html b/src/registrar/templates/domain_senior_official.html
index e51bf255c..7ed90c2ec 100644
--- a/src/registrar/templates/domain_senior_official.html
+++ b/src/registrar/templates/domain_senior_official.html
@@ -5,8 +5,8 @@
{% block domain_content %}
{% if generic_org_type == "federal" or generic_org_type == "tribal" %}
- {% include "includes/senior_official.html" with can_edit=False %}
+ {% include "includes/senior_official.html" with can_edit=False include_read_more_text=True %}
{% else %}
- {% include "includes/senior_official.html" with can_edit=True %}
+ {% include "includes/senior_official.html" with can_edit=True include_read_more_text=True %}
{% endif %}
{% endblock %}
diff --git a/src/registrar/templates/includes/senior_official.html b/src/registrar/templates/includes/senior_official.html
index b080127f4..ef816e297 100644
--- a/src/registrar/templates/includes/senior_official.html
+++ b/src/registrar/templates/includes/senior_official.html
@@ -8,8 +8,10 @@
Your senior official is a person within your organization who can authorize domain requests.
- This person must be in a role of significant, executive responsibility within the organization.
+ This person must be in a role of significant, executive responsibility within the organization.
+ {% if include_read_more_text %}
Read more about who can serve as a senior official.
+ {% endif %}
{% if can_edit %}
From bea53019a5f66940e9ccf650bdd55ab85fc2b3f8 Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Mon, 12 Aug 2024 15:28:02 -0400
Subject: [PATCH 29/37] added clean method which performs validation on user
model
---
src/registrar/models/user.py | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/src/registrar/models/user.py b/src/registrar/models/user.py
index 0221c2d50..73d5f6a1d 100644
--- a/src/registrar/models/user.py
+++ b/src/registrar/models/user.py
@@ -3,6 +3,7 @@ import logging
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.db.models import Q
+from django.forms import ValidationError
from registrar.models.domain_information import DomainInformation
from registrar.models.user_domain_role import UserDomainRole
@@ -229,6 +230,15 @@ class User(AbstractUser):
def has_contact_info(self):
return bool(self.title or self.email or self.phone)
+ def clean(self):
+ super().clean()
+
+ if self.portfolio is None and self._get_portfolio_permissions():
+ raise ValidationError("When portfolio roles or additional permissions are assigned, portfolio is required.")
+
+ if self.portfolio is not None and not self._get_portfolio_permissions():
+ raise ValidationError("When portfolio is assigned, portfolio roles or additional permissions are required.")
+
def _get_portfolio_permissions(self):
"""
Retrieve the permissions for the user's portfolio roles.
From 334b6eac3816c9b2bff0b15029ac3b5d221be065 Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Mon, 12 Aug 2024 15:29:15 -0400
Subject: [PATCH 30/37] commented
---
src/registrar/models/user.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/registrar/models/user.py b/src/registrar/models/user.py
index 73d5f6a1d..81d3b9b61 100644
--- a/src/registrar/models/user.py
+++ b/src/registrar/models/user.py
@@ -231,6 +231,7 @@ class User(AbstractUser):
return bool(self.title or self.email or self.phone)
def clean(self):
+ """Extends clean method to perform additional validation, which can raise errors in django admin."""
super().clean()
if self.portfolio is None and self._get_portfolio_permissions():
From a2a6b9e68f83bd51dc304a417731d30fab4c818e Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Mon, 12 Aug 2024 15:36:14 -0400
Subject: [PATCH 31/37] fix zap for adding suborganization url
---
src/zap.conf | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/zap.conf b/src/zap.conf
index d6d22995c..c97897aeb 100644
--- a/src/zap.conf
+++ b/src/zap.conf
@@ -71,6 +71,7 @@
10038 OUTOFSCOPE http://app:8080/domain_requests/
10038 OUTOFSCOPE http://app:8080/domains/
10038 OUTOFSCOPE http://app:8080/organization/
+10038 OUTOFSCOPE http://app:8080/suborganization/
# This URL always returns 404, so include it as well.
10038 OUTOFSCOPE http://app:8080/todo
# OIDC isn't configured in the test environment and DEBUG=True so this gives a 500 without CSP headers
From 77cf108323d4c7848109adad735e277d6758543f Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Mon, 12 Aug 2024 15:46:52 -0400
Subject: [PATCH 32/37] test cases added
---
src/registrar/tests/test_models.py | 36 ++++++++++++++++++++++++++++++
1 file changed, 36 insertions(+)
diff --git a/src/registrar/tests/test_models.py b/src/registrar/tests/test_models.py
index 8c69517e9..cfb66eb2a 100644
--- a/src/registrar/tests/test_models.py
+++ b/src/registrar/tests/test_models.py
@@ -1,3 +1,4 @@
+from django.forms import ValidationError
from django.test import TestCase
from django.db.utils import IntegrityError
from django.db import transaction
@@ -1348,6 +1349,7 @@ class TestUser(TestCase):
self.user.phone = None
self.assertFalse(self.user.has_contact_info())
+ @less_console_noise_decorator
def test_has_portfolio_permission(self):
"""
0. Returns False when user does not have a permission
@@ -1401,6 +1403,40 @@ class TestUser(TestCase):
Portfolio.objects.all().delete()
+ @less_console_noise_decorator
+ def test_user_with_portfolio_but_no_roles(self):
+ # Create an instance of User with a portfolio but no roles or additional permissions
+ portfolio, _ = Portfolio.objects.get_or_create(creator=self.user, organization_name="Hotel California")
+
+ self.user.portfolio = portfolio
+ self.user.portfolio_roles = []
+
+ # Test if the ValidationError is raised with the correct message
+ with self.assertRaises(ValidationError) as cm:
+ self.user.clean()
+
+ self.assertEqual(
+ cm.exception.message, "When portfolio is assigned, portfolio roles or additional permissions are required."
+ )
+ self.user.refresh_from_db()
+ Portfolio.objects.all().delete()
+
+ @less_console_noise_decorator
+ def test_user_with_portfolio_roles_but_no_portfolio(self):
+ # Create an instance of User with a portfolio role but no portfolio
+ self.user.portfolio = None
+ self.user.portfolio_roles = [UserPortfolioRoleChoices.ORGANIZATION_ADMIN]
+
+ # Test if the ValidationError is raised with the correct message
+ with self.assertRaises(ValidationError) as cm:
+ self.user.clean()
+
+ self.assertEqual(
+ cm.exception.message, "When portfolio roles or additional permissions are assigned, portfolio is required."
+ )
+ self.user.refresh_from_db()
+ Portfolio.objects.all().delete()
+
class TestContact(TestCase):
@less_console_noise_decorator
From 8ff5ba8ba0093eccec9a57536cfae3591ca8972a Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Mon, 12 Aug 2024 14:29:12 -0600
Subject: [PATCH 33/37] Update senior_official.html
---
src/registrar/templates/includes/senior_official.html | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/registrar/templates/includes/senior_official.html b/src/registrar/templates/includes/senior_official.html
index ef816e297..67a66c3f8 100644
--- a/src/registrar/templates/includes/senior_official.html
+++ b/src/registrar/templates/includes/senior_official.html
@@ -7,9 +7,9 @@
Senior Official
- Your senior official is a person within your organization who can authorize domain requests.
+ Your senior official is a person within your organization who can authorize domain requests.
+ {% if include_read_more_text %}
This person must be in a role of significant, executive responsibility within the organization.
- {% if include_read_more_text %}
Read more about who can serve as a senior official.
{% endif %}
From 2c50c35ed485952187f389c2464832880a59ccb3 Mon Sep 17 00:00:00 2001
From: David Kennedy
Date: Mon, 12 Aug 2024 17:22:33 -0400
Subject: [PATCH 34/37] fixed tests
---
src/registrar/tests/test_models.py | 3 ---
1 file changed, 3 deletions(-)
diff --git a/src/registrar/tests/test_models.py b/src/registrar/tests/test_models.py
index cfb66eb2a..5167aac99 100644
--- a/src/registrar/tests/test_models.py
+++ b/src/registrar/tests/test_models.py
@@ -1418,7 +1418,6 @@ class TestUser(TestCase):
self.assertEqual(
cm.exception.message, "When portfolio is assigned, portfolio roles or additional permissions are required."
)
- self.user.refresh_from_db()
Portfolio.objects.all().delete()
@less_console_noise_decorator
@@ -1434,8 +1433,6 @@ class TestUser(TestCase):
self.assertEqual(
cm.exception.message, "When portfolio roles or additional permissions are assigned, portfolio is required."
)
- self.user.refresh_from_db()
- Portfolio.objects.all().delete()
class TestContact(TestCase):
From 59166fa2e6556d1ed5ac1667c66ffc78aaa6ebbd Mon Sep 17 00:00:00 2001
From: matthewswspence
Date: Tue, 13 Aug 2024 09:36:07 -0500
Subject: [PATCH 35/37] remove header specificity on
register-form-review-header styling
---
src/registrar/assets/sass/_theme/_register-form.scss | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/registrar/assets/sass/_theme/_register-form.scss b/src/registrar/assets/sass/_theme/_register-form.scss
index 7c93f0a10..41d2980e3 100644
--- a/src/registrar/assets/sass/_theme/_register-form.scss
+++ b/src/registrar/assets/sass/_theme/_register-form.scss
@@ -25,7 +25,7 @@
}
}
-h3.register-form-review-header {
+.register-form-review-header {
color: color('primary-dark');
margin-top: units(2);
margin-bottom: 0;
From 6b89ca43e43ee60b1b272d5a8c0056e7ed48794b Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Tue, 13 Aug 2024 09:21:00 -0600
Subject: [PATCH 36/37] Add comments
---
src/registrar/admin.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/registrar/admin.py b/src/registrar/admin.py
index 590ccbaac..18c1052fc 100644
--- a/src/registrar/admin.py
+++ b/src/registrar/admin.py
@@ -493,7 +493,7 @@ class CustomLogEntryAdmin(LogEntryAdmin):
# return super().change_view(request, object_id, form_url, extra_context=extra_context)
-# TODO - this should be refactored. This is shared among every class that inherits this,
+# TODO #2571 - this should be refactored. This is shared among every class that inherits this,
# and it breaks the senior_official field because it exists both as model "Contact" and "SeniorOfficial".
class AdminSortFields:
_name_sort = ["first_name", "last_name", "email"]
@@ -1550,6 +1550,7 @@ class DomainInformationAdmin(ListHeaderAdmin, ImportExportModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
"""Customize the behavior of formfields with foreign key relationships. This will customize
the behavior of selects. Customized behavior includes sorting of objects in list."""
+ # TODO #2571
# Remove this check on senior_official if this underlying model changes from
# "Contact" to "SeniorOfficial" or if we refactor AdminSortFields.
# Removing this will cause the list on django admin to return SeniorOffical
@@ -2227,6 +2228,7 @@ class DomainRequestAdmin(ListHeaderAdmin, ImportExportModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
"""Customize the behavior of formfields with foreign key relationships. This will customize
the behavior of selects. Customized behavior includes sorting of objects in list."""
+ # TODO #2571
# Remove this check on senior_official if this underlying model changes from
# "Contact" to "SeniorOfficial" or if we refactor AdminSortFields.
# Removing this will cause the list on django admin to return SeniorOffical
From 0efa02006fdcb1358378e393904e0f4eb518da84 Mon Sep 17 00:00:00 2001
From: zandercymatics <141044360+zandercymatics@users.noreply.github.com>
Date: Tue, 13 Aug 2024 09:29:18 -0600
Subject: [PATCH 37/37] Add period
---
src/registrar/templates/includes/senior_official.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/registrar/templates/includes/senior_official.html b/src/registrar/templates/includes/senior_official.html
index 67a66c3f8..fda97b6a9 100644
--- a/src/registrar/templates/includes/senior_official.html
+++ b/src/registrar/templates/includes/senior_official.html
@@ -19,7 +19,7 @@
{% else %}
The senior official for your organization can’t be updated here.
- To suggest an update, email help@get.gov
+ To suggest an update, email help@get.gov.