Files
igny8/backend/igny8_core/modules/writer/views.py
2025-11-30 13:02:27 +05:00

276 lines
11 KiB
Python

from rest_framework import viewsets, filters, status
from rest_framework.decorators import action
from rest_framework.response import Response
from django_filters.rest_framework import DjangoFilterBackend
from django.db import transaction, models
from django.db.models import Q
from drf_spectacular.utils import extend_schema, extend_schema_view
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 igny8_core.api.throttles import DebugScopedRateThrottle
from igny8_core.api.permissions import IsAuthenticatedAndActive, IsViewerOrAbove, IsEditorOrAbove
from .models import Tasks, Images, Content
from .serializers import (
TasksSerializer,
ImagesSerializer,
ContentSerializer,
ContentTaxonomySerializer,
)
from igny8_core.business.content.models import ContentTaxonomy # ContentAttribute model exists but serializer removed in Stage 1
from igny8_core.business.content.services.content_generation_service import ContentGenerationService
from igny8_core.business.content.services.validation_service import ContentValidationService
from igny8_core.business.content.services.metadata_mapping_service import MetadataMappingService
from igny8_core.business.billing.exceptions import InsufficientCreditsError
@extend_schema_view(
list=extend_schema(tags=['Writer']),
create=extend_schema(tags=['Writer']),
retrieve=extend_schema(tags=['Writer']),
update=extend_schema(tags=['Writer']),
partial_update=extend_schema(tags=['Writer']),
destroy=extend_schema(tags=['Writer']),
)
class TasksViewSet(SiteSectorModelViewSet):
"""
ViewSet for managing tasks with CRUD operations
Unified API Standard v1.0 compliant
Stage 1 Refactored - removed deprecated filters
"""
queryset = Tasks.objects.select_related('cluster', 'site', 'sector')
serializer_class = TasksSerializer
permission_classes = [IsAuthenticatedAndActive, IsViewerOrAbove]
pagination_class = CustomPageNumberPagination # Explicitly use custom pagination
throttle_scope = 'writer'
throttle_classes = [DebugScopedRateThrottle]
# DRF filtering configuration
filter_backends = [DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter]
# Search configuration
search_fields = ['title', 'keywords']
# Ordering configuration
ordering_fields = ['title', 'created_at', 'status']
ordering = ['-created_at'] # Default ordering (newest first)
# Filter configuration - Stage 1: removed entity_type, cluster_role
filterset_fields = ['status', 'cluster_id', 'content_type', 'content_structure']
def perform_create(self, serializer):
"""Require explicit site_id and sector_id - no defaults."""
user = getattr(self.request, 'user', None)
try:
query_params = getattr(self.request, 'query_params', None)
if query_params is None:
query_params = getattr(self.request, 'GET', {})
except AttributeError:
query_params = {}
site_id = serializer.validated_data.get('site_id') or query_params.get('site_id')
sector_id = serializer.validated_data.get('sector_id') or query_params.get('sector_id')
from igny8_core.auth.models import Site, Sector
from rest_framework.exceptions import ValidationError
# Site ID is REQUIRED
if not site_id:
raise ValidationError("site_id is required. Please select a site.")
try:
site = Site.objects.get(id=site_id)
except Site.DoesNotExist:
raise ValidationError(f"Site with id {site_id} does not exist")
# Sector ID is REQUIRED
if not sector_id:
raise ValidationError("sector_id is required. Please select a sector.")
try:
sector = Sector.objects.get(id=sector_id)
if sector.site_id != site_id:
raise ValidationError(f"Sector does not belong to the selected site")
except Sector.DoesNotExist:
raise ValidationError(f"Sector with id {sector_id} does not exist")
serializer.validated_data.pop('site_id', None)
serializer.validated_data.pop('sector_id', None)
account = getattr(self.request, 'account', None)
if not account and user and user.is_authenticated and user.account:
account = user.account
if not account:
account = site.account
serializer.save(account=account, site=site, sector=sector)
@action(detail=False, methods=['POST'], url_path='bulk_delete', url_name='bulk_delete')
def bulk_delete(self, request):
"""Bulk delete tasks with improved error handling"""
ids = request.data.get('ids', [])
if not ids:
return error_response(
error='No IDs provided',
status_code=status.HTTP_400_BAD_REQUEST,
request=request
)
# Validate that all IDs are integers
try:
ids = [int(id) for id in ids]
except (ValueError, TypeError):
return error_response(
error='Invalid ID format. All IDs must be integers.',
status_code=status.HTTP_400_BAD_REQUEST,
request=request
)
# Get queryset with proper filtering
queryset = self.get_queryset()
# Filter by IDs and account/site context
filtered_queryset = queryset.filter(id__in=ids)
# Check if user has permission to delete all tasks
if not filtered_queryset.exists():
return error_response(
error='No valid tasks found for deletion',
status_code=status.HTTP_404_NOT_FOUND,
request=request
)
# Perform bulk delete
try:
deleted_count, _ = filtered_queryset.delete()
# Return success response
return success_response(
data={'deleted_count': deleted_count},
message=f'Successfully deleted {deleted_count} task(s)',
request=request
)
except Exception as e:
return error_response(
error=f'Failed to delete tasks: {str(e)}',
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request
)
@action(detail=False, methods=['post'], url_path='bulk_update', url_name='bulk_update')
def bulk_update(self, request):
"""Bulk update task status"""
ids = request.data.get('ids', [])
status_value = request.data.get('status')
if not ids:
return error_response(
error='No IDs provided',
status_code=status.HTTP_400_BAD_REQUEST,
request=request
)
if not status_value:
return error_response(
error='No status provided',
status_code=status.HTTP_400_BAD_REQUEST,
request=request
)
queryset = self.get_queryset()
updated_count = queryset.filter(id__in=ids).update(status=status_value)
return success_response(data={'updated_count': updated_count}, request=request)
@action(detail=False, methods=['post'], url_path='auto_generate_content', url_name='auto_generate_content')
def auto_generate_content(self, request):
"""Auto-generate content for tasks using ContentGenerationService"""
import logging
logger = logging.getLogger(__name__)
try:
ids = request.data.get('ids', [])
if not ids:
return error_response(
error='No IDs provided',
status_code=status.HTTP_400_BAD_REQUEST,
request=request
)
if len(ids) > 10:
return error_response(
error='Maximum 10 tasks allowed for content generation',
status_code=status.HTTP_400_BAD_REQUEST,
request=request
)
# Get account
account = getattr(request, 'account', None)
if not account:
return error_response(
error='Account is required',
status_code=status.HTTP_400_BAD_REQUEST,
request=request
)
# Validate task IDs exist
queryset = self.get_queryset()
existing_tasks = queryset.filter(id__in=ids, account=account)
existing_count = existing_tasks.count()
if existing_count == 0:
return error_response(
error=f'No tasks found for the provided IDs: {ids}',
status_code=status.HTTP_404_NOT_FOUND,
request=request
)
# Use service to generate content
service = ContentGenerationService()
try:
result = service.generate_content(ids, account)
if result.get('success'):
if 'task_id' in result:
# Async task queued
return success_response(
data={'task_id': result['task_id']},
message=result.get('message', 'Content generation started'),
request=request
)
else:
# Synchronous execution
return success_response(
data=result,
message='Content generated successfully',
request=request
)
else:
return error_response(
error=result.get('error', 'Content generation failed'),
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request
)
except InsufficientCreditsError as e:
return error_response(
error=str(e),
status_code=status.HTTP_402_PAYMENT_REQUIRED,
request=request
)
except Exception as e:
logger.error(f"Error in auto_generate_content: {str(e)}", exc_info=True)
return error_response(
error=f'Content generation failed: {str(e)}',
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request
)
except Exception as e:
logger.error(f"Unexpected error in auto_generate_content: {str(e)}", exc_info=True)
return error_response(
error=f'Unexpected error: {str(e)}',
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request
)