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 Domain
as 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 TenantTestCase
which 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.com
for 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.