Создание, удаление и переключение между тенантами программно и через админку.
Научитесь создавать, удалять и переключаться между тенантами программно и через админку.
Самый распространённый способ — создание через Django shell или код приложения:
from customers.models import Client, Domain
# Создаём клиента (тенанта)
client = Client.objects.create(
name='Acme Corporation',
description='Крупный клиент из сферы технологий',
active=True
)
# Создаём домен для клиента
domain = Domain.objects.create(
domain='acme.example.com',
tenant=client,
is_primary=True # Основной домен
)
print(f"Тенант создан: {client.name}")
print(f"Схема: {client.schema_name}")
print(f"Домен: {domain.domain}")save(), который проверяет auto_create_schemaauto_create_schema=True, создаётся новая схема PostgreSQLПо умолчанию schema_name генерируется автоматически из названия (например, acme_corporation). Можно указать своё:
client = Client.objects.create(
name='Acme Corporation',
schema_name='acme' # Кастомное имя схемы
)Требования к имени схемы:
public, pg_catalog)Если вы зарегистрировали модели в админке:
# customers/admin.py
from django.contrib import admin
from django_tenants.admin import TenantAdminMixin
from .models import Client, Domain
@admin.register(Client)
class ClientAdmin(TenantAdminMixin, admin.ModelAdmin):
list_display = ['name', 'schema_name', 'created_on', 'active']
list_filter = ['active', 'created_on']
search_fields = ['name', 'description']
@admin.register(Domain)
class DomainAdmin(admin.ModelAdmin):
list_display = ['domain', 'tenant', 'is_primary']
list_filter = ['is_primary']
search_fields = ['domain']Теперь можно создавать тенантов через /admin/.
В production переключение происходит автоматически через TenantMiddleware:
Запрос на acme.example.com → Middleware → schema_context('acme') → Запрос в схеме acme
Вам не нужно ничего делать — просто используйте домены.
Иногда нужно выполнить код в контексте конкретного тенанта:
from django_tenants.utils import schema_context
# Выполнить код в контексте тенанта 'acme'
with schema_context('acme'):
projects = Project.objects.all() # Запрос в схеме acme
print(f"Проектов: {projects.count()}")
# Вне контекста — запрос в текущей схеме (обычно public)from django_tenants.utils import tenant_context
client = Client.objects.get(schema_name='acme')
with tenant_context(client):
projects = Project.objects.all()
# Работа с данными тенантаfrom django_tenants.utils import get_current_schema_name, get_tenant_model
# Имя текущей схемы
schema = get_current_schema_name()
print(f"Текущая схема: {schema}")
# Объект текущего тенанта (если в tenant контексте)
Tenant = get_tenant_model()
tenant = Tenant.objects.get(schema_name=schema)from customers.models import Client, Domain
client = Client.objects.get(schema_name='acme')
# Удаление тенанта
client.delete()Что происходит:
Важно: Удаление необратимо! Все данные тенанта будут потеряны.
Иногда нужно удалить тенанта из системы, но сохранить данные:
client = Client.objects.get(schema_name='acme')
# Деактивировать без удаления
client.active = False
client.save()
# Или переименовать схему для архивации
from django.db import connection
with connection.cursor() as cursor:
cursor.execute("ALTER SCHEMA acme RENAME TO acme_archive")У тенанта может быть несколько доменов:
client = Client.objects.get(schema_name='acme')
# Основной домен
Domain.objects.create(
domain='acme.example.com',
tenant=client,
is_primary=True
)
# Дополнительные домены
Domain.objects.create(
domain='www.acme.example.com',
tenant=client,
is_primary=False
)
Domain.objects.create(
domain='acme.com',
tenant=client,
is_primary=False
)Все домены будут указывать на одну схему тенанта.
# Снять is_primary со всех доменов
client.domains.update(is_primary=False)
# Установить новый основной домен
primary_domain = client.domains.get(domain='acme.com')
primary_domain.is_primary = True
primary_domain.save()from django_tenants.utils import get_tenant_model, schema_context
Tenant = get_tenant_model()
for tenant in Tenant.objects.filter(active=True):
with tenant_context(tenant):
# Работа с данными каждого тенанта
projects_count = Project.objects.count()
print(f"{tenant.schema_name}: {projects_count} проектов")tenants_data = [
{'name': 'Acme', 'domain': 'acme.example.com'},
{'name': 'Globex', 'domain': 'globex.example.com'},
{'name': 'Initech', 'domain': 'initech.example.com'},
]
for data in tenants_data:
client = Client.objects.create(name=data['name'])
Domain.objects.create(
domain=data['domain'],
tenant=client,
is_primary=True
)Если вы добавили новую tenant app к существующим тенантам:
# Применить миграции ко всем существующим тенантам
python manage.py migrate_schemas --tenantСоздайте команду для удобного создания тенантов:
# customers/management/commands/create_tenant.py
from django.core.management.base import BaseCommand
from customers.models import Client, Domain
class Command(BaseCommand):
help = 'Создаёт нового тенанта'
def add_arguments(self, parser):
parser.add_argument('name', type=str)
parser.add_argument('domain', type=str)
parser.add_argument('--schema', type=str, help='Имя схемы (опционально)')
def handle(self, *args, **options):
name = options['name']
domain = options['domain']
schema = options.get('schema')
kwargs = {'name': name}
if schema:
kwargs['schema_name'] = schema
client = Client.objects.create(**kwargs)
Domain.objects.create(
domain=domain,
tenant=client,
is_primary=True
)
self.stdout.write(
self.style.SUCCESS(
f'Тенант "{name}" создан (схема: {client.schema_name})'
)
)Использование:
python manage.py create_tenant "Acme Corp" "acme.example.com"
python manage.py create_tenant "Globex" "globex.example.com" --schema=globexfrom customers.models import Client
for client in Client.objects.all().select_related('domains'):
print(f"{client.name} ({client.schema_name})")
for domain in client.domains.all():
print(f" - {domain.domain} {'(primary)' if domain.is_primary else ''}")from django.db import connection
def schema_exists(schema_name):
with connection.cursor() as cursor:
cursor.execute("""
SELECT schema_name
FROM information_schema.schemata
WHERE schema_name = %s
""", [schema_name])
return cursor.fetchone() is not None
print(schema_exists('acme')) # True или Falsefrom django.db import connection
def get_schema_size(schema_name):
with connection.cursor() as cursor:
cursor.execute("""
SELECT pg_size_pretty(
pg_total_relation_size(quote_ident(schemaname) || '.' || quote_ident(tablename))
)
FROM pg_tables
WHERE schemaname = %s
""", [schema_name])
return cursor.fetchall()# Неправильно — тенант без домена не будет доступен
client = Client.objects.create(name='Acme')
# Нет Domain.objects.create(...)
# Правильно
client = Client.objects.create(name='Acme')
Domain.objects.create(
domain='acme.example.com',
tenant=client,
is_primary=True
)# Неправильно — запрос выполнится в public schema
projects = Project.objects.all()
# Правильно
with schema_context('acme'):
projects = Project.objects.all()# Никогда не удаляйте public тенанта!
# Это сломает всё приложение# Неправильно — 'public' зарезервировано
client = Client.objects.create(
name='Public Corp',
schema_name='public' # Ошибка!
)
# Правильно — уникальное имя
client = Client.objects.create(
name='Public Corp',
schema_name='public_corp'
)Теперь вы умеете управлять тенантами. В следующей теме вы узнаете об изоляции данных и работе с общими данными.
Переходите к следующей теме → Изоляция данных и общие данные
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.