Как создавать модели для публичной и tenant-схем, как работают миграции в django-tenants и какие есть особенности.
Понимание различий между SharedModel и TenantModel, а также работа с миграциями — ключ к успешной мультитенантной архитектуре.
В django-tenants существуют два типа моделей, и понимание разницы между ними критически важно:
Модели, наследующиеся от SharedModel, хранятся только в public schema. Их данные видны всем тенантам.
from django_tenants.models import SharedModel
class Plan(SharedModel):
name = models.CharField(max_length=100)
price = models.DecimalField(max_digits=10, decimal_places=2)
max_users = models.IntegerField(default=10)
def __str__(self):
return self.nameКогда использовать SharedModel:
Модели, наследующиеся от TenantModel, создаются в схеме каждого тенанта. Данные изолированы.
from django_tenants.models import TenantModel
class Project(TenantModel):
name = models.CharField(max_length=200)
description = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-created_at']Когда использовать TenantModel:
При запуске migrate_schemas --shared:
-- Создаётся в public schema
CREATE TABLE billing_plan (
id serial PRIMARY KEY,
name varchar(100),
price decimal(10,2),
max_users integer
);При запуске migrate_schemas --tenant:
-- Создаётся в public schema как шаблон
-- И автоматически создаётся в схеме каждого нового тенанта
CREATE TABLE projects_project (
id serial PRIMARY KEY,
name varchar(200),
description text,
created_at timestamp
);# Запрос к SharedModel — всегда в public schema
plans = Plan.objects.all() # SELECT * FROM billing_plan
# Запрос к TenantModel — в схеме текущего тенанта
projects = Project.objects.all() # SELECT * FROM acme.projects_projectВам не нужно указывать схему — django-tenants делает это автоматически на основе:
schema_context()Создание миграций не отличается от обычного Django:
python manage.py makemigrationsdjango-tenants автоматически определит, какие модели относятся к shared, а какие к tenant.
Важно: В django-tenants используется другой подход к применению миграций.
python manage.py migrate_schemas --sharedЭта команда:
SHARED_APPSpython manage.py migrate_schemas --tenantЭта команда:
TENANT_APPSПри создании нового тенанта с auto_create_schema = True:
client = Client.objects.create(name='New Client')django-tenants автоматически:
new_client)# Проверка миграций для shared apps
python manage.py migrate_schemas --shared --plan
# Проверка миграций для tenant apps
python manage.py migrate_schemas --tenant --planМожно создать связь от tenant модели к shared модели:
from django_tenants.models import TenantModel, SharedModel
class Plan(SharedModel):
name = models.CharField(max_length=100)
class Project(TenantModel):
name = models.CharField(max_length=200)
# Связь с shared моделью — ОК
plan = models.ForeignKey('billing.Plan', on_delete=models.PROTECT)Это работает, потому что shared таблицы находятся в public schema, которая видна из всех tenant схем.
Нельзя создать связь от shared модели к tenant модели:
# НЕПРАВИЛЬНО — не будет работать!
class Plan(SharedModel):
name = models.CharField(max_length=100)
# Ошибка: нельзя ссылаться на tenant модель из shared
default_project = models.ForeignKey('projects.Project', on_delete=models.SET_NULL)Почему: Shared таблица в public schema не может ссылаться на tenant таблицу, которая существует в множестве схем.
Связи между tenant моделями работают нормально:
class Project(TenantModel):
name = models.CharField(max_length=200)
class Task(TenantModel):
title = models.CharField(max_length=200)
# Связь между tenant моделями — ОК
project = models.ForeignKey('projects.Project', on_delete=models.CASCADE)Всегда указывайте app_label в Meta классе tenant моделей:
class Project(TenantModel):
name = models.CharField(max_length=200)
class Meta:
app_label = 'projects'
db_table = 'projects_project' # ОпциональноЭто помогает django-tenants правильно определить, к какому приложению относится модель.
Избегайте одинаковых имён таблиц в shared и tenant apps:
# billing/models.py
class Plan(SharedModel):
class Meta:
db_table = 'billing_plan' # Уникальное имя
# projects/models.py
class Plan(TenantModel): # Избегайте одинаковых имён!
class Meta:
db_table = 'projects_plan'Лучше использовать уникальные имена моделей в разных приложениях.
Наследование от SharedModel и TenantModel работает, но есть нюансы:
# Базовый класс для всех проектов
class BaseProject(TenantModel):
name = models.CharField(max_length=200)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
abstract = True # Абстрактная модель
class Project(BaseProject):
description = models.TextField()
# Concrete модель, будет создана в схеме тенантаВажно: Абстрактные базовые классы могут наследоваться от TenantModel или SharedModel. Concrete модели наследуют тип родителя.
M2M связи работают для обоих типов моделей:
class Project(TenantModel):
name = models.CharField(max_length=200)
# M2M к другой tenant модели
members = models.ManyToManyField('auth.User') # User — shared модельПромежуточная таблица создаётся в той же схеме, что и модель:
Можно создавать кастомные менеджеры для tenant моделей:
class ProjectManager(models.Manager):
def active(self):
return self.filter(status='active')
class Project(TenantModel):
name = models.CharField(max_length=200)
status = models.CharField(max_length=20, default='active')
objects = ProjectManager()Менеджер работает прозрачно в контексте любого тенанта.
from django_tenants.utils import get_current_schema_name
schema = get_current_schema_name()
print(f"Текущая схема: {schema}") # 'public' или 'acme_inc'from projects.models import Project
from billing.models import Plan
print(Project._meta.db_table) # projects_project
print(Plan._meta.db_table) # billing_plan
# Проверка типа модели
from django_tenants.models import TenantModel, SharedModel
print(issubclass(Project, TenantModel)) # True
print(issubclass(Plan, SharedModel)) # TrueДля отладки включите логирование SQL:
# settings.py
LOGGING = {
'version': 1,
'handlers': {
'console': {'class': 'logging.StreamHandler'},
},
'loggers': {
'django.db.backends': {
'handlers': ['console'],
'level': 'DEBUG',
},
},
}Вы увидите, в какой схеме выполняются запросы:
SET search_path TO acme_inc;
SELECT * FROM projects_project;
# Неправильно — данные будут в public schema
class Project(models.Model):
name = models.CharField(max_length=200)
# Правильно — данные в схеме тенанта
class Project(TenantModel):
name = models.CharField(max_length=200)# Неправильно
class Plan(SharedModel):
project = models.ForeignKey('projects.Project') # Ошибка!
# Правильно
class Plan(SharedModel):
pass # Без связей к tenant моделям# Может вызвать проблемы
class Project(TenantModel):
name = models.CharField(max_length=200)
# Нет Meta.app_label
# Правильно
class Project(TenantModel):
name = models.CharField(max_length=200)
class Meta:
app_label = 'projects'# Неправильно
python manage.py migrate
# Правильно
python manage.py migrate_schemas --shared
python manage.py migrate_schemas --tenantТеперь вы понимаете, как работать с моделями и миграциями. В следующей теме вы научитесь управлять тенантами: создавать, удалять и переключаться между ними.
Переходите к следующей теме → Операции с тенантами
Вопросы ещё не добавлены
Вопросы для этой подтемы ещё не добавлены.