In this short guide, you will learn how to modify the user signup flow for your app.
When a user signs up, a user_signed_up
signal is by the django-allauth lib. The signal handler for this event can be found in apps/users/signals.py
.
Users can either sign up via an invite link or directly on the website. For signups via an invite link, the user is added to the tenant/workspace of the inviter. For direct signups, a new tenant is created automatically for the user.
In some cases you may want to change this behavior, for example, to allow users to add a workspace name, and define a domain name for that workspace.
The workflow for the above would look like this:
First we will remove the Tenant.setup_for_owner(..)
method from the user_signed_up signal handler in modify apps/users/signals.py.
A workspace is required for each user, therefore, we need to ensure that an authenticated user will only get access to the dashboard when a workspace has been created. Since This rule needs to apply to all views, we will create a middleware to handle this behavior.
apps/tenants/middleware.py
class TenantRequiredMiddleware:
"""
Checks if user has tenant
Must be placed AFTER TenantMiddleware
Checks tenant_id in session is not None
If None redirects user to create tenant view
Alternatively, this check can be explicity
`user.has_tenant()`. Please not that this
would be slower as it would require db access
"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
user = request.user
if user and user.is_authenticated:
url = reverse("tenants:new-workspace")
# uses TenantMiddleware above, replace with
# not user.has_tenant() to be explicit
has_no_tenant = request.session.get("tenant_id", None) is None
# must not redirect for current url
excluded_paths = [url]
# redirect if conditions met
if (
has_no_tenant
and settings.WORKSPACE_REQUIRED
and settings.ADD_WORKSPACE_ENABLED
and request.path not in excluded_paths
):
print("redirect as no tenant found")
return redirect(url)
return self.get_response(request)
Notes
Authentication
and TenantMiddleware
apps/tenants/views.py
@user_passes_test(lambda u: not u.has_tenant)
@require_http_methods(["GET", "POST"])
@login_required
def create_signup_workspace_view(request):
template_name = "tenants/components/add_workspace.html"
if request.method == "GET":
form = TenantForm()
return render(request, template_name, {"form": form})
if request.method == "POST":
if request.htmx:
template_name = "tenants/htmx/add_workspace_form.html"
form = TenantForm(request.POST)
if form.is_valid():
user = request.user
tenant = Tenant.setup_tenant_for_owner(
user,
name=form.cleaned_data["name"],
domain=form.cleaned_data["domain"],
)
user.set_session_tenant(tenant_id=str(tenant.id), request=request)
url = reverse(settings.LOGIN_REDIRECT_URL)
messages.info(request, "Workspace created")
return htmx_redirect(request, url)
return render(request, template_name, {"form": form})
apps/tenants/urls.py
path("new/", view=create_signup_workspace_view, name="new-workspace"),
Notes
templates/tenanats/add_workspace.html
{% extends "layouts/blank.html" %}
{% load static %}
{% block shortcut_buttons %}
{% endblock %}
{% block page_content %}
<div class="min-h-full flex">
<div class="flex-1 flex flex-col justify-center py-12 px-4 sm:px-6 lg:flex-none lg:px-20 xl:px-24">
<form novalidate
hx-target="#workspaceForm"
hx-swap="outerHTML"
hx-post="{% url 'tenants:new-workspace' %}"
>
<div class="mx-auto w-full max-w-sm lg:w-96">
<div>
{% include "shared/logo/logo.html" %}
<h2 class="mt-6 text-3xl font-extrabold text-gray-900">Create a workspace</h2>
<p class="mt-2 text-sm text-gray-600">
<span class="font-medium">To start your free trial </a>
</p>
</div>
<div id="workspaceForm">
{{ form }}
</div>
<div class="py-3 sm:flex sm:flex-row-reverse sm:flex sm:flex-row-reverse sm:space-x-reverse sm:space-x-2 rounded-b-lg;">
<button class="btn btn-sm">
Save
</button>
</div>
</div>
</form>
</div>
<div class="hidden lg:block relative w-0 flex-1 bg-slate-50 overflow-hidden">
<div class="flex w-full h-full justify-center items-center m-20 relative">
<img class="object-cover" src="{% static 'images/content.svg' %}" alt="">
</div>
</div>
</div>
{% endblock %}
That's it. Remember Django is infinitely customizable! If you need any help modifying the Vanty Starter Kit, don't hesitate to reach out via email or slack.
Success
Error
Warning
Info