Proyecto LCX Dispatcharr multicuenta
Base Image Build / prepare (push) Has been cancelled
Build and Push Multi-Arch Docker Image / build-and-push (push) Has been cancelled
Frontend Tests / test (push) Has been cancelled
Base Image Build / docker (amd64, ubuntu-24.04) (push) Has been cancelled
Base Image Build / docker (arm64, ubuntu-24.04-arm) (push) Has been cancelled
Base Image Build / create-manifest (push) Has been cancelled
Base Image Build / prepare (push) Has been cancelled
Build and Push Multi-Arch Docker Image / build-and-push (push) Has been cancelled
Frontend Tests / test (push) Has been cancelled
Base Image Build / docker (amd64, ubuntu-24.04) (push) Has been cancelled
Base Image Build / docker (arm64, ubuntu-24.04-arm) (push) Has been cancelled
Base Image Build / create-manifest (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
# Generated by Django 5.2.4 on 2025-08-28 18:16
|
||||
|
||||
import django.db.models.deletion
|
||||
import uuid
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('dispatcharr_channels', '0024_alter_channelgroupm3uaccount_channel_group'),
|
||||
('m3u', '0016_m3uaccount_priority'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Movie',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('description', models.TextField(blank=True, null=True)),
|
||||
('year', models.IntegerField(blank=True, null=True)),
|
||||
('rating', models.CharField(blank=True, max_length=10, null=True)),
|
||||
('genre', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('duration_secs', models.IntegerField(blank=True, help_text='Duration in seconds', null=True)),
|
||||
('tmdb_id', models.CharField(blank=True, help_text='TMDB ID for metadata', max_length=50, null=True, unique=True)),
|
||||
('imdb_id', models.CharField(blank=True, help_text='IMDB ID for metadata', max_length=50, null=True, unique=True)),
|
||||
('custom_properties', models.JSONField(blank=True, help_text='Additional metadata and properties for the movie', null=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('logo', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='movie', to='dispatcharr_channels.logo')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Movie',
|
||||
'verbose_name_plural': 'Movies',
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Series',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('description', models.TextField(blank=True, null=True)),
|
||||
('year', models.IntegerField(blank=True, null=True)),
|
||||
('rating', models.CharField(blank=True, max_length=10, null=True)),
|
||||
('genre', models.CharField(blank=True, max_length=255, null=True)),
|
||||
('tmdb_id', models.CharField(blank=True, help_text='TMDB ID for metadata', max_length=50, null=True, unique=True)),
|
||||
('imdb_id', models.CharField(blank=True, help_text='IMDB ID for metadata', max_length=50, null=True, unique=True)),
|
||||
('custom_properties', models.JSONField(blank=True, help_text='Additional metadata and properties for the series', null=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('logo', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='series', to='dispatcharr_channels.logo')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Series',
|
||||
'verbose_name_plural': 'Series',
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Episode',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, unique=True)),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('description', models.TextField(blank=True, null=True)),
|
||||
('air_date', models.DateField(blank=True, null=True)),
|
||||
('rating', models.CharField(blank=True, max_length=10, null=True)),
|
||||
('duration_secs', models.IntegerField(blank=True, help_text='Duration in seconds', null=True)),
|
||||
('season_number', models.IntegerField(blank=True, null=True)),
|
||||
('episode_number', models.IntegerField(blank=True, null=True)),
|
||||
('tmdb_id', models.CharField(blank=True, db_index=True, help_text='TMDB ID for metadata', max_length=50, null=True)),
|
||||
('imdb_id', models.CharField(blank=True, db_index=True, help_text='IMDB ID for metadata', max_length=50, null=True)),
|
||||
('custom_properties', models.JSONField(blank=True, help_text='Custom properties for this episode', null=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('series', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='episodes', to='vod.series')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Episode',
|
||||
'verbose_name_plural': 'Episodes',
|
||||
'ordering': ['series__name', 'season_number', 'episode_number'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='VODCategory',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('category_type', models.CharField(choices=[('movie', 'Movie'), ('series', 'Series')], default='movie', help_text='Type of content this category contains', max_length=10)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'VOD Category',
|
||||
'verbose_name_plural': 'VOD Categories',
|
||||
'ordering': ['name'],
|
||||
'unique_together': {('name', 'category_type')},
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='M3UVODCategoryRelation',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('enabled', models.BooleanField(default=False, help_text='Set to false to deactivate this category for the M3U account')),
|
||||
('custom_properties', models.JSONField(blank=True, help_text='Provider-specific data like quality, language, etc.', null=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('m3u_account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='category_relations', to='m3u.m3uaccount')),
|
||||
('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='m3u_relations', to='vod.vodcategory')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'M3U VOD Category Relation',
|
||||
'verbose_name_plural': 'M3U VOD Category Relations',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='M3USeriesRelation',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('external_series_id', models.CharField(help_text='External series ID from M3U provider', max_length=255)),
|
||||
('custom_properties', models.JSONField(blank=True, help_text='Provider-specific data', null=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('last_episode_refresh', models.DateTimeField(blank=True, help_text='Last time episodes were refreshed', null=True)),
|
||||
('m3u_account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='series_relations', to='m3u.m3uaccount')),
|
||||
('series', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='m3u_relations', to='vod.series')),
|
||||
('category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='vod.vodcategory')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'M3U Series Relation',
|
||||
'verbose_name_plural': 'M3U Series Relations',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='M3UMovieRelation',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('stream_id', models.CharField(help_text='External stream ID from M3U provider', max_length=255)),
|
||||
('container_extension', models.CharField(blank=True, max_length=10, null=True)),
|
||||
('custom_properties', models.JSONField(blank=True, help_text='Provider-specific data like quality, language, etc.', null=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('last_advanced_refresh', models.DateTimeField(blank=True, help_text='Last time advanced data was fetched from provider', null=True)),
|
||||
('m3u_account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='movie_relations', to='m3u.m3uaccount')),
|
||||
('movie', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='m3u_relations', to='vod.movie')),
|
||||
('category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='vod.vodcategory')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'M3U Movie Relation',
|
||||
'verbose_name_plural': 'M3U Movie Relations',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='M3UEpisodeRelation',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('stream_id', models.CharField(help_text='External stream ID from M3U provider', max_length=255)),
|
||||
('container_extension', models.CharField(blank=True, max_length=10, null=True)),
|
||||
('custom_properties', models.JSONField(blank=True, help_text='Provider-specific data like quality, language, etc.', null=True)),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('episode', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='m3u_relations', to='vod.episode')),
|
||||
('m3u_account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='episode_relations', to='m3u.m3uaccount')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'M3U Episode Relation',
|
||||
'verbose_name_plural': 'M3U Episode Relations',
|
||||
'unique_together': {('m3u_account', 'stream_id')},
|
||||
},
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='movie',
|
||||
constraint=models.UniqueConstraint(condition=models.Q(('tmdb_id__isnull', True), ('imdb_id__isnull', True)), fields=('name', 'year'), name='unique_movie_name_year_no_external_id'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='series',
|
||||
constraint=models.UniqueConstraint(condition=models.Q(('tmdb_id__isnull', True), ('imdb_id__isnull', True)), fields=('name', 'year'), name='unique_series_name_year_no_external_id'),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='episode',
|
||||
unique_together={('series', 'season_number', 'episode_number')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='m3uvodcategoryrelation',
|
||||
unique_together={('m3u_account', 'category')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='m3useriesrelation',
|
||||
unique_together={('m3u_account', 'external_series_id')},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='m3umovierelation',
|
||||
unique_together={('m3u_account', 'stream_id')},
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,29 @@
|
||||
# Generated by Django 5.2.4 on 2025-09-04 21:12
|
||||
|
||||
import django.utils.timezone
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('vod', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='m3uepisoderelation',
|
||||
name='last_seen',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, help_text='Last time this relation was seen during VOD scan'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='m3umovierelation',
|
||||
name='last_seen',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, help_text='Last time this relation was seen during VOD scan'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='m3useriesrelation',
|
||||
name='last_seen',
|
||||
field=models.DateTimeField(default=django.utils.timezone.now, help_text='Last time this relation was seen during VOD scan'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,264 @@
|
||||
# Generated by Django 5.2.4 on 2025-11-06 23:01
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def migrate_vod_logos_forward(apps, schema_editor):
|
||||
"""
|
||||
Migrate VOD logos from the Logo table to the new VODLogo table.
|
||||
This copies all logos referenced by movies or series to VODLogo.
|
||||
Uses pure SQL for maximum performance.
|
||||
"""
|
||||
from django.db import connection
|
||||
|
||||
print("\n" + "="*80)
|
||||
print("Starting VOD logo migration...")
|
||||
print("="*80)
|
||||
|
||||
with connection.cursor() as cursor:
|
||||
# Step 1: Copy unique logos from Logo table to VODLogo table
|
||||
# Only copy logos that are used by movies or series
|
||||
print("Copying logos to VODLogo table...")
|
||||
cursor.execute("""
|
||||
INSERT INTO vod_vodlogo (name, url)
|
||||
SELECT DISTINCT l.name, l.url
|
||||
FROM dispatcharr_channels_logo l
|
||||
WHERE l.id IN (
|
||||
SELECT DISTINCT logo_id FROM vod_movie WHERE logo_id IS NOT NULL
|
||||
UNION
|
||||
SELECT DISTINCT logo_id FROM vod_series WHERE logo_id IS NOT NULL
|
||||
)
|
||||
ON CONFLICT (url) DO NOTHING
|
||||
""")
|
||||
print(f"Created VODLogo entries")
|
||||
|
||||
# Step 2: Update movies to point to VODLogo IDs using JOIN
|
||||
print("Updating movie references...")
|
||||
cursor.execute("""
|
||||
UPDATE vod_movie m
|
||||
SET logo_id = v.id
|
||||
FROM dispatcharr_channels_logo l
|
||||
INNER JOIN vod_vodlogo v ON l.url = v.url
|
||||
WHERE m.logo_id = l.id
|
||||
AND m.logo_id IS NOT NULL
|
||||
""")
|
||||
movie_count = cursor.rowcount
|
||||
print(f"Updated {movie_count} movies with new VOD logo references")
|
||||
|
||||
# Step 3: Update series to point to VODLogo IDs using JOIN
|
||||
print("Updating series references...")
|
||||
cursor.execute("""
|
||||
UPDATE vod_series s
|
||||
SET logo_id = v.id
|
||||
FROM dispatcharr_channels_logo l
|
||||
INNER JOIN vod_vodlogo v ON l.url = v.url
|
||||
WHERE s.logo_id = l.id
|
||||
AND s.logo_id IS NOT NULL
|
||||
""")
|
||||
series_count = cursor.rowcount
|
||||
print(f"Updated {series_count} series with new VOD logo references")
|
||||
|
||||
print("="*80)
|
||||
print("VOD logo migration completed successfully!")
|
||||
print(f"Summary: Updated {movie_count} movies and {series_count} series")
|
||||
print("="*80 + "\n")
|
||||
|
||||
|
||||
def migrate_vod_logos_backward(apps, schema_editor):
|
||||
"""
|
||||
Reverse migration - moves VODLogos back to Logo table.
|
||||
This recreates Logo entries for all VODLogos and updates Movie/Series references.
|
||||
"""
|
||||
Logo = apps.get_model('dispatcharr_channels', 'Logo')
|
||||
VODLogo = apps.get_model('vod', 'VODLogo')
|
||||
Movie = apps.get_model('vod', 'Movie')
|
||||
Series = apps.get_model('vod', 'Series')
|
||||
|
||||
print("\n" + "="*80)
|
||||
print("REVERSE: Moving VOD logos back to Logo table...")
|
||||
print("="*80)
|
||||
|
||||
# Get all VODLogos
|
||||
vod_logos = VODLogo.objects.all()
|
||||
print(f"Found {vod_logos.count()} VOD logos to reverse migrate")
|
||||
|
||||
# Create Logo entries for each VODLogo
|
||||
logos_to_create = []
|
||||
vod_to_logo_mapping = {} # VODLogo ID -> Logo ID
|
||||
|
||||
for vod_logo in vod_logos:
|
||||
# Check if a Logo with this URL already exists
|
||||
existing_logo = Logo.objects.filter(url=vod_logo.url).first()
|
||||
|
||||
if existing_logo:
|
||||
# Logo already exists, just map to it
|
||||
vod_to_logo_mapping[vod_logo.id] = existing_logo.id
|
||||
print(f"Logo already exists for URL: {vod_logo.url[:50]}... (using existing)")
|
||||
else:
|
||||
# Create new Logo entry
|
||||
new_logo = Logo(name=vod_logo.name, url=vod_logo.url)
|
||||
logos_to_create.append(new_logo)
|
||||
|
||||
# Bulk create new Logo entries
|
||||
if logos_to_create:
|
||||
print(f"Creating {len(logos_to_create)} new Logo entries...")
|
||||
Logo.objects.bulk_create(logos_to_create, ignore_conflicts=True)
|
||||
print("Logo entries created")
|
||||
|
||||
# Get the created Logo instances with their IDs
|
||||
for vod_logo in vod_logos:
|
||||
if vod_logo.id not in vod_to_logo_mapping:
|
||||
try:
|
||||
logo = Logo.objects.get(url=vod_logo.url)
|
||||
vod_to_logo_mapping[vod_logo.id] = logo.id
|
||||
except Logo.DoesNotExist:
|
||||
print(f"Warning: Could not find Logo for URL: {vod_logo.url[:100]}...")
|
||||
|
||||
print(f"Created mapping for {len(vod_to_logo_mapping)} VOD logos -> Logos")
|
||||
|
||||
# Update movies to point back to Logo table
|
||||
movie_count = 0
|
||||
for movie in Movie.objects.exclude(logo__isnull=True):
|
||||
if movie.logo_id in vod_to_logo_mapping:
|
||||
movie.logo_id = vod_to_logo_mapping[movie.logo_id]
|
||||
movie.save(update_fields=['logo_id'])
|
||||
movie_count += 1
|
||||
print(f"Updated {movie_count} movies to use Logo table")
|
||||
|
||||
# Update series to point back to Logo table
|
||||
series_count = 0
|
||||
for series in Series.objects.exclude(logo__isnull=True):
|
||||
if series.logo_id in vod_to_logo_mapping:
|
||||
series.logo_id = vod_to_logo_mapping[series.logo_id]
|
||||
series.save(update_fields=['logo_id'])
|
||||
series_count += 1
|
||||
print(f"Updated {series_count} series to use Logo table")
|
||||
|
||||
# Delete VODLogos (they're now redundant)
|
||||
vod_logo_count = vod_logos.count()
|
||||
vod_logos.delete()
|
||||
print(f"Deleted {vod_logo_count} VOD logos")
|
||||
|
||||
print("="*80)
|
||||
print("Reverse migration completed!")
|
||||
print(f"Summary: Created/reused {len(vod_to_logo_mapping)} logos, updated {movie_count} movies and {series_count} series")
|
||||
print("="*80 + "\n")
|
||||
|
||||
|
||||
def cleanup_migrated_logos(apps, schema_editor):
|
||||
"""
|
||||
Delete Logo entries that were successfully migrated to VODLogo.
|
||||
|
||||
Uses efficient JOIN-based approach with LEFT JOIN to exclude channel usage.
|
||||
"""
|
||||
from django.db import connection
|
||||
|
||||
print("\n" + "="*80)
|
||||
print("Cleaning up migrated Logo entries...")
|
||||
print("="*80)
|
||||
|
||||
with connection.cursor() as cursor:
|
||||
# Single efficient query using JOINs:
|
||||
# - JOIN with vod_vodlogo to find migrated logos
|
||||
# - LEFT JOIN with channels to find which aren't used
|
||||
cursor.execute("""
|
||||
DELETE FROM dispatcharr_channels_logo
|
||||
WHERE id IN (
|
||||
SELECT l.id
|
||||
FROM dispatcharr_channels_logo l
|
||||
INNER JOIN vod_vodlogo v ON l.url = v.url
|
||||
LEFT JOIN dispatcharr_channels_channel c ON c.logo_id = l.id
|
||||
WHERE c.id IS NULL
|
||||
)
|
||||
""")
|
||||
deleted_count = cursor.rowcount
|
||||
|
||||
print(f"✓ Deleted {deleted_count} migrated Logo entries (not used by channels)")
|
||||
print("="*80 + "\n")
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('vod', '0002_add_last_seen_with_default'),
|
||||
('dispatcharr_channels', '0013_alter_logo_url'), # Ensure Logo table exists
|
||||
]
|
||||
|
||||
operations = [
|
||||
# Step 1: Create the VODLogo model
|
||||
migrations.CreateModel(
|
||||
name='VODLogo',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255)),
|
||||
('url', models.TextField(unique=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'VOD Logo',
|
||||
'verbose_name_plural': 'VOD Logos',
|
||||
},
|
||||
),
|
||||
|
||||
# Step 2: Remove foreign key constraints temporarily (so we can change the IDs)
|
||||
# We need to find and drop the actual constraint names dynamically
|
||||
migrations.RunSQL(
|
||||
sql=[
|
||||
# Drop movie logo constraint (find it dynamically)
|
||||
"""
|
||||
DO $$
|
||||
DECLARE
|
||||
constraint_name text;
|
||||
BEGIN
|
||||
SELECT conname INTO constraint_name
|
||||
FROM pg_constraint
|
||||
WHERE conrelid = 'vod_movie'::regclass
|
||||
AND conname LIKE '%logo_id%fk%';
|
||||
|
||||
IF constraint_name IS NOT NULL THEN
|
||||
EXECUTE 'ALTER TABLE vod_movie DROP CONSTRAINT ' || constraint_name;
|
||||
END IF;
|
||||
END $$;
|
||||
""",
|
||||
# Drop series logo constraint (find it dynamically)
|
||||
"""
|
||||
DO $$
|
||||
DECLARE
|
||||
constraint_name text;
|
||||
BEGIN
|
||||
SELECT conname INTO constraint_name
|
||||
FROM pg_constraint
|
||||
WHERE conrelid = 'vod_series'::regclass
|
||||
AND conname LIKE '%logo_id%fk%';
|
||||
|
||||
IF constraint_name IS NOT NULL THEN
|
||||
EXECUTE 'ALTER TABLE vod_series DROP CONSTRAINT ' || constraint_name;
|
||||
END IF;
|
||||
END $$;
|
||||
""",
|
||||
],
|
||||
reverse_sql=[
|
||||
# The AlterField operations will recreate the constraints pointing to VODLogo,
|
||||
# so we don't need to manually recreate them in reverse
|
||||
migrations.RunSQL.noop,
|
||||
],
|
||||
),
|
||||
|
||||
# Step 3: Migrate the data (this copies logos and updates references)
|
||||
migrations.RunPython(migrate_vod_logos_forward, migrate_vod_logos_backward),
|
||||
|
||||
# Step 4: Now we can safely alter the foreign keys to point to VODLogo
|
||||
migrations.AlterField(
|
||||
model_name='movie',
|
||||
name='logo',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='movie', to='vod.vodlogo'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='series',
|
||||
name='logo',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='series', to='vod.vodlogo'),
|
||||
),
|
||||
|
||||
# Step 5: Clean up migrated Logo entries
|
||||
migrations.RunPython(cleanup_migrated_logos, migrations.RunPython.noop),
|
||||
]
|
||||
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 5.2.11 on 2026-02-24 23:53
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('vod', '0003_vodlogo_alter_movie_logo_alter_series_logo'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='m3uepisoderelation',
|
||||
name='series_relation',
|
||||
field=models.ForeignKey(blank=True, help_text='The series relation this episode relation belongs to. CASCADE ensures cleanup when the series relation is removed.', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='episode_relations', to='vod.m3useriesrelation'),
|
||||
),
|
||||
]
|
||||
Reference in New Issue
Block a user