From a187ad8ee84f5d1c1221c9b5147de4787b369a39 Mon Sep 17 00:00:00 2001 From: Jonathan Baecker Date: Wed, 15 Apr 2020 18:10:16 +0200 Subject: [PATCH] add config model --- ffplayout/api/admin.py | 12 ++++++- ffplayout/api/models.py | 33 +++++++++++++++++- ffplayout/api/serializers.py | 57 ++++++++++++++++++++++++++++++++ ffplayout/api/utils.py | 28 ++++++++++------ ffplayout/api/views.py | 41 +++++++++++++++++++++-- ffplayout/db.sqlite3 | Bin 131072 -> 135168 bytes ffplayout/ffplayout/settings.py | 7 ++-- ffplayout/ffplayout/urls.py | 3 ++ 8 files changed, 160 insertions(+), 21 deletions(-) create mode 100644 ffplayout/api/serializers.py 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 a6f82c828150f231c1eb679e5963a8fdb5fa7ebb..3725d087c8aa276931cc2c84d8593ce1cd9142cc 100644 GIT binary patch delta 2762 zcmb_eduUtN8NWyON>}eMv7FkmokW(CP*<^ZpL*P6q^|8cb}czh zUHR1&SzQJr?Y4Si81~1qK`8|GKxmu(SPETA=*a#^wzZT(Uu=|aqsz9!*1-nby>jf> zj?EZUpx?dcp6_?Q*E!#JR#x;Y-_yU`xj%|wShw=u_>HcfU{SPo`5NdCTa=GBlemf{Ole<`^NUY7QA?0zkF=vaSIDi%}uTv5?SN9#Z8 z3=yr<7ztw-{1L|B=P(aHfEVGdTEzn$tvKF?;%PCL%tys|HkFHJ@<}6n2Wzb++($iK z77O03S8Se%CJXt}oZ?{1hUe=~iy1$HngXSwRKVM`IE&W0XOPWL+N`(}DSk(jrBqRp zT>ecaOyvy=>l{`!9tp>|qE$>)fuT@qo(MJf>hsgPq zw6N8>u66>^h>S}aNtU)wbgoatW|Tl}o9H+|ASWWlczmn)!TOPBK1Ta=`j;^4P0JPY zlP104w*IABWl{gk3nz8jz!hzOd`U|B7nk!Ho}*`&%p&b!a`IBqzcfG1KfC1RBcTa8 zyc}Clo@^vLL`4SUGr??_2{I#mWGa#x4^1!uW`Z6c3@it;=hEY$;B0ugYR`ZDJUt#7 zVj{yAW`o0{>G7!vKAi4f3a2O8V0LmLFgUpo%wCucOkJ1>gaULp6v;+HF=ix3eQ9HB zF@8JE2{bFvUKhvvIEt#@P6;}_4tg;77WCAOb8T?!C>}un%L`roO!?+M4Yb!P0O;}6 z%BkwbYsb#>VLIW>4ooaw@DCh)(l;`Ycs4g6Ga1U`6DOXUUYtxiOX95ejPu;ti{7)7 z%*1fu;=Gex*tAeREDtQCJ$_I1?lnOJ_v-ssJ_cl{fA-@RJKhDCs^nc-4|zyI3@+6w zhoD`HBSdyJv9pOCO*~jHefZ7G4nS{WaMb!6!`r%>mId=wU8{DA__pbD!)g7H=@kA; z^k(DuwLv7{8tm+9WvnrmLocJo+aoB1Y>#X| zH0o&Wr&)BP@z1A_x%C8~!%xhd}6io zkH&Ekb!#-nPZ}dJ^j&4&V}w?zHN@x(`~75_(}}(kmc{9ewBzjU7L><`~#(EyajiZ#p`g0zzxW#fpX)rmU<`*V&&O3K8G4xwy++7XoGkYtpF9HRb5ShiWBtr-J~6!{KB{Z7 zoycI=5l#f~8 z$D^oem729PTg~#RX;aNo{AZZGtx~$=gRH5Sr7qK}Yvf%->&?8GF#Xo_YORtmm7QH$ z4E1>*>dpgV&aQTByK*J4Z(Jzcor*Aha+jX1im!L_?SzhX!o$5>B`9A{DlL8+qxYb~ zaPm8wyeh7Ev`w^e`91WCZJ!Q?G58(40k6PUApyg+3JX1l@&3(H@o5jE4 z|3P0itIUza;-88=*((maPSGF!-j@zFeA%zg4>IZMGrEF%pK)L@5r8M*@ delta 632 zcmXv|Ur1AN82x>_+ugr=_ifH{V5F90jI;X`k6xx@`D2zv+yl|?=TLK1}aV9?Wg2^*o`;p1=)eCIpaM}}WiwUoX`CbYOJUqmEadl#a~;!G8gm4<@e7vl zB^EI!3mFu1{WFxqQYY%Kcw}LVRqAei2aC>FB9>KlmgzDZj3rrkVN^jYB zk~Njkqe}PvRjVxYp*U1LaxKJhQ7#w@`KGT28*g51N=3MVK)lTtyBrL9qbIquZ5O)+ zIs>thA@5zTX{v9mbEb7_pe@zzb$i_|x6kGA)_Z)s*TcI{o!~gm-O?(q6?w&$-A1MS zjIE~}m=^VGm749Sq`%Jl51F)!1RE`%jO+9&JH_PaYTXU(qPe^W&0p1d8Xkk3f3GnK z9aZ|8s0)OY1ef5T`sy6;lm<3sVFT3l9vB4pSPOEUQ3 zKtcGUIs?$4(Ga5c1mU4xeR~EzXr%UDSW&yWVIIJwz~)Z>lh@XTyq0n>eo4OumBPmu k)xQDDc8d