Merge remote-tracking branch 'origin' into rh/2258-update-ao-to-so

This commit is contained in:
Rebecca Hsieh 2024-06-25 11:07:40 -07:00
commit 0ec2a0db9d
No known key found for this signature in database
14 changed files with 205 additions and 233 deletions

View file

@ -697,3 +697,31 @@ Example: `cf ssh getgov-za`
| | Parameter | Description |
|:-:|:-------------------------- |:----------------------------------------------------------------------------|
| 1 | **debug** | Increases logging detail. Defaults to False. |
## Email current metadata report
### Running on sandboxes
#### Step 1: Login to CloudFoundry
```cf login -a api.fr.cloud.gov --sso```
#### Step 2: SSH into your environment
```cf ssh getgov-{space}```
Example: `cf ssh getgov-za`
#### Step 3: Create a shell instance
```/tmp/lifecycle/shell```
#### Step 4: Running the script
```./manage.py email_current_metadata_report --emailTo {desired email address}```
### Running locally
#### Step 1: Running the script
```docker-compose exec app ./manage.py email_current_metadata_report --emailTo {desired email address}```
##### Parameters
| | Parameter | Description |
|:-:|:-------------------------- |:-----------------------------------------------------------------------------------|
| 1 | **emailTo** | Specifies where the email will be emailed. Defaults to help@get.gov on production. |

View file

@ -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"]

View file

@ -1,7 +1,6 @@
"""Generates current-metadata.csv then uploads to S3 + sends email"""
import logging
import os
import pyzipper
from datetime import datetime
@ -9,7 +8,7 @@ from datetime import datetime
from django.core.management import BaseCommand
from django.conf import settings
from registrar.utility import csv_export
from registrar.utility.s3_bucket import S3ClientHelper
from io import StringIO
from ...utility.email import send_templated_email
@ -17,89 +16,101 @@ logger = logging.getLogger(__name__)
class Command(BaseCommand):
"""Emails a encrypted zip file containing a csv of our domains and domain requests"""
help = (
"Generates and uploads a domain-metadata.csv file to our S3 bucket "
"which is based off of all existing Domains."
)
current_date = datetime.now().strftime("%m%d%Y")
def add_arguments(self, parser):
"""Add our two filename arguments."""
parser.add_argument("--directory", default="migrationdata", help="Desired directory")
parser.add_argument(
"--checkpath",
default=True,
help="Flag that determines if we do a check for os.path.exists. Used for test cases",
"--emailTo",
default=settings.DEFAULT_FROM_EMAIL,
help="Defines where we should email this report",
)
def handle(self, **options):
"""Grabs the directory then creates domain-metadata.csv in that directory"""
file_name = "domain-metadata.csv"
# Ensures a slash is added
directory = os.path.join(options.get("directory"), "")
check_path = options.get("checkpath")
zip_filename = f"domain-metadata-{self.current_date}.zip"
email_to = options.get("emailTo")
# Don't email to DEFAULT_FROM_EMAIL when not prod.
if not settings.IS_PRODUCTION and email_to == settings.DEFAULT_FROM_EMAIL:
raise ValueError(
"The --emailTo arg must be specified in non-prod environments, "
"and the arg must not equal the DEFAULT_FROM_EMAIL value (aka: help@get.gov)."
)
logger.info("Generating report...")
try:
self.email_current_metadata_report(directory, file_name, check_path)
self.email_current_metadata_report(zip_filename, email_to)
except Exception as err:
# TODO - #1317: Notify operations when auto report generation fails
raise err
else:
logger.info(f"Success! Created {file_name} and successfully sent out an email!")
logger.info(f"Success! Created {zip_filename} and successfully sent out an email!")
def email_current_metadata_report(self, directory, file_name, check_path):
"""Creates a current-metadata.csv file under the specified directory,
then uploads it to a AWS S3 bucket. This is done for resiliency
reasons in the event our application goes down and/or the email
cannot send -- we'll still be able to grab info from the S3
instance"""
s3_client = S3ClientHelper()
file_path = os.path.join(directory, file_name)
def email_current_metadata_report(self, zip_filename, email_to):
"""Emails a password protected zip containing domain-metadata and domain-request-metadata"""
reports = {
"Domain report": {
"report_filename": f"domain-metadata-{self.current_date}.csv",
"report_function": csv_export.export_data_type_to_csv,
},
"Domain request report": {
"report_filename": f"domain-request-metadata-{self.current_date}.csv",
"report_function": csv_export.DomainRequestExport.export_full_domain_request_report,
},
}
# Generate a file locally for upload
with open(file_path, "w") as file:
csv_export.export_data_type_to_csv(file)
# Set the password equal to our content in SECRET_ENCRYPT_METADATA.
# For local development, this will be "devpwd" unless otherwise set.
# Uncomment these lines if you want to use this:
# override = settings.SECRET_ENCRYPT_METADATA is None and not settings.IS_PRODUCTION
# password = "devpwd" if override else settings.SECRET_ENCRYPT_METADATA
password = settings.SECRET_ENCRYPT_METADATA
if not password:
raise ValueError("No password was specified for this zip file.")
if check_path and not os.path.exists(file_path):
raise FileNotFoundError(f"Could not find newly created file at '{file_path}'")
s3_client.upload_file(file_path, file_name)
# Set zip file name
current_date = datetime.now().strftime("%m%d%Y")
current_filename = f"domain-metadata-{current_date}.zip"
# Pre-set zip file name
encrypted_metadata_output = current_filename
# Set context for the subject
current_date_str = datetime.now().strftime("%Y-%m-%d")
# Encrypt the metadata
encrypted_metadata_in_bytes = self._encrypt_metadata(
s3_client.get_file(file_name), encrypted_metadata_output, str.encode(settings.SECRET_ENCRYPT_METADATA)
)
encrypted_zip_in_bytes = self.get_encrypted_zip(zip_filename, reports, password)
# Send the metadata file that is zipped
send_templated_email(
template_name="emails/metadata_body.txt",
subject_template_name="emails/metadata_subject.txt",
to_address=settings.DEFAULT_FROM_EMAIL,
context={"current_date_str": current_date_str},
attachment_file=encrypted_metadata_in_bytes,
to_address=email_to,
context={"current_date_str": datetime.now().strftime("%Y-%m-%d")},
attachment_file=encrypted_zip_in_bytes,
)
def _encrypt_metadata(self, input_file, output_file, password):
def get_encrypted_zip(self, zip_filename, reports, password):
"""Helper function for encrypting the attachment file"""
current_date = datetime.now().strftime("%m%d%Y")
current_filename = f"domain-metadata-{current_date}.csv"
# Using ZIP_DEFLATED bc it's a more common compression method supported by most zip utilities and faster
# We could also use compression=pyzipper.ZIP_LZMA if we are looking for smaller file size
with pyzipper.AESZipFile(
output_file, "w", compression=pyzipper.ZIP_DEFLATED, encryption=pyzipper.WZ_AES
zip_filename, "w", compression=pyzipper.ZIP_DEFLATED, encryption=pyzipper.WZ_AES
) as f_out:
f_out.setpassword(password)
f_out.writestr(current_filename, input_file)
with open(output_file, "rb") as file_data:
f_out.setpassword(str.encode(password))
for report_name, report in reports.items():
logger.info(f"Generating {report_name}")
report_content = self.write_and_return_report(report["report_function"])
f_out.writestr(report["report_filename"], report_content)
# Get the final report for emailing purposes
with open(zip_filename, "rb") as file_data:
attachment_in_bytes = file_data.read()
return attachment_in_bytes
def write_and_return_report(self, report_function):
"""Writes a report to a StringIO object given a report_function and returns the string."""
report_bytes = StringIO()
report_function(report_bytes)
# Rewind the buffer to the beginning after writing
report_bytes.seek(0)
return report_bytes.read()

View file

@ -17,6 +17,8 @@ from .utility.time_stamped_model import TimeStampedModel
from ..utility.email import send_templated_email, EmailSendingError
from itertools import chain
from waffle.decorators import flag_is_active
logger = logging.getLogger(__name__)
@ -1165,19 +1167,21 @@ class DomainRequest(TimeStampedModel):
def _is_policy_acknowledgement_complete(self):
return self.is_policy_acknowledged is not None
def _is_general_form_complete(self):
def _is_general_form_complete(self, request):
has_profile_feature_flag = flag_is_active(request, "profile_feature")
return (
self._is_organization_name_and_address_complete()
and self._is_senior_official_complete()
and self._is_requested_domain_complete()
and self._is_purpose_complete()
and self._is_submitter_complete()
# NOTE: This flag leaves submitter as empty (request wont submit) hence preset to True
and (self._is_submitter_complete() if not has_profile_feature_flag else True)
and self._is_other_contacts_complete()
and self._is_additional_details_complete()
and self._is_policy_acknowledgement_complete()
)
def _form_complete(self):
def _form_complete(self, request):
match self.generic_org_type:
case DomainRequest.OrganizationChoices.FEDERAL:
is_complete = self._is_federal_complete()
@ -1198,8 +1202,6 @@ class DomainRequest(TimeStampedModel):
case _:
# NOTE: Shouldn't happen, this is only if somehow they didn't choose an org type
is_complete = False
if not is_complete or not self._is_general_form_complete():
if not is_complete or not self._is_general_form_complete(request):
return False
return True

View file

@ -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:
if hasattr(request.user, "finished_setup") and not request.user.finished_setup:
profile_page = self.profile_page
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)
profile_page = self.setup_page
if hasattr(request.user, "finished_setup") and not request.user.finished_setup:
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:
"""

View file

@ -18,7 +18,7 @@
completing your domain request might take around 15 minutes.</p>
{% if has_profile_feature_flag %}
<h2>How well reach you</h2>
<p>While reviewing your domain request, we may need to reach out with questions. Well 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. Well 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 %}

View file

@ -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 %}

View file

@ -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>

View file

@ -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 %}

View file

@ -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 %}

View file

@ -3,6 +3,8 @@ from django.db.utils import IntegrityError
from unittest.mock import patch
from django.contrib.auth import get_user_model
from django.test import RequestFactory
from registrar.models import (
Contact,
DomainRequest,
@ -1610,6 +1612,7 @@ class TestDomainInformationCustomSave(TestCase):
class TestDomainRequestIncomplete(TestCase):
def setUp(self):
super().setUp()
self.factory = RequestFactory()
username = "test_user"
first_name = "First"
last_name = "Last"
@ -2013,7 +2016,10 @@ class TestDomainRequestIncomplete(TestCase):
self.assertFalse(self.domain_request._is_policy_acknowledgement_complete())
def test_form_complete(self):
self.assertTrue(self.domain_request._form_complete())
request = self.factory.get("/")
request.user = self.user
self.assertTrue(self.domain_request._form_complete(request))
self.domain_request.generic_org_type = None
self.domain_request.save()
self.assertFalse(self.domain_request._form_complete())
self.assertFalse(self.domain_request._form_complete(request))

View file

@ -531,7 +531,10 @@ 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):
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")

View file

@ -383,7 +383,7 @@ class DomainRequestWizard(DomainRequestWizardPermissionView, TemplateView):
has_profile_flag = flag_is_active(self.request, "profile_feature")
context_stuff = {}
if DomainRequest._form_complete(self.domain_request):
if DomainRequest._form_complete(self.domain_request, self.request):
modal_button = '<button type="submit" ' 'class="usa-button" ' ">Submit request</button>"
context_stuff = {
"not_form": False,
@ -695,7 +695,7 @@ class Review(DomainRequestWizard):
forms = [] # type: ignore
def get_context_data(self):
if DomainRequest._form_complete(self.domain_request) is False:
if DomainRequest._form_complete(self.domain_request, self.request) is False:
logger.warning("User arrived at review page with an incomplete form.")
context = super().get_context_data()
context["Step"] = Step.__members__

View file

@ -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
# 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"
context["show_back_button"] = False
# 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()