mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-25 20:18:38 +02:00
Replace form with modelform
Pull in content from related PR and refactor around it
This commit is contained in:
parent
d202d2601f
commit
9a6ccc1c44
10 changed files with 230 additions and 363 deletions
|
@ -873,35 +873,16 @@ function hideDeletedForms() {
|
||||||
let fieldId = getInputFieldId(fieldName)
|
let fieldId = getInputFieldId(fieldName)
|
||||||
let inputField = document.querySelector(fieldId);
|
let inputField = document.querySelector(fieldId);
|
||||||
|
|
||||||
let nameFieldset = document.querySelector("#contact-full-name-fieldset");
|
let nameFieldset = document.querySelector("#profile-name-fieldset");
|
||||||
if (nameFieldset){
|
if (nameFieldset){
|
||||||
nameFieldset.classList.remove("display-none");
|
nameFieldset.classList.remove("display-none");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputField) {
|
if (inputField) {
|
||||||
let readonlyId = getReadonlyFieldId(fieldName)
|
// Remove the "full_name" field
|
||||||
let readonlyField = document.querySelector(readonlyId)
|
inputFieldParentDiv = inputField.closest("div");
|
||||||
if (readonlyField) {
|
if (inputFieldParentDiv) {
|
||||||
// Update the <use> element's xlink:href attribute
|
inputFieldParentDiv.remove();
|
||||||
let useElement = readonlyField.querySelector("use");
|
|
||||||
if (useElement) {
|
|
||||||
let currentHref = useElement.getAttribute("xlink:href");
|
|
||||||
let parts = currentHref.split("#");
|
|
||||||
|
|
||||||
// Update the icon reference to the info icon
|
|
||||||
if (parts.length > 1) {
|
|
||||||
parts[1] = "info_outline";
|
|
||||||
useElement.setAttribute("xlink:href", parts.join("#"));
|
|
||||||
|
|
||||||
// Change the color to => $dhs-dark-gray-60
|
|
||||||
useElement.closest('svg').style.fill = '#444547';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let parentDiv = readonlyField.closest("div");
|
|
||||||
if (parentDiv) {
|
|
||||||
parentDiv.classList.toggle("overlapped-full-name-field");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,23 +10,8 @@ fieldset:not(:first-child) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldset.registrar-fieldset__contact {
|
fieldset.registrar-fieldset__contact {
|
||||||
border-width: 2px;
|
// This fieldset is for SR purposes only
|
||||||
border-left: none;
|
border: 0;
|
||||||
border-right: none;
|
margin: 0;
|
||||||
border-bottom: none;
|
padding: 0;
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 800px) {
|
|
||||||
fieldset.registrar-fieldset__contact {
|
|
||||||
margin-top: 28px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 800px){
|
|
||||||
fieldset.registrar-fieldset__contact {
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -103,8 +103,8 @@ urlpatterns = [
|
||||||
path(
|
path(
|
||||||
# We embed the current user ID here, but we have a permission check
|
# We embed the current user ID here, but we have a permission check
|
||||||
# that ensures the user is who they say they are.
|
# that ensures the user is who they say they are.
|
||||||
"finish-user-setup/<int:pk>",
|
"finish-profile-setup/<int:pk>",
|
||||||
views.FinishUserSetupView.as_view(),
|
views.FinishProfileSetupView.as_view(),
|
||||||
name="finish-user-profile-setup",
|
name="finish-user-profile-setup",
|
||||||
),
|
),
|
||||||
path(
|
path(
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
from django import forms
|
|
||||||
from phonenumber_field.formfields import PhoneNumberField # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
class FinishUserSetupForm(forms.Form):
|
|
||||||
"""Form for adding or editing user information"""
|
|
||||||
|
|
||||||
def clean(self):
|
|
||||||
cleaned_data = super().clean()
|
|
||||||
# Remove the full name property
|
|
||||||
if "full_name" in cleaned_data:
|
|
||||||
# Delete the full name element as its purely decorative.
|
|
||||||
# We include it as a normal Charfield for all the advantages
|
|
||||||
# and utility that it brings, but we're playing pretend.
|
|
||||||
del cleaned_data["full_name"]
|
|
||||||
return cleaned_data
|
|
||||||
|
|
||||||
full_name = forms.CharField(
|
|
||||||
label="Full name",
|
|
||||||
error_messages={"required": "Enter your full name"},
|
|
||||||
)
|
|
||||||
first_name = forms.CharField(
|
|
||||||
label="First name / given name",
|
|
||||||
error_messages={"required": "Enter your first name / given name."},
|
|
||||||
)
|
|
||||||
middle_name = forms.CharField(
|
|
||||||
required=False,
|
|
||||||
label="Middle name (optional)",
|
|
||||||
)
|
|
||||||
last_name = forms.CharField(
|
|
||||||
label="Last name / family name",
|
|
||||||
error_messages={"required": "Enter your last name / family name."},
|
|
||||||
)
|
|
||||||
title = forms.CharField(
|
|
||||||
label="Title or role in your organization",
|
|
||||||
error_messages={
|
|
||||||
"required": ("Enter your title or role in your organization (e.g., Chief Information Officer).")
|
|
||||||
},
|
|
||||||
)
|
|
||||||
email = forms.EmailField(
|
|
||||||
label="Organization email",
|
|
||||||
required=False,
|
|
||||||
max_length=None,
|
|
||||||
)
|
|
||||||
phone = PhoneNumberField(
|
|
||||||
label="Phone",
|
|
||||||
error_messages={"invalid": "Enter a valid 10-digit phone number.", "required": "Enter your phone number."},
|
|
||||||
)
|
|
|
@ -55,6 +55,34 @@ class UserProfileForm(forms.ModelForm):
|
||||||
"required": "Enter your email address in the required format, like name@example.com."
|
"required": "Enter your email address in the required format, like name@example.com."
|
||||||
}
|
}
|
||||||
self.fields["phone"].error_messages["required"] = "Enter your phone number."
|
self.fields["phone"].error_messages["required"] = "Enter your phone number."
|
||||||
self.domainInfo = None
|
|
||||||
|
|
||||||
DomainHelper.disable_field(self.fields["email"], disable_required=True)
|
DomainHelper.disable_field(self.fields["email"], disable_required=True)
|
||||||
|
|
||||||
|
|
||||||
|
class FinishSetupProfileForm(UserProfileForm):
|
||||||
|
"""Form for updating user profile."""
|
||||||
|
|
||||||
|
full_name = forms.CharField(required=True, label="Full name")
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
cleaned_data = super().clean()
|
||||||
|
# Remove the full name property
|
||||||
|
if "full_name" in cleaned_data:
|
||||||
|
# Delete the full name element as its purely decorative.
|
||||||
|
# We include it as a normal Charfield for all the advantages
|
||||||
|
# and utility that it brings, but we're playing pretend.
|
||||||
|
del cleaned_data["full_name"]
|
||||||
|
return cleaned_data
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"""Override the inerited __init__ method to update the fields."""
|
||||||
|
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
# Set custom form label for email
|
||||||
|
self.fields["email"].label = "Organization email"
|
||||||
|
self.fields["title"].label = "Title or role in your organization"
|
||||||
|
|
||||||
|
# Define the "full_name" value
|
||||||
|
if self.instance and hasattr(self.instance, 'full_name'):
|
||||||
|
self.fields["full_name"].initial = self.instance.get_formatted_name()
|
||||||
|
|
|
@ -102,13 +102,6 @@ class Contact(TimeStampedModel):
|
||||||
names = [n for n in [self.first_name, self.middle_name, self.last_name] if n]
|
names = [n for n in [self.first_name, self.middle_name, self.last_name] if n]
|
||||||
return " ".join(names) if names else "Unknown"
|
return " ".join(names) if names else "Unknown"
|
||||||
|
|
||||||
@property
|
|
||||||
def full_name(self):
|
|
||||||
"""
|
|
||||||
Returns the full name (first_name, middle_name, last_name) of this contact.
|
|
||||||
"""
|
|
||||||
return self.get_formatted_name()
|
|
||||||
|
|
||||||
def has_contact_info(self):
|
def has_contact_info(self):
|
||||||
return bool(self.title or self.email or self.phone)
|
return bool(self.title or self.email or self.phone)
|
||||||
|
|
||||||
|
|
|
@ -62,25 +62,17 @@
|
||||||
<legend class="usa-sr-only">
|
<legend class="usa-sr-only">
|
||||||
Your contact information
|
Your contact information
|
||||||
</legend>
|
</legend>
|
||||||
|
|
||||||
{# TODO: if an error is thrown here or edit clicked, show first last and middle fields #}
|
|
||||||
{# Also todo: consolidate all of the scattered classes into this usa form one #}
|
|
||||||
{% with show_edit_button=True show_readonly=True group_classes="usa-form-readonly padding-top-2" %}
|
{% with show_edit_button=True show_readonly=True group_classes="usa-form-readonly padding-top-2" %}
|
||||||
{% input_with_errors form.full_name %}
|
{% input_with_errors form.full_name %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
<fieldset id="contact-full-name-fieldset" class="registrar-fieldset__contact display-none">
|
<fieldset id="profile-name-fieldset" class="registrar-fieldset__contact display-none">
|
||||||
{% with show_edit_button=True show_readonly=True group_classes="usa-form-readonly" %}
|
{% input_with_errors form.first_name %}
|
||||||
{% input_with_errors form.first_name %}
|
|
||||||
{% endwith %}
|
|
||||||
|
|
||||||
{% with show_edit_button=True show_readonly=True group_classes="usa-form-readonly padding-top-2" %}
|
{% input_with_errors form.middle_name %}
|
||||||
{% input_with_errors form.middle_name %}
|
|
||||||
{% endwith %}
|
|
||||||
|
|
||||||
{% with show_edit_button=True show_readonly=True group_classes="usa-form-readonly padding-top-2"%}
|
{% input_with_errors form.last_name %}
|
||||||
{% input_with_errors form.last_name %}
|
|
||||||
{% endwith %}
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
{# This field doesn't have the readonly button but it has common design elements from it #}
|
{# This field doesn't have the readonly button but it has common design elements from it #}
|
|
@ -14,9 +14,6 @@ from .domain import (
|
||||||
DomainInvitationDeleteView,
|
DomainInvitationDeleteView,
|
||||||
DomainDeleteUserView,
|
DomainDeleteUserView,
|
||||||
)
|
)
|
||||||
from .user_profile import UserProfileView
|
from .user_profile import UserProfileView, FinishProfileSetupView
|
||||||
from .finish_user_setup import (
|
|
||||||
FinishUserSetupView,
|
|
||||||
)
|
|
||||||
from .health import *
|
from .health import *
|
||||||
from .index import *
|
from .index import *
|
||||||
|
|
|
@ -1,241 +0,0 @@
|
||||||
from enum import Enum
|
|
||||||
from waffle.decorators import waffle_flag
|
|
||||||
from urllib.parse import quote
|
|
||||||
from django.urls import NoReverseMatch, reverse
|
|
||||||
from registrar.forms.finish_user_setup import FinishUserSetupForm
|
|
||||||
from django.contrib.messages.views import SuccessMessageMixin
|
|
||||||
from registrar.models.contact import Contact
|
|
||||||
from registrar.templatetags.url_helpers import public_site_url
|
|
||||||
from registrar.views.utility.permission_views import ContactPermissionView
|
|
||||||
from django.views.generic.edit import FormMixin
|
|
||||||
from registrar.models.utility.generic_helper import replace_url_queryparams, to_database, from_database
|
|
||||||
from django.utils.safestring import mark_safe
|
|
||||||
|
|
||||||
from django.utils.decorators import method_decorator
|
|
||||||
from django.views.decorators.csrf import csrf_protect
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class BaseContactView(SuccessMessageMixin, ContactPermissionView):
|
|
||||||
|
|
||||||
def get_success_message(self, cleaned_data):
|
|
||||||
"""Content of the returned success message"""
|
|
||||||
return "Contact updated successfully."
|
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
|
||||||
self._update_object_and_session(request)
|
|
||||||
context = self.get_context_data(object=self.object)
|
|
||||||
return self.render_to_response(context)
|
|
||||||
|
|
||||||
def _update_object_and_session(self, request):
|
|
||||||
self.session = request.session
|
|
||||||
|
|
||||||
contact_pk = "contact:" + str(self.kwargs.get("pk"))
|
|
||||||
cached_contact = self.session.get(contact_pk)
|
|
||||||
|
|
||||||
if cached_contact:
|
|
||||||
self.object = cached_contact
|
|
||||||
else:
|
|
||||||
self.object = self.get_object()
|
|
||||||
|
|
||||||
self._refresh_session()
|
|
||||||
|
|
||||||
def _refresh_session(self):
|
|
||||||
"""
|
|
||||||
Set contact pk in the session cache
|
|
||||||
"""
|
|
||||||
contact_pk = "contact:" + str(self.kwargs.get("pk"))
|
|
||||||
self.session[contact_pk] = self.object
|
|
||||||
|
|
||||||
|
|
||||||
class ContactFormBaseView(BaseContactView, FormMixin):
|
|
||||||
"""Adds a FormMixin to BaseContactView, and handles post"""
|
|
||||||
|
|
||||||
def form_invalid(self, form):
|
|
||||||
# updates session cache with contact
|
|
||||||
self._refresh_session()
|
|
||||||
|
|
||||||
# superclass has the redirect
|
|
||||||
return super().form_invalid(form)
|
|
||||||
|
|
||||||
|
|
||||||
class FinishUserSetupView(ContactFormBaseView):
|
|
||||||
"""This view forces the user into providing additional details that
|
|
||||||
we may have missed from Login.gov"""
|
|
||||||
|
|
||||||
template_name = "finish_contact_setup.html"
|
|
||||||
form_class = FinishUserSetupForm
|
|
||||||
model = Contact
|
|
||||||
|
|
||||||
redirect_type = None
|
|
||||||
|
|
||||||
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 reverse("finish-user-profile-setup")
|
|
||||||
- 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"
|
|
||||||
BACK_TO_SELF = "back_to_self"
|
|
||||||
COMPLETE_SETUP = "complete_setup"
|
|
||||||
TO_SPECIFIC_PAGE = "domain_request"
|
|
||||||
|
|
||||||
def get_initial(self):
|
|
||||||
"""The initial value for the form (which is a formset here)."""
|
|
||||||
db_object = from_database(form_class=self.form_class, obj=self.object)
|
|
||||||
return db_object
|
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
|
||||||
|
|
||||||
context = super().get_context_data(**kwargs)
|
|
||||||
context["email_sublabel_text"] = self._email_sublabel_text()
|
|
||||||
|
|
||||||
if self.redirect_type == self.RedirectType.COMPLETE_SETUP:
|
|
||||||
context["confirm_changes"] = True
|
|
||||||
|
|
||||||
return context
|
|
||||||
|
|
||||||
def _email_sublabel_text(self):
|
|
||||||
"""Returns the lengthy sublabel for the email field"""
|
|
||||||
help_url = public_site_url("help/account-management/#get-help-with-login.gov")
|
|
||||||
return mark_safe(
|
|
||||||
"We recommend using your work email for your .gov account. "
|
|
||||||
"If the wrong email is displayed below, you’ll need to update your Login.gov account "
|
|
||||||
f'and log back in. <a class="usa-link" href={help_url}>Get help with your Login.gov account.</a>'
|
|
||||||
) # nosec
|
|
||||||
|
|
||||||
|
|
||||||
def get_success_message(self, cleaned_data):
|
|
||||||
"""Content of the returned success message"""
|
|
||||||
return "Your profile has been successfully updated."
|
|
||||||
|
|
||||||
@waffle_flag("profile_feature")
|
|
||||||
@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
|
|
||||||
redirect_type = request.GET.get("redirect", self.RedirectType.BACK_TO_SELF)
|
|
||||||
|
|
||||||
all_redirect_types = [r.value for r in self.RedirectType]
|
|
||||||
if redirect_type in all_redirect_types:
|
|
||||||
self.redirect_type = self.RedirectType(redirect_type)
|
|
||||||
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_type)
|
|
||||||
|
|
||||||
return super().dispatch(request, *args, **kwargs)
|
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
|
||||||
"""Form submission posts to this view.
|
|
||||||
|
|
||||||
This post method harmonizes using BaseContactView and FormMixin
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Set the current object in cache
|
|
||||||
self._update_object_and_session(request)
|
|
||||||
|
|
||||||
form = self.get_form()
|
|
||||||
|
|
||||||
# Get the current form and validate it
|
|
||||||
if form.is_valid():
|
|
||||||
|
|
||||||
completed_states = [self.RedirectType.TO_SPECIFIC_PAGE, self.RedirectType.HOME]
|
|
||||||
if self.redirect_type in completed_states:
|
|
||||||
self.request.user.finished_setup = True
|
|
||||||
self.request.user.save()
|
|
||||||
|
|
||||||
if "contact_setup_save_button" in request.POST:
|
|
||||||
# Logic for when the 'Save' button is clicked
|
|
||||||
self.redirect_type = self.RedirectType.COMPLETE_SETUP
|
|
||||||
elif "contact_setup_submit_button" in request.POST:
|
|
||||||
if "redirect_viewname" in self.session:
|
|
||||||
self.redirect_type = self.RedirectType.TO_SPECIFIC_PAGE
|
|
||||||
else:
|
|
||||||
self.redirect_type = self.RedirectType.HOME
|
|
||||||
|
|
||||||
return self.form_valid(form)
|
|
||||||
else:
|
|
||||||
return self.form_invalid(form)
|
|
||||||
|
|
||||||
def form_valid(self, form):
|
|
||||||
"""Saves the current contact to the database, and if the user is complete
|
|
||||||
with their setup, then we mark user.finished_setup to True."""
|
|
||||||
completed_states = [self.RedirectType.TO_SPECIFIC_PAGE, self.RedirectType.HOME]
|
|
||||||
if self.redirect_type in completed_states:
|
|
||||||
self.request.user.finished_setup = True
|
|
||||||
self.request.user.save()
|
|
||||||
|
|
||||||
to_database(form=form, obj=self.object)
|
|
||||||
self._refresh_session()
|
|
||||||
|
|
||||||
return super().form_valid(form)
|
|
||||||
|
|
||||||
def get_success_url(self):
|
|
||||||
"""Redirect to the nameservers page for the domain."""
|
|
||||||
redirect_url = self.get_redirect_url()
|
|
||||||
return 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 = ""
|
|
||||||
try:
|
|
||||||
if self.redirect_type in self_redirect:
|
|
||||||
base_url = reverse("finish-user-profile-setup", kwargs={"pk": self.object.pk})
|
|
||||||
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"]
|
|
||||||
base_url = reverse(desired_view)
|
|
||||||
else:
|
|
||||||
base_url = reverse("home")
|
|
||||||
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
|
|
||||||
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
|
|
||||||
|
|
|
@ -2,18 +2,25 @@
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from enum import Enum
|
||||||
import logging
|
import logging
|
||||||
|
from urllib.parse import quote
|
||||||
|
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.views.generic.edit import FormMixin
|
from django.views.generic.edit import FormMixin
|
||||||
from registrar.forms.user_profile import UserProfileForm
|
from registrar.forms.user_profile import UserProfileForm, FinishSetupProfileForm
|
||||||
from django.urls import reverse
|
from django.urls import NoReverseMatch, reverse
|
||||||
from registrar.models import (
|
from registrar.models import (
|
||||||
Contact,
|
Contact,
|
||||||
)
|
)
|
||||||
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
|
||||||
|
|
||||||
|
from registrar.templatetags.url_helpers import public_site_url
|
||||||
|
from registrar.models.utility.generic_helper import replace_url_queryparams
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
from django.views.decorators.csrf import csrf_protect
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -30,11 +37,16 @@ class UserProfileView(UserProfilePermissionView, FormMixin):
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
"""Handle get requests by getting user's contact object and setting object
|
"""Handle get requests by getting user's contact object and setting object
|
||||||
and form to context before rendering."""
|
and form to context before rendering."""
|
||||||
self.object = self.get_object()
|
self._refresh_session_and_object(request)
|
||||||
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)
|
||||||
return self.render_to_response(context)
|
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
|
@waffle_flag("profile_feature") # type: ignore
|
||||||
def dispatch(self, request, *args, **kwargs): # type: ignore
|
def dispatch(self, request, *args, **kwargs): # type: ignore
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
@ -52,7 +64,7 @@ class UserProfileView(UserProfilePermissionView, FormMixin):
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
"""Handle post requests (form submissions)"""
|
"""Handle post requests (form submissions)"""
|
||||||
self.object = self.get_object()
|
self._refresh_session_and_object(request)
|
||||||
form = self.form_class(request.POST, instance=self.object)
|
form = self.form_class(request.POST, instance=self.object)
|
||||||
|
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
|
@ -75,3 +87,171 @@ class UserProfileView(UserProfilePermissionView, FormMixin):
|
||||||
if hasattr(user, "contact"): # Check if the user has a contact instance
|
if hasattr(user, "contact"): # Check if the user has a contact instance
|
||||||
return user.contact
|
return user.contact
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class FinishProfileSetupView(UserProfileView):
|
||||||
|
"""This view forces the user into providing additional details that
|
||||||
|
we may have missed from Login.gov"""
|
||||||
|
|
||||||
|
template_name = "finish_profile_setup.html"
|
||||||
|
form_class = FinishSetupProfileForm
|
||||||
|
model = Contact
|
||||||
|
|
||||||
|
redirect_type = None
|
||||||
|
|
||||||
|
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 reverse("finish-user-profile-setup")
|
||||||
|
- 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"
|
||||||
|
BACK_TO_SELF = "back_to_self"
|
||||||
|
COMPLETE_SETUP = "complete_setup"
|
||||||
|
TO_SPECIFIC_PAGE = "domain_request"
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
context["email_sublabel_text"] = self._email_sublabel_text()
|
||||||
|
|
||||||
|
if self.redirect_type == self.RedirectType.COMPLETE_SETUP:
|
||||||
|
context["confirm_changes"] = True
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
def _email_sublabel_text(self):
|
||||||
|
"""Returns the lengthy sublabel for the email field"""
|
||||||
|
help_url = public_site_url("help/account-management/#get-help-with-login.gov")
|
||||||
|
return mark_safe(
|
||||||
|
"We recommend using your work email for your .gov account. "
|
||||||
|
"If the wrong email is displayed below, you’ll need to update your Login.gov account "
|
||||||
|
f'and log back in. <a class="usa-link" href={help_url}>Get help with your Login.gov account.</a>'
|
||||||
|
) # nosec
|
||||||
|
|
||||||
|
|
||||||
|
def get_success_message(self, cleaned_data):
|
||||||
|
"""Content of the returned success message"""
|
||||||
|
return "Your profile has been successfully updated."
|
||||||
|
|
||||||
|
@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
|
||||||
|
redirect_type = request.GET.get("redirect", self.RedirectType.BACK_TO_SELF)
|
||||||
|
|
||||||
|
all_redirect_types = [r.value for r in self.RedirectType]
|
||||||
|
if redirect_type in all_redirect_types:
|
||||||
|
self.redirect_type = self.RedirectType(redirect_type)
|
||||||
|
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_type)
|
||||||
|
|
||||||
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
"""Form submission posts to this view.
|
||||||
|
|
||||||
|
This post method harmonizes using BaseContactView and FormMixin
|
||||||
|
"""
|
||||||
|
self._refresh_session_and_object(request)
|
||||||
|
form = self.form_class(request.POST, instance=self.object)
|
||||||
|
|
||||||
|
# Get the current form and validate it
|
||||||
|
if form.is_valid():
|
||||||
|
|
||||||
|
completed_states = [self.RedirectType.TO_SPECIFIC_PAGE, self.RedirectType.HOME]
|
||||||
|
if self.redirect_type in completed_states:
|
||||||
|
self.request.user.finished_setup = True
|
||||||
|
self.request.user.save()
|
||||||
|
|
||||||
|
if "contact_setup_save_button" in request.POST:
|
||||||
|
# Logic for when the 'Save' button is clicked
|
||||||
|
self.redirect_type = self.RedirectType.COMPLETE_SETUP
|
||||||
|
elif "contact_setup_submit_button" in request.POST:
|
||||||
|
if "redirect_viewname" in self.session:
|
||||||
|
self.redirect_type = self.RedirectType.TO_SPECIFIC_PAGE
|
||||||
|
else:
|
||||||
|
self.redirect_type = self.RedirectType.HOME
|
||||||
|
|
||||||
|
return self.form_valid(form)
|
||||||
|
else:
|
||||||
|
return self.form_invalid(form)
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
"""Saves the current contact to the database, and if the user is complete
|
||||||
|
with their setup, then we mark user.finished_setup to True."""
|
||||||
|
completed_states = [self.RedirectType.TO_SPECIFIC_PAGE, self.RedirectType.HOME]
|
||||||
|
if self.redirect_type in completed_states:
|
||||||
|
self.request.user.finished_setup = True
|
||||||
|
self.request.user.save()
|
||||||
|
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
"""Redirect to the nameservers page for the domain."""
|
||||||
|
redirect_url = self.get_redirect_url()
|
||||||
|
return 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 = ""
|
||||||
|
try:
|
||||||
|
if self.redirect_type in self_redirect:
|
||||||
|
base_url = reverse("finish-user-profile-setup", kwargs={"pk": self.object.pk})
|
||||||
|
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"]
|
||||||
|
base_url = reverse(desired_view)
|
||||||
|
else:
|
||||||
|
base_url = reverse("home")
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue