Skip to main content
You can enable multi-tenancy in your Django application using the Django-Tenants library, which allows each tenant to have its own subdomain and database schema, keeping data fully isolated. The following instructions guide you through setting up a Django-Tenants-enabled application on Sevalla. This example assumes you already have a deployed Django app. It is recommended to start with an empty database, as Django-Tenants applies migrations in a tenant-specific manner. Additionally, a custom domain is required for this setup, since each tenant will use a separate subdomain to implement the multi-tenancy structure.

Installation and settings

  1. Install the django-tenants library and add it to your dependencies.
    pip install django-tenants
    pip freeze > requirements.txt
    
  2. Update the database engine to use the Django-Tenants Postgres backend.
    # settings.py
    DATABASES = {
        'default': env.db(engine="django_tenants.postgresql_backend"), 
    }
    
  3. A custom database router can then be added to settings.py.
    # settings.py
    DATABASE_ROUTERS = ( 
        'django_tenants.routers.TenantSyncRouter',
    )
    
  4. Add the TenantMainMiddleware to the MIDDLEWARE list. It should be added after CommonMiddleware to avoid any errors.
    # settings.py
    MIDDLEWARE = [
        'django.middleware.security.SecurityMiddleware',
        'whitenoise.middleware.WhiteNoiseMiddleware',
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django_tenants.middleware.main.TenantMainMiddleware', 
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
    ]
    

App and model

In this example, you’ll create two Django apps. The first, app, will contain the models used to manage tenants and domains, along with any views that are publicly accessible. The second app, dashboard, will be tenant-specific and will contain functionality and views intended only for individual tenants.
python manage.py startapp app
python manage.py startapp dashboard
The Client and Domain models must be created in app/models.py. The Client model is used to create all tenants. The Domain model is used to associate subdomains with the tenants you create.
# app/models.py
from django_tenants.models import TenantMixin, DomainMixin 

class Client(TenantMixin):
    name = models.CharField(max_length=100)
    created_on = models.DateField(auto_now_add=True)

    auto_create_schema = True

class Domain(DomainMixin):
    pass

Other settings

Because some apps should be available globally across the project, while others should be isolated per tenant, you’ll need to adjust how INSTALLED_APPS is defined. Start by defining a SHARED_APPS list, which replaces the traditional INSTALLED_APPS and includes apps that are shared across all tenants. Next, create a TENANT_APPS list containing only the apps that should be available to individual tenants. Finally, define INSTALLED_APPS as a combination of both lists. The SHARED_APPS list should also include the django_tenants app and the main app application that manages tenants and domains.
SHARED_APPS = [ 
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django_tenants', # [!code ++: 2]
    'app',
]

TENANT_APPS = ["dashboard"] 

INSTALLED_APPS = SHARED_APPS + [app for app in TENANT_APPS if app not in SHARED_APPS] 
In settings.py, configure Django-Tenants, so that it knows where your core tenant and domain models are defined.
TENANT_MODEL = "app.Client"

TENANT_DOMAIN_MODEL = "app.Domain"
Finally, in settings.py, replace the original ROOT_URLCONF with a new configuration pointing to urls_tenants, which you’ll create shortly. Then, define PUBLIC_SCHEMA_URLCONF to point to your original urls.py file, which will continue to handle all public (non-tenant) views.
ROOT_URLCONF = 'example.urls_tenants'
PUBLIC_SCHEMA_URLCONF = 'example.urls'

Views

Below are two simple example views that illustrate the difference between public views and tenant-specific views.

Public View

This view lives in the shared app application and is accessible across the entire project, regardless of tenant.
# app/views.py
from django.http import HttpResponse 

def index(request):
    return HttpResponse("Hello, this is the public-specific view!")

Tenant-Specific View

This view belongs to the dashboard application and is only available within a tenant context. It uses request.tenant to access tenant-specific data.
# dashboard/views.py
from django.http import HttpResponse 

def index(request):
    return HttpResponse(f"Hello, {request.tenant.name}. This is the tenant-specific view!")

Tenant URL Configuration

Next, create a urls_tenants.py file to define routes that should only be accessible to tenants. This file references views from the tenant-only dashboard app.
# example/urls_tenants.py
from django.urls import path 

from dashboard import views

urlpatterns = [
    path('', views.index, name='index'),
]

Public URL Configuration

Finally, keep your public routes in the existing urls.py file. These URLs are served from the public schema and remain accessible without a tenant context.
# example/urls.py
from django.contrib import admin
from django.urls import path

from app import views 

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', views.index, name='index'), 
]

Deploying to Sevalla

Before deploying your application, create migrations to create the new models in the database.
python manage.py makemigrations
Add a custom start command to prevent the default migrations from running.
gunicorn example.wsgi
Create a job process that runs before deployment that uses the migrate_schemas command from Django-Tenants.
python manage.py migrate_schemas --shared
Add a custom domain to your project so the subdomains work with django-tenants. You can now deploy your app in Deployments > Deploy now.

Create a Tenant

Once your app is deployed and you can see the public view on your page, add a tenant to the system. The tenant can be added through the web terminal. Use the bash web terminal, turn on the virtual environment, and then start a Django shell session.
. /opt/venv/bin/activate
python manage.py shell
Finally, create tenant and domain objects. Ensure the domain you use here matches the domain associated with your app.
tenant = Client.objects.create(schema_name='tenant1', name='Tenant One')
Domain.objects.create(domain='tenant1.your-domain.com', tenant=tenant, is_primary=True)
Now you should be able to visit both your base domain and the tenant subdomain and see the public and tenant pages. You can also check your database and see that multiple schemas exist.