Remove obsolete migration files and update initial migration timestamps for various modules; ensure consistency in migration history across the project.
This commit is contained in:
39
backend/igny8_core/ai/migrations/0001_initial.py
Normal file
39
backend/igny8_core/ai/migrations/0001_initial.py
Normal file
@@ -0,0 +1,39 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-20 23:27
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AITaskLog',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('task_id', models.CharField(blank=True, db_index=True, max_length=255, null=True)),
|
||||
('function_name', models.CharField(db_index=True, max_length=100)),
|
||||
('phase', models.CharField(default='INIT', max_length=50)),
|
||||
('message', models.TextField(blank=True)),
|
||||
('status', models.CharField(choices=[('success', 'Success'), ('error', 'Error'), ('pending', 'Pending')], default='pending', max_length=20)),
|
||||
('duration', models.IntegerField(blank=True, help_text='Duration in milliseconds', null=True)),
|
||||
('cost', models.DecimalField(decimal_places=6, default=0.0, max_digits=10)),
|
||||
('tokens', models.IntegerField(default=0)),
|
||||
('request_steps', models.JSONField(blank=True, default=list)),
|
||||
('response_steps', models.JSONField(blank=True, default=list)),
|
||||
('error', models.TextField(blank=True, null=True)),
|
||||
('payload', models.JSONField(blank=True, null=True)),
|
||||
('result', models.JSONField(blank=True, null=True)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_ai_task_logs',
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
]
|
||||
34
backend/igny8_core/ai/migrations/0002_initial.py
Normal file
34
backend/igny8_core/ai/migrations/0002_initial.py
Normal file
@@ -0,0 +1,34 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-20 23:27
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('ai', '0001_initial'),
|
||||
('igny8_core_auth', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='aitasklog',
|
||||
name='account',
|
||||
field=models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.account'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='aitasklog',
|
||||
index=models.Index(fields=['task_id'], name='igny8_ai_ta_task_id_310356_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='aitasklog',
|
||||
index=models.Index(fields=['function_name', 'account'], name='igny8_ai_ta_functio_0e5a30_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='aitasklog',
|
||||
index=models.Index(fields=['status', 'created_at'], name='igny8_ai_ta_status_ed93b5_idx'),
|
||||
),
|
||||
]
|
||||
@@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.2.7 on 2025-11-02 21:42
|
||||
# Generated by Django 5.2.8 on 2025-11-20 23:27
|
||||
|
||||
import django.contrib.auth.models
|
||||
import django.contrib.auth.validators
|
||||
@@ -25,12 +25,22 @@ class Migration(migrations.Migration):
|
||||
('name', models.CharField(max_length=255)),
|
||||
('slug', models.SlugField(max_length=255, unique=True)),
|
||||
('price', models.DecimalField(decimal_places=2, max_digits=10)),
|
||||
('credits_per_month', models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0)])),
|
||||
('max_sites', models.IntegerField(default=1, help_text='Maximum number of sites allowed (1-10)', validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(10)])),
|
||||
('features', models.JSONField(default=dict, help_text='Plan features as JSON')),
|
||||
('stripe_price_id', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('billing_cycle', models.CharField(choices=[('monthly', 'Monthly'), ('annual', 'Annual')], default='monthly', max_length=20)),
|
||||
('features', models.JSONField(blank=True, default=list, help_text="Plan features as JSON array (e.g., ['ai_writer', 'image_gen', 'auto_publish'])")),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('max_users', models.IntegerField(default=1, help_text='Total users allowed per account', validators=[django.core.validators.MinValueValidator(1)])),
|
||||
('max_sites', models.IntegerField(default=1, help_text='Maximum number of sites allowed', validators=[django.core.validators.MinValueValidator(1)])),
|
||||
('max_industries', models.IntegerField(blank=True, default=None, help_text='Optional limit for industries/sectors', null=True, validators=[django.core.validators.MinValueValidator(1)])),
|
||||
('max_author_profiles', models.IntegerField(default=5, help_text='Limit for saved writing styles', validators=[django.core.validators.MinValueValidator(0)])),
|
||||
('included_credits', models.IntegerField(default=0, help_text='Monthly credits included', validators=[django.core.validators.MinValueValidator(0)])),
|
||||
('extra_credit_price', models.DecimalField(decimal_places=2, default=0.01, help_text='Price per additional credit', max_digits=10)),
|
||||
('allow_credit_topup', models.BooleanField(default=True, help_text='Can user purchase more credits?')),
|
||||
('auto_credit_topup_threshold', models.IntegerField(blank=True, default=None, help_text='Auto top-up trigger point (optional)', null=True, validators=[django.core.validators.MinValueValidator(0)])),
|
||||
('auto_credit_topup_amount', models.IntegerField(blank=True, default=None, help_text='How many credits to auto-buy', null=True, validators=[django.core.validators.MinValueValidator(1)])),
|
||||
('stripe_product_id', models.CharField(blank=True, help_text='For Stripe plan sync', max_length=255, null=True)),
|
||||
('stripe_price_id', models.CharField(blank=True, help_text='Monthly price ID for Stripe', max_length=255, null=True)),
|
||||
('credits_per_month', models.IntegerField(default=0, help_text='DEPRECATED: Use included_credits instead', validators=[django.core.validators.MinValueValidator(0)])),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_plans',
|
||||
@@ -50,7 +60,7 @@ class Migration(migrations.Migration):
|
||||
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
|
||||
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
||||
('role', models.CharField(choices=[('owner', 'Owner'), ('admin', 'Admin'), ('editor', 'Editor'), ('viewer', 'Viewer'), ('system_bot', 'System Bot')], default='viewer', max_length=20)),
|
||||
('role', models.CharField(choices=[('developer', 'Developer / Super Admin'), ('owner', 'Owner'), ('admin', 'Admin'), ('editor', 'Editor'), ('viewer', 'Viewer'), ('system_bot', 'System Bot')], default='viewer', max_length=20)),
|
||||
('email', models.EmailField(max_length=254, unique=True, verbose_name='email address')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
@@ -65,7 +75,7 @@ class Migration(migrations.Migration):
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Tenant',
|
||||
name='Account',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
@@ -75,28 +85,93 @@ class Migration(migrations.Migration):
|
||||
('status', models.CharField(choices=[('active', 'Active'), ('suspended', 'Suspended'), ('trial', 'Trial'), ('cancelled', 'Cancelled')], default='trial', max_length=20)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('owner', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='owned_tenants', to=settings.AUTH_USER_MODEL)),
|
||||
('plan', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='tenants', to='igny8_core_auth.plan')),
|
||||
('owner', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='owned_accounts', to=settings.AUTH_USER_MODEL)),
|
||||
('plan', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='accounts', to='igny8_core_auth.plan')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Account',
|
||||
'verbose_name_plural': 'Accounts',
|
||||
'db_table': 'igny8_tenants',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='account',
|
||||
field=models.ForeignKey(blank=True, db_column='tenant_id', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='users', to='igny8_core_auth.account'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Subscription',
|
||||
name='Industry',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('stripe_subscription_id', models.CharField(max_length=255, unique=True)),
|
||||
('status', models.CharField(choices=[('active', 'Active'), ('past_due', 'Past Due'), ('canceled', 'Canceled'), ('trialing', 'Trialing')], max_length=20)),
|
||||
('current_period_start', models.DateTimeField()),
|
||||
('current_period_end', models.DateTimeField()),
|
||||
('cancel_at_period_end', models.BooleanField(default=False)),
|
||||
('name', models.CharField(max_length=255, unique=True)),
|
||||
('slug', models.SlugField(max_length=255, unique=True)),
|
||||
('description', models.TextField(blank=True, null=True)),
|
||||
('is_active', models.BooleanField(db_index=True, default=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('tenant', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='subscription', to='igny8_core_auth.tenant')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_subscriptions',
|
||||
'verbose_name': 'Industry',
|
||||
'verbose_name_plural': 'Industries',
|
||||
'db_table': 'igny8_industries',
|
||||
'ordering': ['name'],
|
||||
'indexes': [models.Index(fields=['slug'], name='igny8_indus_slug_2f8769_idx'), models.Index(fields=['is_active'], name='igny8_indus_is_acti_146d41_idx')],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='IndustrySector',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('slug', models.SlugField(max_length=255)),
|
||||
('description', models.TextField(blank=True, null=True)),
|
||||
('suggested_keywords', models.JSONField(default=list, help_text='List of suggested keywords for this sector template')),
|
||||
('is_active', models.BooleanField(db_index=True, default=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('industry', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sectors', to='igny8_core_auth.industry')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Industry Sector',
|
||||
'verbose_name_plural': 'Industry Sectors',
|
||||
'db_table': 'igny8_industry_sectors',
|
||||
'ordering': ['industry', 'name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PasswordResetToken',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('token', models.CharField(db_index=True, max_length=255, unique=True)),
|
||||
('expires_at', models.DateTimeField()),
|
||||
('used', models.BooleanField(default=False)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='password_reset_tokens', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_password_reset_tokens',
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SeedKeyword',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('keyword', models.CharField(db_index=True, max_length=255)),
|
||||
('volume', models.IntegerField(default=0, help_text='Search volume estimate')),
|
||||
('difficulty', models.IntegerField(default=0, help_text='Keyword difficulty (0-100)', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100)])),
|
||||
('intent', models.CharField(choices=[('informational', 'Informational'), ('navigational', 'Navigational'), ('commercial', 'Commercial'), ('transactional', 'Transactional')], default='informational', max_length=50)),
|
||||
('is_active', models.BooleanField(db_index=True, default=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('industry', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='seed_keywords', to='igny8_core_auth.industry')),
|
||||
('sector', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='seed_keywords', to='igny8_core_auth.industrysector')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Seed Keyword',
|
||||
'verbose_name_plural': 'Seed Keywords',
|
||||
'db_table': 'igny8_seed_keywords',
|
||||
'ordering': ['keyword'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
@@ -111,13 +186,18 @@ class Migration(migrations.Migration):
|
||||
('status', models.CharField(choices=[('active', 'Active'), ('inactive', 'Inactive'), ('suspended', 'Suspended')], default='active', max_length=20)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('wp_url', models.URLField(blank=True, help_text='WordPress site URL', null=True)),
|
||||
('wp_url', models.URLField(blank=True, help_text='WordPress site URL (legacy - use SiteIntegration)', null=True)),
|
||||
('wp_username', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('wp_app_password', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('tenant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.tenant')),
|
||||
('site_type', models.CharField(choices=[('marketing', 'Marketing Site'), ('ecommerce', 'Ecommerce Site'), ('blog', 'Blog'), ('portfolio', 'Portfolio'), ('corporate', 'Corporate')], db_index=True, default='marketing', help_text='Type of site', max_length=50)),
|
||||
('hosting_type', models.CharField(choices=[('igny8_sites', 'IGNY8 Sites'), ('wordpress', 'WordPress'), ('shopify', 'Shopify'), ('multi', 'Multi-Destination')], db_index=True, default='igny8_sites', help_text='Target hosting platform', max_length=50)),
|
||||
('seo_metadata', models.JSONField(blank=True, default=dict, help_text='SEO metadata: meta tags, Open Graph, Schema.org')),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.account')),
|
||||
('industry', models.ForeignKey(blank=True, help_text='Industry this site belongs to', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='sites', to='igny8_core_auth.industry')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_sites',
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
@@ -131,18 +211,14 @@ class Migration(migrations.Migration):
|
||||
('status', models.CharField(choices=[('active', 'Active'), ('inactive', 'Inactive')], default='active', max_length=20)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.account')),
|
||||
('industry_sector', models.ForeignKey(blank=True, help_text='Reference to the industry sector template', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='site_sectors', to='igny8_core_auth.industrysector')),
|
||||
('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sectors', to='igny8_core_auth.site')),
|
||||
('tenant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.tenant')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_sectors',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='user',
|
||||
name='tenant',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='users', to='igny8_core_auth.tenant'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SiteUserAccess',
|
||||
fields=[
|
||||
@@ -153,34 +229,111 @@ class Migration(migrations.Migration):
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='site_access', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Site User Access',
|
||||
'verbose_name_plural': 'Site User Access',
|
||||
'db_table': 'igny8_site_user_access',
|
||||
'indexes': [models.Index(fields=['user', 'site'], name='igny8_site__user_id_61951e_idx')],
|
||||
'unique_together': {('user', 'site')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Subscription',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('stripe_subscription_id', models.CharField(max_length=255, unique=True)),
|
||||
('status', models.CharField(choices=[('active', 'Active'), ('past_due', 'Past Due'), ('canceled', 'Canceled'), ('trialing', 'Trialing')], max_length=20)),
|
||||
('current_period_start', models.DateTimeField()),
|
||||
('current_period_end', models.DateTimeField()),
|
||||
('cancel_at_period_end', models.BooleanField(default=False)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('account', models.OneToOneField(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='subscription', to='igny8_core_auth.account')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_subscriptions',
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='tenant',
|
||||
model_name='user',
|
||||
index=models.Index(fields=['account', 'role'], name='igny8_users_tenant__0ab02b_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='user',
|
||||
index=models.Index(fields=['email'], name='igny8_users_email_fd61ff_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='industrysector',
|
||||
index=models.Index(fields=['industry', 'is_active'], name='igny8_indus_industr_00b524_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='industrysector',
|
||||
index=models.Index(fields=['slug'], name='igny8_indus_slug_101d63_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='industrysector',
|
||||
unique_together={('industry', 'slug')},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='passwordresettoken',
|
||||
index=models.Index(fields=['token'], name='igny8_passw_token_0eaf0c_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='passwordresettoken',
|
||||
index=models.Index(fields=['user', 'used'], name='igny8_passw_user_id_320c02_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='passwordresettoken',
|
||||
index=models.Index(fields=['expires_at'], name='igny8_passw_expires_c9aa03_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='account',
|
||||
index=models.Index(fields=['slug'], name='igny8_tenan_slug_f25e97_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='tenant',
|
||||
model_name='account',
|
||||
index=models.Index(fields=['status'], name='igny8_tenan_status_5dc02a_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='subscription',
|
||||
index=models.Index(fields=['status'], name='igny8_subsc_status_2fa897_idx'),
|
||||
model_name='seedkeyword',
|
||||
index=models.Index(fields=['keyword'], name='igny8_seed__keyword_efa089_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='seedkeyword',
|
||||
index=models.Index(fields=['industry', 'sector'], name='igny8_seed__industr_c41841_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='seedkeyword',
|
||||
index=models.Index(fields=['industry', 'sector', 'is_active'], name='igny8_seed__industr_da0030_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='seedkeyword',
|
||||
index=models.Index(fields=['intent'], name='igny8_seed__intent_15020d_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='seedkeyword',
|
||||
unique_together={('keyword', 'industry', 'sector')},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='site',
|
||||
index=models.Index(fields=['tenant', 'is_active'], name='igny8_sites_tenant__e0f31d_idx'),
|
||||
index=models.Index(fields=['account', 'is_active'], name='igny8_sites_tenant__e0f31d_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='site',
|
||||
index=models.Index(fields=['tenant', 'status'], name='igny8_sites_tenant__a20275_idx'),
|
||||
index=models.Index(fields=['account', 'status'], name='igny8_sites_tenant__a20275_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='site',
|
||||
index=models.Index(fields=['industry'], name='igny8_sites_industr_66e004_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='site',
|
||||
index=models.Index(fields=['site_type'], name='igny8_sites_site_ty_0dfbc3_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='site',
|
||||
index=models.Index(fields=['hosting_type'], name='igny8_sites_hosting_c484c2_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='site',
|
||||
unique_together={('tenant', 'slug')},
|
||||
unique_together={('account', 'slug')},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='sector',
|
||||
@@ -188,18 +341,26 @@ class Migration(migrations.Migration):
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='sector',
|
||||
index=models.Index(fields=['tenant', 'site'], name='igny8_secto_tenant__af54ae_idx'),
|
||||
index=models.Index(fields=['account', 'site'], name='igny8_secto_tenant__af54ae_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='sector',
|
||||
index=models.Index(fields=['industry_sector'], name='igny8_secto_industr_1cf990_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='sector',
|
||||
unique_together={('site', 'slug')},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='user',
|
||||
index=models.Index(fields=['tenant', 'role'], name='igny8_users_tenant__0ab02b_idx'),
|
||||
model_name='siteuseraccess',
|
||||
index=models.Index(fields=['user', 'site'], name='igny8_site__user_id_61951e_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='siteuseraccess',
|
||||
unique_together={('user', 'site')},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='user',
|
||||
index=models.Index(fields=['email'], name='igny8_users_email_fd61ff_idx'),
|
||||
model_name='subscription',
|
||||
index=models.Index(fields=['status'], name='igny8_subsc_status_2fa897_idx'),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
# Generated by Django 5.2.7 on 2025-11-02 22:27
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('igny8_core_auth', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
||||
@@ -1,18 +0,0 @@
|
||||
# Generated by Django 5.2.7 on 2025-11-03 13:22
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('igny8_core_auth', '0002_add_developer_role'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='role',
|
||||
field=models.CharField(choices=[('developer', 'Developer / Super Admin'), ('owner', 'Owner'), ('admin', 'Admin'), ('editor', 'Editor'), ('viewer', 'Viewer'), ('system_bot', 'System Bot')], default='viewer', max_length=20),
|
||||
),
|
||||
]
|
||||
@@ -1,75 +0,0 @@
|
||||
# Generated migration for Industry and IndustrySector models
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('igny8_core_auth', '0003_alter_user_role'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Industry',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255, unique=True)),
|
||||
('slug', models.SlugField(db_index=True, max_length=255, unique=True)),
|
||||
('description', models.TextField(blank=True, null=True)),
|
||||
('is_active', models.BooleanField(db_index=True, default=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_industries',
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='IndustrySector',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('slug', models.SlugField(db_index=True, max_length=255)),
|
||||
('description', models.TextField(blank=True, null=True)),
|
||||
('suggested_keywords', models.JSONField(default=list, help_text='List of suggested keywords for this sector template')),
|
||||
('is_active', models.BooleanField(db_index=True, default=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('industry', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sectors', to='igny8_core_auth.industry')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_industry_sectors',
|
||||
'ordering': ['industry', 'name'],
|
||||
'unique_together': {('industry', 'slug')},
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='sector',
|
||||
name='industry_sector',
|
||||
field=models.ForeignKey(blank=True, help_text='Reference to the industry sector template', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='site_sectors', to='igny8_core_auth.industrysector'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='industry',
|
||||
index=models.Index(fields=['slug'], name='igny8_indu_slug_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='industry',
|
||||
index=models.Index(fields=['is_active'], name='igny8_indu_is_acti_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='industrysector',
|
||||
index=models.Index(fields=['industry', 'is_active'], name='igny8_indu_industr_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='industrysector',
|
||||
index=models.Index(fields=['slug'], name='igny8_indu_slug_1_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='sector',
|
||||
index=models.Index(fields=['industry_sector'], name='igny8_sect_industr_idx'),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
# Migration to add industry field to Site model
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('igny8_core_auth', '0004_add_industry_models'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='site',
|
||||
name='industry',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text='Industry this site belongs to',
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name='sites',
|
||||
to='igny8_core_auth.industry'
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='site',
|
||||
index=models.Index(fields=['industry'], name='igny8_site_industr_idx'),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
"""Add extended plan configuration fields"""
|
||||
from decimal import Decimal
|
||||
|
||||
from django.core.validators import MinValueValidator
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('igny8_core_auth', '0006_add_industry_to_site'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='plan',
|
||||
name='ai_cost_per_request',
|
||||
field=models.JSONField(default=dict, help_text="Cost per request type (e.g., {'cluster': 2, 'idea': 3, 'content': 5, 'image': 1})"),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='plan',
|
||||
name='allow_credit_topup',
|
||||
field=models.BooleanField(default=True, help_text='Can user purchase more credits?'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='plan',
|
||||
name='billing_cycle',
|
||||
field=models.CharField(choices=[('monthly', 'Monthly'), ('annual', 'Annual')], default='monthly', max_length=20),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='plan',
|
||||
name='daily_ai_request_limit',
|
||||
field=models.IntegerField(default=100, help_text='Global daily AI request cap', validators=[MinValueValidator(0)]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='plan',
|
||||
name='daily_ai_requests',
|
||||
field=models.IntegerField(default=50, help_text='Total AI executions (content + idea + image) allowed per day', validators=[MinValueValidator(0)]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='plan',
|
||||
name='daily_cluster_limit',
|
||||
field=models.IntegerField(default=10, help_text='Max clusters that can be created per day', validators=[MinValueValidator(0)]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='plan',
|
||||
name='daily_content_tasks',
|
||||
field=models.IntegerField(default=10, help_text='Max number of content tasks (blogs) per day', validators=[MinValueValidator(0)]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='plan',
|
||||
name='daily_keyword_import_limit',
|
||||
field=models.IntegerField(default=100, help_text='SeedKeywords import limit per day', validators=[MinValueValidator(0)]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='plan',
|
||||
name='extra_credit_price',
|
||||
field=models.DecimalField(decimal_places=2, default=Decimal('0.01'), help_text='Price per additional credit', max_digits=10),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='plan',
|
||||
name='image_model_choices',
|
||||
field=models.JSONField(default=list, help_text="Allowed image models (e.g., ['dalle3', 'hidream'])"),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='plan',
|
||||
name='included_credits',
|
||||
field=models.IntegerField(default=0, help_text='Monthly credits included', validators=[MinValueValidator(0)]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='plan',
|
||||
name='max_author_profiles',
|
||||
field=models.IntegerField(default=5, help_text='Limit for saved writing styles', validators=[MinValueValidator(0)]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='plan',
|
||||
name='max_clusters',
|
||||
field=models.IntegerField(default=100, help_text='Total clusters allowed (global)', validators=[MinValueValidator(0)]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='plan',
|
||||
name='max_images_per_task',
|
||||
field=models.IntegerField(default=4, help_text='Max images per content task', validators=[MinValueValidator(1)]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='plan',
|
||||
name='max_industries',
|
||||
field=models.IntegerField(blank=True, default=None, help_text='Optional limit for industries/sectors', null=True, validators=[MinValueValidator(1)]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='plan',
|
||||
name='max_keywords',
|
||||
field=models.IntegerField(default=1000, help_text='Total keywords allowed (global limit)', validators=[MinValueValidator(0)]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='plan',
|
||||
name='max_users',
|
||||
field=models.IntegerField(default=1, help_text='Total users allowed per account', validators=[MinValueValidator(1)]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='plan',
|
||||
name='monthly_ai_credit_limit',
|
||||
field=models.IntegerField(default=500, help_text='Unified credit ceiling per month (all AI functions)', validators=[MinValueValidator(0)]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='plan',
|
||||
name='monthly_cluster_ai_credits',
|
||||
field=models.IntegerField(default=50, help_text='AI credits allocated for clustering', validators=[MinValueValidator(0)]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='plan',
|
||||
name='monthly_content_ai_credits',
|
||||
field=models.IntegerField(default=200, help_text='AI credit pool for content generation', validators=[MinValueValidator(0)]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='plan',
|
||||
name='monthly_image_ai_credits',
|
||||
field=models.IntegerField(default=100, help_text='AI credit pool for image generation', validators=[MinValueValidator(0)]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='plan',
|
||||
name='monthly_image_count',
|
||||
field=models.IntegerField(default=100, help_text='Max images per month', validators=[MinValueValidator(0)]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='plan',
|
||||
name='monthly_word_count_limit',
|
||||
field=models.IntegerField(default=50000, help_text='Monthly word limit (for generated content)', validators=[MinValueValidator(0)]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='plan',
|
||||
name='auto_credit_topup_threshold',
|
||||
field=models.IntegerField(blank=True, default=None, help_text='Auto top-up trigger point (optional)', null=True, validators=[MinValueValidator(0)]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='plan',
|
||||
name='auto_credit_topup_amount',
|
||||
field=models.IntegerField(blank=True, default=None, help_text='How many credits to auto-buy', null=True, validators=[MinValueValidator(1)]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='plan',
|
||||
name='stripe_product_id',
|
||||
field=models.CharField(blank=True, help_text='For Stripe plan sync', max_length=255, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='plan',
|
||||
name='features',
|
||||
field=models.JSONField(default=list, help_text="Plan features as JSON array (e.g., ['ai_writer', 'image_gen', 'auto_publish'])"),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-07 10:06
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('igny8_core_auth', '0007_expand_plan_limits'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='PasswordResetToken',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('token', models.CharField(db_index=True, max_length=255, unique=True)),
|
||||
('expires_at', models.DateTimeField()),
|
||||
('used', models.BooleanField(default=False)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_password_reset_tokens',
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='industry',
|
||||
options={'ordering': ['name'], 'verbose_name': 'Industry', 'verbose_name_plural': 'Industries'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='industrysector',
|
||||
options={'ordering': ['industry', 'name'], 'verbose_name': 'Industry Sector', 'verbose_name_plural': 'Industry Sectors'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='site',
|
||||
options={'ordering': ['-created_at']},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='siteuseraccess',
|
||||
options={'verbose_name': 'Site User Access', 'verbose_name_plural': 'Site User Access'},
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name='industry',
|
||||
new_name='igny8_indus_slug_2f8769_idx',
|
||||
old_name='igny8_indu_slug_idx',
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name='industry',
|
||||
new_name='igny8_indus_is_acti_146d41_idx',
|
||||
old_name='igny8_indu_is_acti_idx',
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name='industrysector',
|
||||
new_name='igny8_indus_industr_00b524_idx',
|
||||
old_name='igny8_indu_industr_idx',
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name='industrysector',
|
||||
new_name='igny8_indus_slug_101d63_idx',
|
||||
old_name='igny8_indu_slug_1_idx',
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name='sector',
|
||||
new_name='igny8_secto_industr_1cf990_idx',
|
||||
old_name='igny8_sect_industr_idx',
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name='site',
|
||||
new_name='igny8_sites_industr_66e004_idx',
|
||||
old_name='igny8_site_industr_idx',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='plan',
|
||||
name='credits_per_month',
|
||||
field=models.IntegerField(default=0, help_text='DEPRECATED: Use included_credits instead', validators=[django.core.validators.MinValueValidator(0)]),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='plan',
|
||||
name='extra_credit_price',
|
||||
field=models.DecimalField(decimal_places=2, default=0.01, help_text='Price per additional credit', max_digits=10),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='plan',
|
||||
name='stripe_price_id',
|
||||
field=models.CharField(blank=True, help_text='Monthly price ID for Stripe', max_length=255, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='passwordresettoken',
|
||||
name='user',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='password_reset_tokens', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='passwordresettoken',
|
||||
index=models.Index(fields=['token'], name='igny8_passw_token_0eaf0c_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='passwordresettoken',
|
||||
index=models.Index(fields=['user', 'used'], name='igny8_passw_user_id_320c02_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='passwordresettoken',
|
||||
index=models.Index(fields=['expires_at'], name='igny8_passw_expires_c9aa03_idx'),
|
||||
),
|
||||
]
|
||||
@@ -1,88 +0,0 @@
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def forward_fix_admin_log_fk(apps, schema_editor):
|
||||
if schema_editor.connection.vendor != "postgresql":
|
||||
return
|
||||
schema_editor.execute(
|
||||
"""
|
||||
ALTER TABLE django_admin_log
|
||||
DROP CONSTRAINT IF EXISTS django_admin_log_user_id_c564eba6_fk_auth_user_id;
|
||||
"""
|
||||
)
|
||||
schema_editor.execute(
|
||||
"""
|
||||
UPDATE django_admin_log
|
||||
SET user_id = sub.new_user_id
|
||||
FROM (
|
||||
SELECT id AS new_user_id
|
||||
FROM igny8_users
|
||||
ORDER BY id
|
||||
LIMIT 1
|
||||
) AS sub
|
||||
WHERE django_admin_log.user_id NOT IN (
|
||||
SELECT id FROM igny8_users
|
||||
);
|
||||
"""
|
||||
)
|
||||
schema_editor.execute(
|
||||
"""
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint
|
||||
WHERE conname = 'django_admin_log_user_id_c564eba6_fk_igny8_users_id'
|
||||
) THEN
|
||||
ALTER TABLE django_admin_log
|
||||
ADD CONSTRAINT django_admin_log_user_id_c564eba6_fk_igny8_users_id
|
||||
FOREIGN KEY (user_id) REFERENCES igny8_users(id) DEFERRABLE INITIALLY DEFERRED;
|
||||
END IF;
|
||||
END $$;
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def reverse_fix_admin_log_fk(apps, schema_editor):
|
||||
if schema_editor.connection.vendor != "postgresql":
|
||||
return
|
||||
schema_editor.execute(
|
||||
"""
|
||||
ALTER TABLE django_admin_log
|
||||
DROP CONSTRAINT IF EXISTS django_admin_log_user_id_c564eba6_fk_igny8_users_id;
|
||||
"""
|
||||
)
|
||||
schema_editor.execute(
|
||||
"""
|
||||
UPDATE django_admin_log
|
||||
SET user_id = sub.old_user_id
|
||||
FROM (
|
||||
SELECT id AS old_user_id
|
||||
FROM auth_user
|
||||
ORDER BY id
|
||||
LIMIT 1
|
||||
) AS sub
|
||||
WHERE django_admin_log.user_id NOT IN (
|
||||
SELECT id FROM auth_user
|
||||
);
|
||||
"""
|
||||
)
|
||||
schema_editor.execute(
|
||||
"""
|
||||
ALTER TABLE django_admin_log
|
||||
ADD CONSTRAINT django_admin_log_user_id_c564eba6_fk_auth_user_id
|
||||
FOREIGN KEY (user_id) REFERENCES auth_user(id) DEFERRABLE INITIALLY DEFERRED;
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("igny8_core_auth", "0008_passwordresettoken_alter_industry_options_and_more"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(forward_fix_admin_log_fk, reverse_fix_admin_log_fk),
|
||||
]
|
||||
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-07 11:34
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('igny8_core_auth', '0009_fix_admin_log_user_fk'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SeedKeyword',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('keyword', models.CharField(db_index=True, max_length=255)),
|
||||
('volume', models.IntegerField(default=0, help_text='Search volume estimate')),
|
||||
('difficulty', models.IntegerField(default=0, help_text='Keyword difficulty (0-100)', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100)])),
|
||||
('intent', models.CharField(choices=[('informational', 'Informational'), ('navigational', 'Navigational'), ('commercial', 'Commercial'), ('transactional', 'Transactional')], default='informational', max_length=50)),
|
||||
('is_active', models.BooleanField(db_index=True, default=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('industry', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='seed_keywords', to='igny8_core_auth.industry')),
|
||||
('sector', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='seed_keywords', to='igny8_core_auth.industrysector')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Seed Keyword',
|
||||
'verbose_name_plural': 'Seed Keywords',
|
||||
'db_table': 'igny8_seed_keywords',
|
||||
'ordering': ['keyword'],
|
||||
'indexes': [models.Index(fields=['keyword'], name='igny8_seed__keyword_efa089_idx'), models.Index(fields=['industry', 'sector'], name='igny8_seed__industr_c41841_idx'), models.Index(fields=['industry', 'sector', 'is_active'], name='igny8_seed__industr_da0030_idx'), models.Index(fields=['intent'], name='igny8_seed__intent_15020d_idx')],
|
||||
'unique_together': {('keyword', 'industry', 'sector')},
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -1,29 +0,0 @@
|
||||
# Generated by Django 5.2.7 on 2025-11-07 11:45
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('igny8_core_auth', '0010_add_seed_keyword'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='plan',
|
||||
name='daily_image_generation_limit',
|
||||
field=models.IntegerField(default=25, help_text='Max images that can be generated per day', validators=[django.core.validators.MinValueValidator(0)]),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='plan',
|
||||
name='max_content_ideas',
|
||||
field=models.IntegerField(default=300, help_text='Total content ideas allowed (global limit)', validators=[django.core.validators.MinValueValidator(0)]),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='plan',
|
||||
name='max_sites',
|
||||
field=models.IntegerField(default=1, help_text='Maximum number of sites allowed', validators=[django.core.validators.MinValueValidator(1)]),
|
||||
),
|
||||
]
|
||||
@@ -1,28 +0,0 @@
|
||||
# Generated by Django 5.2.7 on 2025-11-07 11:56
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('igny8_core_auth', '0011_add_plan_fields_and_fix_constraints'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='plan',
|
||||
name='ai_cost_per_request',
|
||||
field=models.JSONField(blank=True, default=dict, help_text="Cost per request type (e.g., {'cluster': 2, 'idea': 3, 'content': 5, 'image': 1})"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='plan',
|
||||
name='features',
|
||||
field=models.JSONField(blank=True, default=list, help_text="Plan features as JSON array (e.g., ['ai_writer', 'image_gen', 'auto_publish'])"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='plan',
|
||||
name='image_model_choices',
|
||||
field=models.JSONField(blank=True, default=list, help_text="Allowed image models (e.g., ['dalle3', 'hidream'])"),
|
||||
),
|
||||
]
|
||||
@@ -1,17 +0,0 @@
|
||||
# Generated by Django 5.2.7 on 2025-11-07 12:01
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('igny8_core_auth', '0012_allow_blank_json_fields'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='plan',
|
||||
name='ai_cost_per_request',
|
||||
),
|
||||
]
|
||||
@@ -1,86 +0,0 @@
|
||||
# Generated manually for Phase 0: Remove plan operation limit fields (credit-only system)
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('igny8_core_auth', '0013_remove_ai_cost_per_request'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
# Remove Planner Limits
|
||||
migrations.RemoveField(
|
||||
model_name='plan',
|
||||
name='max_keywords',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='plan',
|
||||
name='max_clusters',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='plan',
|
||||
name='max_content_ideas',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='plan',
|
||||
name='daily_cluster_limit',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='plan',
|
||||
name='daily_keyword_import_limit',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='plan',
|
||||
name='monthly_cluster_ai_credits',
|
||||
),
|
||||
# Remove Writer Limits
|
||||
migrations.RemoveField(
|
||||
model_name='plan',
|
||||
name='daily_content_tasks',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='plan',
|
||||
name='daily_ai_requests',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='plan',
|
||||
name='monthly_word_count_limit',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='plan',
|
||||
name='monthly_content_ai_credits',
|
||||
),
|
||||
# Remove Image Generation Limits
|
||||
migrations.RemoveField(
|
||||
model_name='plan',
|
||||
name='monthly_image_count',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='plan',
|
||||
name='daily_image_generation_limit',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='plan',
|
||||
name='monthly_image_ai_credits',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='plan',
|
||||
name='max_images_per_task',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='plan',
|
||||
name='image_model_choices',
|
||||
),
|
||||
# Remove AI Request Controls
|
||||
migrations.RemoveField(
|
||||
model_name='plan',
|
||||
name='daily_ai_request_limit',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='plan',
|
||||
name='monthly_ai_credit_limit',
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
# Generated manually for Phase 6: Site Model Extensions
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('igny8_core_auth', '0014_remove_plan_operation_limits_phase0'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='site',
|
||||
name='site_type',
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
('marketing', 'Marketing Site'),
|
||||
('ecommerce', 'Ecommerce Site'),
|
||||
('blog', 'Blog'),
|
||||
('portfolio', 'Portfolio'),
|
||||
('corporate', 'Corporate'),
|
||||
],
|
||||
db_index=True,
|
||||
default='marketing',
|
||||
help_text='Type of site',
|
||||
max_length=50
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='site',
|
||||
name='hosting_type',
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
('igny8_sites', 'IGNY8 Sites'),
|
||||
('wordpress', 'WordPress'),
|
||||
('shopify', 'Shopify'),
|
||||
('multi', 'Multi-Destination'),
|
||||
],
|
||||
db_index=True,
|
||||
default='igny8_sites',
|
||||
help_text='Target hosting platform',
|
||||
max_length=50
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='site',
|
||||
index=models.Index(fields=['site_type'], name='igny8_sites_site_ty_123abc_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='site',
|
||||
index=models.Index(fields=['hosting_type'], name='igny8_sites_hostin_456def_idx'),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
# Generated manually for Phase 7: Site SEO Metadata
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('igny8_core_auth', '0015_add_site_type_hosting_type'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='site',
|
||||
name='seo_metadata',
|
||||
field=models.JSONField(
|
||||
default=dict,
|
||||
blank=True,
|
||||
help_text='SEO metadata: meta tags, Open Graph, Schema.org'
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
# Generated manually for Phase 2: Automation System
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('igny8_core_auth', '0008_passwordresettoken_alter_industry_options_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AutomationRule',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('name', models.CharField(help_text='Rule name', max_length=255)),
|
||||
('description', models.TextField(blank=True, help_text='Rule description', null=True)),
|
||||
('trigger', models.CharField(choices=[('schedule', 'Schedule'), ('event', 'Event'), ('manual', 'Manual')], default='manual', max_length=50)),
|
||||
('schedule', models.CharField(blank=True, help_text="Cron-like schedule string (e.g., '0 0 * * *' for daily at midnight)", max_length=100, null=True)),
|
||||
('conditions', models.JSONField(default=list, help_text='List of conditions that must be met for rule to execute')),
|
||||
('actions', models.JSONField(default=list, help_text='List of actions to execute when rule triggers')),
|
||||
('is_active', models.BooleanField(default=True, help_text='Whether rule is active')),
|
||||
('status', models.CharField(choices=[('active', 'Active'), ('inactive', 'Inactive'), ('paused', 'Paused')], default='active', max_length=50)),
|
||||
('last_executed_at', models.DateTimeField(blank=True, null=True)),
|
||||
('execution_count', models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0)])),
|
||||
('metadata', models.JSONField(default=dict, help_text='Additional metadata')),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.account')),
|
||||
('site', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='igny8_core_auth.site')),
|
||||
('sector', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='igny8_core_auth.sector')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_automation_rules',
|
||||
'ordering': ['-created_at'],
|
||||
'verbose_name': 'Automation Rule',
|
||||
'verbose_name_plural': 'Automation Rules',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ScheduledTask',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('scheduled_at', models.DateTimeField(help_text='When the task is scheduled to run')),
|
||||
('executed_at', models.DateTimeField(blank=True, help_text='When the task was actually executed', null=True)),
|
||||
('status', models.CharField(choices=[('pending', 'Pending'), ('running', 'Running'), ('completed', 'Completed'), ('failed', 'Failed'), ('cancelled', 'Cancelled')], default='pending', max_length=50)),
|
||||
('result', models.JSONField(default=dict, help_text='Execution result data')),
|
||||
('error_message', models.TextField(blank=True, help_text='Error message if execution failed', null=True)),
|
||||
('metadata', models.JSONField(default=dict, help_text='Additional metadata')),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.account')),
|
||||
('automation_rule', models.ForeignKey(help_text='The automation rule this task belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='scheduled_tasks', to='automation.automationrule')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_scheduled_tasks',
|
||||
'ordering': ['-scheduled_at'],
|
||||
'verbose_name': 'Scheduled Task',
|
||||
'verbose_name_plural': 'Scheduled Tasks',
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='automationrule',
|
||||
index=models.Index(fields=['trigger', 'is_active'], name='igny8_autom_trigger_123abc_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='automationrule',
|
||||
index=models.Index(fields=['status'], name='igny8_autom_status_456def_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='automationrule',
|
||||
index=models.Index(fields=['site', 'sector'], name='igny8_autom_site_id_789ghi_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='automationrule',
|
||||
index=models.Index(fields=['trigger', 'is_active', 'status'], name='igny8_autom_trigger_0abjkl_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='scheduledtask',
|
||||
index=models.Index(fields=['automation_rule', 'status'], name='igny8_sched_automation_123abc_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='scheduledtask',
|
||||
index=models.Index(fields=['scheduled_at', 'status'], name='igny8_sched_scheduled_456def_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='scheduledtask',
|
||||
index=models.Index(fields=['account', 'status'], name='igny8_sched_account_789ghi_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='scheduledtask',
|
||||
index=models.Index(fields=['status', 'scheduled_at'], name='igny8_sched_status_0abjkl_idx'),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Generated manually for Phase 6: Integration System
|
||||
# Generated by Django 5.2.8 on 2025-11-20 23:27
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
@@ -9,7 +9,7 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('igny8_core_auth', '0014_remove_plan_operation_limits_phase0'),
|
||||
('igny8_core_auth', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
@@ -17,8 +17,6 @@ class Migration(migrations.Migration):
|
||||
name='SiteIntegration',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('platform', models.CharField(choices=[('wordpress', 'WordPress'), ('shopify', 'Shopify'), ('custom', 'Custom API')], db_index=True, help_text="Platform name: 'wordpress', 'shopify', 'custom'", max_length=50)),
|
||||
('platform_type', models.CharField(choices=[('cms', 'CMS'), ('ecommerce', 'Ecommerce'), ('custom_api', 'Custom API')], default='cms', help_text="Platform type: 'cms', 'ecommerce', 'custom_api'", max_length=50)),
|
||||
('config_json', models.JSONField(default=dict, help_text='Platform-specific configuration (URLs, endpoints, etc.)')),
|
||||
@@ -28,33 +26,16 @@ class Migration(migrations.Migration):
|
||||
('last_sync_at', models.DateTimeField(blank=True, help_text='Last successful sync timestamp', null=True)),
|
||||
('sync_status', models.CharField(choices=[('success', 'Success'), ('failed', 'Failed'), ('pending', 'Pending'), ('syncing', 'Syncing')], db_index=True, default='pending', help_text='Current sync status', max_length=20)),
|
||||
('sync_error', models.TextField(blank=True, help_text='Last sync error message', null=True)),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.tenant')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.account')),
|
||||
('site', models.ForeignKey(help_text='Site this integration belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='integrations', to='igny8_core_auth.site')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_site_integrations',
|
||||
'ordering': ['-created_at'],
|
||||
'indexes': [models.Index(fields=['site', 'platform'], name='igny8_site__site_id_3901ba_idx'), models.Index(fields=['site', 'is_active'], name='igny8_site__site_id_71bc1a_idx'), models.Index(fields=['account', 'platform'], name='igny8_site__tenant__920542_idx'), models.Index(fields=['sync_status'], name='igny8_site__sync_st_e79021_idx')],
|
||||
'unique_together': {('site', 'platform')},
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='siteintegration',
|
||||
index=models.Index(fields=['site', 'platform'], name='igny8_integ_site_pl_123abc_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='siteintegration',
|
||||
index=models.Index(fields=['site', 'is_active'], name='igny8_integ_site_is_456def_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='siteintegration',
|
||||
index=models.Index(fields=['account', 'platform'], name='igny8_integ_account_789ghi_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='siteintegration',
|
||||
index=models.Index(fields=['sync_status'], name='igny8_integ_sync_st_012jkl_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='siteintegration',
|
||||
unique_together={('site', 'platform')},
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Generated manually for Phase 4: Optimization System
|
||||
# Generated by Django 5.2.8 on 2025-11-20 23:27
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
@@ -10,8 +10,7 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('igny8_core_auth', '0013_remove_ai_cost_per_request'),
|
||||
('writer', '0009_add_content_site_source_fields'),
|
||||
('igny8_core_auth', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
@@ -19,8 +18,6 @@ class Migration(migrations.Migration):
|
||||
name='OptimizationTask',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('scores_before', models.JSONField(default=dict, help_text='Optimization scores before')),
|
||||
('scores_after', models.JSONField(default=dict, help_text='Optimization scores after')),
|
||||
('html_before', models.TextField(blank=True, help_text='HTML content before optimization')),
|
||||
@@ -28,27 +25,15 @@ class Migration(migrations.Migration):
|
||||
('status', models.CharField(choices=[('pending', 'Pending'), ('running', 'Running'), ('completed', 'Completed'), ('failed', 'Failed')], db_index=True, default='pending', help_text='Optimization task status', max_length=20)),
|
||||
('credits_used', models.IntegerField(default=0, help_text='Credits used for optimization', validators=[django.core.validators.MinValueValidator(0)])),
|
||||
('metadata', models.JSONField(blank=True, default=dict, help_text='Additional metadata')),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.tenant')),
|
||||
('content', models.ForeignKey(help_text='The content being optimized', on_delete=django.db.models.deletion.CASCADE, related_name='optimization_tasks', to='writer.content')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.account')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_optimization_tasks',
|
||||
'ordering': ['-created_at'],
|
||||
'verbose_name': 'Optimization Task',
|
||||
'verbose_name_plural': 'Optimization Tasks',
|
||||
'db_table': 'igny8_optimization_tasks',
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='optimizationtask',
|
||||
index=models.Index(fields=['content', 'status'], name='igny8_optim_content_status_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='optimizationtask',
|
||||
index=models.Index(fields=['account', 'status'], name='igny8_optim_account_status_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='optimizationtask',
|
||||
index=models.Index(fields=['status', 'created_at'], name='igny8_optim_status_created_idx'),
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-20 23:27
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('optimization', '0001_initial'),
|
||||
('writer', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='optimizationtask',
|
||||
name='content',
|
||||
field=models.ForeignKey(help_text='The content being optimized', on_delete=django.db.models.deletion.CASCADE, related_name='optimization_tasks', to='writer.content'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='optimizationtask',
|
||||
index=models.Index(fields=['content', 'status'], name='igny8_optim_content_25603a_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='optimizationtask',
|
||||
index=models.Index(fields=['account', 'status'], name='igny8_optim_tenant__2ee3c3_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='optimizationtask',
|
||||
index=models.Index(fields=['status', 'created_at'], name='igny8_optim_status_164ee5_idx'),
|
||||
),
|
||||
]
|
||||
@@ -1,7 +1,7 @@
|
||||
# Generated manually for Phase 5: Publishing System
|
||||
# Generated by Django 5.2.8 on 2025-11-20 23:27
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
@@ -9,42 +9,16 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('igny8_core_auth', '0014_remove_plan_operation_limits_phase0'),
|
||||
('igny8_core_auth', '0001_initial'),
|
||||
('site_building', '0001_initial'),
|
||||
('writer', '0009_add_content_site_source_fields'),
|
||||
('writer', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='PublishingRecord',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('destination', models.CharField(db_index=True, help_text="Destination platform: 'wordpress', 'sites', 'shopify'", max_length=50)),
|
||||
('destination_id', models.CharField(blank=True, help_text='External ID in destination platform', max_length=255, null=True)),
|
||||
('destination_url', models.URLField(blank=True, help_text='URL of published content/site', null=True)),
|
||||
('status', models.CharField(choices=[('pending', 'Pending'), ('publishing', 'Publishing'), ('published', 'Published'), ('failed', 'Failed')], db_index=True, default='pending', max_length=20)),
|
||||
('published_at', models.DateTimeField(blank=True, null=True)),
|
||||
('error_message', models.TextField(blank=True, null=True)),
|
||||
('metadata', models.JSONField(default=dict, help_text='Platform-specific metadata')),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.tenant')),
|
||||
('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')),
|
||||
('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.site')),
|
||||
('sector', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.sector')),
|
||||
('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')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_publishing_records',
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='DeploymentRecord',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('version', models.IntegerField(help_text='Blueprint version being deployed')),
|
||||
('deployed_version', models.IntegerField(blank=True, help_text='Currently deployed version (after successful deployment)', null=True)),
|
||||
('status', models.CharField(choices=[('pending', 'Pending'), ('deploying', 'Deploying'), ('deployed', 'Deployed'), ('failed', 'Failed'), ('rolled_back', 'Rolled Back')], db_index=True, default='pending', max_length=20)),
|
||||
@@ -52,47 +26,42 @@ class Migration(migrations.Migration):
|
||||
('deployment_url', models.URLField(blank=True, help_text='Public URL of deployed site', null=True)),
|
||||
('error_message', models.TextField(blank=True, null=True)),
|
||||
('metadata', models.JSONField(default=dict, help_text='Deployment metadata (build info, file paths, etc.)')),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.tenant')),
|
||||
('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.site')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('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')),
|
||||
],
|
||||
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')],
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='publishingrecord',
|
||||
index=models.Index(fields=['destination', 'status'], name='igny8_publi_destina_123abc_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='publishingrecord',
|
||||
index=models.Index(fields=['content', 'destination'], name='igny8_publi_content_456def_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='publishingrecord',
|
||||
index=models.Index(fields=['site_blueprint', 'destination'], name='igny8_publi_site_bl_789ghi_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='publishingrecord',
|
||||
index=models.Index(fields=['account', 'status'], name='igny8_publi_account_012jkl_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='deploymentrecord',
|
||||
index=models.Index(fields=['site_blueprint', 'status'], name='igny8_deplo_site_bl_345mno_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='deploymentrecord',
|
||||
index=models.Index(fields=['site_blueprint', 'version'], name='igny8_deplo_site_bl_678pqr_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='deploymentrecord',
|
||||
index=models.Index(fields=['status'], name='igny8_deplo_status_901stu_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='deploymentrecord',
|
||||
index=models.Index(fields=['account', 'status'], name='igny8_deplo_account_234vwx_idx'),
|
||||
migrations.CreateModel(
|
||||
name='PublishingRecord',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('destination', models.CharField(db_index=True, help_text="Destination platform: 'wordpress', 'sites', 'shopify'", max_length=50)),
|
||||
('destination_id', models.CharField(blank=True, help_text='External ID in destination platform', max_length=255, null=True)),
|
||||
('destination_url', models.URLField(blank=True, help_text='URL of published content/site', null=True)),
|
||||
('status', models.CharField(choices=[('pending', 'Pending'), ('publishing', 'Publishing'), ('published', 'Published'), ('failed', 'Failed')], db_index=True, default='pending', max_length=20)),
|
||||
('published_at', models.DateTimeField(blank=True, null=True)),
|
||||
('error_message', models.TextField(blank=True, null=True)),
|
||||
('metadata', models.JSONField(default=dict, help_text='Platform-specific metadata')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.account')),
|
||||
('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')),
|
||||
],
|
||||
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')],
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
# Generated by Django 5.2.8 on 2025-11-20 23:27
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
@@ -8,17 +10,91 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('igny8_core_auth', '0014_remove_plan_operation_limits_phase0'),
|
||||
('writer', '0009_add_content_site_source_fields'),
|
||||
('igny8_core_auth', '0001_initial'),
|
||||
('planner', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AudienceProfile',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=120, unique=True)),
|
||||
('description', models.CharField(blank=True, max_length=255)),
|
||||
('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)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Audience Profile',
|
||||
'verbose_name_plural': 'Audience Profiles',
|
||||
'db_table': 'igny8_site_builder_audience_profiles',
|
||||
'ordering': ['order', 'name'],
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='BrandPersonality',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=120, unique=True)),
|
||||
('description', models.CharField(blank=True, max_length=255)),
|
||||
('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)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Brand Personality',
|
||||
'verbose_name_plural': 'Brand Personalities',
|
||||
'db_table': 'igny8_site_builder_brand_personalities',
|
||||
'ordering': ['order', 'name'],
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='BusinessType',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=120, unique=True)),
|
||||
('description', models.CharField(blank=True, max_length=255)),
|
||||
('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)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Business Type',
|
||||
'verbose_name_plural': 'Business Types',
|
||||
'db_table': 'igny8_site_builder_business_types',
|
||||
'ordering': ['order', 'name'],
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='HeroImageryDirection',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=120, unique=True)),
|
||||
('description', models.CharField(blank=True, max_length=255)),
|
||||
('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)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Hero Imagery Direction',
|
||||
'verbose_name_plural': 'Hero Imagery Directions',
|
||||
'db_table': 'igny8_site_builder_hero_imagery',
|
||||
'ordering': ['order', 'name'],
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SiteBlueprint',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('name', models.CharField(help_text='Site name', max_length=255)),
|
||||
('description', models.TextField(blank=True, help_text='Site description', null=True)),
|
||||
('config_json', models.JSONField(default=dict, help_text='Wizard configuration: business_type, style, objectives, etc.')),
|
||||
@@ -27,9 +103,11 @@ class Migration(migrations.Migration):
|
||||
('hosting_type', models.CharField(choices=[('igny8_sites', 'IGNY8 Sites'), ('wordpress', 'WordPress'), ('shopify', 'Shopify'), ('multi', 'Multiple Destinations')], default='igny8_sites', help_text='Target hosting platform', max_length=50)),
|
||||
('version', models.IntegerField(default=1, help_text='Blueprint version', validators=[django.core.validators.MinValueValidator(1)])),
|
||||
('deployed_version', models.IntegerField(blank=True, help_text='Currently deployed version', null=True)),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='siteblueprint_set', to='igny8_core_auth.tenant')),
|
||||
('sector', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='siteblueprint_set', to='igny8_core_auth.sector')),
|
||||
('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='siteblueprint_set', to='igny8_core_auth.site')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('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')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Site Blueprint',
|
||||
@@ -42,54 +120,129 @@ class Migration(migrations.Migration):
|
||||
name='PageBlueprint',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('slug', models.SlugField(help_text='Page URL slug', max_length=255)),
|
||||
('title', models.CharField(help_text='Page title', max_length=255)),
|
||||
('type', models.CharField(choices=[('home', 'Home'), ('about', 'About'), ('services', 'Services'), ('products', 'Products'), ('blog', 'Blog'), ('contact', 'Contact'), ('custom', 'Custom')], default='custom', help_text='Page type', max_length=50)),
|
||||
('blocks_json', models.JSONField(default=list, help_text="Page content blocks: [{'type': 'hero', 'data': {...}}, ...]")),
|
||||
('status', models.CharField(choices=[('draft', 'Draft'), ('generating', 'Generating'), ('ready', 'Ready')], db_index=True, default='draft', help_text='Page status', max_length=20)),
|
||||
('status', models.CharField(choices=[('draft', 'Draft'), ('generating', 'Generating'), ('ready', 'Ready'), ('published', 'Published')], db_index=True, default='draft', help_text='Page status', max_length=20)),
|
||||
('order', models.IntegerField(default=0, help_text='Page order in navigation')),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='pageblueprint_set', to='igny8_core_auth.tenant')),
|
||||
('sector', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='pageblueprint_set', to='igny8_core_auth.sector')),
|
||||
('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='pageblueprint_set', to='igny8_core_auth.site')),
|
||||
('site_blueprint', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='pages', to='site_building.siteblueprint')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('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='The site blueprint this page belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='pages', to='site_building.siteblueprint')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Page Blueprint',
|
||||
'verbose_name_plural': 'Page Blueprints',
|
||||
'db_table': 'igny8_page_blueprints',
|
||||
'ordering': ['order', 'created_at'],
|
||||
'unique_together': {('site_blueprint', 'slug')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SiteBlueprintCluster',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('role', models.CharField(choices=[('hub', 'Hub Page'), ('supporting', 'Supporting Page'), ('attribute', 'Attribute Page')], default='hub', max_length=50)),
|
||||
('coverage_status', models.CharField(choices=[('pending', 'Pending'), ('in_progress', 'In Progress'), ('complete', 'Complete')], default='pending', max_length=50)),
|
||||
('metadata', models.JSONField(blank=True, default=dict, 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)),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.account')),
|
||||
('cluster', models.ForeignKey(help_text='Planner cluster being mapped into the site blueprint', on_delete=django.db.models.deletion.CASCADE, related_name='blueprint_links', to='planner.clusters')),
|
||||
('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 that is planning coverage for the cluster', on_delete=django.db.models.deletion.CASCADE, related_name='cluster_links', to='site_building.siteblueprint')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Site Blueprint Cluster',
|
||||
'verbose_name_plural': 'Site Blueprint Clusters',
|
||||
'db_table': 'igny8_site_blueprint_clusters',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SiteBlueprintTaxonomy',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(help_text='Display name', max_length=255)),
|
||||
('slug', models.SlugField(help_text='Slug/identifier within the site blueprint', max_length=255)),
|
||||
('taxonomy_type', models.CharField(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')], default='blog_category', max_length=50)),
|
||||
('description', models.TextField(blank=True, null=True)),
|
||||
('metadata', models.JSONField(blank=True, default=dict, help_text='Additional taxonomy metadata or AI hints')),
|
||||
('external_reference', models.CharField(blank=True, help_text='External system ID (WordPress/WooCommerce/etc.)', max_length=255, null=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.account')),
|
||||
('clusters', models.ManyToManyField(blank=True, help_text='Planner clusters that this taxonomy maps to', related_name='blueprint_taxonomies', to='planner.clusters')),
|
||||
('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 this taxonomy belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='taxonomies', to='site_building.siteblueprint')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Site Blueprint Taxonomy',
|
||||
'verbose_name_plural': 'Site Blueprint Taxonomies',
|
||||
'db_table': 'igny8_site_blueprint_taxonomies',
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='siteblueprint',
|
||||
index=models.Index(fields=['status'], name='igny8_site__status_247ddc_idx'),
|
||||
index=models.Index(fields=['status'], name='igny8_site__status_e7ca10_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='siteblueprint',
|
||||
index=models.Index(fields=['hosting_type'], name='igny8_site__hosting_c4bb41_idx'),
|
||||
index=models.Index(fields=['hosting_type'], name='igny8_site__hosting_7a9a3e_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='siteblueprint',
|
||||
index=models.Index(fields=['site', 'sector'], name='igny8_site__site_id__5f0a4e_idx'),
|
||||
index=models.Index(fields=['site', 'sector'], name='igny8_site__site_id_cb1aca_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='siteblueprint',
|
||||
index=models.Index(fields=['account', 'status'], name='igny8_site__account__38f18a_idx'),
|
||||
index=models.Index(fields=['account', 'status'], name='igny8_site__tenant__1bb483_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='pageblueprint',
|
||||
index=models.Index(fields=['site_blueprint', 'status'], name='igny8_page__site_bl_1b5d8b_idx'),
|
||||
index=models.Index(fields=['site_blueprint', 'status'], name='igny8_page__site_bl_2dede2_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='pageblueprint',
|
||||
index=models.Index(fields=['type'], name='igny8_page__type_b11552_idx'),
|
||||
index=models.Index(fields=['type'], name='igny8_page__type_4af2bd_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='pageblueprint',
|
||||
index=models.Index(fields=['site_blueprint', 'order'], name='igny8_page__site_bl_7a77d7_idx'),
|
||||
index=models.Index(fields=['site_blueprint', 'order'], name='igny8_page__site_bl_c56196_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='pageblueprint',
|
||||
unique_together={('site_blueprint', 'slug')},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='siteblueprintcluster',
|
||||
index=models.Index(fields=['site_blueprint', 'cluster'], name='igny8_site__site_bl_904406_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='siteblueprintcluster',
|
||||
index=models.Index(fields=['site_blueprint', 'coverage_status'], name='igny8_site__site_bl_cff5ab_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='siteblueprintcluster',
|
||||
index=models.Index(fields=['cluster', 'role'], name='igny8_site__cluster_d75a2f_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='siteblueprintcluster',
|
||||
unique_together={('site_blueprint', 'cluster', 'role')},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='siteblueprinttaxonomy',
|
||||
index=models.Index(fields=['site_blueprint', 'taxonomy_type'], name='igny8_site__site_bl_f952f7_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='siteblueprinttaxonomy',
|
||||
index=models.Index(fields=['taxonomy_type'], name='igny8_site__taxonom_178987_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='siteblueprinttaxonomy',
|
||||
unique_together={('site_blueprint', 'slug')},
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def seed_site_builder_metadata(apps, schema_editor):
|
||||
BusinessType = apps.get_model('site_building', 'BusinessType')
|
||||
AudienceProfile = apps.get_model('site_building', 'AudienceProfile')
|
||||
BrandPersonality = apps.get_model('site_building', 'BrandPersonality')
|
||||
HeroImageryDirection = apps.get_model('site_building', 'HeroImageryDirection')
|
||||
|
||||
business_types = [
|
||||
("Productized Services", "Standardized service offering with clear deliverables."),
|
||||
("B2B SaaS Platform", "Subscription software platform targeting business teams."),
|
||||
("eCommerce Brand", "Direct-to-consumer catalog with premium merchandising."),
|
||||
("Marketplace / Platform", "Two-sided marketplace connecting buyers and sellers."),
|
||||
("Advisory / Consulting", "Expert advisory firm or boutique consultancy."),
|
||||
("Education / Training", "Learning platform, cohort, or academy."),
|
||||
("Community / Membership", "Member-driven experience with gated content."),
|
||||
("Mission-Driven / Nonprofit", "Impact-focused organization or foundation."),
|
||||
]
|
||||
|
||||
audience_profiles = [
|
||||
("Enterprise Operations Leaders", "COO / Ops executives at scale-ups."),
|
||||
("Marketing Directors & CMOs", "Growth and brand owners across industries."),
|
||||
("Founders & Executive Teams", "Visionaries leading fast-moving companies."),
|
||||
("Revenue & Sales Leaders", "CROs, VPs of Sales, and GTM owners."),
|
||||
("Product & Innovation Teams", "Product managers and innovation leaders."),
|
||||
("IT & Engineering Teams", "Technical buyers evaluating new platforms."),
|
||||
("HR & People Leaders", "People ops and talent professionals."),
|
||||
("Healthcare Administrators", "Clinical and operational healthcare leads."),
|
||||
("Financial Services Professionals", "Banking, fintech, and investment teams."),
|
||||
("Consumers / Prospects", "End-user or prospect-focused experience."),
|
||||
]
|
||||
|
||||
brand_personalities = [
|
||||
("Bold Visionary", "Decisive, future-forward, and thought-leading."),
|
||||
("Trusted Advisor", "Calm, credible, and risk-aware guidance."),
|
||||
("Analytical Expert", "Data-backed, precise, and rigorous."),
|
||||
("Friendly Guide", "Welcoming, warm, and supportive tone."),
|
||||
("Luxe & Premium", "High-touch, elevated, and detail-obsessed."),
|
||||
("Playful Creative", "Vibrant, unexpected, and energetic."),
|
||||
("Minimalist Modern", "Clean, refined, and confident."),
|
||||
("Fearless Innovator", "Experimental, edgy, and fast-moving."),
|
||||
]
|
||||
|
||||
hero_imagery = [
|
||||
("Real team collaboration photography", "Documentary-style shots of real teams."),
|
||||
("Product close-ups with UI overlays", "Focus on interfaces and feature highlights."),
|
||||
("Lifestyle scenes featuring customers", "Story-driven photography of real scenarios."),
|
||||
("Abstract gradients & motion graphics", "Modern, colorful abstract compositions."),
|
||||
("Illustration + iconography blend", "Custom illustrations paired with icon systems."),
|
||||
]
|
||||
|
||||
for order, (name, description) in enumerate(business_types):
|
||||
BusinessType.objects.update_or_create(
|
||||
name=name,
|
||||
defaults={'description': description, 'order': order, 'is_active': True},
|
||||
)
|
||||
|
||||
for order, (name, description) in enumerate(audience_profiles):
|
||||
AudienceProfile.objects.update_or_create(
|
||||
name=name,
|
||||
defaults={'description': description, 'order': order, 'is_active': True},
|
||||
)
|
||||
|
||||
for order, (name, description) in enumerate(brand_personalities):
|
||||
BrandPersonality.objects.update_or_create(
|
||||
name=name,
|
||||
defaults={'description': description, 'order': order, 'is_active': True},
|
||||
)
|
||||
|
||||
for order, (name, description) in enumerate(hero_imagery):
|
||||
HeroImageryDirection.objects.update_or_create(
|
||||
name=name,
|
||||
defaults={'description': description, 'order': order, 'is_active': True},
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('site_building', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AudienceProfile',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=120, unique=True)),
|
||||
('description', models.CharField(blank=True, max_length=255)),
|
||||
('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)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Audience Profile',
|
||||
'verbose_name_plural': 'Audience Profiles',
|
||||
'db_table': 'igny8_site_builder_audience_profiles',
|
||||
'ordering': ['order', 'name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='BrandPersonality',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=120, unique=True)),
|
||||
('description', models.CharField(blank=True, max_length=255)),
|
||||
('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)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Brand Personality',
|
||||
'verbose_name_plural': 'Brand Personalities',
|
||||
'db_table': 'igny8_site_builder_brand_personalities',
|
||||
'ordering': ['order', 'name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='BusinessType',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=120, unique=True)),
|
||||
('description', models.CharField(blank=True, max_length=255)),
|
||||
('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)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Business Type',
|
||||
'verbose_name_plural': 'Business Types',
|
||||
'db_table': 'igny8_site_builder_business_types',
|
||||
'ordering': ['order', 'name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='HeroImageryDirection',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=120, unique=True)),
|
||||
('description', models.CharField(blank=True, max_length=255)),
|
||||
('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)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Hero Imagery Direction',
|
||||
'verbose_name_plural': 'Hero Imagery Directions',
|
||||
'db_table': 'igny8_site_builder_hero_imagery',
|
||||
'ordering': ['order', 'name'],
|
||||
},
|
||||
),
|
||||
migrations.RunPython(seed_site_builder_metadata, migrations.RunPython.noop),
|
||||
]
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('planner', '0007_merge_20251109_2138'),
|
||||
('site_building', '0002_sitebuilder_metadata'),
|
||||
('igny8_core_auth', '0014_remove_plan_operation_limits_phase0'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SiteBlueprintCluster',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('role', models.CharField(choices=[('hub', 'Hub Page'), ('supporting', 'Supporting Page'), ('attribute', 'Attribute Page')], default='hub', max_length=50)),
|
||||
('coverage_status', models.CharField(choices=[('pending', 'Pending'), ('in_progress', 'In Progress'), ('complete', 'Complete')], default='pending', max_length=50)),
|
||||
('metadata', models.JSONField(blank=True, default=dict, help_text='Additional coverage metadata (target pages, keyword counts, ai hints)')),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='siteblueprintcluster_set', to='igny8_core_auth.tenant')),
|
||||
('cluster', models.ForeignKey(help_text='Planner cluster being mapped into the site blueprint', on_delete=django.db.models.deletion.CASCADE, related_name='blueprint_links', to='planner.clusters')),
|
||||
('sector', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='siteblueprintcluster_set', to='igny8_core_auth.sector')),
|
||||
('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='siteblueprintcluster_set', to='igny8_core_auth.site')),
|
||||
('site_blueprint', models.ForeignKey(help_text='Site blueprint that is planning coverage for the cluster', on_delete=django.db.models.deletion.CASCADE, related_name='cluster_links', to='site_building.siteblueprint')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Site Blueprint Cluster',
|
||||
'verbose_name_plural': 'Site Blueprint Clusters',
|
||||
'db_table': 'igny8_site_blueprint_clusters',
|
||||
'ordering': ['-created_at'],
|
||||
'unique_together': {('site_blueprint', 'cluster', 'role')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='WorkflowState',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('current_step', models.CharField(default='business_details', max_length=50)),
|
||||
('step_status', models.JSONField(blank=True, default=dict, help_text='Dictionary of step → status/progress metadata')),
|
||||
('blocking_reason', models.TextField(blank=True, help_text='Human-readable explanation when blocked', null=True)),
|
||||
('completed', models.BooleanField(default=False, help_text='Marks wizard completion')),
|
||||
('metadata', models.JSONField(blank=True, default=dict)),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='workflowstate_set', to='igny8_core_auth.tenant')),
|
||||
('sector', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workflowstate_set', to='igny8_core_auth.sector')),
|
||||
('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='workflowstate_set', to='igny8_core_auth.site')),
|
||||
('site_blueprint', models.OneToOneField(help_text='Blueprint whose progress is being tracked', on_delete=django.db.models.deletion.CASCADE, related_name='workflow_state', to='site_building.siteblueprint')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Workflow State',
|
||||
'verbose_name_plural': 'Workflow States',
|
||||
'db_table': 'igny8_site_blueprint_workflow_states',
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SiteBlueprintTaxonomy',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('name', models.CharField(help_text='Display name', max_length=255)),
|
||||
('slug', models.SlugField(help_text='Slug/identifier within the site blueprint', max_length=255)),
|
||||
('taxonomy_type', models.CharField(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')], default='blog_category', max_length=50)),
|
||||
('description', models.TextField(blank=True, null=True)),
|
||||
('metadata', models.JSONField(blank=True, default=dict, help_text='Additional taxonomy metadata or AI hints')),
|
||||
('external_reference', models.CharField(blank=True, help_text='External system ID (WordPress/WooCommerce/etc.)', max_length=255, null=True)),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='siteblueprinttaxonomy_set', to='igny8_core_auth.tenant')),
|
||||
('sector', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='siteblueprinttaxonomy_set', to='igny8_core_auth.sector')),
|
||||
('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='siteblueprinttaxonomy_set', to='igny8_core_auth.site')),
|
||||
('site_blueprint', models.ForeignKey(help_text='Site blueprint this taxonomy belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='taxonomies', to='site_building.siteblueprint')),
|
||||
('clusters', models.ManyToManyField(blank=True, help_text='Planner clusters that this taxonomy maps to', related_name='blueprint_taxonomies', to='planner.clusters')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Site Blueprint Taxonomy',
|
||||
'verbose_name_plural': 'Site Blueprint Taxonomies',
|
||||
'db_table': 'igny8_site_blueprint_taxonomies',
|
||||
'ordering': ['-created_at'],
|
||||
'unique_together': {('site_blueprint', 'slug')},
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='siteblueprintcluster',
|
||||
index=models.Index(fields=['site_blueprint', 'cluster'], name='site_buildi_site_bl_4234c0_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='siteblueprintcluster',
|
||||
index=models.Index(fields=['cluster', 'role'], name='site_buildi_cluster__9a078f_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='siteblueprintcluster',
|
||||
index=models.Index(fields=['site_blueprint', 'coverage_status'], name='site_buildi_site_bl_459d80_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='workflowstate',
|
||||
index=models.Index(fields=['site_blueprint'], name='site_buildi_site_bl_312cd0_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='workflowstate',
|
||||
index=models.Index(fields=['current_step'], name='site_buildi_current_a25dce_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='workflowstate',
|
||||
index=models.Index(fields=['completed'], name='site_buildi_complet_4649af_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='siteblueprinttaxonomy',
|
||||
index=models.Index(fields=['site_blueprint', 'taxonomy_type'], name='site_buildi_site_bl_33fadc_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='siteblueprinttaxonomy',
|
||||
index=models.Index(fields=['taxonomy_type'], name='site_buildi_taxonom_4a7dde_idx'),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# Generated manually for Phase 2: Automation System
|
||||
# Generated by Django 5.2.8 on 2025-11-20 23:27
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
@@ -10,7 +9,6 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('igny8_core_auth', '0008_passwordresettoken_alter_industry_options_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
@@ -18,8 +16,6 @@ class Migration(migrations.Migration):
|
||||
name='AutomationRule',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('name', models.CharField(help_text='Rule name', max_length=255)),
|
||||
('description', models.TextField(blank=True, help_text='Rule description', null=True)),
|
||||
('trigger', models.CharField(choices=[('schedule', 'Schedule'), ('event', 'Event'), ('manual', 'Manual')], default='manual', max_length=50)),
|
||||
@@ -31,70 +27,34 @@ class Migration(migrations.Migration):
|
||||
('last_executed_at', models.DateTimeField(blank=True, null=True)),
|
||||
('execution_count', models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0)])),
|
||||
('metadata', models.JSONField(default=dict, help_text='Additional metadata')),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.tenant')),
|
||||
('site', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='igny8_core_auth.site')),
|
||||
('sector', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='igny8_core_auth.sector')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_automation_rules',
|
||||
'ordering': ['-created_at'],
|
||||
'verbose_name': 'Automation Rule',
|
||||
'verbose_name_plural': 'Automation Rules',
|
||||
'db_table': 'igny8_automation_rules',
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ScheduledTask',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('scheduled_at', models.DateTimeField(help_text='When the task is scheduled to run')),
|
||||
('executed_at', models.DateTimeField(blank=True, help_text='When the task was actually executed', null=True)),
|
||||
('status', models.CharField(choices=[('pending', 'Pending'), ('running', 'Running'), ('completed', 'Completed'), ('failed', 'Failed'), ('cancelled', 'Cancelled')], default='pending', max_length=50)),
|
||||
('result', models.JSONField(default=dict, help_text='Execution result data')),
|
||||
('error_message', models.TextField(blank=True, help_text='Error message if execution failed', null=True)),
|
||||
('metadata', models.JSONField(default=dict, help_text='Additional metadata')),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.tenant')),
|
||||
('automation_rule', models.ForeignKey(help_text='The automation rule this task belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='scheduled_tasks', to='automation.automationrule')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_scheduled_tasks',
|
||||
'ordering': ['-scheduled_at'],
|
||||
'verbose_name': 'Scheduled Task',
|
||||
'verbose_name_plural': 'Scheduled Tasks',
|
||||
'db_table': 'igny8_scheduled_tasks',
|
||||
'ordering': ['-scheduled_at'],
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='automationrule',
|
||||
index=models.Index(fields=['trigger', 'is_active'], name='igny8_autom_trigger_123abc_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='automationrule',
|
||||
index=models.Index(fields=['status'], name='igny8_autom_status_456def_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='automationrule',
|
||||
index=models.Index(fields=['site', 'sector'], name='igny8_autom_site_id_789ghi_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='automationrule',
|
||||
index=models.Index(fields=['trigger', 'is_active', 'status'], name='igny8_autom_trigger_0abjkl_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='scheduledtask',
|
||||
index=models.Index(fields=['automation_rule', 'status'], name='igny8_sched_automation_123abc_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='scheduledtask',
|
||||
index=models.Index(fields=['scheduled_at', 'status'], name='igny8_sched_scheduled_456def_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='scheduledtask',
|
||||
index=models.Index(fields=['account', 'status'], name='igny8_sched_account_789ghi_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='scheduledtask',
|
||||
index=models.Index(fields=['status', 'scheduled_at'], name='igny8_sched_status_0abjkl_idx'),
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-20 23:27
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('automation', '0001_initial'),
|
||||
('igny8_core_auth', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='automationrule',
|
||||
name='account',
|
||||
field=models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.account'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='automationrule',
|
||||
name='sector',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.sector'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='automationrule',
|
||||
name='site',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.site'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='scheduledtask',
|
||||
name='account',
|
||||
field=models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.account'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='scheduledtask',
|
||||
name='automation_rule',
|
||||
field=models.ForeignKey(help_text='The automation rule this task belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='scheduled_tasks', to='automation.automationrule'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='automationrule',
|
||||
index=models.Index(fields=['trigger', 'is_active'], name='igny8_autom_trigger_32979f_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='automationrule',
|
||||
index=models.Index(fields=['status'], name='igny8_autom_status_827c0d_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='automationrule',
|
||||
index=models.Index(fields=['site', 'sector'], name='igny8_autom_site_id_d0a51d_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='automationrule',
|
||||
index=models.Index(fields=['trigger', 'is_active', 'status'], name='igny8_autom_trigger_f3f3e2_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='scheduledtask',
|
||||
index=models.Index(fields=['automation_rule', 'status'], name='igny8_sched_automat_da6c85_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='scheduledtask',
|
||||
index=models.Index(fields=['scheduled_at', 'status'], name='igny8_sched_schedul_1e3342_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='scheduledtask',
|
||||
index=models.Index(fields=['account', 'status'], name='igny8_sched_tenant__7244a8_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='scheduledtask',
|
||||
index=models.Index(fields=['status', 'scheduled_at'], name='igny8_sched_status_21f32f_idx'),
|
||||
),
|
||||
]
|
||||
@@ -1,7 +1,6 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-07 10:37
|
||||
# Generated by Django 5.2.8 on 2025-11-20 23:27
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
@@ -10,7 +9,6 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('igny8_core_auth', '0008_passwordresettoken_alter_industry_options_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
@@ -25,12 +23,10 @@ class Migration(migrations.Migration):
|
||||
('description', models.CharField(max_length=255)),
|
||||
('metadata', models.JSONField(default=dict, help_text='Additional context (AI call details, etc.)')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('tenant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.tenant')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_credit_transactions',
|
||||
'ordering': ['-created_at'],
|
||||
'indexes': [models.Index(fields=['tenant', 'transaction_type'], name='igny8_credi_tenant__c4281c_idx'), models.Index(fields=['tenant', 'created_at'], name='igny8_credi_tenant__69abd3_idx')],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
@@ -38,7 +34,7 @@ class Migration(migrations.Migration):
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('operation_type', models.CharField(choices=[('clustering', 'Keyword Clustering'), ('ideas', 'Content Ideas Generation'), ('content', 'Content Generation'), ('images', 'Image Generation'), ('reparse', 'Content Reparse')], db_index=True, max_length=50)),
|
||||
('operation_type', models.CharField(choices=[('clustering', 'Keyword Clustering'), ('idea_generation', 'Content Ideas Generation'), ('content_generation', 'Content Generation'), ('image_generation', 'Image Generation'), ('reparse', 'Content Reparse'), ('ideas', 'Content Ideas Generation'), ('content', 'Content Generation'), ('images', 'Image Generation')], db_index=True, max_length=50)),
|
||||
('credits_used', models.IntegerField(validators=[django.core.validators.MinValueValidator(0)])),
|
||||
('cost_usd', models.DecimalField(blank=True, decimal_places=4, max_digits=10, null=True)),
|
||||
('model_used', models.CharField(blank=True, max_length=100)),
|
||||
@@ -48,12 +44,10 @@ class Migration(migrations.Migration):
|
||||
('related_object_id', models.IntegerField(blank=True, null=True)),
|
||||
('metadata', models.JSONField(default=dict)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('tenant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.tenant')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_credit_usage_logs',
|
||||
'ordering': ['-created_at'],
|
||||
'indexes': [models.Index(fields=['tenant', 'operation_type'], name='igny8_credi_tenant__3545ed_idx'), models.Index(fields=['tenant', 'created_at'], name='igny8_credi_tenant__4aee99_idx'), models.Index(fields=['tenant', 'operation_type', 'created_at'], name='igny8_credi_tenant__a00d57_idx')],
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-20 23:27
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('billing', '0001_initial'),
|
||||
('igny8_core_auth', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='credittransaction',
|
||||
name='account',
|
||||
field=models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.account'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='creditusagelog',
|
||||
name='account',
|
||||
field=models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.account'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='credittransaction',
|
||||
index=models.Index(fields=['account', 'transaction_type'], name='igny8_credi_tenant__c4281c_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='credittransaction',
|
||||
index=models.Index(fields=['account', 'created_at'], name='igny8_credi_tenant__69abd3_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='creditusagelog',
|
||||
index=models.Index(fields=['account', 'operation_type'], name='igny8_credi_tenant__3545ed_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='creditusagelog',
|
||||
index=models.Index(fields=['account', 'created_at'], name='igny8_credi_tenant__4aee99_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='creditusagelog',
|
||||
index=models.Index(fields=['account', 'operation_type', 'created_at'], name='igny8_credi_tenant__a00d57_idx'),
|
||||
),
|
||||
]
|
||||
@@ -1,23 +0,0 @@
|
||||
# Generated migration to rename tenant field to account
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('billing', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='credittransaction',
|
||||
old_name='tenant',
|
||||
new_name='account',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='creditusagelog',
|
||||
old_name='tenant',
|
||||
new_name='account',
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
# Generated migration to fix tenant_id column name
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('billing', '0002_rename_tenant_to_account'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
# Rename the database column from account_id to tenant_id to match model's db_column
|
||||
migrations.RunSQL(
|
||||
sql="ALTER TABLE igny8_credit_transactions RENAME COLUMN account_id TO tenant_id;",
|
||||
reverse_sql="ALTER TABLE igny8_credit_transactions RENAME COLUMN tenant_id TO account_id;"
|
||||
),
|
||||
migrations.RunSQL(
|
||||
sql="ALTER TABLE igny8_credit_usage_logs RENAME COLUMN account_id TO tenant_id;",
|
||||
reverse_sql="ALTER TABLE igny8_credit_usage_logs RENAME COLUMN tenant_id TO account_id;"
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.2.7 on 2025-11-02 21:42
|
||||
# Generated by Django 5.2.8 on 2025-11-20 23:27
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
@@ -13,6 +13,24 @@ class Migration(migrations.Migration):
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Keywords',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('volume_override', models.IntegerField(blank=True, help_text='Site-specific volume override (uses seed_keyword.volume if not set)', null=True)),
|
||||
('difficulty_override', models.IntegerField(blank=True, help_text='Site-specific difficulty override (uses seed_keyword.difficulty if not set)', null=True)),
|
||||
('attribute_values', models.JSONField(blank=True, default=list, help_text='Optional attribute metadata (e.g., product specs, service modifiers)')),
|
||||
('status', models.CharField(choices=[('active', 'Active'), ('pending', 'Pending'), ('archived', 'Archived')], default='pending', max_length=50)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Keyword',
|
||||
'verbose_name_plural': 'Keywords',
|
||||
'db_table': 'igny8_keywords',
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Clusters',
|
||||
fields=[
|
||||
@@ -23,64 +41,46 @@ class Migration(migrations.Migration):
|
||||
('volume', models.IntegerField(default=0)),
|
||||
('mapped_pages', models.IntegerField(default=0)),
|
||||
('status', models.CharField(default='active', max_length=50)),
|
||||
('context_type', models.CharField(choices=[('topic', 'Topic Cluster'), ('attribute', 'Attribute Cluster'), ('service_line', 'Service Line')], default='topic', help_text='Primary dimension for this cluster (topic, attribute, service line)', max_length=50)),
|
||||
('dimension_meta', models.JSONField(blank=True, default=dict, help_text='Extended metadata (taxonomy hints, attribute suggestions, coverage targets)')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('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')),
|
||||
('tenant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.tenant')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Cluster',
|
||||
'verbose_name_plural': 'Clusters',
|
||||
'db_table': 'igny8_clusters',
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Keywords',
|
||||
name='ContentIdeas',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('keyword', models.CharField(db_index=True, max_length=255)),
|
||||
('volume', models.IntegerField(default=0)),
|
||||
('difficulty', models.IntegerField(default=0)),
|
||||
('intent', models.CharField(choices=[('informational', 'Informational'), ('navigational', 'Navigational'), ('commercial', 'Commercial'), ('transactional', 'Transactional')], default='informational', max_length=50)),
|
||||
('status', models.CharField(choices=[('active', 'Active'), ('pending', 'Pending'), ('archived', 'Archived')], default='pending', max_length=50)),
|
||||
('idea_title', models.CharField(db_index=True, max_length=255)),
|
||||
('description', models.TextField(blank=True, null=True)),
|
||||
('content_structure', models.CharField(choices=[('cluster_hub', 'Cluster Hub'), ('landing_page', 'Landing Page'), ('pillar_page', 'Pillar Page'), ('supporting_page', 'Supporting Page')], default='blog_post', max_length=50)),
|
||||
('content_type', models.CharField(choices=[('blog_post', 'Blog Post'), ('article', 'Article'), ('guide', 'Guide'), ('tutorial', 'Tutorial')], default='blog_post', max_length=50)),
|
||||
('target_keywords', models.CharField(blank=True, max_length=500)),
|
||||
('status', models.CharField(choices=[('new', 'New'), ('scheduled', 'Scheduled'), ('published', 'Published')], default='new', max_length=50)),
|
||||
('estimated_word_count', models.IntegerField(default=1000)),
|
||||
('site_entity_type', models.CharField(choices=[('page', 'Site Page'), ('blog_post', 'Blog Post'), ('product', 'Product'), ('service', 'Service'), ('taxonomy', 'Taxonomy Page')], default='page', help_text='Target entity type when promoting idea into tasks/pages', max_length=50)),
|
||||
('cluster_role', models.CharField(choices=[('hub', 'Hub Page'), ('supporting', 'Supporting Page'), ('attribute', 'Attribute Page')], default='hub', help_text='Role within the cluster-driven sitemap', max_length=50)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('cluster', models.ForeignKey(blank=True, limit_choices_to={'sector': models.F('sector')}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='keywords', to='planner.clusters')),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.account')),
|
||||
('keyword_cluster', models.ForeignKey(blank=True, limit_choices_to={'sector': models.F('sector')}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='ideas', to='planner.clusters')),
|
||||
('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')),
|
||||
('tenant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.tenant')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_keywords',
|
||||
'verbose_name': 'Content Idea',
|
||||
'verbose_name_plural': 'Content Ideas',
|
||||
'db_table': 'igny8_content_ideas',
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='clusters',
|
||||
index=models.Index(fields=['name'], name='igny8_clust_name_0f98bb_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='clusters',
|
||||
index=models.Index(fields=['status'], name='igny8_clust_status_c50486_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='clusters',
|
||||
index=models.Index(fields=['site', 'sector'], name='igny8_clust_site_id_a9aee3_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='keywords',
|
||||
index=models.Index(fields=['keyword'], name='igny8_keywo_keyword_462bff_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='keywords',
|
||||
index=models.Index(fields=['status'], name='igny8_keywo_status_9a0dd6_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='keywords',
|
||||
index=models.Index(fields=['cluster'], name='igny8_keywo_cluster_d1ea95_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='keywords',
|
||||
index=models.Index(fields=['site', 'sector'], name='igny8_keywo_site_id_7bcb63_idx'),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
# Legacy migration kept for compatibility; database schema already includes
|
||||
# the account/site/sector fields as part of the initial state.
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('igny8_core_auth', '0001_initial'),
|
||||
('planner', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = []
|
||||
|
||||
121
backend/igny8_core/modules/planner/migrations/0002_initial.py
Normal file
121
backend/igny8_core/modules/planner/migrations/0002_initial.py
Normal file
@@ -0,0 +1,121 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-20 23:27
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('igny8_core_auth', '0001_initial'),
|
||||
('planner', '0001_initial'),
|
||||
('site_building', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='contentideas',
|
||||
name='taxonomy',
|
||||
field=models.ForeignKey(blank=True, help_text='Optional taxonomy association when derived from blueprint planning', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='content_ideas', to='site_building.siteblueprinttaxonomy'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='keywords',
|
||||
name='account',
|
||||
field=models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.account'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='keywords',
|
||||
name='cluster',
|
||||
field=models.ForeignKey(blank=True, limit_choices_to={'sector': models.F('sector')}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='keywords', to='planner.clusters'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='keywords',
|
||||
name='sector',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.sector'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='keywords',
|
||||
name='seed_keyword',
|
||||
field=models.ForeignKey(help_text='Reference to the global seed keyword', on_delete=django.db.models.deletion.PROTECT, related_name='site_keywords', to='igny8_core_auth.seedkeyword'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='keywords',
|
||||
name='site',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.site'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='contentideas',
|
||||
name='keyword_objects',
|
||||
field=models.ManyToManyField(blank=True, help_text='Individual keywords linked to this content idea', related_name='content_ideas', to='planner.keywords'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='clusters',
|
||||
index=models.Index(fields=['name'], name='igny8_clust_name_0f98bb_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='clusters',
|
||||
index=models.Index(fields=['status'], name='igny8_clust_status_c50486_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='clusters',
|
||||
index=models.Index(fields=['site', 'sector'], name='igny8_clust_site_id_a9aee3_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='clusters',
|
||||
index=models.Index(fields=['context_type'], name='igny8_clust_context_0d6bd7_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='keywords',
|
||||
index=models.Index(fields=['seed_keyword'], name='igny8_keywo_seed_ke_919a9d_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='keywords',
|
||||
index=models.Index(fields=['status'], name='igny8_keywo_status_9a0dd6_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='keywords',
|
||||
index=models.Index(fields=['cluster'], name='igny8_keywo_cluster_d1ea95_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='keywords',
|
||||
index=models.Index(fields=['site', 'sector'], name='igny8_keywo_site_id_7bcb63_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='keywords',
|
||||
index=models.Index(fields=['seed_keyword', 'site', 'sector'], name='igny8_keywo_seed_ke_69146b_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='keywords',
|
||||
unique_together={('seed_keyword', 'site', 'sector')},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='contentideas',
|
||||
index=models.Index(fields=['idea_title'], name='igny8_conte_idea_ti_1e15a5_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='contentideas',
|
||||
index=models.Index(fields=['status'], name='igny8_conte_status_6be5bc_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='contentideas',
|
||||
index=models.Index(fields=['keyword_cluster'], name='igny8_conte_keyword_4d2151_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='contentideas',
|
||||
index=models.Index(fields=['content_structure'], name='igny8_conte_content_3eede7_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='contentideas',
|
||||
index=models.Index(fields=['site_entity_type'], name='igny8_conte_site_en_511349_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='contentideas',
|
||||
index=models.Index(fields=['cluster_role'], name='igny8_conte_cluster_234240_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='contentideas',
|
||||
index=models.Index(fields=['site', 'sector'], name='igny8_conte_site_id_03b520_idx'),
|
||||
),
|
||||
]
|
||||
@@ -1,59 +0,0 @@
|
||||
# Generated by Django 5.2.7 on 2025-11-03 13:22
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('igny8_core_auth', '0003_alter_user_role'),
|
||||
('planner', '0002_add_site_sector_tenant'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='clusters',
|
||||
name='sector',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.sector'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='clusters',
|
||||
name='site',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.site'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='keywords',
|
||||
name='sector',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.sector'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='keywords',
|
||||
name='site',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.site'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ContentIdeas',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('idea_title', models.CharField(db_index=True, max_length=255)),
|
||||
('description', models.TextField(blank=True, null=True)),
|
||||
('content_structure', models.CharField(choices=[('cluster_hub', 'Cluster Hub'), ('landing_page', 'Landing Page'), ('pillar_page', 'Pillar Page'), ('supporting_page', 'Supporting Page')], default='blog_post', max_length=50)),
|
||||
('content_type', models.CharField(choices=[('blog_post', 'Blog Post'), ('article', 'Article'), ('guide', 'Guide'), ('tutorial', 'Tutorial')], default='blog_post', max_length=50)),
|
||||
('target_keywords', models.CharField(blank=True, max_length=500)),
|
||||
('status', models.CharField(choices=[('new', 'New'), ('scheduled', 'Scheduled'), ('published', 'Published')], default='new', max_length=50)),
|
||||
('estimated_word_count', models.IntegerField(default=1000)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('keyword_cluster', models.ForeignKey(blank=True, limit_choices_to={'sector': models.F('sector')}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='ideas', to='planner.clusters')),
|
||||
('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')),
|
||||
('tenant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.tenant')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_content_ideas',
|
||||
'ordering': ['-created_at'],
|
||||
'indexes': [models.Index(fields=['idea_title'], name='igny8_conte_idea_ti_1e15a5_idx'), models.Index(fields=['status'], name='igny8_conte_status_6be5bc_idx'), models.Index(fields=['keyword_cluster'], name='igny8_conte_keyword_4d2151_idx'), models.Index(fields=['content_structure'], name='igny8_conte_content_3eede7_idx'), models.Index(fields=['site', 'sector'], name='igny8_conte_site_id_03b520_idx')],
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -1,24 +0,0 @@
|
||||
# Generated manually to fix missing ManyToMany table
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('planner', '0003_alter_clusters_sector_alter_clusters_site_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='contentideas',
|
||||
name='keyword_objects',
|
||||
field=models.ManyToManyField(
|
||||
blank=True,
|
||||
help_text='Individual keywords linked to this content idea',
|
||||
related_name='content_ideas',
|
||||
to='planner.keywords'
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
# Generated manually for adding seed_keyword relationship to Keywords
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('igny8_core_auth', '0010_add_seed_keyword'),
|
||||
('planner', '0003_alter_clusters_sector_alter_clusters_site_and_more'),
|
||||
]
|
||||
|
||||
# Duplicate of planner.0006_add_seed_keyword_to_keywords.
|
||||
# This migration is kept as a no-op to avoid applying the schema changes twice.
|
||||
operations = []
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-07 10:06
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('planner', '0004_add_keyword_objects_to_contentideas'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='clusters',
|
||||
options={'ordering': ['name'], 'verbose_name': 'Cluster', 'verbose_name_plural': 'Clusters'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='contentideas',
|
||||
options={'ordering': ['-created_at'], 'verbose_name': 'Content Idea', 'verbose_name_plural': 'Content Ideas'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='keywords',
|
||||
options={'ordering': ['-created_at'], 'verbose_name': 'Keyword', 'verbose_name_plural': 'Keywords'},
|
||||
),
|
||||
]
|
||||
@@ -1,86 +0,0 @@
|
||||
# Generated manually for adding seed_keyword relationship to Keywords
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('igny8_core_auth', '0010_add_seed_keyword'),
|
||||
('planner', '0005_alter_clusters_options_alter_contentideas_options_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
# Remove old fields (keyword, volume, difficulty, intent)
|
||||
migrations.RemoveField(
|
||||
model_name='keywords',
|
||||
name='keyword',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='keywords',
|
||||
name='volume',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='keywords',
|
||||
name='difficulty',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='keywords',
|
||||
name='intent',
|
||||
),
|
||||
# Add seed_keyword FK
|
||||
migrations.AddField(
|
||||
model_name='keywords',
|
||||
name='seed_keyword',
|
||||
field=models.ForeignKey(
|
||||
help_text='Reference to the global seed keyword',
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name='site_keywords',
|
||||
to='igny8_core_auth.seedkeyword',
|
||||
null=True # Temporarily nullable for migration
|
||||
),
|
||||
),
|
||||
# Add override fields
|
||||
migrations.AddField(
|
||||
model_name='keywords',
|
||||
name='volume_override',
|
||||
field=models.IntegerField(blank=True, help_text='Site-specific volume override (uses seed_keyword.volume if not set)', null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='keywords',
|
||||
name='difficulty_override',
|
||||
field=models.IntegerField(blank=True, help_text='Site-specific difficulty override (uses seed_keyword.difficulty if not set)', null=True),
|
||||
),
|
||||
# Make seed_keyword required (after data migration if needed)
|
||||
migrations.AlterField(
|
||||
model_name='keywords',
|
||||
name='seed_keyword',
|
||||
field=models.ForeignKey(
|
||||
help_text='Reference to the global seed keyword',
|
||||
on_delete=django.db.models.deletion.PROTECT,
|
||||
related_name='site_keywords',
|
||||
to='igny8_core_auth.seedkeyword'
|
||||
),
|
||||
),
|
||||
# Add unique constraint
|
||||
migrations.AlterUniqueTogether(
|
||||
name='keywords',
|
||||
unique_together={('seed_keyword', 'site', 'sector')},
|
||||
),
|
||||
# Update indexes
|
||||
migrations.AlterIndexTogether(
|
||||
name='keywords',
|
||||
index_together=set(),
|
||||
),
|
||||
# Add new indexes
|
||||
migrations.AddIndex(
|
||||
model_name='keywords',
|
||||
index=models.Index(fields=['seed_keyword'], name='igny8_keyw_seed_k_12345_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='keywords',
|
||||
index=models.Index(fields=['seed_keyword', 'site', 'sector'], name='igny8_keyw_seed_si_67890_idx'),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-09 21:38
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('planner', '0004_add_seed_keyword_to_keywords'),
|
||||
('planner', '0006_add_seed_keyword_to_keywords'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
]
|
||||
@@ -1,74 +0,0 @@
|
||||
#
|
||||
# Generated by Django 5.2.8 on 2025-11-19
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
def default_json():
|
||||
return {}
|
||||
|
||||
|
||||
def default_list():
|
||||
return []
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('site_building', '0003_workflow_and_taxonomies'),
|
||||
('planner', '0007_merge_20251109_2138'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='clusters',
|
||||
name='context_type',
|
||||
field=models.CharField(choices=[('topic', 'Topic Cluster'), ('attribute', 'Attribute Cluster'), ('service_line', 'Service Line')], default='topic', help_text='Primary dimension for this cluster (topic, attribute, service line)', max_length=50),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='clusters',
|
||||
name='dimension_meta',
|
||||
field=models.JSONField(blank=True, default=default_json, help_text='Extended metadata (taxonomy hints, attribute suggestions, coverage targets)'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='contentideas',
|
||||
name='cluster_role',
|
||||
field=models.CharField(choices=[('hub', 'Hub Page'), ('supporting', 'Supporting Page'), ('attribute', 'Attribute Page')], default='hub', help_text='Role within the cluster-driven sitemap', max_length=50),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='contentideas',
|
||||
name='site_entity_type',
|
||||
field=models.CharField(choices=[('page', 'Site Page'), ('blog_post', 'Blog Post'), ('product', 'Product'), ('service', 'Service'), ('taxonomy', 'Taxonomy Page')], default='page', help_text='Target entity type when promoting idea into tasks/pages', max_length=50),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='contentideas',
|
||||
name='taxonomy',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text='Optional taxonomy association when derived from blueprint planning',
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='content_ideas',
|
||||
to='site_building.SiteBlueprintTaxonomy',
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='keywords',
|
||||
name='attribute_values',
|
||||
field=models.JSONField(blank=True, default=default_list, help_text='Optional attribute metadata (e.g., product specs, service modifiers)'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='clusters',
|
||||
index=models.Index(fields=['context_type'], name='planner_cl_context__2ed54f_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='contentideas',
|
||||
index=models.Index(fields=['site_entity_type'], name='planner_co_site_ent_d3183c_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='contentideas',
|
||||
index=models.Index(fields=['cluster_role'], name='planner_co_cluster__f97a65_idx'),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.2.7 on 2025-11-02 21:42
|
||||
# Generated by Django 5.2.8 on 2025-11-20 23:27
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
@@ -16,42 +16,196 @@ class Migration(migrations.Migration):
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='SystemLog',
|
||||
name='SystemSettings',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('key', models.CharField(db_index=True, help_text='Settings key identifier', max_length=255, unique=True)),
|
||||
('value', models.JSONField(default=dict, help_text='Settings value as JSON')),
|
||||
('description', models.TextField(blank=True, help_text='Description of this setting')),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('module', models.CharField(choices=[('planner', 'Planner'), ('writer', 'Writer'), ('thinker', 'Thinker'), ('ai', 'AI Pipeline'), ('wp_bridge', 'WordPress Bridge'), ('system', 'System')], db_index=True, max_length=50)),
|
||||
('level', models.CharField(choices=[('info', 'Info'), ('warning', 'Warning'), ('error', 'Error'), ('success', 'Success')], db_index=True, default='info', max_length=20)),
|
||||
('action', models.CharField(max_length=255)),
|
||||
('message', models.TextField()),
|
||||
('metadata', models.JSONField(blank=True, default=dict)),
|
||||
('tenant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.tenant')),
|
||||
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_system_logs',
|
||||
'ordering': ['-created_at'],
|
||||
'indexes': [models.Index(fields=['tenant', 'module', 'level', '-created_at'], name='igny8_syste_tenant__1a6cda_idx')],
|
||||
'db_table': 'igny8_system_settings',
|
||||
'ordering': ['key'],
|
||||
'indexes': [models.Index(fields=['key'], name='igny8_syste_key_20500b_idx')],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SystemStatus',
|
||||
name='AccountSettings',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('config', models.JSONField(default=dict, help_text='Settings configuration as JSON')),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('key', models.CharField(db_index=True, help_text='Settings key identifier', max_length=255)),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.account')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_account_settings',
|
||||
'ordering': ['key'],
|
||||
'indexes': [models.Index(fields=['account', 'key'], name='igny8_accou_tenant__308b8f_idx')],
|
||||
'unique_together': {('account', 'key')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AIPrompt',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('prompt_type', models.CharField(choices=[('clustering', 'Clustering'), ('ideas', 'Ideas Generation'), ('content_generation', 'Content Generation'), ('image_prompt_extraction', 'Image Prompt Extraction'), ('image_prompt_template', 'Image Prompt Template'), ('negative_prompt', 'Negative Prompt'), ('site_structure_generation', 'Site Structure Generation'), ('product_generation', 'Product Content Generation'), ('service_generation', 'Service Page Generation'), ('taxonomy_generation', 'Taxonomy Generation')], db_index=True, max_length=50)),
|
||||
('prompt_value', models.TextField(help_text='The prompt template text')),
|
||||
('default_prompt', models.TextField(help_text='Default prompt value (for reset)')),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.account')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_ai_prompts',
|
||||
'ordering': ['prompt_type'],
|
||||
'indexes': [models.Index(fields=['prompt_type'], name='igny8_ai_pr_prompt__4b2dbe_idx'), models.Index(fields=['account', 'prompt_type'], name='igny8_ai_pr_tenant__9e7b95_idx')],
|
||||
'unique_together': {('account', 'prompt_type')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AISettings',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('integration_type', models.CharField(db_index=True, help_text="Integration type (e.g., 'openai', 'runware')", max_length=50)),
|
||||
('config', models.JSONField(default=dict, help_text='Integration configuration (API keys, settings, etc.)')),
|
||||
('model_preferences', models.JSONField(default=dict, help_text='Model preferences per operation type')),
|
||||
('cost_limits', models.JSONField(default=dict, help_text='Cost limits and budgets')),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.account')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_ai_settings',
|
||||
'ordering': ['integration_type'],
|
||||
'indexes': [models.Index(fields=['integration_type'], name='igny8_ai_se_integra_4f0b21_idx'), models.Index(fields=['account', 'integration_type'], name='igny8_ai_se_tenant__05ae98_idx')],
|
||||
'unique_together': {('account', 'integration_type')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='AuthorProfile',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(help_text="Profile name (e.g., 'SaaS B2B Informative')", max_length=255)),
|
||||
('description', models.TextField(blank=True, help_text='Description of the writing style')),
|
||||
('tone', models.CharField(help_text="Writing tone (e.g., 'Professional', 'Casual', 'Technical', 'Conversational')", max_length=100)),
|
||||
('language', models.CharField(default='en', help_text="Language code (e.g., 'en', 'es', 'fr')", max_length=50)),
|
||||
('structure_template', models.JSONField(default=dict, help_text='Structure template defining content sections and their order')),
|
||||
('is_active', models.BooleanField(db_index=True, default=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.account')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Author Profile',
|
||||
'verbose_name_plural': 'Author Profiles',
|
||||
'db_table': 'igny8_author_profiles',
|
||||
'ordering': ['name'],
|
||||
'indexes': [models.Index(fields=['account', 'is_active'], name='igny8_autho_tenant__97d2c2_idx'), models.Index(fields=['name'], name='igny8_autho_name_8295f3_idx')],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='IntegrationSettings',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('integration_type', models.CharField(choices=[('openai', 'OpenAI'), ('runware', 'Runware'), ('gsc', 'Google Search Console'), ('image_generation', 'Image Generation Service')], db_index=True, max_length=50)),
|
||||
('config', models.JSONField(default=dict, help_text='Integration configuration (API keys, settings, etc.)')),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.account')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_integration_settings',
|
||||
'ordering': ['integration_type'],
|
||||
'indexes': [models.Index(fields=['integration_type'], name='igny8_integ_integra_5e382e_idx'), models.Index(fields=['account', 'integration_type'], name='igny8_integ_tenant__5da472_idx')],
|
||||
'unique_together': {('account', 'integration_type')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ModuleEnableSettings',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('component', models.CharField(db_index=True, max_length=100)),
|
||||
('status', models.CharField(choices=[('healthy', 'Healthy'), ('warning', 'Warning'), ('error', 'Error'), ('maintenance', 'Maintenance')], default='healthy', max_length=20)),
|
||||
('message', models.TextField(blank=True)),
|
||||
('last_check', models.DateTimeField(auto_now=True)),
|
||||
('metadata', models.JSONField(blank=True, default=dict)),
|
||||
('tenant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.tenant')),
|
||||
('planner_enabled', models.BooleanField(default=True, help_text='Enable Planner module')),
|
||||
('writer_enabled', models.BooleanField(default=True, help_text='Enable Writer module')),
|
||||
('thinker_enabled', models.BooleanField(default=True, help_text='Enable Thinker module')),
|
||||
('automation_enabled', models.BooleanField(default=True, help_text='Enable Automation module')),
|
||||
('site_builder_enabled', models.BooleanField(default=True, help_text='Enable Site Builder module')),
|
||||
('linker_enabled', models.BooleanField(default=True, help_text='Enable Linker module')),
|
||||
('optimizer_enabled', models.BooleanField(default=True, help_text='Enable Optimizer module')),
|
||||
('publisher_enabled', models.BooleanField(default=True, help_text='Enable Publisher module')),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.account')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_system_status',
|
||||
'indexes': [models.Index(fields=['tenant', 'status'], name='igny8_syste_tenant__0e1889_idx')],
|
||||
'unique_together': {('tenant', 'component')},
|
||||
'db_table': 'igny8_module_enable_settings',
|
||||
'unique_together': {('account',)},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ModuleSettings',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('config', models.JSONField(default=dict, help_text='Settings configuration as JSON')),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('module_name', models.CharField(db_index=True, help_text="Module name (e.g., 'planner', 'writer')", max_length=100)),
|
||||
('key', models.CharField(db_index=True, help_text='Settings key identifier', max_length=255)),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.account')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_module_settings',
|
||||
'ordering': ['module_name', 'key'],
|
||||
'indexes': [models.Index(fields=['account', 'module_name', 'key'], name='igny8_modul_tenant__21ee25_idx'), models.Index(fields=['module_name', 'key'], name='igny8_modul_module__95373a_idx')],
|
||||
'unique_together': {('account', 'module_name', 'key')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Strategy',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(help_text='Strategy name', max_length=255)),
|
||||
('description', models.TextField(blank=True, help_text='Description of the content strategy')),
|
||||
('prompt_types', models.JSONField(default=list, help_text="List of prompt types to use (e.g., ['clustering', 'ideas', 'content_generation'])")),
|
||||
('section_logic', models.JSONField(default=dict, help_text='Section logic configuration defining content structure and flow')),
|
||||
('is_active', models.BooleanField(db_index=True, default=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('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(blank=True, help_text='Optional: Link strategy to a specific sector', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='strategies', to='igny8_core_auth.sector')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Strategy',
|
||||
'verbose_name_plural': 'Strategies',
|
||||
'db_table': 'igny8_strategies',
|
||||
'ordering': ['name'],
|
||||
'indexes': [models.Index(fields=['account', 'is_active'], name='igny8_strat_tenant__344de9_idx'), models.Index(fields=['account', 'sector'], name='igny8_strat_tenant__279cfa_idx'), models.Index(fields=['name'], name='igny8_strat_name_8fe823_idx')],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UserSettings',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('key', models.CharField(db_index=True, help_text='Settings key identifier', max_length=255)),
|
||||
('value', models.JSONField(default=dict, help_text='Settings value as JSON')),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='user_settings', to='igny8_core_auth.account')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_settings', to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_user_settings',
|
||||
'ordering': ['key'],
|
||||
'indexes': [models.Index(fields=['user', 'account', 'key'], name='igny8_user__user_id_ac09d9_idx'), models.Index(fields=['account', 'key'], name='igny8_user__tenant__01033d_idx')],
|
||||
'unique_together': {('user', 'account', 'key')},
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
# Generated migration for IntegrationSettings and AIPrompt models
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('igny8_core_auth', '0001_initial'),
|
||||
('system', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AIPrompt',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('prompt_type', models.CharField(choices=[('clustering', 'Clustering'), ('ideas', 'Ideas Generation'), ('content_generation', 'Content Generation'), ('image_prompt_template', 'Image Prompt Template'), ('negative_prompt', 'Negative Prompt')], db_index=True, max_length=50)),
|
||||
('prompt_value', models.TextField(help_text='The prompt template text')),
|
||||
('default_prompt', models.TextField(help_text='Default prompt value (for reset)')),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('tenant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.tenant')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_ai_prompts',
|
||||
'ordering': ['prompt_type'],
|
||||
'unique_together': {('tenant', 'prompt_type')},
|
||||
'indexes': [
|
||||
models.Index(fields=['prompt_type'], name='igny8_ai_pr_prompt__idx'),
|
||||
models.Index(fields=['tenant', 'prompt_type'], name='igny8_ai_pr_tenant__idx'),
|
||||
],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='IntegrationSettings',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('integration_type', models.CharField(choices=[('openai', 'OpenAI'), ('runware', 'Runware'), ('gsc', 'Google Search Console')], db_index=True, max_length=50)),
|
||||
('config', models.JSONField(default=dict, help_text='Integration configuration (API keys, settings, etc.)')),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('tenant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.tenant')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_integration_settings',
|
||||
'ordering': ['integration_type'],
|
||||
'unique_together': {('tenant', 'integration_type')},
|
||||
'indexes': [
|
||||
models.Index(fields=['integration_type'], name='igny8_integ_integra_idx'),
|
||||
models.Index(fields=['tenant', 'integration_type'], name='igny8_integ_tenant__idx'),
|
||||
],
|
||||
},
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
# Generated migration to add image_generation integration type
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('system', '0002_integration_settings_ai_prompts'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='integrationsettings',
|
||||
name='integration_type',
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
('openai', 'OpenAI'),
|
||||
('runware', 'Runware'),
|
||||
('gsc', 'Google Search Console'),
|
||||
('image_generation', 'Image Generation Service'),
|
||||
],
|
||||
db_index=True,
|
||||
max_length=50
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-07 10:06
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('igny8_core_auth', '0008_passwordresettoken_alter_industry_options_and_more'),
|
||||
('system', '0003_add_image_generation_integration_type'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AISettings',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('integration_type', models.CharField(db_index=True, help_text="Integration type (e.g., 'openai', 'runware')", max_length=50)),
|
||||
('config', models.JSONField(default=dict, help_text='Integration configuration (API keys, settings, etc.)')),
|
||||
('model_preferences', models.JSONField(default=dict, help_text='Model preferences per operation type')),
|
||||
('cost_limits', models.JSONField(default=dict, help_text='Cost limits and budgets')),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_ai_settings',
|
||||
'ordering': ['integration_type'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ModuleSettings',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('config', models.JSONField(default=dict, help_text='Settings configuration as JSON')),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('module_name', models.CharField(db_index=True, help_text="Module name (e.g., 'planner', 'writer')", max_length=100)),
|
||||
('key', models.CharField(db_index=True, help_text='Settings key identifier', max_length=255)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_module_settings',
|
||||
'ordering': ['module_name', 'key'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SystemSettings',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('key', models.CharField(db_index=True, help_text='Settings key identifier', max_length=255, unique=True)),
|
||||
('value', models.JSONField(default=dict, help_text='Settings value as JSON')),
|
||||
('description', models.TextField(blank=True, help_text='Description of this setting')),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_system_settings',
|
||||
'ordering': ['key'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TenantSettings',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('config', models.JSONField(default=dict, help_text='Settings configuration as JSON')),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('key', models.CharField(db_index=True, help_text='Settings key identifier', max_length=255)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_tenant_settings',
|
||||
'ordering': ['key'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='UserSettings',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('key', models.CharField(db_index=True, help_text='Settings key identifier', max_length=255)),
|
||||
('value', models.JSONField(default=dict, help_text='Settings value as JSON')),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_user_settings',
|
||||
'ordering': ['key'],
|
||||
},
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name='aiprompt',
|
||||
new_name='igny8_ai_pr_prompt__4b2dbe_idx',
|
||||
old_name='igny8_ai_pr_prompt__idx',
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name='aiprompt',
|
||||
new_name='igny8_ai_pr_tenant__9e7b95_idx',
|
||||
old_name='igny8_ai_pr_tenant__idx',
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name='integrationsettings',
|
||||
new_name='igny8_integ_integra_5e382e_idx',
|
||||
old_name='igny8_integ_integra_idx',
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name='integrationsettings',
|
||||
new_name='igny8_integ_tenant__5da472_idx',
|
||||
old_name='igny8_integ_tenant__idx',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='aiprompt',
|
||||
name='prompt_type',
|
||||
field=models.CharField(choices=[('clustering', 'Clustering'), ('ideas', 'Ideas Generation'), ('content_generation', 'Content Generation'), ('image_prompt_extraction', 'Image Prompt Extraction'), ('image_prompt_template', 'Image Prompt Template'), ('negative_prompt', 'Negative Prompt')], db_index=True, max_length=50),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='aisettings',
|
||||
name='tenant',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.tenant'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='modulesettings',
|
||||
name='tenant',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.tenant'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='systemsettings',
|
||||
index=models.Index(fields=['key'], name='igny8_syste_key_20500b_idx'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='tenantsettings',
|
||||
name='tenant',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.tenant'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='usersettings',
|
||||
name='tenant',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_settings', to='igny8_core_auth.tenant'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='usersettings',
|
||||
name='user',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='user_settings', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.RunSQL(
|
||||
sql="DROP TABLE IF EXISTS igny8_system_logs CASCADE",
|
||||
reverse_sql=migrations.RunSQL.noop,
|
||||
),
|
||||
migrations.RunSQL(
|
||||
sql="DROP TABLE IF EXISTS igny8_system_status CASCADE",
|
||||
reverse_sql=migrations.RunSQL.noop,
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='aisettings',
|
||||
index=models.Index(fields=['integration_type'], name='igny8_ai_se_integra_4f0b21_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='aisettings',
|
||||
index=models.Index(fields=['tenant', 'integration_type'], name='igny8_ai_se_tenant__05ae98_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='aisettings',
|
||||
unique_together={('tenant', 'integration_type')},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='modulesettings',
|
||||
index=models.Index(fields=['tenant', 'module_name', 'key'], name='igny8_modul_tenant__21ee25_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='modulesettings',
|
||||
index=models.Index(fields=['module_name', 'key'], name='igny8_modul_module__95373a_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='modulesettings',
|
||||
unique_together={('tenant', 'module_name', 'key')},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='tenantsettings',
|
||||
index=models.Index(fields=['tenant', 'key'], name='igny8_tenan_tenant__8ce0b3_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='tenantsettings',
|
||||
unique_together={('tenant', 'key')},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='usersettings',
|
||||
index=models.Index(fields=['user', 'tenant', 'key'], name='igny8_user__user_id_ac09d9_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='usersettings',
|
||||
index=models.Index(fields=['tenant', 'key'], name='igny8_user__tenant__01033d_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='usersettings',
|
||||
unique_together={('user', 'tenant', 'key')},
|
||||
),
|
||||
]
|
||||
@@ -1,77 +0,0 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-07 11:34
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('igny8_core_auth', '0010_add_seed_keyword'),
|
||||
('system', '0004_aisettings_modulesettings_systemsettings_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='AuthorProfile',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(help_text="Profile name (e.g., 'SaaS B2B Informative')", max_length=255)),
|
||||
('description', models.TextField(blank=True, help_text='Description of the writing style')),
|
||||
('tone', models.CharField(help_text="Writing tone (e.g., 'Professional', 'Casual', 'Technical', 'Conversational')", max_length=100)),
|
||||
('language', models.CharField(default='en', help_text="Language code (e.g., 'en', 'es', 'fr')", max_length=50)),
|
||||
('structure_template', models.JSONField(default=dict, help_text='Structure template defining content sections and their order')),
|
||||
('is_active', models.BooleanField(db_index=True, default=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('tenant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.tenant')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Author Profile',
|
||||
'verbose_name_plural': 'Author Profiles',
|
||||
'db_table': 'igny8_author_profiles',
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Strategy',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(help_text='Strategy name', max_length=255)),
|
||||
('description', models.TextField(blank=True, help_text='Description of the content strategy')),
|
||||
('prompt_types', models.JSONField(default=list, help_text="List of prompt types to use (e.g., ['clustering', 'ideas', 'content_generation'])")),
|
||||
('section_logic', models.JSONField(default=dict, help_text='Section logic configuration defining content structure and flow')),
|
||||
('is_active', models.BooleanField(db_index=True, default=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('sector', models.ForeignKey(blank=True, help_text='Optional: Link strategy to a specific sector', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='strategies', to='igny8_core_auth.sector')),
|
||||
('tenant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.tenant')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Strategy',
|
||||
'verbose_name_plural': 'Strategies',
|
||||
'db_table': 'igny8_strategies',
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='authorprofile',
|
||||
index=models.Index(fields=['tenant', 'is_active'], name='igny8_autho_tenant__97d2c2_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='authorprofile',
|
||||
index=models.Index(fields=['name'], name='igny8_autho_name_8295f3_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='strategy',
|
||||
index=models.Index(fields=['tenant', 'is_active'], name='igny8_strat_tenant__344de9_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='strategy',
|
||||
index=models.Index(fields=['tenant', 'sector'], name='igny8_strat_tenant__279cfa_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='strategy',
|
||||
index=models.Index(fields=['name'], name='igny8_strat_name_8fe823_idx'),
|
||||
),
|
||||
]
|
||||
@@ -1,71 +0,0 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-07 14:17
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('system', '0005_add_author_profile_strategy'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
# Remove unique_together constraint if it exists and table exists
|
||||
migrations.RunSQL(
|
||||
"""
|
||||
DO $$
|
||||
BEGIN
|
||||
-- Drop unique constraint if table and constraint exist
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.tables
|
||||
WHERE table_name = 'igny8_system_status'
|
||||
) AND EXISTS (
|
||||
SELECT 1 FROM pg_constraint
|
||||
WHERE conname LIKE '%systemstatus%tenant_id%component%'
|
||||
) THEN
|
||||
ALTER TABLE igny8_system_status DROP CONSTRAINT IF EXISTS igny8_system_status_tenant_id_component_key;
|
||||
END IF;
|
||||
END $$;
|
||||
""",
|
||||
reverse_sql=migrations.RunSQL.noop
|
||||
),
|
||||
# Only remove field if table exists
|
||||
migrations.RunSQL(
|
||||
"""
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.tables
|
||||
WHERE table_name = 'igny8_system_status'
|
||||
) AND EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'igny8_system_status' AND column_name = 'tenant_id'
|
||||
) THEN
|
||||
ALTER TABLE igny8_system_status DROP COLUMN IF EXISTS tenant_id;
|
||||
END IF;
|
||||
END $$;
|
||||
""",
|
||||
reverse_sql=migrations.RunSQL.noop
|
||||
),
|
||||
# Delete models only if tables exist
|
||||
migrations.RunSQL(
|
||||
"""
|
||||
DO $$
|
||||
BEGIN
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.tables
|
||||
WHERE table_name = 'igny8_system_logs'
|
||||
) THEN
|
||||
DROP TABLE IF EXISTS igny8_system_logs CASCADE;
|
||||
END IF;
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM information_schema.tables
|
||||
WHERE table_name = 'igny8_system_status'
|
||||
) THEN
|
||||
DROP TABLE IF EXISTS igny8_system_status CASCADE;
|
||||
END IF;
|
||||
END $$;
|
||||
""",
|
||||
reverse_sql=migrations.RunSQL.noop
|
||||
),
|
||||
]
|
||||
@@ -1,39 +0,0 @@
|
||||
# Generated manually for Phase 0: Module Enable Settings
|
||||
# Using RunSQL to create table directly to avoid model resolution issues with new unified API model
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('system', '0006_alter_systemstatus_unique_together_and_more'),
|
||||
('igny8_core_auth', '0008_passwordresettoken_alter_industry_options_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
# Create table using raw SQL to avoid model resolution issues
|
||||
# The model state is automatically discovered from models.py
|
||||
migrations.RunSQL(
|
||||
sql="""
|
||||
CREATE TABLE IF NOT EXISTS igny8_module_enable_settings (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
planner_enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
writer_enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
thinker_enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
automation_enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
site_builder_enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
linker_enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
optimizer_enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
publisher_enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
tenant_id BIGINT NOT NULL REFERENCES igny8_tenants(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS igny8_module_enable_settings_tenant_id_idx ON igny8_module_enable_settings(tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS igny8_module_enable_settings_account_created_idx ON igny8_module_enable_settings(tenant_id, created_at);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS unique_account_module_enable_settings ON igny8_module_enable_settings(tenant_id);
|
||||
""",
|
||||
reverse_sql="DROP TABLE IF EXISTS igny8_module_enable_settings CASCADE;",
|
||||
),
|
||||
]
|
||||
@@ -1,31 +0,0 @@
|
||||
# Generated manually for Phase 7: Prompt Management UI
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('system', '0007_add_module_enable_settings'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='aiprompt',
|
||||
name='prompt_type',
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
('clustering', 'Clustering'),
|
||||
('ideas', 'Ideas Generation'),
|
||||
('content_generation', 'Content Generation'),
|
||||
('image_prompt_extraction', 'Image Prompt Extraction'),
|
||||
('image_prompt_template', 'Image Prompt Template'),
|
||||
('negative_prompt', 'Negative Prompt'),
|
||||
('site_structure_generation', 'Site Structure Generation'),
|
||||
],
|
||||
db_index=True,
|
||||
max_length=50
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
# Generated manually for Phase 8: Universal Content Types
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('system', '0008_add_site_structure_generation_prompt_type'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='aiprompt',
|
||||
name='prompt_type',
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
('clustering', 'Clustering'),
|
||||
('ideas', 'Ideas Generation'),
|
||||
('content_generation', 'Content Generation'),
|
||||
('image_prompt_extraction', 'Image Prompt Extraction'),
|
||||
('image_prompt_template', 'Image Prompt Template'),
|
||||
('negative_prompt', 'Negative Prompt'),
|
||||
('site_structure_generation', 'Site Structure Generation'),
|
||||
('product_generation', 'Product Content Generation'),
|
||||
('service_generation', 'Service Page Generation'),
|
||||
('taxonomy_generation', 'Taxonomy Generation'),
|
||||
],
|
||||
db_index=True,
|
||||
max_length=50
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# Generated by Django 5.2.7 on 2025-11-03 13:22
|
||||
# Generated by Django 5.2.8 on 2025-11-20 23:27
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
@@ -9,11 +10,53 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('igny8_core_auth', '0003_alter_user_role'),
|
||||
('planner', '0003_alter_clusters_sector_alter_clusters_site_and_more'),
|
||||
('igny8_core_auth', '0001_initial'),
|
||||
('planner', '0001_initial'),
|
||||
('site_building', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Content',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('html_content', models.TextField(help_text='Final AI-generated HTML content')),
|
||||
('word_count', models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0)])),
|
||||
('metadata', models.JSONField(default=dict, help_text='Additional metadata (SEO, structure, etc.)')),
|
||||
('title', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('meta_title', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('meta_description', models.TextField(blank=True, null=True)),
|
||||
('primary_keyword', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('secondary_keywords', models.JSONField(blank=True, default=list, help_text='List of secondary keywords')),
|
||||
('tags', models.JSONField(blank=True, default=list, help_text='List of tags')),
|
||||
('categories', models.JSONField(blank=True, default=list, help_text='List of categories')),
|
||||
('status', models.CharField(choices=[('draft', 'Draft'), ('review', 'Review'), ('publish', 'Publish')], default='draft', help_text='Content workflow status (draft, review, publish)', max_length=50)),
|
||||
('generated_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('source', models.CharField(choices=[('igny8', 'IGNY8 Generated'), ('wordpress', 'WordPress Synced'), ('shopify', 'Shopify Synced'), ('custom', 'Custom API Synced')], db_index=True, default='igny8', help_text='Source of the content', max_length=50)),
|
||||
('sync_status', models.CharField(choices=[('native', 'Native IGNY8 Content'), ('imported', 'Imported from External'), ('synced', 'Synced from External')], db_index=True, default='native', help_text='Sync status of the content', max_length=50)),
|
||||
('external_id', models.CharField(blank=True, help_text='External platform ID', max_length=255, null=True)),
|
||||
('external_url', models.URLField(blank=True, help_text='External platform URL', null=True)),
|
||||
('sync_metadata', models.JSONField(blank=True, default=dict, help_text='Platform-specific sync metadata')),
|
||||
('internal_links', models.JSONField(blank=True, default=list, help_text='Internal links added by linker')),
|
||||
('linker_version', models.IntegerField(default=0, help_text='Version of linker processing')),
|
||||
('optimizer_version', models.IntegerField(default=0, help_text='Version of optimizer processing')),
|
||||
('optimization_scores', models.JSONField(blank=True, default=dict, help_text='Optimization scores (SEO, readability, engagement)')),
|
||||
('entity_type', models.CharField(choices=[('blog_post', 'Blog Post'), ('article', 'Article'), ('product', 'Product'), ('service', 'Service Page'), ('taxonomy', 'Taxonomy Page'), ('page', 'Page')], db_index=True, default='blog_post', help_text='Type of content entity', max_length=50)),
|
||||
('json_blocks', models.JSONField(blank=True, default=list, help_text='Structured content blocks (for products, services, taxonomies)')),
|
||||
('structure_data', models.JSONField(blank=True, default=dict, help_text='Content structure data (metadata, schema, etc.)')),
|
||||
('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')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Content',
|
||||
'verbose_name_plural': 'Contents',
|
||||
'db_table': 'igny8_content',
|
||||
'ordering': ['-generated_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Tasks',
|
||||
fields=[
|
||||
@@ -23,7 +66,9 @@ class Migration(migrations.Migration):
|
||||
('keywords', models.CharField(blank=True, max_length=500)),
|
||||
('content_structure', models.CharField(choices=[('cluster_hub', 'Cluster Hub'), ('landing_page', 'Landing Page'), ('pillar_page', 'Pillar Page'), ('supporting_page', 'Supporting Page')], default='blog_post', max_length=50)),
|
||||
('content_type', models.CharField(choices=[('blog_post', 'Blog Post'), ('article', 'Article'), ('guide', 'Guide'), ('tutorial', 'Tutorial')], default='blog_post', max_length=50)),
|
||||
('status', models.CharField(choices=[('queued', 'Queued'), ('in_progress', 'In Progress'), ('draft', 'Draft'), ('review', 'Review'), ('published', 'Published'), ('completed', 'Completed')], default='queued', max_length=50)),
|
||||
('status', models.CharField(choices=[('queued', 'Queued'), ('completed', 'Completed')], default='queued', max_length=50)),
|
||||
('entity_type', models.CharField(blank=True, choices=[('blog_post', 'Blog Post'), ('article', 'Article'), ('product', 'Product'), ('service', 'Service Page'), ('taxonomy', 'Taxonomy Page'), ('page', 'Page')], db_index=True, default='blog_post', help_text='Type of content entity (inherited from idea/blueprint)', max_length=50, null=True)),
|
||||
('cluster_role', models.CharField(blank=True, choices=[('hub', 'Hub Page'), ('supporting', 'Supporting Page'), ('attribute', 'Attribute Page')], default='hub', help_text='Role within the cluster-driven sitemap', max_length=50, null=True)),
|
||||
('content', models.TextField(blank=True, null=True)),
|
||||
('word_count', models.IntegerField(default=0)),
|
||||
('meta_title', models.CharField(blank=True, max_length=255, null=True)),
|
||||
@@ -32,39 +77,110 @@ class Migration(migrations.Migration):
|
||||
('post_url', models.URLField(blank=True, null=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.account')),
|
||||
('cluster', models.ForeignKey(blank=True, limit_choices_to={'sector': models.F('sector')}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tasks', to='planner.clusters')),
|
||||
('idea', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tasks', to='planner.contentideas')),
|
||||
('keyword_objects', models.ManyToManyField(blank=True, help_text='Individual keywords linked to this task', related_name='tasks', to='planner.keywords')),
|
||||
('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')),
|
||||
('tenant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.tenant')),
|
||||
('taxonomy', models.ForeignKey(blank=True, help_text='Taxonomy association when derived from blueprint planning', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tasks', to='site_building.siteblueprinttaxonomy')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Task',
|
||||
'verbose_name_plural': 'Tasks',
|
||||
'db_table': 'igny8_tasks',
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TaskImages',
|
||||
name='Images',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('image_type', models.CharField(choices=[('featured', 'Featured Image'), ('desktop', 'Desktop Image'), ('mobile', 'Mobile Image'), ('in_article', 'In-Article Image')], default='featured', max_length=50)),
|
||||
('image_url', models.URLField(blank=True, null=True)),
|
||||
('image_path', models.CharField(blank=True, max_length=500, null=True)),
|
||||
('prompt', models.TextField(blank=True, null=True)),
|
||||
('status', models.CharField(default='pending', max_length=50)),
|
||||
('position', models.IntegerField(default=0)),
|
||||
('image_url', models.CharField(blank=True, help_text='URL of the generated/stored image', max_length=500, null=True)),
|
||||
('image_path', models.CharField(blank=True, help_text='Local path if stored locally', max_length=500, null=True)),
|
||||
('prompt', models.TextField(blank=True, help_text='Image generation prompt used', null=True)),
|
||||
('status', models.CharField(default='pending', help_text='Status: pending, generated, failed', max_length=50)),
|
||||
('position', models.IntegerField(default=0, help_text='Position for in-article images ordering')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.account')),
|
||||
('content', models.ForeignKey(blank=True, help_text='The content this image belongs to (preferred)', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='images', 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')),
|
||||
('tenant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.tenant')),
|
||||
('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='images', to='writer.tasks')),
|
||||
('task', models.ForeignKey(blank=True, help_text='The task this image belongs to (legacy, use content instead)', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='images', to='writer.tasks')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_task_images',
|
||||
'ordering': ['task', 'position', '-created_at'],
|
||||
'verbose_name': 'Image',
|
||||
'verbose_name_plural': 'Images',
|
||||
'db_table': 'igny8_images',
|
||||
'ordering': ['content', 'position', '-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ContentTaxonomyMap',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('source', models.CharField(choices=[('blueprint', 'Blueprint'), ('manual', 'Manual'), ('import', 'Import')], default='blueprint', max_length=50)),
|
||||
('metadata', models.JSONField(blank=True, default=dict)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.account')),
|
||||
('content', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='taxonomy_mappings', 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')),
|
||||
('taxonomy', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='content_mappings', to='site_building.siteblueprinttaxonomy')),
|
||||
('task', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='taxonomy_mappings', to='writer.tasks')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_content_taxonomy_map',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ContentClusterMap',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('role', models.CharField(choices=[('hub', 'Hub Page'), ('supporting', 'Supporting Page'), ('attribute', 'Attribute Page')], default='hub', max_length=50)),
|
||||
('source', models.CharField(choices=[('blueprint', 'Blueprint'), ('manual', 'Manual'), ('import', 'Import')], default='blueprint', max_length=50)),
|
||||
('metadata', models.JSONField(blank=True, default=dict)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.account')),
|
||||
('cluster', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='content_mappings', to='planner.clusters')),
|
||||
('content', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='cluster_mappings', 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')),
|
||||
('task', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='cluster_mappings', to='writer.tasks')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_content_cluster_map',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ContentAttributeMap',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=120)),
|
||||
('value', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('source', models.CharField(choices=[('blueprint', 'Blueprint'), ('manual', 'Manual'), ('import', 'Import')], default='blueprint', max_length=50)),
|
||||
('metadata', models.JSONField(blank=True, default=dict)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.account')),
|
||||
('content', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='attribute_mappings', 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')),
|
||||
('task', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='attribute_mappings', to='writer.tasks')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_content_attribute_map',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='content',
|
||||
name='task',
|
||||
field=models.OneToOneField(blank=True, help_text='The task this content belongs to', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='content_record', to='writer.tasks'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='tasks',
|
||||
index=models.Index(fields=['title'], name='igny8_tasks_title_adc50b_idx'),
|
||||
@@ -81,16 +197,104 @@ class Migration(migrations.Migration):
|
||||
model_name='tasks',
|
||||
index=models.Index(fields=['content_type'], name='igny8_tasks_content_343539_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='tasks',
|
||||
index=models.Index(fields=['entity_type'], name='igny8_tasks_entity__1dc185_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='tasks',
|
||||
index=models.Index(fields=['cluster_role'], name='igny8_tasks_cluster_c87903_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='tasks',
|
||||
index=models.Index(fields=['site', 'sector'], name='igny8_tasks_site_id_9880f4_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='taskimages',
|
||||
index=models.Index(fields=['task', 'image_type'], name='igny8_task__task_id_143db4_idx'),
|
||||
model_name='images',
|
||||
index=models.Index(fields=['content', 'image_type'], name='igny8_image_content_830ae4_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='taskimages',
|
||||
index=models.Index(fields=['status'], name='igny8_task__status_4f869f_idx'),
|
||||
model_name='images',
|
||||
index=models.Index(fields=['task', 'image_type'], name='igny8_image_task_id_c80e87_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='images',
|
||||
index=models.Index(fields=['status'], name='igny8_image_status_4a4de2_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='images',
|
||||
index=models.Index(fields=['content', 'position'], name='igny8_image_content_4ab256_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='images',
|
||||
index=models.Index(fields=['task', 'position'], name='igny8_image_task_id_6340e6_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='contenttaxonomymap',
|
||||
index=models.Index(fields=['taxonomy'], name='igny8_conte_taxonom_c3da58_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='contenttaxonomymap',
|
||||
index=models.Index(fields=['content', 'taxonomy'], name='igny8_conte_content_9dc417_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='contenttaxonomymap',
|
||||
index=models.Index(fields=['task', 'taxonomy'], name='igny8_conte_task_id_e94754_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='contenttaxonomymap',
|
||||
unique_together={('content', 'taxonomy')},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='contentclustermap',
|
||||
index=models.Index(fields=['cluster', 'role'], name='igny8_conte_cluster_c12778_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='contentclustermap',
|
||||
index=models.Index(fields=['content', 'role'], name='igny8_conte_content_9fa5cc_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='contentclustermap',
|
||||
index=models.Index(fields=['task', 'role'], name='igny8_conte_task_id_821851_idx'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='contentclustermap',
|
||||
unique_together={('content', 'cluster', 'role')},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='contentattributemap',
|
||||
index=models.Index(fields=['name'], name='igny8_conte_name_f79e3e_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='contentattributemap',
|
||||
index=models.Index(fields=['content', 'name'], name='igny8_conte_content_f255f2_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='contentattributemap',
|
||||
index=models.Index(fields=['task', 'name'], name='igny8_conte_task_id_d60684_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='content',
|
||||
index=models.Index(fields=['task'], name='igny8_conte_task_id_712988_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='content',
|
||||
index=models.Index(fields=['generated_at'], name='igny8_conte_generat_7128df_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='content',
|
||||
index=models.Index(fields=['source'], name='igny8_conte_source_5ef8a1_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='content',
|
||||
index=models.Index(fields=['sync_status'], name='igny8_conte_sync_st_02d5bd_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='content',
|
||||
index=models.Index(fields=['source', 'sync_status'], name='igny8_conte_source_df78d0_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='content',
|
||||
index=models.Index(fields=['entity_type'], name='igny8_conte_entity__f559b3_idx'),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
"""Rename TaskImages, add Content model, and keyword links"""
|
||||
from django.core.validators import MinValueValidator
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('planner', '0004_add_keyword_objects_to_contentideas'),
|
||||
('writer', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameModel(
|
||||
old_name='TaskImages',
|
||||
new_name='Images',
|
||||
),
|
||||
migrations.AlterModelTable(
|
||||
name='images',
|
||||
table='igny8_images',
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='images',
|
||||
options={'ordering': ['task', 'position', '-created_at']},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='tasks',
|
||||
name='keyword_objects',
|
||||
field=models.ManyToManyField(blank=True, help_text='Individual keywords linked to this task', related_name='tasks', to='planner.keywords'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Content',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('html_content', models.TextField(help_text='Final AI-generated HTML content')),
|
||||
('word_count', models.IntegerField(default=0, help_text='Word count of generated content', validators=[MinValueValidator(0)])),
|
||||
('metadata', models.JSONField(default=dict, help_text='Additional metadata (SEO, structure, etc.)')),
|
||||
('generated_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('sector', models.ForeignKey(on_delete=models.CASCADE, related_name='content_set', to='igny8_core_auth.sector')),
|
||||
('site', models.ForeignKey(on_delete=models.CASCADE, related_name='content_set', to='igny8_core_auth.site')),
|
||||
('task', models.OneToOneField(help_text='The task this content belongs to', on_delete=models.CASCADE, related_name='content_record', to='writer.tasks')),
|
||||
('tenant', models.ForeignKey(on_delete=models.CASCADE, related_name='content_set', to='igny8_core_auth.tenant')),
|
||||
],
|
||||
options={
|
||||
'db_table': 'igny8_content',
|
||||
'ordering': ['-generated_at'],
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='images',
|
||||
index=models.Index(fields=['task', 'position'], name='igny8_images_task_id_pos_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='content',
|
||||
index=models.Index(fields=['task'], name='igny8_content_task_id_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='content',
|
||||
index=models.Index(fields=['generated_at'], name='igny8_content_generated_idx'),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-07 10:06
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('igny8_core_auth', '0008_passwordresettoken_alter_industry_options_and_more'),
|
||||
('writer', '0002_rename_taskimages_add_content_and_keywords'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='content',
|
||||
options={'ordering': ['-generated_at'], 'verbose_name': 'Content', 'verbose_name_plural': 'Contents'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='images',
|
||||
options={'ordering': ['task', 'position', '-created_at'], 'verbose_name': 'Image', 'verbose_name_plural': 'Images'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='tasks',
|
||||
options={'ordering': ['-created_at'], 'verbose_name': 'Task', 'verbose_name_plural': 'Tasks'},
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name='content',
|
||||
new_name='igny8_conte_task_id_712988_idx',
|
||||
old_name='igny8_content_task_id_idx',
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name='content',
|
||||
new_name='igny8_conte_generat_7128df_idx',
|
||||
old_name='igny8_content_generated_idx',
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name='images',
|
||||
new_name='igny8_image_task_id_c80e87_idx',
|
||||
old_name='igny8_task__task_id_143db4_idx',
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name='images',
|
||||
new_name='igny8_image_status_4a4de2_idx',
|
||||
old_name='igny8_task__status_4f869f_idx',
|
||||
),
|
||||
migrations.RenameIndex(
|
||||
model_name='images',
|
||||
new_name='igny8_image_task_id_6340e6_idx',
|
||||
old_name='igny8_images_task_id_pos_idx',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='content',
|
||||
name='created_at',
|
||||
field=models.DateTimeField(auto_now_add=True, db_index=True, default=django.utils.timezone.now),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='content',
|
||||
name='sector',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.sector'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='content',
|
||||
name='site',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.site'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='content',
|
||||
name='tenant',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(class)s_set', to='igny8_core_auth.tenant'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='content',
|
||||
name='word_count',
|
||||
field=models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0)]),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='images',
|
||||
name='image_path',
|
||||
field=models.CharField(blank=True, help_text='Local path if stored locally', max_length=500, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='images',
|
||||
name='image_url',
|
||||
field=models.URLField(blank=True, help_text='URL of the generated/stored image', null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='images',
|
||||
name='position',
|
||||
field=models.IntegerField(default=0, help_text='Position for in-article images ordering'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='images',
|
||||
name='prompt',
|
||||
field=models.TextField(blank=True, help_text='Image generation prompt used', null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='images',
|
||||
name='status',
|
||||
field=models.CharField(default='pending', help_text='Status: pending, generated, failed', max_length=50),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='images',
|
||||
name='task',
|
||||
field=models.ForeignKey(help_text='The task this image belongs to', on_delete=django.db.models.deletion.CASCADE, related_name='images', to='writer.tasks'),
|
||||
),
|
||||
]
|
||||
@@ -1,35 +0,0 @@
|
||||
# Generated migration for adding SEO fields to Tasks model
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('writer', '0003_alter_content_options_alter_images_options_and_more'),
|
||||
('igny8_core_auth', '0008_passwordresettoken_alter_industry_options_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='tasks',
|
||||
name='primary_keyword',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='tasks',
|
||||
name='secondary_keywords',
|
||||
field=models.JSONField(blank=True, default=list, help_text='List of secondary keywords'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='tasks',
|
||||
name='tags',
|
||||
field=models.JSONField(blank=True, default=list, help_text='List of tags'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='tasks',
|
||||
name='categories',
|
||||
field=models.JSONField(blank=True, default=list, help_text='List of categories'),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def _normalize_list(value):
|
||||
if not value:
|
||||
return []
|
||||
if isinstance(value, list):
|
||||
return value
|
||||
if isinstance(value, tuple):
|
||||
return list(value)
|
||||
return [value]
|
||||
|
||||
|
||||
def forwards(apps, schema_editor):
|
||||
Tasks = apps.get_model('writer', 'Tasks')
|
||||
Content = apps.get_model('writer', 'Content')
|
||||
|
||||
for task in Tasks.objects.all():
|
||||
account_id = getattr(task, 'account_id', None)
|
||||
if account_id is None and getattr(task, 'account', None):
|
||||
account_id = task.account.id
|
||||
|
||||
site_id = getattr(task, 'site_id', None)
|
||||
if site_id is None and getattr(task, 'site', None):
|
||||
site_id = task.site.id
|
||||
|
||||
sector_id = getattr(task, 'sector_id', None)
|
||||
if sector_id is None and getattr(task, 'sector', None):
|
||||
sector_id = task.sector.id if task.sector else None
|
||||
|
||||
tenant_id = getattr(task, 'tenant_id', None)
|
||||
if tenant_id is None and getattr(task, 'tenant', None):
|
||||
tenant_id = task.tenant.id
|
||||
|
||||
# Prepare defaults for new content record
|
||||
defaults = {
|
||||
'html_content': task.content or '',
|
||||
'word_count': task.word_count or 0,
|
||||
'title': getattr(task, 'title', None),
|
||||
'meta_title': getattr(task, 'meta_title', None),
|
||||
'meta_description': getattr(task, 'meta_description', None),
|
||||
'primary_keyword': getattr(task, 'primary_keyword', None),
|
||||
'secondary_keywords': _normalize_list(getattr(task, 'secondary_keywords', [])),
|
||||
'tags': _normalize_list(getattr(task, 'tags', [])),
|
||||
'categories': _normalize_list(getattr(task, 'categories', [])),
|
||||
'status': 'draft',
|
||||
}
|
||||
|
||||
content_record = Content.objects.filter(task_id=task.id).first()
|
||||
created = False
|
||||
if not content_record:
|
||||
content_record = Content(task_id=task.id, **defaults)
|
||||
created = True
|
||||
|
||||
# Update existing records with the latest information
|
||||
if not created:
|
||||
if task.content:
|
||||
content_record.html_content = task.content
|
||||
if task.word_count:
|
||||
content_record.word_count = task.word_count
|
||||
|
||||
if getattr(task, 'title', None):
|
||||
content_record.title = task.title
|
||||
if getattr(task, 'meta_title', None):
|
||||
content_record.meta_title = task.meta_title
|
||||
if getattr(task, 'meta_description', None):
|
||||
content_record.meta_description = task.meta_description
|
||||
|
||||
if hasattr(task, 'primary_keyword'):
|
||||
content_record.primary_keyword = task.primary_keyword or ''
|
||||
if hasattr(task, 'secondary_keywords'):
|
||||
content_record.secondary_keywords = _normalize_list(task.secondary_keywords)
|
||||
if hasattr(task, 'tags'):
|
||||
content_record.tags = _normalize_list(task.tags)
|
||||
if hasattr(task, 'categories'):
|
||||
content_record.categories = _normalize_list(task.categories)
|
||||
|
||||
if not content_record.status:
|
||||
content_record.status = 'draft'
|
||||
|
||||
# Ensure account/site/sector alignment (save() will also set this)
|
||||
if account_id:
|
||||
content_record.account_id = account_id
|
||||
if site_id:
|
||||
content_record.site_id = site_id
|
||||
if sector_id:
|
||||
content_record.sector_id = sector_id
|
||||
if tenant_id:
|
||||
content_record.tenant_id = tenant_id
|
||||
|
||||
# Preserve existing metadata but ensure it's a dict
|
||||
metadata = content_record.metadata or {}
|
||||
content_record.metadata = metadata
|
||||
|
||||
content_record.save()
|
||||
|
||||
|
||||
def backwards(apps, schema_editor):
|
||||
"""
|
||||
Reverse data migration is intentionally left as a no-op because
|
||||
reverting would require reintroducing the removed fields on Tasks.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('writer', '0004_add_content_seo_fields'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='content',
|
||||
name='categories',
|
||||
field=models.JSONField(blank=True, default=list, help_text='List of categories'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='content',
|
||||
name='meta_description',
|
||||
field=models.TextField(blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='content',
|
||||
name='meta_title',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='content',
|
||||
name='primary_keyword',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='content',
|
||||
name='secondary_keywords',
|
||||
field=models.JSONField(blank=True, default=list, help_text='List of secondary keywords'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='content',
|
||||
name='status',
|
||||
field=models.CharField(default='draft', help_text='Content workflow status (draft, review, published, etc.)', max_length=50),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='content',
|
||||
name='tags',
|
||||
field=models.JSONField(blank=True, default=list, help_text='List of tags'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='content',
|
||||
name='title',
|
||||
field=models.CharField(blank=True, max_length=255, null=True),
|
||||
),
|
||||
migrations.RunPython(forwards, backwards),
|
||||
migrations.RemoveField(
|
||||
model_name='tasks',
|
||||
name='categories',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='tasks',
|
||||
name='primary_keyword',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='tasks',
|
||||
name='secondary_keywords',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='tasks',
|
||||
name='tags',
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
# Generated manually to update status field choices and normalize data
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def normalize_statuses_forward(apps, schema_editor):
|
||||
Tasks = apps.get_model('writer', 'Tasks')
|
||||
Content = apps.get_model('writer', 'Content')
|
||||
|
||||
# Normalize task statuses to queued/completed
|
||||
queued_states = {'queued', 'in_progress'}
|
||||
completed_states = {'draft', 'review', 'published', 'completed'}
|
||||
|
||||
Tasks.objects.filter(status__in=queued_states).update(status='queued')
|
||||
Tasks.objects.filter(status__in=completed_states).update(status='completed')
|
||||
|
||||
# Normalize content statuses: published -> publish (new choice)
|
||||
Content.objects.filter(status='published').update(status='publish')
|
||||
|
||||
|
||||
def normalize_statuses_backward(apps, schema_editor):
|
||||
Tasks = apps.get_model('writer', 'Tasks')
|
||||
Content = apps.get_model('writer', 'Content')
|
||||
|
||||
# Restore pre-normalization values
|
||||
Tasks.objects.filter(status='queued').update(status='queued')
|
||||
Tasks.objects.filter(status='completed').update(status='published')
|
||||
|
||||
Content.objects.filter(status='publish').update(status='published')
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('writer', '0005_move_content_fields_to_content'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(normalize_statuses_forward, normalize_statuses_backward),
|
||||
migrations.AlterField(
|
||||
model_name='tasks',
|
||||
name='status',
|
||||
field=models.CharField(
|
||||
choices=[('queued', 'Queued'), ('completed', 'Completed')],
|
||||
default='queued',
|
||||
max_length=50,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='content',
|
||||
name='status',
|
||||
field=models.CharField(
|
||||
choices=[('draft', 'Draft'), ('review', 'Review'), ('publish', 'Publish')],
|
||||
default='draft',
|
||||
help_text='Content workflow status (draft, review, publish)',
|
||||
max_length=50,
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,83 +0,0 @@
|
||||
# Generated manually for adding content ForeignKey to Images model
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
def migrate_task_to_content(apps, schema_editor):
|
||||
"""Migrate existing Images to use content instead of task"""
|
||||
Images = apps.get_model('writer', 'Images')
|
||||
Content = apps.get_model('writer', 'Content')
|
||||
|
||||
# Update images that have a task with a content_record
|
||||
for image in Images.objects.filter(task__isnull=False, content__isnull=True):
|
||||
try:
|
||||
# Try to get content via task.content_record
|
||||
task = image.task
|
||||
if task:
|
||||
try:
|
||||
content = Content.objects.get(task=task)
|
||||
image.content = content
|
||||
image.save(update_fields=['content'])
|
||||
except Content.DoesNotExist:
|
||||
# If content doesn't exist, leave content as null
|
||||
pass
|
||||
except Exception:
|
||||
# If any error occurs, leave content as null
|
||||
pass
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('writer', '0006_update_status_choices'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
# Make task field nullable first
|
||||
migrations.AlterField(
|
||||
model_name='images',
|
||||
name='task',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text='The task this image belongs to (legacy, use content instead)',
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='images',
|
||||
to='writer.tasks'
|
||||
),
|
||||
),
|
||||
# Add content field
|
||||
migrations.AddField(
|
||||
model_name='images',
|
||||
name='content',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text='The content this image belongs to (preferred)',
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name='images',
|
||||
to='writer.content'
|
||||
),
|
||||
),
|
||||
# Update ordering
|
||||
migrations.AlterModelOptions(
|
||||
name='images',
|
||||
options={'ordering': ['content', 'position', '-created_at'], 'verbose_name': 'Image', 'verbose_name_plural': 'Images'},
|
||||
),
|
||||
# Add new indexes
|
||||
migrations.AddIndex(
|
||||
model_name='images',
|
||||
index=models.Index(fields=['content', 'image_type'], name='igny8_image_content_image_type_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='images',
|
||||
index=models.Index(fields=['content', 'position'], name='igny8_image_content_position_idx'),
|
||||
),
|
||||
# Data migration: populate content field from task.content_record
|
||||
migrations.RunPython(
|
||||
code=migrate_task_to_content,
|
||||
reverse_code=migrations.RunPython.noop,
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
# Generated migration to change image_url from URLField to CharField
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('writer', '0007_add_content_to_images'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='images',
|
||||
name='image_url',
|
||||
field=models.CharField(blank=True, help_text='URL of the generated/stored image', max_length=500, null=True),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('igny8_core_auth', '0008_passwordresettoken_alter_industry_options_and_more'),
|
||||
('writer', '0008_change_image_url_to_charfield'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='content',
|
||||
name='source',
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
('igny8', 'IGNY8 Generated'),
|
||||
('wordpress', 'WordPress Synced'),
|
||||
('shopify', 'Shopify Synced'),
|
||||
('custom', 'Custom API Synced'),
|
||||
],
|
||||
db_index=True,
|
||||
default='igny8',
|
||||
help_text='Source of the content',
|
||||
max_length=50,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='content',
|
||||
name='sync_status',
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
('native', 'Native IGNY8 Content'),
|
||||
('imported', 'Imported from External'),
|
||||
('synced', 'Synced from External'),
|
||||
],
|
||||
db_index=True,
|
||||
default='native',
|
||||
help_text='Sync status of the content',
|
||||
max_length=50,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='content',
|
||||
name='external_id',
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
help_text='External platform ID',
|
||||
max_length=255,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='content',
|
||||
name='external_url',
|
||||
field=models.URLField(
|
||||
blank=True,
|
||||
help_text='External platform URL',
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='content',
|
||||
name='sync_metadata',
|
||||
field=models.JSONField(
|
||||
blank=True,
|
||||
default=dict,
|
||||
help_text='Platform-specific sync metadata',
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='content',
|
||||
name='internal_links',
|
||||
field=models.JSONField(
|
||||
blank=True,
|
||||
default=list,
|
||||
help_text='Internal links added by linker',
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='content',
|
||||
name='linker_version',
|
||||
field=models.IntegerField(
|
||||
default=0,
|
||||
help_text='Version of linker processing',
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='content',
|
||||
name='optimizer_version',
|
||||
field=models.IntegerField(
|
||||
default=0,
|
||||
help_text='Version of optimizer processing',
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='content',
|
||||
name='optimization_scores',
|
||||
field=models.JSONField(
|
||||
blank=True,
|
||||
default=dict,
|
||||
help_text='Optimization scores (SEO, readability, engagement)',
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='content',
|
||||
index=models.Index(fields=['source'], name='igny8_conte_source_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='content',
|
||||
index=models.Index(fields=['sync_status'], name='igny8_conte_sync_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='content',
|
||||
index=models.Index(fields=['source', 'sync_status'], name='igny8_conte_source_sync_idx'),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
# Generated migration to make Content.task nullable for Phase 4 synced content
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('writer', '0009_add_content_site_source_fields'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='content',
|
||||
name='task',
|
||||
field=models.OneToOneField(
|
||||
blank=True,
|
||||
help_text='The task this content belongs to',
|
||||
null=True,
|
||||
on_delete=models.CASCADE,
|
||||
related_name='content_record',
|
||||
to='writer.tasks'
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
# Generated manually for Phase 8: Universal Content Types
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('writer', '0010_make_content_task_nullable'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='content',
|
||||
name='entity_type',
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
('blog_post', 'Blog Post'),
|
||||
('article', 'Article'),
|
||||
('product', 'Product'),
|
||||
('service', 'Service Page'),
|
||||
('taxonomy', 'Taxonomy Page'),
|
||||
('page', 'Page'),
|
||||
],
|
||||
db_index=True,
|
||||
default='blog_post',
|
||||
help_text='Type of content entity',
|
||||
max_length=50
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='content',
|
||||
name='json_blocks',
|
||||
field=models.JSONField(
|
||||
blank=True,
|
||||
default=list,
|
||||
help_text='Structured content blocks (for products, services, taxonomies)'
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='content',
|
||||
name='structure_data',
|
||||
field=models.JSONField(
|
||||
blank=True,
|
||||
default=dict,
|
||||
help_text='Content structure data (metadata, schema, etc.)'
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='content',
|
||||
index=models.Index(fields=['entity_type'], name='igny8_conte_entity__idx'),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,252 +0,0 @@
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
def backfill_metadata_mappings_stub(apps, schema_editor):
|
||||
"""
|
||||
Stage 3: Backfill metadata mappings for existing Content/Task records.
|
||||
|
||||
This function backfills:
|
||||
- ContentClusterMap records from existing Content/Task -> Cluster relationships
|
||||
- ContentTaxonomyMap records from existing taxonomy associations
|
||||
- ContentAttributeMap records from existing attribute data
|
||||
- entity_type on Tasks from existing content_type or other fields (if field exists)
|
||||
"""
|
||||
Tasks = apps.get_model('writer', 'Tasks')
|
||||
Content = apps.get_model('writer', 'Content')
|
||||
ContentClusterMap = apps.get_model('writer', 'ContentClusterMap')
|
||||
ContentTaxonomyMap = apps.get_model('writer', 'ContentTaxonomyMap')
|
||||
ContentAttributeMap = apps.get_model('writer', 'ContentAttributeMap')
|
||||
|
||||
# Check if entity_type field exists (added in migration 0013)
|
||||
task_fields = [f.name for f in Tasks._meta.get_fields()]
|
||||
has_entity_type = 'entity_type' in task_fields
|
||||
|
||||
# Backfill Tasks: Set entity_type from content_type if field exists and not set
|
||||
tasks_updated = 0
|
||||
if has_entity_type:
|
||||
for task in Tasks.objects.filter(entity_type__isnull=True):
|
||||
# Map content_type to entity_type
|
||||
entity_type_map = {
|
||||
'blog_post': 'blog_post',
|
||||
'article': 'article',
|
||||
'guide': 'article',
|
||||
'tutorial': 'article',
|
||||
}
|
||||
task.entity_type = entity_type_map.get(task.content_type, 'blog_post')
|
||||
task.save(update_fields=['entity_type'])
|
||||
tasks_updated += 1
|
||||
|
||||
# Backfill Content: Set entity_type from task if not set
|
||||
content_updated = 0
|
||||
content_fields = [f.name for f in Content._meta.get_fields()]
|
||||
if 'entity_type' in content_fields:
|
||||
for content in Content.objects.filter(entity_type__isnull=True):
|
||||
if content.task and has_entity_type and hasattr(content.task, 'entity_type') and content.task.entity_type:
|
||||
content.entity_type = content.task.entity_type
|
||||
content.save(update_fields=['entity_type'])
|
||||
content_updated += 1
|
||||
|
||||
# Backfill ContentClusterMap: Create mappings from Task->Cluster relationships
|
||||
cluster_maps_created = 0
|
||||
has_cluster_role = 'cluster_role' in task_fields
|
||||
content_fields = [f.name for f in Content._meta.get_fields()]
|
||||
|
||||
for task in Tasks.objects.filter(cluster__isnull=False):
|
||||
# Find all Content records for this task
|
||||
contents = Content.objects.filter(task=task)
|
||||
for content in contents:
|
||||
# Check if mapping already exists
|
||||
if not ContentClusterMap.objects.filter(
|
||||
content=content,
|
||||
cluster=task.cluster
|
||||
).exists():
|
||||
# Get cluster_role if field exists
|
||||
role = 'hub' # Default
|
||||
if has_cluster_role and hasattr(task, 'cluster_role') and task.cluster_role:
|
||||
role = task.cluster_role
|
||||
|
||||
# Get account/site/sector from content or task
|
||||
account_id = getattr(content, 'account_id', None) or getattr(content, 'tenant_id', None) or getattr(task, 'account_id', None) or getattr(task, 'tenant_id', None)
|
||||
site_id = getattr(content, 'site_id', None) or getattr(task, 'site_id', None)
|
||||
sector_id = getattr(content, 'sector_id', None) or getattr(task, 'sector_id', None)
|
||||
|
||||
if account_id and site_id and sector_id:
|
||||
ContentClusterMap.objects.create(
|
||||
content=content,
|
||||
task=task,
|
||||
cluster=task.cluster,
|
||||
role=role,
|
||||
account_id=account_id,
|
||||
site_id=site_id,
|
||||
sector_id=sector_id,
|
||||
source='blueprint' if task.idea else 'manual',
|
||||
metadata={},
|
||||
)
|
||||
cluster_maps_created += 1
|
||||
|
||||
# Backfill ContentTaxonomyMap: Create mappings from Task->Taxonomy relationships
|
||||
taxonomy_maps_created = 0
|
||||
has_taxonomy = 'taxonomy' in task_fields
|
||||
if has_taxonomy:
|
||||
for task in Tasks.objects.filter(taxonomy__isnull=False):
|
||||
contents = Content.objects.filter(task=task)
|
||||
for content in contents:
|
||||
if not ContentTaxonomyMap.objects.filter(
|
||||
content=content,
|
||||
taxonomy=task.taxonomy
|
||||
).exists():
|
||||
# Get account/site/sector from content or task
|
||||
account_id = getattr(content, 'account_id', None) or getattr(content, 'tenant_id', None) or getattr(task, 'account_id', None) or getattr(task, 'tenant_id', None)
|
||||
site_id = getattr(content, 'site_id', None) or getattr(task, 'site_id', None)
|
||||
sector_id = getattr(content, 'sector_id', None) or getattr(task, 'sector_id', None)
|
||||
|
||||
if account_id and site_id and sector_id:
|
||||
ContentTaxonomyMap.objects.create(
|
||||
content=content,
|
||||
task=task,
|
||||
taxonomy=task.taxonomy,
|
||||
account_id=account_id,
|
||||
site_id=site_id,
|
||||
sector_id=sector_id,
|
||||
source='blueprint',
|
||||
metadata={},
|
||||
)
|
||||
taxonomy_maps_created += 1
|
||||
|
||||
print(f"Backfill complete:")
|
||||
print(f" - Tasks entity_type updated: {tasks_updated}")
|
||||
print(f" - Content entity_type updated: {content_updated}")
|
||||
print(f" - Cluster mappings created: {cluster_maps_created}")
|
||||
print(f" - Taxonomy mappings created: {taxonomy_maps_created}")
|
||||
|
||||
|
||||
def reverse_backfill_metadata_mappings_stub(apps, schema_editor):
|
||||
"""
|
||||
Reverse operation for metadata backfill (no-op for Stage 1).
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('site_building', '0003_workflow_and_taxonomies'),
|
||||
('planner', '0008_stage1_site_builder_fields'),
|
||||
('writer', '0011_add_universal_content_types'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ContentClusterMap',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('role', models.CharField(choices=[('hub', 'Hub Page'), ('supporting', 'Supporting Page'), ('attribute', 'Attribute Page')], default='hub', max_length=50)),
|
||||
('source', models.CharField(choices=[('blueprint', 'Blueprint'), ('manual', 'Manual'), ('import', 'Import')], default='blueprint', max_length=50)),
|
||||
('metadata', models.JSONField(blank=True, default=dict)),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='contentclustermap_set', to='igny8_core_auth.tenant')),
|
||||
('cluster', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='content_mappings', to='planner.clusters')),
|
||||
('content', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='cluster_mappings', to='writer.content')),
|
||||
('sector', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='contentclustermap_set', to='igny8_core_auth.sector')),
|
||||
('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='contentclustermap_set', to='igny8_core_auth.site')),
|
||||
('task', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='cluster_mappings', to='writer.tasks')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Content Cluster Map',
|
||||
'verbose_name_plural': 'Content Cluster Maps',
|
||||
'db_table': 'igny8_content_cluster_map',
|
||||
'ordering': ['-created_at'],
|
||||
'unique_together': {('content', 'cluster', 'role')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ContentTaxonomyMap',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('source', models.CharField(choices=[('blueprint', 'Blueprint'), ('manual', 'Manual'), ('import', 'Import')], default='blueprint', max_length=50)),
|
||||
('metadata', models.JSONField(blank=True, default=dict)),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='contenttaxonomymap_set', to='igny8_core_auth.tenant')),
|
||||
('content', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='taxonomy_mappings', to='writer.content')),
|
||||
('sector', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='contenttaxonomymap_set', to='igny8_core_auth.sector')),
|
||||
('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='contenttaxonomymap_set', to='igny8_core_auth.site')),
|
||||
('taxonomy', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='content_mappings', to='site_building.SiteBlueprintTaxonomy')),
|
||||
('task', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='taxonomy_mappings', to='writer.tasks')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Content Taxonomy Map',
|
||||
'verbose_name_plural': 'Content Taxonomy Maps',
|
||||
'db_table': 'igny8_content_taxonomy_map',
|
||||
'ordering': ['-created_at'],
|
||||
'unique_together': {('content', 'taxonomy')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='ContentAttributeMap',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, db_index=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('name', models.CharField(max_length=120)),
|
||||
('value', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('source', models.CharField(choices=[('blueprint', 'Blueprint'), ('manual', 'Manual'), ('import', 'Import')], default='blueprint', max_length=50)),
|
||||
('metadata', models.JSONField(blank=True, default=dict)),
|
||||
('account', models.ForeignKey(db_column='tenant_id', on_delete=django.db.models.deletion.CASCADE, related_name='contentattributemap_set', to='igny8_core_auth.tenant')),
|
||||
('content', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='attribute_mappings', to='writer.content')),
|
||||
('sector', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='contentattributemap_set', to='igny8_core_auth.sector')),
|
||||
('site', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='contentattributemap_set', to='igny8_core_auth.site')),
|
||||
('task', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='attribute_mappings', to='writer.tasks')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Content Attribute Map',
|
||||
'verbose_name_plural': 'Content Attribute Maps',
|
||||
'db_table': 'igny8_content_attribute_map',
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='contentclustermap',
|
||||
index=models.Index(fields=['cluster', 'role'], name='writer_con_cluster__d06bd6_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='contentclustermap',
|
||||
index=models.Index(fields=['content', 'role'], name='writer_con_content__bb02dd_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='contentclustermap',
|
||||
index=models.Index(fields=['task', 'role'], name='writer_con_task__role_828ce1_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='contenttaxonomymap',
|
||||
index=models.Index(fields=['taxonomy'], name='writer_con_taxonomy_d55410_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='contenttaxonomymap',
|
||||
index=models.Index(fields=['content', 'taxonomy'], name='writer_con_content__0af6a6_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='contenttaxonomymap',
|
||||
index=models.Index(fields=['task', 'taxonomy'], name='writer_con_task__taxon_e3bdad_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='contentattributemap',
|
||||
index=models.Index(fields=['name'], name='writer_con_name_a9671a_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='contentattributemap',
|
||||
index=models.Index(fields=['content', 'name'], name='writer_con_content__34a91e_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='contentattributemap',
|
||||
index=models.Index(fields=['task', 'name'], name='writer_con_task__name_fa4a4e_idx'),
|
||||
),
|
||||
# Stage 1: Data migration stub for Stage 3 backfill
|
||||
migrations.RunPython(
|
||||
backfill_metadata_mappings_stub,
|
||||
reverse_backfill_metadata_mappings_stub,
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('writer', '0012_metadata_mapping_tables'),
|
||||
('site_building', '0003_workflow_and_taxonomies'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='tasks',
|
||||
name='entity_type',
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
('blog_post', 'Blog Post'),
|
||||
('article', 'Article'),
|
||||
('product', 'Product'),
|
||||
('service', 'Service Page'),
|
||||
('taxonomy', 'Taxonomy Page'),
|
||||
('page', 'Page'),
|
||||
],
|
||||
db_index=True,
|
||||
default='blog_post',
|
||||
help_text='Type of content entity (inherited from idea/blueprint)',
|
||||
max_length=50,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='tasks',
|
||||
name='taxonomy',
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
help_text='Taxonomy association when derived from blueprint planning',
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name='tasks',
|
||||
to='site_building.SiteBlueprintTaxonomy',
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='tasks',
|
||||
name='cluster_role',
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
choices=[
|
||||
('hub', 'Hub Page'),
|
||||
('supporting', 'Supporting Page'),
|
||||
('attribute', 'Attribute Page'),
|
||||
],
|
||||
default='hub',
|
||||
help_text='Role within the cluster-driven sitemap',
|
||||
max_length=50,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='tasks',
|
||||
index=models.Index(fields=['entity_type'], name='writer_tasks_entity_type_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='tasks',
|
||||
index=models.Index(fields=['cluster_role'], name='writer_tasks_cluster_role_idx'),
|
||||
),
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user