# -*- coding: utf-8 -*- """ Management команда для создания нового тенанта (магазина). Использование: python manage.py create_tenant """ from django.core.management.base import BaseCommand from django.db import transaction from tenants.models import Client, Domain import re class Command(BaseCommand): help = 'Создать нового тенанта (магазин) с собственной схемой БД' def handle(self, *args, **options): self.stdout.write(self.style.SUCCESS('\n=== Создание нового магазина (тенанта) ===\n')) # Получаем данные от пользователя name = self.get_shop_name() schema_name = self.get_schema_name() domain_name = self.get_domain_name(schema_name) owner_name = self.get_owner_name() owner_email = self.get_owner_email() phone = input('Телефон владельца (опционально): ').strip() # Подтверждение self.stdout.write('\n' + '='*60) self.stdout.write(self.style.WARNING('Проверьте введенные данные:')) self.stdout.write(f'Название магазина: {name}') self.stdout.write(f'Схема БД: {schema_name}') self.stdout.write(f'Домен: {domain_name}') self.stdout.write(f'Владелец: {owner_name} ({owner_email})') if phone: self.stdout.write(f'Телефон: {phone}') self.stdout.write('='*60 + '\n') confirm = input('Создать магазин? (yes/no): ').strip().lower() if confirm not in ['yes', 'y', 'да']: self.stdout.write(self.style.ERROR('Отменено')) return # Создаем тенанта try: with transaction.atomic(): # Создаем тенанта self.stdout.write('Создание тенанта...') tenant = Client.objects.create( schema_name=schema_name, name=name, owner_name=owner_name, owner_email=owner_email, phone=phone if phone else None, ) self.stdout.write(self.style.SUCCESS(f'✓ Тенант создан: {tenant}')) # Создаем домен self.stdout.write(f'Создание домена {domain_name}...') domain = Domain.objects.create( domain=domain_name, tenant=tenant, is_primary=True ) self.stdout.write(self.style.SUCCESS(f'✓ Домен создан: {domain}')) self.stdout.write('\n' + '='*60) self.stdout.write(self.style.SUCCESS('✓ Магазин успешно создан!')) self.stdout.write('='*60 + '\n') self.stdout.write(self.style.WARNING('Следующие шаги:')) self.stdout.write(f'1. Добавьте в hosts файл: 127.0.0.1 {domain_name}') self.stdout.write(f'2. Откройте в браузере: http://{domain_name}:8000/admin/') self.stdout.write(f'3. Схема БД "{schema_name}" создана автоматически') self.stdout.write(f'4. Все таблицы тенанта созданы в схеме "{schema_name}"') self.stdout.write('') except Exception as e: self.stdout.write(self.style.ERROR(f'\n✗ Ошибка при создании тенанта: {e}')) raise def get_shop_name(self): """Получить название магазина""" while True: name = input('Название магазина: ').strip() if name: return name self.stdout.write(self.style.ERROR('Название не может быть пустым')) def get_schema_name(self): """Получить имя схемы БД""" while True: schema = input('Имя схемы БД (латиница, цифры, подчеркивания): ').strip().lower() # Валидация if not schema: self.stdout.write(self.style.ERROR('Имя схемы не может быть пустым')) continue if not re.match(r'^[a-z0-9_]+$', schema): self.stdout.write(self.style.ERROR('Только латинские буквы, цифры и подчеркивания')) continue if schema in ['public', 'postgres', 'information_schema', 'pg_catalog']: self.stdout.write(self.style.ERROR('Это зарезервированное имя схемы')) continue # Проверка существования if Client.objects.filter(schema_name=schema).exists(): self.stdout.write(self.style.ERROR(f'Схема "{schema}" уже существует')) continue return schema def get_domain_name(self, default_subdomain): """Получить доменное имя""" while True: default_domain = f'{default_subdomain}.localhost' domain = input(f'Доменное имя [{default_domain}]: ').strip().lower() if not domain: domain = default_domain # Валидация if not re.match(r'^[a-z0-9.-]+$', domain): self.stdout.write(self.style.ERROR('Неверный формат домена')) continue # Проверка существования if Domain.objects.filter(domain=domain).exists(): self.stdout.write(self.style.ERROR(f'Домен "{domain}" уже используется')) continue return domain def get_owner_name(self): """Получить имя владельца""" while True: name = input('Имя владельца: ').strip() if name: return name self.stdout.write(self.style.ERROR('Имя не может быть пустым')) def get_owner_email(self): """Получить email владельца""" while True: email = input('Email владельца: ').strip().lower() # Простая валидация email if not email: self.stdout.write(self.style.ERROR('Email не может быть пустым')) continue if '@' not in email or '.' not in email: self.stdout.write(self.style.ERROR('Неверный формат email')) continue return email