This commit is contained in:
alorig
2025-11-18 07:13:34 +05:00
parent 51c3986e01
commit 2074191eee
17 changed files with 2578 additions and 0 deletions

View File

@@ -158,6 +158,37 @@ class Content(SiteSectorBaseModel):
optimizer_version = models.IntegerField(default=0, help_text="Version of optimizer processing")
optimization_scores = models.JSONField(default=dict, blank=True, help_text="Optimization scores (SEO, readability, engagement)")
# Phase 8: Universal Content Types
ENTITY_TYPE_CHOICES = [
('blog_post', 'Blog Post'),
('article', 'Article'),
('product', 'Product'),
('service', 'Service Page'),
('taxonomy', 'Taxonomy Page'),
('page', 'Page'),
]
entity_type = models.CharField(
max_length=50,
choices=ENTITY_TYPE_CHOICES,
default='blog_post',
db_index=True,
help_text="Type of content entity"
)
# Phase 8: Structured content blocks
json_blocks = models.JSONField(
default=list,
blank=True,
help_text="Structured content blocks (for products, services, taxonomies)"
)
# Phase 8: Content structure data
structure_data = models.JSONField(
default=dict,
blank=True,
help_text="Content structure data (metadata, schema, etc.)"
)
class Meta:
app_label = 'writer'
db_table = 'igny8_content'
@@ -170,6 +201,7 @@ class Content(SiteSectorBaseModel):
models.Index(fields=['source']),
models.Index(fields=['sync_status']),
models.Index(fields=['source', 'sync_status']),
models.Index(fields=['entity_type']), # Phase 8
]
def save(self, *args, **kwargs):

View File

@@ -72,4 +72,201 @@ class ContentGenerationService:
'success': False,
'error': str(e)
}
def generate_product_content(self, product_data, account, site=None, sector=None):
"""
Generate product content.
Args:
product_data: Dict with product information (name, description, features, etc.)
account: Account instance
site: Site instance (optional)
sector: Sector instance (optional)
Returns:
dict: Result with success status and data
Raises:
InsufficientCreditsError: If account doesn't have enough credits
"""
# Calculate estimated credits needed (default 1500 words for product content)
estimated_word_count = product_data.get('word_count', 1500)
# Check credits
try:
self.credit_service.check_credits(account, 'content_generation', estimated_word_count)
except InsufficientCreditsError:
raise
# Delegate to AI task
from igny8_core.ai.tasks import run_ai_task
try:
payload = {
'product_name': product_data.get('name', ''),
'product_description': product_data.get('description', ''),
'product_features': product_data.get('features', []),
'target_audience': product_data.get('target_audience', ''),
'primary_keyword': product_data.get('primary_keyword', ''),
'site_id': site.id if site else None,
'sector_id': sector.id if sector else None,
}
if hasattr(run_ai_task, 'delay'):
# Celery available - queue async
task = run_ai_task.delay(
function_name='generate_product_content',
payload=payload,
account_id=account.id
)
return {
'success': True,
'task_id': str(task.id),
'message': 'Product content generation started'
}
else:
# Celery not available - execute synchronously
result = run_ai_task(
function_name='generate_product_content',
payload=payload,
account_id=account.id
)
return result
except Exception as e:
logger.error(f"Error in generate_product_content: {str(e)}", exc_info=True)
return {
'success': False,
'error': str(e)
}
def generate_service_page(self, service_data, account, site=None, sector=None):
"""
Generate service page content.
Args:
service_data: Dict with service information (name, description, benefits, etc.)
account: Account instance
site: Site instance (optional)
sector: Sector instance (optional)
Returns:
dict: Result with success status and data
Raises:
InsufficientCreditsError: If account doesn't have enough credits
"""
# Calculate estimated credits needed (default 1800 words for service page)
estimated_word_count = service_data.get('word_count', 1800)
# Check credits
try:
self.credit_service.check_credits(account, 'content_generation', estimated_word_count)
except InsufficientCreditsError:
raise
# Delegate to AI task
from igny8_core.ai.tasks import run_ai_task
try:
payload = {
'service_name': service_data.get('name', ''),
'service_description': service_data.get('description', ''),
'service_benefits': service_data.get('benefits', []),
'target_audience': service_data.get('target_audience', ''),
'primary_keyword': service_data.get('primary_keyword', ''),
'site_id': site.id if site else None,
'sector_id': sector.id if sector else None,
}
if hasattr(run_ai_task, 'delay'):
# Celery available - queue async
task = run_ai_task.delay(
function_name='generate_service_page',
payload=payload,
account_id=account.id
)
return {
'success': True,
'task_id': str(task.id),
'message': 'Service page generation started'
}
else:
# Celery not available - execute synchronously
result = run_ai_task(
function_name='generate_service_page',
payload=payload,
account_id=account.id
)
return result
except Exception as e:
logger.error(f"Error in generate_service_page: {str(e)}", exc_info=True)
return {
'success': False,
'error': str(e)
}
def generate_taxonomy(self, taxonomy_data, account, site=None, sector=None):
"""
Generate taxonomy page content.
Args:
taxonomy_data: Dict with taxonomy information (name, description, items, etc.)
account: Account instance
site: Site instance (optional)
sector: Sector instance (optional)
Returns:
dict: Result with success status and data
Raises:
InsufficientCreditsError: If account doesn't have enough credits
"""
# Calculate estimated credits needed (default 1200 words for taxonomy page)
estimated_word_count = taxonomy_data.get('word_count', 1200)
# Check credits
try:
self.credit_service.check_credits(account, 'content_generation', estimated_word_count)
except InsufficientCreditsError:
raise
# Delegate to AI task
from igny8_core.ai.tasks import run_ai_task
try:
payload = {
'taxonomy_name': taxonomy_data.get('name', ''),
'taxonomy_description': taxonomy_data.get('description', ''),
'taxonomy_items': taxonomy_data.get('items', []),
'primary_keyword': taxonomy_data.get('primary_keyword', ''),
'site_id': site.id if site else None,
'sector_id': sector.id if sector else None,
}
if hasattr(run_ai_task, 'delay'):
# Celery available - queue async
task = run_ai_task.delay(
function_name='generate_taxonomy',
payload=payload,
account_id=account.id
)
return {
'success': True,
'task_id': str(task.id),
'message': 'Taxonomy generation started'
}
else:
# Celery not available - execute synchronously
result = run_ai_task(
function_name='generate_taxonomy',
payload=payload,
account_id=account.id
)
return result
except Exception as e:
logger.error(f"Error in generate_taxonomy: {str(e)}", exc_info=True)
return {
'success': False,
'error': str(e)
}

View File

@@ -0,0 +1,280 @@
"""
Tests for Universal Content Types (Phase 8)
Tests for product, service, and taxonomy content generation
"""
from unittest.mock import patch, MagicMock
from django.test import TestCase
from igny8_core.business.content.models import Content
from igny8_core.business.content.services.content_generation_service import ContentGenerationService
from igny8_core.api.tests.test_integration_base import IntegrationTestBase
class UniversalContentTypesTests(IntegrationTestBase):
"""Tests for Phase 8: Universal Content Types"""
def setUp(self):
super().setUp()
self.service = ContentGenerationService()
@patch('igny8_core.business.content.services.content_generation_service.run_ai_task')
def test_product_content_generates_correctly(self, mock_run_ai_task):
"""
Test: Product content generates correctly
Task 17: Verify product generation creates content with correct entity_type and structure
"""
# Mock AI task response
mock_task = MagicMock()
mock_task.id = 'test-task-123'
mock_run_ai_task.delay.return_value = mock_task
product_data = {
'name': 'Test Product',
'description': 'A test product description',
'features': ['Feature 1', 'Feature 2', 'Feature 3'],
'target_audience': 'Small businesses',
'primary_keyword': 'test product',
'word_count': 1500
}
# Generate product content
result = self.service.generate_product_content(
product_data=product_data,
account=self.account,
site=self.site,
sector=self.sector
)
# Verify result
self.assertTrue(result.get('success'))
self.assertIsNotNone(result.get('task_id'))
self.assertEqual(result.get('message'), 'Product content generation started')
# Verify AI task was called with correct function name
mock_run_ai_task.delay.assert_called_once()
call_args = mock_run_ai_task.delay.call_args
self.assertEqual(call_args[1]['function_name'], 'generate_product_content')
self.assertEqual(call_args[1]['payload']['product_name'], 'Test Product')
@patch('igny8_core.business.content.services.content_generation_service.run_ai_task')
def test_service_pages_work_correctly(self, mock_run_ai_task):
"""
Test: Service pages work correctly
Task 18: Verify service page generation creates content with correct entity_type
"""
# Mock AI task response
mock_task = MagicMock()
mock_task.id = 'test-task-456'
mock_run_ai_task.delay.return_value = mock_task
service_data = {
'name': 'Test Service',
'description': 'A test service description',
'benefits': ['Benefit 1', 'Benefit 2', 'Benefit 3'],
'target_audience': 'Enterprise clients',
'primary_keyword': 'test service',
'word_count': 1800
}
# Generate service page
result = self.service.generate_service_page(
service_data=service_data,
account=self.account,
site=self.site,
sector=self.sector
)
# Verify result
self.assertTrue(result.get('success'))
self.assertIsNotNone(result.get('task_id'))
self.assertEqual(result.get('message'), 'Service page generation started')
# Verify AI task was called with correct function name
mock_run_ai_task.delay.assert_called_once()
call_args = mock_run_ai_task.delay.call_args
self.assertEqual(call_args[1]['function_name'], 'generate_service_page')
self.assertEqual(call_args[1]['payload']['service_name'], 'Test Service')
@patch('igny8_core.business.content.services.content_generation_service.run_ai_task')
def test_taxonomy_pages_work_correctly(self, mock_run_ai_task):
"""
Test: Taxonomy pages work correctly
Task 19: Verify taxonomy generation creates content with correct entity_type
"""
# Mock AI task response
mock_task = MagicMock()
mock_task.id = 'test-task-789'
mock_run_ai_task.delay.return_value = mock_task
taxonomy_data = {
'name': 'Test Taxonomy',
'description': 'A test taxonomy description',
'items': ['Category 1', 'Category 2', 'Category 3'],
'primary_keyword': 'test taxonomy',
'word_count': 1200
}
# Generate taxonomy
result = self.service.generate_taxonomy(
taxonomy_data=taxonomy_data,
account=self.account,
site=self.site,
sector=self.sector
)
# Verify result
self.assertTrue(result.get('success'))
self.assertIsNotNone(result.get('task_id'))
self.assertEqual(result.get('message'), 'Taxonomy generation started')
# Verify AI task was called with correct function name
mock_run_ai_task.delay.assert_called_once()
call_args = mock_run_ai_task.delay.call_args
self.assertEqual(call_args[1]['function_name'], 'generate_taxonomy')
self.assertEqual(call_args[1]['payload']['taxonomy_name'], 'Test Taxonomy')
def test_product_content_has_correct_structure(self):
"""
Test: Product content generates correctly
Task 17: Verify product content has correct entity_type, json_blocks, and structure_data
"""
# Create product content manually to test structure
product_content = Content.objects.create(
account=self.account,
site=self.site,
sector=self.sector,
title='Test Product',
html_content='<p>Product content</p>',
entity_type='product',
json_blocks=[
{
'type': 'product_overview',
'heading': 'Product Overview',
'content': 'Product description'
},
{
'type': 'features',
'heading': 'Key Features',
'items': ['Feature 1', 'Feature 2']
},
{
'type': 'specifications',
'heading': 'Specifications',
'data': {'Spec 1': 'Value 1'}
}
],
structure_data={
'product_type': 'software',
'price_range': '$99-$199',
'target_market': 'SMB'
},
word_count=1500,
status='draft'
)
# Verify structure
self.assertEqual(product_content.entity_type, 'product')
self.assertIsNotNone(product_content.json_blocks)
self.assertEqual(len(product_content.json_blocks), 3)
self.assertEqual(product_content.json_blocks[0]['type'], 'product_overview')
self.assertIsNotNone(product_content.structure_data)
self.assertEqual(product_content.structure_data['product_type'], 'software')
def test_service_content_has_correct_structure(self):
"""
Test: Service pages work correctly
Task 18: Verify service content has correct entity_type and json_blocks
"""
# Create service content manually to test structure
service_content = Content.objects.create(
account=self.account,
site=self.site,
sector=self.sector,
title='Test Service',
html_content='<p>Service content</p>',
entity_type='service',
json_blocks=[
{
'type': 'service_overview',
'heading': 'Service Overview',
'content': 'Service description'
},
{
'type': 'benefits',
'heading': 'Benefits',
'items': ['Benefit 1', 'Benefit 2']
},
{
'type': 'process',
'heading': 'Our Process',
'steps': ['Step 1', 'Step 2']
}
],
structure_data={
'service_type': 'consulting',
'duration': '3-6 months',
'target_market': 'Enterprise'
},
word_count=1800,
status='draft'
)
# Verify structure
self.assertEqual(service_content.entity_type, 'service')
self.assertIsNotNone(service_content.json_blocks)
self.assertEqual(len(service_content.json_blocks), 3)
self.assertEqual(service_content.json_blocks[0]['type'], 'service_overview')
self.assertIsNotNone(service_content.structure_data)
self.assertEqual(service_content.structure_data['service_type'], 'consulting')
def test_taxonomy_content_has_correct_structure(self):
"""
Test: Taxonomy pages work correctly
Task 19: Verify taxonomy content has correct entity_type and json_blocks
"""
# Create taxonomy content manually to test structure
taxonomy_content = Content.objects.create(
account=self.account,
site=self.site,
sector=self.sector,
title='Test Taxonomy',
html_content='<p>Taxonomy content</p>',
entity_type='taxonomy',
json_blocks=[
{
'type': 'taxonomy_overview',
'heading': 'Taxonomy Overview',
'content': 'Taxonomy description'
},
{
'type': 'categories',
'heading': 'Categories',
'items': [
{
'name': 'Category 1',
'description': 'Category description',
'subcategories': ['Subcat 1', 'Subcat 2']
}
]
},
{
'type': 'tags',
'heading': 'Tags',
'items': ['Tag 1', 'Tag 2', 'Tag 3']
}
],
structure_data={
'taxonomy_type': 'product_categories',
'item_count': 10,
'hierarchy_levels': 3
},
word_count=1200,
status='draft'
)
# Verify structure
self.assertEqual(taxonomy_content.entity_type, 'taxonomy')
self.assertIsNotNone(taxonomy_content.json_blocks)
self.assertEqual(len(taxonomy_content.json_blocks), 3)
self.assertEqual(taxonomy_content.json_blocks[0]['type'], 'taxonomy_overview')
self.assertIsNotNone(taxonomy_content.structure_data)
self.assertEqual(taxonomy_content.structure_data['taxonomy_type'], 'product_categories')