fix: Сохранять файл фото ДО запуска Celery task

При асинхронной обработке фото нужно сначала сохранить файл в БД,
потом запустить Celery task. Иначе task не найдет файл.

Изменения:
- BasePhoto.save() теперь сохраняет файл перед запуском task
- Исправлена проблема 'Photo has no image file' в Celery worker

🤖 Generated with Claude Code
This commit is contained in:
2025-11-15 11:11:08 +03:00
parent a03f4c3047
commit 0791ebb13b
11 changed files with 968 additions and 67 deletions

View File

@@ -0,0 +1,182 @@
"""
API endpoints для отслеживания статуса асинхронной обработки фото.
Фронтенд опрашивает эти endpoints через AJAX для получения информации о прогрессе.
"""
import logging
from django.http import JsonResponse
from django.views.decorators.http import require_GET
from django.views.decorators.csrf import csrf_exempt
from celery.result import AsyncResult
from products.models import PhotoProcessingStatus
logger = logging.getLogger(__name__)
@csrf_exempt
@require_GET
def photo_processing_status(request, task_id):
"""
Получить статус обработки фото по task_id Celery.
URL: /api/photos/status/<task_id>/
Returns:
{
'status': 'PENDING' | 'STARTED' | 'SUCCESS' | 'FAILURE',
'task_id': str,
'progress': 0-100,
'message': str,
'result': {...} # если завершено успешно
}
Пример JavaScript вызова:
fetch('/api/photos/status/abc123/')
.then(r => r.json())
.then(data => {
if (data.status === 'SUCCESS') {
// Фото обработано успешно
location.reload();
}
})
"""
try:
# Получаем результат задачи Celery по task_id
result = AsyncResult(task_id)
response_data = {
'status': result.state,
'task_id': task_id,
'message': 'Неизвестный статус',
}
if result.state == 'PENDING':
# Задача еще не запущена (в очереди)
response_data['progress'] = 0
response_data['message'] = 'В очереди на обработку...'
elif result.state == 'STARTED':
# Задача выполняется в данный момент
response_data['progress'] = 50
response_data['message'] = 'Обрабатывается изображение...'
elif result.state == 'SUCCESS':
# Задача завершена успешно
response_data['progress'] = 100
response_data['message'] = 'Готово'
response_data['result'] = result.result
logger.info(f"[PhotoStatusAPI] Photo processing completed (task_id: {task_id})")
elif result.state == 'FAILURE':
# Произошла ошибка при обработке
response_data['progress'] = 0
response_data['message'] = 'Ошибка при обработке'
response_data['error'] = str(result.info)
logger.error(f"[PhotoStatusAPI] Photo processing failed (task_id: {task_id}): {str(result.info)}")
elif result.state == 'RETRY':
# Задача повторяется (была ошибка, но пытаемся еще раз)
response_data['progress'] = 25
response_data['message'] = 'Повторная попытка обработки...'
return JsonResponse(response_data, status=200)
except Exception as e:
logger.error(f"[PhotoStatusAPI] Error getting task status: {str(e)}", exc_info=True)
return JsonResponse({
'status': 'error',
'message': 'Ошибка при получении статуса',
'error': str(e)
}, status=500)
@csrf_exempt
@require_GET
def batch_photo_status(request):
"""
Получить статус обработки для нескольких фото одновременно.
URL: /api/photos/batch-status/?task_ids=id1&task_ids=id2&task_ids=id3
Параметры:
task_ids: Список task_id (может передаваться несколько раз или через запятую)
Returns:
{
'results': [
{
'task_id': str,
'status': 'PENDING' | 'SUCCESS' | 'FAILURE',
'progress': 0-100,
'message': str,
},
...
],
'completed': int,
'failed': int,
'processing': int,
}
"""
try:
# Получаем task_ids из query параметров
task_ids = request.GET.getlist('task_ids')
if not task_ids:
return JsonResponse({
'error': 'Параметр task_ids обязателен',
'results': []
}, status=400)
results = []
completed_count = 0
failed_count = 0
processing_count = 0
for task_id in task_ids:
result = AsyncResult(task_id)
status_info = {
'task_id': task_id,
'status': result.state,
}
if result.state == 'PENDING':
status_info['progress'] = 0
status_info['message'] = 'В очереди'
processing_count += 1
elif result.state == 'STARTED':
status_info['progress'] = 50
status_info['message'] = 'Обрабатывается'
processing_count += 1
elif result.state == 'SUCCESS':
status_info['progress'] = 100
status_info['message'] = 'Готово'
status_info['result'] = result.result
completed_count += 1
elif result.state == 'FAILURE':
status_info['progress'] = 0
status_info['message'] = 'Ошибка'
status_info['error'] = str(result.info)
failed_count += 1
results.append(status_info)
return JsonResponse({
'results': results,
'completed': completed_count,
'failed': failed_count,
'processing': processing_count,
'total': len(task_ids),
}, status=200)
except Exception as e:
logger.error(f"[PhotoStatusAPI] Error in batch status: {str(e)}", exc_info=True)
return JsonResponse({
'error': str(e),
'results': []
}, status=500)