- 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.
434 lines
11 KiB
Markdown
434 lines
11 KiB
Markdown
# IGNY8 Unified Content Architecture - Quick Reference
|
|
|
|
## ✅ What Changed
|
|
|
|
### Old Way ❌
|
|
```python
|
|
# Scattered entity types
|
|
task.entity_type = 'blog_post'
|
|
task.content_type = 'article'
|
|
task.content_structure = 'pillar_page'
|
|
|
|
# JSON arrays for taxonomies
|
|
content.categories = ['SEO', 'WordPress']
|
|
content.tags = ['tutorial', 'guide']
|
|
|
|
# Fragmented attributes
|
|
ContentAttributeMap(name='Color', value='Blue')
|
|
```
|
|
|
|
### New Way ✅
|
|
```python
|
|
# Single unified entity type
|
|
content.entity_type = 'post' # What it is
|
|
content.content_format = 'article' # How it's structured
|
|
content.cluster_role = 'hub' # Semantic role
|
|
|
|
# Real M2M relationships
|
|
content.taxonomies.add(seo_category)
|
|
content.taxonomies.add(tutorial_tag)
|
|
|
|
# Enhanced attributes with WP sync
|
|
ContentAttribute(
|
|
content=content,
|
|
attribute_type='product_spec',
|
|
name='Color',
|
|
value='Blue',
|
|
external_id=101, # WP term ID
|
|
external_attribute_name='pa_color'
|
|
)
|
|
```
|
|
|
|
---
|
|
|
|
## 📚 Core Models
|
|
|
|
### 1. Content (Enhanced)
|
|
```python
|
|
from igny8_core.business.content.models import Content
|
|
|
|
# Create content
|
|
content = Content.objects.create(
|
|
title="Best SEO Tools 2025",
|
|
entity_type='post', # post, page, product, service, taxonomy_term
|
|
content_format='listicle', # article, listicle, guide, comparison, review
|
|
cluster_role='hub', # hub, supporting, attribute
|
|
html_content="<h1>Best SEO Tools...</h1>",
|
|
|
|
# WordPress sync
|
|
external_id=427, # WP post ID
|
|
external_url="https://site.com/seo-tools/",
|
|
external_type='post', # WP post_type
|
|
source='wordpress',
|
|
sync_status='imported',
|
|
|
|
# SEO
|
|
meta_title="15 Best SEO Tools...",
|
|
primary_keyword="seo tools",
|
|
|
|
# Relationships
|
|
cluster=seo_cluster,
|
|
site=site,
|
|
sector=sector,
|
|
)
|
|
|
|
# Add taxonomies
|
|
content.taxonomies.add(seo_category, tools_tag)
|
|
```
|
|
|
|
### 2. ContentTaxonomy (New)
|
|
```python
|
|
from igny8_core.business.content.models import ContentTaxonomy
|
|
|
|
# WordPress category
|
|
category = ContentTaxonomy.objects.create(
|
|
name="SEO",
|
|
slug="seo",
|
|
taxonomy_type='category', # category, tag, product_cat, product_tag, product_attr
|
|
description="All about SEO",
|
|
|
|
# WordPress sync
|
|
external_id=12, # WP term ID
|
|
external_taxonomy='category', # WP taxonomy name
|
|
sync_status='imported',
|
|
count=45, # Post count from WP
|
|
|
|
site=site,
|
|
sector=sector,
|
|
)
|
|
|
|
# Map to semantic clusters
|
|
category.clusters.add(seo_cluster, content_marketing_cluster)
|
|
|
|
# Hierarchical taxonomy
|
|
subcategory = ContentTaxonomy.objects.create(
|
|
name="Technical SEO",
|
|
slug="technical-seo",
|
|
taxonomy_type='category',
|
|
parent=category, # Parent category
|
|
site=site,
|
|
sector=sector,
|
|
)
|
|
```
|
|
|
|
### 3. ContentAttribute (Enhanced)
|
|
```python
|
|
from igny8_core.business.content.models import ContentAttribute
|
|
|
|
# WooCommerce product attribute
|
|
attribute = ContentAttribute.objects.create(
|
|
content=product_content,
|
|
attribute_type='product_spec', # product_spec, service_modifier, semantic_facet
|
|
name='Color',
|
|
value='Blue',
|
|
|
|
# WooCommerce sync
|
|
external_id=101, # WP attribute term ID
|
|
external_attribute_name='pa_color', # WP attribute slug
|
|
|
|
source='wordpress',
|
|
site=site,
|
|
sector=sector,
|
|
)
|
|
|
|
# Semantic cluster attribute
|
|
semantic_attr = ContentAttribute.objects.create(
|
|
cluster=enterprise_seo_cluster,
|
|
attribute_type='semantic_facet',
|
|
name='Target Audience',
|
|
value='Enterprise',
|
|
source='manual',
|
|
site=site,
|
|
sector=sector,
|
|
)
|
|
```
|
|
|
|
---
|
|
|
|
## 🔄 WordPress Sync Workflows
|
|
|
|
### Scenario 1: Import WP Categories
|
|
```python
|
|
from igny8_core.business.content.models import ContentTaxonomy
|
|
|
|
# Fetch from WP /wp-json/wp/v2/categories
|
|
wp_categories = [
|
|
{'id': 12, 'name': 'SEO', 'slug': 'seo', 'count': 45},
|
|
{'id': 15, 'name': 'WordPress', 'slug': 'wordpress', 'count': 32},
|
|
]
|
|
|
|
for wp_cat in wp_categories:
|
|
taxonomy, created = ContentTaxonomy.objects.update_or_create(
|
|
site=site,
|
|
external_id=wp_cat['id'],
|
|
external_taxonomy='category',
|
|
defaults={
|
|
'name': wp_cat['name'],
|
|
'slug': wp_cat['slug'],
|
|
'taxonomy_type': 'category',
|
|
'count': wp_cat['count'],
|
|
'sync_status': 'imported',
|
|
'sector': site.sectors.first(),
|
|
}
|
|
)
|
|
```
|
|
|
|
### Scenario 2: Import WP Posts (Titles Only)
|
|
```python
|
|
from igny8_core.business.content.models import Content, ContentTaxonomy
|
|
|
|
# Fetch from WP /wp-json/wp/v2/posts
|
|
wp_posts = [
|
|
{
|
|
'id': 427,
|
|
'title': {'rendered': 'Best SEO Tools 2025'},
|
|
'link': 'https://site.com/seo-tools/',
|
|
'type': 'post',
|
|
'categories': [12, 15],
|
|
'tags': [45, 67],
|
|
}
|
|
]
|
|
|
|
for wp_post in wp_posts:
|
|
# Create content (title only, no html_content yet)
|
|
content, created = Content.objects.update_or_create(
|
|
site=site,
|
|
external_id=wp_post['id'],
|
|
defaults={
|
|
'title': wp_post['title']['rendered'],
|
|
'entity_type': 'post',
|
|
'external_url': wp_post['link'],
|
|
'external_type': wp_post['type'],
|
|
'source': 'wordpress',
|
|
'sync_status': 'imported',
|
|
'sector': site.sectors.first(),
|
|
}
|
|
)
|
|
|
|
# Map categories
|
|
for cat_id in wp_post['categories']:
|
|
try:
|
|
taxonomy = ContentTaxonomy.objects.get(
|
|
site=site,
|
|
external_id=cat_id,
|
|
taxonomy_type='category'
|
|
)
|
|
content.taxonomies.add(taxonomy)
|
|
except ContentTaxonomy.DoesNotExist:
|
|
pass
|
|
|
|
# Map tags
|
|
for tag_id in wp_post['tags']:
|
|
try:
|
|
taxonomy = ContentTaxonomy.objects.get(
|
|
site=site,
|
|
external_id=tag_id,
|
|
taxonomy_type='tag'
|
|
)
|
|
content.taxonomies.add(taxonomy)
|
|
except ContentTaxonomy.DoesNotExist:
|
|
pass
|
|
```
|
|
|
|
### Scenario 3: Fetch Full Content On-Demand
|
|
```python
|
|
def fetch_full_content(content_id):
|
|
"""Fetch full HTML content from WP when needed for AI analysis."""
|
|
content = Content.objects.get(id=content_id)
|
|
|
|
if content.source == 'wordpress' and content.external_id:
|
|
# Fetch from WP /wp-json/wp/v2/posts/{external_id}
|
|
wp_response = requests.get(
|
|
f"{content.site.url}/wp-json/wp/v2/posts/{content.external_id}"
|
|
)
|
|
wp_data = wp_response.json()
|
|
|
|
# Update content
|
|
content.html_content = wp_data['content']['rendered']
|
|
content.word_count = len(wp_data['content']['rendered'].split())
|
|
content.meta_title = wp_data.get('yoast_head_json', {}).get('title', '')
|
|
content.meta_description = wp_data.get('yoast_head_json', {}).get('description', '')
|
|
content.save()
|
|
|
|
return content
|
|
```
|
|
|
|
### Scenario 4: Import WooCommerce Product Attributes
|
|
```python
|
|
from igny8_core.business.content.models import Content, ContentAttribute
|
|
|
|
# Fetch from WP /wp-json/wc/v3/products/{id}
|
|
wp_product = {
|
|
'id': 88,
|
|
'name': 'Blue Widget',
|
|
'type': 'simple',
|
|
'attributes': [
|
|
{'id': 1, 'name': 'Color', 'slug': 'pa_color', 'option': 'Blue'},
|
|
{'id': 2, 'name': 'Size', 'slug': 'pa_size', 'option': 'Large'},
|
|
]
|
|
}
|
|
|
|
# Create product content
|
|
product = Content.objects.create(
|
|
site=site,
|
|
title=wp_product['name'],
|
|
entity_type='product',
|
|
external_id=wp_product['id'],
|
|
external_type='product',
|
|
source='wordpress',
|
|
sync_status='imported',
|
|
sector=site.sectors.first(),
|
|
)
|
|
|
|
# Import attributes
|
|
for attr in wp_product['attributes']:
|
|
ContentAttribute.objects.create(
|
|
content=product,
|
|
attribute_type='product_spec',
|
|
name=attr['name'],
|
|
value=attr['option'],
|
|
external_attribute_name=attr['slug'],
|
|
source='wordpress',
|
|
site=site,
|
|
sector=site.sectors.first(),
|
|
)
|
|
```
|
|
|
|
---
|
|
|
|
## 🔍 Query Examples
|
|
|
|
### Find Content by Entity Type
|
|
```python
|
|
# All blog posts
|
|
posts = Content.objects.filter(entity_type='post')
|
|
|
|
# All listicles
|
|
listicles = Content.objects.filter(entity_type='post', content_format='listicle')
|
|
|
|
# All hub pages
|
|
hubs = Content.objects.filter(cluster_role='hub')
|
|
|
|
# All WP-synced products
|
|
products = Content.objects.filter(
|
|
entity_type='product',
|
|
source='wordpress',
|
|
sync_status='imported'
|
|
)
|
|
```
|
|
|
|
### Find Taxonomies
|
|
```python
|
|
# All categories with WP sync
|
|
categories = ContentTaxonomy.objects.filter(
|
|
taxonomy_type='category',
|
|
external_id__isnull=False
|
|
)
|
|
|
|
# Product attributes (color, size, etc.)
|
|
product_attrs = ContentTaxonomy.objects.filter(taxonomy_type='product_attr')
|
|
|
|
# Taxonomies mapped to a cluster
|
|
cluster_terms = ContentTaxonomy.objects.filter(clusters=seo_cluster)
|
|
|
|
# Get all content for a taxonomy
|
|
seo_content = Content.objects.filter(taxonomies=seo_category)
|
|
```
|
|
|
|
### Find Attributes
|
|
```python
|
|
# All product specs for a content
|
|
specs = ContentAttribute.objects.filter(
|
|
content=product,
|
|
attribute_type='product_spec'
|
|
)
|
|
|
|
# All attributes in a cluster
|
|
cluster_attrs = ContentAttribute.objects.filter(
|
|
cluster=enterprise_cluster,
|
|
attribute_type='semantic_facet'
|
|
)
|
|
|
|
# Find content by attribute value
|
|
blue_products = Content.objects.filter(
|
|
attributes__name='Color',
|
|
attributes__value='Blue'
|
|
)
|
|
```
|
|
|
|
---
|
|
|
|
## 📊 Relationships Diagram
|
|
|
|
```
|
|
Site
|
|
├─ Content (post, page, product, service, taxonomy_term)
|
|
│ ├─ entity_type (what it is)
|
|
│ ├─ content_format (how it's structured)
|
|
│ ├─ cluster_role (semantic role)
|
|
│ ├─ cluster FK → Clusters
|
|
│ ├─ taxonomies M2M → ContentTaxonomy
|
|
│ └─ attributes FK ← ContentAttribute
|
|
│
|
|
├─ ContentTaxonomy (category, tag, product_cat, product_tag, product_attr)
|
|
│ ├─ external_id (WP term ID)
|
|
│ ├─ external_taxonomy (WP taxonomy name)
|
|
│ ├─ parent FK → self (hierarchical)
|
|
│ ├─ clusters M2M → Clusters
|
|
│ └─ contents M2M ← Content
|
|
│
|
|
└─ Clusters
|
|
├─ contents FK ← Content
|
|
├─ taxonomy_terms M2M ← ContentTaxonomy
|
|
└─ attributes FK ← ContentAttribute
|
|
```
|
|
|
|
---
|
|
|
|
## ⚠️ Migration Notes
|
|
|
|
### Deprecated Fields (Still Available)
|
|
|
|
**Don't use these anymore:**
|
|
```python
|
|
# ❌ Old way
|
|
task.content = "..." # Use Content.html_content
|
|
task.entity_type = "..." # Use Content.entity_type
|
|
content.categories = ["SEO"] # Use content.taxonomies M2M
|
|
content.tags = ["tutorial"] # Use content.taxonomies M2M
|
|
```
|
|
|
|
**Use these instead:**
|
|
```python
|
|
# ✅ New way
|
|
content.html_content = "..."
|
|
content.entity_type = "post"
|
|
content.taxonomies.add(seo_category)
|
|
content.taxonomies.add(tutorial_tag)
|
|
```
|
|
|
|
### Backward Compatibility
|
|
|
|
Legacy values still work:
|
|
```python
|
|
# These still map correctly
|
|
content.entity_type = 'blog_post' # → internally handled as 'post'
|
|
content.entity_type = 'article' # → internally handled as 'post'
|
|
```
|
|
|
|
---
|
|
|
|
## 🚀 Next: Frontend Integration
|
|
|
|
Ready for Phase 4:
|
|
1. Site Settings → "Content Types" tab
|
|
2. Display imported taxonomies
|
|
3. Enable/disable sync per type
|
|
4. Set fetch limits
|
|
5. Trigger AI semantic mapping
|
|
|
|
---
|
|
|
|
**Questions?** Check `/data/app/igny8/backend/MIGRATION_SUMMARY.md` for full migration details.
|
|
|