Repository: cisagov/manage.get.gov

Issue:
Allow users to edit their profile from the Preamble and return to their domain requests
### Issue description

As part of the new User profile feature we are releasing, we've added a snapshot of a user's contact information to the preamble of the request form so they can quickly see what information we have on file for them before they continue their request. This flow allows users to go from Preamble > Your profile > back to Preamble (continuing their request). Note the subtle text change on the back button in the figma

### Acceptance criteria

- [ ] On [the preamble](https://www.figma.com/file/xFtGLHVrhp0lvwh0gYWnbx/.gov-Designs-CISA?type=design&node-id=3353-71111&mode=design&t=OHRKmYXjUiqxr3Si-11), link "your profile" under the "How we'll reach you" section to link to [the user's profile](https://www.figma.com/file/xFtGLHVrhp0lvwh0gYWnbx/.gov-Designs-CISA?type=design&node-id=3353-71409&mode=design&t=OHRKmYXjUiqxr3Si-11)
- [ ] After a user makes their changes, [show a success message and show a backlink](https://www.figma.com/file/xFtGLHVrhp0lvwh0gYWnbx/.gov-Designs-CISA?type=design&node-id=3353-71455&mode=design&t=OHRKmYXjUiqxr3Si-11) that leads them back to their in-progress request (starting on the preamble with their info updated).
- [ ] Be sure to include this under the profile flag

### Additional context

To see the flow in action, [view the prototype link](https://www.figma.com/proto/xFtGLHVrhp0lvwh0gYWnbx/.gov-Designs-CISA?page-id=3353%3A70823&type=design&node-id=3353-70824&viewport=523%2C475%2C0.25&t=aBe3m5Jl4sUOGYfm-1&scaling=min-zoom&starting-point-node-id=3353%3A70824)

### Links to other issues
related to #2055 
related to #1807 


Discussion:
Removing blocked label as both tickets were merged

Resolving patch:
diff --git a/src/registrar/models/utility/generic_helper.py b/src/registrar/models/utility/generic_helper.py
--- a/src/registrar/models/utility/generic_helper.py
+++ b/src/registrar/models/utility/generic_helper.py
@@ -2,6 +2,7 @@
 
 import time
 import logging
+from urllib.parse import urlparse, urlunparse, urlencode
 
 
 logger = logging.getLogger(__name__)
@@ -266,3 +267,34 @@ def _validate_new_instance(self):
             return False
         else:
             return True
+
+
+def replace_url_queryparams(url_to_modify: str, query_params, convert_list_to_csv=False):
+    """
+    Replaces the query parameters of a given URL.
+    Because this replaces them, this can be used to either add, delete, or modify.
+    Args:
+        url_to_modify (str): The URL whose query parameters need to be modified.
+        query_params (dict): Dictionary of query parameters to use.
+        convert_list_to_csv (bool): If the queryparam contains a list of items,
+        convert it to a csv representation instead.
+    Returns:
+        str: The modified URL with the updated query parameters.
+    """
+
+    # Ensure each key in query_params maps to a single value, not a list
+    if convert_list_to_csv:
+        for key, value in query_params.items():
+            if isinstance(value, list):
+                query_params[key] = ",".join(value)
+
+    # Split the URL into parts
+    url_parts = list(urlparse(url_to_modify))
+
+    # Modify the query param bit
+    url_parts[4] = urlencode(query_params)
+
+    # Reassemble the URL
+    new_url = urlunparse(url_parts)
+
+    return new_url
diff --git a/src/registrar/views/user_profile.py b/src/registrar/views/user_profile.py
--- a/src/registrar/views/user_profile.py
+++ b/src/registrar/views/user_profile.py
@@ -3,6 +3,7 @@
 """
 
 import logging
+from urllib.parse import parse_qs, unquote
 
 from django.contrib import messages
 from django.views.generic.edit import FormMixin
@@ -11,6 +12,7 @@
 from registrar.models import (
     Contact,
 )
+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
 
@@ -30,13 +32,26 @@ class UserProfileView(UserProfilePermissionView, FormMixin):
     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.object = self.get_object()
+        self._refresh_session_and_object(request)
         form = self.form_class(instance=self.object)
         context = self.get_context_data(object=self.object, form=form)
+
+        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):
@@ -48,11 +63,20 @@ def get_context_data(self, **kwargs):
 
     def get_success_url(self):
         """Redirect to the user's profile page."""
-        return reverse("user-profile")
+
+        query_params = {}
+        if "query_params" in self.session:
+            params = unquote(self.session["query_params"])
+            query_params = parse_qs(params)
+
+        # 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
 
     def post(self, request, *args, **kwargs):
         """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)
 
         if form.is_valid():


Test patch:
diff --git a/src/registrar/tests/test_views.py b/src/registrar/tests/test_views.py
--- a/src/registrar/tests/test_views.py
+++ b/src/registrar/tests/test_views.py
@@ -597,6 +597,16 @@ def test_user_profile_returns_404_when_feature_off(self):
             response = self.client.get("/user-profile")
         self.assertEqual(response.status_code, 404)
 
+    @less_console_noise_decorator
+    def test_user_profile_back_button_when_coming_from_domain_request(self):
+        """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")
+        self.assertContains(response, "Your profile")
+        self.assertContains(response, "Go back to your domain request")
+        self.assertNotContains(response, "Back to manage your domains")
+
     @less_console_noise_decorator
     def test_domain_detail_profile_feature_on(self):
         """test that domain detail view when profile_feature is on"""