diff --git a/backend/igny8_core/modules/planner/views.py b/backend/igny8_core/modules/planner/views.py index cccc0cdf..442dd60c 100644 --- a/backend/igny8_core/modules/planner/views.py +++ b/backend/igny8_core/modules/planner/views.py @@ -10,6 +10,7 @@ import json import time from igny8_core.api.base import SiteSectorModelViewSet from igny8_core.api.pagination import CustomPageNumberPagination +from igny8_core.api.response import success_response, error_response from .models import Keywords, Clusters, ContentIdeas from .serializers import KeywordSerializer, ContentIdeasSerializer from .cluster_serializers import ClusterSerializer @@ -124,10 +125,10 @@ class KeywordViewSet(SiteSectorModelViewSet): return Response(serializer.data) except Exception as e: logger.error(f"Error in KeywordViewSet.list(): {type(e).__name__}: {str(e)}", exc_info=True) - return Response({ - 'error': f'Error loading keywords: {str(e)}', - 'type': type(e).__name__ - }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + return error_response( + error=f'Error loading keywords: {str(e)}', + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ) def perform_create(self, serializer): """Require explicit site_id and sector_id - no defaults.""" @@ -190,12 +191,18 @@ class KeywordViewSet(SiteSectorModelViewSet): """Bulk delete keywords""" ids = request.data.get('ids', []) if not ids: - return Response({'error': 'No IDs provided'}, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='No IDs provided', + status_code=status.HTTP_400_BAD_REQUEST + ) queryset = self.get_queryset() deleted_count, _ = queryset.filter(id__in=ids).delete() - return Response({'deleted_count': deleted_count}, status=status.HTTP_200_OK) + return success_response( + data={'deleted_count': deleted_count}, + message=f'Successfully deleted {deleted_count} keyword(s)' + ) @action(detail=False, methods=['post'], url_path='bulk_update', url_name='bulk_update') def bulk_update(self, request): @@ -204,14 +211,23 @@ class KeywordViewSet(SiteSectorModelViewSet): status_value = request.data.get('status') if not ids: - return Response({'error': 'No IDs provided'}, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='No IDs provided', + status_code=status.HTTP_400_BAD_REQUEST + ) if not status_value: - return Response({'error': 'No status provided'}, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='No status provided', + status_code=status.HTTP_400_BAD_REQUEST + ) queryset = self.get_queryset() updated_count = queryset.filter(id__in=ids).update(status=status_value) - return Response({'updated_count': updated_count}, status=status.HTTP_200_OK) + return success_response( + data={'updated_count': updated_count}, + message=f'Successfully updated {updated_count} keyword(s)' + ) @action(detail=False, methods=['post'], url_path='bulk_add_from_seed', url_name='bulk_add_from_seed') def bulk_add_from_seed(self, request): @@ -223,32 +239,53 @@ class KeywordViewSet(SiteSectorModelViewSet): sector_id = request.data.get('sector_id') if not seed_keyword_ids: - return Response({'error': 'No seed keyword IDs provided'}, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='No seed keyword IDs provided', + status_code=status.HTTP_400_BAD_REQUEST + ) if not site_id: - return Response({'error': 'site_id is required'}, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='site_id is required', + status_code=status.HTTP_400_BAD_REQUEST + ) if not sector_id: - return Response({'error': 'sector_id is required'}, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='sector_id is required', + status_code=status.HTTP_400_BAD_REQUEST + ) try: site = Site.objects.get(id=site_id) sector = Sector.objects.get(id=sector_id) except (Site.DoesNotExist, Sector.DoesNotExist) as e: - return Response({'error': f'Invalid site or sector: {str(e)}'}, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error=f'Invalid site or sector: {str(e)}', + status_code=status.HTTP_400_BAD_REQUEST + ) # Validate sector belongs to site if sector.site != site: - return Response({'error': 'Sector does not belong to the specified site'}, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='Sector does not belong to the specified site', + status_code=status.HTTP_400_BAD_REQUEST + ) # Get account from site account = site.account if not account: - return Response({'error': 'Site has no account assigned'}, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='Site has no account assigned', + status_code=status.HTTP_400_BAD_REQUEST + ) # Get SeedKeywords seed_keywords = SeedKeyword.objects.filter(id__in=seed_keyword_ids, is_active=True) if not seed_keywords.exists(): - return Response({'error': 'No valid seed keywords found'}, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='No valid seed keywords found', + status_code=status.HTTP_400_BAD_REQUEST + ) created_count = 0 skipped_count = 0 @@ -288,12 +325,14 @@ class KeywordViewSet(SiteSectorModelViewSet): errors.append(f"Error adding '{seed_keyword.keyword}': {str(e)}") skipped_count += 1 - return Response({ - 'success': True, - 'created': created_count, - 'skipped': skipped_count, - 'errors': errors[:10] if errors else [] # Limit errors to first 10 - }, status=status.HTTP_200_OK) + return success_response( + data={ + 'created': created_count, + 'skipped': skipped_count, + 'errors': errors[:10] if errors else [] # Limit errors to first 10 + }, + message=f'Successfully added {created_count} keyword(s) to workflow' + ) @action(detail=False, methods=['get'], url_path='export', url_name='export') def export(self, request): @@ -366,11 +405,17 @@ class KeywordViewSet(SiteSectorModelViewSet): Automatically links keywords to current active site/sector. """ if 'file' not in request.FILES: - return Response({'error': 'No file provided'}, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='No file provided', + status_code=status.HTTP_400_BAD_REQUEST + ) file = request.FILES['file'] if not file.name.endswith('.csv'): - return Response({'error': 'File must be a CSV'}, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='File must be a CSV', + status_code=status.HTTP_400_BAD_REQUEST + ) user = getattr(request, 'user', None) @@ -391,23 +436,38 @@ class KeywordViewSet(SiteSectorModelViewSet): # Site ID is REQUIRED if not site_id: - return Response({'error': 'site_id is required'}, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='site_id is required', + status_code=status.HTTP_400_BAD_REQUEST + ) try: site = Site.objects.get(id=site_id) except Site.DoesNotExist: - return Response({'error': f'Site with id {site_id} does not exist'}, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error=f'Site with id {site_id} does not exist', + status_code=status.HTTP_400_BAD_REQUEST + ) # Sector ID is REQUIRED if not sector_id: - return Response({'error': 'sector_id is required'}, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='sector_id is required', + status_code=status.HTTP_400_BAD_REQUEST + ) try: sector = Sector.objects.get(id=sector_id) if sector.site_id != site_id: - return Response({'error': 'Sector does not belong to the selected site'}, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='Sector does not belong to the selected site', + status_code=status.HTTP_400_BAD_REQUEST + ) except Sector.DoesNotExist: - return Response({'error': f'Sector with id {sector_id} does not exist'}, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error=f'Sector with id {sector_id} does not exist', + status_code=status.HTTP_400_BAD_REQUEST + ) # Get account account = getattr(request, 'account', None) @@ -461,17 +521,20 @@ class KeywordViewSet(SiteSectorModelViewSet): errors.append(f"Row {row_num}: {str(e)}") continue - return Response({ - 'success': True, - 'imported': imported_count, - 'skipped': skipped_count, - 'errors': errors[:10] if errors else [] # Limit errors to first 10 - }, status=status.HTTP_200_OK) + return success_response( + data={ + 'imported': imported_count, + 'skipped': skipped_count, + 'errors': errors[:10] if errors else [] # Limit errors to first 10 + }, + message=f'Successfully imported {imported_count} keyword(s)' + ) except Exception as e: - return Response({ - 'error': f'Failed to parse CSV: {str(e)}' - }, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error=f'Failed to parse CSV: {str(e)}', + status_code=status.HTTP_400_BAD_REQUEST + ) @action(detail=False, methods=['post'], url_path='auto_cluster', url_name='auto_cluster') def auto_cluster(self, request): @@ -497,16 +560,16 @@ class KeywordViewSet(SiteSectorModelViewSet): # Validate basic input if not payload['ids']: - return Response({ - 'success': False, - 'error': 'No IDs provided' - }, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='No IDs provided', + status_code=status.HTTP_400_BAD_REQUEST + ) if len(payload['ids']) > 20: - return Response({ - 'success': False, - 'error': 'Maximum 20 keywords allowed for clustering' - }, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='Maximum 20 keywords allowed for clustering', + status_code=status.HTTP_400_BAD_REQUEST + ) # Try to queue Celery task try: @@ -517,11 +580,12 @@ class KeywordViewSet(SiteSectorModelViewSet): account_id=account_id ) logger.info(f"Task queued: {task.id}") - return Response({ - 'success': True, - 'task_id': str(task.id), - 'message': 'Clustering started' - }, status=status.HTTP_200_OK) + return success_response( + data={ + 'task_id': str(task.id) + }, + message='Clustering started' + ) else: # Celery not available - execute synchronously logger.warning("Celery not available, executing synchronously") @@ -531,15 +595,15 @@ class KeywordViewSet(SiteSectorModelViewSet): account_id=account_id ) if result.get('success'): - return Response({ - 'success': True, - **result - }, status=status.HTTP_200_OK) + return success_response( + data=result, + message='Clustering completed successfully' + ) else: - return Response({ - 'success': False, - 'error': result.get('error', 'Clustering failed') - }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + return error_response( + error=result.get('error', 'Clustering failed'), + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ) except (KombuOperationalError, ConnectionError) as e: # Broker connection failed - fall back to synchronous execution logger.warning(f"Celery broker unavailable, falling back to synchronous execution: {str(e)}") @@ -549,27 +613,27 @@ class KeywordViewSet(SiteSectorModelViewSet): account_id=account_id ) if result.get('success'): - return Response({ - 'success': True, - **result - }, status=status.HTTP_200_OK) + return success_response( + data=result, + message='Clustering completed successfully' + ) else: - return Response({ - 'success': False, - 'error': result.get('error', 'Clustering failed') - }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + return error_response( + error=result.get('error', 'Clustering failed'), + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ) except Exception as e: logger.error(f"Error in auto_cluster: {str(e)}", exc_info=True) - return Response({ - 'success': False, - 'error': str(e) - }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + return error_response( + error=str(e), + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ) except Exception as e: logger.error(f"Unexpected error in auto_cluster: {str(e)}", exc_info=True) - return Response({ - 'success': False, - 'error': f'Unexpected error: {str(e)}' - }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + return error_response( + error=f'Unexpected error: {str(e)}', + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ) class ClusterViewSet(SiteSectorModelViewSet): @@ -719,12 +783,18 @@ class ClusterViewSet(SiteSectorModelViewSet): """Bulk delete clusters""" ids = request.data.get('ids', []) if not ids: - return Response({'error': 'No IDs provided'}, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='No IDs provided', + status_code=status.HTTP_400_BAD_REQUEST + ) queryset = self.get_queryset() deleted_count, _ = queryset.filter(id__in=ids).delete() - return Response({'deleted_count': deleted_count}, status=status.HTTP_200_OK) + return success_response( + data={'deleted_count': deleted_count}, + message=f'Successfully deleted {deleted_count} cluster(s)' + ) @action(detail=False, methods=['post'], url_path='auto_generate_ideas', url_name='auto_generate_ideas') def auto_generate_ideas(self, request): @@ -749,16 +819,16 @@ class ClusterViewSet(SiteSectorModelViewSet): # Validate basic input if not payload['ids']: - return Response({ - 'success': False, - 'error': 'No IDs provided' - }, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='No IDs provided', + status_code=status.HTTP_400_BAD_REQUEST + ) if len(payload['ids']) > 10: - return Response({ - 'success': False, - 'error': 'Maximum 10 clusters allowed for idea generation' - }, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='Maximum 10 clusters allowed for idea generation', + status_code=status.HTTP_400_BAD_REQUEST + ) # Try to queue Celery task try: @@ -769,11 +839,12 @@ class ClusterViewSet(SiteSectorModelViewSet): account_id=account_id ) logger.info(f"Task queued: {task.id}") - return Response({ - 'success': True, - 'task_id': str(task.id), - 'message': 'Idea generation started' - }, status=status.HTTP_200_OK) + return success_response( + data={ + 'task_id': str(task.id) + }, + message='Idea generation started' + ) else: # Celery not available - execute synchronously logger.warning("Celery not available, executing synchronously") @@ -783,15 +854,15 @@ class ClusterViewSet(SiteSectorModelViewSet): account_id=account_id ) if result.get('success'): - return Response({ - 'success': True, - **result - }, status=status.HTTP_200_OK) + return success_response( + data=result, + message='Idea generation completed successfully' + ) else: - return Response({ - 'success': False, - 'error': result.get('error', 'Idea generation failed') - }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + return error_response( + error=result.get('error', 'Idea generation failed'), + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ) except (KombuOperationalError, ConnectionError) as e: # Broker connection failed - fall back to synchronous execution logger.warning(f"Celery broker unavailable, falling back to synchronous execution: {str(e)}") @@ -801,27 +872,27 @@ class ClusterViewSet(SiteSectorModelViewSet): account_id=account_id ) if result.get('success'): - return Response({ - 'success': True, - **result - }, status=status.HTTP_200_OK) + return success_response( + data=result, + message='Idea generation completed successfully' + ) else: - return Response({ - 'success': False, - 'error': result.get('error', 'Idea generation failed') - }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + return error_response( + error=result.get('error', 'Idea generation failed'), + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ) except Exception as e: logger.error(f"Error in auto_generate_ideas: {str(e)}", exc_info=True) - return Response({ - 'success': False, - 'error': str(e) - }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + return error_response( + error=str(e), + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ) except Exception as e: logger.error(f"Unexpected error in auto_generate_ideas: {str(e)}", exc_info=True) - return Response({ - 'success': False, - 'error': f'Unexpected error: {str(e)}' - }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + return error_response( + error=f'Unexpected error: {str(e)}', + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ) def list(self, request, *args, **kwargs): """ @@ -919,19 +990,28 @@ class ContentIdeasViewSet(SiteSectorModelViewSet): """Bulk delete content ideas""" ids = request.data.get('ids', []) if not ids: - return Response({'error': 'No IDs provided'}, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='No IDs provided', + status_code=status.HTTP_400_BAD_REQUEST + ) queryset = self.get_queryset() deleted_count, _ = queryset.filter(id__in=ids).delete() - return Response({'deleted_count': deleted_count}, status=status.HTTP_200_OK) + return success_response( + data={'deleted_count': deleted_count}, + message=f'Successfully deleted {deleted_count} content idea(s)' + ) @action(detail=False, methods=['post'], url_path='bulk_queue_to_writer', url_name='bulk_queue_to_writer') def bulk_queue_to_writer(self, request): """Queue ideas to writer by creating Tasks""" ids = request.data.get('ids', []) if not ids: - return Response({'error': 'No IDs provided'}, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='No IDs provided', + status_code=status.HTTP_400_BAD_REQUEST + ) queryset = self.get_queryset() ideas = queryset.filter(id__in=ids, status='new') # Only queue 'new' ideas @@ -958,11 +1038,12 @@ class ContentIdeasViewSet(SiteSectorModelViewSet): idea.status = 'scheduled' idea.save() - return Response({ - 'success': True, - 'created_count': len(created_tasks), - 'task_ids': created_tasks, - 'message': f'Successfully queued {len(created_tasks)} ideas to writer' - }, status=status.HTTP_200_OK) + return success_response( + data={ + 'created_count': len(created_tasks), + 'task_ids': created_tasks + }, + message=f'Successfully queued {len(created_tasks)} ideas to writer' + ) # REMOVED: generate_idea action - idea generation function removed diff --git a/backend/igny8_core/modules/writer/views.py b/backend/igny8_core/modules/writer/views.py index 19d963f1..9d519a5d 100644 --- a/backend/igny8_core/modules/writer/views.py +++ b/backend/igny8_core/modules/writer/views.py @@ -6,6 +6,7 @@ from django.db import transaction, models from django.db.models import Q from igny8_core.api.base import SiteSectorModelViewSet from igny8_core.api.pagination import CustomPageNumberPagination +from igny8_core.api.response import success_response, error_response from .models import Tasks, Images, Content from .serializers import TasksSerializer, ImagesSerializer, ContentSerializer @@ -84,12 +85,18 @@ class TasksViewSet(SiteSectorModelViewSet): """Bulk delete tasks""" ids = request.data.get('ids', []) if not ids: - return Response({'error': 'No IDs provided'}, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='No IDs provided', + status_code=status.HTTP_400_BAD_REQUEST + ) queryset = self.get_queryset() deleted_count, _ = queryset.filter(id__in=ids).delete() - return Response({'deleted_count': deleted_count}, status=status.HTTP_200_OK) + return success_response( + data={'deleted_count': deleted_count}, + message=f'Successfully deleted {deleted_count} task(s)' + ) @action(detail=False, methods=['post'], url_path='bulk_update', url_name='bulk_update') def bulk_update(self, request): @@ -98,14 +105,23 @@ class TasksViewSet(SiteSectorModelViewSet): status_value = request.data.get('status') if not ids: - return Response({'error': 'No IDs provided'}, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='No IDs provided', + status_code=status.HTTP_400_BAD_REQUEST + ) if not status_value: - return Response({'error': 'No status provided'}, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='No status provided', + status_code=status.HTTP_400_BAD_REQUEST + ) queryset = self.get_queryset() updated_count = queryset.filter(id__in=ids).update(status=status_value) - return Response({'updated_count': updated_count}, status=status.HTTP_200_OK) + return success_response( + data={'updated_count': updated_count}, + message=f'Successfully updated {updated_count} task(s)' + ) @action(detail=False, methods=['post'], url_path='auto_generate_content', url_name='auto_generate_content') def auto_generate_content(self, request): @@ -120,17 +136,17 @@ class TasksViewSet(SiteSectorModelViewSet): ids = request.data.get('ids', []) if not ids: logger.warning("auto_generate_content: No IDs provided") - return Response({ - 'error': 'No IDs provided', - 'type': 'ValidationError' - }, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='No IDs provided', + status_code=status.HTTP_400_BAD_REQUEST + ) if len(ids) > 10: logger.warning(f"auto_generate_content: Too many IDs provided: {len(ids)}") - return Response({ - 'error': 'Maximum 10 tasks allowed for content generation', - 'type': 'ValidationError' - }, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='Maximum 10 tasks allowed for content generation', + status_code=status.HTTP_400_BAD_REQUEST + ) logger.info(f"auto_generate_content: Processing {len(ids)} task IDs: {ids}") @@ -151,11 +167,10 @@ class TasksViewSet(SiteSectorModelViewSet): if existing_count == 0: logger.error(f"auto_generate_content: No tasks found for IDs: {ids}") - return Response({ - 'error': f'No tasks found for the provided IDs: {ids}', - 'type': 'NotFound', - 'requested_ids': ids - }, status=status.HTTP_404_NOT_FOUND) + return error_response( + error=f'No tasks found for the provided IDs: {ids}', + status_code=status.HTTP_404_NOT_FOUND + ) if existing_count < len(ids): missing_ids = set(ids) - set(existing_ids) @@ -171,11 +186,10 @@ class TasksViewSet(SiteSectorModelViewSet): logger.error(f" - Account ID: {account_id}") logger.error("=" * 80, exc_info=True) - return Response({ - 'error': f'Database error while querying tasks: {str(db_error)}', - 'type': 'OperationalError', - 'details': 'Failed to retrieve tasks from database. Please check database connection and try again.' - }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + return error_response( + error=f'Database error while querying tasks: {str(db_error)}', + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ) # Try to queue Celery task, fall back to synchronous if Celery not available try: @@ -192,11 +206,10 @@ class TasksViewSet(SiteSectorModelViewSet): account_id=account_id ) logger.info(f"auto_generate_content: Celery task queued successfully: {task.id}") - return Response({ - 'success': True, - 'task_id': str(task.id), - 'message': 'Content generation started' - }, status=status.HTTP_200_OK) + return success_response( + data={'task_id': str(task.id)}, + message='Content generation started' + ) except KombuOperationalError as celery_error: logger.error("=" * 80) logger.error("CELERY ERROR: Failed to queue task") @@ -206,10 +219,10 @@ class TasksViewSet(SiteSectorModelViewSet): logger.error(f" - Account ID: {account_id}") logger.error("=" * 80, exc_info=True) - return Response({ - 'error': 'Task queue unavailable. Please try again.', - 'type': 'QueueError' - }, status=status.HTTP_503_SERVICE_UNAVAILABLE) + return error_response( + error='Task queue unavailable. Please try again.', + status_code=status.HTTP_503_SERVICE_UNAVAILABLE + ) except Exception as celery_error: logger.error("=" * 80) logger.error("CELERY ERROR: Failed to queue task") @@ -227,16 +240,15 @@ class TasksViewSet(SiteSectorModelViewSet): account_id=account_id ) if result.get('success'): - return Response({ - 'success': True, - 'tasks_updated': result.get('count', 0), - 'message': 'Content generated successfully (synchronous)' - }, status=status.HTTP_200_OK) + return success_response( + data={'tasks_updated': result.get('count', 0)}, + message='Content generated successfully (synchronous)' + ) else: - return Response({ - 'error': result.get('error', 'Content generation failed'), - 'type': 'TaskExecutionError' - }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + return error_response( + error=result.get('error', 'Content generation failed'), + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ) else: # Celery not available - execute synchronously logger.info(f"auto_generate_content: Executing synchronously (Celery not available)") @@ -247,17 +259,16 @@ class TasksViewSet(SiteSectorModelViewSet): ) if result.get('success'): logger.info(f"auto_generate_content: Synchronous execution successful: {result.get('count', 0)} tasks updated") - return Response({ - 'success': True, - 'tasks_updated': result.get('count', 0), - 'message': 'Content generated successfully' - }, status=status.HTTP_200_OK) + return success_response( + data={'tasks_updated': result.get('count', 0)}, + message='Content generated successfully' + ) else: logger.error(f"auto_generate_content: Synchronous execution failed: {result.get('error', 'Unknown error')}") - return Response({ - 'error': result.get('error', 'Content generation failed'), - 'type': 'TaskExecutionError' - }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + return error_response( + error=result.get('error', 'Content generation failed'), + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ) except ImportError as import_error: logger.error(f"auto_generate_content: ImportError - tasks module not available: {str(import_error)}") @@ -268,21 +279,20 @@ class TasksViewSet(SiteSectorModelViewSet): updated_count = tasks.update(status='completed', content='[AI content generation not available]') logger.info(f"auto_generate_content: Updated {updated_count} tasks (AI generation not available)") - return Response({ - 'updated_count': updated_count, - 'message': 'Tasks updated (AI generation not available)' - }, status=status.HTTP_200_OK) + return success_response( + data={'updated_count': updated_count}, + message='Tasks updated (AI generation not available)' + ) except (OperationalError, DatabaseError) as db_error: logger.error("=" * 80) logger.error("DATABASE ERROR: Failed to update tasks") logger.error(f" - Error type: {type(db_error).__name__}") logger.error(f" - Error message: {str(db_error)}") logger.error("=" * 80, exc_info=True) - return Response({ - 'error': f'Database error while updating tasks: {str(db_error)}', - 'type': 'OperationalError', - 'details': 'Failed to update tasks in database. Please check database connection.' - }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + return error_response( + error=f'Database error while updating tasks: {str(db_error)}', + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ) except (OperationalError, DatabaseError) as db_error: logger.error("=" * 80) @@ -293,11 +303,10 @@ class TasksViewSet(SiteSectorModelViewSet): logger.error(f" - Account ID: {account_id}") logger.error("=" * 80, exc_info=True) - return Response({ - 'error': f'Database error during content generation: {str(db_error)}', - 'type': 'OperationalError', - 'details': 'A database operation failed. This may be due to connection issues, constraint violations, or data integrity problems. Check the logs for more details.' - }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + return error_response( + error=f'Database error during content generation: {str(db_error)}', + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ) except IntegrityError as integrity_error: logger.error("=" * 80) @@ -306,18 +315,17 @@ class TasksViewSet(SiteSectorModelViewSet): logger.error(f" - Task IDs: {ids}") logger.error("=" * 80, exc_info=True) - return Response({ - 'error': f'Data integrity error: {str(integrity_error)}', - 'type': 'IntegrityError', - 'details': 'The operation violated database constraints. This may indicate missing required relationships or invalid data.' - }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + return error_response( + error=f'Data integrity error: {str(integrity_error)}', + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ) except ValidationError as validation_error: logger.error(f"auto_generate_content: ValidationError: {str(validation_error)}") - return Response({ - 'error': f'Validation error: {str(validation_error)}', - 'type': 'ValidationError' - }, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error=f'Validation error: {str(validation_error)}', + status_code=status.HTTP_400_BAD_REQUEST + ) except Exception as e: logger.error("=" * 80) @@ -328,11 +336,10 @@ class TasksViewSet(SiteSectorModelViewSet): logger.error(f" - Account ID: {account_id}") logger.error("=" * 80, exc_info=True) - return Response({ - 'error': f'Unexpected error: {str(e)}', - 'type': type(e).__name__, - 'details': 'An unexpected error occurred. Please check the logs for more details.' - }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + return error_response( + error=f'Unexpected error: {str(e)}', + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ) except Exception as outer_error: logger.error("=" * 80) @@ -341,10 +348,10 @@ class TasksViewSet(SiteSectorModelViewSet): logger.error(f" - Error message: {str(outer_error)}") logger.error("=" * 80, exc_info=True) - return Response({ - 'error': f'Critical error: {str(outer_error)}', - 'type': type(outer_error).__name__ - }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + return error_response( + error=f'Critical error: {str(outer_error)}', + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ) class ImagesViewSet(SiteSectorModelViewSet): @@ -383,30 +390,34 @@ class ImagesViewSet(SiteSectorModelViewSet): try: image = Images.objects.get(pk=pk) except Images.DoesNotExist: - return Response({ - 'error': 'Image not found' - }, status=status.HTTP_404_NOT_FOUND) + return error_response( + error='Image not found', + status_code=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) + return error_response( + error='No local file path available for this image', + status_code=status.HTTP_404_NOT_FOUND + ) file_path = image.image_path # Verify file exists at the saved path if not os.path.exists(file_path): logger.error(f"[serve_image_file] Image {pk} - File not found at saved path: {file_path}") - return Response({ - 'error': f'Image file not found at: {file_path}' - }, status=status.HTTP_404_NOT_FOUND) + return error_response( + error=f'Image file not found at: {file_path}', + status_code=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) + return error_response( + error='Image file is not readable', + status_code=status.HTTP_403_FORBIDDEN + ) # Determine content type from file extension import mimetypes @@ -422,31 +433,40 @@ class ImagesViewSet(SiteSectorModelViewSet): 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) + return error_response( + error=f'Failed to serve file: {str(e)}', + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ) except Images.DoesNotExist: - return Response({ - 'error': 'Image not found' - }, status=status.HTTP_404_NOT_FOUND) + return error_response( + error='Image not found', + status_code=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) + return error_response( + error=f'Failed to serve image: {str(e)}', + status_code=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""" task_ids = request.data.get('task_ids', []) if not task_ids: - return Response({'error': 'No task IDs provided'}, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='No task IDs provided', + status_code=status.HTTP_400_BAD_REQUEST + ) if len(task_ids) > 10: - return Response({'error': 'Maximum 10 tasks allowed for image generation'}, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='Maximum 10 tasks allowed for image generation', + status_code=status.HTTP_400_BAD_REQUEST + ) # Get account account = getattr(request, 'account', None) @@ -464,11 +484,10 @@ class ImagesViewSet(SiteSectorModelViewSet): payload={'ids': task_ids}, account_id=account_id ) - return Response({ - 'success': True, - 'task_id': str(task.id), - 'message': 'Image generation started' - }, status=status.HTTP_200_OK) + return success_response( + data={'task_id': str(task.id)}, + message='Image generation started' + ) else: # Celery not available - execute synchronously result = run_ai_task( @@ -477,33 +496,34 @@ class ImagesViewSet(SiteSectorModelViewSet): account_id=account_id ) if result.get('success'): - return Response({ - 'success': True, - 'images_created': result.get('count', 0), - 'message': result.get('message', 'Image generation completed') - }, status=status.HTTP_200_OK) + return success_response( + data={'images_created': result.get('count', 0)}, + message=result.get('message', 'Image generation completed') + ) else: - return Response({ - 'error': result.get('error', 'Image generation failed') - }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + return error_response( + error=result.get('error', 'Image generation failed'), + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ) except KombuOperationalError as e: - return Response({ - 'error': 'Task queue unavailable. Please try again.', - 'type': 'QueueError' - }, status=status.HTTP_503_SERVICE_UNAVAILABLE) + return error_response( + error='Task queue unavailable. Please try again.', + status_code=status.HTTP_503_SERVICE_UNAVAILABLE + ) except ImportError: # Tasks module not available - return Response({ - 'error': 'Image generation task not available' - }, status=status.HTTP_503_SERVICE_UNAVAILABLE) + return error_response( + error='Image generation task not available', + status_code=status.HTTP_503_SERVICE_UNAVAILABLE + ) except Exception as e: import logging logger = logging.getLogger(__name__) logger.error(f"Error queuing image generation task: {str(e)}", exc_info=True) - return Response({ - 'error': f'Failed to start image generation: {str(e)}', - 'type': 'TaskError' - }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + return error_response( + error=f'Failed to start image generation: {str(e)}', + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ) @action(detail=False, methods=['post'], url_path='bulk_update', url_name='bulk_update') def bulk_update(self, request): @@ -518,7 +538,10 @@ class ImagesViewSet(SiteSectorModelViewSet): status_value = request.data.get('status') if not status_value: - return Response({'error': 'No status provided'}, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='No status provided', + status_code=status.HTTP_400_BAD_REQUEST + ) queryset = self.get_queryset() @@ -534,13 +557,22 @@ class ImagesViewSet(SiteSectorModelViewSet): Q(content=content) | Q(task=content.task) ).update(status=status_value) except Content.DoesNotExist: - return Response({'error': 'Content not found'}, status=status.HTTP_404_NOT_FOUND) + return error_response( + error='Content not found', + status_code=status.HTTP_404_NOT_FOUND + ) elif image_ids: updated_count = queryset.filter(id__in=image_ids).update(status=status_value) else: - return Response({'error': 'Either content_id or ids must be provided'}, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='Either content_id or ids must be provided', + status_code=status.HTTP_400_BAD_REQUEST + ) - return Response({'updated_count': updated_count}, status=status.HTTP_200_OK) + return success_response( + data={'updated_count': updated_count}, + message=f'Successfully updated {updated_count} image(s)' + ) @action(detail=False, methods=['get'], url_path='content_images', url_name='content_images') def content_images(self, request): @@ -621,10 +653,12 @@ class ImagesViewSet(SiteSectorModelViewSet): # Sort by content title grouped_data.sort(key=lambda x: x['content_title']) - return Response({ - 'count': len(grouped_data), - 'results': grouped_data - }, status=status.HTTP_200_OK) + return success_response( + data={ + 'count': len(grouped_data), + 'results': grouped_data + } + ) @action(detail=False, methods=['post'], url_path='generate_images', url_name='generate_images') def generate_images(self, request): @@ -636,10 +670,10 @@ class ImagesViewSet(SiteSectorModelViewSet): content_id = request.data.get('content_id') if not image_ids: - return Response({ - 'error': 'No image IDs provided', - 'type': 'ValidationError' - }, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='No image IDs provided', + status_code=status.HTTP_400_BAD_REQUEST + ) account_id = account.id if account else None @@ -651,11 +685,10 @@ class ImagesViewSet(SiteSectorModelViewSet): account_id=account_id, content_id=content_id ) - return Response({ - 'success': True, - 'task_id': str(task.id), - 'message': 'Image generation started' - }, status=status.HTTP_200_OK) + return success_response( + data={'task_id': str(task.id)}, + message='Image generation started' + ) else: # Fallback to synchronous execution (for testing) result = process_image_generation_queue( @@ -663,13 +696,19 @@ class ImagesViewSet(SiteSectorModelViewSet): account_id=account_id, content_id=content_id ) - return Response(result, status=status.HTTP_200_OK) + if result.get('success'): + return success_response(data=result) + else: + return error_response( + error=result.get('error', 'Image generation failed'), + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ) except Exception as e: logger.error(f"[generate_images] Error: {str(e)}", exc_info=True) - return Response({ - 'error': str(e), - 'type': 'ExecutionError' - }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + return error_response( + error=str(e), + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ) class ContentViewSet(SiteSectorModelViewSet): """ @@ -702,10 +741,10 @@ class ContentViewSet(SiteSectorModelViewSet): ids = request.data.get('ids', []) if not ids: - return Response({ - 'error': 'No IDs provided', - 'type': 'ValidationError' - }, status=status.HTTP_400_BAD_REQUEST) + return error_response( + error='No IDs provided', + status_code=status.HTTP_400_BAD_REQUEST + ) account_id = account.id if account else None @@ -717,11 +756,10 @@ class ContentViewSet(SiteSectorModelViewSet): payload={'ids': ids}, account_id=account_id ) - return Response({ - 'success': True, - 'task_id': str(task.id), - 'message': 'Image prompt generation started' - }, status=status.HTTP_200_OK) + return success_response( + data={'task_id': str(task.id)}, + message='Image prompt generation started' + ) else: # Fallback to synchronous execution result = run_ai_task( @@ -730,19 +768,18 @@ class ContentViewSet(SiteSectorModelViewSet): account_id=account_id ) if result.get('success'): - return Response({ - 'success': True, - 'prompts_created': result.get('count', 0), - 'message': 'Image prompts generated successfully' - }, status=status.HTTP_200_OK) + return success_response( + data={'prompts_created': result.get('count', 0)}, + message='Image prompts generated successfully' + ) else: - return Response({ - 'error': result.get('error', 'Image prompt generation failed'), - 'type': 'TaskExecutionError' - }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + return error_response( + error=result.get('error', 'Image prompt generation failed'), + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + ) except Exception as e: - return Response({ - 'error': str(e), - 'type': 'ExecutionError' - }, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + return error_response( + error=str(e), + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR + )