Multi-tenant in Django

In Django, multi-tenant implementation is implemented quite often and there is a library for this django-multitenant.

Let's install and configure

Let's install django-multitenant via pip:

pip install django-multitenant

Once installed, it's time to add django_multitenant V INSTALLED_APPS Django project settings.py.

You need to update your database settings using 'ENGINE': 'django_tenants.postgresql_backend'to enable support for, for example, PostgreSQL schemas:

DATABASES = {
    'default': {
        'ENGINE': 'django_tenants.postgresql_backend',
        'NAME': 'your_db_name',
        'USER': 'your_db_user',
        'PASSWORD': 'your_db_password',
        'HOST': 'your_db_host',
        'PORT': 'your_db_port',
    }
}

You also need to add django_tenants to the list of installed applications:

INSTALLED_APPS = [
    ...
    'django_tenants',
    ...
]

Working with tenants

To create a tenant and associated domains, you need to define models Tenant And Domainas described in the basic settings:

# models.py
from django_tenants.models import TenantMixin, DomainMixin

class Client(TenantMixin):
    name = models.CharField(max_length=100)

class Domain(DomainMixin):
    pass

After creating the models, you can programmatically add new tenants and domains:

# добавление нового тенанта
from your_app.models import Client, Domain

tenant = Client(schema_name="new_tenant", name="New Tenant")
tenant.save()  # сначала сохраняем тенанта

# добавление домена для тенанта
domain = Domain(domain='newtenant.example.com', tenant=tenant, is_primary=True)
domain.save()

To apply migrations to a specific tenant’s schema, there is a command migrate_schemas:

python manage.py migrate_schemas --schema=new_tenant

In some cases, dynamic creation of tenants may be required:

def create_tenant(user):
    new_schema_name = generate_schema_name(user)
    new_tenant = Client(schema_name=new_schema_name, name=user.company_name)
    new_tenant.save()

    domain = Domain(domain=f"{new_schema_name}.yourdomain.com", tenant=new_tenant, is_primary=True)
    domain.save()

Can I usemiddlewarewhich help determine which tenant the current request belongs to:

MIDDLEWARE = [
    'django_tenants.middleware.main.TenantMainMiddleware',
    # другие middleware...
]

IN django-tenants there is isolation of static and media files between tenants. The paths for these files are configured in settings.py:

STATIC_URL = '/static/'
MEDIA_URL = '/media/'

# django-tenants для управления файлами
MULTITENANT_RELATIVE_MEDIA_ROOT = '/tenant_media/'

Tests

django-tenants provides a class TenantTestCasewhich is a subclass django.test.TestCase. TenantTestCase automatically creates a public tenant before running tests and deletes it after:

from django_tenants.test.cases import TenantTestCase

class YourTenantTest(TenantTestCase):
    def test_something(self):
        # тестовый код

Inside TenantTestCase You can create additional tenants to test scenarios that require interaction between different tenants:

from django_tenants.utils import tenant_context

class MultiTenantTest(TenantTestCase):
    def test_multi_tenant_interaction(self):
        # создание нового тенанта для теста
        new_tenant = self.create_tenant(domain_url="newtenant.test.com", schema_name="newtenant")
        # использование контекста тенанта для тестирования взаимодействия
        with tenant_context(new_tenant):
            # тестовый код

You can manage migrations in test scenarios:

from django_tenants.test.cases import FastTenantTestCase

class YourMigrationTest(FastTenantTestCase):
    def test_migration(self):
        # тест

There is also FastTenantTestCase which is faster TenantTestCase by minimizing database operations.

Can work with DNS

IN settings.py project, you can determine which applications are common to all tenants SHARED_APPS and which applications are unique to each tenant TENANT_APPS:

# settings.py

SHARED_APPS = [
    'django_tenants',  # обязательно
    'your_app',  # здесь указывается имя приложения
    # другие общие приложения
]

TENANT_APPS = [
    'django.contrib.contenttypes',
    # приложения специфичные для тенантов
]

INSTALLED_APPS = list(SHARED_APPS) + [app for app in TENANT_APPS if app not in SHARED_APPS]

To process requests to different tenants, you need to configure the URL configuration accordingly using django-tenants URL router:

# urls.py
from django.conf.urls import url
from django_tenants.utils import tenant_urlpatterns

urlpatterns = [
    # URL-конфигурации
]

urlpatterns += tenant_urlpatterns([
    # URL-конфигурации специфичные для тенантов
])

On the DNS side, for subdomains representing different tenants, you need to create corresponding A or CNAME records pointing to the IP address of the server where the Django project is hosted.

For example, if the main domain example.comfor tenant tenant1 there will be a record like this:

When creating a new tenant, you can automatically add domain records for it using Django signals or overriding the method save Tenant models:

# models.py
from django.db.models.signals import post_save
from django.dispatch import receiver

@receiver(post_save, sender=Client)
def create_domain_for_new_tenant(sender, instance, created, **kwargs):
    if created:
        Domain.objects.create(domain='{}.example.com'.format(instance.name.lower()), tenant=instance, is_primary=True)

More details can be found in the documentation check it out here.

You can learn more about application architecture in online courses from practicing industry experts. Details in the catalog.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *