Refactor image processing and add image file serving functionality
- Updated image directory handling to prioritize mounted volume for persistence. - Enhanced logging for directory write tests and fallback mechanisms. - Introduced a new endpoint to serve image files directly from local paths. - Added error handling for file serving, including checks for file existence and readability. - Updated the frontend to include a new ContentView component and corresponding route.
This commit is contained in:
@@ -536,9 +536,10 @@ def process_image_generation_queue(self, image_ids: list, account_id: int = None
|
||||
from django.conf import settings
|
||||
from pathlib import Path
|
||||
|
||||
# Create /data/app/images directory if it doesn't exist
|
||||
# Try absolute path first, fallback to project-relative if needed
|
||||
images_dir = '/data/app/images'
|
||||
# Create images directory if it doesn't exist
|
||||
# Use /data/app/igny8/images (mounted volume) for persistence
|
||||
# Fallback to /data/app/images if mounted path not available
|
||||
images_dir = '/data/app/igny8/images' # Use mounted volume path
|
||||
write_test_passed = False
|
||||
|
||||
try:
|
||||
@@ -549,16 +550,13 @@ def process_image_generation_queue(self, image_ids: list, account_id: int = None
|
||||
f.write('test')
|
||||
os.remove(test_file)
|
||||
write_test_passed = True
|
||||
logger.info(f"[process_image_generation_queue] Image {image_id} - Directory writable: {images_dir}")
|
||||
logger.info(f"[process_image_generation_queue] Image {image_id} - Directory writable (mounted volume): {images_dir}")
|
||||
except Exception as write_test_error:
|
||||
logger.warning(f"[process_image_generation_queue] Image {image_id} - Directory not writable: {images_dir}, error: {write_test_error}")
|
||||
# Fallback to project-relative path
|
||||
from django.conf import settings
|
||||
base_dir = Path(settings.BASE_DIR) if hasattr(settings, 'BASE_DIR') else Path(__file__).resolve().parent.parent.parent
|
||||
images_dir = str(base_dir / 'data' / 'app' / 'images')
|
||||
logger.warning(f"[process_image_generation_queue] Image {image_id} - Mounted directory not writable: {images_dir}, error: {write_test_error}")
|
||||
# Fallback to /data/app/images
|
||||
images_dir = '/data/app/images'
|
||||
try:
|
||||
os.makedirs(images_dir, exist_ok=True)
|
||||
# Test fallback directory write access
|
||||
test_file = os.path.join(images_dir, '.write_test')
|
||||
with open(test_file, 'w') as f:
|
||||
f.write('test')
|
||||
@@ -566,8 +564,22 @@ def process_image_generation_queue(self, image_ids: list, account_id: int = None
|
||||
write_test_passed = True
|
||||
logger.info(f"[process_image_generation_queue] Image {image_id} - Using fallback directory (writable): {images_dir}")
|
||||
except Exception as fallback_error:
|
||||
logger.error(f"[process_image_generation_queue] Image {image_id} - Fallback directory also not writable: {images_dir}, error: {fallback_error}")
|
||||
raise Exception(f"Neither /data/app/images nor {images_dir} is writable. Last error: {fallback_error}")
|
||||
logger.warning(f"[process_image_generation_queue] Image {image_id} - Fallback directory also not writable: {images_dir}, error: {fallback_error}")
|
||||
# Final fallback to project-relative path
|
||||
from django.conf import settings
|
||||
base_dir = Path(settings.BASE_DIR) if hasattr(settings, 'BASE_DIR') else Path(__file__).resolve().parent.parent.parent
|
||||
images_dir = str(base_dir / 'data' / 'app' / 'images')
|
||||
try:
|
||||
os.makedirs(images_dir, exist_ok=True)
|
||||
test_file = os.path.join(images_dir, '.write_test')
|
||||
with open(test_file, 'w') as f:
|
||||
f.write('test')
|
||||
os.remove(test_file)
|
||||
write_test_passed = True
|
||||
logger.info(f"[process_image_generation_queue] Image {image_id} - Using project-relative directory (writable): {images_dir}")
|
||||
except Exception as final_error:
|
||||
logger.error(f"[process_image_generation_queue] Image {image_id} - All directories not writable. Last error: {final_error}")
|
||||
raise Exception(f"None of the image directories are writable. Last error: {final_error}")
|
||||
|
||||
if not write_test_passed:
|
||||
raise Exception(f"Failed to find writable directory for saving images")
|
||||
@@ -620,15 +632,29 @@ def process_image_generation_queue(self, image_ids: list, account_id: int = None
|
||||
logger.info(f"[process_image_generation_queue] Image {image_id} - URL length {len(image_url)} chars (was limited to 200, now supports 500)")
|
||||
|
||||
try:
|
||||
# Save file path if available, otherwise save URL
|
||||
# Save file path and URL appropriately
|
||||
if saved_file_path:
|
||||
# Store relative path or full path in image_url field
|
||||
image.image_url = saved_file_path
|
||||
# Store local file path in image_path field
|
||||
image.image_path = saved_file_path
|
||||
# Also keep the original URL in image_url field for reference
|
||||
if image_url:
|
||||
image.image_url = image_url
|
||||
logger.info(f"[process_image_generation_queue] Image {image_id} - Saved local path: {saved_file_path}")
|
||||
else:
|
||||
# Only URL available, save to image_url
|
||||
image.image_url = image_url
|
||||
logger.info(f"[process_image_generation_queue] Image {image_id} - Saved URL only: {image_url[:100] if image_url else 'None'}...")
|
||||
image.status = 'generated'
|
||||
logger.info(f"[process_image_generation_queue] Image {image_id} - Attempting to save to database")
|
||||
image.save(update_fields=['image_url', 'status'])
|
||||
|
||||
# Determine which fields to update
|
||||
update_fields = ['status']
|
||||
if saved_file_path:
|
||||
update_fields.append('image_path')
|
||||
if image_url:
|
||||
update_fields.append('image_url')
|
||||
|
||||
logger.info(f"[process_image_generation_queue] Image {image_id} - Attempting to save to database (fields: {update_fields})")
|
||||
image.save(update_fields=update_fields)
|
||||
logger.info(f"[process_image_generation_queue] Image {image_id} - Successfully saved to database")
|
||||
except Exception as save_error:
|
||||
error_str = str(save_error)
|
||||
@@ -659,7 +685,8 @@ def process_image_generation_queue(self, image_ids: list, account_id: int = None
|
||||
'results': results + [{
|
||||
'image_id': image_id,
|
||||
'status': 'completed',
|
||||
'image_url': saved_file_path or image_url,
|
||||
'image_url': image_url, # Original URL from API
|
||||
'image_path': saved_file_path, # Local file path if saved
|
||||
'revised_prompt': result.get('revised_prompt')
|
||||
}]
|
||||
}
|
||||
@@ -668,7 +695,8 @@ def process_image_generation_queue(self, image_ids: list, account_id: int = None
|
||||
results.append({
|
||||
'image_id': image_id,
|
||||
'status': 'completed',
|
||||
'image_url': saved_file_path or image_url,
|
||||
'image_url': image_url, # Original URL from API
|
||||
'image_path': saved_file_path, # Local file path if saved
|
||||
'revised_prompt': result.get('revised_prompt')
|
||||
})
|
||||
completed += 1
|
||||
|
||||
@@ -367,6 +367,76 @@ class ImagesViewSet(SiteSectorModelViewSet):
|
||||
else:
|
||||
serializer.save()
|
||||
|
||||
@action(detail=True, methods=['get'], url_path='file', url_name='image_file')
|
||||
def serve_image_file(self, request, pk=None):
|
||||
"""
|
||||
Serve image file from local path via URL
|
||||
GET /api/v1/writer/images/{id}/file/
|
||||
"""
|
||||
import os
|
||||
from django.http import FileResponse, Http404
|
||||
from django.conf import settings
|
||||
|
||||
try:
|
||||
# Get image directly without account filtering for file serving
|
||||
# This allows public access to image files
|
||||
try:
|
||||
image = Images.objects.get(pk=pk)
|
||||
except Images.DoesNotExist:
|
||||
return Response({
|
||||
'error': 'Image not found'
|
||||
}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# Check if image has a local path
|
||||
if not image.image_path:
|
||||
return Response({
|
||||
'error': 'No local file path available for this image'
|
||||
}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
file_path = image.image_path
|
||||
|
||||
# Verify file exists
|
||||
if not os.path.exists(file_path):
|
||||
return Response({
|
||||
'error': f'Image file not found at: {file_path}'
|
||||
}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
# Check if file is readable
|
||||
if not os.access(file_path, os.R_OK):
|
||||
return Response({
|
||||
'error': 'Image file is not readable'
|
||||
}, status=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
# Determine content type from file extension
|
||||
import mimetypes
|
||||
content_type, _ = mimetypes.guess_type(file_path)
|
||||
if not content_type:
|
||||
content_type = 'image/png' # Default to PNG
|
||||
|
||||
# Serve the file
|
||||
try:
|
||||
return FileResponse(
|
||||
open(file_path, 'rb'),
|
||||
content_type=content_type,
|
||||
filename=os.path.basename(file_path)
|
||||
)
|
||||
except Exception as e:
|
||||
return Response({
|
||||
'error': f'Failed to serve file: {str(e)}'
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
except Images.DoesNotExist:
|
||||
return Response({
|
||||
'error': 'Image not found'
|
||||
}, status=status.HTTP_404_NOT_FOUND)
|
||||
except Exception as e:
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.error(f"Error serving image file: {str(e)}", exc_info=True)
|
||||
return Response({
|
||||
'error': f'Failed to serve image: {str(e)}'
|
||||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
@action(detail=False, methods=['post'], url_path='auto_generate', url_name='auto_generate_images')
|
||||
def auto_generate_images(self, request):
|
||||
"""Auto-generate images for tasks using AI"""
|
||||
|
||||
Reference in New Issue
Block a user