205 lines
6.7 KiB
Python
205 lines
6.7 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Database Migration Verification Script
|
|
Checks for orphaned SiteBlueprint tables and verifies new migrations
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import django
|
|
|
|
# Setup Django
|
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'igny8_core.settings')
|
|
django.setup()
|
|
|
|
from django.db import connection
|
|
from django.core.management import call_command
|
|
|
|
|
|
def check_orphaned_tables():
|
|
"""Check for orphaned blueprint tables"""
|
|
print("\n" + "="*60)
|
|
print("CHECKING FOR ORPHANED SITEBLUEPRINT TABLES")
|
|
print("="*60 + "\n")
|
|
|
|
with connection.cursor() as cursor:
|
|
cursor.execute("""
|
|
SELECT table_name
|
|
FROM information_schema.tables
|
|
WHERE table_schema = 'public'
|
|
AND table_name LIKE '%blueprint%'
|
|
ORDER BY table_name;
|
|
""")
|
|
tables = cursor.fetchall()
|
|
|
|
if tables:
|
|
print("⚠️ Found blueprint-related tables:")
|
|
for table in tables:
|
|
print(f" - {table[0]}")
|
|
print("\n💡 These tables can be safely dropped if no longer needed.")
|
|
else:
|
|
print("✅ No orphaned blueprint tables found.")
|
|
|
|
return len(tables) if tables else 0
|
|
|
|
|
|
def verify_cluster_constraint():
|
|
"""Verify cluster unique constraint is per-site/sector"""
|
|
print("\n" + "="*60)
|
|
print("VERIFYING CLUSTER UNIQUE CONSTRAINT")
|
|
print("="*60 + "\n")
|
|
|
|
with connection.cursor() as cursor:
|
|
cursor.execute("""
|
|
SELECT
|
|
tc.constraint_name,
|
|
tc.constraint_type,
|
|
string_agg(kcu.column_name, ', ' ORDER BY kcu.ordinal_position) as columns
|
|
FROM information_schema.table_constraints tc
|
|
JOIN information_schema.key_column_usage kcu
|
|
ON tc.constraint_name = kcu.constraint_name
|
|
AND tc.table_schema = kcu.table_schema
|
|
WHERE tc.table_name = 'igny8_clusters'
|
|
AND tc.constraint_type = 'UNIQUE'
|
|
GROUP BY tc.constraint_name, tc.constraint_type;
|
|
""")
|
|
constraints = cursor.fetchall()
|
|
|
|
if constraints:
|
|
print("Found unique constraints on igny8_clusters:")
|
|
for constraint in constraints:
|
|
name, ctype, columns = constraint
|
|
print(f" {name}: {columns}")
|
|
|
|
# Check if it includes site and sector
|
|
if 'site' in columns.lower() and 'sector' in columns.lower():
|
|
print(f" ✅ Constraint is scoped per-site/sector")
|
|
else:
|
|
print(f" ⚠️ Constraint may need updating")
|
|
else:
|
|
print("⚠️ No unique constraints found on igny8_clusters")
|
|
|
|
|
|
def verify_automation_delays():
|
|
"""Verify automation delay fields exist"""
|
|
print("\n" + "="*60)
|
|
print("VERIFYING AUTOMATION DELAY CONFIGURATION")
|
|
print("="*60 + "\n")
|
|
|
|
with connection.cursor() as cursor:
|
|
cursor.execute("""
|
|
SELECT
|
|
column_name,
|
|
data_type,
|
|
column_default
|
|
FROM information_schema.columns
|
|
WHERE table_name = 'igny8_automationconfig'
|
|
AND column_name IN ('within_stage_delay', 'between_stage_delay')
|
|
ORDER BY column_name;
|
|
""")
|
|
columns = cursor.fetchall()
|
|
|
|
if len(columns) == 2:
|
|
print("✅ Delay configuration fields found:")
|
|
for col in columns:
|
|
name, dtype, default = col
|
|
print(f" {name}: {dtype} (default: {default})")
|
|
else:
|
|
print(f"⚠️ Expected 2 delay fields, found {len(columns)}")
|
|
|
|
|
|
def check_migration_status():
|
|
"""Check migration status"""
|
|
print("\n" + "="*60)
|
|
print("CHECKING MIGRATION STATUS")
|
|
print("="*60 + "\n")
|
|
|
|
with connection.cursor() as cursor:
|
|
cursor.execute("""
|
|
SELECT app, name, applied
|
|
FROM django_migrations
|
|
WHERE name LIKE '%cluster%' OR name LIKE '%delay%'
|
|
ORDER BY applied DESC
|
|
LIMIT 10;
|
|
""")
|
|
migrations = cursor.fetchall()
|
|
|
|
if migrations:
|
|
print("Recent relevant migrations:")
|
|
for mig in migrations:
|
|
app, name, applied = mig
|
|
status = "✅" if applied else "⏳"
|
|
print(f" {status} {app}.{name}")
|
|
print(f" Applied: {applied}")
|
|
else:
|
|
print("No relevant migrations found in history")
|
|
|
|
|
|
def check_data_integrity():
|
|
"""Check for data integrity issues"""
|
|
print("\n" + "="*60)
|
|
print("DATA INTEGRITY CHECKS")
|
|
print("="*60 + "\n")
|
|
|
|
from igny8_core.business.planning.models import Clusters, Keywords
|
|
|
|
# Check for clusters with 'active' status (should all be 'new' or 'mapped')
|
|
active_clusters = Clusters.objects.filter(status='active').count()
|
|
if active_clusters > 0:
|
|
print(f"⚠️ Found {active_clusters} clusters with status='active'")
|
|
print(" These should be updated to 'new' or 'mapped'")
|
|
else:
|
|
print("✅ No clusters with invalid 'active' status")
|
|
|
|
# Check for duplicate cluster names in same site/sector
|
|
with connection.cursor() as cursor:
|
|
cursor.execute("""
|
|
SELECT name, site_id, sector_id, COUNT(*) as count
|
|
FROM igny8_clusters
|
|
GROUP BY name, site_id, sector_id
|
|
HAVING COUNT(*) > 1;
|
|
""")
|
|
duplicates = cursor.fetchall()
|
|
|
|
if duplicates:
|
|
print(f"\n⚠️ Found {len(duplicates)} duplicate cluster names in same site/sector:")
|
|
for dup in duplicates[:5]: # Show first 5
|
|
print(f" - '{dup[0]}' (site={dup[1]}, sector={dup[2]}): {dup[3]} duplicates")
|
|
else:
|
|
print("✅ No duplicate cluster names within same site/sector")
|
|
|
|
|
|
def main():
|
|
print("\n" + "#"*60)
|
|
print("# IGNY8 DATABASE MIGRATION VERIFICATION")
|
|
print("# Date:", __import__('datetime').datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
|
|
print("#"*60)
|
|
|
|
try:
|
|
orphaned = check_orphaned_tables()
|
|
verify_cluster_constraint()
|
|
verify_automation_delays()
|
|
check_migration_status()
|
|
check_data_integrity()
|
|
|
|
print("\n" + "="*60)
|
|
print("VERIFICATION COMPLETE")
|
|
print("="*60)
|
|
|
|
if orphaned > 0:
|
|
print(f"\n⚠️ {orphaned} orphaned table(s) found - review recommended")
|
|
else:
|
|
print("\n✅ All verifications passed!")
|
|
|
|
print("\n")
|
|
|
|
except Exception as e:
|
|
print(f"\n❌ ERROR: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|