Обновления в tenants: админ, модели, миграции и сервисы

This commit is contained in:
2026-01-08 22:13:20 +03:00
parent 7f91244d63
commit 76acf419fc
4 changed files with 100 additions and 34 deletions

View File

@@ -5,10 +5,10 @@
Единственный источник истины для создания тенанта и всех связанных сущностей.
"""
import logging
import secrets
from django.db import connection, transaction
from django.utils import timezone
from django.conf import settings
from django.contrib.auth import get_user_model
from django.core.management import call_command
from tenants.models import Client, Domain, TenantRegistration, Subscription
@@ -25,7 +25,7 @@ class TenantOnboardingService:
- Создание Domain (домен)
- Создание Subscription (подписка)
- Миграции схемы
- Создание пользователей (admin, owner)
- Создание владельца (owner)
- Инициализация системных данных
"""
@@ -63,7 +63,8 @@ class TenantOnboardingService:
# 5. Создание подписки
subscription = cls._create_subscription(client)
# 6. Инициализация данных тенанта (в контексте схемы)
# 6. Инициализация данных тенанта (в контексте tenant schema)
# CustomUser теперь в TENANT_APPS - создаётся в схеме тенанта
connection.set_tenant(client)
try:
cls._init_tenant_users(registration)
@@ -71,7 +72,7 @@ class TenantOnboardingService:
finally:
connection.set_schema_to_public()
# 7. Отправка email владельцу
# 8. Отправка email владельцу
cls._send_welcome_email(registration)
# 8. Обновление статуса заявки
@@ -199,28 +200,23 @@ class TenantOnboardingService:
@classmethod
def _init_tenant_users(cls, registration: TenantRegistration):
"""Создание пользователей тенанта (admin и owner)."""
User = get_user_model()
"""
Создание пользователей тенанта: владелец + техподдержка платформы.
# Суперпользователь (системный admin)
if not User.objects.filter(email=settings.TENANT_ADMIN_EMAIL).exists():
superuser = User.objects.create_superuser(
email=settings.TENANT_ADMIN_EMAIL,
name=settings.TENANT_ADMIN_NAME,
password=settings.TENANT_ADMIN_PASSWORD
)
logger.info(f"Суперпользователь создан: {superuser.id}")
else:
logger.warning(f"Пользователь {settings.TENANT_ADMIN_EMAIL} уже существует")
ВАЖНО: CustomUser в TENANT_APPS - создаётся в схеме тенанта.
Вызывается в контексте tenant schema (connection.set_tenant уже выполнен).
# Владелец тенанта
if not User.objects.filter(email=registration.owner_email).exists():
owner = User.objects.create_user(
Django Admin доступна только для PlatformAdmin (из public schema).
CustomUser используют только фронтенд.
"""
from accounts.models import CustomUser
# 1. Владелец тенанта
if not CustomUser.objects.filter(email=registration.owner_email).exists():
owner = CustomUser.objects.create_user(
email=registration.owner_email,
name=registration.owner_name,
password=None, # Пароль будет установлен через ссылку
is_staff=False,
is_superuser=False
)
owner.is_email_confirmed = True
owner.email_confirmed_at = timezone.now()
@@ -228,11 +224,14 @@ class TenantOnboardingService:
owner.save()
logger.info(f"Аккаунт владельца создан: {owner.id}")
# Роли
# Назначаем роль owner
cls._assign_owner_role(owner)
else:
logger.warning(f"Пользователь {registration.owner_email} уже существует")
# 2. Техподдержка платформы (скрытый аккаунт)
cls._create_platform_support_user(registration)
@classmethod
def _assign_owner_role(cls, owner):
"""Назначение роли owner владельцу тенанта."""
@@ -246,6 +245,51 @@ class TenantOnboardingService:
except Exception as e:
logger.error(f"Ошибка при назначении роли: {e}", exc_info=True)
@classmethod
def _create_platform_support_user(cls, registration: TenantRegistration):
"""
Создание скрытого аккаунта техподдержки платформы.
- Email берётся из настроек PLATFORM_SUPPORT_EMAIL
- Пароль генерируется уникальный для каждого тенанта
- Пароль выводится в лог (для владельца платформы)
- Пользователь не виден владельцу тенанта
"""
from accounts.models import CustomUser
from user_roles.services import RoleService
from user_roles.models import Role
support_email = getattr(settings, 'PLATFORM_SUPPORT_EMAIL', None)
if not support_email:
logger.info("PLATFORM_SUPPORT_EMAIL не задан - пропускаем создание техподдержки")
return
if CustomUser.objects.filter(email=support_email).exists():
logger.info(f"Техподдержка {support_email} уже существует в этом тенанте")
return
# Генерируем уникальный пароль для этого тенанта
password = secrets.token_urlsafe(16)
support_user = CustomUser.objects.create_user(
email=support_email,
name='Техподдержка',
password=password,
)
support_user.is_email_confirmed = True
support_user.email_confirmed_at = timezone.now()
support_user.is_active = True
support_user.save()
# Назначаем роль platform_support
RoleService.assign_role_to_user(support_user, Role.PLATFORM_SUPPORT, created_by=None)
# Выводим пароль в лог (безопасно, т.к. логи доступны только владельцу платформы)
logger.info(
f"[PLATFORM_SUPPORT] Тенант: {registration.schema_name} | "
f"Email: {support_email} | Пароль: {password}"
)
@classmethod
def _init_tenant_data(cls):
"""Инициализация системных данных тенанта."""
@@ -271,7 +315,18 @@ class TenantOnboardingService:
"""Финализация заявки на регистрацию."""
registration.status = TenantRegistration.STATUS_APPROVED
registration.processed_at = timezone.now()
registration.processed_by = admin_user
# admin_user теперь должен быть PlatformAdmin
if admin_user is not None:
from platform_admin.models import PlatformAdmin
if isinstance(admin_user, PlatformAdmin):
registration.processed_by = admin_user
else:
logger.warning(f"admin_user не является PlatformAdmin: {type(admin_user)}")
registration.processed_by = None
else:
registration.processed_by = None
registration.tenant = client
registration.save()
logger.info(f"Заявка {registration.id} финализирована")