add object permissions with guardian; users only have access to their own streams/restreamconfigs
This commit is contained in:
		
							parent
							
								
									b16e9c955c
								
							
						
					
					
						commit
						730889b9a1
					
				| 
						 | 
					@ -0,0 +1,10 @@
 | 
				
			||||||
 | 
					from django.contrib.contenttypes.models import ContentType
 | 
				
			||||||
 | 
					from django.db.models import Q
 | 
				
			||||||
 | 
					from guardian.models import UserObjectPermission
 | 
				
			||||||
 | 
					from guardian.models import GroupObjectPermission
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def remove_obj_perms_connected_with_user(sender, instance, **kwargs):
 | 
				
			||||||
 | 
					    filters = Q(content_type=ContentType.objects.get_for_model(instance), object_pk=instance.pk)
 | 
				
			||||||
 | 
					    UserObjectPermission.objects.filter(filters).delete()
 | 
				
			||||||
 | 
					    GroupObjectPermission.objects.filter(filters).delete()
 | 
				
			||||||
| 
						 | 
					@ -36,6 +36,7 @@ INSTALLED_APPS = [
 | 
				
			||||||
    'django.contrib.sessions',
 | 
					    'django.contrib.sessions',
 | 
				
			||||||
    'django.contrib.messages',
 | 
					    'django.contrib.messages',
 | 
				
			||||||
    'django.contrib.staticfiles',
 | 
					    'django.contrib.staticfiles',
 | 
				
			||||||
 | 
					    'guardian',
 | 
				
			||||||
    'django_registration',
 | 
					    'django_registration',
 | 
				
			||||||
    'bootstrap4',
 | 
					    'bootstrap4',
 | 
				
			||||||
    'fa',
 | 
					    'fa',
 | 
				
			||||||
| 
						 | 
					@ -55,6 +56,11 @@ MIDDLEWARE = [
 | 
				
			||||||
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
 | 
					    'django.middleware.clickjacking.XFrameOptionsMiddleware',
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					AUTHENTICATION_BACKENDS = (
 | 
				
			||||||
 | 
					    'django.contrib.auth.backends.ModelBackend',
 | 
				
			||||||
 | 
					    'guardian.backends.ObjectPermissionBackend',
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ROOT_URLCONF = 'portier.urls'
 | 
					ROOT_URLCONF = 'portier.urls'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TEMPLATES = [
 | 
					TEMPLATES = [
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,7 @@
 | 
				
			||||||
django>=3.0
 | 
					django>=3.0
 | 
				
			||||||
django-registration>=3.1
 | 
					django-registration>=3.1
 | 
				
			||||||
django-bootstrap4
 | 
					django-bootstrap4
 | 
				
			||||||
 | 
					django-guardian
 | 
				
			||||||
django-fa
 | 
					django-fa
 | 
				
			||||||
celery>=4.4
 | 
					celery>=4.4
 | 
				
			||||||
gunicorn>=20
 | 
					gunicorn>=20
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,9 @@
 | 
				
			||||||
from django.contrib import admin
 | 
					from django.contrib import admin
 | 
				
			||||||
 | 
					from guardian.admin import GuardedModelAdmin
 | 
				
			||||||
from .models import RestreamConfig
 | 
					from .models import RestreamConfig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class RestreamConfigAdmin(admin.ModelAdmin):
 | 
					class RestreamConfigAdmin(GuardedModelAdmin):
 | 
				
			||||||
    fields = ['name', 'active', 'stream', 'target']
 | 
					    fields = ['name', 'active', 'stream', 'target']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,16 @@
 | 
				
			||||||
 | 
					from django.forms import ModelForm
 | 
				
			||||||
 | 
					from guardian.shortcuts import get_objects_for_user
 | 
				
			||||||
 | 
					from . import models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RestreamConfigFilteredStreamForm(ModelForm):
 | 
				
			||||||
 | 
					    class Meta:
 | 
				
			||||||
 | 
					        model = models.RestreamConfig
 | 
				
			||||||
 | 
					        fields = ['name', 'stream', 'target', 'active']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def __init__(self, *args, **kwargs):
 | 
				
			||||||
 | 
					        user = kwargs.pop('user', None)
 | 
				
			||||||
 | 
					        super().__init__(*args, **kwargs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # limit the stream selection to user-accessible streams
 | 
				
			||||||
 | 
					        self.fields['stream'].queryset = get_objects_for_user(user, 'rtmp.view_stream')
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,8 @@
 | 
				
			||||||
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_lazy as _
 | 
					from django.utils.translation import gettext_lazy as _
 | 
				
			||||||
 | 
					from django.db.models.signals import pre_delete
 | 
				
			||||||
 | 
					from portier.common import handlers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from rtmp.models import Stream
 | 
					from rtmp.models import Stream
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,3 +25,6 @@ class RestreamConfig(models.Model):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __str__(self):
 | 
					    def __str__(self):
 | 
				
			||||||
        return '{} to {}'.format(self.stream, self.name)
 | 
					        return '{} to {}'.format(self.stream, self.name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pre_delete.connect(handlers.remove_obj_perms_connected_with_user, sender=RestreamConfig)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,8 @@
 | 
				
			||||||
{% load i18n %}
 | 
					{% load i18n %}
 | 
				
			||||||
{% load bootstrap4 %}
 | 
					{% load bootstrap4 %}
 | 
				
			||||||
{% load font_awesome %}
 | 
					{% load font_awesome %}
 | 
				
			||||||
 | 
					{% load guardian_tags %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% block 'content' %}
 | 
					{% block 'content' %}
 | 
				
			||||||
<h6>{% trans "restreamconfig_configuration_header" %}</h6>
 | 
					<h6>{% trans "restreamconfig_configuration_header" %}</h6>
 | 
				
			||||||
<hr class="my-4">
 | 
					<hr class="my-4">
 | 
				
			||||||
| 
						 | 
					@ -19,15 +21,20 @@
 | 
				
			||||||
  </thead>
 | 
					  </thead>
 | 
				
			||||||
  <tbody>
 | 
					  <tbody>
 | 
				
			||||||
    {% for object in object_list %}
 | 
					    {% for object in object_list %}
 | 
				
			||||||
 | 
					    {% get_obj_perms user for object as "obj_perms" %}
 | 
				
			||||||
 | 
					    {% if "view_restreamconfig" in obj_perms %}
 | 
				
			||||||
    <tr>
 | 
					    <tr>
 | 
				
			||||||
      <th scope="ro">{{ object.name }}</th>
 | 
					      <th scope="ro">{{ object.name }}</th>
 | 
				
			||||||
      <td align="right">
 | 
					      <td align="right">
 | 
				
			||||||
        <div class="btn-group" role="group">
 | 
					        <div class="btn-group" role="group">
 | 
				
			||||||
        <a href="{% url 'restream:restreamconfig_detail' pk=object.pk %}" type="button" class="btn btn-sm btn-primary">{% trans "details" %}</a>
 | 
					        <a href="{% url 'restream:restreamconfig_detail' pk=object.pk %}" type="button" class="btn btn-sm btn-primary">{% trans "details" %}</a>
 | 
				
			||||||
 | 
					        {% if "delete_restreamconfig" in obj_perms %}
 | 
				
			||||||
        <a href="{% url 'restream:restreamconfig_delete' pk=object.pk %}" type="button" class="btn btn-sm btn-danger">{% fa 'trash' %}</a>
 | 
					        <a href="{% url 'restream:restreamconfig_delete' pk=object.pk %}" type="button" class="btn btn-sm btn-danger">{% fa 'trash' %}</a>
 | 
				
			||||||
 | 
					        {% endif %}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </td>
 | 
					      </td>
 | 
				
			||||||
    </tr>
 | 
					    </tr>
 | 
				
			||||||
 | 
					    {% endif %}
 | 
				
			||||||
    {% endfor %}
 | 
					    {% endfor %}
 | 
				
			||||||
  </tbody>
 | 
					  </tbody>
 | 
				
			||||||
</table>
 | 
					</table>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,27 +2,54 @@ from django.urls import reverse_lazy
 | 
				
			||||||
from django.contrib.auth.decorators import login_required
 | 
					from django.contrib.auth.decorators import login_required
 | 
				
			||||||
from django.utils.decorators import method_decorator
 | 
					from django.utils.decorators import method_decorator
 | 
				
			||||||
from django.views.generic import ListView, DetailView, CreateView, DeleteView
 | 
					from django.views.generic import ListView, DetailView, CreateView, DeleteView
 | 
				
			||||||
 | 
					from guardian.decorators import permission_required_or_403
 | 
				
			||||||
 | 
					from guardian.shortcuts import assign_perm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from . import models
 | 
					from . import models
 | 
				
			||||||
 | 
					from . import forms
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@method_decorator(login_required, name='dispatch')
 | 
					@method_decorator(login_required, name='dispatch')
 | 
				
			||||||
 | 
					@method_decorator(permission_required_or_403('restream.add_restreamconfig'),
 | 
				
			||||||
 | 
					                  name='dispatch')
 | 
				
			||||||
class RestreamConfigList(ListView):
 | 
					class RestreamConfigList(ListView):
 | 
				
			||||||
    model = models.RestreamConfig
 | 
					    model = models.RestreamConfig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@method_decorator(login_required, name='dispatch')
 | 
					@method_decorator(login_required, name='dispatch')
 | 
				
			||||||
 | 
					@method_decorator(permission_required_or_403('restream.view_restreamconfig',
 | 
				
			||||||
 | 
					                  (models.RestreamConfig, 'pk', 'pk')),
 | 
				
			||||||
 | 
					                  name='dispatch')
 | 
				
			||||||
class RestreamConfigDetail(DetailView):
 | 
					class RestreamConfigDetail(DetailView):
 | 
				
			||||||
    model = models.RestreamConfig
 | 
					    model = models.RestreamConfig
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@method_decorator(login_required, name='dispatch')
 | 
					@method_decorator(login_required, name='dispatch')
 | 
				
			||||||
 | 
					@method_decorator(permission_required_or_403('restream.add_restreamconfig'),
 | 
				
			||||||
 | 
					                  name='dispatch')
 | 
				
			||||||
class RestreamConfigCreate(CreateView):
 | 
					class RestreamConfigCreate(CreateView):
 | 
				
			||||||
    model = models.RestreamConfig
 | 
					    model = models.RestreamConfig
 | 
				
			||||||
    fields = ["name", "stream", "target", "active"]
 | 
					    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(login_required, name='dispatch')
 | 
				
			||||||
 | 
					@method_decorator(permission_required_or_403('restream.delete_restreamconfig',
 | 
				
			||||||
 | 
					                  (models.RestreamConfig, 'pk', 'pk')),
 | 
				
			||||||
 | 
					                  name='dispatch')
 | 
				
			||||||
class RestreamConfigDelete(DeleteView):
 | 
					class RestreamConfigDelete(DeleteView):
 | 
				
			||||||
    model = models.RestreamConfig
 | 
					    model = models.RestreamConfig
 | 
				
			||||||
    success_url = reverse_lazy('restream:restreamconfig_list')
 | 
					    success_url = reverse_lazy('restream:restreamconfig_list')
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,13 @@
 | 
				
			||||||
from django.contrib import admin
 | 
					from django.contrib import admin
 | 
				
			||||||
 | 
					from guardian.admin import GuardedModelAdmin
 | 
				
			||||||
from .models import Application, Stream
 | 
					from .models import Application, Stream
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ApplicationAdmin(admin.ModelAdmin):
 | 
					class ApplicationAdmin(GuardedModelAdmin):
 | 
				
			||||||
    fields = ['name']
 | 
					    fields = ['name']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class StreamAdmin(admin.ModelAdmin):
 | 
					class StreamAdmin(GuardedModelAdmin):
 | 
				
			||||||
    fields = ['application', 'stream', 'name', 'publish_counter']
 | 
					    fields = ['application', 'stream', 'name', 'publish_counter']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,10 @@
 | 
				
			||||||
 | 
					import uuid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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 _
 | 
				
			||||||
import uuid
 | 
					from django.db.models.signals import pre_delete
 | 
				
			||||||
 | 
					from portier.common import handlers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from . import signals
 | 
					from . import signals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -66,3 +69,7 @@ class Stream(models.Model):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __str__(self):
 | 
					    def __str__(self):
 | 
				
			||||||
        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)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,6 +2,8 @@
 | 
				
			||||||
{% load i18n %}
 | 
					{% load i18n %}
 | 
				
			||||||
{% load bootstrap4 %}
 | 
					{% load bootstrap4 %}
 | 
				
			||||||
{% load font_awesome %}
 | 
					{% load font_awesome %}
 | 
				
			||||||
 | 
					{% load guardian_tags %}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{% block 'content' %}
 | 
					{% block 'content' %}
 | 
				
			||||||
<h6>{% trans "stream_configuration_header" %}</h6>
 | 
					<h6>{% trans "stream_configuration_header" %}</h6>
 | 
				
			||||||
<hr class="my-4">
 | 
					<hr class="my-4">
 | 
				
			||||||
| 
						 | 
					@ -19,15 +21,20 @@
 | 
				
			||||||
  </thead>
 | 
					  </thead>
 | 
				
			||||||
  <tbody>
 | 
					  <tbody>
 | 
				
			||||||
    {% for object in object_list %}
 | 
					    {% for object in object_list %}
 | 
				
			||||||
 | 
					    {% get_obj_perms user for object as "obj_perms" %}
 | 
				
			||||||
 | 
					    {% if "view_stream" in obj_perms %}
 | 
				
			||||||
    <tr>
 | 
					    <tr>
 | 
				
			||||||
      <th scope="ro">{{ object.name }}</th>
 | 
					      <th scope="ro">{{ object.name }}</th>
 | 
				
			||||||
      <td align="right">
 | 
					      <td align="right">
 | 
				
			||||||
        <div class="btn-group" role="group">
 | 
					        <div class="btn-group" role="group">
 | 
				
			||||||
        <a href="{% url 'rtmp:stream_detail' pk=object.pk %}" type="button" class="btn btn-sm btn-primary">{% trans "details" %}</a>
 | 
					        <a href="{% url 'rtmp:stream_detail' pk=object.pk %}" type="button" class="btn btn-sm btn-primary">{% trans "details" %}</a>
 | 
				
			||||||
 | 
					        {% if "delete_stream" in obj_perms %}
 | 
				
			||||||
        <a href="{% url 'rtmp:stream_delete' pk=object.pk %}" type="button" class="btn btn-sm btn-danger">{% fa 'trash' %}</a>
 | 
					        <a href="{% url 'rtmp:stream_delete' pk=object.pk %}" type="button" class="btn btn-sm btn-danger">{% fa 'trash' %}</a>
 | 
				
			||||||
 | 
					        {% endif %}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </td>
 | 
					      </td>
 | 
				
			||||||
    </tr>
 | 
					    </tr>
 | 
				
			||||||
 | 
					    {% endif %}
 | 
				
			||||||
    {% endfor %}
 | 
					    {% endfor %}
 | 
				
			||||||
  </tbody>
 | 
					  </tbody>
 | 
				
			||||||
</table>
 | 
					</table>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,6 +9,8 @@ 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
 | 
				
			||||||
from django.views.generic import ListView, DetailView, CreateView, DeleteView
 | 
					from django.views.generic import ListView, DetailView, CreateView, DeleteView
 | 
				
			||||||
 | 
					from guardian.decorators import permission_required_or_403
 | 
				
			||||||
 | 
					from guardian.shortcuts import assign_perm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from . import models
 | 
					from . import models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -48,22 +50,41 @@ 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'),
 | 
				
			||||||
 | 
					                  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',
 | 
				
			||||||
 | 
					                  (models.Stream, 'pk', 'pk')),
 | 
				
			||||||
 | 
					                  name='dispatch')
 | 
				
			||||||
class StreamDetail(DetailView):
 | 
					class StreamDetail(DetailView):
 | 
				
			||||||
    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.add_stream'),
 | 
				
			||||||
 | 
					                  name='dispatch')
 | 
				
			||||||
class StreamCreate(CreateView):
 | 
					class StreamCreate(CreateView):
 | 
				
			||||||
    model = models.Stream
 | 
					    model = models.Stream
 | 
				
			||||||
    fields = ["name", "application"]
 | 
					    fields = ["name", "application"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def form_valid(self, form):
 | 
				
			||||||
 | 
					        valid = super().form_valid(form)
 | 
				
			||||||
 | 
					        if valid:
 | 
				
			||||||
 | 
					            user = self.request.user
 | 
				
			||||||
 | 
					            assign_perm('view_stream', user, self.object)
 | 
				
			||||||
 | 
					            assign_perm('change_stream', user, self.object)
 | 
				
			||||||
 | 
					            assign_perm('delete_stream', user, self.object)
 | 
				
			||||||
 | 
					        return valid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@method_decorator(login_required, name='dispatch')
 | 
					@method_decorator(login_required, name='dispatch')
 | 
				
			||||||
 | 
					@method_decorator(permission_required_or_403('rtmp.delete_stream',
 | 
				
			||||||
 | 
					                  (models.Stream, 'pk', 'pk')),
 | 
				
			||||||
 | 
					                  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('rtmp:stream_list')
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue