diff --git a/docker/create_public_tenant.py b/docker/create_public_tenant.py index 6bd3196..9e60179 100644 --- a/docker/create_public_tenant.py +++ b/docker/create_public_tenant.py @@ -49,12 +49,13 @@ def ensure_public_tenant(): print(f"WARNING: Domain {domain_name} is assigned to another tenant!") # 3. Init system data (System Customer, etc.) - print("Initializing system data for public tenant...") - from django.core.management import call_command - try: - call_command('init_tenant_data', schema='public') - except Exception as e: - print(f"Error initializing system data: {e}") + # SKIP for public tenant as it doesn't have these tables (they separate in tenant schemas) + # print("Initializing system data for public tenant...") + # from django.core.management import call_command + # try: + # call_command('init_tenant_data', schema='public') + # except Exception as e: + # print(f"Error initializing system data: {e}") if __name__ == '__main__': ensure_public_tenant() diff --git a/myproject/inventory/tasks.py b/myproject/inventory/tasks.py index d41d578..297c070 100644 --- a/myproject/inventory/tasks.py +++ b/myproject/inventory/tasks.py @@ -3,6 +3,7 @@ from celery import shared_task from django.utils import timezone from django.db.models import Q import logging +from django_tenants.utils import get_tenant_model, schema_context logger = logging.getLogger(__name__) @@ -15,67 +16,70 @@ def cleanup_expired_cart_locks(): но блокировка истекла (timeout 30 минут). Запускается каждые 5 минут (настроить в celery beat schedule). + Проходит по всем тенантам. Returns: dict: Статистика очистки { - 'released_count': int, # Количество освобожденных блокировок - 'affected_kits': list # ID освобожденных комплектов + 'released_count': int, # Общее количество освобожденных блокировок + '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: - # Находим все резервы с истекшей блокировкой - 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') + count = expired_locks.count() + + if count > 0: + affected_kits = list( + expired_locks.values_list('product_kit_id', flat=True).distinct() + ) + + logger.info( + f"[{tenant.schema_name}] Очистка истекших блокировок: {count} резервов" + ) - # Собираем статистику перед очисткой - affected_kits = list( - expired_locks.values_list('product_kit_id', flat=True).distinct() - ) - released_count = expired_locks.count() + # Очищаем блокировки + expired_locks.update( + cart_lock_expires_at=None, + locked_by_user=None, + cart_session_id=None + ) + + overall_stats['released_count'] += count + overall_stats['details'].append({ + 'tenant': tenant.schema_name, + 'released': count, + 'kits': affected_kits + }) - # Логируем информацию о блокировках - if released_count > 0: - logger.info( - f"Очистка истекших блокировок: {released_count} резервов, " - f"{len(affected_kits)} комплектов" - ) + except Exception as e: + # Логируем ошибку, но не прерываем обработку других тенантов + logger.error(f"[{tenant.schema_name}] Ошибка при очистке блокировок: {e}", exc_info=True) + overall_stats['details'].append({ + 'tenant': tenant.schema_name, + 'error': str(e) + }) - 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}" - ) + if overall_stats['released_count'] > 0: + logger.info(f"Общая очистка блокировок завершена: {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) - } + return overall_stats diff --git a/myproject/myproject/settings.py b/myproject/myproject/settings.py index dd518c0..df93fab 100644 --- a/myproject/myproject/settings.py +++ b/myproject/myproject/settings.py @@ -9,6 +9,7 @@ Example: shop1.inventory.by, shop2.inventory.by """ from pathlib import Path +import os import environ # 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 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 CELERY_BEAT_SCHEDULE = {