Убраны временные утилитарные скрипты и личные заметки из отслеживания Git
- Удалены из версионирования: check_duplicates.py, cleanup_stuck_photos.py, cleanup_commands.txt, start_all.bat, ГИД ПО ЗАПУСКУ - Обновлён .gitignore для предотвращения отслеживания временных скриптов обслуживания - Добавлены шаблоны для исключения личных заметок и руководств - Файлы сохранены локально для использования при необходимости Эти файлы являются временными инструментами диагностики и личными заметками разработчика, которые не должны находиться под контролем версий.
This commit is contained in:
13
.gitignore
vendored
13
.gitignore
vendored
@@ -77,3 +77,16 @@ start_celery.sh
|
|||||||
# Customer export files
|
# Customer export files
|
||||||
customers_*.xlsx
|
customers_*.xlsx
|
||||||
customers_*.csv
|
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
|
||||||
|
|||||||
@@ -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!")
|
|
||||||
@@ -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
|
|
||||||
@@ -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}")
|
|
||||||
@@ -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"
|
|
||||||
@@ -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
|
|
||||||
Reference in New Issue
Block a user