Source code for ferrosoft.apps.ferrobase.views.onboarding

#  Copyright (c) 2025 Ferrosoft GmbH. All rights reserved.
from time import sleep

from django.shortcuts import render, redirect
from django.views.generic import FormView
from django.utils.translation import gettext_lazy as _

from ferrosoft.apps.ferrobase.models import Language, AccountActivationRequest
from ferrosoft.apps.ferrobase.services.captcha import Captcha
from ferrosoft.apps.ferrobase.services.taskqueue import background_task
from ferrosoft.apps.ferrobase.services.onboarding import SignupRequest, activate_account
from ferrosoft.apps.ferrobase.tenant.forms import SignupForm, SignupUserForm
from ferrosoft.apps.ferrobase.views.generic import PlatformActivityMixin


[docs] class SignupView(PlatformActivityMixin, FormView): """Public signup form with CAPTCHA gating and background-task dispatch.""" SLEEP_TIME = 2 """Seconds of artificial delay added to POSTs as a basic rate limiter.""" activity_title = _("Sign Up") form_class = SignupForm template_name = "ferrobase/onboarding/signup.html" def __init__(self, *args, **kwargs): """Initialise the per-instance CAPTCHA and submission flag.""" super().__init__(*args, **kwargs) self._request_received = False self._captcha = Captcha()
[docs] def get(self, request, *args, **kwargs): """Render the form with a fresh CAPTCHA challenge.""" self._captcha.set_challenge(request) return super().get(request, *args, **kwargs)
[docs] def post(self, request, *args, **kwargs): """Sleep, verify the CAPTCHA, then process the form (or re-render on failure).""" sleep(self.SLEEP_TIME) if not self._captcha.verify(request): self._captcha.set_challenge(self.request) return super().get(request, *args, **kwargs) self._captcha.set_challenge(self.request) return super().post(request, *args, **kwargs)
[docs] def form_valid(self, form): """Enqueue ``initiate_signup`` and redirect to the conversion-tracking URL. When ``ONBOARDING_CONVERSION_URL`` is not configured the user is instead returned to the form template with a "request received" banner. """ from django.conf import settings dat = form.cleaned_data background_task( "ferrosoft.apps.ferrobase.services.onboarding.initiate_signup", SignupRequest( dat["email"], dat["company"], dat["first_name"], dat["last_name"], Language.get_supported_language(self.request.LANGUAGE_CODE), ), self.request.build_absolute_uri("/"), ) self._request_received = True # For conversion tracking, an external website is used which includes # all the tracking stuff. It is expected that the page shows a link to # login page. conversion_url = getattr(settings, "ONBOARDING_CONVERSION_URL", None) if isinstance(conversion_url, str): return redirect(conversion_url) else: return self.render_to_response(self.get_context_data())
[docs] def get_context_data(self, **kwargs): """Pass the rendered CAPTCHA image and POST field name to the template.""" return super().get_context_data(**kwargs) | { "request_received": self._request_received, "captcha": self._captcha.make_image(), "captcha_field": self._captcha.POST_KEY, }
[docs] class AccountActivationView(PlatformActivityMixin, FormView): """Final step of self-service onboarding: set a password to activate the account.""" activity_title = _("Sign Up") form_class = SignupUserForm template_name = "ferrobase/onboarding/finish_activation.html" def __init__(self, *args, **kwargs): """Initialise the lazily-loaded activation-request slot.""" super().__init__(*args, **kwargs) self._activation_request = None def _validate_activation_request(self, request): """Resolve the activation token; return the "expired" template if invalid.""" self._activation_request = AccountActivationRequest.get_if_not_expired( request.resolver_match.kwargs["access_key"] ) if self._activation_request is None: return render(request, "ferrobase/onboarding/activation_expired.html") return None
[docs] def get(self, request, *args, **kwargs): """Validate the token, then dispatch to the base GET handler.""" response = self._validate_activation_request(request) if response is not None: return response return super().get(request, *args, **kwargs)
[docs] def post(self, request, *args, **kwargs): """Validate the token, then dispatch to the base POST handler.""" response = self._validate_activation_request(request) if response is not None: return response return super().post(request, *args, **kwargs)
[docs] def form_valid(self, form): """Activate the account and redirect to the login page.""" activate_account(self._activation_request, form.cleaned_data["password"]) return redirect("ferrobase:login")