from rest_framework import serializers from django.db import models from django.core.exceptions import ObjectDoesNotExist, ValidationError from django.conf import settings from .models import Tasks, Images, Content from igny8_core.business.planning.models import Clusters from igny8_core.business.content.models import ContentTaxonomy class TasksSerializer(serializers.ModelSerializer): """Serializer for Tasks model - Stage 1 refactored""" cluster_name = serializers.SerializerMethodField() sector_name = serializers.SerializerMethodField() site_id = serializers.IntegerField(write_only=True, required=False) sector_id = serializers.IntegerField(write_only=True, required=False) class Meta: model = Tasks fields = [ 'id', 'title', 'description', 'cluster_id', 'cluster_name', 'content_type', 'content_structure', 'taxonomy_term_id', 'word_count', 'status', 'sector_name', 'site_id', 'sector_id', 'account_id', 'created_at', 'updated_at', ] read_only_fields = ['id', 'created_at', 'updated_at', 'account_id'] def validate(self, attrs): """Ensure required fields for Task creation""" if self.instance is None: # Create operation if not attrs.get('cluster_id') and not attrs.get('cluster'): raise ValidationError({'cluster': 'Cluster is required'}) if not attrs.get('content_type'): raise ValidationError({'content_type': 'Content type is required'}) if not attrs.get('content_structure'): raise ValidationError({'content_structure': 'Content structure is required'}) # Default status to queued if not provided if 'status' not in attrs: attrs['status'] = 'queued' return attrs def get_cluster_name(self, obj): """Get cluster name from Clusters model""" if obj.cluster_id: try: cluster = Clusters.objects.get(id=obj.cluster_id) return cluster.name except Clusters.DoesNotExist: return None return None def get_sector_name(self, obj): """Get sector name from Sector model""" if obj.sector_id: try: from igny8_core.auth.models import Sector sector = Sector.objects.get(id=obj.sector_id) return sector.name except Sector.DoesNotExist: return None return None class ImagesSerializer(serializers.ModelSerializer): """Serializer for Images model""" task_title = serializers.SerializerMethodField() content_title = serializers.SerializerMethodField() class Meta: model = Images fields = [ 'id', 'task_id', 'task_title', 'content_id', 'content_title', 'image_type', 'image_url', 'image_path', 'prompt', 'status', 'position', 'created_at', 'updated_at', 'account_id', ] read_only_fields = ['id', 'created_at', 'updated_at', 'account_id'] def get_task_title(self, obj): """Get task title""" if obj.task_id: try: task = Tasks.objects.get(id=obj.task_id) return task.title except Tasks.DoesNotExist: return None return None def get_content_title(self, obj): """Get content title""" if obj.content: return obj.content.title or obj.content.meta_title return None class ContentImageSerializer(serializers.ModelSerializer): """Serializer for individual image in grouped content images""" image_url = serializers.SerializerMethodField() class Meta: model = Images fields = [ 'id', 'image_type', 'image_url', 'image_path', 'prompt', 'status', 'position', 'created_at', 'updated_at', ] def get_image_url(self, obj): """ Return original image_url from database (Runware/OpenAI URL). No transformation - returns the exact value stored in image_url field. """ return obj.image_url class ContentImagesGroupSerializer(serializers.Serializer): """Serializer for grouped content images - one row per content""" content_id = serializers.IntegerField() content_title = serializers.CharField() content_status = serializers.CharField() # draft, review, publish featured_image = ContentImageSerializer(allow_null=True) in_article_images = ContentImageSerializer(many=True) overall_status = serializers.CharField() # 'pending', 'partial', 'complete', 'failed' class ContentSerializer(serializers.ModelSerializer): """Serializer for Content model - Stage 1 refactored""" cluster_name = serializers.SerializerMethodField() sector_name = serializers.SerializerMethodField() taxonomy_terms_data = serializers.SerializerMethodField() tags = serializers.SerializerMethodField() categories = serializers.SerializerMethodField() has_image_prompts = serializers.SerializerMethodField() image_status = serializers.SerializerMethodField() has_generated_images = serializers.SerializerMethodField() site_id = serializers.IntegerField(write_only=True, required=False) sector_id = serializers.IntegerField(write_only=True, required=False) class Meta: model = Content fields = [ 'id', 'title', 'content_html', 'meta_title', 'meta_description', 'primary_keyword', 'secondary_keywords', 'cluster_id', 'cluster_name', 'content_type', 'content_structure', 'taxonomy_terms_data', 'tags', 'categories', 'external_id', 'external_url', 'source', 'status', 'word_count', 'sector_name', 'site_id', 'sector_id', 'account_id', 'has_image_prompts', 'image_status', 'has_generated_images', 'created_at', 'updated_at', ] read_only_fields = ['id', 'created_at', 'updated_at', 'account_id'] def validate(self, attrs): """Ensure required fields for Content creation""" if self.instance is None: # Create operation if not attrs.get('cluster_id') and not attrs.get('cluster'): raise ValidationError({'cluster': 'Cluster is required'}) if not attrs.get('content_type'): raise ValidationError({'content_type': 'Content type is required'}) if not attrs.get('content_structure'): raise ValidationError({'content_structure': 'Content structure is required'}) if not attrs.get('title'): raise ValidationError({'title': 'Title is required'}) # Default source to igny8 if not provided if 'source' not in attrs: attrs['source'] = 'igny8' # Default status to draft if not provided if 'status' not in attrs: attrs['status'] = 'draft' return attrs def get_cluster_name(self, obj): """Get cluster name""" if obj.cluster_id: try: cluster = Clusters.objects.get(id=obj.cluster_id) return cluster.name except Clusters.DoesNotExist: return None return None def get_sector_name(self, obj): """Get sector name""" if obj.sector_id: try: from igny8_core.auth.models import Sector sector = Sector.objects.get(id=obj.sector_id) return sector.name except Sector.DoesNotExist: return None return None def get_taxonomy_terms_data(self, obj): """Get taxonomy terms with details""" return [ { 'id': term.id, 'name': term.name, 'slug': term.slug, 'taxonomy_type': term.taxonomy_type, 'external_id': term.external_id, 'external_taxonomy': term.external_taxonomy, } for term in obj.taxonomy_terms.all() ] def get_tags(self, obj): """Get only tags (taxonomy_type='tag')""" return [ term.name for term in obj.taxonomy_terms.filter(taxonomy_type='tag') ] def get_categories(self, obj): """Get only categories (taxonomy_type='category')""" return [ term.name for term in obj.taxonomy_terms.filter(taxonomy_type='category') ] def get_has_image_prompts(self, obj): """Check if content has any image prompts (images with prompts)""" return obj.images.filter(prompt__isnull=False).exclude(prompt='').exists() def get_image_status(self, obj): """Get image generation status: 'generated', 'pending', 'failed', or None""" images = obj.images.all() if not images.exists(): return None # Check statuses has_failed = images.filter(status='failed').exists() has_generated = images.filter(status='generated').exists() has_pending = images.filter(status='pending').exists() # Priority: failed > pending > generated if has_failed: return 'failed' elif has_pending: return 'pending' elif has_generated: return 'generated' return None def get_has_generated_images(self, obj): """Check if content has any successfully generated images""" return obj.images.filter(status='generated', image_url__isnull=False).exclude(image_url='').exists() class ContentTaxonomySerializer(serializers.ModelSerializer): """Serializer for ContentTaxonomy model - Stage 1 refactored""" content_count = serializers.SerializerMethodField() site_id = serializers.IntegerField(write_only=True, required=False) sector_id = serializers.IntegerField(write_only=True, required=False) class Meta: model = ContentTaxonomy fields = [ 'id', 'name', 'slug', 'taxonomy_type', 'external_id', 'external_taxonomy', 'content_count', 'site_id', 'sector_id', 'account_id', 'created_at', 'updated_at', ] read_only_fields = ['id', 'created_at', 'updated_at', 'account_id'] def get_content_count(self, obj): """Get count of content using this taxonomy""" return obj.contents.count() # ContentAttributeSerializer and ContentTaxonomyRelationSerializer removed in Stage 1 # These models no longer exist - simplified to direct M2M relationships # UpdatedTasksSerializer removed - duplicate of TasksSerializer which is already refactored