# 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 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)