Merge remote-tracking branch 'origin/main' into nl/2248-add-org-and-portfolio-table

This commit is contained in:
CocoByte 2024-06-18 11:56:30 -06:00
commit 1f2d63070d
No known key found for this signature in database
GPG key ID: BBFAA2526384C97F
16 changed files with 364 additions and 31 deletions

View file

@ -27,6 +27,7 @@ jobs:
|| startsWith(github.head_ref, 'cb/') || startsWith(github.head_ref, 'cb/')
|| startsWith(github.head_ref, 'hotgov/') || startsWith(github.head_ref, 'hotgov/')
|| startsWith(github.head_ref, 'litterbox/') || startsWith(github.head_ref, 'litterbox/')
|| startsWith(github.head_ref, 'ag/')
outputs: outputs:
environment: ${{ steps.var.outputs.environment}} environment: ${{ steps.var.outputs.environment}}
runs-on: "ubuntu-latest" runs-on: "ubuntu-latest"

View file

@ -16,6 +16,7 @@ on:
- stable - stable
- staging - staging
- development - development
- ag
- litterbox - litterbox
- hotgov - hotgov
- cb - cb

View file

@ -16,6 +16,7 @@ on:
options: options:
- staging - staging
- development - development
- ag
- litterbox - litterbox
- hotgov - hotgov
- cb - cb

View file

@ -0,0 +1,32 @@
---
applications:
- name: getgov-ag
buildpacks:
- python_buildpack
path: ../../src
instances: 1
memory: 512M
stack: cflinuxfs4
timeout: 180
command: ./run.sh
health-check-type: http
health-check-http-endpoint: /health
health-check-invocation-timeout: 40
env:
# Send stdout and stderr straight to the terminal without buffering
PYTHONUNBUFFERED: yup
# Tell Django where to find its configuration
DJANGO_SETTINGS_MODULE: registrar.config.settings
# Tell Django where it is being hosted
DJANGO_BASE_URL: https://getgov-ag.app.cloud.gov
# Tell Django how much stuff to log
DJANGO_LOG_LEVEL: INFO
# default public site location
GETGOV_PUBLIC_SITE_URL: https://get.gov
# Flag to disable/enable features in prod environments
IS_PRODUCTION: False
routes:
- route: getgov-ag.app.cloud.gov
services:
- getgov-credentials
- getgov-ag-database

View file

@ -1604,6 +1604,18 @@ document.addEventListener('DOMContentLoaded', function() {
/**
* An IIFE that displays confirmation modal on the user profile page
*/
(function userProfileListener() {
const showConfirmationModalTrigger = document.querySelector('.show-confirmation-modal');
if (showConfirmationModalTrigger) {
showConfirmationModalTrigger.click();
}
}
)();
/** /**
* An IIFE that hooks up the edit buttons on the finish-user-setup page * An IIFE that hooks up the edit buttons on the finish-user-setup page
*/ */

View file

@ -70,7 +70,7 @@ body {
top: 50%; top: 50%;
left: 0; left: 0;
width: 0; /* No width since it's a border */ width: 0; /* No width since it's a border */
height: 50%; height: 40%;
border-left: solid 1px color('base-light'); border-left: solid 1px color('base-light');
transform: translateY(-50%); transform: translateY(-50%);
} }

View file

@ -659,6 +659,7 @@ ALLOWED_HOSTS = [
"getgov-stable.app.cloud.gov", "getgov-stable.app.cloud.gov",
"getgov-staging.app.cloud.gov", "getgov-staging.app.cloud.gov",
"getgov-development.app.cloud.gov", "getgov-development.app.cloud.gov",
"getgov-ag.app.cloud.gov",
"getgov-litterbox.app.cloud.gov", "getgov-litterbox.app.cloud.gov",
"getgov-hotgov.app.cloud.gov", "getgov-hotgov.app.cloud.gov",
"getgov-cb.app.cloud.gov", "getgov-cb.app.cloud.gov",

View file

@ -47,7 +47,7 @@ class UserProfileForm(forms.ModelForm):
self.fields["middle_name"].label = "Middle name (optional)" self.fields["middle_name"].label = "Middle name (optional)"
self.fields["last_name"].label = "Last name / family name" self.fields["last_name"].label = "Last name / family name"
self.fields["title"].label = "Title or role in your organization" self.fields["title"].label = "Title or role in your organization"
self.fields["email"].label = "Organizational email" self.fields["email"].label = "Organization email"
# Set custom error messages # Set custom error messages
self.fields["first_name"].error_messages = {"required": "Enter your first name / given name."} self.fields["first_name"].error_messages = {"required": "Enter your first name / given name."}

View file

@ -5,6 +5,7 @@ Contains middleware used in settings.py
from urllib.parse import parse_qs from urllib.parse import parse_qs
from django.urls import reverse from django.urls import reverse
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from registrar.models.user import User
from waffle.decorators import flag_is_active from waffle.decorators import flag_is_active
from registrar.models.utility.generic_helper import replace_url_queryparams from registrar.models.utility.generic_helper import replace_url_queryparams
@ -38,10 +39,17 @@ class CheckUserProfileMiddleware:
self.get_response = get_response self.get_response = get_response
self.setup_page = reverse("finish-user-profile-setup") self.setup_page = reverse("finish-user-profile-setup")
self.profile_page = reverse("user-profile")
self.logout_page = reverse("logout") self.logout_page = reverse("logout")
self.excluded_pages = [ self.regular_excluded_pages = [
self.setup_page, self.setup_page,
self.logout_page, self.logout_page,
"/admin",
]
self.other_excluded_pages = [
self.profile_page,
self.logout_page,
"/admin",
] ]
def __call__(self, request): def __call__(self, request):
@ -61,12 +69,15 @@ class CheckUserProfileMiddleware:
if request.user.is_authenticated: if request.user.is_authenticated:
if hasattr(request.user, "finished_setup") and not request.user.finished_setup: if hasattr(request.user, "finished_setup") and not request.user.finished_setup:
return self._handle_setup_not_finished(request) if request.user.verification_type == User.VerificationTypeChoices.REGULAR:
return self._handle_regular_user_setup_not_finished(request)
else:
return self._handle_other_user_setup_not_finished(request)
# Continue processing the view # Continue processing the view
return None return None
def _handle_setup_not_finished(self, request): def _handle_regular_user_setup_not_finished(self, request):
"""Redirects the given user to the finish setup page. """Redirects the given user to the finish setup page.
We set the "redirect" query param equal to where the user wants to go. We set the "redirect" query param equal to where the user wants to go.
@ -82,7 +93,7 @@ class CheckUserProfileMiddleware:
custom_redirect = "domain-request:" if request.path == "/request/" else None custom_redirect = "domain-request:" if request.path == "/request/" else None
# Don't redirect on excluded pages (such as the setup page itself) # Don't redirect on excluded pages (such as the setup page itself)
if not any(request.path.startswith(page) for page in self.excluded_pages): if not any(request.path.startswith(page) for page in self.regular_excluded_pages):
# Preserve the original query parameters, and coerce them into a dict # Preserve the original query parameters, and coerce them into a dict
query_params = parse_qs(request.META["QUERY_STRING"]) query_params = parse_qs(request.META["QUERY_STRING"])
@ -98,3 +109,13 @@ class CheckUserProfileMiddleware:
else: else:
# Process the view as normal # Process the view as normal
return None return None
def _handle_other_user_setup_not_finished(self, request):
"""Redirects the given user to the profile page to finish setup."""
# Don't redirect on excluded pages (such as the setup page itself)
if not any(request.path.startswith(page) for page in self.other_excluded_pages):
return HttpResponseRedirect(self.profile_page)
else:
# Process the view as normal
return None

View file

@ -15,7 +15,11 @@
{% endblock %} {% endblock %}
<h1>Manage your domains</h2> <h1>Manage your domains</h2>
{% comment %}
IMPORTANT:
If this button is added on any other page, make sure to update the
relevant view to reset request.session["new_request"] = True
{% endcomment %}
<p class="margin-top-4"> <p class="margin-top-4">
<a href="{% url 'domain-request:' %}" class="usa-button" <a href="{% url 'domain-request:' %}" class="usa-button"
> >

View file

@ -5,6 +5,11 @@ Edit your User Profile |
{% endblock title %} {% endblock title %}
{% load static url_helpers %} {% load static url_helpers %}
{# Disable the redirect #}
{% block logo %}
{% include "includes/gov_extended_logo.html" with logo_clickable=user_finished_setup %}
{% endblock %}
{% block content %} {% block content %}
<main id="main-content" class="grid-container"> <main id="main-content" class="grid-container">
<div class="grid-col desktop:grid-offset-2 desktop:grid-col-8"> <div class="grid-col desktop:grid-offset-2 desktop:grid-col-8">
@ -35,6 +40,61 @@ Edit your User Profile |
{% endif %} {% endif %}
</a> </a>
{% endif %} {% endif %}
{% if show_confirmation_modal %}
<a
href="#toggle-confirmation-modal"
class="usa-button display-none show-confirmation-modal"
aria-controls="toggle-confirmation-modal"
data-open-modal
>Open confirmation modal</a>
<div
class="usa-modal usa-modal--lg is-visible"
id="toggle-confirmation-modal"
aria-labelledby="Add contact information"
aria-describedby="Add contact information"
data-force-action
>
<div class="usa-modal__content">
<div class="usa-modal__main">
<h2 class="usa-modal__heading" id="modal-1-heading">
Add contact information
</h2>
<div class="usa-prose">
<p id="modal-1-description">
.Gov domain registrants must maintain accurate contact information in the .gov registrar.
Before you can manage your domain, we need you to add your contact information.
</p>
</div>
<div class="usa-modal__footer">
<ul class="usa-button-group">
<li class="usa-button-group__item">
<button
type="button"
class="usa-button padding-105 text-center"
data-close-modal
>
Add contact information
</button>
</li>
</ul>
</div>
</div>
<button
type="button"
class="usa-button usa-modal__close"
aria-label="Close this window"
data-close-modal
>
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img">
<use xlink:href="{%static 'img/sprite.svg'%}#close"></use>
</svg>
</button>
</div>
</div>
{% endif %}
{% endblock content %} {% endblock content %}
@ -43,3 +103,7 @@ Edit your User Profile |
</div> </div>
</main> </main>
{% endblock content_bottom %} {% endblock content_bottom %}
{% block footer %}
{% include "includes/footer.html" with show_manage_your_domains=user_finished_setup %}
{% endblock footer %}

View file

@ -63,11 +63,24 @@ class TestWithUser(MockEppLib):
self.user.contact.title = title self.user.contact.title = title
self.user.contact.save() self.user.contact.save()
username_incomplete = "test_user_incomplete" username_regular_incomplete = "test_regular_user_incomplete"
username_other_incomplete = "test_other_user_incomplete"
first_name_2 = "Incomplete" first_name_2 = "Incomplete"
email_2 = "unicorn@igorville.com" email_2 = "unicorn@igorville.com"
self.incomplete_user = get_user_model().objects.create( # in the case below, REGULAR user is 'Verified by Login.gov, ie. IAL2
username=username_incomplete, first_name=first_name_2, email=email_2 self.incomplete_regular_user = get_user_model().objects.create(
username=username_regular_incomplete,
first_name=first_name_2,
email=email_2,
verification_type=User.VerificationTypeChoices.REGULAR,
)
# in the case below, other user is representative of GRANDFATHERED,
# VERIFIED_BY_STAFF, INVITED, FIXTURE_USER, ie. IAL1
self.incomplete_other_user = get_user_model().objects.create(
username=username_other_incomplete,
first_name=first_name_2,
email=email_2,
verification_type=User.VerificationTypeChoices.VERIFIED_BY_STAFF,
) )
def tearDown(self): def tearDown(self):
@ -75,8 +88,7 @@ class TestWithUser(MockEppLib):
super().tearDown() super().tearDown()
DomainRequest.objects.all().delete() DomainRequest.objects.all().delete()
DomainInformation.objects.all().delete() DomainInformation.objects.all().delete()
self.user.delete() User.objects.all().delete()
self.incomplete_user.delete()
class TestEnvironmentVariablesEffects(TestCase): class TestEnvironmentVariablesEffects(TestCase):
@ -526,7 +538,7 @@ class FinishUserProfileTests(TestWithUser, WebTest):
@less_console_noise_decorator @less_console_noise_decorator
def test_new_user_with_profile_feature_on(self): def test_new_user_with_profile_feature_on(self):
"""Tests that a new user is redirected to the profile setup page when profile_feature is on""" """Tests that a new user is redirected to the profile setup page when profile_feature is on"""
self.app.set_user(self.incomplete_user.username) self.app.set_user(self.incomplete_regular_user.username)
with override_flag("profile_feature", active=True): with override_flag("profile_feature", active=True):
# This will redirect the user to the setup page. # This will redirect the user to the setup page.
# Follow implicity checks if our redirect is working. # Follow implicity checks if our redirect is working.
@ -565,7 +577,7 @@ class FinishUserProfileTests(TestWithUser, WebTest):
def test_new_user_goes_to_domain_request_with_profile_feature_on(self): def test_new_user_goes_to_domain_request_with_profile_feature_on(self):
"""Tests that a new user is redirected to the domain request page when profile_feature is on""" """Tests that a new user is redirected to the domain request page when profile_feature is on"""
self.app.set_user(self.incomplete_user.username) self.app.set_user(self.incomplete_regular_user.username)
with override_flag("profile_feature", active=True): with override_flag("profile_feature", active=True):
# This will redirect the user to the setup page # This will redirect the user to the setup page
finish_setup_page = self.app.get(reverse("domain-request:")).follow() finish_setup_page = self.app.get(reverse("domain-request:")).follow()
@ -619,6 +631,106 @@ class FinishUserProfileTests(TestWithUser, WebTest):
self.assertContains(response, "Youre about to start your .gov domain request") self.assertContains(response, "Youre about to start your .gov domain request")
class FinishUserProfileForOtherUsersTests(TestWithUser, WebTest):
"""A series of tests that target the user profile page intercept for incomplete IAL1 user profiles."""
# csrf checks do not work well with WebTest.
# We disable them here.
csrf_checks = False
def setUp(self):
super().setUp()
self.user.title = None
self.user.save()
self.client.force_login(self.user)
self.domain, _ = Domain.objects.get_or_create(name="sampledomain.gov", state=Domain.State.READY)
self.role, _ = UserDomainRole.objects.get_or_create(
user=self.user, domain=self.domain, role=UserDomainRole.Roles.MANAGER
)
def tearDown(self):
super().tearDown()
PublicContact.objects.filter(domain=self.domain).delete()
self.role.delete()
self.domain.delete()
Domain.objects.all().delete()
Website.objects.all().delete()
Contact.objects.all().delete()
def _set_session_cookie(self):
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
def _submit_form_webtest(self, form, follow=False):
page = form.submit()
self._set_session_cookie()
return page.follow() if follow else page
@less_console_noise_decorator
def test_new_user_with_profile_feature_on(self):
"""Tests that a new user is redirected to the profile setup page when profile_feature is on,
and testing that the confirmation modal is present"""
self.app.set_user(self.incomplete_other_user.username)
with override_flag("profile_feature", active=True):
# This will redirect the user to the user profile page.
# Follow implicity checks if our redirect is working.
user_profile_page = self.app.get(reverse("home")).follow()
self._set_session_cookie()
# Assert that we're on the right page by testing for the modal
self.assertContains(user_profile_page, "domain registrants must maintain accurate contact information")
user_profile_page = self._submit_form_webtest(user_profile_page.form)
self.assertEqual(user_profile_page.status_code, 200)
# Assert that modal does not appear on subsequent submits
self.assertNotContains(user_profile_page, "domain registrants must maintain accurate contact information")
# Assert that unique error message appears by testing the message in a specific div
html_content = user_profile_page.content.decode("utf-8")
# Normalize spaces and line breaks in the HTML content
normalized_html_content = " ".join(html_content.split())
# Expected string without extra spaces and line breaks
expected_string = "Before you can manage your domain, we need you to add contact information."
# Check for the presence of the <div> element with the specific text
self.assertIn(f'<div class="usa-alert__body"> {expected_string} </div>', normalized_html_content)
# We're missing a phone number, so the page should tell us that
self.assertContains(user_profile_page, "Enter your phone number.")
# We need to assert that links to manage your domain are not present (in both body and footer)
self.assertNotContains(user_profile_page, "Manage your domains")
# Assert the tooltip on the logo, indicating that the logo is not clickable
self.assertContains(
user_profile_page, 'title="Before you can manage your domains, we need you to add contact information."'
)
# Assert that modal does not appear on subsequent submits
self.assertNotContains(user_profile_page, "domain registrants must maintain accurate contact information")
# Add a phone number
finish_setup_form = user_profile_page.form
finish_setup_form["phone"] = "(201) 555-0123"
finish_setup_form["title"] = "CEO"
finish_setup_form["last_name"] = "example"
save_page = self._submit_form_webtest(finish_setup_form, follow=True)
self.assertEqual(save_page.status_code, 200)
self.assertContains(save_page, "Your profile has been updated.")
# We need to assert that logo is not clickable and links to manage your domain are not present
self.assertContains(save_page, "anage your domains", count=2)
self.assertNotContains(
save_page, "Before you can manage your domains, we need you to add contact information"
)
# Assert that modal does not appear on subsequent submits
self.assertNotContains(save_page, "domain registrants must maintain accurate contact information")
# Try to navigate back to the home page.
# This is the same as clicking the back button.
completed_setup_page = self.app.get(reverse("home"))
self.assertContains(completed_setup_page, "Manage your domain")
class UserProfileTests(TestWithUser, WebTest): class UserProfileTests(TestWithUser, WebTest):
"""A series of tests that target your profile functionality""" """A series of tests that target your profile functionality"""

View file

@ -102,6 +102,58 @@ class DomainRequestTests(TestWithUser, WebTest):
self.assertContains(type_page, "You cannot submit this request yet") self.assertContains(type_page, "You cannot submit this request yet")
def test_domain_request_into_acknowledgement_creates_new_request(self):
"""
We had to solve a bug where the wizard was creating 2 requests on first intro acknowledgement ('continue')
The wizard was also creating multiiple requests on 'continue' -> back button -> 'continue' etc.
This tests that the domain requests get created only when they should.
"""
# Get the intro page
self.app.get(reverse("home"))
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
intro_page = self.app.get(reverse("domain-request:"))
# Select the form
intro_form = intro_page.forms[0]
# Submit the form, this creates 1 Request
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
response = intro_form.submit(name="submit_button", value="intro_acknowledge")
# Landing on the next page used to create another 1 request
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
response.follow()
# Check if a new DomainRequest object has been created
domain_request_count = DomainRequest.objects.count()
self.assertEqual(domain_request_count, 1)
# Let's go back to intro and submit again, this should not create a new request
# This is the equivalent of a back button nav from step 1 to intro -> continue
intro_form = intro_page.forms[0]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
type_form = intro_form.submit(name="submit_button", value="intro_acknowledge")
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
type_form.follow()
domain_request_count = DomainRequest.objects.count()
self.assertEqual(domain_request_count, 1)
# Go home, which will reset the session flag for new request
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
self.app.get(reverse("home"))
# This time, clicking continue will create a new request
intro_form = intro_page.forms[0]
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
intro_result = intro_form.submit(name="submit_button", value="intro_acknowledge")
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
intro_result.follow()
domain_request_count = DomainRequest.objects.count()
self.assertEqual(domain_request_count, 2)
@boto3_mocking.patching @boto3_mocking.patching
def test_domain_request_form_submission(self): def test_domain_request_form_submission(self):
""" """

View file

@ -219,22 +219,23 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
self.storage["domain_request_id"] = kwargs["id"] self.storage["domain_request_id"] = kwargs["id"]
self.storage["step_history"] = self.db_check_for_unlocking_steps() self.storage["step_history"] = self.db_check_for_unlocking_steps()
# if accessing this class directly, redirect to the first step # if accessing this class directly, redirect to either to an acknowledgement
# in other words, if `DomainRequestWizard` is called as view # page or to the first step in the processes (if an edit rather than a new request);
# directly by some redirect or url handler, we'll send users # subclasseswill NOT be redirected. The purpose of this is to allow code to
# either to an acknowledgement page or to the first step in # send users "to the domain request wizard" without needing to know which view
# the processes (if an edit rather than a new request); subclasses # is first in the list of steps.
# will NOT be redirected. The purpose of this is to allow code to
# send users "to the domain request wizard" without needing to
# know which view is first in the list of steps.
context = self.get_context_data()
if self.__class__ == DomainRequestWizard: if self.__class__ == DomainRequestWizard:
if request.path_info == self.NEW_URL_NAME: if request.path_info == self.NEW_URL_NAME:
context = self.get_context_data() # Clear context so the prop getter won't create a request here.
return render(request, "domain_request_intro.html", context=context) # Creating a request will be handled in the post method for the
# intro page. Only TEMPORARY context needed is has_profile_flag
has_profile_flag = flag_is_active(self.request, "profile_feature")
context_stuff = {"has_profile_feature_flag": has_profile_flag}
return render(request, "domain_request_intro.html", context=context_stuff)
else: else:
return self.goto(self.steps.first) return self.goto(self.steps.first)
context = self.get_context_data()
self.steps.current = current_url self.steps.current = current_url
context["forms"] = self.get_forms() context["forms"] = self.get_forms()
@ -434,6 +435,10 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
return step_list return step_list
def goto(self, step): def goto(self, step):
if step == "generic_org_type":
# We need to avoid creating a new domain request if the user
# clicks the back button
self.request.session["new_request"] = False
self.steps.current = step self.steps.current = step
return redirect(reverse(f"{self.URL_NAMESPACE}:{step}")) return redirect(reverse(f"{self.URL_NAMESPACE}:{step}"))
@ -456,11 +461,17 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
# which button did the user press? # which button did the user press?
button: str = request.POST.get("submit_button", "") button: str = request.POST.get("submit_button", "")
# If a user hits the new request url directly
if "new_request" not in request.session:
request.session["new_request"] = True
# if user has acknowledged the intro message # if user has acknowledged the intro message
if button == "intro_acknowledge": if button == "intro_acknowledge":
if request.path_info == self.NEW_URL_NAME: if request.path_info == self.NEW_URL_NAME:
del self.storage
if self.request.session["new_request"] is True:
# This will trigger the domain_request getter into creating a new DomainRequest
del self.storage
return self.goto(self.steps.first) return self.goto(self.steps.first)
# if accessing this class directly, redirect to the first step # if accessing this class directly, redirect to the first step

View file

@ -10,4 +10,7 @@ def index(request):
# This is a django waffle flag which toggles features based off of the "flag" table # This is a django waffle flag which toggles features based off of the "flag" table
context["has_profile_feature_flag"] = flag_is_active(request, "profile_feature") context["has_profile_feature_flag"] = flag_is_active(request, "profile_feature")
# This controls the creation of a new domain request in the wizard
request.session["new_request"] = True
return render(request, "home.html", context) return render(request, "home.html", context)

View file

@ -15,6 +15,7 @@ from django.urls import NoReverseMatch, reverse
from registrar.models import ( from registrar.models import (
Contact, Contact,
) )
from registrar.models.user import User
from registrar.models.utility.generic_helper import replace_url_queryparams from registrar.models.utility.generic_helper import replace_url_queryparams
from registrar.views.utility.permission_views import UserProfilePermissionView from registrar.views.utility.permission_views import UserProfilePermissionView
from waffle.decorators import flag_is_active, waffle_flag from waffle.decorators import flag_is_active, waffle_flag
@ -41,6 +42,13 @@ class UserProfileView(UserProfilePermissionView, FormMixin):
form = self.form_class(instance=self.object) form = self.form_class(instance=self.object)
context = self.get_context_data(object=self.object, form=form) context = self.get_context_data(object=self.object, form=form)
if (
hasattr(self.user, "finished_setup")
and not self.user.finished_setup
and self.user.verification_type != User.VerificationTypeChoices.REGULAR
):
context["show_confirmation_modal"] = True
return_to_request = request.GET.get("return_to_request") return_to_request = request.GET.get("return_to_request")
if return_to_request: if return_to_request:
context["return_to_request"] = True context["return_to_request"] = True
@ -67,7 +75,11 @@ class UserProfileView(UserProfilePermissionView, FormMixin):
# The text for the back button on this page # The text for the back button on this page
context["profile_back_button_text"] = "Go to manage your domains" context["profile_back_button_text"] = "Go to manage your domains"
context["show_back_button"] = True context["show_back_button"] = False
if hasattr(self.user, "finished_setup") and self.user.finished_setup:
context["user_finished_setup"] = True
context["show_back_button"] = True
return context return context
@ -94,6 +106,12 @@ class UserProfileView(UserProfilePermissionView, FormMixin):
else: else:
return self.form_invalid(form) return self.form_invalid(form)
def form_invalid(self, form):
"""If the form is invalid, conditionally display an additional error."""
if hasattr(self.user, "finished_setup") and not self.user.finished_setup:
messages.error(self.request, "Before you can manage your domain, we need you to add contact information.")
return super().form_invalid(form)
def form_valid(self, form): def form_valid(self, form):
"""Handle successful and valid form submissions.""" """Handle successful and valid form submissions."""
form.save() form.save()
@ -105,9 +123,9 @@ class UserProfileView(UserProfilePermissionView, FormMixin):
def get_object(self, queryset=None): def get_object(self, queryset=None):
"""Override get_object to return the logged-in user's contact""" """Override get_object to return the logged-in user's contact"""
user = self.request.user # get the logged in user self.user = self.request.user # get the logged in user
if hasattr(user, "contact"): # Check if the user has a contact instance if hasattr(self.user, "contact"): # Check if the user has a contact instance
return user.contact return self.user.contact
return None return None