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:
182
myproject/products/views/photo_status_api.py
Normal file
182
myproject/products/views/photo_status_api.py
Normal 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)
|
||||
Reference in New Issue
Block a user