refactor: move rtmp, restream into config, portal into core

This commit is contained in:
Jan Koppe 2024-02-27 20:44:35 +01:00
parent ba03d9be1a
commit 72824d32d4
Signed by: thunfisch
GPG Key ID: BE935B0735A2129B
70 changed files with 247 additions and 715 deletions

View File

@ -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 django.db.models.deletion
import uuid import uuid
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
@ -10,7 +10,7 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
('rtmp', '0002_stream_publish_counter'), ('config', '0001_initial'),
] ]
operations = [ operations = [
@ -18,27 +18,22 @@ class Migration(migrations.Migration):
name='Identity', name='Identity',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('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)), ('name', models.CharField(max_length=100)),
('notes', models.TextField()), ('notes', models.TextField(blank=True)),
('heartbeat', models.DateTimeField(blank=True)), ('heartbeat', models.DateTimeField(blank=True, null=True)),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
name='Task', name='Task',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('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)), ('type', models.CharField(max_length=100)),
('config_id', models.IntegerField()),
('configuration', models.TextField()), ('configuration', models.TextField()),
('stream', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rtmp.Stream')), ('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')),
),
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')),
], ],
), ),
] ]

View File

@ -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),
),
]

View File

@ -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),
),
]

View File

@ -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',
),
]

View File

@ -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),
),
]

View File

@ -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,
),
]

View File

@ -1,6 +1,6 @@
import uuid import uuid
from django.db import models from django.db import models
from rtmp.models import Stream from config.models import Stream
class Identity(models.Model): class Identity(models.Model):

View File

@ -1,7 +1,7 @@
from django.dispatch import receiver from django.dispatch import receiver
from rtmp.signals import stream_inactive
from .models import Task from .models import Task
from rtmp.models import Stream from config.signals import stream_inactive
from config.models import Stream
@receiver(stream_inactive) @receiver(stream_inactive)

View File

@ -1,3 +0,0 @@
from django.test import TestCase # noqa
# Create your tests here.

12
source/config/admin.py Normal file
View File

@ -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']

9
source/config/apps.py Normal file
View File

@ -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

View File

@ -3,9 +3,9 @@ from guardian.shortcuts import get_objects_for_user
from . import models from . import models
class RestreamConfigFilteredStreamForm(ModelForm): class RestreamFilteredStreamForm(ModelForm):
class Meta: class Meta:
model = models.RestreamConfig model = models.Restream
fields = ['name', 'stream', 'target', 'format', 'active'] fields = ['name', 'stream', 'target', 'format', 'active']
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -13,4 +13,4 @@ class RestreamConfigFilteredStreamForm(ModelForm):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
# limit the stream selection to user-accessible streams # 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')

View File

@ -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',
},
),
]

View File

@ -1,30 +1,16 @@
import json
import uuid import uuid
from django.conf import settings
from django.db import models from django.db import models
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.db.models.signals import pre_delete from django.db.models.signals import pre_delete
from portier.common import handlers from portier.common import handlers
from . import signals_shared
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
class Stream(models.Model): 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')) stream = models.UUIDField(unique=True, default=uuid.uuid4, help_text=_('stream_stream_help'))
name = models.CharField(max_length=100, help_text=_('stream_name_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 # if so far there were less than one incoming streams, this stream
# is now being considered active # is now being considered active
if self.publish_counter < 1: if self.publish_counter < 1:
signals.stream_active.send(sender=self.__class__, signals_shared.stream_active.send(sender=self.__class__,
stream=str(self.stream), stream=str(self.stream),
param=param param=param
) )
@ -55,14 +41,14 @@ class Stream(models.Model):
# if we now have less than one incoming stream, this stream is being # if we now have less than one incoming stream, this stream is being
# considered inactive # considered inactive
if self.publish_counter < 1: if self.publish_counter < 1:
signals.stream_inactive.send(sender=self.__class__, signals_shared.stream_inactive.send(sender=self.__class__,
stream=str(self.stream), stream=str(self.stream),
param=param param=param
) )
self.save() self.save()
def get_absolute_url(self): 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): def class_name(self):
return _('stream_class_name') return _('stream_class_name')
@ -71,5 +57,41 @@ class Stream(models.Model):
return self.name 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) 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)

View File

@ -1,22 +1,20 @@
from django.dispatch import receiver from django.dispatch import receiver
from django.db.models.signals import post_save, post_delete from django.db.models.signals import post_save, post_delete
from rtmp.signals import stream_active from .models import Restream, Stream
from .models import RestreamConfig
from rtmp.models import Stream
from concierge.models import Task from concierge.models import Task
from .signals_shared import stream_active, stream_inactive
@receiver(stream_active) @receiver(stream_active)
def create_tasks(sender, **kwargs): def create_tasks(sender, **kwargs):
stream = Stream.objects.get(stream=kwargs['stream']) 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: for instance in instances:
task = Task(stream=instance.stream, type='restream', config_id=instance.id, task = Task(stream=instance.stream, type='restream', config_id=instance.id,
configuration=instance.get_json_config()) configuration=instance.get_json_config())
task.save() task.save()
@receiver(post_save, sender=RestreamConfig) @receiver(post_save, sender=Restream)
def update_tasks(sender, **kwargs): def update_tasks(sender, **kwargs):
instance = kwargs['instance'] instance = kwargs['instance']
# TODO: check for breaking changes using update_fields. This needs custom save_model functions though. # 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() task.save()
@receiver(post_delete, sender=RestreamConfig) @receiver(post_delete, sender=Restream)
def delete_tasks(sender, **kwargs): def delete_tasks(sender, **kwargs):
instance = kwargs['instance'] instance = kwargs['instance']
# Get the current task instance if it exists, and remove it # Get the current task instance if it exists, and remove it

View File

@ -1,4 +1,4 @@
from django.dispatch import Signal from django.dispatch import Signal
stream_active = Signal() stream_active = Signal()
stream_inactive = Signal() stream_inactive = Signal()

View File

@ -9,7 +9,7 @@
<div class="col-sm border-right"> <div class="col-sm border-right">
<form method="post"> <form method="post">
{% csrf_token %} {% csrf_token %}
<p>{% blocktrans with restreamconfig_config_name=object.name %}are_you_sure_you_want_to_delete_"{{ restreamconfig_config_name }}"?{% endblocktrans %}</p> <p>{% blocktrans with restream_config_name=object.name %}are_you_sure_you_want_to_delete_"{{ restream_config_name }}"?{% endblocktrans %}</p>
{% buttons %} {% buttons %}
<button type="submit" class="btn btn-danger" value="login"> <button type="submit" class="btn btn-danger" value="login">
{% fa5_icon 'trash' %} {% trans "delete" %} {% fa5_icon 'trash' %} {% trans "delete" %}

View File

@ -9,15 +9,15 @@
{% block 'content' %} {% block 'content' %}
<div class="row justify-content-between"> <div class="row justify-content-between">
<div class="col"> <div class="col">
<h6>{% trans "restreamconfig_configuration_details_header" %}</h6> <h6>{% trans "restream_configuration_details_header" %}</h6>
</div> </div>
{% get_obj_perms user for object as "obj_perms" %} {% get_obj_perms user for object as "obj_perms" %}
<div class="col-auto"> <div class="col-auto">
{% if "change_restreamconfig" in obj_perms %} {% if "change_restream" in obj_perms %}
<a href="{% url 'restream:restreamconfig_change' pk=object.pk %}" type="button" class="btn btn-sm btn-outline-primary">{% fa5_icon 'edit' %} {% trans 'change' %}</a> <a href="{% url 'config:restream_change' pk=object.pk %}" type="button" class="btn btn-sm btn-outline-primary">{% fa5_icon 'edit' %} {% trans 'change' %}</a>
{% endif %} {% endif %}
{% if "delete_restreamconfig" in obj_perms %} {% if "delete_restream" in obj_perms %}
<a href="{% url 'restream:restreamconfig_delete' pk=object.pk %}" type="button" class="btn btn-sm btn-outline-danger">{% fa5_icon 'trash' %} {% trans 'delete' %}</a> <a href="{% url 'config:restream_delete' pk=object.pk %}" type="button" class="btn btn-sm btn-outline-danger">{% fa5_icon 'trash' %} {% trans 'delete' %}</a>
{% endif %} {% endif %}
</div> </div>
</div> </div>
@ -28,7 +28,7 @@
<dt class="col-sm-3">{% trans "name" %}</dt> <dt class="col-sm-3">{% trans "name" %}</dt>
<dd class="col-sm-9">{{ object.name }}</dd> <dd class="col-sm-9">{{ object.name }}</dd>
<dt class="col-sm-3">{% trans "stream" %}</dt> <dt class="col-sm-3">{% trans "stream" %}</dt>
<dd class="col-sm-9"><a href="{% url 'rtmp:stream_detail' pk=object.stream.pk %}">{{ object.stream.name }}</a></dd> <dd class="col-sm-9"><a href="{% url 'config:stream_detail' pk=object.stream.pk %}">{{ object.stream.name }}</a></dd>
<dt class="col-sm-3">{% trans "active" %}</dt> <dt class="col-sm-3">{% trans "active" %}</dt>
<dd class="col-sm-9"> <dd class="col-sm-9">
{% if object.active %} {% if object.active %}

View File

@ -2,7 +2,7 @@
{% load i18n %} {% load i18n %}
{% load bootstrap4 %} {% load bootstrap4 %}
{% block 'content' %} {% block 'content' %}
<h6>{% trans "update_restreamconfig_configuration_header" %}</h6> <h6>{% trans "create_new_restream_configuration_header" %}</h6>
<hr class="my-4"> <hr class="my-4">
<div class="row"> <div class="row">
<div class="col-sm border-right"> <div class="col-sm border-right">
@ -18,7 +18,7 @@
</form> </form>
</div> </div>
<div class="col-sm"> <div class="col-sm">
{% trans "restreamconfig_configuration_text_html" %} {% trans "restream_configuration_text_html" %}
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -8,12 +8,12 @@
<div class="row justify-content-between"> <div class="row justify-content-between">
<div class="col"> <div class="col">
<h6>{% trans "restreamconfig_configuration_header" %}</h6> <h6>{% trans "restream_configuration_header" %}</h6>
</div> </div>
<div class="col-auto"> <div class="col-auto">
<div class="btn-toolbar" role="toolbar"> <div class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group"> <div class="btn-group" role="group">
<a href="{% url 'restream:restreamconfig_create' %}" type="button" class="btn btn-sm btn-outline-primary">{% fa5_icon 'plus' %} {% trans "create" %}</a> <a href="{% url 'config:restream_create' %}" type="button" class="btn btn-sm btn-outline-primary">{% fa5_icon 'plus' %} {% trans "create" %}</a>
</div> </div>
</div> </div>
</div> </div>
@ -59,5 +59,5 @@
<script src="{% static 'js/vue.min.js' %}"></script> <script src="{% static 'js/vue.min.js' %}"></script>
<script src="{% static 'js/axios.min.js' %}"></script> <script src="{% static 'js/axios.min.js' %}"></script>
<script src="{% static 'js/restreamconfig-list.js' %}"></script> <script src="{% static 'js/restream-list.js' %}"></script>
{% endblock %} {% endblock %}

View File

@ -2,7 +2,7 @@
{% load i18n %} {% load i18n %}
{% load bootstrap4 %} {% load bootstrap4 %}
{% block 'content' %} {% block 'content' %}
<h6>{% trans "create_new_restreamconfig_configuration_header" %}</h6> <h6>{% trans "update_restream_configuration_header" %}</h6>
<hr class="my-4"> <hr class="my-4">
<div class="row"> <div class="row">
<div class="col-sm border-right"> <div class="col-sm border-right">
@ -18,7 +18,7 @@
</form> </form>
</div> </div>
<div class="col-sm"> <div class="col-sm">
{% trans "restreamconfig_configuration_text_html" %} {% trans "restream_configuration_text_html" %}
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -14,10 +14,10 @@
{% get_obj_perms user for object as "obj_perms" %} {% get_obj_perms user for object as "obj_perms" %}
<div class="col-auto"> <div class="col-auto">
{% if "change_stream" in obj_perms %} {% if "change_stream" in obj_perms %}
<a href="{% url 'rtmp:stream_change' pk=object.pk %}" type="button" class="btn btn-sm btn-outline-primary">{% fa5_icon 'edit' %} {% trans 'change' %}</a> <a href="{% url 'config:stream_change' pk=object.pk %}" type="button" class="btn btn-sm btn-outline-primary">{% fa5_icon 'edit' %} {% trans 'change' %}</a>
{% endif %} {% endif %}
{% if "delete_stream" in obj_perms %} {% if "delete_stream" in obj_perms %}
<a href="{% url 'rtmp:stream_delete' pk=object.pk %}" type="button" class="btn btn-sm btn-outline-danger">{% fa5_icon 'trash' %} {% trans 'delete' %}</a> <a href="{% url 'config:stream_delete' pk=object.pk %}" type="button" class="btn btn-sm btn-outline-danger">{% fa5_icon 'trash' %} {% trans 'delete' %}</a>
{% endif %} {% endif %}
</div> </div>
</div> </div>
@ -27,8 +27,6 @@
<dl class="row"> <dl class="row">
<dt class="col-sm-3">{% trans "name" %}</dt> <dt class="col-sm-3">{% trans "name" %}</dt>
<dd class="col-sm-9">{{ object.name }}</dd> <dd class="col-sm-9">{{ object.name }}</dd>
<dt class="col-sm-3">{% trans "application" %}</dt>
<dd class="col-sm-9">{{ object.application }}</dd>
</dl> </dl>
<h6>{% trans "how_to_configure_your_encoder_header" %}</h6> <h6>{% trans "how_to_configure_your_encoder_header" %}</h6>
<hr class="my-4"> <hr class="my-4">

View File

@ -13,7 +13,7 @@
<div class="col-auto"> <div class="col-auto">
<div class="btn-toolbar" role="toolbar"> <div class="btn-toolbar" role="toolbar">
<div class="btn-group" role="group"> <div class="btn-group" role="group">
<a href="{% url 'rtmp:stream_create' %}" type="button" class="btn btn-sm btn-outline-primary">{% fa5_icon 'plus' %} {% trans "create" %}</a> <a href="{% url 'config:stream_create' %}" type="button" class="btn btn-sm btn-outline-primary">{% fa5_icon 'plus' %} {% trans "create" %}</a>
</div> </div>
</div> </div>
</div> </div>

3
source/config/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

View File

@ -1,7 +1,7 @@
from django.urls import path from django.urls import path
from . import views from . import views
app_name = 'rtmp' app_name = 'config'
urlpatterns = [ urlpatterns = [
path('callback/srs', views.callback_srs, name='callback_srs'), path('callback/srs', views.callback_srs, name='callback_srs'),
@ -10,4 +10,9 @@ urlpatterns = [
path('streams/<int:pk>/change', views.StreamChange.as_view(), name='stream_change'), path('streams/<int:pk>/change', views.StreamChange.as_view(), name='stream_change'),
path('streams/<int:pk>/delete', views.StreamDelete.as_view(), name='stream_delete'), path('streams/<int:pk>/delete', views.StreamDelete.as_view(), name='stream_delete'),
path('streams/create', views.StreamCreate.as_view(), name='stream_create'), path('streams/create', views.StreamCreate.as_view(), name='stream_create'),
path('restream/', views.RestreamList.as_view(), name='restream_list'),
path('restream/<int:pk>/', views.RestreamDetail.as_view(), name='restream_detail'),
path('restream/<int:pk>/change', views.RestreamUpdate.as_view(), name='restream_change'),
path('restream/<int:pk>/delete', views.RestreamDelete.as_view(), name='restream_delete'),
path('restream/create', views.RestreamCreate.as_view(), name='restream_create'),
] ]

View File

@ -7,13 +7,12 @@ from django.urls import reverse_lazy
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.admin.utils import NestedObjects from django.contrib.admin.utils import NestedObjects
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt, ensure_csrf_cookie
from django.views.generic import ListView, DetailView, CreateView, DeleteView, UpdateView from django.views.generic import ListView, DetailView, CreateView, DeleteView, UpdateView
from guardian.decorators import permission_required_or_403 from guardian.decorators import permission_required_or_403
from guardian.shortcuts import assign_perm from guardian.shortcuts import assign_perm
from . import models from . import models, forms
from . import forms
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -40,8 +39,7 @@ def callback_srs(request):
except KeyError: except KeyError:
return HttpResponse('1', status=401) return HttpResponse('1', status=401)
try: try:
application = models.Application.objects.get(name=app_name) stream = models.Stream.objects.get(stream=stream_name)
stream = models.Stream.objects.get(stream=stream_name, application=application)
except ObjectDoesNotExist: except ObjectDoesNotExist:
return HttpResponse('1', status=401) return HttpResponse('1', status=401)
@ -56,14 +54,14 @@ def callback_srs(request):
@method_decorator(login_required, name='dispatch') @method_decorator(login_required, name='dispatch')
@method_decorator(permission_required_or_403('rtmp.add_stream'), @method_decorator(permission_required_or_403('config.add_stream'),
name='dispatch') name='dispatch')
class StreamList(ListView): class StreamList(ListView):
model = models.Stream model = models.Stream
@method_decorator(login_required, name='dispatch') @method_decorator(login_required, name='dispatch')
@method_decorator(permission_required_or_403('rtmp.view_stream', @method_decorator(permission_required_or_403('config.view_stream',
(models.Stream, 'pk', 'pk')), (models.Stream, 'pk', 'pk')),
name='dispatch') name='dispatch')
class StreamDetail(DetailView): class StreamDetail(DetailView):
@ -71,12 +69,11 @@ class StreamDetail(DetailView):
@method_decorator(login_required, name='dispatch') @method_decorator(login_required, name='dispatch')
@method_decorator(permission_required_or_403('rtmp.change_stream', @method_decorator(permission_required_or_403('config.change_stream',
(models.Stream, 'pk', 'pk')), (models.Stream, 'pk', 'pk')),
name='dispatch') name='dispatch')
class StreamChange(UpdateView): class StreamChange(UpdateView):
model = models.Stream model = models.Stream
form_class = forms.StreamFilteredApplicationForm
template_name_suffix = '_update_form' template_name_suffix = '_update_form'
def get_form_kwargs(self): def get_form_kwargs(self):
@ -86,16 +83,11 @@ class StreamChange(UpdateView):
@method_decorator(login_required, name='dispatch') @method_decorator(login_required, name='dispatch')
@method_decorator(permission_required_or_403('rtmp.add_stream'), @method_decorator(permission_required_or_403('config.add_stream'),
name='dispatch') name='dispatch')
class StreamCreate(CreateView): class StreamCreate(CreateView):
model = models.Stream model = models.Stream
form_class = forms.StreamFilteredApplicationForm fields = ['name']
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
def form_valid(self, form): def form_valid(self, form):
valid = super().form_valid(form) valid = super().form_valid(form)
@ -108,12 +100,12 @@ class StreamCreate(CreateView):
@method_decorator(login_required, name='dispatch') @method_decorator(login_required, name='dispatch')
@method_decorator(permission_required_or_403('rtmp.delete_stream', @method_decorator(permission_required_or_403('config.delete_stream',
(models.Stream, 'pk', 'pk')), (models.Stream, 'pk', 'pk')),
name='dispatch') name='dispatch')
class StreamDelete(DeleteView): class StreamDelete(DeleteView):
model = models.Stream model = models.Stream
success_url = reverse_lazy('rtmp:stream_list') success_url = reverse_lazy('config:stream_list')
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
@ -125,3 +117,65 @@ class StreamDelete(DeleteView):
print(context['to_delete']) print(context['to_delete'])
return context return context
@method_decorator(login_required, name='dispatch')
@method_decorator(permission_required_or_403('config.add_restream'),
name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class RestreamList(ListView):
model = models.Restream
@method_decorator(login_required, name='dispatch')
@method_decorator(permission_required_or_403('config.view_restream',
(models.Restream, 'pk', 'pk')),
name='dispatch')
class RestreamDetail(DetailView):
model = models.Restream
@method_decorator(login_required, name='dispatch')
@method_decorator(permission_required_or_403('config.change_restream',
(models.Restream, 'pk', 'pk')),
name='dispatch')
class RestreamUpdate(UpdateView):
model = models.Restream
form_class = forms.RestreamFilteredStreamForm
template_name_suffix = '_update_form'
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
@method_decorator(login_required, name='dispatch')
@method_decorator(permission_required_or_403('config.add_restream'),
name='dispatch')
class RestreamCreate(CreateView):
model = models.Restream
form_class = forms.RestreamFilteredStreamForm
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
def form_valid(self, form):
valid = super().form_valid(form)
if valid:
user = self.request.user
assign_perm('view_restream', user, self.object)
assign_perm('change_restream', user, self.object)
assign_perm('delete_restream', user, self.object)
return valid
@method_decorator(login_required, name='dispatch')
@method_decorator(permission_required_or_403('config.delete_restream',
(models.Restream, 'pk', 'pk')),
name='dispatch')
class RestreamDelete(DeleteView):
model = models.Restream
success_url = reverse_lazy('config:restream_list')

View File

@ -5,7 +5,7 @@ from django.conf import settings
PERMISSIONS = [ PERMISSIONS = [
'add_stream', 'add_stream',
'add_restreamconfig' 'add_restream'
] ]

5
source/core/views.py Normal file
View File

@ -0,0 +1,5 @@
from django.shortcuts import render
def index(request):
return render(request, 'core/index.html')

View File

@ -1,3 +0,0 @@
from django.contrib import admin # noqa
# Register your models here.

View File

@ -1,5 +0,0 @@
from django.apps import AppConfig
class PortalConfig(AppConfig):
name = 'portal'

View File

@ -1,3 +0,0 @@
from django.db import models # noqa
# Create your models here.

View File

@ -1,3 +0,0 @@
from django.test import TestCase # noqa
# Create your tests here.

View File

@ -1,6 +0,0 @@
from django.shortcuts import render
def index(request):
# do fancy stuff here maybe
return render(request, 'portal/index.html')

View File

@ -30,7 +30,7 @@ CSRF_TRUSTED_ORIGINS = os.environ.get("DJANGO_CSRF_TRUSTED_ORIGINS", default="ht
DEFAULT_GROUP = 'default' DEFAULT_GROUP = 'default'
DEFAULT_RTMP_APPPLICATION = 'live' GLOBAL_STREAM_NAMESPACE = 'live'
# Application definition # Application definition
@ -47,10 +47,8 @@ INSTALLED_APPS = [
'bootstrap4', 'bootstrap4',
'fontawesome_5', 'fontawesome_5',
'core.apps.CoreConfig', 'core.apps.CoreConfig',
'portal.apps.PortalConfig', 'config.apps.ConfigConfig',
'rtmp.apps.RtmpConfig',
'concierge.apps.ConciergeConfig', 'concierge.apps.ConciergeConfig',
'restream.apps.RestreamConfig',
] ]
MIDDLEWARE = [ MIDDLEWARE = [

View File

@ -1,18 +1,3 @@
"""portier URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/3.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin from django.contrib import admin
from django.urls import include, path from django.urls import include, path
@ -21,9 +6,8 @@ urlpatterns = [
path('accounts/', include('django.contrib.auth.urls')), path('accounts/', include('django.contrib.auth.urls')),
path('i18n/', include('django.conf.urls.i18n')), path('i18n/', include('django.conf.urls.i18n')),
path('admin/', admin.site.urls), path('admin/', admin.site.urls),
path('rtmp/', include('rtmp.urls')), path('config/', include('config.urls')),
path('restream/', include('restream.urls')),
path('concierge/', include('concierge.urls')), path('concierge/', include('concierge.urls')),
path('api/v1/', include('restapi.urls')), path('api/v1/', include('restapi.urls')),
path('', include('portal.urls')), path('', include('core.urls')),
] ]

View File

@ -1,13 +1,12 @@
from django.urls import path, include from django.urls import path, include
from rest_framework import routers from rest_framework import routers
from .views import ApplicationViewSet, StreamViewSet, RestreamConfigViewSet from .views import StreamViewSet, RestreamViewSet
router = routers.DefaultRouter() router = routers.DefaultRouter()
router.register(r'applications', ApplicationViewSet)
router.register(r'streams', StreamViewSet) router.register(r'streams', StreamViewSet)
router.register(r'restreamconfigs', RestreamConfigViewSet) router.register(r'restreams', RestreamViewSet)
app_name = 'restapi' app_name = 'restapi'

View File

@ -1,20 +1,7 @@
from rest_framework_guardian.serializers import ObjectPermissionsAssignmentMixin from rest_framework_guardian.serializers import ObjectPermissionsAssignmentMixin
from rest_framework import serializers, viewsets from rest_framework import serializers, viewsets
from rest_framework_guardian import filters from rest_framework_guardian import filters
from rtmp.models import Application, Stream from config.models import Stream, Restream
from restream.models import RestreamConfig
class ApplicationSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Application
fields = ['id', 'name']
class ApplicationViewSet(viewsets.ReadOnlyModelViewSet):
queryset = Application.objects.all()
serializer_class = ApplicationSerializer
filter_backends = [filters.ObjectPermissionsFilter]
class StreamSerializer(ObjectPermissionsAssignmentMixin, serializers.ModelSerializer): class StreamSerializer(ObjectPermissionsAssignmentMixin, serializers.ModelSerializer):
@ -38,9 +25,9 @@ class StreamViewSet(viewsets.ModelViewSet):
filter_backends = [filters.ObjectPermissionsFilter] filter_backends = [filters.ObjectPermissionsFilter]
class RestreamConfigSerializer(ObjectPermissionsAssignmentMixin, serializers.ModelSerializer): class RestreamSerializer(ObjectPermissionsAssignmentMixin, serializers.ModelSerializer):
class Meta: class Meta:
model = RestreamConfig model = Restream
fields = '__all__' fields = '__all__'
def get_permissions_map(self, created): def get_permissions_map(self, created):
@ -53,12 +40,12 @@ class RestreamConfigSerializer(ObjectPermissionsAssignmentMixin, serializers.Mod
def validate_stream(self, value): def validate_stream(self, value):
request = self.context['request'] request = self.context['request']
if not request.user.has_perm('rtmp.view_stream', value): if not request.user.has_perm('config.view_stream', value):
raise serializers.ValidationError('Access to stream is not authorized') raise serializers.ValidationError('Access to stream is not authorized')
return value return value
class RestreamConfigViewSet(viewsets.ModelViewSet): class RestreamViewSet(viewsets.ModelViewSet):
queryset = RestreamConfig.objects.all() queryset = Restream.objects.all()
serializer_class = RestreamConfigSerializer serializer_class = RestreamSerializer
filter_backends = [filters.ObjectPermissionsFilter] filter_backends = [filters.ObjectPermissionsFilter]

View File

@ -1,10 +0,0 @@
from django.contrib import admin
from guardian.admin import GuardedModelAdmin
from .models import RestreamConfig
@admin.register(RestreamConfig)
class RestreamConfigAdmin(GuardedModelAdmin):
fields = ['name', 'active', 'stream', 'format', 'target']

View File

@ -1,8 +0,0 @@
from django.apps import AppConfig
class RestreamConfig(AppConfig):
name = 'restream'
def ready(self):
import restream.signals # noqa

View File

@ -1,26 +0,0 @@
# Generated by Django 3.0.5 on 2020-04-23 19:04
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('rtmp', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='RestreamConfig',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('target', models.CharField(max_length=500)),
('name', models.CharField(max_length=100)),
('active', models.BooleanField()),
('stream', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rtmp.Stream')),
],
),
]

View File

@ -1,39 +0,0 @@
# Generated by Django 3.0.5 on 2020-05-01 13:02
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('rtmp', '0004_auto_20200501_1302'),
('restream', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='restreamconfig',
options={'verbose_name': 'restreamconfig_verbose_name', 'verbose_name_plural': 'restreamconfig_verbose_name_plural'},
),
migrations.AlterField(
model_name='restreamconfig',
name='active',
field=models.BooleanField(help_text='restreamconfig_activate_help'),
),
migrations.AlterField(
model_name='restreamconfig',
name='name',
field=models.CharField(help_text='restreamconfig_name_help', max_length=100),
),
migrations.AlterField(
model_name='restreamconfig',
name='stream',
field=models.ForeignKey(help_text='restreamconfig_stream_help', on_delete=django.db.models.deletion.CASCADE, to='rtmp.Stream'),
),
migrations.AlterField(
model_name='restreamconfig',
name='target',
field=models.CharField(help_text='restreamconfig_target_help', max_length=500),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 3.1.13 on 2021-12-14 17:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('restream', '0002_auto_20200501_1302'),
]
operations = [
migrations.AddField(
model_name='restreamconfig',
name='format',
field=models.CharField(choices=[('flv', 'flv (RTMP)'), ('mpegts', 'mpegts (SRT)')], default='flv', help_text='restreamconfig_format_help', max_length=6),
),
]

View File

@ -1,46 +0,0 @@
from django.db import models
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django.db.models.signals import pre_delete
from portier.common import handlers
import json
from rtmp.models import Stream
class RestreamConfig(models.Model):
FORMATS = (
('flv', 'flv (RTMP)'),
('mpegts', 'mpegts (SRT)'),
)
stream = models.ForeignKey(Stream, on_delete=models.CASCADE, help_text=_('restreamconfig_stream_help'))
target = models.CharField(max_length=500, help_text=_('restreamconfig_target_help'))
name = models.CharField(max_length=100, help_text=_('restreamconfig_name_help'))
active = models.BooleanField(help_text=_('restreamconfig_activate_help'))
format = models.CharField(max_length=6, choices=FORMATS, default='flv', help_text=_('restreamconfig_format_help'))
class Meta:
verbose_name = _('restreamconfig_verbose_name')
verbose_name_plural = _('restreamconfig_verbose_name_plural')
def class_name(self):
return _('restreamconfig_class_name')
def get_absolute_url(self):
return reverse('restream:restreamconfig_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': self.stream.application.name,
'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=RestreamConfig)

View File

@ -1,3 +0,0 @@
from django.test import TestCase # noqa
# Create your tests here.

View File

@ -1,12 +0,0 @@
from django.urls import path
from . import views
app_name = 'restream'
urlpatterns = [
path('restreamconfig/', views.RestreamConfigList.as_view(), name='restreamconfig_list'),
path('restreamconfig/<int:pk>/', views.RestreamConfigDetail.as_view(), name='restreamconfig_detail'),
path('restreamconfig/<int:pk>/change', views.RestreamConfigChange.as_view(), name='restreamconfig_change'),
path('restreamconfig/<int:pk>/delete', views.RestreamConfigDelete.as_view(), name='restreamconfig_delete'),
path('restreamconfig/create', views.RestreamConfigCreate.as_view(), name='restreamconfig_create'),
]

View File

@ -1,72 +0,0 @@
from django.urls import reverse_lazy
from django.contrib.auth.decorators import login_required
from django.views.decorators.csrf import ensure_csrf_cookie
from django.utils.decorators import method_decorator
from django.views.generic import ListView, DetailView, CreateView, DeleteView, UpdateView
from guardian.decorators import permission_required_or_403
from guardian.shortcuts import assign_perm
from . import models
from . import forms
@method_decorator(login_required, name='dispatch')
@method_decorator(permission_required_or_403('restream.add_restreamconfig'),
name='dispatch')
@method_decorator(ensure_csrf_cookie, name='dispatch')
class RestreamConfigList(ListView):
model = models.RestreamConfig
@method_decorator(login_required, name='dispatch')
@method_decorator(permission_required_or_403('restream.view_restreamconfig',
(models.RestreamConfig, 'pk', 'pk')),
name='dispatch')
class RestreamConfigDetail(DetailView):
model = models.RestreamConfig
@method_decorator(login_required, name='dispatch')
@method_decorator(permission_required_or_403('restream.change_restreamconfig',
(models.RestreamConfig, 'pk', 'pk')),
name='dispatch')
class RestreamConfigChange(UpdateView):
model = models.RestreamConfig
form_class = forms.RestreamConfigFilteredStreamForm
template_name_suffix = '_update_form'
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
@method_decorator(login_required, name='dispatch')
@method_decorator(permission_required_or_403('restream.add_restreamconfig'),
name='dispatch')
class RestreamConfigCreate(CreateView):
model = models.RestreamConfig
form_class = forms.RestreamConfigFilteredStreamForm
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
def form_valid(self, form):
valid = super().form_valid(form)
if valid:
user = self.request.user
assign_perm('view_restreamconfig', user, self.object)
assign_perm('change_restreamconfig', user, self.object)
assign_perm('delete_restreamconfig', user, self.object)
return valid
@method_decorator(login_required, name='dispatch')
@method_decorator(permission_required_or_403('restream.delete_restreamconfig',
(models.RestreamConfig, 'pk', 'pk')),
name='dispatch')
class RestreamConfigDelete(DeleteView):
model = models.RestreamConfig
success_url = reverse_lazy('restream:restreamconfig_list')

View File

@ -1,15 +0,0 @@
from django.contrib import admin
from guardian.admin import GuardedModelAdmin
from .models import Application, Stream
@admin.register(Application)
class ApplicationAdmin(GuardedModelAdmin):
fields = ['name']
@admin.register(Stream)
class StreamAdmin(GuardedModelAdmin):
fields = ['application', 'stream', 'name', 'publish_counter']

View File

@ -1,8 +0,0 @@
from django.apps import AppConfig
class RtmpConfig(AppConfig):
name = 'rtmp'
def ready(self):
import rtmp.signals # noqa

View File

@ -1,16 +0,0 @@
from django.forms import ModelForm
from guardian.shortcuts import get_objects_for_user
from . import models
class StreamFilteredApplicationForm(ModelForm):
class Meta:
model = models.Stream
fields = ['name', 'application']
def __init__(self, *args, **kwargs):
user = kwargs.pop('user', None)
super().__init__(*args, **kwargs)
# limit the stream selection to user-accessible streams
self.fields['application'].queryset = get_objects_for_user(user, 'rtmp.view_application')

View File

@ -1,16 +0,0 @@
from django.core.management.base import BaseCommand
from django.contrib.auth.models import Group
from django.conf import settings
from guardian.shortcuts import assign_perm
from rtmp import models
class Command(BaseCommand):
help = 'Creates a default RTMP application that is available to all users in the default group'
def handle(self, *args, **options):
default_group, _ = Group.objects.get_or_create(name=settings.DEFAULT_GROUP)
default_app, _ = models.Application.objects.get_or_create(name=settings.DEFAULT_RTMP_APPPLICATION)
assign_perm('view_application', default_group, default_app)

View File

@ -1,32 +0,0 @@
# Generated by Django 3.0.5 on 2020-04-23 19:04
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Application',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(help_text='rtmp_application_name', max_length=100, unique=True)),
],
),
migrations.CreateModel(
name='Stream',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('stream', models.CharField(default=uuid.uuid4, max_length=64, unique=True)),
('name', models.CharField(max_length=100)),
('application', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rtmp.Application')),
],
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 3.0.5 on 2020-04-26 17:25
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rtmp', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='stream',
name='publish_counter',
field=models.PositiveIntegerField(default=0),
),
]

View File

@ -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 = [
('rtmp', '0002_stream_publish_counter'),
]
operations = [
migrations.AlterField(
model_name='stream',
name='stream',
field=models.UUIDField(default=uuid.uuid4, unique=True),
),
]

View File

@ -1,34 +0,0 @@
# Generated by Django 3.0.5 on 2020-05-01 13:02
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('rtmp', '0003_auto_20200426_1834'),
]
operations = [
migrations.AlterModelOptions(
name='application',
options={'verbose_name': 'application_verbose_name', 'verbose_name_plural': 'application_verbose_name_plural'},
),
migrations.AlterField(
model_name='stream',
name='application',
field=models.ForeignKey(help_text='stream_application_help', on_delete=django.db.models.deletion.CASCADE, to='rtmp.Application'),
),
migrations.AlterField(
model_name='stream',
name='name',
field=models.CharField(help_text='stream_name_help', max_length=100),
),
migrations.AlterField(
model_name='stream',
name='stream',
field=models.UUIDField(default=uuid.uuid4, help_text='stream_stream_help', unique=True),
),
]

View File

@ -1,39 +0,0 @@
# Generated by Django 3.0.6 on 2020-05-31 09:51
from django.db import migrations, models
import django.db.models.deletion
import uuid
class Migration(migrations.Migration):
dependencies = [
('rtmp', '0004_auto_20200501_1302'),
]
operations = [
migrations.AlterModelOptions(
name='application',
options={'verbose_name': 'RTMP application', 'verbose_name_plural': 'RTMP applications'},
),
migrations.AlterField(
model_name='application',
name='name',
field=models.CharField(help_text='RTMP application name', max_length=100, unique=True),
),
migrations.AlterField(
model_name='stream',
name='application',
field=models.ForeignKey(help_text='Application which the stream is assigned to', on_delete=django.db.models.deletion.CASCADE, to='rtmp.Application'),
),
migrations.AlterField(
model_name='stream',
name='name',
field=models.CharField(help_text='Name for this stream', max_length=100),
),
migrations.AlterField(
model_name='stream',
name='stream',
field=models.UUIDField(default=uuid.uuid4, help_text='Stream ID for this stream', unique=True),
),
]

View File

@ -1,3 +0,0 @@
from django.test import TestCase # noqa
# Create your tests here.

View File

@ -25,7 +25,6 @@ migrate() {
initialize() { initialize() {
python manage.py createdefaultgroup python manage.py createdefaultgroup
python manage.py createdefaultapplication
} }
wait_for_redis wait_for_redis

View File

@ -13,7 +13,7 @@ var app = new Vue({
}, },
toggleActive(cfg) { toggleActive(cfg) {
axios axios
.patch('/api/v1/restreamconfigs/' + cfg.id + '/', { active: !cfg.active }) .patch('/api/v1/restream/' + cfg.id + '/', { active: !cfg.active })
.then(response => { .then(response => {
i = this.cfgs.findIndex((obj => obj.id == cfg.id)) i = this.cfgs.findIndex((obj => obj.id == cfg.id))
Vue.set(this.cfgs, i, response.data) Vue.set(this.cfgs, i, response.data)
@ -22,7 +22,7 @@ var app = new Vue({
}, },
fetchData() { fetchData() {
axios axios
.get('/api/v1/restreamconfigs/') .get('/api/v1/restreams/')
.then(response => { .then(response => {
this.cfgs = response.data this.cfgs = response.data
this.isLoading = false this.isLoading = false

View File

@ -39,8 +39,8 @@
{% trans "navbar_streaming" %} {% trans "navbar_streaming" %}
</a> </a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarStreamingDropdown"> <div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarStreamingDropdown">
<a class="dropdown-item{% if not perms.rtmp.add_stream %} disabled{% endif %}" href="{% url 'rtmp:stream_list' %}">{% trans "navbar_configuration_streams" %}</a> <a class="dropdown-item{% if not perms.config.add_stream %} disabled{% endif %}" href="{% url 'config:stream_list' %}">{% trans "navbar_configuration_streams" %}</a>
<a class="dropdown-item{% if not perms.restream.add_restreamconfig %} disabled{% endif %}" href="{% url 'restream:restreamconfig_list' %}">{% trans "navbar_configuration_restreams" %}</a> <a class="dropdown-item{% if not perms.config.add_restream %} disabled{% endif %}" href="{% url 'config:restream_list' %}">{% trans "navbar_configuration_restreams" %}</a>
</div> </div>
</li> </li>
{% endif %} {% endif %}