Files
octopus/УДАЛЕНИЕ_ТЕНАНТОВ.md
Andrey Smakotin e0437cdb5a Исправлено двойное списание товаров при смене статуса заказа
Проблема:
- При изменении статуса заказа на 'Выполнен' товар списывался дважды
- Заказ на 10 шт создавал Sale на 10 шт, но со склада уходило 20 шт

Найдено ДВЕ причины:

1. Повторное обновление резервов через .save() (inventory/signals.py)
   - Резервы обновлялись через res.save() каждый раз при сохранении заказа
   - Это вызывало сигнал update_stock_on_reservation_change
   - При повторном сохранении заказа происходило двойное срабатывание

   Решение:
   - Проверка дубликатов ПЕРЕД обновлением резервов
   - Замена .save() на .update() для массового обновления без вызова сигналов
   - Ручное обновление Stock после .update()

2. Двойное FIFO-списание (inventory/services/sale_processor.py)
   - Sale создавалась с processed=False
   - Сигнал process_sale_fifo срабатывал и списывал товар (1-й раз)
   - Затем SaleProcessor.create_sale() тоже списывал товар (2-й раз)

   Решение:
   - Sale создаётся сразу с processed=True
   - Сигнал не срабатывает, списание только в сервисе

Дополнительно:
- Ограничен выбор статусов при создании заказа только промежуточными
- Статус 'Черновик' установлен по умолчанию
- Убран пустой выбор '-------' из поля статуса

Изменённые файлы:
- myproject/orders/forms.py - настройки статусов для формы заказа
- myproject/inventory/signals.py - исправление сигнала create_sale_on_order_completion
- myproject/inventory/services/sale_processor.py - исправление create_sale
- myproject/test_order_status_default.py - обновлён тест
- DOUBLE_SALE_FIX.md - документация по исправлению
2025-12-01 00:56:26 +03:00

23 KiB
Raw Blame History

Удаление Тенантов в Django-Tenants

⚠️ КРИТИЧЕСКИ ВАЖНО

В этом проекте auto_drop_schema = False, поэтому ВСЕ команды удаления (включая cleanup_tenant и delete_tenant) НЕ удаляют схему из PostgreSQL автоматически!

После удаления тенанта через любую команду ОБЯЗАТЕЛЬНО нужно вручную удалить схему из базы данных, иначе:

  • Запись Client удалится
  • Схема со всеми таблицами и данными останется в PostgreSQL
  • При повторной регистрации с тем же именем новый тенант увидит старые данные!

Как удалить схему вручную:

Вариант 1 — через Django shell:

from django.db import connection

with connection.cursor() as cursor:
    cursor.execute('DROP SCHEMA IF EXISTS имя_схемы CASCADE;')

Вариант 2 — одной командой из консоли:

python manage.py shell -c "from django.db import connection; cur = connection.cursor(); cur.execute('DROP SCHEMA IF EXISTS имя_схемы CASCADE;')"

Вариант 3 — напрямую в PostgreSQL (psql):

DROP SCHEMA IF EXISTS имя_схемы CASCADE;

Быстрая справка

Рекомендуемый способ (с улучшенной командой):

# Базовое удаление (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 (см. выше) или удалите схему вручную.

# Удалить конкретного тенанта (только запись 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 (см. раздел выше).

Параметры:

--schema=<имя>          # Имя тенанта (обязательно)
--noinput              # Не запрашивать подтверждение
--purge-registration   # Удалить TenantRegistration (иначе оставляет с tenant=NULL)
--delete-files         # Удалить физические файлы из /media/tenants/

Варианты использования:

1 Базовое удаление (рекомендуется):

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 Полная очистка:

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 Удаление с заявкой:

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

Команда:

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: Интерактивное удаление (с выбором)

Команда:

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):

Remove-Item -Path 'c:\Users\team_\Desktop\test_qwen\myproject\media\tenants' -Recurse -Force
Write-Host 'Removed tenants directory'

На Windows (CMD):

rmdir /s /q "c:\Users\team_\Desktop\test_qwen\myproject\media\tenants"

На Linux/Mac:

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)

Для полного удаления выполните:

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:

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;')"

3 Проверить удаление схемы (опционально):

Test-Path 'c:\Users\team_\Desktop\test_qwen\myproject\media\tenants'
# Должно вернуть: False

Примеры

Пример 1: Удалить тенант "papa"

cd myproject
python manage.py delete_tenant --schema=papa --noinput
Remove-Item -Path 'media/tenants/papa' -Recurse -Force

Пример 2: Удалить все тенанты

# Удалить из БД (нужно сделать для каждого тенанта)
python manage.py delete_tenant --schema=papa --noinput
python manage.py delete_tenant --schema=customer1 --noinput
python manage.py delete_tenant --schema=test --noinput
# Удалить все файлы
Remove-Item -Path 'media/tenants' -Recurse -Force

Пример 3: Список доступных тенантов

# Интерактивный режим - введите ? для списка
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=

python manage.py delete_tenant --schema=papa --noinput

Ошибка: "Tenant doesn't exist"

Error: Tenant doesn't exist

Решение: Проверьте точное имя schema

# Посмотрите список тенантов
python manage.py delete_tenant
# Введите: ?

Файлы не удаляются на Windows

# Если файл заблокирован, закройте все приложения и попробуйте:
Remove-Item -Path 'media/tenants' -Recurse -Force -ErrorAction SilentlyContinue

Команда для быстрого удаления (скрипт)

delete_all_tenants.sh (для Linux/Mac):

#!/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):

# Удалить БД
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: Оставить заявку в истории (рекомендуется)

python manage.py cleanup_tenant --schema=papa --noinput

Заявка остается в админке с tenant=NULL. Это:

  • Сохраняет историю регистраций
  • Видна попытка создания магазина (для аналитики)
  • Требует ручного удаления старой заявки перед новой регистрацией

Если клиент захочет зарегистрироваться снова:

  1. Вручную удалить старую TenantRegistration через админку
  2. Тогда он сможет создать новую заявку с тем же schema_name

Вариант 2: Удалить заявку автоматически

python manage.py cleanup_tenant --schema=papa --noinput --purge-registration

Удаляет:

  • Client
  • TenantRegistration (полная очистка)
  • Теряется история регистраций
  • ⚠️ Требует ручного удаления схемы PostgreSQL

Если клиент захочет зарегистрироваться снова:

  1. Просто заполняет форму регистрации
  2. Может использовать тот же schema_name
  3. Все работает как в первый раз

Мой совет

Для тестового проекта: используй --purge-registration (чище)

python manage.py cleanup_tenant --schema=papa --noinput --purge-registration

Для боевого проекта: оставляй заявку в истории (для аудита)

python manage.py cleanup_tenant --schema=papa --noinput

Вопросы и ответы

Q: Как посмотреть список тенантов? A: Введите ? в интерактивном режиме:

python manage.py delete_tenant
# Введите: ?

Q: Могу ли я восстановить удаленного тенанта? A: Нет, удаление необратимо. Только из бэкапа БД.

Q: Что если тенант все еще используется? A: Появится ошибка. Закройте все приложения работающие с БД и попробуйте снова.

Q: Как удалить тенант если забыл его schema name? A: Посмотрите в таблице django_tenants_tenant:

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:
    tenant.delete()
    
  • На модели тенанта (Client) есть флаг:
    auto_drop_schema = False  # по умолчанию
    
  • Если auto_drop_schema = False:
    • При tenant.delete() удаляется только запись в таблице клиентов.
    • Схема в PostgreSQL (schema_name) физически остаётся со всеми таблицами и данными.
  • Если auto_drop_schema = True:
    • При tenant.delete() будет выполнен DROP SCHEMA <schema_name> CASCADE.
    • Это удобно, но ОЧЕНЬ опасно: любое удаление тенанта через ORM (например, через админку) без дополнительных проверок сразу дропает схему.

Выбранная стратегия: полный ручной контроль

Для этого проекта принято решение:

  • В модели Client оставить:
    auto_drop_schema = False
    
  • НЕ полагаться на автоматический auto_drop_schema=True.
  • Всегда явно контролировать момент, когда схема в PostgreSQL удаляется.

Это даёт:

  • Защиту от случайного дропа схемы через админку или произвольный .delete().
  • Прозрачный и предсказуемый процесс: схема дропается только явной SQL-командой.
  • Возможность временно сохранить схему для отладки/анализа.
  • Требуется дополнительный шаг — ручное удаление схемы после каждого удаления тенанта.

Рекомендуемый workflow удаления

# Шаг 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;

Практические выводы

  • При удалении тенанта в этом проекте НЕЛЬЗЯ полагаться только на:

    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
  • 📖 Добавлены примеры использования для разных сценариев