from rest_framework import serializers from django.db import models from django.core.exceptions import ObjectDoesNotExist from .models import Tasks, Images, Content from igny8_core.business.planning.models import Clusters, ContentIdeas from igny8_core.business.content.models import ( ContentClusterMap, ContentTaxonomyMap, ContentAttributeMap, ) class TasksSerializer(serializers.ModelSerializer): """Serializer for Tasks model""" cluster_name = serializers.SerializerMethodField() sector_name = serializers.SerializerMethodField() idea_title = serializers.SerializerMethodField() site_id = serializers.IntegerField(write_only=True, required=False) sector_id = serializers.IntegerField(write_only=True, required=False) content_html = serializers.SerializerMethodField() content_primary_keyword = serializers.SerializerMethodField() content_secondary_keywords = serializers.SerializerMethodField() content_tags = serializers.SerializerMethodField() content_categories = serializers.SerializerMethodField() cluster_mappings = serializers.SerializerMethodField() taxonomy_mappings = serializers.SerializerMethodField() attribute_mappings = serializers.SerializerMethodField() class Meta: model = Tasks fields = [ 'id', 'title', 'description', 'keywords', 'cluster_id', 'cluster_name', 'sector_name', 'idea_id', 'idea_title', 'content_structure', 'content_type', 'status', 'content', 'word_count', 'meta_title', 'meta_description', 'content_html', 'content_primary_keyword', 'content_secondary_keywords', 'content_tags', 'content_categories', 'cluster_mappings', 'taxonomy_mappings', 'attribute_mappings', 'assigned_post_id', 'post_url', 'created_at', 'updated_at', 'site_id', 'sector_id', 'account_id', ] read_only_fields = ['id', 'created_at', 'updated_at', 'account_id'] 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 def get_idea_title(self, obj): """Get idea title from ContentIdeas model""" if obj.idea_id: try: idea = ContentIdeas.objects.get(id=obj.idea_id) return idea.idea_title except ContentIdeas.DoesNotExist: return None return None def _get_content_record(self, obj): try: return obj.content_record except (AttributeError, ObjectDoesNotExist): return None def get_content_html(self, obj): record = self._get_content_record(obj) return record.html_content if record else None def get_content_primary_keyword(self, obj): record = self._get_content_record(obj) return record.primary_keyword if record else None def get_content_secondary_keywords(self, obj): record = self._get_content_record(obj) return record.secondary_keywords if record else [] def get_content_tags(self, obj): record = self._get_content_record(obj) return record.tags if record else [] def get_content_categories(self, obj): record = self._get_content_record(obj) return record.categories if record else [] def _cluster_map_qs(self, obj): return ContentClusterMap.objects.filter(task=obj).select_related('cluster') def _taxonomy_map_qs(self, obj): return ContentTaxonomyMap.objects.filter(task=obj).select_related('taxonomy') def _attribute_map_qs(self, obj): return ContentAttributeMap.objects.filter(task=obj) def get_cluster_mappings(self, obj): mappings = [] for mapping in self._cluster_map_qs(obj): mappings.append({ 'cluster_id': mapping.cluster_id, 'cluster_name': mapping.cluster.name if mapping.cluster else None, 'role': mapping.role, 'source': mapping.source, }) return mappings def get_taxonomy_mappings(self, obj): mappings = [] for mapping in self._taxonomy_map_qs(obj): taxonomy = mapping.taxonomy mappings.append({ 'taxonomy_id': taxonomy.id if taxonomy else None, 'taxonomy_name': taxonomy.name if taxonomy else None, 'taxonomy_type': taxonomy.taxonomy_type if taxonomy else None, 'source': mapping.source, }) return mappings def get_attribute_mappings(self, obj): mappings = [] for mapping in self._attribute_map_qs(obj): mappings.append({ 'name': mapping.name, 'value': mapping.value, 'source': mapping.source, }) return mappings 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() 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""" task_title = serializers.SerializerMethodField() sector_name = serializers.SerializerMethodField() has_image_prompts = serializers.SerializerMethodField() has_generated_images = serializers.SerializerMethodField() cluster_mappings = serializers.SerializerMethodField() taxonomy_mappings = serializers.SerializerMethodField() attribute_mappings = serializers.SerializerMethodField() class Meta: model = Content fields = [ 'id', 'task_id', 'task_title', 'sector_name', 'html_content', 'word_count', 'metadata', 'title', 'meta_title', 'meta_description', 'primary_keyword', 'secondary_keywords', 'tags', 'categories', 'status', 'generated_at', 'updated_at', 'account_id', 'has_image_prompts', 'has_generated_images', # Phase 8: Universal Content Types 'entity_type', 'json_blocks', 'structure_data', 'cluster_mappings', 'taxonomy_mappings', 'attribute_mappings', ] read_only_fields = ['id', 'generated_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_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 def get_has_image_prompts(self, obj): """Check if content has any image prompts generated""" # Check if any images exist with prompts for this content return Images.objects.filter( models.Q(content=obj) | models.Q(task=obj.task) ).exclude(prompt__isnull=True).exclude(prompt='').exists() def get_has_generated_images(self, obj): """Check if content has any generated images (status='generated' and has URL)""" # Check if any images are generated (have status='generated' and image_url) return Images.objects.filter( models.Q(content=obj) | models.Q(task=obj.task), status='generated', image_url__isnull=False ).exclude(image_url='').exists() def get_cluster_mappings(self, obj): mappings = ContentClusterMap.objects.filter(content=obj).select_related('cluster') results = [] for mapping in mappings: results.append({ 'cluster_id': mapping.cluster_id, 'cluster_name': mapping.cluster.name if mapping.cluster else None, 'role': mapping.role, 'source': mapping.source, }) return results def get_taxonomy_mappings(self, obj): mappings = ContentTaxonomyMap.objects.filter(content=obj).select_related('taxonomy') results = [] for mapping in mappings: taxonomy = mapping.taxonomy results.append({ 'taxonomy_id': taxonomy.id if taxonomy else None, 'taxonomy_name': taxonomy.name if taxonomy else None, 'taxonomy_type': taxonomy.taxonomy_type if taxonomy else None, 'source': mapping.source, }) return results def get_attribute_mappings(self, obj): mappings = ContentAttributeMap.objects.filter(content=obj) results = [] for mapping in mappings: results.append({ 'name': mapping.name, 'value': mapping.value, 'source': mapping.source, }) return results