mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-25 20:18:38 +02:00
Merge pull request #2338 from cisagov/dk/2320-profile-redirect
Issue #2320: Redirect user to either home or domain request
This commit is contained in:
commit
05d4461a1f
9 changed files with 96 additions and 171 deletions
|
@ -10,6 +10,8 @@ from registrar.models.utility.domain_helper import DomainHelper
|
|||
class UserProfileForm(forms.ModelForm):
|
||||
"""Form for updating user profile."""
|
||||
|
||||
redirect = forms.CharField(widget=forms.HiddenInput(), required=False)
|
||||
|
||||
class Meta:
|
||||
model = Contact
|
||||
fields = ["first_name", "middle_name", "last_name", "title", "email", "phone"]
|
||||
|
|
|
@ -45,6 +45,7 @@ class CheckUserProfileMiddleware:
|
|||
self.setup_page = reverse("finish-user-profile-setup")
|
||||
self.profile_page = reverse("user-profile")
|
||||
self.logout_page = reverse("logout")
|
||||
|
||||
self.regular_excluded_pages = [
|
||||
self.setup_page,
|
||||
self.logout_page,
|
||||
|
@ -56,6 +57,14 @@ class CheckUserProfileMiddleware:
|
|||
"/admin",
|
||||
]
|
||||
|
||||
self.excluded_pages = {
|
||||
self.setup_page: self.regular_excluded_pages,
|
||||
self.profile_page: self.other_excluded_pages,
|
||||
}
|
||||
|
||||
def _get_excluded_pages(self, page):
|
||||
return self.excluded_pages.get(page, [])
|
||||
|
||||
def __call__(self, request):
|
||||
response = self.get_response(request)
|
||||
return response
|
||||
|
@ -72,16 +81,16 @@ class CheckUserProfileMiddleware:
|
|||
return None
|
||||
|
||||
if request.user.is_authenticated:
|
||||
profile_page = self.profile_page
|
||||
if request.user.verification_type == User.VerificationTypeChoices.REGULAR:
|
||||
profile_page = self.setup_page
|
||||
if hasattr(request.user, "finished_setup") and not request.user.finished_setup:
|
||||
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)
|
||||
return self._handle_user_setup_not_finished(request, profile_page)
|
||||
|
||||
# Continue processing the view
|
||||
return None
|
||||
|
||||
def _handle_regular_user_setup_not_finished(self, request):
|
||||
def _handle_user_setup_not_finished(self, request, profile_page):
|
||||
"""Redirects the given user to the finish setup page.
|
||||
|
||||
We set the "redirect" query param equal to where the user wants to go.
|
||||
|
@ -97,7 +106,7 @@ class CheckUserProfileMiddleware:
|
|||
custom_redirect = "domain-request:" if request.path == "/request/" else None
|
||||
|
||||
# Don't redirect on excluded pages (such as the setup page itself)
|
||||
if not any(request.path.startswith(page) for page in self.regular_excluded_pages):
|
||||
if not any(request.path.startswith(page) for page in self._get_excluded_pages(profile_page)):
|
||||
|
||||
# Preserve the original query parameters, and coerce them into a dict
|
||||
query_params = parse_qs(request.META["QUERY_STRING"])
|
||||
|
@ -107,23 +116,13 @@ class CheckUserProfileMiddleware:
|
|||
query_params["redirect"] = custom_redirect
|
||||
|
||||
# Add our new query param, while preserving old ones
|
||||
new_setup_page = replace_url_queryparams(self.setup_page, query_params) if query_params else self.setup_page
|
||||
new_setup_page = replace_url_queryparams(profile_page, query_params) if query_params else profile_page
|
||||
|
||||
return HttpResponseRedirect(new_setup_page)
|
||||
else:
|
||||
# Process the view as normal
|
||||
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
|
||||
|
||||
|
||||
class CheckPortfolioMiddleware:
|
||||
"""
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
completing your domain request might take around 15 minutes.</p>
|
||||
{% if has_profile_feature_flag %}
|
||||
<h2>How we’ll reach you</h2>
|
||||
<p>While reviewing your domain request, we may need to reach out with questions. We’ll also email you when we complete our review If the contact information below is not correct, visit <a href="{% url 'user-profile' %}?return_to_request=True" class="usa-link">your profile</a> to make updates.</p>
|
||||
<p>While reviewing your domain request, we may need to reach out with questions. We’ll also email you when we complete our review If the contact information below is not correct, visit <a href="{% url 'user-profile' %}?redirect=domain-request:" class="usa-link">your profile</a> to make updates.</p>
|
||||
{% include "includes/profile_information.html" with user=user%}
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
{# Disable the redirect #}
|
||||
{% block logo %}
|
||||
{% include "includes/gov_extended_logo.html" with logo_clickable=confirm_changes %}
|
||||
{% include "includes/gov_extended_logo.html" with logo_clickable=user_finished_setup %}
|
||||
{% endblock %}
|
||||
|
||||
{# Add the new form #}
|
||||
|
@ -16,5 +16,5 @@
|
|||
{% endblock content_bottom %}
|
||||
|
||||
{% block footer %}
|
||||
{% include "includes/footer.html" with show_manage_your_domains=confirm_changes %}
|
||||
{% include "includes/footer.html" with show_manage_your_domains=user_finished_setup %}
|
||||
{% endblock footer %}
|
||||
|
|
|
@ -33,6 +33,8 @@
|
|||
Your contact information
|
||||
</legend>
|
||||
|
||||
<input type="hidden" name="redirect" value="{{ form.initial.redirect }}">
|
||||
|
||||
{% with show_edit_button=True show_readonly=True group_classes="usa-form-editable usa-form-editable--no-border padding-top-2" %}
|
||||
{% input_with_errors form.full_name %}
|
||||
{% endwith %}
|
||||
|
@ -78,7 +80,7 @@
|
|||
<button type="submit" name="contact_setup_save_button" class="usa-button ">
|
||||
Save
|
||||
</button>
|
||||
{% if confirm_changes and going_to_specific_page %}
|
||||
{% if user_finished_setup and going_to_specific_page %}
|
||||
<button type="submit" name="contact_setup_submit_button" class="usa-button usa-button--outline">
|
||||
{{redirect_button_text }}
|
||||
</button>
|
||||
|
|
|
@ -19,6 +19,9 @@
|
|||
<form class="usa-form usa-form--large" method="post" novalidate>
|
||||
{% csrf_token %}
|
||||
|
||||
{# Include the hidden 'redirect' field #}
|
||||
<input type="hidden" name="redirect" value="{{ form.initial.redirect }}">
|
||||
|
||||
{% input_with_errors form.first_name %}
|
||||
|
||||
{% input_with_errors form.middle_name %}
|
||||
|
|
|
@ -25,19 +25,13 @@ Edit your User Profile |
|
|||
{% include "includes/form_errors.html" with form=form %}
|
||||
|
||||
{% if show_back_button %}
|
||||
<a href="{% if not return_to_request %}{% url 'home' %}{% else %}{% url 'domain-request:' %}{% endif %}" class="breadcrumb__back">
|
||||
<a href="{% url form.initial.redirect %}" class="breadcrumb__back">
|
||||
<svg class="usa-icon" aria-hidden="true" focusable="false" role="img">
|
||||
<use xlink:href="{% static 'img/sprite.svg' %}#arrow_back"></use>
|
||||
</svg>
|
||||
{% if not return_to_request %}
|
||||
<p class="margin-left-05 margin-top-0 margin-bottom-0 line-height-sans-1">
|
||||
{{ profile_back_button_text }}
|
||||
</p>
|
||||
{% else %}
|
||||
<p class="margin-left-05 margin-top-0 margin-bottom-0 line-height-sans-1">
|
||||
Go back to your domain request
|
||||
</p>
|
||||
{% endif %}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
|
|
|
@ -531,8 +531,11 @@ class FinishUserProfileTests(TestWithUser, WebTest):
|
|||
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()
|
||||
def _submit_form_webtest(self, form, follow=False, name=None):
|
||||
if name:
|
||||
page = form.submit(name=name)
|
||||
else:
|
||||
page = form.submit()
|
||||
self._set_session_cookie()
|
||||
return page.follow() if follow else page
|
||||
|
||||
|
@ -606,6 +609,15 @@ class FinishUserProfileTests(TestWithUser, WebTest):
|
|||
|
||||
self.assertEqual(completed_setup_page.status_code, 200)
|
||||
|
||||
finish_setup_form = completed_setup_page.form
|
||||
|
||||
# Submit the form using the specific submit button to execute the redirect
|
||||
completed_setup_page = self._submit_form_webtest(
|
||||
finish_setup_form, follow=True, name="contact_setup_submit_button"
|
||||
)
|
||||
self.assertEqual(completed_setup_page.status_code, 200)
|
||||
|
||||
# Assert that we are still on the
|
||||
# Assert that we're on the domain request page
|
||||
self.assertNotContains(completed_setup_page, "Finish setting up your profile")
|
||||
self.assertNotContains(completed_setup_page, "What contact information should we use to reach you?")
|
||||
|
@ -822,7 +834,7 @@ class UserProfileTests(TestWithUser, WebTest):
|
|||
"""tests user profile when profile_feature is on,
|
||||
and when they are redirected from the domain request page"""
|
||||
with override_flag("profile_feature", active=True):
|
||||
response = self.client.get("/user-profile?return_to_request=True")
|
||||
response = self.client.get("/user-profile?redirect=domain-request:")
|
||||
self.assertContains(response, "Your profile")
|
||||
self.assertContains(response, "Go back to your domain request")
|
||||
self.assertNotContains(response, "Back to manage your domains")
|
||||
|
|
|
@ -2,13 +2,10 @@
|
|||
|
||||
"""
|
||||
|
||||
from enum import Enum
|
||||
import logging
|
||||
from urllib.parse import parse_qs, unquote
|
||||
|
||||
from urllib.parse import quote
|
||||
|
||||
from django.contrib import messages
|
||||
from django.http import QueryDict
|
||||
from django.views.generic.edit import FormMixin
|
||||
from registrar.forms.user_profile import UserProfileForm, FinishSetupProfileForm
|
||||
from django.urls import NoReverseMatch, reverse
|
||||
|
@ -20,9 +17,6 @@ from registrar.models.utility.generic_helper import replace_url_queryparams
|
|||
from registrar.views.utility.permission_views import UserProfilePermissionView
|
||||
from waffle.decorators import flag_is_active, waffle_flag
|
||||
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.csrf import csrf_protect
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -34,13 +28,18 @@ class UserProfileView(UserProfilePermissionView, FormMixin):
|
|||
model = Contact
|
||||
template_name = "profile.html"
|
||||
form_class = UserProfileForm
|
||||
base_view_name = "user-profile"
|
||||
|
||||
def get(self, request, *args, **kwargs):
|
||||
"""Handle get requests by getting user's contact object and setting object
|
||||
and form to context before rendering."""
|
||||
self._refresh_session_and_object(request)
|
||||
form = self.form_class(instance=self.object)
|
||||
context = self.get_context_data(object=self.object, form=form)
|
||||
self.object = self.get_object()
|
||||
|
||||
# Get the redirect parameter from the query string
|
||||
redirect = request.GET.get("redirect", "home")
|
||||
|
||||
form = self.form_class(instance=self.object, initial={"redirect": redirect})
|
||||
context = self.get_context_data(object=self.object, form=form, redirect=redirect)
|
||||
|
||||
if (
|
||||
hasattr(self.user, "finished_setup")
|
||||
|
@ -49,22 +48,10 @@ class UserProfileView(UserProfilePermissionView, FormMixin):
|
|||
):
|
||||
context["show_confirmation_modal"] = True
|
||||
|
||||
return_to_request = request.GET.get("return_to_request")
|
||||
if return_to_request:
|
||||
context["return_to_request"] = True
|
||||
|
||||
return self.render_to_response(context)
|
||||
|
||||
def _refresh_session_and_object(self, request):
|
||||
"""Sets the current session to self.session and the current object to self.object"""
|
||||
self.session = request.session
|
||||
self.object = self.get_object()
|
||||
|
||||
@waffle_flag("profile_feature") # type: ignore
|
||||
def dispatch(self, request, *args, **kwargs): # type: ignore
|
||||
# Store the original queryparams to persist them
|
||||
query_params = request.META["QUERY_STRING"]
|
||||
request.session["query_params"] = query_params
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
@ -73,10 +60,14 @@ class UserProfileView(UserProfilePermissionView, FormMixin):
|
|||
# This is a django waffle flag which toggles features based off of the "flag" table
|
||||
context["has_profile_feature_flag"] = flag_is_active(self.request, "profile_feature")
|
||||
|
||||
# The text for the back button on this page
|
||||
context["profile_back_button_text"] = "Go to manage your domains"
|
||||
context["show_back_button"] = False
|
||||
# Set the profile_back_button_text based on the redirect parameter
|
||||
if kwargs.get("redirect") == "domain-request:":
|
||||
context["profile_back_button_text"] = "Go back to your domain request"
|
||||
else:
|
||||
context["profile_back_button_text"] = "Go to manage your domains"
|
||||
|
||||
# Show back button conditional on user having finished setup
|
||||
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
|
||||
|
@ -84,21 +75,29 @@ class UserProfileView(UserProfilePermissionView, FormMixin):
|
|||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
"""Redirect to the user's profile page."""
|
||||
"""Redirect to the user's profile page with updated query parameters."""
|
||||
|
||||
query_params = {}
|
||||
if "query_params" in self.session:
|
||||
params = unquote(self.session["query_params"])
|
||||
query_params = parse_qs(params)
|
||||
# Get the redirect parameter from the form submission
|
||||
redirect_param = self.request.POST.get("redirect", None)
|
||||
|
||||
# Preserve queryparams and add them back to the url
|
||||
base_url = reverse("user-profile")
|
||||
new_redirect = replace_url_queryparams(base_url, query_params, convert_list_to_csv=True)
|
||||
return new_redirect
|
||||
# Initialize QueryDict with existing query parameters from current request
|
||||
query_params = QueryDict(mutable=True)
|
||||
query_params.update(self.request.GET)
|
||||
|
||||
# Update query parameters with the 'redirect' value from form submission
|
||||
if redirect_param and redirect_param != "home":
|
||||
query_params["redirect"] = redirect_param
|
||||
|
||||
# Generate the URL with updated query parameters
|
||||
base_url = reverse(self.base_view_name)
|
||||
|
||||
# Generate the full url from the given query params
|
||||
full_url = replace_url_queryparams(base_url, query_params)
|
||||
return full_url
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
"""Handle post requests (form submissions)"""
|
||||
self._refresh_session_and_object(request)
|
||||
self.object = self.get_object()
|
||||
form = self.form_class(request.POST, instance=self.object)
|
||||
|
||||
if form.is_valid():
|
||||
|
@ -133,139 +132,53 @@ class FinishProfileSetupView(UserProfileView):
|
|||
"""This view forces the user into providing additional details that
|
||||
we may have missed from Login.gov"""
|
||||
|
||||
class RedirectType(Enum):
|
||||
"""
|
||||
Enums for each type of redirection. Enforces behaviour on `get_redirect_url()`.
|
||||
|
||||
- HOME: We want to redirect to reverse("home")
|
||||
- BACK_TO_SELF: We want to redirect back to this page
|
||||
- TO_SPECIFIC_PAGE: We want to redirect to the page specified in the queryparam "redirect"
|
||||
- COMPLETE_SETUP: Indicates that we want to navigate BACK_TO_SELF, but the subsequent
|
||||
redirect after the next POST should be either HOME or TO_SPECIFIC_PAGE
|
||||
"""
|
||||
|
||||
HOME = "home"
|
||||
TO_SPECIFIC_PAGE = "domain_request"
|
||||
BACK_TO_SELF = "back_to_self"
|
||||
COMPLETE_SETUP = "complete_setup"
|
||||
|
||||
@classmethod
|
||||
def get_all_redirect_types(cls) -> list[str]:
|
||||
"""Returns the value of every redirect type defined in this enum."""
|
||||
return [r.value for r in cls]
|
||||
|
||||
template_name = "finish_profile_setup.html"
|
||||
form_class = FinishSetupProfileForm
|
||||
model = Contact
|
||||
|
||||
all_redirect_types = RedirectType.get_all_redirect_types()
|
||||
redirect_type: RedirectType
|
||||
base_view_name = "finish-user-profile-setup"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
|
||||
"""Extend get_context_data to include has_profile_feature_flag"""
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
# Hide the back button by default
|
||||
# Show back button conditional on user having finished setup
|
||||
context["show_back_button"] = False
|
||||
|
||||
if self.redirect_type == self.RedirectType.COMPLETE_SETUP:
|
||||
context["confirm_changes"] = True
|
||||
|
||||
if "redirect_viewname" not in self.session:
|
||||
if hasattr(self.user, "finished_setup") and self.user.finished_setup:
|
||||
if kwargs.get("redirect") == "home":
|
||||
context["show_back_button"] = True
|
||||
else:
|
||||
context["going_to_specific_page"] = True
|
||||
context["redirect_button_text"] = "Continue to your request"
|
||||
|
||||
return context
|
||||
|
||||
@method_decorator(csrf_protect)
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
"""
|
||||
Handles dispatching of the view, applying CSRF protection and checking the 'profile_feature' flag.
|
||||
|
||||
This method sets the redirect type based on the 'redirect' query parameter,
|
||||
defaulting to BACK_TO_SELF if not provided.
|
||||
It updates the session with the redirect view name if the redirect type is TO_SPECIFIC_PAGE.
|
||||
|
||||
Returns:
|
||||
HttpResponse: The response generated by the parent class's dispatch method.
|
||||
"""
|
||||
|
||||
# Update redirect type based on the query parameter if present
|
||||
default_redirect_value = self.RedirectType.BACK_TO_SELF.value
|
||||
redirect_value = request.GET.get("redirect", default_redirect_value)
|
||||
|
||||
if redirect_value in self.all_redirect_types:
|
||||
# If the redirect value is a preexisting value in our enum, set it to that.
|
||||
self.redirect_type = self.RedirectType(redirect_value)
|
||||
else:
|
||||
# If the redirect type is undefined, then we assume that we are specifying a particular page to redirect to.
|
||||
self.redirect_type = self.RedirectType.TO_SPECIFIC_PAGE
|
||||
|
||||
# Store the page that we want to redirect to for later use
|
||||
request.session["redirect_viewname"] = str(redirect_value)
|
||||
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
"""Form submission posts to this view."""
|
||||
self._refresh_session_and_object(request)
|
||||
self.object = self.get_object()
|
||||
form = self.form_class(request.POST, instance=self.object)
|
||||
|
||||
# Get the current form and validate it
|
||||
if form.is_valid():
|
||||
self.redirect_page = False
|
||||
if "contact_setup_save_button" in request.POST:
|
||||
# Logic for when the 'Save' button is clicked
|
||||
self.redirect_type = self.RedirectType.COMPLETE_SETUP
|
||||
# Logic for when the 'Save' button is clicked, which indicates
|
||||
# user should stay on this page
|
||||
self.redirect_page = False
|
||||
elif "contact_setup_submit_button" in request.POST:
|
||||
specific_redirect = "redirect_viewname" in self.session
|
||||
self.redirect_type = self.RedirectType.TO_SPECIFIC_PAGE if specific_redirect else self.RedirectType.HOME
|
||||
# Logic for when the other button is clicked, which indicates
|
||||
# the user should be taken to the redirect page
|
||||
self.redirect_page = True
|
||||
return self.form_valid(form)
|
||||
else:
|
||||
return self.form_invalid(form)
|
||||
|
||||
def get_success_url(self):
|
||||
"""Redirect to the nameservers page for the domain."""
|
||||
return self.get_redirect_url()
|
||||
|
||||
def get_redirect_url(self):
|
||||
"""
|
||||
Returns a URL string based on the current value of self.redirect_type.
|
||||
|
||||
Depending on self.redirect_type, constructs a base URL and appends a
|
||||
'redirect' query parameter. Handles different redirection types such as
|
||||
HOME, BACK_TO_SELF, COMPLETE_SETUP, and TO_SPECIFIC_PAGE.
|
||||
|
||||
Returns:
|
||||
str: The full URL with the appropriate query parameters.
|
||||
"""
|
||||
|
||||
# These redirect types redirect to the same page
|
||||
self_redirect = [self.RedirectType.BACK_TO_SELF, self.RedirectType.COMPLETE_SETUP]
|
||||
|
||||
# Maps the redirect type to a URL
|
||||
base_url = ""
|
||||
"""Redirect to the redirect page, or redirect to the current page"""
|
||||
try:
|
||||
if self.redirect_type in self_redirect:
|
||||
base_url = reverse("finish-user-profile-setup")
|
||||
elif self.redirect_type == self.RedirectType.TO_SPECIFIC_PAGE:
|
||||
# We only allow this session value to use viewnames,
|
||||
# because this restricts what can be redirected to.
|
||||
desired_view = self.session["redirect_viewname"]
|
||||
self.session.pop("redirect_viewname")
|
||||
base_url = reverse(desired_view)
|
||||
else:
|
||||
base_url = reverse("home")
|
||||
# Get the redirect parameter from the form submission
|
||||
redirect_param = self.request.POST.get("redirect", None)
|
||||
if self.redirect_page and redirect_param:
|
||||
return reverse(redirect_param)
|
||||
except NoReverseMatch as err:
|
||||
logger.error(f"get_redirect_url -> Could not find the specified page. Err: {err}")
|
||||
|
||||
query_params = {}
|
||||
|
||||
# Quote cleans up the value so that it can be used in a url
|
||||
if self.redirect_type and self.redirect_type.value:
|
||||
query_params["redirect"] = quote(self.redirect_type.value)
|
||||
|
||||
# Generate the full url from the given query params
|
||||
full_url = replace_url_queryparams(base_url, query_params)
|
||||
return full_url
|
||||
return super().get_success_url()
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue