Initial commit: igny8 project

This commit is contained in:
igny8
2025-11-09 10:27:02 +00:00
commit 60b8188111
27265 changed files with 4360521 additions and 0 deletions

View File

@@ -0,0 +1,142 @@
"""
Django management command to add keywords to sectors
Usage: python manage.py add_keywords_to_sectors --site "Test Site" --keywords-per-sector 5
"""
from django.core.management.base import BaseCommand
from django.db import transaction
from igny8_core.auth.models import Site, Sector
from igny8_core.modules.planner.models import Keywords
class Command(BaseCommand):
help = 'Add keywords to each sector of a specified site'
def add_arguments(self, parser):
parser.add_argument(
'--site',
type=str,
default='Test Site',
help='Name of the site to add keywords to (default: "Test Site")'
)
parser.add_argument(
'--keywords-per-sector',
type=int,
default=5,
help='Number of keywords to add to each sector (default: 5)'
)
def handle(self, *args, **options):
site_name = options['site']
keywords_per_sector = options['keywords_per_sector']
try:
# Find the site
site = Site.objects.get(name=site_name)
self.stdout.write(self.style.SUCCESS(f'Found site: {site.name} (ID: {site.id})'))
# Find all sectors for this site
sectors = Sector.objects.filter(site=site, is_active=True)
if not sectors.exists():
self.stdout.write(self.style.WARNING(f'No active sectors found for site "{site_name}"'))
return
self.stdout.write(f'Found {sectors.count()} active sector(s) for site "{site_name}"')
# Technology-related keywords for technology sectors
tech_keywords = [
'artificial intelligence',
'machine learning',
'cloud computing',
'data analytics',
'cybersecurity',
'blockchain technology',
'web development',
'mobile app development',
'software engineering',
'digital transformation',
'IoT solutions',
'API development',
'microservices architecture',
'devops practices',
'automated testing',
'agile methodology',
'scrum framework',
'version control systems',
'container orchestration',
'serverless computing',
]
total_keywords_created = 0
with transaction.atomic():
for sector in sectors:
self.stdout.write(f'\nProcessing sector: {sector.name} (ID: {sector.id})')
# Get existing keywords for this sector to avoid duplicates
existing_keywords = set(
Keywords.objects.filter(sector=sector)
.values_list('keyword', flat=True)
)
# Select keywords that don't already exist
available_keywords = [kw for kw in tech_keywords if kw.lower() not in existing_keywords]
if len(available_keywords) < keywords_per_sector:
self.stdout.write(
self.style.WARNING(
f'Only {len(available_keywords)} unique keywords available. '
f'Creating {len(available_keywords)} keywords instead of {keywords_per_sector}.'
)
)
keywords_to_create = available_keywords[:keywords_per_sector]
if not keywords_to_create:
self.stdout.write(
self.style.WARNING(f'All keywords already exist for sector "{sector.name}". Skipping.')
)
continue
# Create keywords
created_count = 0
for keyword_text in keywords_to_create:
keyword, created = Keywords.objects.get_or_create(
site=site,
sector=sector,
keyword=keyword_text,
defaults={
'account': site.account,
'volume': 1000 + (created_count * 100), # Varying volumes
'difficulty': 30 + (created_count * 10), # Varying difficulty (0-100 scale)
'intent': 'informational' if created_count % 2 == 0 else 'commercial',
'status': 'active',
}
)
if created:
created_count += 1
total_keywords_created += 1
self.stdout.write(f' ✓ Created: "{keyword_text}"')
self.stdout.write(
self.style.SUCCESS(
f'Created {created_count} keyword(s) for sector "{sector.name}"'
)
)
self.stdout.write(
self.style.SUCCESS(
f'\n✅ Successfully created {total_keywords_created} keyword(s) across {sectors.count()} sector(s)'
)
)
except Site.DoesNotExist:
self.stdout.write(
self.style.ERROR(f'Site "{site_name}" not found. Available sites:')
)
for site in Site.objects.all():
self.stdout.write(f' - {site.name} (ID: {site.id})')
except Exception as e:
self.stdout.write(self.style.ERROR(f'Error: {str(e)}'))
raise

View File

@@ -0,0 +1,124 @@
"""
Management command to find and remove duplicate Keywords records.
Duplicates are defined as records with the same (seed_keyword, site, sector) combination.
The unique_together constraint should prevent new duplicates, but this cleans up any existing ones.
"""
from django.core.management.base import BaseCommand
from django.db.models import Count
from igny8_core.modules.planner.models import Keywords
class Command(BaseCommand):
help = 'Find and remove duplicate Keywords records (same seed_keyword, site, sector)'
def add_arguments(self, parser):
parser.add_argument(
'--dry-run',
action='store_true',
help='Show what would be deleted without actually deleting',
)
def handle(self, *args, **options):
dry_run = options['dry_run']
if dry_run:
self.stdout.write(self.style.WARNING('DRY RUN MODE - No records will be deleted'))
# Find duplicates by grouping on (seed_keyword, site, sector)
duplicates = (
Keywords.objects
.values('seed_keyword', 'site', 'sector')
.annotate(count=Count('id'))
.filter(count__gt=1)
.order_by('-count')
)
total_duplicate_groups = duplicates.count()
if total_duplicate_groups == 0:
self.stdout.write(self.style.SUCCESS('✓ No duplicate records found. Database is clean.'))
return
self.stdout.write(
self.style.WARNING(
f'Found {total_duplicate_groups} duplicate group(s) (same seed_keyword, site, sector)'
)
)
total_to_delete = 0
deleted_count = 0
for dup_group in duplicates:
seed_keyword_id = dup_group['seed_keyword']
site_id = dup_group['site']
sector_id = dup_group['sector']
count = dup_group['count']
# Get all records in this duplicate group, ordered by created_at (keep the oldest)
duplicate_records = Keywords.objects.filter(
seed_keyword_id=seed_keyword_id,
site_id=site_id,
sector_id=sector_id
).order_by('created_at')
# Keep the first (oldest) record, delete the rest
records_to_delete = duplicate_records[1:]
to_delete_count = records_to_delete.count()
total_to_delete += to_delete_count
# Get keyword text for display
keyword_text = duplicate_records.first().keyword if duplicate_records.exists() else 'Unknown'
self.stdout.write(
f' Group: seed_keyword={seed_keyword_id}, site={site_id}, sector={sector_id} '
f'({count} records, will keep 1, delete {to_delete_count})'
)
self.stdout.write(f' Keyword: "{keyword_text}"')
if not dry_run:
# Delete duplicates, keeping the oldest
deleted = records_to_delete.delete()[0]
deleted_count += deleted
self.stdout.write(
self.style.SUCCESS(f' ✓ Deleted {deleted} duplicate record(s)')
)
else:
for record in records_to_delete:
self.stdout.write(
f' Would delete: ID={record.id}, created={record.created_at}'
)
if dry_run:
self.stdout.write(
self.style.WARNING(
f'\nDRY RUN: Would delete {total_to_delete} duplicate record(s)'
)
)
self.stdout.write('Run without --dry-run to actually delete these records.')
else:
self.stdout.write(
self.style.SUCCESS(
f'\n✓ Successfully deleted {deleted_count} duplicate record(s)'
)
)
# Verify no duplicates remain
remaining_duplicates = (
Keywords.objects
.values('seed_keyword', 'site', 'sector')
.annotate(count=Count('id'))
.filter(count__gt=1)
.count()
)
if remaining_duplicates == 0:
self.stdout.write(self.style.SUCCESS('✓ Verified: No duplicates remain in database'))
else:
self.stdout.write(
self.style.WARNING(
f'⚠ Warning: {remaining_duplicates} duplicate group(s) still remain. '
'This may indicate a database constraint issue.'
)
)

View File

@@ -0,0 +1,273 @@
"""
Django management command to create Home & Garden industry with sectors and keywords
Usage: python manage.py create_home_garden_industry
"""
from django.core.management.base import BaseCommand
from django.db import transaction
from django.utils.text import slugify
from igny8_core.auth.models import Industry, IndustrySector, Site, Sector, Account
from igny8_core.modules.planner.models import Keywords
class Command(BaseCommand):
help = 'Create Home & Garden industry with sectors and add keywords to each sector'
def handle(self, *args, **options):
with transaction.atomic():
# Step 1: Create or get Home & Garden industry
industry, created = Industry.objects.get_or_create(
slug='home-garden',
defaults={
'name': 'Home & Garden',
'description': 'Home improvement, gardening, landscaping, and interior design',
'is_active': True,
}
)
if created:
self.stdout.write(self.style.SUCCESS(f'✅ Created industry: {industry.name}'))
else:
self.stdout.write(f'Industry "{industry.name}" already exists, using existing one.')
# Step 2: Define Home & Garden sectors with their keywords (max 5 per site)
sectors_data = [
{
'name': 'Gardening',
'slug': 'gardening',
'description': 'Plants, flowers, vegetables, and garden maintenance',
'keywords': [
'organic gardening',
'vegetable gardening',
'flower garden design',
'garden tools',
'plant care tips',
'composting guide',
'garden pest control',
'herb garden ideas',
'garden irrigation systems',
'seasonal planting guide',
]
},
{
'name': 'Home Improvement',
'slug': 'home-improvement',
'description': 'DIY projects, renovations, and home repairs',
'keywords': [
'home renovation ideas',
'diy home projects',
'kitchen remodeling',
'bathroom renovation',
'flooring installation',
'painting tips',
'home repair guide',
'power tools review',
'home maintenance checklist',
'interior design trends',
]
},
{
'name': 'Landscaping',
'slug': 'landscaping',
'description': 'Outdoor design, lawn care, and hardscaping',
'keywords': [
'landscape design ideas',
'lawn care tips',
'outdoor patio design',
'deck building guide',
'garden pathways',
'outdoor lighting ideas',
'lawn mowing tips',
'tree planting guide',
'outdoor kitchen design',
'garden edging ideas',
]
},
{
'name': 'Interior Design',
'slug': 'interior-design',
'description': 'Home decoration, furniture, and interior styling',
'keywords': [
'interior design styles',
'home decor ideas',
'furniture arrangement',
'color scheme ideas',
'small space design',
'home staging tips',
'decoration trends',
'room makeover ideas',
'interior lighting design',
'home organization tips',
]
},
{
'name': 'Home Decor',
'slug': 'home-decor',
'description': 'Decorative items, accessories, and home styling',
'keywords': [
'home decor accessories',
'wall art ideas',
'curtain design tips',
'pillow arrangement',
'vase decoration ideas',
'home fragrance tips',
'decorative mirrors',
'rug selection guide',
'home accent pieces',
'seasonal home decor',
]
},
]
# Step 3: Create IndustrySector templates
industry_sectors = []
for sector_data in sectors_data:
# Create IndustrySector with suggested_keywords (required field in DB)
industry_sector, created = IndustrySector.objects.get_or_create(
industry=industry,
slug=sector_data['slug'],
defaults={
'name': sector_data['name'],
'description': sector_data['description'],
'suggested_keywords': sector_data['keywords'], # JSON array of keywords
'is_active': True,
}
)
industry_sectors.append((industry_sector, sector_data['keywords'], created))
if created:
self.stdout.write(f' ✓ Created sector template: {industry_sector.name}')
else:
self.stdout.write(f' Sector template "{industry_sector.name}" already exists')
self.stdout.write(self.style.SUCCESS(f'\n✅ Created {len([s for s, _, c in industry_sectors if c])} new sector templates'))
# Step 4: Find or create a site for Home & Garden industry
# Try to find an existing site, or create one
site = None
try:
# Try to find a site with this industry
site = Site.objects.filter(industry=industry, is_active=True).first()
if site:
self.stdout.write(f'Found existing site: {site.name} (ID: {site.id})')
except:
pass
if not site:
# Get the first account or create a test account
account = Account.objects.first()
if not account:
self.stdout.write(self.style.ERROR('No account found. Please create an account first.'))
return
# Create a new site
site, created = Site.objects.get_or_create(
account=account,
slug='home-garden-site',
defaults={
'name': 'Home & Garden Site',
'industry': industry,
'is_active': True,
'status': 'active',
}
)
if created:
self.stdout.write(self.style.SUCCESS(f'✅ Created site: {site.name} (ID: {site.id})'))
else:
self.stdout.write(f'Using existing site: {site.name}')
# Step 5: Create actual Sector instances from IndustrySector templates
created_sectors = []
for industry_sector, keywords_list, _ in industry_sectors:
# Check if sector already exists for this site
sector, sector_created = Sector.objects.get_or_create(
site=site,
slug=industry_sector.slug,
defaults={
'industry_sector': industry_sector,
'name': industry_sector.name,
'description': industry_sector.description,
'is_active': True,
'status': 'active',
'account': site.account,
}
)
created_sectors.append((sector, keywords_list, sector_created))
if sector_created:
self.stdout.write(f' ✓ Created sector: {sector.name} (ID: {sector.id})')
else:
self.stdout.write(f' Sector "{sector.name}" already exists for this site')
# Step 6: Add 10 keywords to each sector
total_keywords_created = 0
for sector, keywords_list, sector_created in created_sectors:
self.stdout.write(f'\nProcessing sector: {sector.name} (ID: {sector.id})')
# Get existing keywords to avoid duplicates
existing_keywords = set(
Keywords.objects.filter(sector=sector)
.values_list('keyword', flat=True)
)
keywords_to_create = []
for keyword_text in keywords_list:
if keyword_text.lower() not in existing_keywords:
keywords_to_create.append(keyword_text)
if len(keywords_to_create) < 10:
# If we have fewer than 10 unique keywords, add some generic ones
generic_keywords = [
f'{sector.name.lower()} tips',
f'{sector.name.lower()} guide',
f'{sector.name.lower()} ideas',
f'best {sector.name.lower()}',
f'{sector.name.lower()} products',
f'{sector.name.lower()} services',
f'{sector.name.lower()} solutions',
f'{sector.name.lower()} advice',
f'{sector.name.lower()} techniques',
f'{sector.name.lower()} trends',
]
for gen_kw in generic_keywords:
if len(keywords_to_create) >= 10:
break
if gen_kw.lower() not in existing_keywords and gen_kw.lower() not in [k.lower() for k in keywords_to_create]:
keywords_to_create.append(gen_kw)
# Create keywords (limit to 10)
created_count = 0
for keyword_text in keywords_to_create[:10]:
keyword, kw_created = Keywords.objects.get_or_create(
site=site,
sector=sector,
keyword=keyword_text.lower(),
defaults={
'account': site.account,
'volume': 500 + (created_count * 50), # Varying volumes
'difficulty': 20 + (created_count * 8), # Varying difficulty (0-100 scale)
'intent': 'informational' if created_count % 2 == 0 else 'commercial',
'status': 'active',
}
)
if kw_created:
created_count += 1
total_keywords_created += 1
self.stdout.write(f' ✓ Created keyword: "{keyword_text}"')
if created_count == 0:
self.stdout.write(self.style.WARNING(f' No new keywords created (all already exist)'))
else:
self.stdout.write(
self.style.SUCCESS(
f' Created {created_count} keyword(s) for sector "{sector.name}"'
)
)
self.stdout.write(
self.style.SUCCESS(
f'\n✅ Successfully created:\n'
f' - Industry: {industry.name}\n'
f' - Sector templates: {len(industry_sectors)}\n'
f' - Site sectors: {len(created_sectors)}\n'
f' - Total keywords: {total_keywords_created}\n'
)
)

View File

@@ -0,0 +1,216 @@
#!/usr/bin/env python
"""
Django management command to:
1. Migrate existing Keywords to SeedKeywords
2. Delete all planner and writer module data
3. Clean up relationships
Usage: python manage.py migrate_keywords_to_seed
"""
from django.core.management.base import BaseCommand
from django.db import transaction
from django.db.models import Q
from igny8_core.modules.planner.models import Keywords, Clusters, ContentIdeas
from igny8_core.modules.writer.models import Tasks, Content, Images
from igny8_core.auth.models import SeedKeyword, Industry, IndustrySector, Site, Sector
class Command(BaseCommand):
help = 'Migrate Keywords to SeedKeywords and clean up planner/writer data'
def add_arguments(self, parser):
parser.add_argument(
'--dry-run',
action='store_true',
help='Show what would be done without making changes',
)
def handle(self, *args, **options):
dry_run = options['dry_run']
if dry_run:
self.stdout.write(self.style.WARNING('DRY RUN MODE - No changes will be made'))
with transaction.atomic():
# Step 1: Migrate Keywords to SeedKeywords
self.stdout.write('=' * 60)
self.stdout.write('Step 1: Migrating Keywords to SeedKeywords')
self.stdout.write('=' * 60)
# Get all keywords with valid industry and industry_sector relationships
keywords = Keywords.objects.select_related(
'site', 'sector', 'sector__industry_sector', 'site__industry'
).filter(
site__industry__isnull=False,
sector__industry_sector__isnull=False
).distinct()
total_keywords = keywords.count()
self.stdout.write(f'Found {total_keywords} keywords to migrate')
created_count = 0
skipped_count = 0
errors = []
# Group by unique (keyword, industry, sector) to avoid duplicates
seen_combinations = set()
for keyword in keywords:
try:
industry = keyword.site.industry
industry_sector = keyword.sector.industry_sector
if not industry or not industry_sector:
skipped_count += 1
continue
# Create unique key for deduplication
unique_key = (keyword.keyword.lower().strip(), industry.id, industry_sector.id)
if unique_key in seen_combinations:
skipped_count += 1
continue
seen_combinations.add(unique_key)
# Check if SeedKeyword already exists
existing = SeedKeyword.objects.filter(
keyword__iexact=keyword.keyword.strip(),
industry=industry,
sector=industry_sector
).first()
if existing:
skipped_count += 1
self.stdout.write(f' ⏭️ Skipped (exists): "{keyword.keyword}" for {industry.name} - {industry_sector.name}')
continue
if not dry_run:
# Create SeedKeyword
seed_keyword = SeedKeyword.objects.create(
keyword=keyword.keyword.strip(),
industry=industry,
sector=industry_sector,
volume=keyword.volume or 0,
difficulty=keyword.difficulty or 0,
intent=keyword.intent or 'informational',
is_active=True
)
created_count += 1
self.stdout.write(
self.style.SUCCESS(
f' ✅ Created: "{seed_keyword.keyword}" for {industry.name} - {industry_sector.name}'
)
)
else:
created_count += 1
self.stdout.write(
f' [DRY RUN] Would create: "{keyword.keyword}" for {industry.name} - {industry_sector.name}'
)
except Exception as e:
error_msg = f'Error migrating keyword "{keyword.keyword}" (ID: {keyword.id}): {str(e)}'
errors.append(error_msg)
self.stdout.write(self.style.ERROR(f'{error_msg}'))
self.stdout.write('')
self.stdout.write(f' Created: {created_count}')
self.stdout.write(f' Skipped: {skipped_count}')
if errors:
self.stdout.write(self.style.ERROR(f' Errors: {len(errors)}'))
if not dry_run:
# Step 2: Delete all planner and writer module data
self.stdout.write('')
self.stdout.write('=' * 60)
self.stdout.write('Step 2: Deleting planner and writer module data')
self.stdout.write('=' * 60)
# Delete in order to respect foreign key constraints
# 1. Delete M2M relationships first
self.stdout.write('Deleting M2M relationships...')
tasks_keywords_count = Tasks.objects.filter(keyword_objects__isnull=False).count()
content_ideas_keywords_count = ContentIdeas.objects.filter(keyword_objects__isnull=False).count()
# Clear M2M relationships
for task in Tasks.objects.all():
task.keyword_objects.clear()
for idea in ContentIdeas.objects.all():
idea.keyword_objects.clear()
self.stdout.write(f' ✅ Cleared {tasks_keywords_count} task-keyword relationships')
self.stdout.write(f' ✅ Cleared {content_ideas_keywords_count} content-idea-keyword relationships')
# 2. Delete writer module data
self.stdout.write('Deleting writer module data...')
images_count = Images.objects.count()
content_count = Content.objects.count()
tasks_count = Tasks.objects.count()
Images.objects.all().delete()
Content.objects.all().delete()
Tasks.objects.all().delete()
self.stdout.write(f' ✅ Deleted {images_count} images')
self.stdout.write(f' ✅ Deleted {content_count} content records')
self.stdout.write(f' ✅ Deleted {tasks_count} tasks')
# 3. Delete planner module data
self.stdout.write('Deleting planner module data...')
content_ideas_count = ContentIdeas.objects.count()
clusters_count = Clusters.objects.count()
keywords_count = Keywords.objects.count()
ContentIdeas.objects.all().delete()
Clusters.objects.all().delete()
Keywords.objects.all().delete()
self.stdout.write(f' ✅ Deleted {content_ideas_count} content ideas')
self.stdout.write(f' ✅ Deleted {clusters_count} clusters')
self.stdout.write(f' ✅ Deleted {keywords_count} keywords')
else:
# Dry run - just show counts
self.stdout.write('')
self.stdout.write('=' * 60)
self.stdout.write('Step 2: Would delete planner and writer module data')
self.stdout.write('=' * 60)
images_count = Images.objects.count()
content_count = Content.objects.count()
tasks_count = Tasks.objects.count()
content_ideas_count = ContentIdeas.objects.count()
clusters_count = Clusters.objects.count()
keywords_count = Keywords.objects.count()
self.stdout.write(f' [DRY RUN] Would delete {images_count} images')
self.stdout.write(f' [DRY RUN] Would delete {content_count} content records')
self.stdout.write(f' [DRY RUN] Would delete {tasks_count} tasks')
self.stdout.write(f' [DRY RUN] Would delete {content_ideas_count} content ideas')
self.stdout.write(f' [DRY RUN] Would delete {clusters_count} clusters')
self.stdout.write(f' [DRY RUN] Would delete {keywords_count} keywords')
# Summary
self.stdout.write('')
self.stdout.write('=' * 60)
self.stdout.write('Summary:')
self.stdout.write('=' * 60)
if not dry_run:
self.stdout.write(self.style.SUCCESS(f'✅ Migrated {created_count} keywords to SeedKeywords'))
self.stdout.write(self.style.SUCCESS('✅ Deleted all planner and writer module data'))
else:
self.stdout.write(f'[DRY RUN] Would migrate {created_count} keywords to SeedKeywords')
self.stdout.write('[DRY RUN] Would delete all planner and writer module data')
if errors:
self.stdout.write('')
self.stdout.write(self.style.ERROR('Errors encountered:'))
for error in errors[:10]: # Show first 10 errors
self.stdout.write(self.style.ERROR(f' - {error}'))
if len(errors) > 10:
self.stdout.write(self.style.ERROR(f' ... and {len(errors) - 10} more errors'))
if dry_run:
self.stdout.write('')
self.stdout.write(self.style.WARNING('This was a DRY RUN. Run without --dry-run to apply changes.'))