feat(api): split api into multiple apps (#1626)

Fixes #1622

- split the api into 4 apps: core, history, schedule, storage
- exploded the settings into testing/prod
This commit is contained in:
Jonas L 2022-04-04 14:38:50 +02:00 committed by GitHub
parent 87d2da9d84
commit fce988aef1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
120 changed files with 1499 additions and 1078 deletions

View File

@ -202,7 +202,6 @@ jobs:
run: run:
shell: bash shell: bash
env: env:
LIBRETIME_GENERAL_API_KEY: test_key
LIBRETIME_DATABASE_HOST: postgres LIBRETIME_DATABASE_HOST: postgres
steps: steps:

View File

@ -13,4 +13,4 @@ clean: .clean
test: $(VENV) test: $(VENV)
source $(VENV)/bin/activate source $(VENV)/bin/activate
LIBRETIME_DEBUG=True $(VENV)/bin/libretime-api test libretime_api DJANGO_SETTINGS_MODULE=libretime_api.settings.testing libretime-api test libretime_api

View File

@ -0,0 +1,8 @@
FILTER_NUMERICAL_LOOKUPS = [
"exact",
"gt",
"lt",
"gte",
"lte",
"range",
]

View File

@ -0,0 +1,5 @@
from pathlib import Path
fixture_path = Path(__file__).resolve().parent
AUDIO_FILENAME = "song.mp3"

View File

@ -1,8 +0,0 @@
from django.apps import AppConfig
from django.db.models.signals import pre_save
class LibreTimeAPIConfig(AppConfig):
name = "libretime_api"
verbose_name = "LibreTime API"
default_auto_field = "django.db.models.AutoField"

16
api/libretime_api/asgi.py Normal file
View File

@ -0,0 +1,16 @@
"""
ASGI config for libretime_api project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "libretime_api.settings.prod")
application = get_asgi_application()

View File

@ -0,0 +1,7 @@
from django.apps import AppConfig
class CoreConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "libretime_api.core"
verbose_name = "LibreTime Core API"

View File

@ -0,0 +1,7 @@
from .auth import LoginAttempt, Session, UserToken
from .country import Country
from .preference import Preference, StreamSetting
from .role import ADMIN, DJ, GUEST, PROGRAM_MANAGER, USER_TYPES
from .service import ServiceRegister
from .user import User, UserManager
from .worker import CeleryTask, ThirdPartyTrackReference

View File

@ -0,0 +1,37 @@
from django.db import models
class UserToken(models.Model):
user = models.ForeignKey("User", models.DO_NOTHING)
action = models.CharField(max_length=255)
token = models.CharField(unique=True, max_length=40)
created = models.DateTimeField()
def get_owner(self):
return self.user
class Meta:
managed = False
db_table = "cc_subjs_token"
class Session(models.Model):
sessid = models.CharField(primary_key=True, max_length=32)
userid = models.ForeignKey(
"User", models.DO_NOTHING, db_column="userid", blank=True, null=True
)
login = models.CharField(max_length=255, blank=True, null=True)
ts = models.DateTimeField(blank=True, null=True)
class Meta:
managed = False
db_table = "cc_sess"
class LoginAttempt(models.Model):
ip = models.CharField(primary_key=True, max_length=32)
attempts = models.IntegerField(blank=True, null=True)
class Meta:
managed = False
db_table = "cc_login_attempts"

View File

@ -14,14 +14,6 @@ class Preference(models.Model):
unique_together = (("subjid", "keystr"),) unique_together = (("subjid", "keystr"),)
class MountName(models.Model):
mount_name = models.CharField(max_length=1024)
class Meta:
managed = False
db_table = "cc_mount_name"
class StreamSetting(models.Model): class StreamSetting(models.Model):
keyname = models.CharField(primary_key=True, max_length=64) keyname = models.CharField(primary_key=True, max_length=64)
value = models.CharField(max_length=255, blank=True, null=True) value = models.CharField(max_length=255, blank=True, null=True)

View File

@ -1,43 +1,37 @@
import hashlib import hashlib
from django.contrib import auth from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, Permission
from django.contrib.auth.models import AbstractBaseUser, Permission
from django.core.exceptions import PermissionDenied
from django.db import models from django.db import models
from libretime_api.managers import UserManager from ...permission_constants import GROUPS
from libretime_api.permission_constants import GROUPS from .role import ADMIN, USER_TYPES
from .user_constants import ADMIN, USER_TYPES
class LoginAttempt(models.Model):
ip = models.CharField(primary_key=True, max_length=32)
attempts = models.IntegerField(blank=True, null=True)
class Meta:
managed = False
db_table = "cc_login_attempts"
class Session(models.Model):
sessid = models.CharField(primary_key=True, max_length=32)
userid = models.ForeignKey(
"User", models.DO_NOTHING, db_column="userid", blank=True, null=True
)
login = models.CharField(max_length=255, blank=True, null=True)
ts = models.DateTimeField(blank=True, null=True)
class Meta:
managed = False
db_table = "cc_sess"
USER_TYPE_CHOICES = () USER_TYPE_CHOICES = ()
for item in USER_TYPES.items(): for item in USER_TYPES.items():
USER_TYPE_CHOICES = USER_TYPE_CHOICES + (item,) USER_TYPE_CHOICES = USER_TYPE_CHOICES + (item,)
class UserManager(BaseUserManager):
def create_user(self, username, type, email, first_name, last_name, password):
user = self.model(
username=username,
type=type,
email=email,
first_name=first_name,
last_name=last_name,
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, username, email, first_name, last_name, password):
user = self.create_user(username, "A", email, first_name, last_name, password)
return user
def get_by_natural_key(self, username):
return self.get(username=username)
class User(AbstractBaseUser): class User(AbstractBaseUser):
username = models.CharField(db_column="login", unique=True, max_length=255) username = models.CharField(db_column="login", unique=True, max_length=255)
password = models.CharField( password = models.CharField(
@ -132,17 +126,3 @@ class User(AbstractBaseUser):
class Meta: class Meta:
managed = False managed = False
db_table = "cc_subjs" db_table = "cc_subjs"
class UserToken(models.Model):
user = models.ForeignKey(User, models.DO_NOTHING)
action = models.CharField(max_length=255)
token = models.CharField(unique=True, max_length=40)
created = models.DateTimeField()
def get_owner(self):
return self.user
class Meta:
managed = False
db_table = "cc_subjs_token"

View File

@ -1,6 +1,18 @@
from django.db import models from django.db import models
class ThirdPartyTrackReference(models.Model):
service = models.CharField(max_length=256)
foreign_id = models.CharField(unique=True, max_length=256, blank=True, null=True)
file = models.ForeignKey("storage.File", models.DO_NOTHING, blank=True, null=True)
upload_time = models.DateTimeField(blank=True, null=True)
status = models.CharField(max_length=256, blank=True, null=True)
class Meta:
managed = False
db_table = "third_party_track_references"
class CeleryTask(models.Model): class CeleryTask(models.Model):
task_id = models.CharField(max_length=256) task_id = models.CharField(max_length=256)
track_reference = models.ForeignKey( track_reference = models.ForeignKey(

View File

@ -0,0 +1,26 @@
from rest_framework import routers
from .views import (
CeleryTaskViewSet,
CountryViewSet,
LoginAttemptViewSet,
PreferenceViewSet,
ServiceRegisterViewSet,
SessionViewSet,
StreamSettingViewSet,
ThirdPartyTrackReferenceViewSet,
UserTokenViewSet,
UserViewSet,
)
router = routers.DefaultRouter()
router.register("countries", CountryViewSet)
router.register("login-attempts", LoginAttemptViewSet)
router.register("preferences", PreferenceViewSet)
router.register("service-registers", ServiceRegisterViewSet)
router.register("sessions", SessionViewSet)
router.register("stream-settings", StreamSettingViewSet)
router.register("users", UserViewSet)
router.register("user-tokens", UserTokenViewSet)
router.register("celery-tasks", CeleryTaskViewSet)
router.register("third-party-track-references", ThirdPartyTrackReferenceViewSet)

View File

@ -0,0 +1,6 @@
from .auth import LoginAttemptSerializer, SessionSerializer, UserTokenSerializer
from .country import CountrySerializer
from .preference import PreferenceSerializer, StreamSettingSerializer
from .service import ServiceRegisterSerializer
from .user import UserSerializer
from .worker import CeleryTaskSerializer, ThirdPartyTrackReferenceSerializer

View File

@ -0,0 +1,21 @@
from rest_framework import serializers
from ..models import LoginAttempt, Session, UserToken
class UserTokenSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = UserToken
fields = "__all__"
class SessionSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Session
fields = "__all__"
class LoginAttemptSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = LoginAttempt
fields = "__all__"

View File

@ -0,0 +1,9 @@
from rest_framework import serializers
from ..models import Country
class CountrySerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Country
fields = "__all__"

View File

@ -0,0 +1,15 @@
from rest_framework import serializers
from ..models import Preference, StreamSetting
class PreferenceSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Preference
fields = "__all__"
class StreamSettingSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = StreamSetting
fields = "__all__"

View File

@ -0,0 +1,9 @@
from rest_framework import serializers
from ..models import ServiceRegister
class ServiceRegisterSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ServiceRegister
fields = "__all__"

View File

@ -0,0 +1,20 @@
from django.contrib.auth import get_user_model
from rest_framework import serializers
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = get_user_model()
fields = [
"item_url",
"username",
"type",
"first_name",
"last_name",
"lastfail",
"skype_contact",
"jabber_contact",
"email",
"cell_phone",
"login_attempts",
]

View File

@ -0,0 +1,15 @@
from rest_framework import serializers
from ..models import CeleryTask, ThirdPartyTrackReference
class ThirdPartyTrackReferenceSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ThirdPartyTrackReference
fields = "__all__"
class CeleryTaskSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = CeleryTask
fields = "__all__"

View File

View File

@ -1,10 +1,8 @@
from django.apps import apps from django.apps import apps
from django.contrib.auth.models import Group
from rest_framework.test import APITestCase from rest_framework.test import APITestCase
from libretime_api.models import User from ....permission_constants import GROUPS
from libretime_api.models.user_constants import DJ, GUEST from ...models import DJ, GUEST, User
from libretime_api.permission_constants import GROUPS
class TestUserManager(APITestCase): class TestUserManager(APITestCase):

View File

@ -0,0 +1,7 @@
from .auth import LoginAttemptViewSet, SessionViewSet, UserTokenViewSet
from .country import CountryViewSet
from .preference import PreferenceViewSet, StreamSettingViewSet
from .service import ServiceRegisterViewSet
from .user import UserViewSet
from .version import version
from .worker import CeleryTaskViewSet, ThirdPartyTrackReferenceViewSet

View File

@ -0,0 +1,22 @@
from rest_framework import viewsets
from ..models import LoginAttempt, Session, UserToken
from ..serializers import LoginAttemptSerializer, SessionSerializer, UserTokenSerializer
class UserTokenViewSet(viewsets.ModelViewSet):
queryset = UserToken.objects.all()
serializer_class = UserTokenSerializer
model_permission_name = "usertoken"
class SessionViewSet(viewsets.ModelViewSet):
queryset = Session.objects.all()
serializer_class = SessionSerializer
model_permission_name = "session"
class LoginAttemptViewSet(viewsets.ModelViewSet):
queryset = LoginAttempt.objects.all()
serializer_class = LoginAttemptSerializer
model_permission_name = "loginattempt"

View File

@ -0,0 +1,10 @@
from rest_framework import viewsets
from ..models import Country
from ..serializers import CountrySerializer
class CountryViewSet(viewsets.ModelViewSet):
queryset = Country.objects.all()
serializer_class = CountrySerializer
model_permission_name = "country"

View File

@ -0,0 +1,16 @@
from rest_framework import viewsets
from ..models import Preference, StreamSetting
from ..serializers import PreferenceSerializer, StreamSettingSerializer
class PreferenceViewSet(viewsets.ModelViewSet):
queryset = Preference.objects.all()
serializer_class = PreferenceSerializer
model_permission_name = "preference"
class StreamSettingViewSet(viewsets.ModelViewSet):
queryset = StreamSetting.objects.all()
serializer_class = StreamSettingSerializer
model_permission_name = "streamsetting"

View File

@ -0,0 +1,10 @@
from rest_framework import viewsets
from ..models import ServiceRegister
from ..serializers import ServiceRegisterSerializer
class ServiceRegisterViewSet(viewsets.ModelViewSet):
queryset = ServiceRegister.objects.all()
serializer_class = ServiceRegisterSerializer
model_permission_name = "serviceregister"

View File

@ -0,0 +1,12 @@
from django.contrib.auth import get_user_model
from rest_framework import viewsets
from ...permissions import IsAdminOrOwnUser
from ..serializers import UserSerializer
class UserViewSet(viewsets.ModelViewSet):
queryset = get_user_model().objects.all()
serializer_class = UserSerializer
permission_classes = [IsAdminOrOwnUser]
model_permission_name = "user"

View File

@ -0,0 +1,10 @@
from django.conf import settings
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import AllowAny
from rest_framework.response import Response
@api_view(["GET"])
@permission_classes((AllowAny,))
def version(request, *args, **kwargs):
return Response({"api_version": settings.API_VERSION})

View File

@ -0,0 +1,16 @@
from rest_framework import viewsets
from ..models import CeleryTask, ThirdPartyTrackReference
from ..serializers import CeleryTaskSerializer, ThirdPartyTrackReferenceSerializer
class ThirdPartyTrackReferenceViewSet(viewsets.ModelViewSet):
queryset = ThirdPartyTrackReference.objects.all()
serializer_class = ThirdPartyTrackReferenceSerializer
model_permission_name = "thirdpartytrackreference"
class CeleryTaskViewSet(viewsets.ModelViewSet):
queryset = CeleryTask.objects.all()
serializer_class = CeleryTaskSerializer
model_permission_name = "celerytask"

View File

View File

@ -0,0 +1,7 @@
from django.apps import AppConfig
class HistoryConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "libretime_api.history"
verbose_name = "LibreTime History API"

View File

@ -0,0 +1,8 @@
from .listener import ListenerCount, MountName, Timestamp
from .live import LiveLog
from .played import (
PlayoutHistory,
PlayoutHistoryMetadata,
PlayoutHistoryTemplate,
PlayoutHistoryTemplateField,
)

View File

@ -0,0 +1,27 @@
from django.db import models
class MountName(models.Model):
mount_name = models.CharField(max_length=1024)
class Meta:
managed = False
db_table = "cc_mount_name"
class Timestamp(models.Model):
timestamp = models.DateTimeField()
class Meta:
managed = False
db_table = "cc_timestamp"
class ListenerCount(models.Model):
timestamp = models.ForeignKey("Timestamp", models.DO_NOTHING)
mount_name = models.ForeignKey("MountName", models.DO_NOTHING)
listener_count = models.IntegerField()
class Meta:
managed = False
db_table = "cc_listener_count"

View File

@ -0,0 +1,11 @@
from django.db import models
class LiveLog(models.Model):
state = models.CharField(max_length=32)
start_time = models.DateTimeField()
end_time = models.DateTimeField(blank=True, null=True)
class Meta:
managed = False
db_table = "cc_live_log"

View File

@ -1,34 +1,12 @@
from django.db import models from django.db import models
from .files import File
class ListenerCount(models.Model):
timestamp = models.ForeignKey("Timestamp", models.DO_NOTHING)
mount_name = models.ForeignKey("MountName", models.DO_NOTHING)
listener_count = models.IntegerField()
class Meta:
managed = False
db_table = "cc_listener_count"
class LiveLog(models.Model):
state = models.CharField(max_length=32)
start_time = models.DateTimeField()
end_time = models.DateTimeField(blank=True, null=True)
class Meta:
managed = False
db_table = "cc_live_log"
class PlayoutHistory(models.Model): class PlayoutHistory(models.Model):
file = models.ForeignKey(File, models.DO_NOTHING, blank=True, null=True) file = models.ForeignKey("storage.File", models.DO_NOTHING, blank=True, null=True)
starts = models.DateTimeField() starts = models.DateTimeField()
ends = models.DateTimeField(blank=True, null=True) ends = models.DateTimeField(blank=True, null=True)
instance = models.ForeignKey( instance = models.ForeignKey(
"ShowInstance", models.DO_NOTHING, blank=True, null=True "schedule.ShowInstance", models.DO_NOTHING, blank=True, null=True
) )
class Meta: class Meta:
@ -37,7 +15,7 @@ class PlayoutHistory(models.Model):
class PlayoutHistoryMetadata(models.Model): class PlayoutHistoryMetadata(models.Model):
history = models.ForeignKey(PlayoutHistory, models.DO_NOTHING) history = models.ForeignKey("PlayoutHistory", models.DO_NOTHING)
key = models.CharField(max_length=128) key = models.CharField(max_length=128)
value = models.CharField(max_length=128) value = models.CharField(max_length=128)
@ -56,7 +34,7 @@ class PlayoutHistoryTemplate(models.Model):
class PlayoutHistoryTemplateField(models.Model): class PlayoutHistoryTemplateField(models.Model):
template = models.ForeignKey(PlayoutHistoryTemplate, models.DO_NOTHING) template = models.ForeignKey("PlayoutHistoryTemplate", models.DO_NOTHING)
name = models.CharField(max_length=128) name = models.CharField(max_length=128)
label = models.CharField(max_length=128) label = models.CharField(max_length=128)
type = models.CharField(max_length=128) type = models.CharField(max_length=128)
@ -66,11 +44,3 @@ class PlayoutHistoryTemplateField(models.Model):
class Meta: class Meta:
managed = False managed = False
db_table = "cc_playout_history_template_field" db_table = "cc_playout_history_template_field"
class Timestamp(models.Model):
timestamp = models.DateTimeField()
class Meta:
managed = False
db_table = "cc_timestamp"

View File

@ -0,0 +1,22 @@
from rest_framework import routers
from .views import (
ListenerCountViewSet,
LiveLogViewSet,
MountNameViewSet,
PlayoutHistoryMetadataViewSet,
PlayoutHistoryTemplateFieldViewSet,
PlayoutHistoryTemplateViewSet,
PlayoutHistoryViewSet,
TimestampViewSet,
)
router = routers.DefaultRouter()
router.register("listener-counts", ListenerCountViewSet)
router.register("live-logs", LiveLogViewSet)
router.register("mount-names", MountNameViewSet)
router.register("playout-history", PlayoutHistoryViewSet)
router.register("playout-history-metadata", PlayoutHistoryMetadataViewSet)
router.register("playout-history-templates", PlayoutHistoryTemplateViewSet)
router.register("playout-history-template-fields", PlayoutHistoryTemplateFieldViewSet)
router.register("timestamps", TimestampViewSet)

View File

@ -0,0 +1,8 @@
from .listener import ListenerCountSerializer, MountNameSerializer, TimestampSerializer
from .live import LiveLogSerializer
from .played import (
PlayoutHistoryMetadataSerializer,
PlayoutHistorySerializer,
PlayoutHistoryTemplateFieldSerializer,
PlayoutHistoryTemplateSerializer,
)

View File

@ -0,0 +1,21 @@
from rest_framework import serializers
from ..models import ListenerCount, MountName, Timestamp
class MountNameSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = MountName
fields = "__all__"
class TimestampSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Timestamp
fields = "__all__"
class ListenerCountSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ListenerCount
fields = "__all__"

View File

@ -0,0 +1,9 @@
from rest_framework import serializers
from ..models import LiveLog
class LiveLogSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = LiveLog
fields = "__all__"

View File

@ -0,0 +1,32 @@
from rest_framework import serializers
from ..models import (
PlayoutHistory,
PlayoutHistoryMetadata,
PlayoutHistoryTemplate,
PlayoutHistoryTemplateField,
)
class PlayoutHistorySerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = PlayoutHistory
fields = "__all__"
class PlayoutHistoryMetadataSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = PlayoutHistoryMetadata
fields = "__all__"
class PlayoutHistoryTemplateSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = PlayoutHistoryTemplate
fields = "__all__"
class PlayoutHistoryTemplateFieldSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = PlayoutHistoryTemplateField
fields = "__all__"

View File

@ -0,0 +1,8 @@
from .listener import ListenerCountViewSet, MountNameViewSet, TimestampViewSet
from .live import LiveLogViewSet
from .played import (
PlayoutHistoryMetadataViewSet,
PlayoutHistoryTemplateFieldViewSet,
PlayoutHistoryTemplateViewSet,
PlayoutHistoryViewSet,
)

View File

@ -0,0 +1,26 @@
from rest_framework import viewsets
from ..models import ListenerCount, MountName, Timestamp
from ..serializers import (
ListenerCountSerializer,
MountNameSerializer,
TimestampSerializer,
)
class MountNameViewSet(viewsets.ModelViewSet):
queryset = MountName.objects.all()
serializer_class = MountNameSerializer
model_permission_name = "mountname"
class TimestampViewSet(viewsets.ModelViewSet):
queryset = Timestamp.objects.all()
serializer_class = TimestampSerializer
model_permission_name = "timestamp"
class ListenerCountViewSet(viewsets.ModelViewSet):
queryset = ListenerCount.objects.all()
serializer_class = ListenerCountSerializer
model_permission_name = "listenercount"

View File

@ -0,0 +1,10 @@
from rest_framework import viewsets
from ..models import LiveLog
from ..serializers import LiveLogSerializer
class LiveLogViewSet(viewsets.ModelViewSet):
queryset = LiveLog.objects.all()
serializer_class = LiveLogSerializer
model_permission_name = "livelog"

View File

@ -0,0 +1,38 @@
from rest_framework import viewsets
from ..models import (
PlayoutHistory,
PlayoutHistoryMetadata,
PlayoutHistoryTemplate,
PlayoutHistoryTemplateField,
)
from ..serializers import (
PlayoutHistoryMetadataSerializer,
PlayoutHistorySerializer,
PlayoutHistoryTemplateFieldSerializer,
PlayoutHistoryTemplateSerializer,
)
class PlayoutHistoryViewSet(viewsets.ModelViewSet):
queryset = PlayoutHistory.objects.all()
serializer_class = PlayoutHistorySerializer
model_permission_name = "playouthistory"
class PlayoutHistoryMetadataViewSet(viewsets.ModelViewSet):
queryset = PlayoutHistoryMetadata.objects.all()
serializer_class = PlayoutHistoryMetadataSerializer
model_permission_name = "playouthistorymetadata"
class PlayoutHistoryTemplateViewSet(viewsets.ModelViewSet):
queryset = PlayoutHistoryTemplate.objects.all()
serializer_class = PlayoutHistoryTemplateSerializer
model_permission_name = "playouthistorytemplate"
class PlayoutHistoryTemplateFieldViewSet(viewsets.ModelViewSet):
queryset = PlayoutHistoryTemplateField.objects.all()
serializer_class = PlayoutHistoryTemplateFieldSerializer
model_permission_name = "playouthistorytemplatefield"

View File

@ -1,12 +1,12 @@
#!/usr/bin/env python3 #!/usr/bin/env python
"""Django's command-line utility for administrative tasks.""" """Django's command-line utility for administrative tasks."""
import os import os
import sys import sys
def main(): def main():
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "libretime_api.settings") """Run administrative tasks."""
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "libretime_api.settings.prod")
os.environ.setdefault("LIBRETIME_CONFIG_FILEPATH", "/etc/airtime/airtime.conf") os.environ.setdefault("LIBRETIME_CONFIG_FILEPATH", "/etc/airtime/airtime.conf")
try: try:
from django.core.management import execute_from_command_line from django.core.management import execute_from_command_line

View File

@ -1,22 +0,0 @@
from django.contrib.auth.models import BaseUserManager
class UserManager(BaseUserManager):
def create_user(self, username, type, email, first_name, last_name, password):
user = self.model(
username=username,
type=type,
email=email,
first_name=first_name,
last_name=last_name,
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, username, email, first_name, last_name, password):
user = self.create_user(username, "A", email, first_name, last_name, password)
return user
def get_by_natural_key(self, username):
return self.get(username=username)

View File

@ -1,14 +0,0 @@
from .authentication import *
from .celery import *
from .countries import *
from .files import *
from .playlists import *
from .playout import *
from .podcasts import *
from .preferences import *
from .schedule import *
from .services import *
from .shows import *
from .smart_blocks import *
from .tracks import *
from .webstreams import *

View File

@ -1,26 +0,0 @@
from django.db import models
from .files import File
class ThirdPartyTrackReference(models.Model):
service = models.CharField(max_length=256)
foreign_id = models.CharField(unique=True, max_length=256, blank=True, null=True)
file = models.ForeignKey(File, models.DO_NOTHING, blank=True, null=True)
upload_time = models.DateTimeField(blank=True, null=True)
status = models.CharField(max_length=256, blank=True, null=True)
class Meta:
managed = False
db_table = "third_party_track_references"
class TrackType(models.Model):
code = models.CharField(max_length=16, unique=True)
type_name = models.CharField(max_length=255, blank=True, null=True)
description = models.CharField(max_length=255, blank=True, null=True)
visibility = models.BooleanField(blank=True, default=True)
class Meta:
managed = False
db_table = "cc_track_types"

View File

@ -1,10 +1,4 @@
import logging from .core.models import DJ, GUEST, PROGRAM_MANAGER, USER_TYPES
from django.contrib.auth.models import Group, Permission
from .models.user_constants import DJ, GUEST, PROGRAM_MANAGER, USER_TYPES
logger = logging.getLogger(__name__)
GUEST_PERMISSIONS = [ GUEST_PERMISSIONS = [
"view_schedule", "view_schedule",
@ -24,6 +18,7 @@ GUEST_PERMISSIONS = [
"view_webstream", "view_webstream",
"view_apiroot", "view_apiroot",
] ]
DJ_PERMISSIONS = GUEST_PERMISSIONS + [ DJ_PERMISSIONS = GUEST_PERMISSIONS + [
"add_file", "add_file",
"add_podcast", "add_podcast",

View File

@ -1,7 +1,7 @@
from django.conf import settings from django.conf import settings
from rest_framework.permissions import BasePermission from rest_framework.permissions import BasePermission
from .models.user_constants import DJ from .core.models.role import DJ
REQUEST_PERMISSION_TYPE_MAP = { REQUEST_PERMISSION_TYPE_MAP = {
"GET": "view", "GET": "view",

View File

View File

@ -0,0 +1,7 @@
from django.apps import AppConfig
class ScheduleConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "libretime_api.schedule"
verbose_name = "LibreTime Schedule API"

View File

@ -0,0 +1,6 @@
from .playlist import Playlist, PlaylistContent
from .podcast import ImportedPodcast, Podcast, PodcastEpisode, StationPodcast
from .schedule import Schedule
from .show import Show, ShowDays, ShowHost, ShowInstance, ShowRebroadcast
from .smart_block import SmartBlock, SmartBlockContent, SmartBlockCriteria
from .webstream import Webstream, WebstreamMetadata

View File

@ -1,14 +1,11 @@
from django.db import models from django.db import models
from .files import File
from .smart_blocks import SmartBlock
class Playlist(models.Model): class Playlist(models.Model):
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
mtime = models.DateTimeField(blank=True, null=True) mtime = models.DateTimeField(blank=True, null=True)
utime = models.DateTimeField(blank=True, null=True) utime = models.DateTimeField(blank=True, null=True)
creator = models.ForeignKey("User", models.DO_NOTHING, blank=True, null=True) creator = models.ForeignKey("core.User", models.DO_NOTHING, blank=True, null=True)
description = models.CharField(max_length=512, blank=True, null=True) description = models.CharField(max_length=512, blank=True, null=True)
length = models.DurationField(blank=True, null=True) length = models.DurationField(blank=True, null=True)
@ -21,9 +18,9 @@ class Playlist(models.Model):
class PlaylistContent(models.Model): class PlaylistContent(models.Model):
playlist = models.ForeignKey(Playlist, models.DO_NOTHING, blank=True, null=True) playlist = models.ForeignKey("Playlist", models.DO_NOTHING, blank=True, null=True)
file = models.ForeignKey(File, models.DO_NOTHING, blank=True, null=True) file = models.ForeignKey("storage.File", models.DO_NOTHING, blank=True, null=True)
block = models.ForeignKey(SmartBlock, models.DO_NOTHING, blank=True, null=True) block = models.ForeignKey("SmartBlock", models.DO_NOTHING, blank=True, null=True)
stream_id = models.IntegerField(blank=True, null=True) stream_id = models.IntegerField(blank=True, null=True)
type = models.SmallIntegerField() type = models.SmallIntegerField()
position = models.IntegerField(blank=True, null=True) position = models.IntegerField(blank=True, null=True)

View File

@ -1,22 +1,5 @@
from django.db import models from django.db import models
from .authentication import User
from .files import File
class ImportedPodcast(models.Model):
auto_ingest = models.BooleanField()
auto_ingest_timestamp = models.DateTimeField(blank=True, null=True)
album_override = models.BooleanField()
podcast = models.ForeignKey("Podcast", models.DO_NOTHING)
def get_owner(self):
return self.podcast.owner
class Meta:
managed = False
db_table = "imported_podcast"
class Podcast(models.Model): class Podcast(models.Model):
url = models.CharField(max_length=4096) url = models.CharField(max_length=4096)
@ -33,7 +16,7 @@ class Podcast(models.Model):
itunes_category = models.CharField(max_length=4096, blank=True, null=True) itunes_category = models.CharField(max_length=4096, blank=True, null=True)
itunes_explicit = models.CharField(max_length=4096, blank=True, null=True) itunes_explicit = models.CharField(max_length=4096, blank=True, null=True)
owner = models.ForeignKey( owner = models.ForeignKey(
User, models.DO_NOTHING, db_column="owner", blank=True, null=True "core.User", models.DO_NOTHING, db_column="owner", blank=True, null=True
) )
def get_owner(self): def get_owner(self):
@ -49,8 +32,8 @@ class Podcast(models.Model):
class PodcastEpisode(models.Model): class PodcastEpisode(models.Model):
file = models.ForeignKey(File, models.DO_NOTHING, blank=True, null=True) file = models.ForeignKey("storage.File", models.DO_NOTHING, blank=True, null=True)
podcast = models.ForeignKey(Podcast, models.DO_NOTHING) podcast = models.ForeignKey("Podcast", models.DO_NOTHING)
publication_date = models.DateTimeField() publication_date = models.DateTimeField()
download_url = models.CharField(max_length=4096) download_url = models.CharField(max_length=4096)
episode_guid = models.CharField(max_length=4096) episode_guid = models.CharField(max_length=4096)
@ -76,7 +59,7 @@ class PodcastEpisode(models.Model):
class StationPodcast(models.Model): class StationPodcast(models.Model):
podcast = models.ForeignKey(Podcast, models.DO_NOTHING) podcast = models.ForeignKey("Podcast", models.DO_NOTHING)
def get_owner(self): def get_owner(self):
return self.podcast.owner return self.podcast.owner
@ -84,3 +67,17 @@ class StationPodcast(models.Model):
class Meta: class Meta:
managed = False managed = False
db_table = "station_podcast" db_table = "station_podcast"
class ImportedPodcast(models.Model):
auto_ingest = models.BooleanField()
auto_ingest_timestamp = models.DateTimeField(blank=True, null=True)
album_override = models.BooleanField()
podcast = models.ForeignKey("Podcast", models.DO_NOTHING)
def get_owner(self):
return self.podcast.owner
class Meta:
managed = False
db_table = "imported_podcast"

View File

@ -1,12 +1,10 @@
from django.db import models from django.db import models
from .files import File
class Schedule(models.Model): class Schedule(models.Model):
starts = models.DateTimeField() starts = models.DateTimeField()
ends = models.DateTimeField() ends = models.DateTimeField()
file = models.ForeignKey(File, models.DO_NOTHING, blank=True, null=True) file = models.ForeignKey("storage.File", models.DO_NOTHING, blank=True, null=True)
stream = models.ForeignKey("Webstream", models.DO_NOTHING, blank=True, null=True) stream = models.ForeignKey("Webstream", models.DO_NOTHING, blank=True, null=True)
clip_length = models.DurationField(blank=True, null=True) clip_length = models.DurationField(blank=True, null=True)
fade_in = models.TimeField(blank=True, null=True) fade_in = models.TimeField(blank=True, null=True)

View File

@ -1,8 +1,5 @@
from django.db import models from django.db import models
from .files import File
from .playlists import Playlist
class Show(models.Model): class Show(models.Model):
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
@ -19,7 +16,9 @@ class Show(models.Model):
is_linkable = models.BooleanField() is_linkable = models.BooleanField()
image_path = models.CharField(max_length=255, blank=True, null=True) image_path = models.CharField(max_length=255, blank=True, null=True)
has_autoplaylist = models.BooleanField() has_autoplaylist = models.BooleanField()
autoplaylist = models.ForeignKey(Playlist, models.DO_NOTHING, blank=True, null=True) autoplaylist = models.ForeignKey(
"Playlist", models.DO_NOTHING, blank=True, null=True
)
autoplaylist_repeat = models.BooleanField() autoplaylist_repeat = models.BooleanField()
def get_owner(self): def get_owner(self):
@ -39,7 +38,7 @@ class ShowDays(models.Model):
day = models.SmallIntegerField(blank=True, null=True) day = models.SmallIntegerField(blank=True, null=True)
repeat_type = models.SmallIntegerField() repeat_type = models.SmallIntegerField()
next_pop_date = models.DateField(blank=True, null=True) next_pop_date = models.DateField(blank=True, null=True)
show = models.ForeignKey(Show, models.DO_NOTHING) show = models.ForeignKey("Show", models.DO_NOTHING)
record = models.SmallIntegerField(blank=True, null=True) record = models.SmallIntegerField(blank=True, null=True)
def get_owner(self): def get_owner(self):
@ -51,8 +50,8 @@ class ShowDays(models.Model):
class ShowHost(models.Model): class ShowHost(models.Model):
show = models.ForeignKey(Show, models.DO_NOTHING) show = models.ForeignKey("Show", models.DO_NOTHING)
subjs = models.ForeignKey("User", models.DO_NOTHING) subjs = models.ForeignKey("core.User", models.DO_NOTHING)
class Meta: class Meta:
managed = False managed = False
@ -63,11 +62,11 @@ class ShowInstance(models.Model):
description = models.CharField(max_length=8192, blank=True, null=True) description = models.CharField(max_length=8192, blank=True, null=True)
starts = models.DateTimeField() starts = models.DateTimeField()
ends = models.DateTimeField() ends = models.DateTimeField()
show = models.ForeignKey(Show, models.DO_NOTHING) show = models.ForeignKey("Show", models.DO_NOTHING)
record = models.SmallIntegerField(blank=True, null=True) record = models.SmallIntegerField(blank=True, null=True)
rebroadcast = models.SmallIntegerField(blank=True, null=True) rebroadcast = models.SmallIntegerField(blank=True, null=True)
instance = models.ForeignKey("self", models.DO_NOTHING, blank=True, null=True) instance = models.ForeignKey("self", models.DO_NOTHING, blank=True, null=True)
file = models.ForeignKey(File, models.DO_NOTHING, blank=True, null=True) file = models.ForeignKey("storage.File", models.DO_NOTHING, blank=True, null=True)
time_filled = models.DurationField(blank=True, null=True) time_filled = models.DurationField(blank=True, null=True)
created = models.DateTimeField() created = models.DateTimeField()
last_scheduled = models.DateTimeField(blank=True, null=True) last_scheduled = models.DateTimeField(blank=True, null=True)
@ -85,7 +84,7 @@ class ShowInstance(models.Model):
class ShowRebroadcast(models.Model): class ShowRebroadcast(models.Model):
day_offset = models.CharField(max_length=1024) day_offset = models.CharField(max_length=1024)
start_time = models.TimeField() start_time = models.TimeField()
show = models.ForeignKey(Show, models.DO_NOTHING) show = models.ForeignKey("Show", models.DO_NOTHING)
def get_owner(self): def get_owner(self):
return show.get_owner() return show.get_owner()

View File

@ -5,7 +5,7 @@ class SmartBlock(models.Model):
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
mtime = models.DateTimeField(blank=True, null=True) mtime = models.DateTimeField(blank=True, null=True)
utime = models.DateTimeField(blank=True, null=True) utime = models.DateTimeField(blank=True, null=True)
creator = models.ForeignKey("User", models.DO_NOTHING, blank=True, null=True) creator = models.ForeignKey("core.User", models.DO_NOTHING, blank=True, null=True)
description = models.CharField(max_length=512, blank=True, null=True) description = models.CharField(max_length=512, blank=True, null=True)
length = models.DurationField(blank=True, null=True) length = models.DurationField(blank=True, null=True)
type = models.CharField(max_length=7, blank=True, null=True) type = models.CharField(max_length=7, blank=True, null=True)
@ -29,8 +29,8 @@ class SmartBlock(models.Model):
class SmartBlockContent(models.Model): class SmartBlockContent(models.Model):
block = models.ForeignKey(SmartBlock, models.DO_NOTHING, blank=True, null=True) block = models.ForeignKey("SmartBlock", models.DO_NOTHING, blank=True, null=True)
file = models.ForeignKey("File", models.DO_NOTHING, blank=True, null=True) file = models.ForeignKey("storage.File", models.DO_NOTHING, blank=True, null=True)
position = models.IntegerField(blank=True, null=True) position = models.IntegerField(blank=True, null=True)
trackoffset = models.FloatField() trackoffset = models.FloatField()
cliplength = models.DurationField(blank=True, null=True) cliplength = models.DurationField(blank=True, null=True)
@ -63,7 +63,7 @@ class SmartBlockCriteria(models.Model):
value = models.CharField(max_length=512) value = models.CharField(max_length=512)
extra = models.CharField(max_length=512, blank=True, null=True) extra = models.CharField(max_length=512, blank=True, null=True)
criteriagroup = models.IntegerField(blank=True, null=True) criteriagroup = models.IntegerField(blank=True, null=True)
block = models.ForeignKey(SmartBlock, models.DO_NOTHING) block = models.ForeignKey("SmartBlock", models.DO_NOTHING)
def get_owner(self): def get_owner(self):
return self.block.get_owner() return self.block.get_owner()

View File

@ -1,8 +1,6 @@
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.db import models from django.db import models
from .schedule import Schedule
class Webstream(models.Model): class Webstream(models.Model):
name = models.CharField(max_length=255) name = models.CharField(max_length=255)
@ -29,7 +27,7 @@ class Webstream(models.Model):
class WebstreamMetadata(models.Model): class WebstreamMetadata(models.Model):
instance = models.ForeignKey(Schedule, models.DO_NOTHING) instance = models.ForeignKey("Schedule", models.DO_NOTHING)
start_time = models.DateTimeField() start_time = models.DateTimeField()
liquidsoap_data = models.CharField(max_length=1024) liquidsoap_data = models.CharField(max_length=1024)

View File

@ -0,0 +1,40 @@
from rest_framework import routers
from .views import (
ImportedPodcastViewSet,
PlaylistContentViewSet,
PlaylistViewSet,
PodcastEpisodeViewSet,
PodcastViewSet,
ScheduleViewSet,
ShowDaysViewSet,
ShowHostViewSet,
ShowInstanceViewSet,
ShowRebroadcastViewSet,
ShowViewSet,
SmartBlockContentViewSet,
SmartBlockCriteriaViewSet,
SmartBlockViewSet,
StationPodcastViewSet,
WebstreamMetadataViewSet,
WebstreamViewSet,
)
router = routers.DefaultRouter()
router.register("playlist-contents", PlaylistContentViewSet)
router.register("playlists", PlaylistViewSet)
router.register("podcast-episodes", PodcastEpisodeViewSet)
router.register("podcasts", PodcastViewSet)
router.register("station-podcasts", StationPodcastViewSet)
router.register("imported-podcasts", ImportedPodcastViewSet)
router.register("schedule", ScheduleViewSet)
router.register("show-days", ShowDaysViewSet)
router.register("show-hosts", ShowHostViewSet)
router.register("show-instances", ShowInstanceViewSet)
router.register("show-rebroadcasts", ShowRebroadcastViewSet)
router.register("shows", ShowViewSet)
router.register("smart-block-contents", SmartBlockContentViewSet)
router.register("smart-block-criteria", SmartBlockCriteriaViewSet)
router.register("smart-blocks", SmartBlockViewSet)
router.register("webstream-metadata", WebstreamMetadataViewSet)
router.register("webstreams", WebstreamViewSet)

View File

@ -0,0 +1,21 @@
from .playlist import PlaylistContentSerializer, PlaylistSerializer
from .podcast import (
ImportedPodcastSerializer,
PodcastEpisodeSerializer,
PodcastSerializer,
StationPodcastSerializer,
)
from .schedule import ScheduleSerializer
from .show import (
ShowDaysSerializer,
ShowHostSerializer,
ShowInstanceSerializer,
ShowRebroadcastSerializer,
ShowSerializer,
)
from .smart_block import (
SmartBlockContentSerializer,
SmartBlockCriteriaSerializer,
SmartBlockSerializer,
)
from .webstream import WebstreamMetadataSerializer, WebstreamSerializer

View File

@ -0,0 +1,15 @@
from rest_framework import serializers
from ..models import Playlist, PlaylistContent
class PlaylistSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Playlist
fields = "__all__"
class PlaylistContentSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = PlaylistContent
fields = "__all__"

View File

@ -0,0 +1,27 @@
from rest_framework import serializers
from ..models import ImportedPodcast, Podcast, PodcastEpisode, StationPodcast
class PodcastSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Podcast
fields = "__all__"
class PodcastEpisodeSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = PodcastEpisode
fields = "__all__"
class StationPodcastSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = StationPodcast
fields = "__all__"
class ImportedPodcastSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ImportedPodcast
fields = "__all__"

View File

@ -0,0 +1,35 @@
from rest_framework import serializers
from ..models import Schedule
class ScheduleSerializer(serializers.HyperlinkedModelSerializer):
file_id = serializers.IntegerField(source="file.id", read_only=True)
stream_id = serializers.IntegerField(source="stream.id", read_only=True)
instance_id = serializers.IntegerField(source="instance.id", read_only=True)
cue_out = serializers.DurationField(source="get_cueout", read_only=True)
ends = serializers.DateTimeField(source="get_ends", read_only=True)
class Meta:
model = Schedule
fields = [
"item_url",
"id",
"starts",
"ends",
"file",
"file_id",
"stream",
"stream_id",
"clip_length",
"fade_in",
"fade_out",
"cue_in",
"cue_out",
"media_item_played",
"instance",
"instance_id",
"playout_status",
"broadcasted",
"position",
]

View File

@ -0,0 +1,69 @@
from rest_framework import serializers
from ..models import Show, ShowDays, ShowHost, ShowInstance, ShowRebroadcast
class ShowSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Show
fields = [
"item_url",
"id",
"name",
"url",
"genre",
"description",
"color",
"background_color",
"linked",
"is_linkable",
"image_path",
"has_autoplaylist",
"autoplaylist_repeat",
"autoplaylist",
]
class ShowDaysSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ShowDays
fields = "__all__"
class ShowHostSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ShowHost
fields = "__all__"
class ShowInstanceSerializer(serializers.HyperlinkedModelSerializer):
show_id = serializers.IntegerField(source="show.id", read_only=True)
file_id = serializers.IntegerField(source="file.id", read_only=True)
class Meta:
model = ShowInstance
fields = [
"item_url",
"id",
"description",
"starts",
"ends",
"record",
"rebroadcast",
"time_filled",
"created",
"last_scheduled",
"modified_instance",
"autoplaylist_built",
"show",
"show_id",
"instance",
"file",
"file_id",
]
class ShowRebroadcastSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ShowRebroadcast
fields = "__all__"

View File

@ -0,0 +1,21 @@
from rest_framework import serializers
from ..models import SmartBlock, SmartBlockContent, SmartBlockCriteria
class SmartBlockSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = SmartBlock
fields = "__all__"
class SmartBlockContentSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = SmartBlockContent
fields = "__all__"
class SmartBlockCriteriaSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = SmartBlockCriteria
fields = "__all__"

View File

@ -0,0 +1,17 @@
from rest_framework import serializers
from ..models import Webstream, WebstreamMetadata
class WebstreamSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.IntegerField(read_only=True)
class Meta:
model = Webstream
fields = "__all__"
class WebstreamMetadataSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = WebstreamMetadata
fields = "__all__"

View File

@ -2,7 +2,7 @@ from datetime import datetime, timedelta
from django.test import TestCase from django.test import TestCase
from libretime_api.models import Schedule, ShowInstance from ...models import Schedule, ShowInstance
class TestSchedule(TestCase): class TestSchedule(TestCase):

View File

@ -2,47 +2,11 @@ import os
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import AnonymousUser
from django.utils import dateparse from django.utils import dateparse
from model_bakery import baker from model_bakery import baker
from rest_framework.test import APIRequestFactory, APITestCase from rest_framework.test import APITestCase
from libretime_api.views import FileViewSet from ...._fixtures import AUDIO_FILENAME, fixture_path
class TestFileViewSet(APITestCase):
@classmethod
def setUpTestData(cls):
cls.path = "/api/v2/files/{id}/download/"
cls.token = settings.CONFIG.general.api_key
def test_invalid(self):
path = self.path.format(id="a")
self.client.credentials(HTTP_AUTHORIZATION=f"Api-Key {self.token}")
response = self.client.get(path)
self.assertEqual(response.status_code, 400)
def test_does_not_exist(self):
path = self.path.format(id="1")
self.client.credentials(HTTP_AUTHORIZATION=f"Api-Key {self.token}")
response = self.client.get(path)
self.assertEqual(response.status_code, 404)
def test_exists(self):
music_dir = baker.make(
"libretime_api.MusicDir",
directory=os.path.join(os.path.dirname(__file__), "resources"),
)
f = baker.make(
"libretime_api.File",
directory=music_dir,
mime="audio/mp3",
filepath="song.mp3",
)
path = self.path.format(id=str(f.pk))
self.client.credentials(HTTP_AUTHORIZATION=f"Api-Key {self.token}")
response = self.client.get(path)
self.assertEqual(response.status_code, 200)
class TestScheduleViewSet(APITestCase): class TestScheduleViewSet(APITestCase):
@ -53,25 +17,25 @@ class TestScheduleViewSet(APITestCase):
def test_schedule_item_full_length(self): def test_schedule_item_full_length(self):
music_dir = baker.make( music_dir = baker.make(
"libretime_api.MusicDir", "storage.MusicDir",
directory=os.path.join(os.path.dirname(__file__), "resources"), directory=str(fixture_path),
) )
f = baker.make( f = baker.make(
"libretime_api.File", "storage.File",
directory=music_dir, directory=music_dir,
mime="audio/mp3", mime="audio/mp3",
filepath="song.mp3", filepath=AUDIO_FILENAME,
length=timedelta(seconds=40.86), length=timedelta(seconds=40.86),
cuein=timedelta(seconds=0), cuein=timedelta(seconds=0),
cueout=timedelta(seconds=40.8131), cueout=timedelta(seconds=40.8131),
) )
show = baker.make( show = baker.make(
"libretime_api.ShowInstance", "schedule.ShowInstance",
starts=datetime.now(tz=timezone.utc) - timedelta(minutes=5), starts=datetime.now(tz=timezone.utc) - timedelta(minutes=5),
ends=datetime.now(tz=timezone.utc) + timedelta(minutes=5), ends=datetime.now(tz=timezone.utc) + timedelta(minutes=5),
) )
scheduleItem = baker.make( scheduleItem = baker.make(
"libretime_api.Schedule", "schedule.Schedule",
starts=datetime.now(tz=timezone.utc), starts=datetime.now(tz=timezone.utc),
ends=datetime.now(tz=timezone.utc) + f.length, ends=datetime.now(tz=timezone.utc) + f.length,
cue_out=f.cueout, cue_out=f.cueout,
@ -87,25 +51,25 @@ class TestScheduleViewSet(APITestCase):
def test_schedule_item_trunc(self): def test_schedule_item_trunc(self):
music_dir = baker.make( music_dir = baker.make(
"libretime_api.MusicDir", "storage.MusicDir",
directory=os.path.join(os.path.dirname(__file__), "resources"), directory=str(fixture_path),
) )
f = baker.make( f = baker.make(
"libretime_api.File", "storage.File",
directory=music_dir, directory=music_dir,
mime="audio/mp3", mime="audio/mp3",
filepath="song.mp3", filepath=AUDIO_FILENAME,
length=timedelta(seconds=40.86), length=timedelta(seconds=40.86),
cuein=timedelta(seconds=0), cuein=timedelta(seconds=0),
cueout=timedelta(seconds=40.8131), cueout=timedelta(seconds=40.8131),
) )
show = baker.make( show = baker.make(
"libretime_api.ShowInstance", "schedule.ShowInstance",
starts=datetime.now(tz=timezone.utc) - timedelta(minutes=5), starts=datetime.now(tz=timezone.utc) - timedelta(minutes=5),
ends=datetime.now(tz=timezone.utc) + timedelta(seconds=20), ends=datetime.now(tz=timezone.utc) + timedelta(seconds=20),
) )
scheduleItem = baker.make( scheduleItem = baker.make(
"libretime_api.Schedule", "schedule.Schedule",
starts=datetime.now(tz=timezone.utc), starts=datetime.now(tz=timezone.utc),
ends=datetime.now(tz=timezone.utc) + f.length, ends=datetime.now(tz=timezone.utc) + f.length,
instance=show, instance=show,
@ -124,33 +88,33 @@ class TestScheduleViewSet(APITestCase):
def test_schedule_item_invalid(self): def test_schedule_item_invalid(self):
music_dir = baker.make( music_dir = baker.make(
"libretime_api.MusicDir", "storage.MusicDir",
directory=os.path.join(os.path.dirname(__file__), "resources"), directory=str(fixture_path),
) )
f = baker.make( f = baker.make(
"libretime_api.File", "storage.File",
directory=music_dir, directory=music_dir,
mime="audio/mp3", mime="audio/mp3",
filepath="song.mp3", filepath=AUDIO_FILENAME,
length=timedelta(seconds=40.86), length=timedelta(seconds=40.86),
cuein=timedelta(seconds=0), cuein=timedelta(seconds=0),
cueout=timedelta(seconds=40.8131), cueout=timedelta(seconds=40.8131),
) )
show = baker.make( show = baker.make(
"libretime_api.ShowInstance", "schedule.ShowInstance",
starts=datetime.now(tz=timezone.utc) - timedelta(minutes=5), starts=datetime.now(tz=timezone.utc) - timedelta(minutes=5),
ends=datetime.now(tz=timezone.utc) + timedelta(minutes=5), ends=datetime.now(tz=timezone.utc) + timedelta(minutes=5),
) )
scheduleItem = baker.make( schedule_item = baker.make(
"libretime_api.Schedule", "schedule.Schedule",
starts=datetime.now(tz=timezone.utc), starts=datetime.now(tz=timezone.utc),
ends=datetime.now(tz=timezone.utc) + f.length, ends=datetime.now(tz=timezone.utc) + f.length,
cue_out=f.cueout, cue_out=f.cueout,
instance=show, instance=show,
file=f, file=f,
) )
invalidScheduleItem = baker.make( invalid_schedule_item = baker.make(
"libretime_api.Schedule", "schedule.Schedule",
starts=show.ends + timedelta(minutes=1), starts=show.ends + timedelta(minutes=1),
ends=show.ends + timedelta(minutes=1) + f.length, ends=show.ends + timedelta(minutes=1) + f.length,
cue_out=f.cueout, cue_out=f.cueout,
@ -163,19 +127,21 @@ class TestScheduleViewSet(APITestCase):
result = response.json() result = response.json()
# The invalid item should be filtered out and not returned # The invalid item should be filtered out and not returned
self.assertEqual(len(result), 1) self.assertEqual(len(result), 1)
self.assertEqual(dateparse.parse_datetime(result[0]["ends"]), scheduleItem.ends) self.assertEqual(
dateparse.parse_datetime(result[0]["ends"]), schedule_item.ends
)
self.assertEqual(dateparse.parse_duration(result[0]["cue_out"]), f.cueout) self.assertEqual(dateparse.parse_duration(result[0]["cue_out"]), f.cueout)
def test_schedule_item_range(self): def test_schedule_item_range(self):
music_dir = baker.make( music_dir = baker.make(
"libretime_api.MusicDir", "storage.MusicDir",
directory=os.path.join(os.path.dirname(__file__), "resources"), directory=str(fixture_path),
) )
f = baker.make( f = baker.make(
"libretime_api.File", "storage.File",
directory=music_dir, directory=music_dir,
mime="audio/mp3", mime="audio/mp3",
filepath="song.mp3", filepath=AUDIO_FILENAME,
length=timedelta(seconds=40.86), length=timedelta(seconds=40.86),
cuein=timedelta(seconds=0), cuein=timedelta(seconds=0),
cueout=timedelta(seconds=40.8131), cueout=timedelta(seconds=40.8131),
@ -183,12 +149,12 @@ class TestScheduleViewSet(APITestCase):
filter_point = datetime.now(tz=timezone.utc) filter_point = datetime.now(tz=timezone.utc)
show = baker.make( show = baker.make(
"libretime_api.ShowInstance", "schedule.ShowInstance",
starts=filter_point - timedelta(minutes=5), starts=filter_point - timedelta(minutes=5),
ends=filter_point + timedelta(minutes=5), ends=filter_point + timedelta(minutes=5),
) )
schedule_item = baker.make( schedule_item = baker.make(
"libretime_api.Schedule", "schedule.Schedule",
starts=filter_point, starts=filter_point,
ends=filter_point + f.length, ends=filter_point + f.length,
cue_out=f.cueout, cue_out=f.cueout,
@ -196,7 +162,7 @@ class TestScheduleViewSet(APITestCase):
file=f, file=f,
) )
previous_item = baker.make( previous_item = baker.make(
"libretime_api.Schedule", "schedule.Schedule",
starts=filter_point - timedelta(minutes=5), starts=filter_point - timedelta(minutes=5),
ends=filter_point - timedelta(minutes=5) + f.length, ends=filter_point - timedelta(minutes=5) + f.length,
cue_out=f.cueout, cue_out=f.cueout,

View File

@ -0,0 +1,21 @@
from .playlist import PlaylistContentViewSet, PlaylistViewSet
from .podcast import (
ImportedPodcastViewSet,
PodcastEpisodeViewSet,
PodcastViewSet,
StationPodcastViewSet,
)
from .schedule import ScheduleViewSet
from .show import (
ShowDaysViewSet,
ShowHostViewSet,
ShowInstanceViewSet,
ShowRebroadcastViewSet,
ShowViewSet,
)
from .smart_block import (
SmartBlockContentViewSet,
SmartBlockCriteriaViewSet,
SmartBlockViewSet,
)
from .webstream import WebstreamMetadataViewSet, WebstreamViewSet

View File

@ -0,0 +1,16 @@
from rest_framework import viewsets
from ..models import Playlist, PlaylistContent
from ..serializers import PlaylistContentSerializer, PlaylistSerializer
class PlaylistViewSet(viewsets.ModelViewSet):
queryset = Playlist.objects.all()
serializer_class = PlaylistSerializer
model_permission_name = "playlist"
class PlaylistContentViewSet(viewsets.ModelViewSet):
queryset = PlaylistContent.objects.all()
serializer_class = PlaylistContentSerializer
model_permission_name = "playlistcontent"

View File

@ -0,0 +1,33 @@
from rest_framework import viewsets
from ..models import ImportedPodcast, Podcast, PodcastEpisode, StationPodcast
from ..serializers import (
ImportedPodcastSerializer,
PodcastEpisodeSerializer,
PodcastSerializer,
StationPodcastSerializer,
)
class PodcastViewSet(viewsets.ModelViewSet):
queryset = Podcast.objects.all()
serializer_class = PodcastSerializer
model_permission_name = "podcast"
class PodcastEpisodeViewSet(viewsets.ModelViewSet):
queryset = PodcastEpisode.objects.all()
serializer_class = PodcastEpisodeSerializer
model_permission_name = "podcastepisode"
class StationPodcastViewSet(viewsets.ModelViewSet):
queryset = StationPodcast.objects.all()
serializer_class = StationPodcastSerializer
model_permission_name = "station"
class ImportedPodcastViewSet(viewsets.ModelViewSet):
queryset = ImportedPodcast.objects.all()
serializer_class = ImportedPodcastSerializer
model_permission_name = "importedpodcast"

View File

@ -0,0 +1,41 @@
from django.db.models import F
from drf_spectacular.utils import OpenApiParameter, extend_schema, extend_schema_view
from rest_framework import viewsets
from ..._constants import FILTER_NUMERICAL_LOOKUPS
from ..models import Schedule
from ..serializers import ScheduleSerializer
@extend_schema_view(
list=extend_schema(
parameters=[
OpenApiParameter(
name="is_valid",
description="Filter on valid instances",
required=False,
type=bool,
),
]
)
)
class ScheduleViewSet(viewsets.ModelViewSet):
queryset = Schedule.objects.all()
serializer_class = ScheduleSerializer
filter_fields = {
"starts": FILTER_NUMERICAL_LOOKUPS,
"ends": FILTER_NUMERICAL_LOOKUPS,
"playout_status": FILTER_NUMERICAL_LOOKUPS,
"broadcasted": FILTER_NUMERICAL_LOOKUPS,
}
model_permission_name = "schedule"
def get_queryset(self):
filter_valid = self.request.query_params.get("is_valid")
if filter_valid is None:
return self.queryset.all()
filter_valid = filter_valid.strip().lower() in ("true", "yes", "1")
if filter_valid:
return self.queryset.filter(starts__lt=F("instance__ends"))
else:
return self.queryset.filter(starts__gte=F("instance__ends"))

View File

@ -0,0 +1,40 @@
from rest_framework import viewsets
from ..models import Show, ShowDays, ShowHost, ShowInstance, ShowRebroadcast
from ..serializers import (
ShowDaysSerializer,
ShowHostSerializer,
ShowInstanceSerializer,
ShowRebroadcastSerializer,
ShowSerializer,
)
class ShowViewSet(viewsets.ModelViewSet):
queryset = Show.objects.all()
serializer_class = ShowSerializer
model_permission_name = "show"
class ShowDaysViewSet(viewsets.ModelViewSet):
queryset = ShowDays.objects.all()
serializer_class = ShowDaysSerializer
model_permission_name = "showdays"
class ShowHostViewSet(viewsets.ModelViewSet):
queryset = ShowHost.objects.all()
serializer_class = ShowHostSerializer
model_permission_name = "showhost"
class ShowInstanceViewSet(viewsets.ModelViewSet):
queryset = ShowInstance.objects.all()
serializer_class = ShowInstanceSerializer
model_permission_name = "showinstance"
class ShowRebroadcastViewSet(viewsets.ModelViewSet):
queryset = ShowRebroadcast.objects.all()
serializer_class = ShowRebroadcastSerializer
model_permission_name = "showrebroadcast"

View File

@ -0,0 +1,26 @@
from rest_framework import viewsets
from ..models import SmartBlock, SmartBlockContent, SmartBlockCriteria
from ..serializers import (
SmartBlockContentSerializer,
SmartBlockCriteriaSerializer,
SmartBlockSerializer,
)
class SmartBlockViewSet(viewsets.ModelViewSet):
queryset = SmartBlock.objects.all()
serializer_class = SmartBlockSerializer
model_permission_name = "smartblock"
class SmartBlockContentViewSet(viewsets.ModelViewSet):
queryset = SmartBlockContent.objects.all()
serializer_class = SmartBlockContentSerializer
model_permission_name = "smartblockcontent"
class SmartBlockCriteriaViewSet(viewsets.ModelViewSet):
queryset = SmartBlockCriteria.objects.all()
serializer_class = SmartBlockCriteriaSerializer
model_permission_name = "smartblockcriteria"

View File

@ -0,0 +1,16 @@
from rest_framework import viewsets
from ..models import Webstream, WebstreamMetadata
from ..serializers import WebstreamMetadataSerializer, WebstreamSerializer
class WebstreamViewSet(viewsets.ModelViewSet):
queryset = Webstream.objects.all()
serializer_class = WebstreamSerializer
model_permission_name = "webstream"
class WebstreamMetadataViewSet(viewsets.ModelViewSet):
queryset = WebstreamMetadata.objects.all()
serializer_class = WebstreamMetadataSerializer
model_permission_name = "webstreametadata"

View File

@ -1,316 +0,0 @@
from django.contrib.auth import get_user_model
from rest_framework import serializers
from .models import *
class UserSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = get_user_model()
fields = [
"item_url",
"username",
"type",
"first_name",
"last_name",
"lastfail",
"skype_contact",
"jabber_contact",
"email",
"cell_phone",
"login_attempts",
]
class SmartBlockSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = SmartBlock
fields = "__all__"
class SmartBlockContentSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = SmartBlockContent
fields = "__all__"
class SmartBlockCriteriaSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = SmartBlockCriteria
fields = "__all__"
class CountrySerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Country
fields = "__all__"
class FileSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.IntegerField(read_only=True)
class Meta:
model = File
fields = "__all__"
class ListenerCountSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ListenerCount
fields = "__all__"
class LiveLogSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = LiveLog
fields = "__all__"
class LoginAttemptSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = LoginAttempt
fields = "__all__"
class MountNameSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = MountName
fields = "__all__"
class MusicDirSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = MusicDir
fields = "__all__"
class PlaylistSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Playlist
fields = "__all__"
class PlaylistContentSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = PlaylistContent
fields = "__all__"
class PlayoutHistorySerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = PlayoutHistory
fields = "__all__"
class PlayoutHistoryMetadataSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = PlayoutHistoryMetadata
fields = "__all__"
class PlayoutHistoryTemplateSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = PlayoutHistoryTemplate
fields = "__all__"
class PlayoutHistoryTemplateFieldSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = PlayoutHistoryTemplateField
fields = "__all__"
class PreferenceSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Preference
fields = "__all__"
class ScheduleSerializer(serializers.HyperlinkedModelSerializer):
file_id = serializers.IntegerField(source="file.id", read_only=True)
stream_id = serializers.IntegerField(source="stream.id", read_only=True)
instance_id = serializers.IntegerField(source="instance.id", read_only=True)
cue_out = serializers.DurationField(source="get_cueout", read_only=True)
ends = serializers.DateTimeField(source="get_ends", read_only=True)
class Meta:
model = Schedule
fields = [
"item_url",
"id",
"starts",
"ends",
"file",
"file_id",
"stream",
"stream_id",
"clip_length",
"fade_in",
"fade_out",
"cue_in",
"cue_out",
"media_item_played",
"instance",
"instance_id",
"playout_status",
"broadcasted",
"position",
]
class ServiceRegisterSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ServiceRegister
fields = "__all__"
class SessionSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Session
fields = "__all__"
class ShowSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Show
fields = [
"item_url",
"id",
"name",
"url",
"genre",
"description",
"color",
"background_color",
"linked",
"is_linkable",
"image_path",
"has_autoplaylist",
"autoplaylist_repeat",
"autoplaylist",
]
class ShowDaysSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ShowDays
fields = "__all__"
class ShowHostSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ShowHost
fields = "__all__"
class ShowInstanceSerializer(serializers.HyperlinkedModelSerializer):
show_id = serializers.IntegerField(source="show.id", read_only=True)
file_id = serializers.IntegerField(source="file.id", read_only=True)
class Meta:
model = ShowInstance
fields = [
"item_url",
"id",
"description",
"starts",
"ends",
"record",
"rebroadcast",
"time_filled",
"created",
"last_scheduled",
"modified_instance",
"autoplaylist_built",
"show",
"show_id",
"instance",
"file",
"file_id",
]
class ShowRebroadcastSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ShowRebroadcast
fields = "__all__"
class StreamSettingSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = StreamSetting
fields = "__all__"
class UserTokenSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = UserToken
fields = "__all__"
class TimestampSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Timestamp
fields = "__all__"
class WebstreamSerializer(serializers.HyperlinkedModelSerializer):
id = serializers.IntegerField(read_only=True)
class Meta:
model = Webstream
fields = "__all__"
class WebstreamMetadataSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = WebstreamMetadata
fields = "__all__"
class CeleryTaskSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = CeleryTask
fields = "__all__"
class CloudFileSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = CloudFile
fields = "__all__"
class ImportedPodcastSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ImportedPodcast
fields = "__all__"
class PodcastSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Podcast
fields = "__all__"
class PodcastEpisodeSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = PodcastEpisode
fields = "__all__"
class StationPodcastSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = StationPodcast
fields = "__all__"
class ThirdPartyTrackReferenceSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = ThirdPartyTrackReference
fields = "__all__"
class TrackTypeSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = TrackType
fields = "__all__"

View File

@ -1,7 +1,11 @@
# Django settings # Django settings
For more information on django settings, see https://docs.djangoproject.com/en/3.2/topics/settings/.
For the full list of settings and their values, see https://docs.djangoproject.com/en/3.2/ref/settings/.
The structure of the django settings module is the following: The structure of the django settings module is the following:
- the `__init__.py` (`libretime_api.settings`) module is the django settings entrypoint. The module contains bindings between the user configuration and the django settings. **Advanced users** may edit this file to better integrate the LibreTime API in their setup.
- the `_internal.py` module contains application settings for django. - the `_internal.py` module contains application settings for django.
- the `_schema.py` module contains the schema for the user configuration parsing and validation. - the `_schema.py` module contains the schema for the user configuration parsing and validation.
- the `prod.py` (`libretime_api.settings.prod`) module is the django settings entrypoint. The module contains bindings between the user configuration and the django settings. **Advanced users** may edit this file to better integrate the LibreTime API in their setup.
- the `testing.py` (`libretime_api.settings.testing`) module is the testing django settings entrypoint.

View File

@ -1,53 +0,0 @@
# pylint: disable=unused-import
from os import getenv
from ._internal import (
AUTH_PASSWORD_VALIDATORS,
AUTH_USER_MODEL,
DEBUG,
INSTALLED_APPS,
MIDDLEWARE,
REST_FRAMEWORK,
ROOT_URLCONF,
STATIC_URL,
TEMPLATES,
TEST_RUNNER,
WSGI_APPLICATION,
setup_logger,
)
from ._schema import Config
API_VERSION = "2.0.0"
LIBRETIME_LOG_FILEPATH = getenv("LIBRETIME_LOG_FILEPATH")
LIBRETIME_CONFIG_FILEPATH = getenv("LIBRETIME_CONFIG_FILEPATH")
CONFIG = Config(filepath=LIBRETIME_CONFIG_FILEPATH)
SECRET_KEY = CONFIG.general.api_key
ALLOWED_HOSTS = ["*"]
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"HOST": CONFIG.database.host,
"PORT": CONFIG.database.port,
"NAME": CONFIG.database.name,
"USER": CONFIG.database.user,
"PASSWORD": CONFIG.database.password,
}
}
LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"
USE_I18N = True
USE_L10N = True
USE_TZ = True
LOGGING = setup_logger(LIBRETIME_LOG_FILEPATH)
SPECTACULAR_SETTINGS = {
"TITLE": "LibreTime API",
"DESCRIPTION": "Radio Broadcast & Automation Platform",
"VERSION": API_VERSION,
}

View File

@ -1,13 +1,18 @@
from os import getenv from os import getenv
from typing import Optional from typing import Optional
API_VERSION = "2.0.0"
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = getenv("LIBRETIME_DEBUG") DEBUG = getenv("LIBRETIME_DEBUG", "false").lower() == "true"
# Application definition # Application definition
INSTALLED_APPS = [ INSTALLED_APPS = [
"libretime_api.apps.LibreTimeAPIConfig", "libretime_api.core",
"libretime_api.history",
"libretime_api.storage",
"libretime_api.schedule",
"django.contrib.auth", "django.contrib.auth",
"django.contrib.contenttypes", "django.contrib.contenttypes",
"django.contrib.sessions", "django.contrib.sessions",
@ -48,8 +53,19 @@ TEMPLATES = [
WSGI_APPLICATION = "libretime_api.wsgi.application" WSGI_APPLICATION = "libretime_api.wsgi.application"
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/
STATIC_URL = "/api/v2/static/"
# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
# Password validation # Password validation
# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators # https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [ AUTH_PASSWORD_VALIDATORS = [
{ {
@ -66,39 +82,11 @@ AUTH_PASSWORD_VALIDATORS = [
}, },
] ]
# Rest Framework settings
# https://www.django-rest-framework.org/api-guide/settings/
renderer_classes = ["rest_framework.renderers.JSONRenderer"]
if DEBUG:
renderer_classes += ["rest_framework.renderers.BrowsableAPIRenderer"]
REST_FRAMEWORK = {
"DEFAULT_RENDERER_CLASSES": renderer_classes,
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework.authentication.SessionAuthentication",
"rest_framework.authentication.BasicAuthentication",
),
"DEFAULT_PERMISSION_CLASSES": [
"libretime_api.permissions.IsSystemTokenOrUser",
],
"DEFAULT_FILTER_BACKENDS": [
"django_filters.rest_framework.DjangoFilterBackend",
],
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
"URL_FIELD_NAME": "item_url",
}
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.0/howto/static-files/
STATIC_URL = "/api/v2/static/"
AUTH_USER_MODEL = "libretime_api.User"
TEST_RUNNER = "libretime_api.tests.runners.ManagedModelTestRunner"
# Logging # Logging
# https://docs.djangoproject.com/en/3.2/topics/logging/#configuring-logging
def setup_logger(log_filepath: Optional[str]): def setup_logger(log_filepath: Optional[str]):
logging_handlers = { logging_handlers = {
"console": { "console": {
@ -143,3 +131,41 @@ def setup_logger(log_filepath: Optional[str]):
}, },
}, },
} }
# Rest Framework
# https://www.django-rest-framework.org/api-guide/settings/
renderer_classes = ["rest_framework.renderers.JSONRenderer"]
if DEBUG:
renderer_classes += ["rest_framework.renderers.BrowsableAPIRenderer"]
REST_FRAMEWORK = {
"DEFAULT_RENDERER_CLASSES": renderer_classes,
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework.authentication.SessionAuthentication",
"rest_framework.authentication.BasicAuthentication",
),
"DEFAULT_PERMISSION_CLASSES": [
"libretime_api.permissions.IsSystemTokenOrUser",
],
"DEFAULT_FILTER_BACKENDS": [
"django_filters.rest_framework.DjangoFilterBackend",
],
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
"URL_FIELD_NAME": "item_url",
}
# Auth
# https://docs.djangoproject.com/en/3.2/topics/auth/customizing/#substituting-a-custom-user-model
AUTH_USER_MODEL = "core.User"
# Spectacular
# https://drf-spectacular.readthedocs.io/en/latest/settings.html
SPECTACULAR_SETTINGS = {
"TITLE": "LibreTime API",
"DESCRIPTION": "Radio Broadcast & Automation Platform",
"VERSION": API_VERSION,
}

View File

@ -0,0 +1,55 @@
from os import getenv
# pylint: disable=unused-import
from ._internal import (
API_VERSION,
AUTH_PASSWORD_VALIDATORS,
AUTH_USER_MODEL,
DEBUG,
DEFAULT_AUTO_FIELD,
INSTALLED_APPS,
MIDDLEWARE,
REST_FRAMEWORK,
ROOT_URLCONF,
SPECTACULAR_SETTINGS,
STATIC_URL,
TEMPLATES,
WSGI_APPLICATION,
setup_logger,
)
from ._schema import Config
LIBRETIME_LOG_FILEPATH = getenv("LIBRETIME_LOG_FILEPATH")
LIBRETIME_CONFIG_FILEPATH = getenv("LIBRETIME_CONFIG_FILEPATH")
CONFIG = Config(filepath=LIBRETIME_CONFIG_FILEPATH)
SECRET_KEY = CONFIG.general.api_key
ALLOWED_HOSTS = ["*"]
LOGGING = setup_logger(LIBRETIME_LOG_FILEPATH)
# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
DATABASES = {
"default": {
"ENGINE": "django.db.backends.postgresql",
"HOST": CONFIG.database.host,
"PORT": CONFIG.database.port,
"NAME": CONFIG.database.name,
"USER": CONFIG.database.user,
"PASSWORD": CONFIG.database.password,
}
}
# Internationalization
# https://docs.djangoproject.com/en/3.2/topics/i18n/
LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"
USE_I18N = True
USE_L10N = True
USE_TZ = True

View File

@ -0,0 +1,35 @@
import os
os.environ.setdefault("LIBRETIME_DEBUG", "true")
os.environ.setdefault("LIBRETIME_GENERAL_API_KEY", "testing")
# pylint: disable=wrong-import-position,unused-import
from .prod import (
ALLOWED_HOSTS,
API_VERSION,
AUTH_PASSWORD_VALIDATORS,
AUTH_USER_MODEL,
CONFIG,
DATABASES,
DEBUG,
DEFAULT_AUTO_FIELD,
INSTALLED_APPS,
LANGUAGE_CODE,
LOGGING,
MIDDLEWARE,
REST_FRAMEWORK,
ROOT_URLCONF,
SECRET_KEY,
STATIC_URL,
TEMPLATES,
TIME_ZONE,
USE_I18N,
USE_L10N,
USE_TZ,
WSGI_APPLICATION,
)
# Testing
# https://docs.djangoproject.com/en/3.2/ref/settings/#test-runner
TEST_RUNNER = "libretime_api.tests.runner.ManagedModelTestRunner"

View File

View File

@ -0,0 +1,7 @@
from django.apps import AppConfig
class StorageConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "libretime_api.storage"
verbose_name = "LibreTime Storage API"

View File

@ -0,0 +1,4 @@
from .cloud_file import CloudFile
from .file import File
from .storage import MusicDir
from .track_type import TrackType

View File

@ -0,0 +1,13 @@
from django.db import models
class CloudFile(models.Model):
storage_backend = models.CharField(max_length=512)
resource_id = models.TextField()
filename = models.ForeignKey(
"File", models.DO_NOTHING, blank=True, null=True, db_column="cc_file_id"
)
class Meta:
managed = False
db_table = "cloud_file"

View File

@ -12,7 +12,7 @@ class File(models.Model):
import_status = models.IntegerField() import_status = models.IntegerField()
currently_accessing = models.IntegerField(db_column="currentlyaccessing") currently_accessing = models.IntegerField(db_column="currentlyaccessing")
edited_by = models.ForeignKey( edited_by = models.ForeignKey(
"User", "core.User",
models.DO_NOTHING, models.DO_NOTHING,
db_column="editedby", db_column="editedby",
blank=True, blank=True,
@ -70,7 +70,7 @@ class File(models.Model):
replay_gain = models.DecimalField( replay_gain = models.DecimalField(
max_digits=8, decimal_places=2, blank=True, null=True max_digits=8, decimal_places=2, blank=True, null=True
) )
owner = models.ForeignKey("User", models.DO_NOTHING, blank=True, null=True) owner = models.ForeignKey("core.User", models.DO_NOTHING, blank=True, null=True)
cuein = models.DurationField(blank=True, null=True) cuein = models.DurationField(blank=True, null=True)
cueout = models.DurationField(blank=True, null=True) cueout = models.DurationField(blank=True, null=True)
silan_check = models.BooleanField(blank=True, null=True) silan_check = models.BooleanField(blank=True, null=True)
@ -92,26 +92,3 @@ class File(models.Model):
("change_own_file", "Change the files where they are the owner"), ("change_own_file", "Change the files where they are the owner"),
("delete_own_file", "Delete the files where they are the owner"), ("delete_own_file", "Delete the files where they are the owner"),
] ]
class MusicDir(models.Model):
directory = models.TextField(unique=True, blank=True, null=True)
type = models.CharField(max_length=255, blank=True, null=True)
exists = models.BooleanField(blank=True, null=True)
watched = models.BooleanField(blank=True, null=True)
class Meta:
managed = False
db_table = "cc_music_dirs"
class CloudFile(models.Model):
storage_backend = models.CharField(max_length=512)
resource_id = models.TextField()
filename = models.ForeignKey(
File, models.DO_NOTHING, blank=True, null=True, db_column="cc_file_id"
)
class Meta:
managed = False
db_table = "cloud_file"

View File

@ -0,0 +1,12 @@
from django.db import models
class MusicDir(models.Model):
directory = models.TextField(unique=True, blank=True, null=True)
type = models.CharField(max_length=255, blank=True, null=True)
exists = models.BooleanField(blank=True, null=True)
watched = models.BooleanField(blank=True, null=True)
class Meta:
managed = False
db_table = "cc_music_dirs"

View File

@ -0,0 +1,12 @@
from django.db import models
class TrackType(models.Model):
code = models.CharField(max_length=16, unique=True)
type_name = models.CharField(max_length=255, blank=True, null=True)
description = models.CharField(max_length=255, blank=True, null=True)
visibility = models.BooleanField(blank=True, default=True)
class Meta:
managed = False
db_table = "cc_track_types"

Some files were not shown because too many files have changed in this diff Show More