Skip to main content

Sqlite Based Multi Tenancy With Django

Project description

Django SQLite Tenants

A lightweight, robust multi-tenancy solution for Django using SQLite. This project isolates tenant data into separate SQLite databases while maintaining a shared database for global data (tenants, users).

Features

  • Database Isolation: Each tenant has its own sqlite3 database file.
  • Admin Separation:
    • Public Admin (/admin/): Managing global entities (Tenants, Users).
    • Tenant Admin (/r/<slug>/admin/): Managing tenant-specific data within the tenant's context.
  • Strict Routing: Database router ensures tenant apps cannot write to the shared database and vice versa.
  • Customizable: Configurable database locations, routing modes, and app sharing.
  • Maintenance Mode: Built-in support for tenant-specific maintenance mode.
  • Domain Support: Multiple domain aliases per tenant with primary domain management.
  • Easy Tenant Switching: Context manager for tenant activation in scripts and Celery tasks.

Installation & Quick Start

  1. Install Dependencies:

    uv sync
    
  2. Migrate System Database:

    uv run python manage.py migrate
    
  3. Create a Tenant:

    python manage.py create_tenant amazon --name "Amazon" --domain "amazon.local"
    
  4. Run Server:

    python manage.py runserver
    
    • Access Public Admin: http://localhost:8000/admin/
    • Access Tenant Admin: http://localhost:8000/r/amazon/admin/

Configuration Settings

Configure these settings in core/settings.py to customize the behavior of the tenant system.

Core Settings

Setting Default Description
TENANT_MODEL Required The dotted path to your Tenant model (e.g., "apps.tenant.CustomTenant").
DOMAIN_MODEL None The dotted path to your Domain model (e.g., "apps.tenant.Domain").
SHARED_APPS [] List of apps that live in the default (shared) database (e.g., auth, contenttypes).
TENANT_APPS [] List of apps that live in the tenant databases (e.g., blog, tenant_users).
TENANTS_DB_FOLDER "tenants" Folder path relative to BASE_DIR where tenant SQLite files are stored.

Routing & Middleware

Setting Default Description
TENANT_ROUTING_MODE "DOMAIN" How tenants are identified. Options:
"SUBFOLDER": /r/<slug>/
"DOMAIN": <slug>.domain.com
TENANT_SUBFOLDER_PREFIX "r" Used with SUBFOLDER mode. The URL prefix (e.g., "r" results in /r/tenant/).
TENANT_BASE_DOMAIN `"localhost" Used withDOMAINmode. The base domain to strip when identifying tenants (e.g.tenant.example.com`).

URL Configuration

Setting Default Description
ROOT_URLCONF Required URL config for the public/shared view (e.g., "core.urls_public").
TENANT_URLCONF None URL config for tenant-specific views (e.g., "core.urls_tenant"). Swapped automatically by middleware.

Migration Settings

Setting Default Description
AUTO_RUN_MIGRATION True Whether to automatically run migrations when creating a tenant.

Application Split Example

Your settings.py should segregate apps to ensure proper migration and routing:

SHARED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "django_sqlite_tenants",
    "apps.tenant",  # The app containing the Tenant model
]

TENANT_APPS = [
    "apps.blog",         # Content specific to a tenant
    "apps.tenant_users", # Users specific to a tenant
]

# Combined for Django internals
INSTALLED_APPS = list(SHARED_APPS) + [
    app for app in TENANT_APPS if app not in SHARED_APPS
]

DJANGO_TENANT_SQLITE = {
    "TENANT_MODEL": "tenant.CustomTenant",
    "DOMAIN_MODEL": "tenant.Domain",
    "TENANT_URLCONF": "core.urls_tenant",
    "TENANT_ROUTING_MODE": "DOMAIN",
    "TENANT_SUBFOLDER_PREFIX": "r",
    "TENANT_BASE_DOMAIN": "localhost:8000",
}

MIDDLEWARE = [
    "django_sqlite_tenants.middlewares.TenantMiddleware",
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
]

DATABASE_ROUTERS = [
    "django_sqlite_tenants.db_routers.TenantRouter",
]

Management Commands

create_tenant

Creates a new tenant and initializes their database.

python manage.py create_tenant <slug> --name "Tenant Name" --domain "custom.domain.com"

Arguments:

  • <slug>: Unique identifier for the tenant (required)
  • --name: Display name (defaults to capitalized slug)
  • --domain: Custom domain (optional)

Example:

python manage.py create_tenant apple --name "Apple Inc." --domain "apple.local"

migrate_tenant

Runs migrations for tenant databases.

python manage.py migrate_tenant [--tenant <slug>]

Options:

  • --tenant <slug>: Migrate only the specified tenant (optional, defaults to all tenants)

Examples:

# Migrate all tenants
python manage.py migrate_tenant

# Migrate specific tenant
python manage.py migrate_tenant --tenant amazon

Tenant & Domain Models

Creating Your Tenant Model

# apps/tenant/models.py
from django_sqlite_tenants.models import TenantMixin

class CustomTenant(TenantMixin):
    # Add additional fields here
    industry = models.CharField(max_length=100, blank=True)
    created_at = models.DateTimeField(auto_now_add=True)
    
    class Meta:
        verbose_name = "Tenant"
        verbose_name_plural = "Tenants"

Creating Your Domain Model

# apps/tenant/models.py
from django_sqlite_tenants.models import DomainMixin

class Domain(DomainMixin):
    # Add additional fields here
    created_at = models.DateTimeField(auto_now_add=True)
    
    class Meta:
        verbose_name = "Domain"
        verbose_name_plural = "Domains"

Usage Examples

Accessing Tenant Data in Views

from django.shortcuts import render
from apps.blog.models import Post

def blog_list(request):
    # Automatically filters posts by current tenant
    posts = Post.objects.all()
    return render(request, 'blog/list.html', {'posts': posts})

Using Tenant Context Manager

from apps.tenant.models import CustomTenant

def run_tenant_task():
    tenant = CustomTenant.objects.get(slug='amazon')
    
    with tenant:
        # Code here runs in the context of 'amazon' tenant
        # All database operations will use the tenant's database
        posts = Post.objects.all()
        print(f"Amazon has {len(posts)} posts")
    
    # Back to public schema
    print("Done with tenant context")

Activating Tenant in Shell

python manage.py shell
>>> from apps.tenant.models import CustomTenant
>>> tenant = CustomTenant.objects.get(slug='amazon')
>>> tenant.activate()
>>> # Now all queries will use Amazon's database
>>> Post.objects.count()
5
>>> CustomTenant.deactivate()  # Return to public schema

Database Routing

The TenantRouter class handles all database routing:

  • Shared apps always use the default database
  • Tenant apps use the current tenant's database
  • Relations between shared and tenant models are allowed
  • Migrations are automatically routed to the correct database

Maintenance Mode

Set a tenant to maintenance mode:

from apps.tenant.models import CustomTenant

tenant = CustomTenant.objects.get(slug='amazon')
tenant.maintenance_mode = True
tenant.save()

When in maintenance mode, all requests will return a 503 Service Unavailable response with a simple "System Under Maintenance" message.

Advanced Configuration

Custom Database Location

DJANGO_TENANT_SQLITE = {
    "TENANTS_DB_FOLDER": "data/tenants/databases",
    # ... other settings
}

Subfolder Routing Mode

DJANGO_TENANT_SQLITE = {
    "TENANT_ROUTING_MODE": "SUBFOLDER",
    "TENANT_SUBFOLDER_PREFIX": "tenants",
    # ... other settings
}

Tenant URLs would then be: /tenants/amazon/blog/

Custom URL Confs

DJANGO_TENANT_SQLITE = {
    "TENANT_URLCONF": "core.urls_tenant",
    "ROOT_URLCONF": "core.urls_public",
    # ... other settings
}

Using with Celery

from celery import shared_task
from apps.tenant.models import CustomTenant
from apps.blog.models import Post

@shared_task
def count_tenant_posts(tenant_slug):
    tenant = CustomTenant.objects.get(slug=tenant_slug)
    
    with tenant:
        count = Post.objects.count()
        return f"Tenant {tenant_slug} has {count} posts"

Example Project

An example project is included in the example/ directory that demonstrates:

  • Tenant and Domain model implementations
  • Blog application with tenant-specific content
  • Tenant users management
  • Admin interface customization
  • Templates with tenant context

To run the example project:

cd example
uv sync
uv run python manage.py migrate
uv run python manage.py create_tenant amazon --name "Amazon" --domain "amazon.local"
uv run python manage.py runserver

Testing

Run the test suite:

uv run pytest

Run specific tests:

uv run pytest django_sqlite_tenants/tests.py -v

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the MIT License.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_sqlite_tenants-0.1.0.tar.gz (22.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_sqlite_tenants-0.1.0-py3-none-any.whl (23.6 kB view details)

Uploaded Python 3

File details

Details for the file django_sqlite_tenants-0.1.0.tar.gz.

File metadata

  • Download URL: django_sqlite_tenants-0.1.0.tar.gz
  • Upload date:
  • Size: 22.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for django_sqlite_tenants-0.1.0.tar.gz
Algorithm Hash digest
SHA256 0abaf447aaf1606fd940d4095128751ee2c8451650cee889a47fdff7cdb461e1
MD5 a7ad9f1761e5b472e282d426ee31e210
BLAKE2b-256 68eeda7d5ec2d79b3703da5cc0ce19e80fce483dee9abb00b5cfdba662c92ff8

See more details on using hashes here.

File details

Details for the file django_sqlite_tenants-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: django_sqlite_tenants-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 23.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for django_sqlite_tenants-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9979873cff072a0cfcfbc05b54a92301cfcf5ddc9867afed86a8cae58a61ac09
MD5 62851619c3bd3d9cfd483d86403c9e00
BLAKE2b-256 d79506e5656b27bc272dd1eab40643b485aae1b9dfb52e8853880264566440dd

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page