Home / Blogs

How to Build a Multi-Tenant Application with Django

Django
·

July 9, 2024

how-to-build-a-multi-tenant-application-with-django

A multi-tenant application serves multiple customers (tenants) from a single instance of the application, with each tenant’s data isolated from the others. This approach benefits SaaS (Software as a Service) applications where multiple organizations or users need separate environments.

Here’s a detailed guide on how to build a multi-tenant application with Django:

1. Introduction to Multi-Tenancy

Types of Multi-Tenancy:

  • Database-per-tenant: Each tenant has its database.
  • Schema-per-tenant: All tenants share the same database but have separate schemas.
  • Shared-database, Shared-schema: All tenants share the same database and schema, with rows separated by a tenant identifier.

This guide will focus on the shared-database, shared-schema approach, which Django commonly uses and supports.

2. Setting Up the Project

Install Django and Create a New Project:


pip install django
django-admin startproject multitenant
cd multitenant

Create a New Django App:


python manage.py startapp core

3. Defining the Tenant Model

Tenant Model: This model will hold data about each tenant.


# core/models.py
from django.db import models

class Tenant(models.Model):
   name = models.CharField(max_length=100)
   domain = models.CharField(max_length=100, unique=True)

   def __str__(self):
       return self.name

Setting Up Tenant-Specific Models: Use a foreign key to associate models with tenants.


# core/models.py
from django.db import models

class Tenant(models.Model):
   name = models.CharField(max_length=100)
   domain = models.CharField(max_length=100, unique=True)

   def __str__(self):
       return self.name

4. Middleware for Tenant Identification

Create middleware to identify the tenant based on the request.

Middleware to Detect Tenant:


# core/middleware.py
from django.utils.deprecation import MiddlewareMixin
from core.models import Tenant

class TenantMiddleware(MiddlewareMixin):
   def process_request(self, request):
       domain = request.get_host().split(':')[0]
       try:
           request.tenant = Tenant.objects.get(domain=domain)
       except Tenant.DoesNotExist:
           request.tenant = None

Register Middleware:



# multitenant/settings.py
MIDDLEWARE = [
   ...
   'core.middleware.TenantMiddleware',
   ...
]

5. Query Filtering by Tenant

Ensure that the tenant filters queries.

Manager for Tenant Filtering:


# core/models.py
from django.db import models
from django.conf import settings

class TenantManager(models.Manager):
   def get_queryset(self):
       return super().get_queryset().filter(tenant=get_current_tenant())

def get_current_tenant():
   from threading import local
   _local = local()
   return getattr(_local, 'tenant', None)

class Customer(models.Model):
   tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE)
   name = models.CharField(max_length=100)
   email = models.EmailField()
   objects = TenantManager()

   def __str__(self):
       return self.name

Setting the Current Tenant:

Update the middleware to set the current tenant globally.


# core/middleware.py
from threading import local

_local = local()

class TenantMiddleware(MiddlewareMixin):
   def process_request(self, request):
       domain = request.get_host().split(':')[0]
       try:
           request.tenant = Tenant.objects.get(domain=domain)
           _local.tenant = request.tenant
       except Tenant.DoesNotExist:
           request.tenant = None
           _local.tenant = None


       return self.name

6. Admin and Views Adjustments

Ensure that Django Admin and views respect tenant boundaries.

Admin Customization:


# core/admin.py
from django.contrib import admin
from .models import Customer, Tenant

class TenantAdmin(admin.ModelAdmin):
   def get_queryset(self, request):
       qs = super().get_queryset(request)
       if request.tenant:
           return qs.filter(tenant=request.tenant)
       return qs

admin.site.register(Customer, TenantAdmin)
admin.site.register(Tenant)

Views and Forms:

Adjust views and forms to filter by tenant automatically.


# core/views.py
from django.shortcuts import render
from .models import Customer

def customer_list(request):
   customers = Customer.objects.filter(tenant=request.tenant)
   return render(request, 'core/customer_list.html', {'customers': customers})

7. Security and Testing

Security Considerations:

  • Isolate tenant data: Ensure strict data separation.
  • Authentication and Authorization: Implement proper checks to prevent unauthorized access.

Testing Multi-Tenancy:

Create tests to verify that data access is properly restricted based on the tenant.


# core/tests.py
from django.test import TestCase
from .models import Tenant, Customer

class TenantTestCase(TestCase):
   def setUp(self):
       self.tenant1 = Tenant.objects.create(name='Tenant 1', domain='tenant1.example.com')
       self.tenant2 = Tenant.objects.create(name='Tenant 2', domain='tenant2.example.com')
       Customer.objects.create(tenant=self.tenant1, name='Customer 1', email='customer1@example.com')
       Customer.objects.create(tenant=self.tenant2, name='Customer 2', email='customer2@example.com')

   def test_tenant_customers(self):
       self.client.get('/', HTTP_HOST='tenant1.example.com')
       customers = Customer.objects.all()
       self.assertEqual(customers.count(), 1)
       self.assertEqual(customers[0].name, 'Customer 1')

       self.client.get('/', HTTP_HOST='tenant2.example.com')
       customers = Customer.objects.all()
       self.assertEqual(customers.count(), 1)
       self.assertEqual(customers[0].name, 'Customer 2')

This approach sets up a basic multi-tenant architecture in Django, ensuring that each tenant’s data is isolated while sharing the same database and schema. This solution is scalable, efficient, and maintains data integrity across tenants.

To read more about enhancing application security with Django AuditLog, refer to our blog How to Enhance Application Security with Django AuditLog

Horilla Editorial Team Author

Horilla Editorial Team is a group of experienced writers and editors who are passionate about HR software. We have a deep understanding of the HR landscape and are committed to providing our readers with the most up-to-date and informative content. We have written extensively on a variety of HR software topics, including applicant tracking systems, performance management software, and payroll software etc. We are always looking for new ways to share our knowledge with the HR community. If you have a question about HR software, please don't hesitate to contact us.