feat: Add script to ensure public tenant and its domain exist.

This commit is contained in:
2025-12-18 00:59:37 +03:00
parent 7b32cdcebf
commit 6c72126276
3 changed files with 69 additions and 60 deletions

View File

@@ -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()

View File

@@ -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,67 +16,70 @@ 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 # Детали по каждому тенанту
} }
""" """
from inventory.models import Reservation 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
# Находим все резервы с истекшей блокировкой
expired_locks = Reservation.objects.filter(
Q(cart_lock_expires_at__lte=timezone.now()) &
Q(cart_lock_expires_at__isnull=False) &
Q(status='reserved')
).select_related('product_kit', 'locked_by_user')
try: count = expired_locks.count()
# Находим все резервы с истекшей блокировкой
expired_locks = Reservation.objects.filter( if count > 0:
Q(cart_lock_expires_at__lte=timezone.now()) & affected_kits = list(
Q(cart_lock_expires_at__isnull=False) & expired_locks.values_list('product_kit_id', flat=True).distinct()
Q(status='reserved') )
).select_related('product_kit', 'locked_by_user')
logger.info(
f"[{tenant.schema_name}] Очистка истекших блокировок: {count} резервов"
)
# Собираем статистику перед очисткой # Очищаем блокировки
affected_kits = list( expired_locks.update(
expired_locks.values_list('product_kit_id', flat=True).distinct() cart_lock_expires_at=None,
) locked_by_user=None,
released_count = expired_locks.count() cart_session_id=None
)
overall_stats['released_count'] += count
overall_stats['details'].append({
'tenant': tenant.schema_name,
'released': count,
'kits': affected_kits
})
# Логируем информацию о блокировках except Exception as e:
if released_count > 0: # Логируем ошибку, но не прерываем обработку других тенантов
logger.info( logger.error(f"[{tenant.schema_name}] Ошибка при очистке блокировок: {e}", exc_info=True)
f"Очистка истекших блокировок: {released_count} резервов, " overall_stats['details'].append({
f"{len(affected_kits)} комплектов" 'tenant': tenant.schema_name,
) 'error': str(e)
})
for lock in expired_locks[:10]: # Логируем первые 10 для отладки if overall_stats['released_count'] > 0:
kit_name = lock.product_kit.name if lock.product_kit else 'N/A' logger.info(f"Общая очистка блокировок завершена: {overall_stats}")
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}"
)
# Очищаем блокировки return overall_stats
expired_locks.update(
cart_lock_expires_at=None,
locked_by_user=None,
cart_session_id=None
)
result = {
'released_count': released_count,
'affected_kits': affected_kits,
'timestamp': timezone.now().isoformat()
}
if released_count > 0:
logger.info(f"Очистка завершена успешно: {result}")
return result
except Exception as e:
logger.error(f"Ошибка при очистке истекших блокировок: {str(e)}", exc_info=True)
return {
'released_count': 0,
'affected_kits': [],
'error': str(e)
}

View File

@@ -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 = {