diff --git a/src/djangooidc/backends.py b/src/djangooidc/backends.py index 96b7a902a..8e4eb023d 100644 --- a/src/djangooidc/backends.py +++ b/src/djangooidc/backends.py @@ -50,7 +50,7 @@ class OpenIdConnectBackend(ModelBackend): user, created = UserModel.objects.get_or_create(**args) - if created: + if created and request is not None: request.session["is_new_user"] = True if not created: @@ -63,7 +63,8 @@ class OpenIdConnectBackend(ModelBackend): try: user = UserModel.objects.get_by_natural_key(username) except UserModel.DoesNotExist: - request.session["is_new_user"] = True + if request is not None: + request.session["is_new_user"] = True return None # run this callback for a each login user.on_each_login() diff --git a/src/registrar/forms/contact.py b/src/registrar/forms/contact.py index fa0bf1fce..9a752bab4 100644 --- a/src/registrar/forms/contact.py +++ b/src/registrar/forms/contact.py @@ -46,4 +46,3 @@ class ContactForm(forms.Form): label="Phone", error_messages={"invalid": "Enter a valid 10-digit phone number.", "required": "Enter your phone number."}, ) - diff --git a/src/registrar/forms/domain.py b/src/registrar/forms/domain.py index 9dfd9773a..db247ad21 100644 --- a/src/registrar/forms/domain.py +++ b/src/registrar/forms/domain.py @@ -202,6 +202,7 @@ NameserverFormset = formset_factory( validate_max=True, ) + # TODO - refactor, wait until daves PR class ContactForm(forms.ModelForm): """Form for updating contacts.""" diff --git a/src/registrar/forms/domain_request_wizard.py b/src/registrar/forms/domain_request_wizard.py index 9d16a30de..dd29df523 100644 --- a/src/registrar/forms/domain_request_wizard.py +++ b/src/registrar/forms/domain_request_wizard.py @@ -150,7 +150,7 @@ class OrganizationContactForm(RegistrarForm): """Require something to be selected when this is a federal agency.""" federal_agency = self.cleaned_data.get("federal_agency", None) # need the domain request object to know if this is federal - if self.domain_request is None: + if hasattr(self, "domain_request") and self.domain_request is None: # hmm, no saved domain request object?, default require the agency if not federal_agency: # no answer was selected diff --git a/src/registrar/models/contact.py b/src/registrar/models/contact.py index 119b78fa6..6e196783b 100644 --- a/src/registrar/models/contact.py +++ b/src/registrar/models/contact.py @@ -3,6 +3,7 @@ from django.db import models from .utility.time_stamped_model import TimeStampedModel from phonenumber_field.modelfields import PhoneNumberField # type: ignore + class Contact(TimeStampedModel): """Contact information follows a similar pattern for each contact.""" diff --git a/src/registrar/models/utility/generic_helper.py b/src/registrar/models/utility/generic_helper.py index 8f504ad9e..6ee598c13 100644 --- a/src/registrar/models/utility/generic_helper.py +++ b/src/registrar/models/utility/generic_helper.py @@ -280,4 +280,4 @@ def from_database(form_class, obj): """Returns a dict of form field values gotten from `obj`.""" if obj is None: return {} - return {name: getattr(obj, name) for name in form_class.declared_fields.keys()} # type: ignore \ No newline at end of file + return {name: getattr(obj, name) for name in form_class.declared_fields.keys()} # type: ignore diff --git a/src/registrar/registrar_middleware.py b/src/registrar/registrar_middleware.py index 427775f34..8dca06019 100644 --- a/src/registrar/registrar_middleware.py +++ b/src/registrar/registrar_middleware.py @@ -1,17 +1,20 @@ """ Contains middleware used in settings.py """ + from urllib.parse import urlparse, urlunparse, parse_qs, urlencode from django.urls import reverse from django.http import HttpResponseRedirect from waffle.decorators import flag_is_active + class CheckUserProfileMiddleware: """ - Checks if the current user has finished_setup = False. + Checks if the current user has finished_setup = False. If they do, redirect them to the setup page regardless of where they are in the application. """ + def __init__(self, get_response): self.get_response = get_response @@ -21,7 +24,7 @@ class CheckUserProfileMiddleware: return response def process_view(self, request, view_func, view_args, view_kwargs): - + # Check that the user is "opted-in" to the profile feature flag has_profile_feature_flag = flag_is_active(request, "profile_feature") @@ -32,10 +35,7 @@ class CheckUserProfileMiddleware: # Check if setup is not finished finished_setup = hasattr(request.user, "finished_setup") and request.user.finished_setup if request.user.is_authenticated and not finished_setup: - setup_page = reverse( - "finish-contact-profile-setup", - kwargs={"pk": request.user.contact.pk} - ) + setup_page = reverse("finish-contact-profile-setup", kwargs={"pk": request.user.contact.pk}) logout_page = reverse("logout") excluded_pages = [ setup_page, @@ -52,7 +52,7 @@ class CheckUserProfileMiddleware: # Don't redirect on excluded pages (such as the setup page itself) if not any(request.path.startswith(page) for page in excluded_pages): # 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"]) if custom_redirect is not None: # Set the redirect value to our redirect location @@ -65,7 +65,6 @@ class CheckUserProfileMiddleware: setup_page_parts[4] = urlencode(query_params) # Reassemble the URL setup_page = urlunparse(setup_page_parts) - # Redirect to the setup page return HttpResponseRedirect(setup_page) @@ -88,4 +87,4 @@ class NoCacheMiddleware: def __call__(self, request): response = self.get_response(request) response["Cache-Control"] = "no-cache" - return response \ No newline at end of file + return response diff --git a/src/registrar/views/contact.py b/src/registrar/views/contact.py index d8ea5a041..322630b07 100644 --- a/src/registrar/views/contact.py +++ b/src/registrar/views/contact.py @@ -20,6 +20,7 @@ logger = logging.getLogger(__name__) class BaseContactView(ContactPermissionView): """Provides a base view for the contact object. On get, the contact is saved in the session and on self.object.""" + def get(self, request, *args, **kwargs): """Sets the current contact in cache, defines the current object as self.object then returns render_to_response""" @@ -56,6 +57,7 @@ class BaseContactView(ContactPermissionView): class ContactFormBaseView(BaseContactView, FormMixin): """Adds a FormMixin to BaseContactView, and handles post""" + def post(self, request, *args, **kwargs): """Form submission posts to this view. @@ -78,8 +80,9 @@ class ContactFormBaseView(BaseContactView, FormMixin): class ContactProfileSetupView(ContactFormBaseView): - """This view forces the user into providing additional details that + """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 = ContactForm model = Contact @@ -99,22 +102,23 @@ class ContactProfileSetupView(ContactFormBaseView): - 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" # TODO - refactor - @waffle_flag('profile_feature') + @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, + + 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. """ @@ -140,7 +144,7 @@ class ContactProfileSetupView(ContactFormBaseView): self.RedirectType.HOME, self.RedirectType.COMPLETE_SETUP, self.RedirectType.BACK_TO_SELF, - self.RedirectType.TO_SPECIFIC_PAGE + self.RedirectType.TO_SPECIFIC_PAGE, ] if redirect_type not in default_redirects: self.redirect_type = self.RedirectType.TO_SPECIFIC_PAGE @@ -154,8 +158,8 @@ class ContactProfileSetupView(ContactFormBaseView): """ 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 + 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: @@ -178,13 +182,11 @@ class ContactProfileSetupView(ContactFormBaseView): base_url = reverse(desired_view) except NoReverseMatch as err: logger.error(err) - logger.error( - "ContactProfileSetupView -> get_redirect_url -> Could not find specified page." - ) + logger.error("ContactProfileSetupView -> get_redirect_url -> Could not find specified page.") base_url = reverse("home") case _: base_url = reverse("home") - + # Quote cleans up the value so that it can be used in a url query_params["redirect"] = quote(self.redirect_type) @@ -231,27 +233,24 @@ class ContactProfileSetupView(ContactFormBaseView): return self.form_invalid(form) def form_valid(self, form): - - completed_states = [ - self.RedirectType.TO_SPECIFIC_PAGE, - self.RedirectType.HOME - ] + + 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._update_session_with_contact() return super().form_valid(form) - + 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() @@ -259,7 +258,7 @@ class ContactProfileSetupView(ContactFormBaseView): 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") diff --git a/src/registrar/views/domain_request.py b/src/registrar/views/domain_request.py index b07f0d53f..f93976138 100644 --- a/src/registrar/views/domain_request.py +++ b/src/registrar/views/domain_request.py @@ -819,4 +819,3 @@ class DomainRequestDeleteView(DomainRequestPermissionDeleteView): duplicates = [item for item, count in object_dict.items() if count > 1] return duplicates - diff --git a/src/registrar/views/utility/mixins.py b/src/registrar/views/utility/mixins.py index 49d172971..3e5e10816 100644 --- a/src/registrar/views/utility/mixins.py +++ b/src/registrar/views/utility/mixins.py @@ -341,7 +341,6 @@ class ContactPermission(PermissionsLoginMixin): if not self.request.user.is_authenticated: return False - given_contact_pk = self.kwargs["pk"] # Grab the user in the DB to do a full object comparision, not just on ids @@ -351,13 +350,10 @@ class ContactPermission(PermissionsLoginMixin): if current_user.contact.pk != given_contact_pk: # Don't allow users to modify other users profiles return False - + # Check if the object at the id we're searching on actually exists requested_user_exists = User.objects.filter(pk=current_user.pk).exists() - requested_contact_exists = Contact.objects.filter( - user=current_user.pk, - pk=given_contact_pk - ).exists() + requested_contact_exists = Contact.objects.filter(user=current_user.pk, pk=given_contact_pk).exists() if not requested_user_exists or not requested_contact_exists: return False