section2-3
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user