diff --git a/.gitignore b/.gitignore index 3338497..2fa7a8f 100644 --- a/.gitignore +++ b/.gitignore @@ -77,3 +77,16 @@ start_celery.sh # Customer export files customers_*.xlsx customers_*.csv + +# Root-level maintenance scripts (temporary fixes, diagnostics) +/check_*.py +/cleanup_*.py +/fix_*.py + +# Personal notes and guides +cleanup_commands.txt +*ГИД* +*гид* + +# Windows batch files +*.bat diff --git a/check_duplicates.py b/check_duplicates.py deleted file mode 100644 index 5f7d560..0000000 --- a/check_duplicates.py +++ /dev/null @@ -1,66 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -import os -import sys -import django - -# Setup Django -sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'myproject')) -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings') -django.setup() - -from inventory.models import Reservation -from django.db.models import Count - -# Найти дубликаты резервов трансформаций -duplicates = Reservation.objects.filter( - transformation_input__isnull=False -).values( - 'transformation_input', 'product', 'warehouse' -).annotate( - count=Count('id') -).filter( - count__gt=1 -) - -print(f"Found {duplicates.count()} duplicate transformation reservations") - -if duplicates.exists(): - print("\nDuplicate groups:") - for dup in duplicates[:10]: - print(f" TransformationInput ID: {dup['transformation_input']}, " - f"Product ID: {dup['product']}, " - f"Warehouse ID: {dup['warehouse']}, " - f"Count: {dup['count']}") - - # Show actual reservations - reservations = Reservation.objects.filter( - transformation_input_id=dup['transformation_input'], - product_id=dup['product'], - warehouse_id=dup['warehouse'] - ) - for res in reservations: - print(f" - Reservation ID {res.id}: quantity={res.quantity}, status={res.status}") - - print("\n--- CLEANING DUPLICATES ---") - - # Для каждой группы дубликатов оставляем только один резерв - for dup in duplicates: - reservations = Reservation.objects.filter( - transformation_input_id=dup['transformation_input'], - product_id=dup['product'], - warehouse_id=dup['warehouse'] - ).order_by('id') - - # Оставляем первый, удаляем остальные - first = reservations.first() - others = reservations.exclude(id=first.id) - count = others.count() - - if count > 0: - others.delete() - print(f"Deleted {count} duplicate reservations for TransformationInput {dup['transformation_input']}") - - print("\n--- DONE ---") -else: - print("No duplicates found!") diff --git a/cleanup_commands.txt b/cleanup_commands.txt deleted file mode 100644 index 9524acb..0000000 --- a/cleanup_commands.txt +++ /dev/null @@ -1,65 +0,0 @@ -# Cleanup Commands for Stuck Celery Tasks - -## Quick Commands - -### 1. Restart Celery Worker (to apply code changes) -```bash -docker restart mix_celery_worker -docker logs -f --tail 50 mix_celery_worker -``` - -### 2. Run Cleanup Script (Interactive) -```bash -# Copy script to container -docker cp cleanup_stuck_photos.py mix_web:/app/ - -# Run interactively -docker exec -it mix_web python manage.py shell ->>> exec(open('cleanup_stuck_photos.py').read()) -``` - -### 3. Manual Cleanup (Django Shell) -```bash -docker exec -it mix_web python manage.py shell -``` - -Then run: -```python -from django.db import connection -from products.models import ProductPhoto, PhotoProcessingStatus -from django.core.files.storage import default_storage - -# Activate schema -connection.set_schema('mixflowers') - -# Find photo #6 -photo = ProductPhoto.objects.get(id=6) -print(f"Photo: {photo}") -print(f"File path: {photo.image.name}") -print(f"File exists: {default_storage.exists(photo.image.name)}") - -# Option 1: Delete the record -photo.delete() - -# Option 2: Clear the image field (keep record) -# photo.image = None -# photo.save() - -# Clean up stuck processing statuses -stuck = PhotoProcessingStatus.objects.filter(status__in=['pending', 'processing']) -print(f"Found {stuck.count()} stuck statuses") -stuck.update(status='failed', error_message='File was deleted before processing') -``` - -## What Was Fixed - -1. **FileNotFoundError handling**: Task now fails immediately instead of retrying when file is missing -2. **File existence check**: Added check before processing to catch missing files early -3. **Status updates**: PhotoProcessingStatus is properly updated to 'failed' when file not found - -## Testing - -After restarting Celery worker: -1. Upload a new photo - should process normally -2. Check logs - should not see retry loops for missing files -3. Verify stuck tasks are cleaned up diff --git a/cleanup_stuck_photos.py b/cleanup_stuck_photos.py deleted file mode 100644 index f84b8db..0000000 --- a/cleanup_stuck_photos.py +++ /dev/null @@ -1,124 +0,0 @@ -""" -Скрипт для очистки застрявших записей фото в БД. -Используется когда файлы были удалены вручную до обработки Celery. - -Использование: - docker exec -it mix_web python manage.py shell < cleanup_stuck_photos.py - -Или интерактивно: - docker exec -it mix_web python manage.py shell - >>> exec(open('cleanup_stuck_photos.py').read()) -""" - -from django.db import connection -from products.models import ProductPhoto, ProductKitPhoto, ProductCategoryPhoto, PhotoProcessingStatus -from django.core.files.storage import default_storage - -# Укажите schema_name вашего тенанта -SCHEMA_NAME = 'mixflowers' - -# Активируем схему -connection.set_schema(SCHEMA_NAME) -print(f"✓ Activated schema: {SCHEMA_NAME}") - -# Функция для проверки и очистки фото -def cleanup_photo_model(model_class, model_name): - """Очищает записи с несуществующими файлами для указанной модели""" - print(f"\n{'='*60}") - print(f"Checking {model_name}...") - print(f"{'='*60}") - - photos = model_class.objects.all() - total = photos.count() - print(f"Total {model_name} records: {total}") - - if total == 0: - print(f"No {model_name} records found.") - return - - missing_files = [] - - for photo in photos: - if photo.image: - file_path = photo.image.name - exists = default_storage.exists(file_path) - - if not exists: - missing_files.append({ - 'id': photo.id, - 'path': file_path, - 'entity': photo.get_entity(), - 'photo': photo - }) - print(f" ✗ Photo #{photo.id}: File NOT found: {file_path}") - else: - print(f" ✓ Photo #{photo.id}: File exists: {file_path}") - - if missing_files: - print(f"\n{'='*60}") - print(f"Found {len(missing_files)} {model_name} with missing files:") - print(f"{'='*60}") - - for item in missing_files: - print(f"\nPhoto ID: {item['id']}") - print(f" Entity: {item['entity']}") - print(f" Missing file: {item['path']}") - - # Спрашиваем подтверждение - print(f"\n{'='*60}") - response = input(f"Delete these {len(missing_files)} {model_name} records? (yes/no): ").strip().lower() - - if response == 'yes': - deleted_count = 0 - for item in missing_files: - try: - item['photo'].delete() - deleted_count += 1 - print(f" ✓ Deleted Photo #{item['id']}") - except Exception as e: - print(f" ✗ Error deleting Photo #{item['id']}: {e}") - - print(f"\n✓ Deleted {deleted_count} {model_name} records") - else: - print(f"\nSkipped deletion for {model_name}") - else: - print(f"\n✓ All {model_name} files exist. No cleanup needed.") - -# Очищаем каждую модель -cleanup_photo_model(ProductPhoto, "ProductPhoto") -cleanup_photo_model(ProductKitPhoto, "ProductKitPhoto") -cleanup_photo_model(ProductCategoryPhoto, "ProductCategoryPhoto") - -# Проверяем застрявшие статусы обработки -print(f"\n{'='*60}") -print(f"Checking PhotoProcessingStatus...") -print(f"{'='*60}") - -stuck_statuses = PhotoProcessingStatus.objects.filter( - status__in=['pending', 'processing'] -).order_by('-created_at') - -if stuck_statuses.exists(): - print(f"Found {stuck_statuses.count()} stuck processing statuses:") - for status in stuck_statuses: - print(f"\n Photo ID: {status.photo_id}") - print(f" Model: {status.photo_model}") - print(f" Status: {status.status}") - print(f" Created: {status.created_at}") - print(f" Task ID: {status.task_id}") - - response = input(f"\nMark these as 'failed'? (yes/no): ").strip().lower() - if response == 'yes': - updated = stuck_statuses.update( - status='failed', - error_message='Marked as failed during cleanup (file was deleted before processing)' - ) - print(f"\n✓ Updated {updated} processing statuses to 'failed'") - else: - print("\nSkipped updating processing statuses") -else: - print("✓ No stuck processing statuses found") - -print(f"\n{'='*60}") -print("Cleanup complete!") -print(f"{'='*60}") diff --git a/start_all.bat b/start_all.bat deleted file mode 100644 index c714287..0000000 --- a/start_all.bat +++ /dev/null @@ -1,9 +0,0 @@ -@echo off -REM Запуск Django сервера в новом окне -start cmd /k "venv\Scripts\activate && cd myproject && python manage.py runserver" - -REM Запуск Celery worker в новом окне -start cmd /k "venv\Scripts\activate && .\start_celery.bat" - -REM Запуск Celery beat в новом окне -start cmd /k "venv\Scripts\activate && cd myproject && celery -A myproject beat -l info" \ No newline at end of file diff --git a/ГИД ПО ЗАПУСКУ b/ГИД ПО ЗАПУСКУ deleted file mode 100644 index ae1761e..0000000 --- a/ГИД ПО ЗАПУСКУ +++ /dev/null @@ -1,38 +0,0 @@ -docker run -d ` - --name postgres17 ` - -e POSTGRES_PASSWORD=postgres ` - -e POSTGRES_USER=postgres ` - -e POSTGRES_DB=inventory_db ` - -p 5432:5432 ` - -v postgres17-data:/var/lib/postgresql/data ` - postgres:17 - - # 2. Создаем миграции с нуля -python manage.py makemigrations - -# 3. Применяем все миграции -python manage.py migrate - -# 4. Создаем главного тенанта (если нужно) -python manage.py shell -# Внутри shell: -from tenants.models import Client, Domain -client = Client.objects.create( - name='Main', - schema_name='public' -) -Domain.objects.create( - domain='localhost', - tenant=client, - is_primary=True -) -# Добавляем 127.0.0.1 как синоним localhost (иначе админка по 127.0.0.1 не будет работать) -Domain.objects.create( - domain='127.0.0.1', - tenant=client, - is_primary=False -) -exit() - -# 5. Создаем суперпользователя для public схемы -python manage.py createsuperuser