refactor-migration again

This commit is contained in:
IGNY8 VPS (Salman)
2025-11-26 15:12:14 +00:00
parent 2ef98b5113
commit f88aae78b1
23 changed files with 942 additions and 211 deletions

View File

@@ -208,29 +208,16 @@ class GenerateIdeasFunction(BaseAIFunction):
# Handle target_keywords
target_keywords = idea_data.get('covered_keywords', '') or idea_data.get('target_keywords', '')
# Map content_type and content_structure to ContentIdeas fields
# AI returns: content_type (post/page/product/service) → site_entity_type
# AI returns: content_structure (article/guide/review/comparison) → cluster_role (hub/supporting/attribute)
site_entity_type = idea_data.get('content_type', 'post') # post, page, product, service
# Map content_structure to cluster_role
# Direct mapping - no conversion needed
content_type = idea_data.get('content_type', 'post')
content_structure = idea_data.get('content_structure', 'article')
structure_to_role = {
'article': 'hub',
'guide': 'supporting',
'review': 'supporting',
'comparison': 'attribute',
'listicle': 'supporting',
'product_page': 'attribute',
}
cluster_role = structure_to_role.get(content_structure, 'hub')
# Create ContentIdeas record
ContentIdeas.objects.create(
idea_title=idea_data.get('title', 'Untitled Idea'),
description=description, # Stored as JSON string
site_entity_type=site_entity_type,
cluster_role=cluster_role,
content_type=content_type,
content_structure=content_structure,
target_keywords=target_keywords,
keyword_cluster=cluster,
estimated_word_count=idea_data.get('estimated_word_count', 1500),

View File

@@ -145,7 +145,15 @@ Output JSON Example:
"covered_keywords": "organic duvet covers, eco-friendly bedding, sustainable sheets"
}
]
}""",
}
Valid content_type values: post, page, product, taxonomy
Valid content_structure by type:
- post: article, guide, comparison, review, listicle
- page: landing_page, business_page, service_page, general, cluster_hub
- product: product_page
- taxonomy: category_archive, tag_archive, attribute_archive""",
'content_generation': """You are an editorial content strategist. Your task is to generate a complete JSON response object based on the provided content idea, keyword cluster, keyword list, and metadata context.
@@ -196,11 +204,11 @@ STYLE & QUALITY RULES
STAGE 3: METADATA CONTEXT (NEW)
===========================
**Cluster Role:**
[IGNY8_CLUSTER_ROLE]
- If role is "hub": Create comprehensive, authoritative content that serves as the main resource for this topic cluster. Include overview sections, key concepts, and links to related topics.
- If role is "supporting": Create detailed, focused content that supports the hub page. Dive deep into specific aspects, use cases, or subtopics.
- If role is "attribute": Create content focused on specific attributes, features, or specifications. Include detailed comparisons, specifications, or attribute-focused information.
**Content Structure:**
[IGNY8_CONTENT_STRUCTURE]
- If structure is "cluster_hub": Create comprehensive, authoritative content that serves as the main resource for this topic cluster. Include overview sections, key concepts, and links to related topics.
- If structure is "article" or "guide": Create detailed, focused content that dives deep into the topic with actionable insights.
- Other structures: Follow the appropriate format (listicle, comparison, review, landing_page, service_page, product_page, category_archive, tag_archive, attribute_archive).
**Taxonomy Context:**
[IGNY8_TAXONOMY]

View File

@@ -11,6 +11,34 @@ class Tasks(SiteSectorBaseModel):
('completed', 'Completed'),
]
CONTENT_TYPE_CHOICES = [
('post', 'Post'),
('page', 'Page'),
('product', 'Product'),
('taxonomy', 'Taxonomy'),
]
CONTENT_STRUCTURE_CHOICES = [
# Post structures
('article', 'Article'),
('guide', 'Guide'),
('comparison', 'Comparison'),
('review', 'Review'),
('listicle', 'Listicle'),
# Page structures
('landing_page', 'Landing Page'),
('business_page', 'Business Page'),
('service_page', 'Service Page'),
('general', 'General'),
('cluster_hub', 'Cluster Hub'),
# Product structures
('product_page', 'Product Page'),
# Taxonomy structures
('category_archive', 'Category Archive'),
('tag_archive', 'Tag Archive'),
('attribute_archive', 'Attribute Archive'),
]
title = models.CharField(max_length=255, db_index=True)
description = models.TextField(blank=True, null=True)
cluster = models.ForeignKey(
@@ -34,16 +62,18 @@ class Tasks(SiteSectorBaseModel):
content_type = models.CharField(
max_length=100,
db_index=True,
help_text="Content type: post, page, product, service, category, tag, etc.",
db_column='entity_type',
help_text="Content type: post, page, product, taxonomy",
choices=CONTENT_TYPE_CHOICES,
default='post',
blank=True,
null=True
)
content_structure = models.CharField(
max_length=100,
db_index=True,
help_text="Content structure/format: article, listicle, guide, comparison, product_page, etc.",
db_column='cluster_role',
help_text="Content structure: article, guide, comparison, review, listicle, landing_page, etc.",
choices=CONTENT_STRUCTURE_CHOICES,
default='article',
blank=True,
null=True
)
@@ -104,9 +134,37 @@ class Content(SiteSectorBaseModel):
Final architecture: simplified content management.
"""
CONTENT_TYPE_CHOICES = [
('post', 'Post'),
('page', 'Page'),
('product', 'Product'),
('taxonomy', 'Taxonomy'),
]
CONTENT_STRUCTURE_CHOICES = [
# Post structures
('article', 'Article'),
('guide', 'Guide'),
('comparison', 'Comparison'),
('review', 'Review'),
('listicle', 'Listicle'),
# Page structures
('landing_page', 'Landing Page'),
('business_page', 'Business Page'),
('service_page', 'Service Page'),
('general', 'General'),
('cluster_hub', 'Cluster Hub'),
# Product structures
('product_page', 'Product Page'),
# Taxonomy structures
('category_archive', 'Category Archive'),
('tag_archive', 'Tag Archive'),
('attribute_archive', 'Attribute Archive'),
]
# Core content fields
title = models.CharField(max_length=255, db_index=True)
content_html = models.TextField(help_text="Final HTML content", db_column='html_content')
content_html = models.TextField(help_text="Final HTML content")
cluster = models.ForeignKey(
'planner.Clusters',
on_delete=models.SET_NULL,
@@ -116,20 +174,18 @@ class Content(SiteSectorBaseModel):
help_text="Parent cluster (required)"
)
content_type = models.CharField(
max_length=100,
max_length=50,
choices=CONTENT_TYPE_CHOICES,
default='post',
db_index=True,
help_text="Content type: post, page, product, service, category, tag, etc.",
db_column='entity_type',
blank=True,
null=True
help_text="Content type: post, page, product, taxonomy"
)
content_structure = models.CharField(
max_length=100,
max_length=50,
choices=CONTENT_STRUCTURE_CHOICES,
default='article',
db_index=True,
help_text="Content structure/format: article, listicle, guide, comparison, product_page, etc.",
db_column='cluster_role',
blank=True,
null=True
help_text="Content structure/format based on content type"
)
# Taxonomy relationships

View File

@@ -146,18 +146,32 @@ class ContentIdeas(SiteSectorBaseModel):
('published', 'Published'),
]
SITE_ENTITY_TYPE_CHOICES = [
CONTENT_TYPE_CHOICES = [
('post', 'Post'),
('page', 'Page'),
('product', 'Product'),
('service', 'Service'),
('taxonomy_term', 'Taxonomy Term'),
('taxonomy', 'Taxonomy'),
]
CLUSTER_ROLE_CHOICES = [
('hub', 'Hub'),
('supporting', 'Supporting'),
('attribute', 'Attribute'),
CONTENT_STRUCTURE_CHOICES = [
# Post structures
('article', 'Article'),
('guide', 'Guide'),
('comparison', 'Comparison'),
('review', 'Review'),
('listicle', 'Listicle'),
# Page structures
('landing_page', 'Landing Page'),
('business_page', 'Business Page'),
('service_page', 'Service Page'),
('general', 'General'),
('cluster_hub', 'Cluster Hub'),
# Product structures
('product_page', 'Product Page'),
# Taxonomy structures
('category_archive', 'Category Archive'),
('tag_archive', 'Tag Archive'),
('attribute_archive', 'Attribute Archive'),
]
idea_title = models.CharField(max_length=255, db_index=True)
@@ -187,17 +201,17 @@ class ContentIdeas(SiteSectorBaseModel):
)
status = models.CharField(max_length=50, choices=STATUS_CHOICES, default='new')
estimated_word_count = models.IntegerField(default=1000)
site_entity_type = models.CharField(
content_type = models.CharField(
max_length=50,
choices=SITE_ENTITY_TYPE_CHOICES,
default='page',
help_text="Target entity type when promoting idea into tasks/pages"
choices=CONTENT_TYPE_CHOICES,
default='post',
help_text="Content type: post, page, product, taxonomy"
)
cluster_role = models.CharField(
content_structure = models.CharField(
max_length=50,
choices=CLUSTER_ROLE_CHOICES,
default='hub',
help_text="Role within the cluster-driven sitemap"
choices=CONTENT_STRUCTURE_CHOICES,
default='article',
help_text="Content structure/format based on content type"
)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
@@ -212,8 +226,8 @@ class ContentIdeas(SiteSectorBaseModel):
models.Index(fields=['idea_title']),
models.Index(fields=['status']),
models.Index(fields=['keyword_cluster']),
models.Index(fields=['site_entity_type']),
models.Index(fields=['cluster_role']),
models.Index(fields=['content_type']),
models.Index(fields=['content_structure']),
models.Index(fields=['site', 'sector']),
]

View File

@@ -125,10 +125,10 @@ class SitesRendererAdapter(BaseAdapter):
# Get blocks from blueprint (placeholders)
blocks = page.blocks_json or []
page_metadata = {
'entity_type': page.entity_type if hasattr(page, 'entity_type') else None,
'content_type': page.content_type if hasattr(page, 'content_type') else None,
'cluster_id': None,
'cluster_name': None,
'cluster_role': None,
'content_structure': None,
'taxonomy_id': None,
'taxonomy_name': None,
'internal_links': []
@@ -178,7 +178,7 @@ class SitesRendererAdapter(BaseAdapter):
if cluster_map and cluster_map.cluster:
page_metadata['cluster_id'] = cluster_map.cluster.id
page_metadata['cluster_name'] = cluster_map.cluster.name
page_metadata['cluster_role'] = cluster_map.role or task.cluster_role if task else None
page_metadata['content_structure'] = cluster_map.role or task.content_structure if task else None
# Get taxonomy mapping
taxonomy_map = ContentTaxonomyMap.objects.filter(content=content).first()
@@ -190,21 +190,21 @@ class SitesRendererAdapter(BaseAdapter):
if content.internal_links:
page_metadata['internal_links'] = content.internal_links
# Use content entity_type if available
if content.entity_type:
page_metadata['entity_type'] = content.entity_type
# Use content_type if available
if content.content_type:
page_metadata['content_type'] = content.content_type
# Fallback to task metadata if content not found
if task and not page_metadata.get('cluster_id'):
if task.cluster:
page_metadata['cluster_id'] = task.cluster.id
page_metadata['cluster_name'] = task.cluster.name
page_metadata['cluster_role'] = task.cluster_role
page_metadata['content_structure'] = task.content_structure
if task.taxonomy:
page_metadata['taxonomy_id'] = task.taxonomy.id
page_metadata['taxonomy_name'] = task.taxonomy.name
if task.entity_type:
page_metadata['entity_type'] = task.entity_type
if task.content_type:
page_metadata['content_type'] = task.content_type
pages.append({
'id': page.id,

View File

@@ -235,19 +235,19 @@ class PageGenerationService:
'contact': 'page',
'custom': 'page',
}
entity_type = entity_type_map.get(page_blueprint.type, 'page')
content_type = entity_type_map.get(page_blueprint.type, 'page')
# Stage 3: Try to find related cluster and taxonomy from blueprint
cluster_role = 'hub' # Default
# Try to find related cluster and taxonomy from blueprint
content_structure = 'article' # Default
taxonomy = None
# Find cluster link for this blueprint to infer role
# Find cluster link for this blueprint to infer structure
from igny8_core.business.site_building.models import SiteBlueprintCluster
cluster_link = SiteBlueprintCluster.objects.filter(
site_blueprint=page_blueprint.site_blueprint
).first()
if cluster_link:
cluster_role = cluster_link.role
content_structure = cluster_link.role or 'article'
# Find taxonomy if page type suggests it (products/services)
if page_blueprint.type in ['products', 'services']:
@@ -264,13 +264,10 @@ class PageGenerationService:
title=title,
description="\n".join(filter(None, description_parts)),
keywords=keywords,
content_structure=self._map_content_structure(page_blueprint.type),
content_type='article',
content_structure=self._map_content_structure(page_blueprint.type) or content_structure,
content_type=content_type,
status='queued',
# Stage 3: Set entity metadata
entity_type=entity_type,
taxonomy=taxonomy,
cluster_role=cluster_role,
)
logger.info(

View File

@@ -59,8 +59,8 @@ class KeywordsAdmin(SiteSectorAdminMixin, admin.ModelAdmin):
@admin.register(ContentIdeas)
class ContentIdeasAdmin(SiteSectorAdminMixin, admin.ModelAdmin):
list_display = ['idea_title', 'site', 'sector', 'description_preview', 'site_entity_type', 'cluster_role', 'status', 'keyword_cluster', 'estimated_word_count', 'created_at']
list_filter = ['status', 'site_entity_type', 'cluster_role', 'site', 'sector']
list_display = ['idea_title', 'site', 'sector', 'description_preview', 'content_type', 'content_structure', 'status', 'keyword_cluster', 'estimated_word_count', 'created_at']
list_filter = ['status', 'content_type', 'content_structure', 'site', 'sector']
search_fields = ['idea_title', 'target_keywords', 'description']
ordering = ['-created_at']
readonly_fields = ['created_at', 'updated_at']
@@ -70,7 +70,7 @@ class ContentIdeasAdmin(SiteSectorAdminMixin, admin.ModelAdmin):
'fields': ('idea_title', 'description', 'status', 'site', 'sector')
}),
('Content Planning', {
'fields': ('site_entity_type', 'cluster_role', 'estimated_word_count')
'fields': ('content_type', 'content_structure', 'estimated_word_count')
}),
('Keywords & Clustering', {
'fields': ('keyword_cluster', 'target_keywords', 'taxonomy')

View File

@@ -0,0 +1,49 @@
# Generated by Django 5.2.8 on 2025-11-26 14:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('igny8_core_auth', '0002_add_wp_api_key_to_site'),
('planner', '0004_remove_clusters_igny8_clust_context_0d6bd7_idx_and_more'),
('site_building', '0001_initial'),
]
operations = [
migrations.RemoveIndex(
model_name='contentideas',
name='igny8_conte_site_en_511349_idx',
),
migrations.RemoveIndex(
model_name='contentideas',
name='igny8_conte_cluster_234240_idx',
),
migrations.RemoveField(
model_name='contentideas',
name='cluster_role',
),
migrations.RemoveField(
model_name='contentideas',
name='site_entity_type',
),
migrations.AddField(
model_name='contentideas',
name='content_structure',
field=models.CharField(choices=[('article', 'Article'), ('guide', 'Guide'), ('comparison', 'Comparison'), ('review', 'Review'), ('listicle', 'Listicle'), ('landing_page', 'Landing Page'), ('business_page', 'Business Page'), ('service_page', 'Service Page'), ('general', 'General'), ('cluster_hub', 'Cluster Hub'), ('product_page', 'Product Page'), ('category_archive', 'Category Archive'), ('tag_archive', 'Tag Archive'), ('attribute_archive', 'Attribute Archive')], default='article', help_text='Content structure/format based on content type', max_length=50),
),
migrations.AddField(
model_name='contentideas',
name='content_type',
field=models.CharField(choices=[('post', 'Post'), ('page', 'Page'), ('product', 'Product'), ('taxonomy', 'Taxonomy')], default='post', help_text='Content type: post, page, product, taxonomy', max_length=50),
),
migrations.AddIndex(
model_name='contentideas',
index=models.Index(fields=['content_type'], name='igny8_conte_content_e74415_idx'),
),
migrations.AddIndex(
model_name='contentideas',
index=models.Index(fields=['content_structure'], name='igny8_conte_content_3eede7_idx'),
),
]

View File

@@ -171,8 +171,8 @@ class ContentIdeasSerializer(serializers.ModelSerializer):
'id',
'idea_title',
'description',
'site_entity_type',
'cluster_role',
'content_type',
'content_structure',
'target_keywords',
'keyword_cluster_id',
'keyword_cluster_name',

View File

@@ -927,7 +927,7 @@ class ContentIdeasViewSet(SiteSectorModelViewSet):
ordering = ['-created_at'] # Default ordering (newest first)
# Filter configuration (updated for new structure)
filterset_fields = ['status', 'keyword_cluster_id', 'site_entity_type', 'cluster_role']
filterset_fields = ['status', 'keyword_cluster_id', 'content_type', 'content_structure']
def perform_create(self, serializer):
"""Require explicit site_id and sector_id - no defaults."""
@@ -1013,27 +1013,13 @@ class ContentIdeasViewSet(SiteSectorModelViewSet):
errors = []
for idea in ideas:
try:
# STAGE 3: Map idea fields to final Task schema
# Map site_entity_type → content_type (with fallback)
content_type = idea.site_entity_type if idea.site_entity_type else 'post'
# Map cluster_role → content_structure (with fallback)
# hub → article, supporting → guide, attribute → comparison
role_to_structure = {
'hub': 'article',
'supporting': 'guide',
'attribute': 'comparison',
}
cluster_role = idea.cluster_role if idea.cluster_role else 'hub'
content_structure = role_to_structure.get(cluster_role, 'article')
# Create task with Stage 1 final fields
# Direct copy - no mapping needed
task = Tasks.objects.create(
title=idea.idea_title,
description=idea.description or '',
cluster=idea.keyword_cluster,
content_type=content_type,
content_structure=content_structure,
content_type=idea.content_type or 'post',
content_structure=idea.content_structure or 'article',
taxonomy_term=None, # Can be set later if taxonomy is available
status='queued',
account=idea.account,
@@ -1056,7 +1042,7 @@ class ContentIdeasViewSet(SiteSectorModelViewSet):
if errors:
return error_response(
error=f'Failed to create {len(errors)} tasks',
details=errors,
errors=errors,
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
request=request
)

View File

@@ -228,10 +228,10 @@ class SiteBlueprintViewSet(SiteSectorModelViewSet):
).values_list('content_id', flat=True).distinct()
cluster_content = content.filter(id__in=cluster_content_ids)
# Count by role
hub_count = cluster_tasks.filter(cluster_role='hub').count()
supporting_count = cluster_tasks.filter(cluster_role='supporting').count()
attribute_count = cluster_tasks.filter(cluster_role='attribute').count()
# Count by structure
hub_count = cluster_tasks.filter(content_structure='cluster_hub').count()
supporting_count = cluster_tasks.filter(content_structure__in=['article', 'guide', 'comparison']).count()
attribute_count = cluster_tasks.filter(content_structure='attribute_archive').count()
cluster_progress.append({
'cluster_id': cluster.id,

View File

@@ -0,0 +1,120 @@
# Generated manually for field rename implementation
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('planner', '0005_field_rename_implementation'),
('writer', '0007_alter_contenttaxonomyrelation_unique_together_and_more'),
]
operations = [
# Rename database columns for Tasks
migrations.RunSQL(
sql="""
-- Rename Tasks columns (only if they exist)
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'igny8_tasks' AND column_name = 'entity_type') THEN
ALTER TABLE igny8_tasks RENAME COLUMN entity_type TO content_type;
END IF;
IF EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'igny8_tasks' AND column_name = 'cluster_role') THEN
ALTER TABLE igny8_tasks RENAME COLUMN cluster_role TO content_structure;
END IF;
END $$;
-- Drop old indexes if they exist
DROP INDEX IF EXISTS igny8_tasks_entity__1dc185_idx;
DROP INDEX IF EXISTS igny8_tasks_cluster_c87903_idx;
-- Create new indexes
CREATE INDEX IF NOT EXISTS igny8_tasks_content_type_idx ON igny8_tasks(content_type);
CREATE INDEX IF NOT EXISTS igny8_tasks_content_structure_idx ON igny8_tasks(content_structure);
""",
reverse_sql="""
ALTER TABLE igny8_tasks RENAME COLUMN content_type TO entity_type;
ALTER TABLE igny8_tasks RENAME COLUMN content_structure TO cluster_role;
DROP INDEX IF EXISTS igny8_tasks_content_type_idx;
DROP INDEX IF EXISTS igny8_tasks_content_structure_idx;
CREATE INDEX igny8_tasks_entity__1dc185_idx ON igny8_tasks(entity_type);
CREATE INDEX igny8_tasks_cluster_c87903_idx ON igny8_tasks(cluster_role);
"""
),
# Rename database columns for Content
migrations.RunSQL(
sql="""
-- Rename Content columns (only if they exist)
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'igny8_content' AND column_name = 'entity_type') THEN
ALTER TABLE igny8_content RENAME COLUMN entity_type TO content_type;
END IF;
IF EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'igny8_content' AND column_name = 'cluster_role') THEN
ALTER TABLE igny8_content RENAME COLUMN cluster_role TO content_structure;
END IF;
IF EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'igny8_content' AND column_name = 'html_content') THEN
ALTER TABLE igny8_content RENAME COLUMN html_content TO content_html;
END IF;
END $$;
-- Drop old indexes if they exist
DROP INDEX IF EXISTS igny8_conte_entity__f559b3_idx;
DROP INDEX IF EXISTS igny8_conte_cluster_32e22a_idx;
-- Create new indexes
CREATE INDEX IF NOT EXISTS igny8_content_content_type_idx ON igny8_content(content_type);
CREATE INDEX IF NOT EXISTS igny8_content_content_structure_idx ON igny8_content(content_structure);
""",
reverse_sql="""
ALTER TABLE igny8_content RENAME COLUMN content_type TO entity_type;
ALTER TABLE igny8_content RENAME COLUMN content_structure TO cluster_role;
ALTER TABLE igny8_content RENAME COLUMN content_html TO html_content;
DROP INDEX IF EXISTS igny8_content_content_type_idx;
DROP INDEX IF EXISTS igny8_content_content_structure_idx;
CREATE INDEX igny8_conte_entity__f559b3_idx ON igny8_content(entity_type);
CREATE INDEX igny8_conte_cluster_32e22a_idx ON igny8_content(cluster_role);
"""
),
# Rename database columns for ContentTaxonomyMap
migrations.RunSQL(
sql="""
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'igny8_content_taxonomy_map' AND column_name = 'entity_type') THEN
ALTER TABLE igny8_content_taxonomy_map RENAME COLUMN entity_type TO content_type;
END IF;
END $$;
""",
reverse_sql="""
ALTER TABLE igny8_content_taxonomy_map RENAME COLUMN content_type TO entity_type;
"""
),
# Rename database columns for TaxonomyTerms
migrations.RunSQL(
sql="""
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.columns
WHERE table_name = 'igny8_taxonomy_terms' AND column_name = 'entity_type') THEN
ALTER TABLE igny8_taxonomy_terms RENAME COLUMN entity_type TO content_type;
END IF;
END $$;
""",
reverse_sql="""
ALTER TABLE igny8_taxonomy_terms RENAME COLUMN content_type TO entity_type;
"""
),
]

View File

@@ -0,0 +1,53 @@
-- COMPREHENSIVE FIELD RENAME MIGRATION
-- Renames all entity_type, cluster_role, site_entity_type columns to content_type and content_structure
-- Date: 2025-11-26
BEGIN;
-- 1. ContentIdeas table (igny8_content_ideas)
ALTER TABLE igny8_content_ideas RENAME COLUMN site_entity_type TO content_type;
ALTER TABLE igny8_content_ideas RENAME COLUMN cluster_role TO content_structure;
-- Update index names for ContentIdeas
DROP INDEX IF EXISTS igny8_content_ideas_site_entity_type_idx;
DROP INDEX IF EXISTS igny8_content_ideas_cluster_role_idx;
CREATE INDEX igny8_content_ideas_content_type_idx ON igny8_content_ideas(content_type);
CREATE INDEX igny8_content_ideas_content_structure_idx ON igny8_content_ideas(content_structure);
-- 2. Tasks table (igny8_tasks)
ALTER TABLE igny8_tasks RENAME COLUMN entity_type TO content_type;
-- cluster_role already mapped via db_column, but let's check if column exists
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'igny8_tasks' AND column_name = 'cluster_role') THEN
ALTER TABLE igny8_tasks RENAME COLUMN cluster_role TO content_structure;
END IF;
END $$;
-- 3. Content table (igny8_content)
ALTER TABLE igny8_content RENAME COLUMN entity_type TO content_type;
-- cluster_role already mapped via db_column, but let's check if column exists
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'igny8_content' AND column_name = 'cluster_role') THEN
ALTER TABLE igny8_content RENAME COLUMN cluster_role TO content_structure;
END IF;
END $$;
-- 4. ContentTaxonomy table (igny8_content_taxonomy)
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'igny8_content_taxonomy' AND column_name = 'entity_type') THEN
ALTER TABLE igny8_content_taxonomy RENAME COLUMN entity_type TO content_type;
END IF;
END $$;
-- 5. AITaskExecution table (igny8_ai_task_execution)
DO $$
BEGIN
IF EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'igny8_ai_task_execution' AND column_name = 'entity_type') THEN
ALTER TABLE igny8_ai_task_execution RENAME COLUMN entity_type TO content_type;
END IF;
END $$;
COMMIT;

View File

@@ -0,0 +1,138 @@
#!/usr/bin/env python
"""
Test script to verify field rename is complete
Run after SQL migration: python manage.py shell < test_field_rename.py
"""
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
django.setup()
from igny8_core.business.planning.models import ContentIdeas
from igny8_core.business.content.models import Tasks, Content
from django.db import connection
def test_models():
"""Test that models can access new field names"""
print("\n=== Testing Model Field Access ===")
# Test ContentIdeas
try:
idea = ContentIdeas.objects.first()
if idea:
print(f"✓ ContentIdeas.content_type: {idea.content_type}")
print(f"✓ ContentIdeas.content_structure: {idea.content_structure}")
else:
print("⚠ No ContentIdeas records to test")
except Exception as e:
print(f"✗ ContentIdeas error: {e}")
# Test Tasks
try:
task = Tasks.objects.first()
if task:
print(f"✓ Tasks.content_type: {task.content_type}")
print(f"✓ Tasks.content_structure: {task.content_structure}")
else:
print("⚠ No Tasks records to test")
except Exception as e:
print(f"✗ Tasks error: {e}")
# Test Content
try:
content = Content.objects.first()
if content:
print(f"✓ Content.content_type: {content.content_type}")
print(f"✓ Content.content_structure: {content.content_structure}")
print(f"✓ Content.content_html: {len(content.content_html) if content.content_html else 0} chars")
else:
print("⚠ No Content records to test")
except Exception as e:
print(f"✗ Content error: {e}")
def test_database_columns():
"""Verify database columns exist"""
print("\n=== Testing Database Column Names ===")
with connection.cursor() as cursor:
# Check igny8_content_ideas
cursor.execute("""
SELECT column_name FROM information_schema.columns
WHERE table_name = 'igny8_content_ideas'
AND column_name IN ('content_type', 'content_structure', 'site_entity_type', 'cluster_role')
ORDER BY column_name
""")
cols = [row[0] for row in cursor.fetchall()]
print(f"igny8_content_ideas columns: {cols}")
if 'content_type' in cols and 'content_structure' in cols:
print("✓ ContentIdeas: new columns exist")
if 'site_entity_type' in cols or 'cluster_role' in cols:
print("✗ ContentIdeas: old columns still exist")
# Check igny8_tasks
cursor.execute("""
SELECT column_name FROM information_schema.columns
WHERE table_name = 'igny8_tasks'
AND column_name IN ('content_type', 'content_structure', 'entity_type', 'cluster_role')
ORDER BY column_name
""")
cols = [row[0] for row in cursor.fetchall()]
print(f"igny8_tasks columns: {cols}")
if 'content_type' in cols and 'content_structure' in cols:
print("✓ Tasks: new columns exist")
if 'entity_type' in cols or 'cluster_role' in cols:
print("✗ Tasks: old columns still exist")
# Check igny8_content
cursor.execute("""
SELECT column_name FROM information_schema.columns
WHERE table_name = 'igny8_content'
AND column_name IN ('content_type', 'content_structure', 'content_html', 'entity_type', 'cluster_role', 'html_content')
ORDER BY column_name
""")
cols = [row[0] for row in cursor.fetchall()]
print(f"igny8_content columns: {cols}")
if 'content_type' in cols and 'content_structure' in cols and 'content_html' in cols:
print("✓ Content: new columns exist")
if 'entity_type' in cols or 'cluster_role' in cols or 'html_content' in cols:
print("✗ Content: old columns still exist")
def test_choices():
"""Test that CHOICES are correctly defined"""
print("\n=== Testing Model CHOICES ===")
print(f"ContentIdeas.CONTENT_TYPE_CHOICES: {len(ContentIdeas.CONTENT_TYPE_CHOICES)} types")
print(f"ContentIdeas.CONTENT_STRUCTURE_CHOICES: {len(ContentIdeas.CONTENT_STRUCTURE_CHOICES)} structures")
print(f"Tasks.CONTENT_TYPE_CHOICES: {len(Tasks.CONTENT_TYPE_CHOICES)} types")
print(f"Tasks.CONTENT_STRUCTURE_CHOICES: {len(Tasks.CONTENT_STRUCTURE_CHOICES)} structures")
print(f"Content.CONTENT_TYPE_CHOICES: {len(Content.CONTENT_TYPE_CHOICES)} types")
print(f"Content.CONTENT_STRUCTURE_CHOICES: {len(Content.CONTENT_STRUCTURE_CHOICES)} structures")
# Verify all 14 structures are present
all_structures = [s[0] for s in Tasks.CONTENT_STRUCTURE_CHOICES]
expected = ['article', 'guide', 'comparison', 'review', 'listicle',
'landing_page', 'business_page', 'service_page', 'general', 'cluster_hub',
'product_page', 'category_archive', 'tag_archive', 'attribute_archive']
for struct in expected:
if struct in all_structures:
print(f"{struct}")
else:
print(f"✗ Missing: {struct}")
if __name__ == '__main__':
print("="*60)
print("Field Rename Validation Test")
print("="*60)
test_models()
test_database_columns()
test_choices()
print("\n" + "="*60)
print("Test Complete")
print("="*60)