Files
igny8/backend/igny8_core/modules/writer/serializers.py
IGNY8 VPS (Salman) 55dfd5ad19 Enhance Content Management with New Taxonomy and Attribute Models
- Introduced `ContentTaxonomy` and `ContentAttribute` models for improved content categorization and attribute management.
- Updated `Content` model to support new fields for content format, cluster role, and external type.
- Refactored serializers and views to accommodate new models, including `ContentTaxonomySerializer` and `ContentAttributeSerializer`.
- Added new API endpoints for managing taxonomies and attributes, enhancing the content management capabilities.
- Updated admin interfaces for `Content`, `ContentTaxonomy`, and `ContentAttribute` to reflect new structures and improve usability.
- Implemented backward compatibility for existing attribute mappings.
- Enhanced filtering and search capabilities in the API for better content retrieval.
2025-11-22 00:21:00 +00:00

475 lines
16 KiB
Python

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()
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 __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):
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()
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',
]
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