User Management

How To Customize The User Registration Flow

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/

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.

Workspace is required for all users

The workflow for the above would look like this:

  • user_signs up
  • workspace domain and name are added
  • user is then redirected to the home page/dashboard.

First we will remove the Tenant.setup_for_owner(..) method from the user_signed_up signal handler in modify apps/users/


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.


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


  • Only applies to authenticated users with no workspaces.
  • You can amend this to only redirect on some urls for example.
  • This must be placed after the Authentication and TenantMiddleware

The view


@user_passes_test(lambda u: not u.has_tenant)
@require_http_methods(["GET", "POST"])
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.set_session_tenant(tenant_id=str(, request=request)
            url = reverse(settings.LOGIN_REDIRECT_URL)
  , "Workspace created")
            return htmx_redirect(request, url)

        return render(request, template_name, {"form": form})


path("new/", view=create_signup_workspace_view, name="new-workspace"),


  • We created a new view to handle creating workspaces at signup
  • In this case, when we setup the workspace for the user we also include the domain name
  • successful sign ups are redirected to the LOGIN_REDIRECT_URL, i.e. the url the users are normally redirected to after sign up.
  • finally we wire up the view

The template


{% 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-post="{% url 'tenants:new-workspace' %}"
            <div class="mx-auto w-full max-w-sm lg:w-96">
                {% 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>
                <div id="workspaceForm">
                    {{ form }}
                <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">
    <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="">
{% 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.