# Удаление Тенантов в Django-Tenants ## ⚠️ КРИТИЧЕСКИ ВАЖНО **В этом проекте `auto_drop_schema = False`, поэтому ВСЕ команды удаления (включая `cleanup_tenant` и `delete_tenant`) НЕ удаляют схему из PostgreSQL автоматически!** После удаления тенанта через любую команду **ОБЯЗАТЕЛЬНО** нужно вручную удалить схему из базы данных, иначе: - ✅ Запись `Client` удалится - ❌ Схема со всеми таблицами и данными останется в PostgreSQL - ❌ При повторной регистрации с тем же именем новый тенант увидит старые данные! ### Как удалить схему вручную: **Вариант 1 — через Django shell:** ```python from django.db import connection with connection.cursor() as cursor: cursor.execute('DROP SCHEMA IF EXISTS имя_схемы CASCADE;') ``` **Вариант 2 — одной командой из консоли:** ```bash python manage.py shell -c "from django.db import connection; cur = connection.cursor(); cur.execute('DROP SCHEMA IF EXISTS имя_схемы CASCADE;')" ``` **Вариант 3 — напрямую в PostgreSQL (psql):** ```sql DROP SCHEMA IF EXISTS имя_схемы CASCADE; ``` --- ## Быстрая справка ### Рекомендуемый способ (с улучшенной командой): ```bash # Базовое удаление (Client + файлы, заявка остается в истории) python manage.py cleanup_tenant --schema=papa --noinput # Полная очистка (Client + заявка + файлы) python manage.py cleanup_tenant --schema=papa --noinput --purge-registration --delete-files # ЗАТЕМ ВРУЧНУЮ удалить схему из PostgreSQL: python manage.py shell -c "from django.db import connection; cur = connection.cursor(); cur.execute('DROP SCHEMA IF EXISTS papa CASCADE;')" ``` ### Альтернативный способ (встроенная команда django-tenants): ⚠️ **ВНИМАНИЕ:** В этом проекте `auto_drop_schema = False`, поэтому команда `delete_tenant` **НЕ удаляет схему из PostgreSQL**. Удаляется только запись `Client`, но все таблицы и данные в схеме остаются в базе. Для полного удаления используйте `cleanup_tenant` (см. выше) или удалите схему вручную. ```bash # Удалить конкретного тенанта (только запись Client, схема БД остаётся!) python manage.py delete_tenant --schema=papa --noinput # Удалить все файлы тенанта (после удаления из БД) Remove-Item -Path 'media/tenants' -Recurse -Force ``` --- ## Подробное руководство ### ⭐ Способ 0: Новая улучшенная команда cleanup_tenant (РЕКОМЕНДУЕТСЯ) **Эта команда решает проблему с TenantRegistration и управляет связанными данными** #### Что это за команда? Это новая management команда, которая автоматически: - Удаляет запись Client из таблицы тенантов - Обрабатывает TenantRegistration (может оставить в истории или удалить) - Опционально удаляет физические файлы - Показывает красивый прогресс с подтверждением ⚠️ **ВАЖНО:** Команда **НЕ удаляет схему PostgreSQL** (т.к. `auto_drop_schema = False`). После выполнения команды нужно вручную удалить схему через SQL (см. раздел выше). #### Параметры: ```bash --schema=<имя> # Имя тенанта (обязательно) --noinput # Не запрашивать подтверждение --purge-registration # Удалить TenantRegistration (иначе оставляет с tenant=NULL) --delete-files # Удалить физические файлы из /media/tenants/ ``` #### Варианты использования: **1️⃣ Базовое удаление (рекомендуется):** ```bash python manage.py cleanup_tenant --schema=papa --noinput # ЗАТЕМ вручную: python manage.py shell -c "from django.db import connection; cur = connection.cursor(); cur.execute('DROP SCHEMA IF EXISTS papa CASCADE;')" ``` Удаляет: Client (заявка остается в истории с tenant=NULL) Требует: Ручное удаление схемы PostgreSQL **2️⃣ Полная очистка:** ```bash python manage.py cleanup_tenant --schema=papa --noinput --purge-registration --delete-files # ЗАТЕМ вручную: python manage.py shell -c "from django.db import connection; cur = connection.cursor(); cur.execute('DROP SCHEMA IF EXISTS papa CASCADE;')" ``` Удаляет: Client + заявка + файлы Требует: Ручное удаление схемы PostgreSQL **3️⃣ Удаление с заявкой:** ```bash python manage.py cleanup_tenant --schema=papa --noinput --purge-registration # ЗАТЕМ вручную: python manage.py shell -c "from django.db import connection; cur = connection.cursor(); cur.execute('DROP SCHEMA IF EXISTS papa CASCADE;')" ``` Удаляет: Client + заявка (файлы остаются) Требует: Ручное удаление схемы PostgreSQL #### Пример вывода: ``` === Удаление тенанта (магазина) === ====================================================================== ВНИМАНИЕ! Будут удалены следующие данные: ====================================================================== 📋 Тенант: • Название: Papa Shop • Schema: papa • Владелец: admin@example.com 💾 База данных: • Запись Client "papa" будет удалена • ⚠️ Схема PostgreSQL останется и требует ручного удаления! 📝 TenantRegistration: • Заявка от Papa Owner (papa@example.com) • Статус: Одобрено • Действие: оставить с tenant=NULL (сохранить историю) ====================================================================== ▶ Начинаю удаление... ====================================================================== 1️⃣ Обновляю TenantRegistration (сохраняю историю)... ✓ TenantRegistration обновлена (tenant=NULL) 2️⃣ Удаляю Client... ✓ Client "Papa Shop" удален ⚠️ Схема БД "papa" НЕ удалена - требует ручного удаления! ====================================================================== ✓ Тенант успешно удален! ====================================================================== ``` --- ### Способ 1: Удаление одного тенанта по schema **Команда:** ```bash cd myproject python manage.py delete_tenant --schema=papa --noinput ``` **Параметры:** - `--schema=papa` - Удалить тенант с именем schema `papa` - `--noinput` - Не запрашивать подтверждение (автоматический режим) **Результат:** ``` Deleting 'papa' Deleted 'papa' ``` ⚠️ **ВНИМАНИЕ:** В этом проекте `auto_drop_schema = False`, поэтому команда `delete_tenant` **НЕ удаляет схему из PostgreSQL**! Эта команда удаляет: - ✅ Запись Client из таблицы тенантов - ❌ НЕ удаляет схему PostgreSQL (остаются все таблицы и данные!) - ❌ НЕ удаляет файлы в `/media/tenants/{tenant_id}/` После выполнения нужно вручную удалить схему (см. раздел "КРИТИЧЕСКИ ВАЖНО" выше). --- ### Способ 2: Интерактивное удаление (с выбором) **Команда:** ```bash python manage.py delete_tenant ``` **Результат:** Система запросит у вас: ``` Enter Tenant Schema ('?' to list schemas): papa Are you sure you want to delete the tenant: papa? (yes/no): yes ``` Введите: - `?` - чтобы увидеть список всех тенантов - Имя schema - чтобы выбрать тенант - `yes` - чтобы подтвердить удаление --- ### Способ 3: Удаление файлов после удаления из БД После удаления тенанта из БД нужно удалить его файлы вручную. **На Windows (PowerShell):** ```powershell Remove-Item -Path 'c:\Users\team_\Desktop\test_qwen\myproject\media\tenants' -Recurse -Force Write-Host 'Removed tenants directory' ``` **На Windows (CMD):** ```cmd rmdir /s /q "c:\Users\team_\Desktop\test_qwen\myproject\media\tenants" ``` **На Linux/Mac:** ```bash rm -rf ./media/tenants ``` --- ## Что удаляется? ### БД (требует РУЧНОГО удаления): ⚠️ **Схема PostgreSQL НЕ удаляется автоматически** из-за `auto_drop_schema = False`! Что остаётся в базе после `cleanup_tenant` или `delete_tenant`: ``` PostgreSQL schema: papa ← ОСТАЁТСЯ В БАЗЕ! ├── products_product ├── products_productphoto ├── products_productkit ├── products_productkitphoto ├── products_productcategory ├── products_productcategoryphoto ├── inventory_* ├── orders_* └── ... (все другие таблицы в schema) ``` Для полного удаления выполните: ```bash python manage.py shell -c "from django.db import connection; cur = connection.cursor(); cur.execute('DROP SCHEMA IF EXISTS papa CASCADE;')" ``` ### Файлы (нужно удалить вручную): ``` media/tenants/papa/ ├── products/ │ ├── {product_id}/{photo_id}/original.jpg │ ├── {product_id}/{photo_id}/large.webp │ ├── {product_id}/{photo_id}/medium.webp │ ├── {product_id}/{photo_id}/thumb.webp │ └── temp/... (временные файлы) ├── kits/ ├── categories/ └── ... (все файлы тенанта) ``` --- ## Полный цикл удаления тенанта ### 1️⃣ Удалить Client: ```bash python manage.py cleanup_tenant --schema=papa --noinput --purge-registration --delete-files ``` ### 2️⃣ Удалить схему PostgreSQL: ```bash python manage.py shell -c "from django.db import connection; cur = connection.cursor(); cur.execute('DROP SCHEMA IF EXISTS papa CASCADE;')" ``` ### 3️⃣ Проверить удаление схемы (опционально): ```powershell Test-Path 'c:\Users\team_\Desktop\test_qwen\myproject\media\tenants' # Должно вернуть: False ``` --- ## Примеры ### Пример 1: Удалить тенант "papa" ```bash cd myproject python manage.py delete_tenant --schema=papa --noinput ``` ```powershell Remove-Item -Path 'media/tenants/papa' -Recurse -Force ``` ### Пример 2: Удалить все тенанты ```bash # Удалить из БД (нужно сделать для каждого тенанта) python manage.py delete_tenant --schema=papa --noinput python manage.py delete_tenant --schema=customer1 --noinput python manage.py delete_tenant --schema=test --noinput ``` ```powershell # Удалить все файлы Remove-Item -Path 'media/tenants' -Recurse -Force ``` ### Пример 3: Список доступных тенантов ```bash # Интерактивный режим - введите ? для списка python manage.py delete_tenant # Введите: ? # Получите: список всех доступных schemas ``` --- ## Важные замечания ⚠️ **ВНИМАНИЕ!** - Удаление **необратимо** - нет возможности восстановления - Сначала удаляйте из БД, потом удаляйте файлы - Не забывайте удалять файлы - они занимают место на диске - При удалении всех тенантов БД может остаться в некорректном состоянии ✅ **РЕКОМЕНДАЦИИ:** - Делайте бэкап перед удалением на продакшене - Используйте `--noinput` для автоматизации скриптами - Удаляйте файлы регулярно, чтобы освободить место - Проверяйте результат удаления --- ## Ошибки и решения ### Ошибка: "EOFError: EOF when reading a line" ``` Enter Tenant Schema ('?' to list schemas): EOFError: EOF when reading a line ``` **Решение:** Используйте флаг `--schema=` ```bash python manage.py delete_tenant --schema=papa --noinput ``` ### Ошибка: "Tenant doesn't exist" ``` Error: Tenant doesn't exist ``` **Решение:** Проверьте точное имя schema ```bash # Посмотрите список тенантов python manage.py delete_tenant # Введите: ? ``` ### Файлы не удаляются на Windows ```powershell # Если файл заблокирован, закройте все приложения и попробуйте: Remove-Item -Path 'media/tenants' -Recurse -Force -ErrorAction SilentlyContinue ``` --- ## Команда для быстрого удаления (скрипт) **delete_all_tenants.sh (для Linux/Mac):** ```bash #!/bin/bash python manage.py delete_tenant --schema=papa --noinput python manage.py delete_tenant --schema=test --noinput rm -rf ./media/tenants echo "All tenants deleted" ``` **delete_all_tenants.ps1 (для Windows):** ```powershell # Удалить БД python manage.py delete_tenant --schema=papa --noinput python manage.py delete_tenant --schema=test --noinput # Удалить файлы Remove-Item -Path 'media/tenants' -Recurse -Force -ErrorAction SilentlyContinue Write-Host "All tenants deleted successfully" ``` --- ## Что делать с TenantRegistration? ### Проблема При удалении Client тенанта остается заявка TenantRegistration со статусом 'approved' и `tenant=NULL`. Это создает проблему: если клиент захочет **повторно зарегистрироваться с тем же поддоменом**, система выдаст ошибку: ``` Error: duplicate key value violates unique constraint 'schema_name' ``` Потому что в таблице TenantRegistration уже есть запись с `schema_name='papa'`. ### Решение **Вариант 1: Оставить заявку в истории (рекомендуется)** ```bash python manage.py cleanup_tenant --schema=papa --noinput ``` Заявка остается в админке с `tenant=NULL`. Это: - ✅ Сохраняет историю регистраций - ✅ Видна попытка создания магазина (для аналитики) - ❌ Требует ручного удаления старой заявки перед новой регистрацией **Если клиент захочет зарегистрироваться снова:** 1. Вручную удалить старую TenantRegistration через админку 2. Тогда он сможет создать новую заявку с тем же schema_name **Вариант 2: Удалить заявку автоматически** ```bash python manage.py cleanup_tenant --schema=papa --noinput --purge-registration ``` Удаляет: - ✅ Client - ✅ TenantRegistration (полная очистка) - ❌ Теряется история регистраций - ⚠️ Требует ручного удаления схемы PostgreSQL **Если клиент захочет зарегистрироваться снова:** 1. Просто заполняет форму регистрации 2. Может использовать тот же schema_name 3. Все работает как в первый раз ### Мой совет Для **тестового проекта**: используй `--purge-registration` (чище) ```bash python manage.py cleanup_tenant --schema=papa --noinput --purge-registration ``` Для **боевого проекта**: оставляй заявку в истории (для аудита) ```bash python manage.py cleanup_tenant --schema=papa --noinput ``` --- ## Вопросы и ответы **Q: Как посмотреть список тенантов?** A: Введите `?` в интерактивном режиме: ```bash python manage.py delete_tenant # Введите: ? ``` **Q: Могу ли я восстановить удаленного тенанта?** A: Нет, удаление необратимо. Только из бэкапа БД. **Q: Что если тенант все еще используется?** A: Появится ошибка. Закройте все приложения работающие с БД и попробуйте снова. **Q: Как удалить тенант если забыл его schema name?** A: Посмотрите в таблице `django_tenants_tenant`: ```bash python manage.py shell # Введите: from django_tenants.models import Client for tenant in Client.objects.all(): print(f"{tenant.name} -> {tenant.schema_name}") ``` --- --- ## Ручной контроль удаления схем (PostgreSQL + django-tenants 3.7.0) ### Что важно знать про django-tenants В `django-tenants==3.7.0` удаление тенанта работает так: - Тенант удаляется через обычный ORM: ```python tenant.delete() ``` - На модели тенанта (`Client`) есть флаг: ```python auto_drop_schema = False # по умолчанию ``` - Если `auto_drop_schema = False`: - При `tenant.delete()` удаляется только запись в таблице клиентов. - Схема в PostgreSQL (`schema_name`) физически остаётся со всеми таблицами и данными. - Если `auto_drop_schema = True`: - При `tenant.delete()` будет выполнен `DROP SCHEMA CASCADE`. - Это удобно, но ОЧЕНЬ опасно: любое удаление тенанта через ORM (например, через админку) без дополнительных проверок сразу дропает схему. ### Выбранная стратегия: полный ручной контроль Для этого проекта принято решение: - В модели `Client` оставить: ```python auto_drop_schema = False ``` - НЕ полагаться на автоматический `auto_drop_schema=True`. - Всегда явно контролировать момент, когда схема в PostgreSQL удаляется. Это даёт: - ✅ Защиту от случайного дропа схемы через админку или произвольный `.delete()`. - ✅ Прозрачный и предсказуемый процесс: схема дропается только явной SQL-командой. - ✅ Возможность временно сохранить схему для отладки/анализа. - ❌ Требуется дополнительный шаг — ручное удаление схемы после каждого удаления тенанта. ### Рекомендуемый workflow удаления ```bash # Шаг 1: Удалить Client и связанные данные python manage.py cleanup_tenant --schema=papa --noinput --purge-registration --delete-files # Шаг 2: Явно удалить схему PostgreSQL python manage.py shell -c "from django.db import connection; cur = connection.cursor(); cur.execute('DROP SCHEMA IF EXISTS papa CASCADE;')" # Или через psql напрямую: # DROP SCHEMA IF EXISTS papa CASCADE; ``` ### Практические выводы - При удалении тенанта в этом проекте НЕЛЬЗЯ полагаться только на: ```bash python manage.py delete_tenant --schema=... ``` - Рекомендуется ВСЕГДА использовать двухшаговый процесс: 1. `cleanup_tenant` для удаления Client, TenantRegistration и файлов 2. Явное `DROP SCHEMA ... CASCADE` для удаления схемы PostgreSQL - Если потребуется временно оставить схему (например, для отладки), достаточно: - Выполнить только шаг 1 (cleanup_tenant) - Отложить шаг 2 (DROP SCHEMA) на потом --- **Дата создания:** 2025-11-23 **Дата обновления:** 2025-12-01 **Версия:** 2.1 **Статус:** Production Ready ✅ ### Что нового в версии 2.1: - 🔴 **КРИТИЧЕСКОЕ:** Добавлено предупреждение о необходимости ручного удаления схем PostgreSQL - 📖 Добавлен раздел "Ручной контроль удаления схем" с объяснением стратегии `auto_drop_schema = False` - 📖 Обновлены все примеры команд с указанием необходимости ручного удаления схемы - 📖 Исправлены описания того, что именно удаляют команды `cleanup_tenant` и `delete_tenant` - ✨ Добавлены три способа удаления схемы вручную (Django shell, консоль, psql) ### Что было в версии 2.0: - ✨ Добавлена новая улучшенная команда `cleanup_tenant` - ✨ Команда автоматически обрабатывает TenantRegistration - ✨ Добавлена опция `--purge-registration` для удаления заявок - ✨ Добавлена опция `--delete-files` для удаления физических файлов - 📖 Расширена документация с объяснением проблемы TenantRegistration - 📖 Добавлены примеры использования для разных сценариев