Files
igny8/backend/igny8_core/modules/writer/serializers.py
IGNY8 VPS (Salman) 4237c203b4 112
2025-11-29 08:59:31 +00:00

330 lines
11 KiB
Python

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()
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