blurpritn adn site builde cleanup

This commit is contained in:
IGNY8 VPS (Salman)
2025-12-01 02:22:02 +00:00
parent 3f2385d4d9
commit a7a772a78c
33 changed files with 1591 additions and 1308 deletions

View File

@@ -272,16 +272,13 @@ class Content(SiteSectorBaseModel):
class ContentTaxonomy(SiteSectorBaseModel):
"""
Universal taxonomy model for WordPress and IGNY8 cluster-based taxonomies.
Supports categories, tags, product attributes, and cluster mappings.
Simplified taxonomy model for AI-generated categories and tags.
Directly linked to Content via many-to-many relationship.
"""
TAXONOMY_TYPE_CHOICES = [
('category', 'Category'),
('tag', 'Tag'),
('product_category', 'Product Category'),
('product_attribute', 'Product Attribute'),
('cluster', 'Cluster Taxonomy'),
]
name = models.CharField(max_length=255, db_index=True, help_text="Term name")
@@ -290,7 +287,7 @@ class ContentTaxonomy(SiteSectorBaseModel):
max_length=50,
choices=TAXONOMY_TYPE_CHOICES,
db_index=True,
help_text="Type of taxonomy"
help_text="Type of taxonomy (category or tag)"
)
# WordPress/external platform sync fields
@@ -298,25 +295,19 @@ class ContentTaxonomy(SiteSectorBaseModel):
max_length=100,
blank=True,
default='',
help_text="WordPress taxonomy slug (category, post_tag, product_cat, pa_*) - empty for cluster taxonomies"
help_text="WordPress taxonomy slug (category, post_tag)"
)
external_id = models.IntegerField(
null=True,
blank=True,
db_index=True,
help_text="WordPress term_id - null for cluster taxonomies"
help_text="WordPress term_id for sync"
)
description = models.TextField(
blank=True,
default='',
help_text="Taxonomy term description"
)
sync_status = models.CharField(
max_length=50,
blank=True,
default='',
help_text="Synchronization status with external platforms"
)
count = models.IntegerField(
default=0,
help_text="Number of times this term is used"
@@ -324,7 +315,7 @@ class ContentTaxonomy(SiteSectorBaseModel):
metadata = models.JSONField(
default=dict,
blank=True,
help_text="Additional metadata for the taxonomy term"
help_text="Additional metadata (AI generation details, etc.)"
)
created_at = models.DateTimeField(auto_now_add=True)
@@ -483,59 +474,6 @@ class ContentClusterMap(SiteSectorBaseModel):
return f"{self.cluster.name} ({self.get_role_display()})"
class ContentTaxonomyMap(SiteSectorBaseModel):
"""Maps content entities to blueprint taxonomies for syncing/publishing."""
SOURCE_CHOICES = [
('blueprint', 'Blueprint'),
('manual', 'Manual'),
('import', 'Import'),
]
content = models.ForeignKey(
Content,
on_delete=models.CASCADE,
related_name='taxonomy_mappings',
null=True,
blank=True,
)
task = models.ForeignKey(
Tasks,
on_delete=models.CASCADE,
related_name='taxonomy_mappings',
null=True,
blank=True,
)
taxonomy = models.ForeignKey(
'site_building.SiteBlueprintTaxonomy',
on_delete=models.CASCADE,
related_name='content_mappings',
)
source = models.CharField(max_length=50, choices=SOURCE_CHOICES, default='blueprint')
metadata = models.JSONField(default=dict, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
app_label = 'writer'
db_table = 'igny8_content_taxonomy_map'
unique_together = [['content', 'taxonomy']]
indexes = [
models.Index(fields=['taxonomy']),
models.Index(fields=['content', 'taxonomy']),
models.Index(fields=['task', 'taxonomy']),
]
def save(self, *args, **kwargs):
provider = self.content or self.task
if provider:
self.account = provider.account
self.site = provider.site
self.sector = provider.sector
super().save(*args, **kwargs)
def __str__(self):
return f"{self.taxonomy.name}"
class ContentAttribute(SiteSectorBaseModel):

View File

@@ -1,6 +1,7 @@
"""
Metadata Mapping Service
Stage 3: Persists cluster/taxonomy/attribute mappings from Tasks to Content
Stage 3: Persists cluster/attribute mappings from Tasks to Content
Legacy: ContentTaxonomyMap removed - taxonomy now uses Content.taxonomy_terms M2M relationship
"""
import logging
from typing import Optional
@@ -10,9 +11,9 @@ from igny8_core.business.content.models import (
Tasks,
Content,
ContentClusterMap,
ContentTaxonomyMap,
ContentAttributeMap,
)
# Removed: ContentTaxonomyMap - replaced by Content.taxonomy_terms ManyToManyField
logger = logging.getLogger(__name__)

View File

@@ -80,16 +80,16 @@ class CandidateEngine:
def _score_candidates(self, content: Content, candidates: List[Content]) -> List[Dict]:
"""Score candidates based on relevance"""
from igny8_core.business.content.models import ContentClusterMap, ContentTaxonomyMap
from igny8_core.business.content.models import ContentClusterMap
# Stage 3: Get cluster mappings for content
content_clusters = set(
ContentClusterMap.objects.filter(content=content)
.values_list('cluster_id', flat=True)
)
# Taxonomy matching using Content.taxonomy_terms M2M relationship
content_taxonomies = set(
ContentTaxonomyMap.objects.filter(content=content)
.values_list('taxonomy_id', flat=True)
content.taxonomy_terms.values_list('id', flat=True)
)
scored = []
@@ -106,10 +106,9 @@ class CandidateEngine:
if cluster_overlap:
score += 50 * len(cluster_overlap) # High weight for cluster matches
# Stage 3: Taxonomy matching
# Stage 3: Taxonomy matching using M2M relationship
candidate_taxonomies = set(
ContentTaxonomyMap.objects.filter(content=candidate)
.values_list('taxonomy_id', flat=True)
candidate.taxonomy_terms.values_list('id', flat=True)
)
taxonomy_overlap = content_taxonomies & candidate_taxonomies
if taxonomy_overlap:

View File

@@ -102,9 +102,8 @@ class ContentAnalyzer:
return ContentClusterMap.objects.filter(content=content).exists()
def _has_taxonomy_mapping(self, content: Content) -> bool:
"""Stage 3: Check if content has taxonomy mapping"""
from igny8_core.business.content.models import ContentTaxonomyMap
return ContentTaxonomyMap.objects.filter(content=content).exists()
"""Stage 3: Check if content has taxonomy mapping (categories/tags)"""
return content.taxonomy_terms.exists()
def _calculate_seo_score(self, content: Content) -> float:
"""Calculate SEO score (0-100)"""

View File

@@ -191,14 +191,7 @@ class ContentIdeas(SiteSectorBaseModel):
related_name='ideas',
limit_choices_to={'sector': models.F('sector')}
)
taxonomy = models.ForeignKey(
'site_building.SiteBlueprintTaxonomy',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='content_ideas',
help_text="Optional taxonomy association when derived from blueprint planning"
)
# REMOVED: taxonomy FK to SiteBlueprintTaxonomy (legacy blueprint functionality)
status = models.CharField(max_length=50, choices=STATUS_CHOICES, default='new')
estimated_word_count = models.IntegerField(default=1000)
content_type = models.CharField(

View File

@@ -10,7 +10,7 @@ class Migration(migrations.Migration):
dependencies = [
('igny8_core_auth', '0001_initial'),
('site_building', '0001_initial'),
# ('site_building', '0001_initial'), # REMOVED: SiteBuilder deprecated
('writer', '0001_initial'),
]
@@ -31,12 +31,12 @@ class Migration(migrations.Migration):
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.account')),
('sector', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.sector')),
('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.site')),
('site_blueprint', models.ForeignKey(help_text='Site blueprint being deployed', on_delete=django.db.models.deletion.CASCADE, related_name='deployments', to='site_building.siteblueprint')),
# ('site_blueprint', ...) REMOVED: SiteBuilder deprecated
],
options={
'db_table': 'igny8_deployment_records',
'ordering': ['-created_at'],
'indexes': [models.Index(fields=['site_blueprint', 'status'], name='igny8_deplo_site_bl_14c185_idx'), models.Index(fields=['site_blueprint', 'version'], name='igny8_deplo_site_bl_34f669_idx'), models.Index(fields=['status'], name='igny8_deplo_status_5cb014_idx'), models.Index(fields=['account', 'status'], name='igny8_deplo_tenant__4de41d_idx')],
'indexes': [models.Index(fields=['status'], name='igny8_deplo_status_5cb014_idx'), models.Index(fields=['account', 'status'], name='igny8_deplo_tenant__4de41d_idx')],
},
),
migrations.CreateModel(
@@ -56,12 +56,12 @@ class Migration(migrations.Migration):
('content', models.ForeignKey(blank=True, help_text='Content being published (if publishing content)', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='publishing_records', to='writer.content')),
('sector', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.sector')),
('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.site')),
('site_blueprint', models.ForeignKey(blank=True, help_text='Site blueprint being published (if publishing site)', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='publishing_records', to='site_building.siteblueprint')),
# ('site_blueprint', ...) REMOVED: SiteBuilder deprecated
],
options={
'db_table': 'igny8_publishing_records',
'ordering': ['-created_at'],
'indexes': [models.Index(fields=['destination', 'status'], name='igny8_publi_destina_5706a3_idx'), models.Index(fields=['content', 'destination'], name='igny8_publi_content_3688ba_idx'), models.Index(fields=['site_blueprint', 'destination'], name='igny8_publi_site_bl_963f5d_idx'), models.Index(fields=['account', 'status'], name='igny8_publi_tenant__2e0749_idx')],
'indexes': [models.Index(fields=['destination', 'status'], name='igny8_publi_destina_5706a3_idx'), models.Index(fields=['content', 'destination'], name='igny8_publi_content_3688ba_idx'), models.Index(fields=['account', 'status'], name='igny8_publi_tenant__2e0749_idx')],
},
),
]

View File

@@ -18,22 +18,14 @@ class PublishingRecord(SiteSectorBaseModel):
('failed', 'Failed'),
]
# Content or SiteBlueprint reference (one must be set)
# Content reference
content = models.ForeignKey(
'writer.Content',
on_delete=models.CASCADE,
null=True,
blank=True,
related_name='publishing_records',
help_text="Content being published (if publishing content)"
)
site_blueprint = models.ForeignKey(
'site_building.SiteBlueprint',
on_delete=models.CASCADE,
null=True,
blank=True,
related_name='publishing_records',
help_text="Site blueprint being published (if publishing site)"
help_text="Content being published"
)
# Destination information
@@ -80,18 +72,17 @@ class PublishingRecord(SiteSectorBaseModel):
indexes = [
models.Index(fields=['destination', 'status']),
models.Index(fields=['content', 'destination']),
models.Index(fields=['site_blueprint', 'destination']),
models.Index(fields=['account', 'status']),
]
def __str__(self):
target = self.content or self.site_blueprint
return f"{target}{self.destination} ({self.get_status_display()})"
return f"{self.content}{self.destination} ({self.get_status_display()})"
class DeploymentRecord(SiteSectorBaseModel):
"""
Track site deployments to Sites renderer.
Legacy model - SiteBlueprint functionality removed.
"""
STATUS_CHOICES = [
@@ -102,12 +93,7 @@ class DeploymentRecord(SiteSectorBaseModel):
('rolled_back', 'Rolled Back'),
]
site_blueprint = models.ForeignKey(
'site_building.SiteBlueprint',
on_delete=models.CASCADE,
related_name='deployments',
help_text="Site blueprint being deployed"
)
# Legacy: site_blueprint field removed - now using site from SiteSectorBaseModel directly
# Version tracking
version = models.IntegerField(
@@ -148,12 +134,12 @@ class DeploymentRecord(SiteSectorBaseModel):
db_table = 'igny8_deployment_records'
ordering = ['-created_at']
indexes = [
models.Index(fields=['site_blueprint', 'status']),
models.Index(fields=['site_blueprint', 'version']),
models.Index(fields=['status']),
models.Index(fields=['account', 'status']),
models.Index(fields=['site', 'status']),
]
def __str__(self):
return f"{self.site_blueprint.name} v{self.version} ({self.get_status_display()})"
return f"Deployment v{self.version} for {self.site.name if self.site else 'Unknown'} ({self.get_status_display()})"

View File

@@ -115,7 +115,7 @@ class SitesRendererAdapter(BaseAdapter):
Returns:
dict: Site definition structure
"""
from igny8_core.business.content.models import Tasks, Content, ContentClusterMap, ContentTaxonomyMap
from igny8_core.business.content.models import Tasks, Content, ContentClusterMap
# Get all pages
pages = []
@@ -129,8 +129,7 @@ class SitesRendererAdapter(BaseAdapter):
'cluster_id': None,
'cluster_name': None,
'content_structure': None,
'taxonomy_id': None,
'taxonomy_name': None,
'taxonomy_terms': [], # Changed from taxonomy_id/taxonomy_name to list of terms
'internal_links': []
}
@@ -180,11 +179,13 @@ class SitesRendererAdapter(BaseAdapter):
page_metadata['cluster_name'] = cluster_map.cluster.name
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()
if taxonomy_map and taxonomy_map.taxonomy:
page_metadata['taxonomy_id'] = taxonomy_map.taxonomy.id
page_metadata['taxonomy_name'] = taxonomy_map.taxonomy.name
# Get taxonomy terms using M2M relationship
taxonomy_terms = content.taxonomy_terms.all()
if taxonomy_terms.exists():
page_metadata['taxonomy_terms'] = [
{'id': term.id, 'name': term.name, 'type': term.taxonomy_type}
for term in taxonomy_terms
]
# Get internal links from content
if content.internal_links:

View File

@@ -1,22 +1,24 @@
"""
Publisher Service
Phase 5: Sites Renderer & Publishing
Phase 5: Content Publishing
Main publishing orchestrator for content and sites.
Main publishing orchestrator for content.
Legacy: SiteBlueprint publishing removed.
"""
import logging
from typing import Optional, List, Dict, Any
from igny8_core.business.publishing.models import PublishingRecord, DeploymentRecord
from igny8_core.business.site_building.models import SiteBlueprint
# Removed: from igny8_core.business.site_building.models import SiteBlueprint
logger = logging.getLogger(__name__)
class PublisherService:
"""
Main publishing service for content and sites.
Main publishing service for content.
Routes to appropriate adapters based on destination.
Legacy: SiteBlueprint publishing removed.
"""
def __init__(self):
@@ -27,36 +29,6 @@ class PublisherService:
"""Lazy load adapters to avoid circular imports"""
pass # Will be implemented when adapters are created
def publish_to_sites(self, site_blueprint: SiteBlueprint) -> Dict[str, Any]:
"""
Publish site to Sites renderer.
Args:
site_blueprint: SiteBlueprint instance to deploy
Returns:
dict: Deployment result with status and deployment record
"""
from igny8_core.business.publishing.services.adapters.sites_renderer_adapter import SitesRendererAdapter
adapter = SitesRendererAdapter()
return adapter.deploy(site_blueprint)
def get_deployment_status(self, site_blueprint: SiteBlueprint) -> Optional[DeploymentRecord]:
"""
Get deployment status for a site.
Args:
site_blueprint: SiteBlueprint instance
Returns:
DeploymentRecord or None
"""
from igny8_core.business.publishing.services.deployment_service import DeploymentService
service = DeploymentService()
return service.get_status(site_blueprint)
def publish_content(
self,
content_id: int,
@@ -216,7 +188,7 @@ class PublisherService:
Publish content to multiple destinations.
Args:
content: Content instance or SiteBlueprint
content: Content instance
destinations: List of destination configs, e.g.:
[
{'platform': 'wordpress', 'site_url': '...', 'username': '...', 'app_password': '...'},
@@ -272,8 +244,7 @@ class PublisherService:
account=account,
site=content.site,
sector=content.sector,
content=content if hasattr(content, 'id') and not isinstance(content, SiteBlueprint) else None,
site_blueprint=content if isinstance(content, SiteBlueprint) else None,
content=content if hasattr(content, 'id') else None,
destination=platform,
status='published' if result.get('success') else 'failed',
destination_id=result.get('external_id'),
@@ -319,7 +290,7 @@ class PublisherService:
Publish content using site integrations.
Args:
content: Content instance or SiteBlueprint
content: Content instance
site: Site instance
account: Account instance
platforms: Optional list of platforms to publish to (all active if None)

View File

@@ -1,250 +1,12 @@
"""
Admin interface for Site Builder models
Admin interface for Site Building
Legacy SiteBlueprint admin removed - models deprecated.
"""
from django.contrib import admin
from django.utils.html import format_html
from django.urls import reverse
from igny8_core.admin.base import SiteSectorAdminMixin
from .models import (
SiteBlueprint,
PageBlueprint,
BusinessType,
AudienceProfile,
BrandPersonality,
HeroImageryDirection,
)
class PageBlueprintInline(admin.TabularInline):
"""Inline admin for Page Blueprints within Site Blueprint"""
model = PageBlueprint
extra = 0
fields = ['slug', 'title', 'type', 'status', 'order']
readonly_fields = ['slug', 'title', 'type', 'status', 'order']
can_delete = False
show_change_link = True
@admin.register(SiteBlueprint)
class SiteBlueprintAdmin(SiteSectorAdminMixin, admin.ModelAdmin):
"""Admin interface for Site Blueprints"""
list_display = [
'name',
'get_site_display',
'get_sector_display',
'status',
'hosting_type',
'version',
'get_pages_count',
'created_at',
]
list_filter = ['status', 'hosting_type', 'site', 'sector', 'account', 'created_at']
search_fields = ['name', 'description', 'site__name', 'sector__name']
readonly_fields = ['created_at', 'updated_at', 'version', 'deployed_version']
ordering = ['-created_at']
inlines = [PageBlueprintInline]
fieldsets = (
('Basic Information', {
'fields': ('name', 'description', 'account', 'site', 'sector')
}),
('Status & Configuration', {
'fields': ('status', 'hosting_type', 'version', 'deployed_version')
}),
('Configuration Data', {
'fields': ('config_json',),
'classes': ('collapse',),
}),
('Structure Data', {
'fields': ('structure_json',),
'classes': ('collapse',),
}),
('Timestamps', {
'fields': ('created_at', 'updated_at'),
'classes': ('collapse',),
}),
)
def get_site_display(self, obj):
"""Safely get site name"""
try:
if obj.site:
url = reverse('admin:igny8_core_auth_site_change', args=[obj.site.id])
return format_html('<a href="{}">{}</a>', url, obj.site.name)
return '-'
except:
return '-'
get_site_display.short_description = 'Site'
def get_sector_display(self, obj):
"""Safely get sector name"""
try:
return obj.sector.name if obj.sector else '-'
except:
return '-'
get_sector_display.short_description = 'Sector'
def get_pages_count(self, obj):
"""Get count of pages for this blueprint"""
try:
count = obj.pages.count()
if count > 0:
url = reverse('admin:site_building_pageblueprint_changelist')
return format_html('<a href="{}?site_blueprint__id__exact={}">{} pages</a>', url, obj.id, count)
return '0 pages'
except:
return '0 pages'
get_pages_count.short_description = 'Pages'
@admin.register(PageBlueprint)
class PageBlueprintAdmin(SiteSectorAdminMixin, admin.ModelAdmin):
"""Admin interface for Page Blueprints"""
list_display = [
'title',
'slug',
'get_site_blueprint_display',
'type',
'status',
'order',
'get_site_display',
'get_sector_display',
'created_at',
]
list_filter = ['status', 'type', 'site_blueprint', 'site', 'sector', 'created_at']
search_fields = ['title', 'slug', 'site_blueprint__name', 'site__name']
readonly_fields = ['created_at', 'updated_at']
ordering = ['site_blueprint', 'order', 'created_at']
fieldsets = (
('Basic Information', {
'fields': ('site_blueprint', 'title', 'slug', 'type', 'status', 'order')
}),
('Account & Site', {
'fields': ('account', 'site', 'sector'),
'classes': ('collapse',),
}),
('Content Blocks', {
'fields': ('blocks_json',),
'classes': ('collapse',),
}),
('Timestamps', {
'fields': ('created_at', 'updated_at'),
'classes': ('collapse',),
}),
)
def get_site_blueprint_display(self, obj):
"""Safely get site blueprint name with link"""
try:
if obj.site_blueprint:
url = reverse('admin:site_building_siteblueprint_change', args=[obj.site_blueprint.id])
return format_html('<a href="{}">{}</a>', url, obj.site_blueprint.name)
return '-'
except:
return '-'
get_site_blueprint_display.short_description = 'Site Blueprint'
def get_site_display(self, obj):
"""Safely get site name"""
try:
if obj.site:
url = reverse('admin:igny8_core_auth_site_change', args=[obj.site.id])
return format_html('<a href="{}">{}</a>', url, obj.site.name)
return '-'
except:
return '-'
get_site_display.short_description = 'Site'
def get_sector_display(self, obj):
"""Safely get sector name"""
try:
return obj.sector.name if obj.sector else '-'
except:
return '-'
get_sector_display.short_description = 'Sector'
@admin.register(BusinessType)
class BusinessTypeAdmin(admin.ModelAdmin):
"""Admin interface for Business Types"""
list_display = ['name', 'description', 'is_active', 'order', 'created_at']
list_filter = ['is_active', 'created_at']
search_fields = ['name', 'description']
list_editable = ['is_active', 'order']
ordering = ['order', 'name']
fieldsets = (
('Basic Information', {
'fields': ('name', 'description', 'is_active', 'order')
}),
('Timestamps', {
'fields': ('created_at', 'updated_at'),
'classes': ('collapse',),
}),
)
readonly_fields = ['created_at', 'updated_at']
@admin.register(AudienceProfile)
class AudienceProfileAdmin(admin.ModelAdmin):
"""Admin interface for Audience Profiles"""
list_display = ['name', 'description', 'is_active', 'order', 'created_at']
list_filter = ['is_active', 'created_at']
search_fields = ['name', 'description']
list_editable = ['is_active', 'order']
ordering = ['order', 'name']
fieldsets = (
('Basic Information', {
'fields': ('name', 'description', 'is_active', 'order')
}),
('Timestamps', {
'fields': ('created_at', 'updated_at'),
'classes': ('collapse',),
}),
)
readonly_fields = ['created_at', 'updated_at']
@admin.register(BrandPersonality)
class BrandPersonalityAdmin(admin.ModelAdmin):
"""Admin interface for Brand Personalities"""
list_display = ['name', 'description', 'is_active', 'order', 'created_at']
list_filter = ['is_active', 'created_at']
search_fields = ['name', 'description']
list_editable = ['is_active', 'order']
ordering = ['order', 'name']
fieldsets = (
('Basic Information', {
'fields': ('name', 'description', 'is_active', 'order')
}),
('Timestamps', {
'fields': ('created_at', 'updated_at'),
'classes': ('collapse',),
}),
)
readonly_fields = ['created_at', 'updated_at']
@admin.register(HeroImageryDirection)
class HeroImageryDirectionAdmin(admin.ModelAdmin):
"""Admin interface for Hero Imagery Directions"""
list_display = ['name', 'description', 'is_active', 'order', 'created_at']
list_filter = ['is_active', 'created_at']
search_fields = ['name', 'description']
list_editable = ['is_active', 'order']
ordering = ['order', 'name']
fieldsets = (
('Basic Information', {
'fields': ('name', 'description', 'is_active', 'order')
}),
('Timestamps', {
'fields': ('created_at', 'updated_at'),
'classes': ('collapse',),
}),
)
readonly_fields = ['created_at', 'updated_at']
# All SiteBuilder admin classes removed:
# - SiteBlueprintAdmin
# - PageBlueprintAdmin
# - BusinessTypeAdmin, AudienceProfileAdmin, BrandPersonalityAdmin, HeroImageryDirectionAdmin
#
# Site Builder functionality has been deprecated and removed from the system.

View File

@@ -0,0 +1,53 @@
# Generated manually on 2025-12-01
# Remove SiteBlueprint, PageBlueprint, SiteBlueprintCluster, and SiteBlueprintTaxonomy models
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('site_building', '0001_initial'), # Changed from 0002_initial
]
operations = [
# Drop tables in reverse dependency order
migrations.RunSQL(
sql=[
# Drop foreign key constraints first
"ALTER TABLE igny8_publishing_records DROP CONSTRAINT IF EXISTS igny8_publishing_recor_site_blueprint_id_9f4e8c7a_fk_igny8_sit CASCADE;",
"ALTER TABLE igny8_deployment_records DROP CONSTRAINT IF EXISTS igny8_deployment_recor_site_blueprint_id_3a2b7c1d_fk_igny8_sit CASCADE;",
# Drop the tables
"DROP TABLE IF EXISTS igny8_site_blueprint_taxonomies CASCADE;",
"DROP TABLE IF EXISTS igny8_site_blueprint_clusters CASCADE;",
"DROP TABLE IF EXISTS igny8_page_blueprints CASCADE;",
"DROP TABLE IF EXISTS igny8_site_blueprints CASCADE;",
"DROP TABLE IF EXISTS igny8_site_builder_business_types CASCADE;",
"DROP TABLE IF EXISTS igny8_site_builder_audience_profiles CASCADE;",
"DROP TABLE IF EXISTS igny8_site_builder_brand_personalities CASCADE;",
"DROP TABLE IF EXISTS igny8_site_builder_hero_imagery CASCADE;",
],
reverse_sql=[
# Reverse migration not supported - this is a destructive operation
"SELECT 1;"
],
),
# Also drop the site_blueprint_id column from PublishingRecord
migrations.RunSQL(
sql=[
"ALTER TABLE igny8_publishing_records DROP COLUMN IF EXISTS site_blueprint_id CASCADE;",
"DROP INDEX IF EXISTS igny8_publishing_recor_site_blueprint_id_des_b7c4e5f8_idx;",
],
reverse_sql=["SELECT 1;"],
),
# Drop the site_blueprint_id column from DeploymentRecord
migrations.RunSQL(
sql=[
"ALTER TABLE igny8_deployment_records DROP COLUMN IF EXISTS site_blueprint_id CASCADE;",
],
reverse_sql=["SELECT 1;"],
),
]

View File

@@ -1,346 +1,15 @@
"""
Site Builder Models
Phase 3: Site Builder
Site Building Models
Legacy SiteBuilder module has been removed.
This file is kept for backwards compatibility with migrations.
"""
from django.db import models
from django.core.validators import MinValueValidator
from igny8_core.auth.models import SiteSectorBaseModel
class SiteBlueprint(SiteSectorBaseModel):
"""
Site Blueprint model for storing AI-generated site structures.
"""
STATUS_CHOICES = [
('draft', 'Draft'),
('generating', 'Generating'),
('ready', 'Ready'),
('deployed', 'Deployed'),
]
HOSTING_TYPE_CHOICES = [
('igny8_sites', 'IGNY8 Sites'),
('wordpress', 'WordPress'),
('shopify', 'Shopify'),
('multi', 'Multiple Destinations'),
]
name = models.CharField(max_length=255, help_text="Site name")
description = models.TextField(blank=True, null=True, help_text="Site description")
# Site configuration (from wizard)
config_json = models.JSONField(
default=dict,
help_text="Wizard configuration: business_type, style, objectives, etc."
)
# Generated structure (from AI)
structure_json = models.JSONField(
default=dict,
help_text="AI-generated structure: pages, layout, theme, etc."
)
# Status tracking
status = models.CharField(
max_length=20,
choices=STATUS_CHOICES,
default='draft',
db_index=True,
help_text="Blueprint status"
)
# Hosting configuration
hosting_type = models.CharField(
max_length=50,
choices=HOSTING_TYPE_CHOICES,
default='igny8_sites',
help_text="Target hosting platform"
)
# Version tracking
version = models.IntegerField(default=1, validators=[MinValueValidator(1)], help_text="Blueprint version")
deployed_version = models.IntegerField(null=True, blank=True, help_text="Currently deployed version")
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
app_label = 'site_building'
db_table = 'igny8_site_blueprints'
ordering = ['-created_at']
verbose_name = 'Site Blueprint'
verbose_name_plural = 'Site Blueprints'
indexes = [
models.Index(fields=['status']),
models.Index(fields=['hosting_type']),
models.Index(fields=['site', 'sector']),
models.Index(fields=['account', 'status']),
]
def __str__(self):
return f"{self.name} ({self.get_status_display()})"
class PageBlueprint(SiteSectorBaseModel):
"""
Page Blueprint model for storing individual page definitions.
"""
PAGE_TYPE_CHOICES = [
('home', 'Home'),
('about', 'About'),
('services', 'Services'),
('products', 'Products'),
('blog', 'Blog'),
('contact', 'Contact'),
('custom', 'Custom'),
]
STATUS_CHOICES = [
('draft', 'Draft'),
('generating', 'Generating'),
('ready', 'Ready'),
('published', 'Published'),
]
site_blueprint = models.ForeignKey(
SiteBlueprint,
on_delete=models.CASCADE,
related_name='pages',
help_text="The site blueprint this page belongs to"
)
slug = models.SlugField(max_length=255, help_text="Page URL slug")
title = models.CharField(max_length=255, help_text="Page title")
# Page type
type = models.CharField(
max_length=50,
choices=PAGE_TYPE_CHOICES,
default='custom',
help_text="Page type"
)
# Page content (blocks)
blocks_json = models.JSONField(
default=list,
help_text="Page content blocks: [{'type': 'hero', 'data': {...}}, ...]"
)
# Status
status = models.CharField(
max_length=20,
choices=STATUS_CHOICES,
default='draft',
db_index=True,
help_text="Page status"
)
# Order
order = models.IntegerField(default=0, help_text="Page order in navigation")
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
app_label = 'site_building'
db_table = 'igny8_page_blueprints'
ordering = ['order', 'created_at']
verbose_name = 'Page Blueprint'
verbose_name_plural = 'Page Blueprints'
unique_together = [['site_blueprint', 'slug']]
indexes = [
models.Index(fields=['site_blueprint', 'status']),
models.Index(fields=['type']),
models.Index(fields=['site_blueprint', 'order']),
]
def save(self, *args, **kwargs):
"""Automatically set account, site, and sector from site_blueprint"""
if self.site_blueprint:
self.account = self.site_blueprint.account
self.site = self.site_blueprint.site
self.sector = self.site_blueprint.sector
super().save(*args, **kwargs)
def __str__(self):
return f"{self.title} ({self.site_blueprint.name})"
class SiteBlueprintCluster(SiteSectorBaseModel):
"""
Mapping table that connects planner clusters to site blueprints
with role/coverage metadata.
"""
ROLE_CHOICES = [
('hub', 'Hub Page'),
('supporting', 'Supporting Page'),
('attribute', 'Attribute Page'),
]
COVERAGE_CHOICES = [
('pending', 'Pending'),
('in_progress', 'In Progress'),
('complete', 'Complete'),
]
site_blueprint = models.ForeignKey(
SiteBlueprint,
on_delete=models.CASCADE,
related_name='cluster_links',
help_text="Site blueprint that is planning coverage for the cluster",
)
cluster = models.ForeignKey(
'planner.Clusters',
on_delete=models.CASCADE,
related_name='blueprint_links',
help_text="Planner cluster being mapped into the site blueprint",
)
role = models.CharField(max_length=50, choices=ROLE_CHOICES, default='hub')
coverage_status = models.CharField(max_length=50, choices=COVERAGE_CHOICES, default='pending')
metadata = models.JSONField(
default=dict,
blank=True,
help_text="Additional coverage metadata (target pages, keyword counts, ai hints)",
)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
app_label = 'site_building'
db_table = 'igny8_site_blueprint_clusters'
unique_together = [['site_blueprint', 'cluster', 'role']]
verbose_name = 'Site Blueprint Cluster'
verbose_name_plural = 'Site Blueprint Clusters'
indexes = [
models.Index(fields=['site_blueprint', 'cluster']),
models.Index(fields=['site_blueprint', 'coverage_status']),
models.Index(fields=['cluster', 'role']),
]
def save(self, *args, **kwargs):
if self.site_blueprint:
self.account = self.site_blueprint.account
self.site = self.site_blueprint.site
self.sector = self.site_blueprint.sector
super().save(*args, **kwargs)
def __str__(self):
return f"{self.site_blueprint.name}{self.cluster.name}"
class SiteBlueprintTaxonomy(SiteSectorBaseModel):
"""
Taxonomy blueprint entity for categories/tags/product attributes tied to clusters.
"""
TAXONOMY_TYPE_CHOICES = [
('blog_category', 'Blog Category'),
('blog_tag', 'Blog Tag'),
('product_category', 'Product Category'),
('product_tag', 'Product Tag'),
('product_attribute', 'Product Attribute'),
('service_category', 'Service Category'),
]
site_blueprint = models.ForeignKey(
SiteBlueprint,
on_delete=models.CASCADE,
related_name='taxonomies',
help_text="Site blueprint this taxonomy belongs to",
)
name = models.CharField(max_length=255, help_text="Display name")
slug = models.SlugField(max_length=255, help_text="Slug/identifier within the site blueprint")
taxonomy_type = models.CharField(max_length=50, choices=TAXONOMY_TYPE_CHOICES, default='blog_category')
description = models.TextField(blank=True, null=True)
metadata = models.JSONField(default=dict, blank=True, help_text="Additional taxonomy metadata or AI hints")
clusters = models.ManyToManyField(
'planner.Clusters',
blank=True,
related_name='blueprint_taxonomies',
help_text="Planner clusters that this taxonomy maps to",
)
external_reference = models.CharField(
max_length=255,
blank=True,
null=True,
help_text="External system ID (WordPress/WooCommerce/etc.)",
)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
app_label = 'site_building'
db_table = 'igny8_site_blueprint_taxonomies'
unique_together = [['site_blueprint', 'slug']]
verbose_name = 'Site Blueprint Taxonomy'
verbose_name_plural = 'Site Blueprint Taxonomies'
indexes = [
models.Index(fields=['site_blueprint', 'taxonomy_type']),
models.Index(fields=['taxonomy_type']),
]
def save(self, *args, **kwargs):
if self.site_blueprint:
self.account = self.site_blueprint.account
self.site = self.site_blueprint.site
self.sector = self.site_blueprint.sector
super().save(*args, **kwargs)
def __str__(self):
return f"{self.name} ({self.get_taxonomy_type_display()})"
class SiteBuilderOption(models.Model):
"""
Base model for Site Builder dropdown metadata.
"""
name = models.CharField(max_length=120, unique=True)
description = models.CharField(max_length=255, blank=True)
is_active = models.BooleanField(default=True)
order = models.PositiveIntegerField(default=0)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
abstract = True
ordering = ['order', 'name']
def __str__(self):
return self.name
class BusinessType(SiteBuilderOption):
class Meta(SiteBuilderOption.Meta):
app_label = 'site_building'
db_table = 'igny8_site_builder_business_types'
verbose_name = 'Business Type'
verbose_name_plural = 'Business Types'
class AudienceProfile(SiteBuilderOption):
class Meta(SiteBuilderOption.Meta):
app_label = 'site_building'
db_table = 'igny8_site_builder_audience_profiles'
verbose_name = 'Audience Profile'
verbose_name_plural = 'Audience Profiles'
class BrandPersonality(SiteBuilderOption):
class Meta(SiteBuilderOption.Meta):
app_label = 'site_building'
db_table = 'igny8_site_builder_brand_personalities'
verbose_name = 'Brand Personality'
verbose_name_plural = 'Brand Personalities'
class HeroImageryDirection(SiteBuilderOption):
class Meta(SiteBuilderOption.Meta):
app_label = 'site_building'
db_table = 'igny8_site_builder_hero_imagery'
verbose_name = 'Hero Imagery Direction'
verbose_name_plural = 'Hero Imagery Directions'
# All SiteBuilder models have been removed:
# - SiteBlueprint
# - PageBlueprint
# - SiteBlueprintCluster
# - SiteBlueprintTaxonomy
# - BusinessType, AudienceProfile, BrandPersonality, HeroImageryDirection
#
# Taxonomy functionality moved to ContentTaxonomy model in business/content/models.py