- Improved response parsing in AICore to handle both array and dictionary formats, including detailed error logging. - Updated image directory handling in tasks to prioritize web-accessible paths for image storage, with robust fallback mechanisms. - Adjusted image URL generation in serializers and frontend components to support new directory structure and ensure proper accessibility.
279 lines
9.3 KiB
Python
279 lines
9.3 KiB
Python
from rest_framework import serializers
|
|
from django.db import models
|
|
from .models import Tasks, Images, Content
|
|
from igny8_core.modules.planner.models import Clusters, ContentIdeas
|
|
|
|
|
|
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()
|
|
|
|
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',
|
|
'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:
|
|
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 []
|
|
|
|
|
|
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 proper HTTP URL for image.
|
|
Priority: If image_path exists and is in ai-images folder, return web URL,
|
|
otherwise return file endpoint URL or image_url (API URL).
|
|
"""
|
|
if obj.image_path:
|
|
# Check if path is in ai-images folder (web-accessible)
|
|
if 'ai-images' in obj.image_path:
|
|
# Extract filename from path
|
|
filename = obj.image_path.split('ai-images/')[-1] if 'ai-images/' in obj.image_path else obj.image_path.split('ai-images\\')[-1]
|
|
if filename:
|
|
# Return web-accessible URL (like /images/logo/logo.svg)
|
|
return f'/images/ai-images/{filename}'
|
|
|
|
# For other local paths, use file endpoint
|
|
request = self.context.get('request')
|
|
if request:
|
|
# Build absolute URL for file endpoint
|
|
file_url = request.build_absolute_uri(f'/api/v1/writer/images/{obj.id}/file/')
|
|
return file_url
|
|
else:
|
|
# Fallback: return relative URL if no request context
|
|
return f'/api/v1/writer/images/{obj.id}/file/'
|
|
# Fallback to original image_url (API URL) if no local path
|
|
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',
|
|
'tags',
|
|
'categories',
|
|
'status',
|
|
'generated_at',
|
|
'updated_at',
|
|
'account_id',
|
|
'has_image_prompts',
|
|
'has_generated_images',
|
|
]
|
|
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()
|
|
|