feat: Добавить автоматическое создание суперпользователей для тенантов

Реализована система автоматического создания суперпользователей при активации
новых тенантов (магазинов). Credentials читаются из .env файла.

Изменения:
- Подключен django-environ для управления переменными окружения
- Обновлен settings.py: SECRET_KEY, DEBUG, DATABASE теперь из .env
- Добавлены настройки TENANT_ADMIN_EMAIL, TENANT_ADMIN_PASSWORD, TENANT_ADMIN_NAME
- Обновлен tenants/admin.py: автоматическое создание superuser при активации
- Создан activate_tenant.py: универсальный скрипт активации заявок
- Обновлен activate_mixflowers.py: добавлено создание superuser
- Создан .gitignore для защиты секретов
- Добавлена документация TENANT_ADMIN_GUIDE.md

Использование:
1. Через админку: Заявки → Активировать (автоматически создаст superuser)
2. Через скрипт: python activate_tenant.py <schema_name>

Доступ к админке тенанта:
- URL: http://{schema_name}.localhost:8000/admin/
- Email: admin@localhost (из .env)
- Password: 1234 (из .env)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-10-27 18:20:26 +03:00
parent a55d0405ed
commit 4b44624f86
6 changed files with 1164 additions and 70 deletions

View File

@@ -1,51 +1,94 @@
# -*- coding: utf-8 -*-
"""
Django settings for myproject project.
Django settings for myproject project with django-tenants support.
Generated by 'django-admin startproject' using Django 5.2.7.
This is a multi-tenant SaaS application where each shop owner gets their own subdomain
and isolated database schema.
For more information on this file, see
https://docs.djangoproject.com/en/5.2/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.2/ref/settings/
Example: shop1.inventory.by, shop2.inventory.by
"""
from pathlib import Path
import environ
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Initialize environment variables
env = environ.Env(
# Set casting and default values
DEBUG=(bool, True),
SECRET_KEY=(str, 'django-insecure-default-key-change-in-production'),
)
# Read .env file
environ.Env.read_env(BASE_DIR / '.env')
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-bs^tx8!&v2qx9!)i0!%*p#=kwn&@x0%r6i3&l-3z14bw%k5yj-'
SECRET_KEY = env('SECRET_KEY')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
DEBUG = env('DEBUG')
ALLOWED_HOSTS = []
ALLOWED_HOSTS = ['*'] # Для разработки. В продакшене указать конкретные домены
# Application definition
# ============================================
# DJANGO-TENANTS CONFIGURATION
# ============================================
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
# Shared apps: доступны в public схеме (общие для всей системы)
SHARED_APPS = [
'django_tenants', # ОБЯЗАТЕЛЬНО ПЕРВЫМ!
'tenants', # Приложение с моделями тенантов
# Django встроенные приложения
'django.contrib.contenttypes',
'django.contrib.auth',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.admin',
'django.contrib.staticfiles',
'nested_admin', # Для вложенных inline в админке
# Accounts должен быть в shared для CustomUser (используется в админке)
'accounts',
'products',
'inventory',
'orders',
'customers',
]
# Tenant apps: создаются в отдельной схеме для каждого тенанта (изолированные данные)
TENANT_APPS = [
'django.contrib.contenttypes', # Дублируем для tenant схем
'django.contrib.auth', # Дублируем для tenant схем
# Приложения с бизнес-логикой (изолированные для каждого магазина)
'nested_admin',
'customers', # Клиенты магазина
'shops', # Точки магазина/самовывоза
'products', # Товары и категории
'orders', # Заказы
'inventory', # Складской учет
]
# Объединяем для INSTALLED_APPS
INSTALLED_APPS = list(SHARED_APPS) + [app for app in TENANT_APPS if app not in SHARED_APPS]
# Модели тенанта и домена
TENANT_MODEL = "tenants.Client"
TENANT_DOMAIN_MODEL = "tenants.Domain"
# Показывать tenant_id в логах (полезно для отладки)
SHOW_PUBLIC_IF_NO_TENANT_FOUND = True
# ============================================
# MIDDLEWARE
# ============================================
MIDDLEWARE = [
'django_tenants.middleware.main.TenantMainMiddleware', # ОБЯЗАТЕЛЬНО ПЕРВЫМ!
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
@@ -55,12 +98,25 @@ MIDDLEWARE = [
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
# ============================================
# URL CONFIGURATION
# ============================================
ROOT_URLCONF = 'myproject.urls'
# URL-конфигурация для public схемы (главный домен inventory.by)
PUBLIC_SCHEMA_URLCONF = 'myproject.urls_public'
# ============================================
# TEMPLATES
# ============================================
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'], # Добавили путь к шаблонам
'DIRS': [BASE_DIR / 'templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
@@ -75,19 +131,35 @@ TEMPLATES = [
WSGI_APPLICATION = 'myproject.wsgi.application'
# Database
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases
# ============================================
# DATABASE CONFIGURATION
# ============================================
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
'ENGINE': 'django_tenants.postgresql_backend', # ВАЖНО: используем backend от django-tenants
'NAME': env('DB_NAME'),
'USER': env('DB_USER'),
'PASSWORD': env('DB_PASSWORD'),
'HOST': env('DB_HOST'),
'PORT': env('DB_PORT'),
'OPTIONS': {
'client_encoding': 'UTF8',
'connect_timeout': 10,
},
'CONN_MAX_AGE': 0,
}
}
# Database router для django-tenants
DATABASE_ROUTERS = [
'django_tenants.routers.TenantSyncRouter',
]
# Password validation
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
# ============================================
# PASSWORD VALIDATION
# ============================================
AUTH_PASSWORD_VALIDATORS = [
{
@@ -105,34 +177,36 @@ AUTH_PASSWORD_VALIDATORS = [
]
# Internationalization
# https://docs.djangoproject.com/en/5.2/topics/i18n/
LANGUAGE_CODE = 'ru-ru' # Изменили на русский
TIME_ZONE = 'Europe/Moscow' # Установили таймзону
# ============================================
# INTERNATIONALIZATION
# ============================================
LANGUAGE_CODE = 'ru-ru'
TIME_ZONE = 'Europe/Moscow'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.2/howto/static-files/
# ============================================
# STATIC FILES (CSS, JavaScript, Images)
# ============================================
STATIC_URL = 'static/'
STATICFILES_DIRS = [BASE_DIR / 'static'] # Добавили директорию для статических файлов
STATIC_ROOT = BASE_DIR / 'staticfiles' # Для collectstatic
STATIC_URL = '/static/'
STATICFILES_DIRS = [BASE_DIR / 'static']
STATIC_ROOT = BASE_DIR / 'staticfiles'
# ============================================
# MEDIA FILES (User uploads)
# ============================================
# Media files (User uploads)
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'
# ============================================
# IMAGE PROCESSING SETTINGS
# ============================================
# Конфигурация для обработки изображений товаров, комплектов и категорий
# Определяет размеры, форматы и качество для каждого типа изображения
IMAGE_PROCESSING_CONFIG = {
'formats': {
@@ -164,42 +238,49 @@ IMAGE_PROCESSING_CONFIG = {
'height': 200,
'description': 'Thumbnail (200x200, WebP format)'
},
},
'folders': {
'original': 'originals',
'large': 'large',
'medium': 'medium',
'thumbnail': 'thumbnails',
}
}
# Настройки категорий товаров
# Максимальная глубина вложенности категорий (защита от слишком глубокой иерархии)
# ============================================
# BUSINESS LOGIC SETTINGS
# ============================================
# Максимальная глубина вложенности категорий товаров
MAX_CATEGORY_DEPTH = 10
# Default primary key field type
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# Настройки для отправки email в консоль
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
DEFAULT_FROM_EMAIL = 'noreply@example.com'
# Настройки телефонных номеров
PHONENUMBER_DEFAULT_REGION = 'BY' # Регион по умолчанию для номеров без кода страны
PHONENUMBER_DEFAULT_REGION = 'BY'
# ============================================
# EMAIL SETTINGS
# ============================================
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
DEFAULT_FROM_EMAIL = 'noreply@inventory.by'
# ============================================
# AUTHENTICATION
# ============================================
# Указываем нашу кастомную модель пользователя
AUTH_USER_MODEL = 'accounts.CustomUser'
# ВРЕМЕННЫЙ ФИХ для SQLite: удалить когда база данных будет PostgreSQL
# Регистрируем кастомную функцию LOWER для поддержки кириллицы в SQLite
if 'sqlite' in DATABASES['default']['ENGINE']:
from django.db.backends.signals import connection_created
from django.dispatch import receiver
@receiver(connection_created)
def setup_sqlite_unicode_support(sender, connection, **kwargs):
"""Добавляет поддержку Unicode для LOWER() в SQLite"""
if connection.vendor == 'sqlite':
connection.connection.create_function('LOWER', 1, lambda s: s.lower() if s else s)
# ============================================
# TENANT ADMIN AUTO-CREATION
# ============================================
# При создании нового тенанта автоматически создается суперпользователь
# с указанными credentials для доступа к админке тенанта
TENANT_ADMIN_EMAIL = env('TENANT_ADMIN_EMAIL')
TENANT_ADMIN_PASSWORD = env('TENANT_ADMIN_PASSWORD')
TENANT_ADMIN_NAME = env('TENANT_ADMIN_NAME')
# ============================================
# DEFAULT SETTINGS
# ============================================
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'