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

This commit is contained in:
root
2026-05-09 21:24:50 +02:00
commit f56b088643
721 changed files with 177870 additions and 0 deletions
+47
View File
@@ -0,0 +1,47 @@
# Generated by Django 5.1.6 on 2025-03-05 22:07
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='EPGData',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('tvg_id', models.CharField(blank=True, max_length=255, null=True)),
('name', models.CharField(max_length=255)),
],
),
migrations.CreateModel(
name='EPGSource',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, unique=True)),
('source_type', models.CharField(choices=[('xmltv', 'XMLTV URL'), ('schedules_direct', 'Schedules Direct API')], max_length=20)),
('url', models.URLField(blank=True, null=True)),
('api_key', models.CharField(blank=True, max_length=255, null=True)),
('is_active', models.BooleanField(default=True)),
],
),
migrations.CreateModel(
name='ProgramData',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('start_time', models.DateTimeField()),
('end_time', models.DateTimeField()),
('title', models.CharField(max_length=255)),
('sub_title', models.CharField(blank=True, max_length=255, null=True)),
('description', models.TextField(blank=True, null=True)),
('tvg_id', models.CharField(blank=True, max_length=255, null=True)),
('epg', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='programs', to='epg.epgdata')),
],
),
]
@@ -0,0 +1,18 @@
# Generated by Django 5.1.6 on 2025-03-19 16:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epg', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='epgsource',
name='file_path',
field=models.CharField(blank=True, max_length=1024, null=True),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 5.1.6 on 2025-03-25 19:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epg', '0002_epgsource_file_path'),
]
operations = [
migrations.AlterField(
model_name='epgdata',
name='tvg_id',
field=models.CharField(blank=True, max_length=255, null=True, unique=True),
),
]
@@ -0,0 +1,24 @@
# Generated by Django 5.1.6 on 2025-03-26 12:44
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epg', '0003_alter_epgdata_tvg_id'),
]
operations = [
migrations.AddField(
model_name='epgdata',
name='epg_source',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='epgs', to='epg.epgsource'),
),
migrations.AlterField(
model_name='epgdata',
name='tvg_id',
field=models.CharField(blank=True, db_index=True, max_length=255, null=True),
),
]
@@ -0,0 +1,22 @@
# Generated by Django 5.1.6 on 2025-03-27 17:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epg', '0004_epgdata_epg_source_alter_epgdata_tvg_id'),
]
operations = [
migrations.AddField(
model_name='programdata',
name='custom_properties',
field=models.TextField(blank=True, null=True),
),
migrations.AlterUniqueTogether(
name='epgdata',
unique_together={('tvg_id', 'epg_source')},
),
]
@@ -0,0 +1,25 @@
# Generated by Django 5.1.6 on 2025-03-29 17:31
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('django_celery_beat', '0019_alter_periodictasks_options'),
('epg', '0005_programdata_custom_properties_and_more'),
]
operations = [
migrations.AddField(
model_name='epgsource',
name='refresh_interval',
field=models.IntegerField(default=24),
),
migrations.AddField(
model_name='epgsource',
name='refresh_task',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='django_celery_beat.periodictask'),
),
]
@@ -0,0 +1,23 @@
# Generated by Django
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epg', '0006_epgsource_refresh_interval_epgsource_refresh_task'),
]
operations = [
migrations.AddField(
model_name='epgsource',
name='status',
field=models.CharField(choices=[('idle', 'Idle'), ('fetching', 'Fetching'), ('parsing', 'Parsing'), ('error', 'Error'), ('success', 'Success')], default='idle', max_length=20),
),
migrations.AddField(
model_name='epgsource',
name='last_error',
field=models.TextField(blank=True, null=True),
),
]
@@ -0,0 +1,52 @@
from django.db import migrations
import json
def create_default_refresh_tasks(apps, schema_editor):
"""
Creates a PeriodicTask for each existing EPGSource that doesn't have one.
"""
IntervalSchedule = apps.get_model("django_celery_beat", "IntervalSchedule")
PeriodicTask = apps.get_model("django_celery_beat", "PeriodicTask")
EPGSource = apps.get_model("epg", "EPGSource")
default_interval, _ = IntervalSchedule.objects.get_or_create(
every=24,
period="hours",
)
for account in EPGSource.objects.all():
if account.refresh_task:
continue
task_name = f"epg_source-refresh-{account.id}"
refresh_task = PeriodicTask.objects.create(
name=task_name,
interval=default_interval,
task="apps.epg.tasks.refresh_epg_data",
kwargs=json.dumps({"account_id": account.id}),
)
account.refresh_task = refresh_task
account.save(update_fields=["refresh_task"])
def reverse_migration(apps, schema_editor):
IntervalSchedule = apps.get_model("django_celery_beat", "IntervalSchedule")
PeriodicTask = apps.get_model("django_celery_beat", "PeriodicTask")
EPGSource = apps.get_model("epg", "EPGSource")
for account in EPGSource.objects.all():
IntervalSchedule.objects.all().delete()
PeriodicTask.objects.all().delete()
class Migration(migrations.Migration):
dependencies = [
("epg", "0006_epgsource_refresh_interval_epgsource_refresh_task"),
("django_celery_beat", "0019_alter_periodictasks_options"),
]
operations = [
migrations.RunPython(create_default_refresh_tasks, reverse_migration),
]
@@ -0,0 +1,24 @@
# Generated by Django 5.1.6 on 2025-04-07 16:29
import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epg', '0007_populate_periodic_tasks'),
]
operations = [
migrations.AddField(
model_name='epgsource',
name='created_at',
field=models.DateTimeField(default=django.utils.timezone.now, help_text='Time when this source was created'),
),
migrations.AddField(
model_name='epgsource',
name='updated_at',
field=models.DateTimeField(default=django.utils.timezone.now, help_text='Time when this source was last updated'),
),
]
@@ -0,0 +1,23 @@
# Generated by Django 5.1.6 on 2025-04-07 16:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epg', '0008_epgsource_created_at_epgsource_updated_at'),
]
operations = [
migrations.AlterField(
model_name='epgsource',
name='created_at',
field=models.DateTimeField(auto_now_add=True, help_text='Time when this source was created'),
),
migrations.AlterField(
model_name='epgsource',
name='updated_at',
field=models.DateTimeField(auto_now=True, help_text='Time when this source was last updated'),
),
]
@@ -0,0 +1,14 @@
# Generated by Django 5.1.6 on 2025-05-03 21:47
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('epg', '0007_epgsource_status_epgsource_last_error'),
('epg', '0009_alter_epgsource_created_at_and_more'),
]
operations = [
]
@@ -0,0 +1,42 @@
# Generated by Django 5.1.6 on 2025-05-04 21:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epg', '0010_merge_20250503_2147'),
]
operations = [
# Change updated_at field
migrations.AlterField(
model_name='epgsource',
name='updated_at',
field=models.DateTimeField(blank=True, help_text='Time when this source was last successfully refreshed', null=True),
),
# Add new last_message field
migrations.AddField(
model_name='epgsource',
name='last_message',
field=models.TextField(blank=True, help_text='Last status message, including success results or error information', null=True),
),
# Copy data from last_error to last_message
migrations.RunPython(
code=lambda apps, schema_editor: apps.get_model('epg', 'EPGSource').objects.all().update(
last_message=models.F('last_error')
),
reverse_code=lambda apps, schema_editor: apps.get_model('epg', 'EPGSource').objects.all().update(
last_error=models.F('last_message')
),
),
# Remove the old field
migrations.RemoveField(
model_name='epgsource',
name='last_error',
),
]
@@ -0,0 +1,18 @@
# Generated by Django 5.1.6 on 2025-05-15 01:05
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epg', '0011_update_epgsource_fields'),
]
operations = [
migrations.AlterField(
model_name='epgsource',
name='status',
field=models.CharField(choices=[('idle', 'Idle'), ('fetching', 'Fetching'), ('parsing', 'Parsing'), ('error', 'Error'), ('success', 'Success'), ('disabled', 'Disabled')], default='idle', max_length=20),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 5.1.6 on 2025-05-21 19:58
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epg', '0012_alter_epgsource_status'),
]
operations = [
migrations.AlterField(
model_name='epgsource',
name='refresh_interval',
field=models.IntegerField(default=0),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 5.1.6 on 2025-05-26 15:48
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epg', '0013_alter_epgsource_refresh_interval'),
]
operations = [
migrations.AddField(
model_name='epgsource',
name='extracted_file_path',
field=models.CharField(blank=True, help_text='Path to extracted XML file after decompression', max_length=1024, null=True),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 5.2.4 on 2025-09-02 14:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epg', '0014_epgsource_extracted_file_path'),
]
operations = [
migrations.AlterField(
model_name='programdata',
name='custom_properties',
field=models.JSONField(blank=True, default=dict, null=True),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 5.2.4 on 2025-09-16 22:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epg', '0015_alter_programdata_custom_properties'),
]
operations = [
migrations.AddField(
model_name='epgdata',
name='icon_url',
field=models.URLField(blank=True, max_length=500, null=True),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 5.2.4 on 2025-09-24 21:07
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epg', '0016_epgdata_icon_url'),
]
operations = [
migrations.AlterField(
model_name='epgsource',
name='url',
field=models.URLField(blank=True, max_length=1000, null=True),
),
]
@@ -0,0 +1,23 @@
# Generated by Django 5.2.4 on 2025-10-17 17:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epg', '0017_alter_epgsource_url'),
]
operations = [
migrations.AddField(
model_name='epgsource',
name='custom_properties',
field=models.JSONField(blank=True, default=dict, help_text='Custom properties for dummy EPG configuration (regex patterns, timezone, duration, etc.)', null=True),
),
migrations.AlterField(
model_name='epgsource',
name='source_type',
field=models.CharField(choices=[('xmltv', 'XMLTV URL'), ('schedules_direct', 'Schedules Direct API'), ('dummy', 'Custom Dummy EPG')], max_length=20),
),
]
@@ -0,0 +1,18 @@
# Generated by Django 5.2.4 on 2025-10-22 21:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epg', '0018_epgsource_custom_properties_and_more'),
]
operations = [
migrations.AlterField(
model_name='programdata',
name='sub_title',
field=models.TextField(blank=True, null=True),
),
]
@@ -0,0 +1,119 @@
# Generated migration to replace {time} placeholders with {starttime}
import re
from django.db import migrations
def migrate_time_placeholders(apps, schema_editor):
"""
Replace {time} with {starttime} and {time24} with {starttime24}
in all dummy EPG source custom_properties templates.
"""
EPGSource = apps.get_model('epg', 'EPGSource')
# Fields that contain templates with placeholders
template_fields = [
'title_template',
'description_template',
'upcoming_title_template',
'upcoming_description_template',
'ended_title_template',
'ended_description_template',
'channel_logo_url',
'program_poster_url',
]
# Get all dummy EPG sources
dummy_sources = EPGSource.objects.filter(source_type='dummy')
updated_count = 0
for source in dummy_sources:
if not source.custom_properties:
continue
modified = False
custom_props = source.custom_properties.copy()
for field in template_fields:
if field in custom_props and custom_props[field]:
original_value = custom_props[field]
# Replace {time24} first (before {time}) to avoid double replacement
# e.g., {time24} shouldn't become {starttime24} via {time} -> {starttime}
new_value = original_value
new_value = re.sub(r'\{time24\}', '{starttime24}', new_value)
new_value = re.sub(r'\{time\}', '{starttime}', new_value)
if new_value != original_value:
custom_props[field] = new_value
modified = True
if modified:
source.custom_properties = custom_props
source.save(update_fields=['custom_properties'])
updated_count += 1
if updated_count > 0:
print(f"Migration complete: Updated {updated_count} dummy EPG source(s) with new placeholder names.")
else:
print("No dummy EPG sources needed placeholder updates.")
def reverse_migration(apps, schema_editor):
"""
Reverse the migration by replacing {starttime} back to {time}.
"""
EPGSource = apps.get_model('epg', 'EPGSource')
template_fields = [
'title_template',
'description_template',
'upcoming_title_template',
'upcoming_description_template',
'ended_title_template',
'ended_description_template',
'channel_logo_url',
'program_poster_url',
]
dummy_sources = EPGSource.objects.filter(source_type='dummy')
updated_count = 0
for source in dummy_sources:
if not source.custom_properties:
continue
modified = False
custom_props = source.custom_properties.copy()
for field in template_fields:
if field in custom_props and custom_props[field]:
original_value = custom_props[field]
# Reverse the replacements
new_value = original_value
new_value = re.sub(r'\{starttime24\}', '{time24}', new_value)
new_value = re.sub(r'\{starttime\}', '{time}', new_value)
if new_value != original_value:
custom_props[field] = new_value
modified = True
if modified:
source.custom_properties = custom_props
source.save(update_fields=['custom_properties'])
updated_count += 1
if updated_count > 0:
print(f"Reverse migration complete: Reverted {updated_count} dummy EPG source(s) to old placeholder names.")
class Migration(migrations.Migration):
dependencies = [
('epg', '0019_alter_programdata_sub_title'),
]
operations = [
migrations.RunPython(migrate_time_placeholders, reverse_migration),
]
@@ -0,0 +1,18 @@
# Generated by Django 5.2.4 on 2025-12-05 15:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('epg', '0020_migrate_time_to_starttime_placeholders'),
]
operations = [
migrations.AddField(
model_name='epgsource',
name='priority',
field=models.PositiveIntegerField(default=0, help_text='Priority for EPG matching (higher numbers = higher priority). Used when multiple EPG sources have matching entries for a channel.'),
),
]
View File