from rest_framework import serializers from django.db import models from django.core.exceptions import ObjectDoesNotExist from django.conf import settings from .models import Tasks, Images, Content from igny8_core.business.planning.models import Clusters, ContentIdeas from igny8_core.business.content.models import ( ContentClusterMap, ContentTaxonomyMap, ContentAttribute, ContentTaxonomy, ContentTaxonomyRelation, ) # Backward compatibility ContentAttributeMap = ContentAttribute 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() # tags/categories removed — use taxonomies M2M on Content class Meta: model = Tasks fields = [ 'id', 'title', 'description', 'keywords', 'cluster_id', 'cluster_name', 'sector_name', 'idea_id', 'idea_title', 'status', # task-level raw content/seo fields removed — stored on Content 'content_html', 'content_primary_keyword', 'content_secondary_keywords', 'site_id', 'sector_id', 'account_id', 'created_at', 'updated_at', ] read_only_fields = ['id', 'created_at', 'updated_at', 'account_id'] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Only include Stage 1 fields when feature flag is enabled if getattr(settings, 'USE_SITE_BUILDER_REFACTOR', False): self.fields['cluster_mappings'] = serializers.SerializerMethodField() self.fields['taxonomy_mappings'] = serializers.SerializerMethodField() self.fields['attribute_mappings'] = serializers.SerializerMethodField() 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): # tags removed; derive taxonomies from Content.taxonomies if needed record = self._get_content_record(obj) if not record: return [] return [t.name for t in record.taxonomies.all()] def get_content_categories(self, obj): # categories removed; derive hierarchical taxonomies from Content.taxonomies record = self._get_content_record(obj) if not record: return [] return [t.name for t in record.taxonomies.filter(taxonomy_type__in=['category','product_cat'])] 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() 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', 'status', 'generated_at', 'updated_at', 'account_id', 'has_image_prompts', 'has_generated_images', # Phase 8: Universal Content Types 'entity_type', 'json_blocks', 'structure_data', ] read_only_fields = ['id', 'generated_at', 'updated_at', 'account_id'] def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Only include Stage 1 fields when feature flag is enabled if getattr(settings, 'USE_SITE_BUILDER_REFACTOR', False): self.fields['cluster_mappings'] = serializers.SerializerMethodField() self.fields['taxonomy_mappings'] = serializers.SerializerMethodField() self.fields['attribute_mappings'] = serializers.SerializerMethodField() 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 = ContentAttribute.objects.filter(content=obj) results = [] for mapping in mappings: results.append({ 'name': mapping.name, 'value': mapping.value, 'attribute_type': mapping.attribute_type, 'source': mapping.source, 'external_id': mapping.external_id, }) return results class ContentTaxonomySerializer(serializers.ModelSerializer): """Serializer for ContentTaxonomy model""" parent_name = serializers.SerializerMethodField() cluster_names = serializers.SerializerMethodField() content_count = serializers.SerializerMethodField() class Meta: model = ContentTaxonomy fields = [ 'id', 'name', 'slug', 'taxonomy_type', 'description', 'parent', 'parent_name', 'external_id', 'external_taxonomy', 'sync_status', 'count', 'metadata', 'cluster_names', 'content_count', 'site_id', 'sector_id', 'account_id', 'created_at', 'updated_at', ] read_only_fields = ['id', 'created_at', 'updated_at', 'account_id'] def get_parent_name(self, obj): return obj.parent.name if obj.parent else None def get_cluster_names(self, obj): return [cluster.name for cluster in obj.clusters.all()] def get_content_count(self, obj): return obj.contents.count() class ContentAttributeSerializer(serializers.ModelSerializer): """Serializer for ContentAttribute model""" content_title = serializers.SerializerMethodField() cluster_name = serializers.SerializerMethodField() class Meta: model = ContentAttribute fields = [ 'id', 'content', 'content_title', 'cluster', 'cluster_name', 'attribute_type', 'name', 'value', 'external_id', 'external_attribute_name', 'source', 'metadata', 'site_id', 'sector_id', 'account_id', 'created_at', 'updated_at', ] read_only_fields = ['id', 'created_at', 'updated_at', 'account_id'] def get_content_title(self, obj): return obj.content.title if obj.content else None def get_cluster_name(self, obj): return obj.cluster.name if obj.cluster else None class ContentTaxonomyRelationSerializer(serializers.ModelSerializer): """Serializer for ContentTaxonomyRelation (M2M through model)""" content_title = serializers.SerializerMethodField() taxonomy_name = serializers.SerializerMethodField() taxonomy_type = serializers.SerializerMethodField() class Meta: model = ContentTaxonomyRelation fields = [ 'id', 'content', 'content_title', 'taxonomy', 'taxonomy_name', 'taxonomy_type', 'created_at', ] read_only_fields = ['id', 'created_at'] def get_content_title(self, obj): return obj.content.title if obj.content else None def get_taxonomy_name(self, obj): return obj.taxonomy.name if obj.taxonomy else None def get_taxonomy_type(self, obj): return obj.taxonomy.taxonomy_type if obj.taxonomy else None class UpdatedTasksSerializer(serializers.ModelSerializer): """Updated Serializer for Tasks model with new fields.""" 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_taxonomies = serializers.SerializerMethodField() content_attributes = 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_taxonomies', 'content_attributes', '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_content_taxonomies(self, obj): """Fetch related taxonomies.""" return ContentTaxonomyRelationSerializer( obj.content.taxonomies.all(), many=True ).data def get_content_attributes(self, obj): """Fetch related attributes.""" return ContentAttributeSerializer( obj.content.attributes.all(), many=True ).data