feat: Add script to ensure public tenant and its domain exist.
This commit is contained in:
@@ -49,12 +49,13 @@ def ensure_public_tenant():
|
|||||||
print(f"WARNING: Domain {domain_name} is assigned to another tenant!")
|
print(f"WARNING: Domain {domain_name} is assigned to another tenant!")
|
||||||
|
|
||||||
# 3. Init system data (System Customer, etc.)
|
# 3. Init system data (System Customer, etc.)
|
||||||
print("Initializing system data for public tenant...")
|
# SKIP for public tenant as it doesn't have these tables (they separate in tenant schemas)
|
||||||
from django.core.management import call_command
|
# print("Initializing system data for public tenant...")
|
||||||
try:
|
# from django.core.management import call_command
|
||||||
call_command('init_tenant_data', schema='public')
|
# try:
|
||||||
except Exception as e:
|
# call_command('init_tenant_data', schema='public')
|
||||||
print(f"Error initializing system data: {e}")
|
# except Exception as e:
|
||||||
|
# print(f"Error initializing system data: {e}")
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
ensure_public_tenant()
|
ensure_public_tenant()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from celery import shared_task
|
|||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
import logging
|
import logging
|
||||||
|
from django_tenants.utils import get_tenant_model, schema_context
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -15,16 +16,29 @@ def cleanup_expired_cart_locks():
|
|||||||
но блокировка истекла (timeout 30 минут).
|
но блокировка истекла (timeout 30 минут).
|
||||||
|
|
||||||
Запускается каждые 5 минут (настроить в celery beat schedule).
|
Запускается каждые 5 минут (настроить в celery beat schedule).
|
||||||
|
Проходит по всем тенантам.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: Статистика очистки {
|
dict: Статистика очистки {
|
||||||
'released_count': int, # Количество освобожденных блокировок
|
'released_count': int, # Общее количество освобожденных блокировок
|
||||||
'affected_kits': list # ID освобожденных комплектов
|
'details': list # Детали по каждому тенанту
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
Tenant = get_tenant_model()
|
||||||
|
overall_stats = {
|
||||||
|
'released_count': 0,
|
||||||
|
'details': []
|
||||||
|
}
|
||||||
|
|
||||||
|
# Проходим по всем тенантам (кроме public)
|
||||||
|
# Public схема не содержит бизнес-данных (корзины, резервы)
|
||||||
|
tenants = Tenant.objects.exclude(schema_name='public')
|
||||||
|
|
||||||
|
for tenant in tenants:
|
||||||
|
try:
|
||||||
|
with schema_context(tenant.schema_name):
|
||||||
from inventory.models import Reservation
|
from inventory.models import Reservation
|
||||||
|
|
||||||
try:
|
|
||||||
# Находим все резервы с истекшей блокировкой
|
# Находим все резервы с истекшей блокировкой
|
||||||
expired_locks = Reservation.objects.filter(
|
expired_locks = Reservation.objects.filter(
|
||||||
Q(cart_lock_expires_at__lte=timezone.now()) &
|
Q(cart_lock_expires_at__lte=timezone.now()) &
|
||||||
@@ -32,26 +46,15 @@ def cleanup_expired_cart_locks():
|
|||||||
Q(status='reserved')
|
Q(status='reserved')
|
||||||
).select_related('product_kit', 'locked_by_user')
|
).select_related('product_kit', 'locked_by_user')
|
||||||
|
|
||||||
# Собираем статистику перед очисткой
|
count = expired_locks.count()
|
||||||
|
|
||||||
|
if count > 0:
|
||||||
affected_kits = list(
|
affected_kits = list(
|
||||||
expired_locks.values_list('product_kit_id', flat=True).distinct()
|
expired_locks.values_list('product_kit_id', flat=True).distinct()
|
||||||
)
|
)
|
||||||
released_count = expired_locks.count()
|
|
||||||
|
|
||||||
# Логируем информацию о блокировках
|
|
||||||
if released_count > 0:
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Очистка истекших блокировок: {released_count} резервов, "
|
f"[{tenant.schema_name}] Очистка истекших блокировок: {count} резервов"
|
||||||
f"{len(affected_kits)} комплектов"
|
|
||||||
)
|
|
||||||
|
|
||||||
for lock in expired_locks[:10]: # Логируем первые 10 для отладки
|
|
||||||
kit_name = lock.product_kit.name if lock.product_kit else 'N/A'
|
|
||||||
user_name = lock.locked_by_user.username if lock.locked_by_user else 'N/A'
|
|
||||||
logger.debug(
|
|
||||||
f"Освобождение блокировки: комплект='{kit_name}', "
|
|
||||||
f"пользователь='{user_name}', "
|
|
||||||
f"истекла={lock.cart_lock_expires_at}"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Очищаем блокировки
|
# Очищаем блокировки
|
||||||
@@ -61,21 +64,22 @@ def cleanup_expired_cart_locks():
|
|||||||
cart_session_id=None
|
cart_session_id=None
|
||||||
)
|
)
|
||||||
|
|
||||||
result = {
|
overall_stats['released_count'] += count
|
||||||
'released_count': released_count,
|
overall_stats['details'].append({
|
||||||
'affected_kits': affected_kits,
|
'tenant': tenant.schema_name,
|
||||||
'timestamp': timezone.now().isoformat()
|
'released': count,
|
||||||
}
|
'kits': affected_kits
|
||||||
|
})
|
||||||
if released_count > 0:
|
|
||||||
logger.info(f"Очистка завершена успешно: {result}")
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Ошибка при очистке истекших блокировок: {str(e)}", exc_info=True)
|
# Логируем ошибку, но не прерываем обработку других тенантов
|
||||||
return {
|
logger.error(f"[{tenant.schema_name}] Ошибка при очистке блокировок: {e}", exc_info=True)
|
||||||
'released_count': 0,
|
overall_stats['details'].append({
|
||||||
'affected_kits': [],
|
'tenant': tenant.schema_name,
|
||||||
'error': str(e)
|
'error': str(e)
|
||||||
}
|
})
|
||||||
|
|
||||||
|
if overall_stats['released_count'] > 0:
|
||||||
|
logger.info(f"Общая очистка блокировок завершена: {overall_stats}")
|
||||||
|
|
||||||
|
return overall_stats
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ Example: shop1.inventory.by, shop2.inventory.by
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import os
|
||||||
import environ
|
import environ
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
@@ -466,6 +467,9 @@ CELERY_TASK_DEFAULT_MAX_RETRIES = 3
|
|||||||
CELERY_TASK_DEFAULT_RETRY_DELAY = 60 # Повторить через 60 секунд при ошибке
|
CELERY_TASK_DEFAULT_RETRY_DELAY = 60 # Повторить через 60 секунд при ошибке
|
||||||
|
|
||||||
# Celery Beat Schedule (периодические задачи)
|
# Celery Beat Schedule (периодические задачи)
|
||||||
|
# Store schedule in /tmp to avoid PermissionError in Docker volumes
|
||||||
|
CELERY_BEAT_SCHEDULE_FILENAME = os.path.join('/tmp', 'celerybeat-schedule')
|
||||||
|
|
||||||
from celery.schedules import crontab
|
from celery.schedules import crontab
|
||||||
|
|
||||||
CELERY_BEAT_SCHEDULE = {
|
CELERY_BEAT_SCHEDULE = {
|
||||||
|
|||||||
Reference in New Issue
Block a user