diff --git a/ffplayout/api/admin.py b/ffplayout/api/admin.py index 8c38f3f3..595e4e28 100644 --- a/ffplayout/api/admin.py +++ b/ffplayout/api/admin.py @@ -1,3 +1,13 @@ from django.contrib import admin -# Register your models here. +from api.models import GuiSettings + + +class GuiSettingsAdmin(admin.ModelAdmin): + + class Meta: + model = GuiSettings + fields = '__all__' + + +admin.site.register(GuiSettings, GuiSettingsAdmin) diff --git a/ffplayout/api/models.py b/ffplayout/api/models.py index 71a83623..d1bb7888 100644 --- a/ffplayout/api/models.py +++ b/ffplayout/api/models.py @@ -1,3 +1,34 @@ +import psutil + from django.db import models -# Create your models here. + +class GuiSettings(models.Model): + """ + Here we manage the settings for the web GUI: + - Player URL + - settings for the statistics + """ + + addrs = psutil.net_if_addrs() + addrs = [(i, i) for i in addrs.keys()] + + player_url = models.CharField(max_length=255) + playout_config = models.CharField(max_length=255) + net_interface = models.CharField( + max_length=20, + choices=addrs, + default=None, + ) + media_disk = models.CharField(max_length=255) + + def save(self, *args, **kwargs): + if self.pk is not None: + super(GuiSettings, self).save(*args, **kwargs) + + def delete(self, *args, **kwargs): + if not self.related_query.all(): + super(GuiSettings, self).delete(*args, **kwargs) + + class Meta: + verbose_name_plural = "guisettings" diff --git a/ffplayout/api/serializers.py b/ffplayout/api/serializers.py new file mode 100644 index 00000000..b0207867 --- /dev/null +++ b/ffplayout/api/serializers.py @@ -0,0 +1,57 @@ +from django.contrib.auth.models import User + +from rest_framework import serializers + +from api.models import GuiSettings + + +class UserSerializer(serializers.ModelSerializer): + confirm_password = serializers.CharField(write_only=False, required=False) + new_password = serializers.CharField(write_only=True, required=False) + old_password = serializers.CharField(write_only=True, required=False) + + class Meta: + model = User + fields = ['username', 'old_password', + 'new_password', 'confirm_password', 'email'] + + def update(self, instance, validated_data): + instance.password = validated_data.get('password', instance.password) + + if not validated_data['new_password']: + raise serializers.ValidationError({'new_password': 'not found'}) + + if not validated_data['old_password']: + raise serializers.ValidationError({'old_password': 'not found'}) + + if not instance.check_password(validated_data['old_password']): + raise serializers.ValidationError( + {'old_password': 'wrong password'}) + + if validated_data['new_password'] != \ + validated_data['confirm_password']: + raise serializers.ValidationError( + {'passwords': 'passwords do not match'}) + + if validated_data['new_password'] == \ + validated_data['confirm_password'] and \ + instance.check_password(validated_data['old_password']): + # instance.password = validated_data['new_password'] + instance.set_password(validated_data['new_password']) + instance.save() + return instance + return instance + + +class GuiSettingsSerializer(serializers.ModelSerializer): + + class Meta: + model = GuiSettings + fields = '__all__' + + def get_fields(self, *args, **kwargs): + fields = super().get_fields(*args, **kwargs) + request = self.context.get('request') + if request is not None and not request.parser_context.get('kwargs'): + fields.pop('id', None) + return fields diff --git a/ffplayout/api/utils.py b/ffplayout/api/utils.py index 86550bc0..a0b58375 100644 --- a/ffplayout/api/utils.py +++ b/ffplayout/api/utils.py @@ -7,19 +7,24 @@ import psutil import yaml from pymediainfo import MediaInfo -from django.conf import settings +from api.models import GuiSettings + from natsort import natsorted def read_yaml(): - if os.path.isfile(settings.FFPLAYOUT_CONFIG): - with open(settings.FFPLAYOUT_CONFIG, 'r') as config_file: + config = GuiSettings.objects.filter(id=1).values()[0] + + if os.path.isfile(config['playout_config']): + with open(config['playout_config'], 'r') as config_file: return yaml.safe_load(config_file) def write_yaml(data): - if os.path.isfile(settings.FFPLAYOUT_CONFIG): - with open(settings.FFPLAYOUT_CONFIG, 'w') as outfile: + config = GuiSettings.objects.filter(id=1).values()[0] + + if os.path.isfile(config['playout_config']): + with open(config['playoutConfig'], 'w') as outfile: yaml.dump(data, outfile, default_flow_style=False, sort_keys=False, indent=4) @@ -43,7 +48,7 @@ def sizeof_fmt(num, suffix='B'): class SystemStats: def __init__(self): - pass + self.config = GuiSettings.objects.filter(id=1).values()[0] def all(self): return { @@ -83,7 +88,7 @@ class SystemStats: } def disk(self): - root = psutil.disk_usage(settings.MEDIA_DISK) + root = psutil.disk_usage(self.config['media_disk']) return { 'disk_total': [root.total, sizeof_fmt(root.total)], 'disk_used': [root.used, sizeof_fmt(root.used)], @@ -102,20 +107,20 @@ class SystemStats: def net_speed(self): net = psutil.net_if_stats() - if settings.NET_INTERFACE not in net: + if self.config['net_interface'] not in net: return { 'net_speed_send': 'no network interface set!', 'net_speed_recv': 'no network interface set!' } - net = psutil.net_io_counters(pernic=True)[settings.NET_INTERFACE] + net = psutil.net_io_counters(pernic=True)[self.config['net_interface']] send_start = net.bytes_sent recv_start = net.bytes_recv sleep(1) - net = psutil.net_io_counters(pernic=True)[settings.NET_INTERFACE] + net = psutil.net_io_counters(pernic=True)[self.config['net_interface']] send_end = net.bytes_sent recv_end = net.bytes_recv @@ -160,7 +165,8 @@ def get_media_path(dir=None): for track in media_info.tracks: if track.track_type == 'General': try: - duration = float(track.to_data()["duration"]) / 1000 + duration = float( + track.to_data()["duration"]) / 1000 break except KeyError: pass diff --git a/ffplayout/api/views.py b/ffplayout/api/views.py index 2de91033..37eae88c 100644 --- a/ffplayout/api/views.py +++ b/ffplayout/api/views.py @@ -1,14 +1,48 @@ import os from urllib.parse import unquote +from django.contrib.auth.models import User +from django_filters import rest_framework as filters +from rest_framework import viewsets from rest_framework.parsers import FileUploadParser, JSONParser from rest_framework.response import Response from rest_framework.views import APIView +from api.models import GuiSettings +from api.serializers import GuiSettingsSerializer, UserSerializer + from .utils import (SystemStats, get_media_path, read_json, read_yaml, write_yaml) +class CurrentUserView(APIView): + def get(self, request): + serializer = UserSerializer(request.user) + return Response(serializer.data) + + +class UserFilter(filters.FilterSet): + + class Meta: + model = User + fields = ['username'] + + +class UserViewSet(viewsets.ModelViewSet): + queryset = User.objects.all() + serializer_class = UserSerializer + filter_backends = (filters.DjangoFilterBackend,) + filterset_class = UserFilter + + +class GuiSettingsViewSet(viewsets.ModelViewSet): + """ + API endpoint that allows media to be viewed. + """ + queryset = GuiSettings.objects.all() + serializer_class = GuiSettingsSerializer + + class Config(APIView): """ read and write config from ffplayout engine @@ -17,7 +51,7 @@ class Config(APIView): parser_classes = [JSONParser] def get(self, request, *args, **kwargs): - if 'config' in request.GET.dict(): + if 'configPlayout' in request.GET.dict(): yaml_input = read_yaml() if yaml_input: @@ -72,10 +106,11 @@ class Statistics(APIView): """ def get(self, request, *args, **kwargs): + stats = SystemStats() if 'stats' in request.GET.dict() and request.GET.dict()['stats'] \ - and hasattr(SystemStats(), request.GET.dict()['stats']): + and hasattr(stats, request.GET.dict()['stats']): return Response( - getattr(SystemStats(), request.GET.dict()['stats'])()) + getattr(stats, request.GET.dict()['stats'])()) else: return Response({"success": False}) diff --git a/ffplayout/db.sqlite3 b/ffplayout/db.sqlite3 index a6f82c82..3725d087 100644 Binary files a/ffplayout/db.sqlite3 and b/ffplayout/db.sqlite3 differ diff --git a/ffplayout/ffplayout/settings.py b/ffplayout/ffplayout/settings.py index 372f7c54..1776d728 100644 --- a/ffplayout/ffplayout/settings.py +++ b/ffplayout/ffplayout/settings.py @@ -38,6 +38,7 @@ INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'django_filters', 'rest_framework', 'corsheaders', 'api' @@ -112,11 +113,7 @@ REST_FRAMEWORK = { ], 'DEFAULT_PERMISSION_CLASSES': ( 'rest_framework.permissions.IsAuthenticated', - ), - 'DEFAULT_PAGINATION_CLASS': ( - 'rest_framework.pagination.LimitOffsetPagination', - ), - 'PAGE_SIZE': 50 + ) } CORS_ORIGIN_WHITELIST = ( diff --git a/ffplayout/ffplayout/urls.py b/ffplayout/ffplayout/urls.py index c7a84486..3f4a99c3 100644 --- a/ffplayout/ffplayout/urls.py +++ b/ffplayout/ffplayout/urls.py @@ -25,6 +25,8 @@ from rest_framework_simplejwt.views import ( ) router = routers.DefaultRouter() +router.register(r'users', views.UserViewSet) +router.register(r'guisettings', views.GuiSettingsViewSet, 'guisettings') urlpatterns = [ path('admin/', admin.site.urls), @@ -32,6 +34,7 @@ urlpatterns = [ path('api/config/', views.Config.as_view()), path('api/playlist/', views.Playlist.as_view()), path('api/stats/', views.Statistics.as_view()), + path('api/current/user/', views.CurrentUserView.as_view()), path('api/media/', views.Media.as_view()), path('api-auth/', include( 'rest_framework.urls', namespace='rest_framework')),