diff --git a/source/concierge/migrations/0001_initial.py b/source/concierge/migrations/0001_initial.py index d64b046..6e7a358 100644 --- a/source/concierge/migrations/0001_initial.py +++ b/source/concierge/migrations/0001_initial.py @@ -1,8 +1,8 @@ -# Generated by Django 3.0.5 on 2020-04-26 17:25 +# Generated by Django 5.0.2 on 2024-02-27 19:20 -from django.db import migrations, models import django.db.models.deletion import uuid +from django.db import migrations, models class Migration(migrations.Migration): @@ -10,7 +10,7 @@ class Migration(migrations.Migration): initial = True dependencies = [ - ('rtmp', '0002_stream_publish_counter'), + ('config', '0001_initial'), ] operations = [ @@ -18,27 +18,22 @@ class Migration(migrations.Migration): name='Identity', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('identity', models.CharField(default=uuid.uuid4, max_length=36, unique=True)), + ('identity', models.UUIDField(default=uuid.uuid4, unique=True)), ('name', models.CharField(max_length=100)), - ('notes', models.TextField()), - ('heartbeat', models.DateTimeField(blank=True)), + ('notes', models.TextField(blank=True)), + ('heartbeat', models.DateTimeField(blank=True, null=True)), ], ), migrations.CreateModel( name='Task', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('uuid', models.UUIDField(default=uuid.uuid4, unique=True)), ('type', models.CharField(max_length=100)), + ('config_id', models.IntegerField()), ('configuration', models.TextField()), - ('stream', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rtmp.Stream')), - ], - ), - migrations.CreateModel( - name='Claim', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='concierge.Identity')), - ('task', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='concierge.Task')), + ('claimed_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='concierge.identity')), + ('stream', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='config.stream')), ], ), ] diff --git a/source/concierge/migrations/0002_auto_20200426_1834.py b/source/concierge/migrations/0002_auto_20200426_1834.py deleted file mode 100644 index a627380..0000000 --- a/source/concierge/migrations/0002_auto_20200426_1834.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 3.0.5 on 2020-04-26 18:34 - -from django.db import migrations, models -import uuid - - -class Migration(migrations.Migration): - - dependencies = [ - ('concierge', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='claim', - name='id', - field=models.CharField(default=uuid.uuid4, max_length=36, primary_key=True, serialize=False, unique=True), - ), - ] diff --git a/source/concierge/migrations/0003_auto_20200426_1835.py b/source/concierge/migrations/0003_auto_20200426_1835.py deleted file mode 100644 index 7c78b95..0000000 --- a/source/concierge/migrations/0003_auto_20200426_1835.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 3.0.5 on 2020-04-26 18:35 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('concierge', '0002_auto_20200426_1834'), - ] - - operations = [ - migrations.AlterField( - model_name='identity', - name='heartbeat', - field=models.DateTimeField(blank=True, null=True), - ), - migrations.AlterField( - model_name='identity', - name='notes', - field=models.TextField(blank=True), - ), - ] diff --git a/source/concierge/migrations/0004_auto_20200426_1912.py b/source/concierge/migrations/0004_auto_20200426_1912.py deleted file mode 100644 index ea8829c..0000000 --- a/source/concierge/migrations/0004_auto_20200426_1912.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 3.0.5 on 2020-04-26 19:12 - -from django.db import migrations, models -import django.db.models.deletion -import uuid - - -class Migration(migrations.Migration): - - dependencies = [ - ('concierge', '0003_auto_20200426_1835'), - ] - - operations = [ - migrations.AddField( - model_name='task', - name='claimed_by', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='concierge.Identity'), - ), - migrations.AddField( - model_name='task', - name='uuid', - field=models.UUIDField(default=uuid.uuid4, serialize=False, unique=True), - ), - migrations.DeleteModel( - name='Claim', - ), - ] diff --git a/source/concierge/migrations/0005_auto_20200426_2007.py b/source/concierge/migrations/0005_auto_20200426_2007.py deleted file mode 100644 index 15293c8..0000000 --- a/source/concierge/migrations/0005_auto_20200426_2007.py +++ /dev/null @@ -1,30 +0,0 @@ -# Generated by Django 3.0.5 on 2020-04-26 20:07 - -from django.db import migrations, models -import django.db.models.deletion -import uuid - - -class Migration(migrations.Migration): - - dependencies = [ - ('concierge', '0004_auto_20200426_1912'), - ] - - operations = [ - migrations.AlterField( - model_name='identity', - name='identity', - field=models.UUIDField(default=uuid.uuid4, unique=True), - ), - migrations.AlterField( - model_name='task', - name='claimed_by', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='concierge.Identity'), - ), - migrations.AlterField( - model_name='task', - name='uuid', - field=models.UUIDField(default=uuid.uuid4, unique=True), - ), - ] diff --git a/source/concierge/migrations/0006_task_config_id.py b/source/concierge/migrations/0006_task_config_id.py deleted file mode 100644 index 9fea2c7..0000000 --- a/source/concierge/migrations/0006_task_config_id.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 3.0.6 on 2020-05-31 09:51 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('concierge', '0005_auto_20200426_2007'), - ] - - operations = [ - migrations.AddField( - model_name='task', - name='config_id', - field=models.IntegerField(default=0), - preserve_default=False, - ), - ] diff --git a/source/concierge/models.py b/source/concierge/models.py index 4944f5c..c31099b 100644 --- a/source/concierge/models.py +++ b/source/concierge/models.py @@ -1,6 +1,6 @@ import uuid from django.db import models -from rtmp.models import Stream +from config.models import Stream class Identity(models.Model): diff --git a/source/concierge/signals.py b/source/concierge/signals.py index 52ab7f6..08a7dd0 100644 --- a/source/concierge/signals.py +++ b/source/concierge/signals.py @@ -1,7 +1,7 @@ from django.dispatch import receiver -from rtmp.signals import stream_inactive from .models import Task -from rtmp.models import Stream +from config.signals import stream_inactive +from config.models import Stream @receiver(stream_inactive) diff --git a/source/concierge/tests.py b/source/concierge/tests.py deleted file mode 100644 index 9a30df3..0000000 --- a/source/concierge/tests.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.test import TestCase # noqa - -# Create your tests here. diff --git a/source/portal/__init__.py b/source/config/__init__.py similarity index 100% rename from source/portal/__init__.py rename to source/config/__init__.py diff --git a/source/config/admin.py b/source/config/admin.py new file mode 100644 index 0000000..beba580 --- /dev/null +++ b/source/config/admin.py @@ -0,0 +1,12 @@ +from django.contrib import admin +from guardian.admin import GuardedModelAdmin +from .models import Stream, Restream + + +@admin.register(Stream) +class StreamAdmin(GuardedModelAdmin): + fields = ['stream', 'name', 'publish_counter'] + +@admin.register(Restream) +class RestreamAdmin(GuardedModelAdmin): + fields = ['name', 'active', 'stream', 'format', 'target'] diff --git a/source/config/apps.py b/source/config/apps.py new file mode 100644 index 0000000..450e90b --- /dev/null +++ b/source/config/apps.py @@ -0,0 +1,9 @@ +from django.apps import AppConfig + + +class ConfigConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'config' + + def ready(self): + import config.signals, config.signals_shared # noqa \ No newline at end of file diff --git a/source/restream/forms.py b/source/config/forms.py similarity index 79% rename from source/restream/forms.py rename to source/config/forms.py index 5e36557..14bb633 100644 --- a/source/restream/forms.py +++ b/source/config/forms.py @@ -3,9 +3,9 @@ from guardian.shortcuts import get_objects_for_user from . import models -class RestreamConfigFilteredStreamForm(ModelForm): +class RestreamFilteredStreamForm(ModelForm): class Meta: - model = models.RestreamConfig + model = models.Restream fields = ['name', 'stream', 'target', 'format', 'active'] def __init__(self, *args, **kwargs): @@ -13,4 +13,4 @@ class RestreamConfigFilteredStreamForm(ModelForm): super().__init__(*args, **kwargs) # limit the stream selection to user-accessible streams - self.fields['stream'].queryset = get_objects_for_user(user, 'rtmp.view_stream') + self.fields['stream'].queryset = get_objects_for_user(user, 'config.view_stream') diff --git a/source/config/migrations/0001_initial.py b/source/config/migrations/0001_initial.py new file mode 100644 index 0000000..0f761b6 --- /dev/null +++ b/source/config/migrations/0001_initial.py @@ -0,0 +1,40 @@ +# Generated by Django 5.0.2 on 2024-02-27 19:20 + +import django.db.models.deletion +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Stream', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('stream', models.UUIDField(default=uuid.uuid4, help_text='Stream ID for this stream', unique=True)), + ('name', models.CharField(help_text='Name for this stream', max_length=100)), + ('publish_counter', models.PositiveIntegerField(default=0)), + ], + ), + migrations.CreateModel( + name='Restream', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('target', models.CharField(help_text='restream_target_help', max_length=500)), + ('name', models.CharField(help_text='restream_name_help', max_length=100)), + ('active', models.BooleanField(help_text='restream_activate_help')), + ('format', models.CharField(choices=[('flv', 'flv (RTMP)'), ('mpegts', 'mpegts (SRT)')], default='flv', help_text='restream_format_help', max_length=6)), + ('stream', models.ForeignKey(help_text='restream_stream_help', on_delete=django.db.models.deletion.CASCADE, to='config.stream')), + ], + options={ + 'verbose_name': 'restream_verbose_name', + 'verbose_name_plural': 'restream_verbose_name_plural', + }, + ), + ] diff --git a/source/portal/migrations/__init__.py b/source/config/migrations/__init__.py similarity index 100% rename from source/portal/migrations/__init__.py rename to source/config/migrations/__init__.py diff --git a/source/rtmp/models.py b/source/config/models.py similarity index 58% rename from source/rtmp/models.py rename to source/config/models.py index f4b9e67..0cf22d1 100644 --- a/source/rtmp/models.py +++ b/source/config/models.py @@ -1,30 +1,16 @@ +import json import uuid +from django.conf import settings from django.db import models from django.urls import reverse from django.utils.translation import gettext as _ from django.db.models.signals import pre_delete from portier.common import handlers - -from . import signals - - -class Application(models.Model): - name = models.CharField(max_length=100, unique=True, help_text=_("rtmp_application_name")) - - class Meta: - verbose_name = _('application_verbose_name') - verbose_name_plural = _('application_verbose_name_plural') - - def class_name(self): - return _('aplication_class_name') - - def __str__(self): - return self.name +from . import signals_shared class Stream(models.Model): - application = models.ForeignKey(Application, on_delete=models.CASCADE, help_text=_('stream_application_help')) stream = models.UUIDField(unique=True, default=uuid.uuid4, help_text=_('stream_stream_help')) name = models.CharField(max_length=100, help_text=_('stream_name_help')) @@ -38,7 +24,7 @@ class Stream(models.Model): # if so far there were less than one incoming streams, this stream # is now being considered active if self.publish_counter < 1: - signals.stream_active.send(sender=self.__class__, + signals_shared.stream_active.send(sender=self.__class__, stream=str(self.stream), param=param ) @@ -55,14 +41,14 @@ class Stream(models.Model): # if we now have less than one incoming stream, this stream is being # considered inactive if self.publish_counter < 1: - signals.stream_inactive.send(sender=self.__class__, + signals_shared.stream_inactive.send(sender=self.__class__, stream=str(self.stream), param=param ) self.save() def get_absolute_url(self): - return reverse('rtmp:stream_detail', kwargs={'pk': self.pk}) + return reverse('config:stream_detail', kwargs={'pk': self.pk}) def class_name(self): return _('stream_class_name') @@ -71,5 +57,41 @@ class Stream(models.Model): return self.name -pre_delete.connect(handlers.remove_obj_perms_connected_with_user, sender=Application) pre_delete.connect(handlers.remove_obj_perms_connected_with_user, sender=Stream) + +class Restream(models.Model): + FORMATS = ( + ('flv', 'flv (RTMP)'), + ('mpegts', 'mpegts (SRT)'), + ) + stream = models.ForeignKey(Stream, on_delete=models.CASCADE, help_text=_('restream_stream_help')) + target = models.CharField(max_length=500, help_text=_('restream_target_help')) + name = models.CharField(max_length=100, help_text=_('restream_name_help')) + active = models.BooleanField(help_text=_('restream_activate_help')) + format = models.CharField(max_length=6, choices=FORMATS, default='flv', help_text=_('restream_format_help')) + + class Meta: + verbose_name = _('restream_verbose_name') + verbose_name_plural = _('restream_verbose_name_plural') + + def class_name(self): + return _('restream_class_name') + + def get_absolute_url(self): + return reverse('config:restream_detail', kwargs={'pk': self.pk}) + + def __str__(self): + return '{} to {}'.format(self.stream, self.name) + + def get_json_config(self): + config = { + 'name': self.name, + 'app': settings.GLOBAL_STREAM_NAMESPACE, + 'stream': str(self.stream.stream), + 'target': self.target, + 'format': self.format + } + return json.dumps(config) + + +pre_delete.connect(handlers.remove_obj_perms_connected_with_user, sender=Restream) \ No newline at end of file diff --git a/source/restream/signals.py b/source/config/signals.py similarity index 84% rename from source/restream/signals.py rename to source/config/signals.py index 23da109..5d48dc8 100644 --- a/source/restream/signals.py +++ b/source/config/signals.py @@ -1,22 +1,20 @@ from django.dispatch import receiver from django.db.models.signals import post_save, post_delete -from rtmp.signals import stream_active -from .models import RestreamConfig -from rtmp.models import Stream +from .models import Restream, Stream from concierge.models import Task - +from .signals_shared import stream_active, stream_inactive @receiver(stream_active) def create_tasks(sender, **kwargs): stream = Stream.objects.get(stream=kwargs['stream']) - instances = RestreamConfig.objects.filter(active=True, stream=stream) + instances = Restream.objects.filter(active=True, stream=stream) for instance in instances: task = Task(stream=instance.stream, type='restream', config_id=instance.id, configuration=instance.get_json_config()) task.save() -@receiver(post_save, sender=RestreamConfig) +@receiver(post_save, sender=Restream) def update_tasks(sender, **kwargs): instance = kwargs['instance'] # TODO: check for breaking changes using update_fields. This needs custom save_model functions though. @@ -35,7 +33,7 @@ def update_tasks(sender, **kwargs): task.save() -@receiver(post_delete, sender=RestreamConfig) +@receiver(post_delete, sender=Restream) def delete_tasks(sender, **kwargs): instance = kwargs['instance'] # Get the current task instance if it exists, and remove it diff --git a/source/rtmp/signals.py b/source/config/signals_shared.py similarity index 69% rename from source/rtmp/signals.py rename to source/config/signals_shared.py index df2eb03..5dd2adc 100644 --- a/source/rtmp/signals.py +++ b/source/config/signals_shared.py @@ -1,4 +1,4 @@ from django.dispatch import Signal stream_active = Signal() -stream_inactive = Signal() +stream_inactive = Signal() \ No newline at end of file diff --git a/source/restream/templates/restream/restreamconfig_confirm_delete.html b/source/config/templates/config/restream_confirm_delete.html similarity index 78% rename from source/restream/templates/restream/restreamconfig_confirm_delete.html rename to source/config/templates/config/restream_confirm_delete.html index 9678bb3..dca1062 100644 --- a/source/restream/templates/restream/restreamconfig_confirm_delete.html +++ b/source/config/templates/config/restream_confirm_delete.html @@ -9,7 +9,7 @@