chore: rename libretimeapi dir to libretime_api
This commit is contained in:
parent
02efadc3d0
commit
6de242db65
36 changed files with 0 additions and 0 deletions
0
api/libretime_api/__init__.py
Normal file
0
api/libretime_api/__init__.py
Normal file
8
api/libretime_api/apps.py
Normal file
8
api/libretime_api/apps.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
from django.apps import AppConfig
|
||||
from django.db.models.signals import pre_save
|
||||
|
||||
|
||||
class LibreTimeAPIConfig(AppConfig):
|
||||
name = "libretimeapi"
|
||||
verbose_name = "LibreTime API"
|
||||
default_auto_field = "django.db.models.AutoField"
|
22
api/libretime_api/cli.py
Executable file
22
api/libretime_api/cli.py
Executable file
|
@ -0,0 +1,22 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""Django's command-line utility for administrative tasks."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "libretimeapi.settings")
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
) from exc
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
22
api/libretime_api/managers.py
Normal file
22
api/libretime_api/managers.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
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)
|
14
api/libretime_api/models/__init__.py
Normal file
14
api/libretime_api/models/__init__.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
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 *
|
148
api/libretime_api/models/authentication.py
Normal file
148
api/libretime_api/models/authentication.py
Normal file
|
@ -0,0 +1,148 @@
|
|||
import hashlib
|
||||
|
||||
from django.contrib import auth
|
||||
from django.contrib.auth.models import AbstractBaseUser, Permission
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db import models
|
||||
|
||||
from libretimeapi.managers import UserManager
|
||||
from libretimeapi.permission_constants import GROUPS
|
||||
|
||||
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 = ()
|
||||
for item in USER_TYPES.items():
|
||||
USER_TYPE_CHOICES = USER_TYPE_CHOICES + (item,)
|
||||
|
||||
|
||||
class User(AbstractBaseUser):
|
||||
username = models.CharField(db_column="login", unique=True, max_length=255)
|
||||
password = models.CharField(
|
||||
db_column="pass", max_length=255
|
||||
) # Field renamed because it was a Python reserved word.
|
||||
type = models.CharField(max_length=1, choices=USER_TYPE_CHOICES)
|
||||
first_name = models.CharField(max_length=255)
|
||||
last_name = models.CharField(max_length=255)
|
||||
last_login = models.DateTimeField(db_column="lastlogin", blank=True, null=True)
|
||||
lastfail = models.DateTimeField(blank=True, null=True)
|
||||
skype_contact = models.CharField(max_length=1024, blank=True, null=True)
|
||||
jabber_contact = models.CharField(max_length=1024, blank=True, null=True)
|
||||
email = models.CharField(max_length=1024, blank=True, null=True)
|
||||
cell_phone = models.CharField(max_length=1024, blank=True, null=True)
|
||||
login_attempts = models.IntegerField(blank=True, null=True)
|
||||
|
||||
USERNAME_FIELD = "username"
|
||||
EMAIL_FIELD = "email"
|
||||
REQUIRED_FIELDS = ["type", "email", "first_name", "last_name"]
|
||||
objects = UserManager()
|
||||
|
||||
def get_full_name(self):
|
||||
return "{} {}".format(self.first_name, self.last_name)
|
||||
|
||||
def get_short_name(self):
|
||||
return self.first_name
|
||||
|
||||
def set_password(self, password):
|
||||
if not password:
|
||||
self.set_unusable_password()
|
||||
else:
|
||||
self.password = hashlib.md5(password.encode()).hexdigest()
|
||||
|
||||
def is_staff(self):
|
||||
return self.type == ADMIN
|
||||
|
||||
def check_password(self, password):
|
||||
if self.has_usable_password():
|
||||
test_password = hashlib.md5(password.encode()).hexdigest()
|
||||
return test_password == self.password
|
||||
return False
|
||||
|
||||
"""
|
||||
The following methods have to be re-implemented here, as PermissionsMixin
|
||||
assumes that the User class has a 'group' attribute, which LibreTime does
|
||||
not currently provide. Once Django starts managing the Database
|
||||
(managed = True), then this can be replaced with
|
||||
django.contrib.auth.models.PermissionMixin.
|
||||
"""
|
||||
|
||||
def is_superuser(self):
|
||||
return self.type == ADMIN
|
||||
|
||||
def get_user_permissions(self, obj=None):
|
||||
"""
|
||||
Users do not have permissions directly, only through groups
|
||||
"""
|
||||
return []
|
||||
|
||||
def get_group_permissions(self, obj=None):
|
||||
permissions = GROUPS[self.type]
|
||||
if obj:
|
||||
obj_name = obj.__class__.__name__.lower()
|
||||
permissions = [perm for perm in permissions if obj_name in perm]
|
||||
# get permissions objects
|
||||
q = models.Q()
|
||||
for perm in permissions:
|
||||
q = q | models.Q(codename=perm)
|
||||
return list(Permission.objects.filter(q))
|
||||
|
||||
def get_all_permissions(self, obj=None):
|
||||
return self.get_user_permissions(obj) + self.get_group_permissions(obj)
|
||||
|
||||
def has_perm(self, perm, obj=None):
|
||||
if self.is_superuser():
|
||||
return True
|
||||
if not perm:
|
||||
return False
|
||||
permissions = self.get_all_permissions(obj)
|
||||
try:
|
||||
permission = Permission.objects.get(codename=perm)
|
||||
return permission in permissions
|
||||
except Permission.DoesNotExist:
|
||||
return False
|
||||
|
||||
def has_perms(self, perm_list, obj=None):
|
||||
result = True
|
||||
for permission in perm_list:
|
||||
result = result and self.has_perm(permission, obj)
|
||||
return result
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
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"
|
15
api/libretime_api/models/celery.py
Normal file
15
api/libretime_api/models/celery.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
from django.db import models
|
||||
|
||||
|
||||
class CeleryTask(models.Model):
|
||||
task_id = models.CharField(max_length=256)
|
||||
track_reference = models.ForeignKey(
|
||||
"ThirdPartyTrackReference", models.DO_NOTHING, db_column="track_reference"
|
||||
)
|
||||
name = models.CharField(max_length=256, blank=True, null=True)
|
||||
dispatch_time = models.DateTimeField(blank=True, null=True)
|
||||
status = models.CharField(max_length=256)
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
db_table = "celery_tasks"
|
10
api/libretime_api/models/countries.py
Normal file
10
api/libretime_api/models/countries.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
from django.db import models
|
||||
|
||||
|
||||
class Country(models.Model):
|
||||
isocode = models.CharField(primary_key=True, max_length=3)
|
||||
name = models.CharField(max_length=255)
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
db_table = "cc_country"
|
117
api/libretime_api/models/files.py
Normal file
117
api/libretime_api/models/files.py
Normal file
|
@ -0,0 +1,117 @@
|
|||
from django.db import models
|
||||
|
||||
|
||||
class File(models.Model):
|
||||
name = models.CharField(max_length=255)
|
||||
mime = models.CharField(max_length=255)
|
||||
ftype = models.CharField(max_length=128)
|
||||
directory = models.ForeignKey(
|
||||
"MusicDir", models.DO_NOTHING, db_column="directory", blank=True, null=True
|
||||
)
|
||||
filepath = models.TextField(blank=True, null=True)
|
||||
import_status = models.IntegerField()
|
||||
currently_accessing = models.IntegerField(db_column="currentlyaccessing")
|
||||
edited_by = models.ForeignKey(
|
||||
"User",
|
||||
models.DO_NOTHING,
|
||||
db_column="editedby",
|
||||
blank=True,
|
||||
null=True,
|
||||
related_name="edited_files",
|
||||
)
|
||||
mtime = models.DateTimeField(blank=True, null=True)
|
||||
utime = models.DateTimeField(blank=True, null=True)
|
||||
lptime = models.DateTimeField(blank=True, null=True)
|
||||
md5 = models.CharField(max_length=32, blank=True, null=True)
|
||||
track_title = models.CharField(max_length=512, blank=True, null=True)
|
||||
artist_name = models.CharField(max_length=512, blank=True, null=True)
|
||||
bit_rate = models.IntegerField(blank=True, null=True)
|
||||
sample_rate = models.IntegerField(blank=True, null=True)
|
||||
format = models.CharField(max_length=128, blank=True, null=True)
|
||||
length = models.DurationField(blank=True, null=True)
|
||||
album_title = models.CharField(max_length=512, blank=True, null=True)
|
||||
genre = models.CharField(max_length=64, blank=True, null=True)
|
||||
comments = models.TextField(blank=True, null=True)
|
||||
year = models.CharField(max_length=16, blank=True, null=True)
|
||||
track_number = models.IntegerField(blank=True, null=True)
|
||||
channels = models.IntegerField(blank=True, null=True)
|
||||
url = models.CharField(max_length=1024, blank=True, null=True)
|
||||
bpm = models.IntegerField(blank=True, null=True)
|
||||
rating = models.CharField(max_length=8, blank=True, null=True)
|
||||
encoded_by = models.CharField(max_length=255, blank=True, null=True)
|
||||
disc_number = models.CharField(max_length=8, blank=True, null=True)
|
||||
mood = models.CharField(max_length=64, blank=True, null=True)
|
||||
label = models.CharField(max_length=512, blank=True, null=True)
|
||||
composer = models.CharField(max_length=512, blank=True, null=True)
|
||||
encoder = models.CharField(max_length=64, blank=True, null=True)
|
||||
checksum = models.CharField(max_length=256, blank=True, null=True)
|
||||
lyrics = models.TextField(blank=True, null=True)
|
||||
orchestra = models.CharField(max_length=512, blank=True, null=True)
|
||||
conductor = models.CharField(max_length=512, blank=True, null=True)
|
||||
lyricist = models.CharField(max_length=512, blank=True, null=True)
|
||||
original_lyricist = models.CharField(max_length=512, blank=True, null=True)
|
||||
radio_station_name = models.CharField(max_length=512, blank=True, null=True)
|
||||
info_url = models.CharField(max_length=512, blank=True, null=True)
|
||||
artist_url = models.CharField(max_length=512, blank=True, null=True)
|
||||
audio_source_url = models.CharField(max_length=512, blank=True, null=True)
|
||||
radio_station_url = models.CharField(max_length=512, blank=True, null=True)
|
||||
buy_this_url = models.CharField(max_length=512, blank=True, null=True)
|
||||
isrc_number = models.CharField(max_length=512, blank=True, null=True)
|
||||
catalog_number = models.CharField(max_length=512, blank=True, null=True)
|
||||
original_artist = models.CharField(max_length=512, blank=True, null=True)
|
||||
copyright = models.CharField(max_length=512, blank=True, null=True)
|
||||
report_datetime = models.CharField(max_length=32, blank=True, null=True)
|
||||
report_location = models.CharField(max_length=512, blank=True, null=True)
|
||||
report_organization = models.CharField(max_length=512, blank=True, null=True)
|
||||
subject = models.CharField(max_length=512, blank=True, null=True)
|
||||
contributor = models.CharField(max_length=512, blank=True, null=True)
|
||||
language = models.CharField(max_length=512, blank=True, null=True)
|
||||
file_exists = models.BooleanField(blank=True, null=True)
|
||||
replay_gain = models.DecimalField(
|
||||
max_digits=8, decimal_places=2, blank=True, null=True
|
||||
)
|
||||
owner = models.ForeignKey("User", models.DO_NOTHING, blank=True, null=True)
|
||||
cuein = models.DurationField(blank=True, null=True)
|
||||
cueout = models.DurationField(blank=True, null=True)
|
||||
silan_check = models.BooleanField(blank=True, null=True)
|
||||
hidden = models.BooleanField(blank=True, null=True)
|
||||
is_scheduled = models.BooleanField(blank=True, null=True)
|
||||
is_playlist = models.BooleanField(blank=True, null=True)
|
||||
filesize = models.IntegerField()
|
||||
description = models.CharField(max_length=512, blank=True, null=True)
|
||||
artwork = models.CharField(max_length=512, blank=True, null=True)
|
||||
track_type = models.CharField(max_length=16, blank=True, null=True)
|
||||
|
||||
def get_owner(self):
|
||||
return self.owner
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
db_table = "cc_files"
|
||||
permissions = [
|
||||
("change_own_file", "Change 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"
|
42
api/libretime_api/models/playlists.py
Normal file
42
api/libretime_api/models/playlists.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
from django.db import models
|
||||
|
||||
from .files import File
|
||||
from .smart_blocks import SmartBlock
|
||||
|
||||
|
||||
class Playlist(models.Model):
|
||||
name = models.CharField(max_length=255)
|
||||
mtime = models.DateTimeField(blank=True, null=True)
|
||||
utime = models.DateTimeField(blank=True, null=True)
|
||||
creator = models.ForeignKey("User", models.DO_NOTHING, blank=True, null=True)
|
||||
description = models.CharField(max_length=512, blank=True, null=True)
|
||||
length = models.DurationField(blank=True, null=True)
|
||||
|
||||
def get_owner(self):
|
||||
return self.creator
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
db_table = "cc_playlist"
|
||||
|
||||
|
||||
class PlaylistContent(models.Model):
|
||||
playlist = models.ForeignKey(Playlist, models.DO_NOTHING, blank=True, null=True)
|
||||
file = models.ForeignKey(File, 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)
|
||||
type = models.SmallIntegerField()
|
||||
position = models.IntegerField(blank=True, null=True)
|
||||
trackoffset = models.FloatField()
|
||||
cliplength = models.DurationField(blank=True, null=True)
|
||||
cuein = models.DurationField(blank=True, null=True)
|
||||
cueout = models.DurationField(blank=True, null=True)
|
||||
fadein = models.TimeField(blank=True, null=True)
|
||||
fadeout = models.TimeField(blank=True, null=True)
|
||||
|
||||
def get_owner(self):
|
||||
return self.playlist.owner
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
db_table = "cc_playlistcontents"
|
76
api/libretime_api/models/playout.py
Normal file
76
api/libretime_api/models/playout.py
Normal file
|
@ -0,0 +1,76 @@
|
|||
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):
|
||||
file = models.ForeignKey(File, models.DO_NOTHING, blank=True, null=True)
|
||||
starts = models.DateTimeField()
|
||||
ends = models.DateTimeField(blank=True, null=True)
|
||||
instance = models.ForeignKey(
|
||||
"ShowInstance", models.DO_NOTHING, blank=True, null=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
db_table = "cc_playout_history"
|
||||
|
||||
|
||||
class PlayoutHistoryMetadata(models.Model):
|
||||
history = models.ForeignKey(PlayoutHistory, models.DO_NOTHING)
|
||||
key = models.CharField(max_length=128)
|
||||
value = models.CharField(max_length=128)
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
db_table = "cc_playout_history_metadata"
|
||||
|
||||
|
||||
class PlayoutHistoryTemplate(models.Model):
|
||||
name = models.CharField(max_length=128)
|
||||
type = models.CharField(max_length=35)
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
db_table = "cc_playout_history_template"
|
||||
|
||||
|
||||
class PlayoutHistoryTemplateField(models.Model):
|
||||
template = models.ForeignKey(PlayoutHistoryTemplate, models.DO_NOTHING)
|
||||
name = models.CharField(max_length=128)
|
||||
label = models.CharField(max_length=128)
|
||||
type = models.CharField(max_length=128)
|
||||
is_file_md = models.BooleanField()
|
||||
position = models.IntegerField()
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
db_table = "cc_playout_history_template_field"
|
||||
|
||||
|
||||
class Timestamp(models.Model):
|
||||
timestamp = models.DateTimeField()
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
db_table = "cc_timestamp"
|
86
api/libretime_api/models/podcasts.py
Normal file
86
api/libretime_api/models/podcasts.py
Normal file
|
@ -0,0 +1,86 @@
|
|||
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):
|
||||
url = models.CharField(max_length=4096)
|
||||
title = models.CharField(max_length=4096)
|
||||
creator = models.CharField(max_length=4096, blank=True, null=True)
|
||||
description = models.CharField(max_length=4096, blank=True, null=True)
|
||||
language = models.CharField(max_length=4096, blank=True, null=True)
|
||||
copyright = models.CharField(max_length=4096, blank=True, null=True)
|
||||
link = models.CharField(max_length=4096, blank=True, null=True)
|
||||
itunes_author = models.CharField(max_length=4096, blank=True, null=True)
|
||||
itunes_keywords = models.CharField(max_length=4096, blank=True, null=True)
|
||||
itunes_summary = models.CharField(max_length=4096, blank=True, null=True)
|
||||
itunes_subtitle = 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)
|
||||
owner = models.ForeignKey(
|
||||
User, models.DO_NOTHING, db_column="owner", blank=True, null=True
|
||||
)
|
||||
|
||||
def get_owner(self):
|
||||
return self.owner
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
db_table = "podcast"
|
||||
permissions = [
|
||||
("change_own_podcast", "Change the podcasts where they are the owner"),
|
||||
("delete_own_podcast", "Delete the podcasts where they are the owner"),
|
||||
]
|
||||
|
||||
|
||||
class PodcastEpisode(models.Model):
|
||||
file = models.ForeignKey(File, models.DO_NOTHING, blank=True, null=True)
|
||||
podcast = models.ForeignKey(Podcast, models.DO_NOTHING)
|
||||
publication_date = models.DateTimeField()
|
||||
download_url = models.CharField(max_length=4096)
|
||||
episode_guid = models.CharField(max_length=4096)
|
||||
episode_title = models.CharField(max_length=4096)
|
||||
episode_description = models.TextField()
|
||||
|
||||
def get_owner(self):
|
||||
return self.podcast.owner
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
db_table = "podcast_episodes"
|
||||
permissions = [
|
||||
(
|
||||
"change_own_podcastepisode",
|
||||
"Change the episodes of podcasts where they are the owner",
|
||||
),
|
||||
(
|
||||
"delete_own_podcastepisode",
|
||||
"Delete the episodes of podcasts where they are the owner",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
class StationPodcast(models.Model):
|
||||
podcast = models.ForeignKey(Podcast, models.DO_NOTHING)
|
||||
|
||||
def get_owner(self):
|
||||
return self.podcast.owner
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
db_table = "station_podcast"
|
32
api/libretime_api/models/preferences.py
Normal file
32
api/libretime_api/models/preferences.py
Normal file
|
@ -0,0 +1,32 @@
|
|||
from django.db import models
|
||||
|
||||
|
||||
class Preference(models.Model):
|
||||
subjid = models.ForeignKey(
|
||||
"User", models.DO_NOTHING, db_column="subjid", blank=True, null=True
|
||||
)
|
||||
keystr = models.CharField(unique=True, max_length=255, blank=True, null=True)
|
||||
valstr = models.TextField(blank=True, null=True)
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
db_table = "cc_pref"
|
||||
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):
|
||||
keyname = models.CharField(primary_key=True, max_length=64)
|
||||
value = models.CharField(max_length=255, blank=True, null=True)
|
||||
type = models.CharField(max_length=16)
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
db_table = "cc_stream_setting"
|
85
api/libretime_api/models/schedule.py
Normal file
85
api/libretime_api/models/schedule.py
Normal file
|
@ -0,0 +1,85 @@
|
|||
from django.db import models
|
||||
|
||||
from .files import File
|
||||
|
||||
|
||||
class Schedule(models.Model):
|
||||
starts = models.DateTimeField()
|
||||
ends = models.DateTimeField()
|
||||
file = models.ForeignKey(File, 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)
|
||||
fade_in = models.TimeField(blank=True, null=True)
|
||||
fade_out = models.TimeField(blank=True, null=True)
|
||||
cue_in = models.DurationField()
|
||||
cue_out = models.DurationField()
|
||||
media_item_played = models.BooleanField(blank=True, null=True)
|
||||
instance = models.ForeignKey("ShowInstance", models.DO_NOTHING)
|
||||
playout_status = models.SmallIntegerField()
|
||||
broadcasted = models.SmallIntegerField()
|
||||
position = models.IntegerField()
|
||||
|
||||
def get_owner(self):
|
||||
return self.instance.get_owner()
|
||||
|
||||
def get_cueout(self):
|
||||
"""
|
||||
Returns a scheduled item cueout that is based on the current show instance.
|
||||
|
||||
Cueout of a specific item can potentially overrun the show that it is
|
||||
scheduled in. In that case, the cueout should be the end of the show.
|
||||
This prevents the next show having overlapping items playing.
|
||||
|
||||
Cases:
|
||||
- When the schedule ends before the end of the show instance,
|
||||
return the stored cueout.
|
||||
|
||||
- When the schedule starts before the end of the show instance
|
||||
and ends after the show instance ends,
|
||||
return timedelta between schedule starts and show instance ends.
|
||||
|
||||
- When the schedule starts after the end of the show instance,
|
||||
return the stored cue_out even if the schedule WILL NOT BE PLAYED.
|
||||
"""
|
||||
if self.starts < self.instance.ends and self.instance.ends < self.ends:
|
||||
return self.instance.ends - self.starts
|
||||
return self.cue_out
|
||||
|
||||
def get_ends(self):
|
||||
"""
|
||||
Returns a scheduled item ends that is based on the current show instance.
|
||||
|
||||
End of a specific item can potentially overrun the show that it is
|
||||
scheduled in. In that case, the end should be the end of the show.
|
||||
This prevents the next show having overlapping items playing.
|
||||
|
||||
Cases:
|
||||
- When the schedule ends before the end of the show instance,
|
||||
return the scheduled item ends.
|
||||
|
||||
- When the schedule starts before the end of the show instance
|
||||
and ends after the show instance ends,
|
||||
return the show instance ends.
|
||||
|
||||
- When the schedule starts after the end of the show instance,
|
||||
return the show instance ends.
|
||||
"""
|
||||
if self.instance.ends < self.ends:
|
||||
return self.instance.ends
|
||||
return self.ends
|
||||
|
||||
@property
|
||||
def is_valid(self):
|
||||
"""
|
||||
A schedule item is valid if it starts before the end of the show instance
|
||||
it is in
|
||||
"""
|
||||
return self.starts < self.instance.ends
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
db_table = "cc_schedule"
|
||||
permissions = [
|
||||
("change_own_schedule", "Change the content on their shows"),
|
||||
("delete_own_schedule", "Delete the content on their shows"),
|
||||
]
|
10
api/libretime_api/models/services.py
Normal file
10
api/libretime_api/models/services.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
from django.db import models
|
||||
|
||||
|
||||
class ServiceRegister(models.Model):
|
||||
name = models.CharField(primary_key=True, max_length=32)
|
||||
ip = models.CharField(max_length=45)
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
db_table = "cc_service_register"
|
95
api/libretime_api/models/shows.py
Normal file
95
api/libretime_api/models/shows.py
Normal file
|
@ -0,0 +1,95 @@
|
|||
from django.db import models
|
||||
|
||||
from .files import File
|
||||
from .playlists import Playlist
|
||||
|
||||
|
||||
class Show(models.Model):
|
||||
name = models.CharField(max_length=255)
|
||||
url = models.CharField(max_length=255, blank=True, null=True)
|
||||
genre = models.CharField(max_length=255, blank=True, null=True)
|
||||
description = models.CharField(max_length=8192, blank=True, null=True)
|
||||
color = models.CharField(max_length=6, blank=True, null=True)
|
||||
background_color = models.CharField(max_length=6, blank=True, null=True)
|
||||
live_stream_using_airtime_auth = models.BooleanField(blank=True, null=True)
|
||||
live_stream_using_custom_auth = models.BooleanField(blank=True, null=True)
|
||||
live_stream_user = models.CharField(max_length=255, blank=True, null=True)
|
||||
live_stream_pass = models.CharField(max_length=255, blank=True, null=True)
|
||||
linked = models.BooleanField()
|
||||
is_linkable = models.BooleanField()
|
||||
image_path = models.CharField(max_length=255, blank=True, null=True)
|
||||
has_autoplaylist = models.BooleanField()
|
||||
autoplaylist = models.ForeignKey(Playlist, models.DO_NOTHING, blank=True, null=True)
|
||||
autoplaylist_repeat = models.BooleanField()
|
||||
|
||||
def get_owner(self):
|
||||
return self.showhost_set.all()
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
db_table = "cc_show"
|
||||
|
||||
|
||||
class ShowDays(models.Model):
|
||||
first_show = models.DateField()
|
||||
last_show = models.DateField(blank=True, null=True)
|
||||
start_time = models.TimeField()
|
||||
timezone = models.CharField(max_length=1024)
|
||||
duration = models.CharField(max_length=1024)
|
||||
day = models.SmallIntegerField(blank=True, null=True)
|
||||
repeat_type = models.SmallIntegerField()
|
||||
next_pop_date = models.DateField(blank=True, null=True)
|
||||
show = models.ForeignKey(Show, models.DO_NOTHING)
|
||||
record = models.SmallIntegerField(blank=True, null=True)
|
||||
|
||||
def get_owner(self):
|
||||
return self.show.get_owner()
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
db_table = "cc_show_days"
|
||||
|
||||
|
||||
class ShowHost(models.Model):
|
||||
show = models.ForeignKey(Show, models.DO_NOTHING)
|
||||
subjs = models.ForeignKey("User", models.DO_NOTHING)
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
db_table = "cc_show_hosts"
|
||||
|
||||
|
||||
class ShowInstance(models.Model):
|
||||
description = models.CharField(max_length=8192, blank=True, null=True)
|
||||
starts = models.DateTimeField()
|
||||
ends = models.DateTimeField()
|
||||
show = models.ForeignKey(Show, models.DO_NOTHING)
|
||||
record = models.SmallIntegerField(blank=True, null=True)
|
||||
rebroadcast = models.SmallIntegerField(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)
|
||||
time_filled = models.DurationField(blank=True, null=True)
|
||||
created = models.DateTimeField()
|
||||
last_scheduled = models.DateTimeField(blank=True, null=True)
|
||||
modified_instance = models.BooleanField()
|
||||
autoplaylist_built = models.BooleanField()
|
||||
|
||||
def get_owner(self):
|
||||
return show.get_owner()
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
db_table = "cc_show_instances"
|
||||
|
||||
|
||||
class ShowRebroadcast(models.Model):
|
||||
day_offset = models.CharField(max_length=1024)
|
||||
start_time = models.TimeField()
|
||||
show = models.ForeignKey(Show, models.DO_NOTHING)
|
||||
|
||||
def get_owner(self):
|
||||
return show.get_owner()
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
db_table = "cc_show_rebroadcast"
|
83
api/libretime_api/models/smart_blocks.py
Normal file
83
api/libretime_api/models/smart_blocks.py
Normal file
|
@ -0,0 +1,83 @@
|
|||
from django.db import models
|
||||
|
||||
|
||||
class SmartBlock(models.Model):
|
||||
name = models.CharField(max_length=255)
|
||||
mtime = models.DateTimeField(blank=True, null=True)
|
||||
utime = models.DateTimeField(blank=True, null=True)
|
||||
creator = models.ForeignKey("User", models.DO_NOTHING, blank=True, null=True)
|
||||
description = models.CharField(max_length=512, blank=True, null=True)
|
||||
length = models.DurationField(blank=True, null=True)
|
||||
type = models.CharField(max_length=7, blank=True, null=True)
|
||||
|
||||
def get_owner(self):
|
||||
return self.creator
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
db_table = "cc_block"
|
||||
permissions = [
|
||||
(
|
||||
"change_own_smartblock",
|
||||
"Change the smartblocks where they are the owner",
|
||||
),
|
||||
(
|
||||
"delete_own_smartblock",
|
||||
"Delete the smartblocks where they are the owner",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
class SmartBlockContent(models.Model):
|
||||
block = models.ForeignKey(SmartBlock, models.DO_NOTHING, blank=True, null=True)
|
||||
file = models.ForeignKey("File", models.DO_NOTHING, blank=True, null=True)
|
||||
position = models.IntegerField(blank=True, null=True)
|
||||
trackoffset = models.FloatField()
|
||||
cliplength = models.DurationField(blank=True, null=True)
|
||||
cuein = models.DurationField(blank=True, null=True)
|
||||
cueout = models.DurationField(blank=True, null=True)
|
||||
fadein = models.TimeField(blank=True, null=True)
|
||||
fadeout = models.TimeField(blank=True, null=True)
|
||||
|
||||
def get_owner(self):
|
||||
return self.block.get_owner()
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
db_table = "cc_blockcontents"
|
||||
permissions = [
|
||||
(
|
||||
"change_own_smartblockcontent",
|
||||
"Change the content of smartblocks where they are the owner",
|
||||
),
|
||||
(
|
||||
"delete_own_smartblockcontent",
|
||||
"Delete the content of smartblocks where they are the owner",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
class SmartBlockCriteria(models.Model):
|
||||
criteria = models.CharField(max_length=32)
|
||||
modifier = models.CharField(max_length=16)
|
||||
value = models.CharField(max_length=512)
|
||||
extra = models.CharField(max_length=512, blank=True, null=True)
|
||||
criteriagroup = models.IntegerField(blank=True, null=True)
|
||||
block = models.ForeignKey(SmartBlock, models.DO_NOTHING)
|
||||
|
||||
def get_owner(self):
|
||||
return self.block.get_owner()
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
db_table = "cc_blockcriteria"
|
||||
permissions = [
|
||||
(
|
||||
"change_own_smartblockcriteria",
|
||||
"Change the criteria of smartblocks where they are the owner",
|
||||
),
|
||||
(
|
||||
"delete_own_smartblockcriteria",
|
||||
"Delete the criteria of smartblocks where they are the owner",
|
||||
),
|
||||
]
|
26
api/libretime_api/models/tracks.py
Normal file
26
api/libretime_api/models/tracks.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
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"
|
11
api/libretime_api/models/user_constants.py
Normal file
11
api/libretime_api/models/user_constants.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
GUEST = "G"
|
||||
DJ = "H"
|
||||
PROGRAM_MANAGER = "P"
|
||||
ADMIN = "A"
|
||||
|
||||
USER_TYPES = {
|
||||
GUEST: "Guest",
|
||||
DJ: "DJ",
|
||||
PROGRAM_MANAGER: "Program Manager",
|
||||
ADMIN: "Admin",
|
||||
}
|
41
api/libretime_api/models/webstreams.py
Normal file
41
api/libretime_api/models/webstreams.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
from django.contrib.auth import get_user_model
|
||||
from django.db import models
|
||||
|
||||
from .schedule import Schedule
|
||||
|
||||
|
||||
class Webstream(models.Model):
|
||||
name = models.CharField(max_length=255)
|
||||
description = models.CharField(max_length=255)
|
||||
url = models.CharField(max_length=512)
|
||||
length = models.DurationField()
|
||||
creator_id = models.IntegerField()
|
||||
mtime = models.DateTimeField()
|
||||
utime = models.DateTimeField()
|
||||
lptime = models.DateTimeField(blank=True, null=True)
|
||||
mime = models.CharField(max_length=1024, blank=True, null=True)
|
||||
|
||||
def get_owner(self):
|
||||
User = get_user_model()
|
||||
return User.objects.get(pk=self.creator_id)
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
db_table = "cc_webstream"
|
||||
permissions = [
|
||||
("change_own_webstream", "Change the webstreams where they are the owner"),
|
||||
("delete_own_webstream", "Delete the webstreams where they are the owner"),
|
||||
]
|
||||
|
||||
|
||||
class WebstreamMetadata(models.Model):
|
||||
instance = models.ForeignKey(Schedule, models.DO_NOTHING)
|
||||
start_time = models.DateTimeField()
|
||||
liquidsoap_data = models.CharField(max_length=1024)
|
||||
|
||||
def get_owner(self):
|
||||
return self.instance.get_owner()
|
||||
|
||||
class Meta:
|
||||
managed = False
|
||||
db_table = "cc_webstream_metadata"
|
109
api/libretime_api/permission_constants.py
Normal file
109
api/libretime_api/permission_constants.py
Normal file
|
@ -0,0 +1,109 @@
|
|||
import logging
|
||||
|
||||
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 = [
|
||||
"view_schedule",
|
||||
"view_show",
|
||||
"view_showdays",
|
||||
"view_showhost",
|
||||
"view_showinstance",
|
||||
"view_showrebroadcast",
|
||||
"view_file",
|
||||
"view_podcast",
|
||||
"view_podcastepisode",
|
||||
"view_playlist",
|
||||
"view_playlistcontent",
|
||||
"view_smartblock",
|
||||
"view_smartblockcontent",
|
||||
"view_smartblockcriteria",
|
||||
"view_webstream",
|
||||
"view_apiroot",
|
||||
]
|
||||
DJ_PERMISSIONS = GUEST_PERMISSIONS + [
|
||||
"add_file",
|
||||
"add_podcast",
|
||||
"add_podcastepisode",
|
||||
"add_playlist",
|
||||
"add_playlistcontent",
|
||||
"add_smartblock",
|
||||
"add_smartblockcontent",
|
||||
"add_smartblockcriteria",
|
||||
"add_webstream",
|
||||
"change_own_schedule",
|
||||
"change_own_file",
|
||||
"change_own_podcast",
|
||||
"change_own_podcastepisode",
|
||||
"change_own_playlist",
|
||||
"change_own_playlistcontent",
|
||||
"change_own_smartblock",
|
||||
"change_own_smartblockcontent",
|
||||
"change_own_smartblockcriteria",
|
||||
"change_own_webstream",
|
||||
"delete_own_schedule",
|
||||
"delete_own_file",
|
||||
"delete_own_podcast",
|
||||
"delete_own_podcastepisode",
|
||||
"delete_own_playlist",
|
||||
"delete_own_playlistcontent",
|
||||
"delete_own_smartblock",
|
||||
"delete_own_smartblockcontent",
|
||||
"delete_own_smartblockcriteria",
|
||||
"delete_own_webstream",
|
||||
]
|
||||
PROGRAM_MANAGER_PERMISSIONS = GUEST_PERMISSIONS + [
|
||||
"add_show",
|
||||
"add_showdays",
|
||||
"add_showhost",
|
||||
"add_showinstance",
|
||||
"add_showrebroadcast",
|
||||
"add_file",
|
||||
"add_podcast",
|
||||
"add_podcastepisode",
|
||||
"add_playlist",
|
||||
"add_playlistcontent",
|
||||
"add_smartblock",
|
||||
"add_smartblockcontent",
|
||||
"add_smartblockcriteria",
|
||||
"add_webstream",
|
||||
"change_schedule",
|
||||
"change_show",
|
||||
"change_showdays",
|
||||
"change_showhost",
|
||||
"change_showinstance",
|
||||
"change_showrebroadcast",
|
||||
"change_file",
|
||||
"change_podcast",
|
||||
"change_podcastepisode",
|
||||
"change_playlist",
|
||||
"change_playlistcontent",
|
||||
"change_smartblock",
|
||||
"change_smartblockcontent",
|
||||
"change_smartblockcriteria",
|
||||
"change_webstream",
|
||||
"delete_schedule",
|
||||
"delete_show",
|
||||
"delete_showdays",
|
||||
"delete_showhost",
|
||||
"delete_showinstance",
|
||||
"delete_showrebroadcast",
|
||||
"delete_file",
|
||||
"delete_podcast",
|
||||
"delete_podcastepisode",
|
||||
"delete_playlist",
|
||||
"delete_playlistcontent",
|
||||
"delete_smartblock",
|
||||
"delete_smartblockcontent",
|
||||
"delete_smartblockcriteria",
|
||||
"delete_webstream",
|
||||
]
|
||||
|
||||
GROUPS = {
|
||||
GUEST: GUEST_PERMISSIONS,
|
||||
DJ: DJ_PERMISSIONS,
|
||||
PROGRAM_MANAGER: PROGRAM_MANAGER,
|
||||
}
|
108
api/libretime_api/permissions.py
Normal file
108
api/libretime_api/permissions.py
Normal file
|
@ -0,0 +1,108 @@
|
|||
from django.conf import settings
|
||||
from rest_framework.permissions import BasePermission
|
||||
|
||||
from .models.user_constants import DJ
|
||||
|
||||
REQUEST_PERMISSION_TYPE_MAP = {
|
||||
"GET": "view",
|
||||
"HEAD": "view",
|
||||
"OPTIONS": "view",
|
||||
"POST": "change",
|
||||
"PUT": "change",
|
||||
"DELETE": "delete",
|
||||
"PATCH": "change",
|
||||
}
|
||||
|
||||
|
||||
def get_own_obj(request, view):
|
||||
user = request.user
|
||||
if user is None or user.type != DJ:
|
||||
return ""
|
||||
if request.method == "GET":
|
||||
return ""
|
||||
qs = view.queryset.all()
|
||||
try:
|
||||
model_owners = []
|
||||
for model in qs:
|
||||
owner = model.get_owner()
|
||||
if owner not in model_owners:
|
||||
model_owners.append(owner)
|
||||
if len(model_owners) == 1 and user in model_owners:
|
||||
return "own_"
|
||||
except AttributeError:
|
||||
return ""
|
||||
return ""
|
||||
|
||||
|
||||
def get_permission_for_view(request, view):
|
||||
try:
|
||||
permission_type = REQUEST_PERMISSION_TYPE_MAP[request.method]
|
||||
if view.__class__.__name__ == "APIRootView":
|
||||
return "{}_apiroot".format(permission_type)
|
||||
model = view.model_permission_name
|
||||
own_obj = get_own_obj(request, view)
|
||||
return "{permission_type}_{own_obj}{model}".format(
|
||||
permission_type=permission_type, own_obj=own_obj, model=model
|
||||
)
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
|
||||
def check_authorization_header(request):
|
||||
auth_header = request.META.get("Authorization")
|
||||
if not auth_header:
|
||||
auth_header = request.META.get("HTTP_AUTHORIZATION", "")
|
||||
|
||||
if auth_header.startswith("Api-Key"):
|
||||
token = auth_header.split()[1]
|
||||
if token == settings.CONFIG.get("general", "api_key"):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class IsAdminOrOwnUser(BasePermission):
|
||||
"""
|
||||
Implements Django Rest Framework permissions. This is separate from
|
||||
Django's standard permission system. For details see
|
||||
https://www.django-rest-framework.org/api-guide/permissions/#custom-permissions
|
||||
"""
|
||||
|
||||
def has_permission(self, request, view):
|
||||
if request.user.is_superuser:
|
||||
return True
|
||||
return False
|
||||
|
||||
def has_object_permission(self, request, view, obj):
|
||||
if request.user.is_superuser:
|
||||
return True
|
||||
return obj.username == request.user
|
||||
|
||||
|
||||
class IsSystemTokenOrUser(BasePermission):
|
||||
"""
|
||||
Implements Django Rest Framework permissions. This is separate from
|
||||
Django's standard permission system. For details see
|
||||
https://www.django-rest-framework.org/api-guide/permissions/#custom-permissions
|
||||
|
||||
This permission allows services (liquidsoap, 3rd-party, etc) to connect with
|
||||
an API-Key header. All standard-users (i.e. not using the API-Key) have their
|
||||
permissions checked against Django's standard permission system.
|
||||
"""
|
||||
|
||||
def has_permission(self, request, view):
|
||||
if request.user and request.user.is_authenticated:
|
||||
perm = get_permission_for_view(request, view)
|
||||
# Required as view_apiroot is a permission not linked to a specific
|
||||
# model. This use-case allows users to view the base of the API
|
||||
# explorer. Their assigned group permissions determine further access
|
||||
# into the explorer.
|
||||
if perm == "view_apiroot":
|
||||
return True
|
||||
return request.user.has_perm(perm)
|
||||
return check_authorization_header(request)
|
||||
|
||||
def has_object_permission(self, request, view, obj):
|
||||
if request.user and request.user.is_authenticated:
|
||||
perm = get_permission_for_view(request, view)
|
||||
return request.user.has_perm(perm, obj)
|
||||
return check_authorization_header(request)
|
312
api/libretime_api/serializers.py
Normal file
312
api/libretime_api/serializers.py
Normal file
|
@ -0,0 +1,312 @@
|
|||
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):
|
||||
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):
|
||||
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__"
|
194
api/libretime_api/settings.py
Normal file
194
api/libretime_api/settings.py
Normal file
|
@ -0,0 +1,194 @@
|
|||
import configparser
|
||||
import os
|
||||
import sys
|
||||
|
||||
from .utils import get_random_string, read_config_file
|
||||
|
||||
LIBRETIME_CONF_DIR = os.getenv("LIBRETIME_CONF_DIR", "/etc/airtime")
|
||||
DEFAULT_CONFIG_PATH = os.getenv(
|
||||
"LIBRETIME_CONF_FILE", os.path.join(LIBRETIME_CONF_DIR, "airtime.conf")
|
||||
)
|
||||
API_VERSION = "2.0.0"
|
||||
|
||||
try:
|
||||
CONFIG = read_config_file(DEFAULT_CONFIG_PATH)
|
||||
except IOError as e:
|
||||
print(f"Unable to read config file {DEFAULT_CONFIG_PATH}", file=sys.stderr)
|
||||
print(e, file=sys.stderr)
|
||||
CONFIG = configparser.ConfigParser()
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = get_random_string(CONFIG.get("general", "api_key", fallback=""))
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = os.getenv("LIBRETIME_DEBUG", False)
|
||||
|
||||
ALLOWED_HOSTS = ["*"]
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
"libretimeapi.apps.LibreTimeAPIConfig",
|
||||
"django.contrib.admin",
|
||||
"django.contrib.auth",
|
||||
"django.contrib.contenttypes",
|
||||
"django.contrib.sessions",
|
||||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
"rest_framework",
|
||||
"django_filters",
|
||||
"drf_spectacular",
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||
"django.middleware.common.CommonMiddleware",
|
||||
"django.middleware.csrf.CsrfViewMiddleware",
|
||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||
"django.contrib.messages.middleware.MessageMiddleware",
|
||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||
]
|
||||
|
||||
ROOT_URLCONF = "libretimeapi.urls"
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
"DIRS": [],
|
||||
"APP_DIRS": True,
|
||||
"OPTIONS": {
|
||||
"context_processors": [
|
||||
"django.template.context_processors.debug",
|
||||
"django.template.context_processors.request",
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = "libretimeapi.wsgi.application"
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
"default": {
|
||||
"ENGINE": "django.db.backends.postgresql",
|
||||
"NAME": CONFIG.get("database", "dbname", fallback=""),
|
||||
"USER": CONFIG.get("database", "dbuser", fallback=""),
|
||||
"PASSWORD": CONFIG.get("database", "dbpass", fallback=""),
|
||||
"HOST": CONFIG.get("database", "host", fallback=""),
|
||||
"PORT": "5432",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
||||
},
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
||||
},
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
|
||||
},
|
||||
{
|
||||
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
|
||||
},
|
||||
]
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
"DEFAULT_AUTHENTICATION_CLASSES": (
|
||||
"rest_framework.authentication.SessionAuthentication",
|
||||
"rest_framework.authentication.BasicAuthentication",
|
||||
),
|
||||
"DEFAULT_PERMISSION_CLASSES": [
|
||||
"libretimeapi.permissions.IsSystemTokenOrUser",
|
||||
],
|
||||
"DEFAULT_FILTER_BACKENDS": [
|
||||
"django_filters.rest_framework.DjangoFilterBackend",
|
||||
],
|
||||
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
|
||||
"URL_FIELD_NAME": "item_url",
|
||||
}
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/3.0/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = "en-us"
|
||||
|
||||
TIME_ZONE = "UTC"
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_L10N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/3.0/howto/static-files/
|
||||
|
||||
STATIC_URL = "/api/static/"
|
||||
if not DEBUG:
|
||||
STATIC_ROOT = os.getenv("LIBRETIME_STATIC_ROOT", "/usr/share/airtime/api")
|
||||
|
||||
AUTH_USER_MODEL = "libretimeapi.User"
|
||||
|
||||
TEST_RUNNER = "libretimeapi.tests.runners.ManagedModelTestRunner"
|
||||
|
||||
LOGGING = {
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
"formatters": {
|
||||
"simple": {
|
||||
"format": "{levelname} {message}",
|
||||
"style": "{",
|
||||
},
|
||||
"verbose": {
|
||||
"format": "{asctime} {module} {levelname} {message}",
|
||||
"style": "{",
|
||||
},
|
||||
},
|
||||
"handlers": {
|
||||
"file": {
|
||||
"level": "DEBUG",
|
||||
"class": "logging.FileHandler",
|
||||
"filename": os.path.join(
|
||||
CONFIG.get("pypo", "log_base_dir", fallback=".").replace("'", ""),
|
||||
"api.log",
|
||||
),
|
||||
"formatter": "verbose",
|
||||
},
|
||||
"console": {
|
||||
"level": "INFO",
|
||||
"class": "logging.StreamHandler",
|
||||
"formatter": "simple",
|
||||
},
|
||||
},
|
||||
"loggers": {
|
||||
"django": {
|
||||
"handlers": ["file", "console"],
|
||||
"level": "INFO",
|
||||
"propagate": True,
|
||||
},
|
||||
"libretimeapi": {
|
||||
"handlers": ["file", "console"],
|
||||
"level": "INFO",
|
||||
"propagate": True,
|
||||
},
|
||||
},
|
||||
}
|
0
api/libretime_api/tests/__init__.py
Normal file
0
api/libretime_api/tests/__init__.py
Normal file
0
api/libretime_api/tests/models/__init__.py
Normal file
0
api/libretime_api/tests/models/__init__.py
Normal file
57
api/libretime_api/tests/models/test_schedule.py
Normal file
57
api/libretime_api/tests/models/test_schedule.py
Normal file
|
@ -0,0 +1,57 @@
|
|||
from datetime import datetime, timedelta
|
||||
|
||||
from django.test import TestCase
|
||||
|
||||
from libretimeapi.models import Schedule, ShowInstance
|
||||
|
||||
|
||||
class TestSchedule(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.show_instance = ShowInstance(
|
||||
created=datetime(year=2021, month=10, day=1, hour=12),
|
||||
starts=datetime(year=2021, month=10, day=2, hour=1),
|
||||
ends=datetime(year=2021, month=10, day=2, hour=2),
|
||||
)
|
||||
cls.length = timedelta(minutes=10)
|
||||
cls.cue_in = timedelta(seconds=1)
|
||||
cls.cue_out = cls.length - timedelta(seconds=4)
|
||||
|
||||
def create_schedule(self, starts):
|
||||
return Schedule(
|
||||
starts=starts,
|
||||
ends=starts + self.length,
|
||||
cue_in=self.cue_in,
|
||||
cue_out=self.cue_out,
|
||||
instance=self.show_instance,
|
||||
)
|
||||
|
||||
def test_get_cueout(self):
|
||||
# No overlapping schedule datetimes, normal usecase:
|
||||
s1_starts = datetime(year=2021, month=10, day=2, hour=1, minute=30)
|
||||
s1 = self.create_schedule(s1_starts)
|
||||
self.assertEqual(s1.get_cueout(), self.cue_out)
|
||||
self.assertEqual(s1.get_ends(), s1_starts + self.length)
|
||||
|
||||
# Mixed overlapping schedule datetimes (only ends is overlapping):
|
||||
s2_starts = datetime(year=2021, month=10, day=2, hour=1, minute=55)
|
||||
s2 = self.create_schedule(s2_starts)
|
||||
self.assertEqual(s2.get_cueout(), timedelta(minutes=5))
|
||||
self.assertEqual(s2.get_ends(), self.show_instance.ends)
|
||||
|
||||
# Fully overlapping schedule datetimes (starts and ends are overlapping):
|
||||
s3_starts = datetime(year=2021, month=10, day=2, hour=2, minute=1)
|
||||
s3 = self.create_schedule(s3_starts)
|
||||
self.assertEqual(s3.get_cueout(), self.cue_out)
|
||||
self.assertEqual(s3.get_ends(), self.show_instance.ends)
|
||||
|
||||
def test_is_valid(self):
|
||||
# Starts before the schedule ends
|
||||
s1_starts = datetime(year=2021, month=10, day=2, hour=1, minute=30)
|
||||
s1 = self.create_schedule(s1_starts)
|
||||
self.assertTrue(s1.is_valid)
|
||||
|
||||
# Starts after the schedule ends
|
||||
s2_starts = datetime(year=2021, month=10, day=2, hour=3)
|
||||
s2 = self.create_schedule(s2_starts)
|
||||
self.assertFalse(s2.is_valid)
|
BIN
api/libretime_api/tests/resources/song.mp3
Normal file
BIN
api/libretime_api/tests/resources/song.mp3
Normal file
Binary file not shown.
23
api/libretime_api/tests/runners.py
Normal file
23
api/libretime_api/tests/runners.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
from django.test.runner import DiscoverRunner
|
||||
|
||||
|
||||
class ManagedModelTestRunner(DiscoverRunner):
|
||||
"""
|
||||
Test runner that automatically makes all unmanaged models in your Django
|
||||
project managed for the duration of the test run, so that one doesn't need
|
||||
to execute the SQL manually to create them.
|
||||
"""
|
||||
|
||||
def setup_test_environment(self, *args, **kwargs):
|
||||
from django.apps import apps
|
||||
|
||||
self.unmanaged_models = [m for m in apps.get_models() if not m._meta.managed]
|
||||
for m in self.unmanaged_models:
|
||||
m._meta.managed = True
|
||||
super(ManagedModelTestRunner, self).setup_test_environment(*args, **kwargs)
|
||||
|
||||
def teardown_test_environment(self, *args, **kwargs):
|
||||
super(ManagedModelTestRunner, self).teardown_test_environment(*args, **kwargs)
|
||||
# reset unmanaged models
|
||||
for m in self.unmanaged_models:
|
||||
m._meta.managed = False
|
48
api/libretime_api/tests/test_models.py
Normal file
48
api/libretime_api/tests/test_models.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
from django.apps import apps
|
||||
from django.contrib.auth.models import Group
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from libretimeapi.models import User
|
||||
from libretimeapi.models.user_constants import DJ, GUEST
|
||||
from libretimeapi.permission_constants import GROUPS
|
||||
|
||||
|
||||
class TestUserManager(APITestCase):
|
||||
def test_create_user(self):
|
||||
user = User.objects.create_user(
|
||||
"test",
|
||||
email="test@example.com",
|
||||
password="test",
|
||||
type=DJ,
|
||||
first_name="test",
|
||||
last_name="user",
|
||||
)
|
||||
db_user = User.objects.get(pk=user.pk)
|
||||
self.assertEqual(db_user.username, user.username)
|
||||
|
||||
def test_create_superuser(self):
|
||||
user = User.objects.create_superuser(
|
||||
"test",
|
||||
email="test@example.com",
|
||||
password="test",
|
||||
first_name="test",
|
||||
last_name="user",
|
||||
)
|
||||
db_user = User.objects.get(pk=user.pk)
|
||||
self.assertEqual(db_user.username, user.username)
|
||||
|
||||
|
||||
class TestUser(APITestCase):
|
||||
def test_guest_get_group_perms(self):
|
||||
user = User.objects.create_user(
|
||||
"test",
|
||||
email="test@example.com",
|
||||
password="test",
|
||||
type=GUEST,
|
||||
first_name="test",
|
||||
last_name="user",
|
||||
)
|
||||
permissions = user.get_group_permissions()
|
||||
# APIRoot permission hardcoded in the check as it isn't a Permission object
|
||||
str_perms = [p.codename for p in permissions] + ["view_apiroot"]
|
||||
self.assertCountEqual(str_perms, GROUPS[GUEST])
|
133
api/libretime_api/tests/test_permissions.py
Normal file
133
api/libretime_api/tests/test_permissions.py
Normal file
|
@ -0,0 +1,133 @@
|
|||
import os
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from model_bakery import baker
|
||||
from rest_framework.test import APIRequestFactory, APITestCase
|
||||
|
||||
from libretimeapi.models.user_constants import ADMIN, DJ, GUEST, PROGRAM_MANAGER
|
||||
from libretimeapi.permission_constants import (
|
||||
DJ_PERMISSIONS,
|
||||
GUEST_PERMISSIONS,
|
||||
PROGRAM_MANAGER_PERMISSIONS,
|
||||
)
|
||||
from libretimeapi.permissions import IsSystemTokenOrUser
|
||||
|
||||
|
||||
class TestIsSystemTokenOrUser(APITestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.path = "/api/v2/files/"
|
||||
|
||||
def test_unauthorized(self):
|
||||
response = self.client.get(self.path.format("files"))
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_token_incorrect(self):
|
||||
token = "doesnotexist"
|
||||
request = APIRequestFactory().get(self.path)
|
||||
request.user = AnonymousUser()
|
||||
request.META["Authorization"] = "Api-Key {token}".format(token=token)
|
||||
allowed = IsSystemTokenOrUser().has_permission(request, None)
|
||||
self.assertFalse(allowed)
|
||||
|
||||
def test_token_correct(self):
|
||||
token = settings.CONFIG.get("general", "api_key")
|
||||
request = APIRequestFactory().get(self.path)
|
||||
request.user = AnonymousUser()
|
||||
request.META["Authorization"] = "Api-Key {token}".format(token=token)
|
||||
allowed = IsSystemTokenOrUser().has_permission(request, None)
|
||||
self.assertTrue(allowed)
|
||||
|
||||
|
||||
class TestPermissions(APITestCase):
|
||||
URLS = [
|
||||
"schedule",
|
||||
"shows",
|
||||
"show-days",
|
||||
"show-hosts",
|
||||
"show-instances",
|
||||
"show-rebroadcasts",
|
||||
"files",
|
||||
"playlists",
|
||||
"playlist-contents",
|
||||
"smart-blocks",
|
||||
"smart-block-contents",
|
||||
"smart-block-criteria",
|
||||
"webstreams",
|
||||
]
|
||||
|
||||
def logged_in_test_model(self, model, name, user_type, fn):
|
||||
path = self.path.format(model)
|
||||
user_created = get_user_model().objects.filter(username=name)
|
||||
if not user_created:
|
||||
user = get_user_model().objects.create_user(
|
||||
name,
|
||||
email="test@example.com",
|
||||
password="test",
|
||||
type=user_type,
|
||||
first_name="test",
|
||||
last_name="user",
|
||||
)
|
||||
self.client.login(username=name, password="test")
|
||||
return fn(path)
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.path = "/api/v2/{}/"
|
||||
|
||||
def test_guest_permissions_success(self):
|
||||
for model in self.URLS:
|
||||
response = self.logged_in_test_model(model, "guest", GUEST, self.client.get)
|
||||
self.assertEqual(
|
||||
response.status_code, 200, msg="Invalid for model {}".format(model)
|
||||
)
|
||||
|
||||
def test_guest_permissions_failure(self):
|
||||
for model in self.URLS:
|
||||
response = self.logged_in_test_model(
|
||||
model, "guest", GUEST, self.client.post
|
||||
)
|
||||
self.assertEqual(
|
||||
response.status_code, 403, msg="Invalid for model {}".format(model)
|
||||
)
|
||||
|
||||
def test_dj_get_permissions(self):
|
||||
for model in self.URLS:
|
||||
response = self.logged_in_test_model(model, "dj", DJ, self.client.get)
|
||||
self.assertEqual(
|
||||
response.status_code, 200, msg="Invalid for model {}".format(model)
|
||||
)
|
||||
|
||||
def test_dj_post_permissions(self):
|
||||
user = get_user_model().objects.create_user(
|
||||
"test-dj",
|
||||
email="test@example.com",
|
||||
password="test",
|
||||
type=DJ,
|
||||
first_name="test",
|
||||
last_name="user",
|
||||
)
|
||||
f = baker.make("libretimeapi.File", owner=user)
|
||||
model = "files/{}".format(f.id)
|
||||
path = self.path.format(model)
|
||||
self.client.login(username="test-dj", password="test")
|
||||
response = self.client.patch(path, {"name": "newFilename"})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_dj_post_permissions_failure(self):
|
||||
user = get_user_model().objects.create_user(
|
||||
"test-dj",
|
||||
email="test@example.com",
|
||||
password="test",
|
||||
type=DJ,
|
||||
first_name="test",
|
||||
last_name="user",
|
||||
)
|
||||
f = baker.make("libretimeapi.File")
|
||||
model = "files/{}".format(f.id)
|
||||
path = self.path.format(model)
|
||||
self.client.login(username="test-dj", password="test")
|
||||
response = self.client.patch(path, {"name": "newFilename"})
|
||||
self.assertEqual(response.status_code, 403)
|
220
api/libretime_api/tests/test_views.py
Normal file
220
api/libretime_api/tests/test_views.py
Normal file
|
@ -0,0 +1,220 @@
|
|||
import os
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.utils import dateparse
|
||||
from model_bakery import baker
|
||||
from rest_framework.test import APIRequestFactory, APITestCase
|
||||
|
||||
from libretimeapi.views import FileViewSet
|
||||
|
||||
|
||||
class TestFileViewSet(APITestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.path = "/api/v2/files/{id}/download/"
|
||||
cls.token = settings.CONFIG.get("general", "api_key")
|
||||
|
||||
def test_invalid(self):
|
||||
path = self.path.format(id="a")
|
||||
self.client.credentials(HTTP_AUTHORIZATION="Api-Key {}".format(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="Api-Key {}".format(self.token))
|
||||
response = self.client.get(path)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_exists(self):
|
||||
music_dir = baker.make(
|
||||
"libretimeapi.MusicDir",
|
||||
directory=os.path.join(os.path.dirname(__file__), "resources"),
|
||||
)
|
||||
f = baker.make(
|
||||
"libretimeapi.File",
|
||||
directory=music_dir,
|
||||
mime="audio/mp3",
|
||||
filepath="song.mp3",
|
||||
)
|
||||
path = self.path.format(id=str(f.pk))
|
||||
self.client.credentials(HTTP_AUTHORIZATION="Api-Key {}".format(self.token))
|
||||
response = self.client.get(path)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
class TestScheduleViewSet(APITestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.path = "/api/v2/schedule/"
|
||||
cls.token = settings.CONFIG.get("general", "api_key")
|
||||
|
||||
def test_schedule_item_full_length(self):
|
||||
music_dir = baker.make(
|
||||
"libretimeapi.MusicDir",
|
||||
directory=os.path.join(os.path.dirname(__file__), "resources"),
|
||||
)
|
||||
f = baker.make(
|
||||
"libretimeapi.File",
|
||||
directory=music_dir,
|
||||
mime="audio/mp3",
|
||||
filepath="song.mp3",
|
||||
length=timedelta(seconds=40.86),
|
||||
cuein=timedelta(seconds=0),
|
||||
cueout=timedelta(seconds=40.8131),
|
||||
)
|
||||
show = baker.make(
|
||||
"libretimeapi.ShowInstance",
|
||||
starts=datetime.now(tz=timezone.utc) - timedelta(minutes=5),
|
||||
ends=datetime.now(tz=timezone.utc) + timedelta(minutes=5),
|
||||
)
|
||||
scheduleItem = baker.make(
|
||||
"libretimeapi.Schedule",
|
||||
starts=datetime.now(tz=timezone.utc),
|
||||
ends=datetime.now(tz=timezone.utc) + f.length,
|
||||
cue_out=f.cueout,
|
||||
instance=show,
|
||||
file=f,
|
||||
)
|
||||
self.client.credentials(HTTP_AUTHORIZATION="Api-Key {}".format(self.token))
|
||||
response = self.client.get(self.path)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
result = response.json()
|
||||
self.assertEqual(dateparse.parse_datetime(result[0]["ends"]), scheduleItem.ends)
|
||||
self.assertEqual(dateparse.parse_duration(result[0]["cue_out"]), f.cueout)
|
||||
|
||||
def test_schedule_item_trunc(self):
|
||||
music_dir = baker.make(
|
||||
"libretimeapi.MusicDir",
|
||||
directory=os.path.join(os.path.dirname(__file__), "resources"),
|
||||
)
|
||||
f = baker.make(
|
||||
"libretimeapi.File",
|
||||
directory=music_dir,
|
||||
mime="audio/mp3",
|
||||
filepath="song.mp3",
|
||||
length=timedelta(seconds=40.86),
|
||||
cuein=timedelta(seconds=0),
|
||||
cueout=timedelta(seconds=40.8131),
|
||||
)
|
||||
show = baker.make(
|
||||
"libretimeapi.ShowInstance",
|
||||
starts=datetime.now(tz=timezone.utc) - timedelta(minutes=5),
|
||||
ends=datetime.now(tz=timezone.utc) + timedelta(seconds=20),
|
||||
)
|
||||
scheduleItem = baker.make(
|
||||
"libretimeapi.Schedule",
|
||||
starts=datetime.now(tz=timezone.utc),
|
||||
ends=datetime.now(tz=timezone.utc) + f.length,
|
||||
instance=show,
|
||||
file=f,
|
||||
)
|
||||
self.client.credentials(HTTP_AUTHORIZATION="Api-Key {}".format(self.token))
|
||||
response = self.client.get(self.path)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
result = response.json()
|
||||
self.assertEqual(dateparse.parse_datetime(result[0]["ends"]), show.ends)
|
||||
expected = show.ends - scheduleItem.starts
|
||||
self.assertEqual(dateparse.parse_duration(result[0]["cue_out"]), expected)
|
||||
self.assertNotEqual(
|
||||
dateparse.parse_datetime(result[0]["ends"]), scheduleItem.ends
|
||||
)
|
||||
|
||||
def test_schedule_item_invalid(self):
|
||||
music_dir = baker.make(
|
||||
"libretimeapi.MusicDir",
|
||||
directory=os.path.join(os.path.dirname(__file__), "resources"),
|
||||
)
|
||||
f = baker.make(
|
||||
"libretimeapi.File",
|
||||
directory=music_dir,
|
||||
mime="audio/mp3",
|
||||
filepath="song.mp3",
|
||||
length=timedelta(seconds=40.86),
|
||||
cuein=timedelta(seconds=0),
|
||||
cueout=timedelta(seconds=40.8131),
|
||||
)
|
||||
show = baker.make(
|
||||
"libretimeapi.ShowInstance",
|
||||
starts=datetime.now(tz=timezone.utc) - timedelta(minutes=5),
|
||||
ends=datetime.now(tz=timezone.utc) + timedelta(minutes=5),
|
||||
)
|
||||
scheduleItem = baker.make(
|
||||
"libretimeapi.Schedule",
|
||||
starts=datetime.now(tz=timezone.utc),
|
||||
ends=datetime.now(tz=timezone.utc) + f.length,
|
||||
cue_out=f.cueout,
|
||||
instance=show,
|
||||
file=f,
|
||||
)
|
||||
invalidScheduleItem = baker.make(
|
||||
"libretimeapi.Schedule",
|
||||
starts=show.ends + timedelta(minutes=1),
|
||||
ends=show.ends + timedelta(minutes=1) + f.length,
|
||||
cue_out=f.cueout,
|
||||
instance=show,
|
||||
file=f,
|
||||
)
|
||||
self.client.credentials(HTTP_AUTHORIZATION="Api-Key {}".format(self.token))
|
||||
response = self.client.get(self.path, {"is_valid": True})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
result = response.json()
|
||||
# The invalid item should be filtered out and not returned
|
||||
self.assertEqual(len(result), 1)
|
||||
self.assertEqual(dateparse.parse_datetime(result[0]["ends"]), scheduleItem.ends)
|
||||
self.assertEqual(dateparse.parse_duration(result[0]["cue_out"]), f.cueout)
|
||||
|
||||
def test_schedule_item_range(self):
|
||||
music_dir = baker.make(
|
||||
"libretimeapi.MusicDir",
|
||||
directory=os.path.join(os.path.dirname(__file__), "resources"),
|
||||
)
|
||||
f = baker.make(
|
||||
"libretimeapi.File",
|
||||
directory=music_dir,
|
||||
mime="audio/mp3",
|
||||
filepath="song.mp3",
|
||||
length=timedelta(seconds=40.86),
|
||||
cuein=timedelta(seconds=0),
|
||||
cueout=timedelta(seconds=40.8131),
|
||||
)
|
||||
filter_point = datetime.now(tz=timezone.utc)
|
||||
|
||||
show = baker.make(
|
||||
"libretimeapi.ShowInstance",
|
||||
starts=filter_point - timedelta(minutes=5),
|
||||
ends=filter_point + timedelta(minutes=5),
|
||||
)
|
||||
schedule_item = baker.make(
|
||||
"libretimeapi.Schedule",
|
||||
starts=filter_point,
|
||||
ends=filter_point + f.length,
|
||||
cue_out=f.cueout,
|
||||
instance=show,
|
||||
file=f,
|
||||
)
|
||||
previous_item = baker.make(
|
||||
"libretimeapi.Schedule",
|
||||
starts=filter_point - timedelta(minutes=5),
|
||||
ends=filter_point - timedelta(minutes=5) + f.length,
|
||||
cue_out=f.cueout,
|
||||
instance=show,
|
||||
file=f,
|
||||
)
|
||||
self.client.credentials(HTTP_AUTHORIZATION="Api-Key {}".format(self.token))
|
||||
range_start = (filter_point - timedelta(minutes=1)).isoformat(
|
||||
timespec="seconds"
|
||||
)
|
||||
range_end = (filter_point + timedelta(minutes=1)).isoformat(timespec="seconds")
|
||||
response = self.client.get(
|
||||
self.path, {"starts__range": "{},{}".format(range_start, range_end)}
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
result = response.json()
|
||||
# The previous_item should be filtered out and not returned
|
||||
self.assertEqual(len(result), 1)
|
||||
self.assertEqual(
|
||||
dateparse.parse_datetime(result[0]["starts"]), schedule_item.starts
|
||||
)
|
58
api/libretime_api/urls.py
Normal file
58
api/libretime_api/urls.py
Normal file
|
@ -0,0 +1,58 @@
|
|||
from django.urls import include, path
|
||||
from drf_spectacular.views import SpectacularAPIView, SpectacularSwaggerView
|
||||
from rest_framework import routers
|
||||
|
||||
from .views import *
|
||||
|
||||
router = routers.DefaultRouter()
|
||||
router.register("smart-blocks", SmartBlockViewSet)
|
||||
router.register("smart-block-contents", SmartBlockContentViewSet)
|
||||
router.register("smart-block-criteria", SmartBlockCriteriaViewSet)
|
||||
router.register("countries", CountryViewSet)
|
||||
router.register("files", FileViewSet)
|
||||
router.register("listener-counts", ListenerCountViewSet)
|
||||
router.register("live-logs", LiveLogViewSet)
|
||||
router.register("login-attempts", LoginAttemptViewSet)
|
||||
router.register("mount-names", MountNameViewSet)
|
||||
router.register("music-dirs", MusicDirViewSet)
|
||||
router.register("playlists", PlaylistViewSet)
|
||||
router.register("playlist-contents", PlaylistContentViewSet)
|
||||
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("preferences", PreferenceViewSet)
|
||||
router.register("schedule", ScheduleViewSet)
|
||||
router.register("service-registers", ServiceRegisterViewSet)
|
||||
router.register("sessions", SessionViewSet)
|
||||
router.register("shows", ShowViewSet)
|
||||
router.register("show-days", ShowDaysViewSet)
|
||||
router.register("show-hosts", ShowHostViewSet)
|
||||
router.register("show-instances", ShowInstanceViewSet)
|
||||
router.register("show-rebroadcasts", ShowRebroadcastViewSet)
|
||||
router.register("stream-settings", StreamSettingViewSet)
|
||||
router.register("users", UserViewSet)
|
||||
router.register("user-tokens", UserTokenViewSet)
|
||||
router.register("timestamps", TimestampViewSet)
|
||||
router.register("webstreams", WebstreamViewSet)
|
||||
router.register("webstream-metadata", WebstreamMetadataViewSet)
|
||||
router.register("celery-tasks", CeleryTaskViewSet)
|
||||
router.register("cloud-files", CloudFileViewSet)
|
||||
router.register("imported-podcasts", ImportedPodcastViewSet)
|
||||
router.register("podcasts", PodcastViewSet)
|
||||
router.register("podcast-episodes", PodcastEpisodeViewSet)
|
||||
router.register("station-podcasts", StationPodcastViewSet)
|
||||
router.register("third-party-track-references", ThirdPartyTrackReferenceViewSet)
|
||||
router.register("track-types", TrackTypeViewSet)
|
||||
|
||||
urlpatterns = [
|
||||
path("api/v2/", include(router.urls)),
|
||||
path("api/v2/schema/", SpectacularAPIView.as_view(), name="schema"),
|
||||
path(
|
||||
"api/v2/schema/swagger-ui/",
|
||||
SpectacularSwaggerView.as_view(url_name="schema"),
|
||||
name="swagger-ui",
|
||||
),
|
||||
path("api/v2/version/", version),
|
||||
path("api-auth/", include("rest_framework.urls", namespace="rest_framework")),
|
||||
]
|
29
api/libretime_api/utils.py
Normal file
29
api/libretime_api/utils.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
import configparser
|
||||
import random
|
||||
import string
|
||||
import sys
|
||||
|
||||
|
||||
def read_config_file(config_path):
|
||||
"""Parse the application's config file located at config_path."""
|
||||
config = configparser.ConfigParser()
|
||||
try:
|
||||
config.readfp(open(config_path))
|
||||
except IOError as e:
|
||||
print(
|
||||
"Failed to open config file at {}: {}".format(config_path, e.strerror),
|
||||
file=sys.stderr,
|
||||
)
|
||||
raise e
|
||||
except Exception as e:
|
||||
print(e.strerror, file=sys.stderr)
|
||||
raise e
|
||||
return config
|
||||
|
||||
|
||||
def get_random_string(seed):
|
||||
"""Generates a random string based on the given seed"""
|
||||
choices = string.ascii_letters + string.digits + string.punctuation
|
||||
seed = seed.encode("utf-8")
|
||||
rand = random.Random(seed)
|
||||
return [rand.choice(choices) for i in range(16)]
|
309
api/libretime_api/views.py
Normal file
309
api/libretime_api/views.py
Normal file
|
@ -0,0 +1,309 @@
|
|||
import os
|
||||
|
||||
from django.conf import settings
|
||||
from django.db.models import F
|
||||
from django.http import FileResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from drf_spectacular.utils import OpenApiParameter, extend_schema, extend_schema_view
|
||||
from rest_framework import fields, status, viewsets
|
||||
from rest_framework.decorators import action, api_view, permission_classes
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.response import Response
|
||||
|
||||
from .permissions import IsAdminOrOwnUser
|
||||
from .serializers import *
|
||||
|
||||
FILTER_NUMERICAL_LOOKUPS = [
|
||||
"exact",
|
||||
"gt",
|
||||
"lt",
|
||||
"gte",
|
||||
"lte",
|
||||
"range",
|
||||
]
|
||||
|
||||
|
||||
class UserViewSet(viewsets.ModelViewSet):
|
||||
queryset = get_user_model().objects.all()
|
||||
serializer_class = UserSerializer
|
||||
permission_classes = [IsAdminOrOwnUser]
|
||||
model_permission_name = "user"
|
||||
|
||||
|
||||
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"
|
||||
|
||||
|
||||
class CountryViewSet(viewsets.ModelViewSet):
|
||||
queryset = Country.objects.all()
|
||||
serializer_class = CountrySerializer
|
||||
model_permission_name = "country"
|
||||
|
||||
|
||||
class FileViewSet(viewsets.ModelViewSet):
|
||||
queryset = File.objects.all()
|
||||
serializer_class = FileSerializer
|
||||
model_permission_name = "file"
|
||||
|
||||
@action(detail=True, methods=["GET"])
|
||||
def download(self, request, pk=None):
|
||||
if pk is None:
|
||||
return Response("No file requested", status=status.HTTP_400_BAD_REQUEST)
|
||||
try:
|
||||
pk = int(pk)
|
||||
except ValueError:
|
||||
return Response(
|
||||
"File ID should be an integer", status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
filename = get_object_or_404(File, pk=pk)
|
||||
directory = filename.directory
|
||||
path = os.path.join(directory.directory, filename.filepath)
|
||||
response = FileResponse(open(path, "rb"), content_type=filename.mime)
|
||||
return response
|
||||
|
||||
|
||||
class ListenerCountViewSet(viewsets.ModelViewSet):
|
||||
queryset = ListenerCount.objects.all()
|
||||
serializer_class = ListenerCountSerializer
|
||||
model_permission_name = "listenercount"
|
||||
|
||||
|
||||
class LiveLogViewSet(viewsets.ModelViewSet):
|
||||
queryset = LiveLog.objects.all()
|
||||
serializer_class = LiveLogSerializer
|
||||
model_permission_name = "livelog"
|
||||
|
||||
|
||||
class LoginAttemptViewSet(viewsets.ModelViewSet):
|
||||
queryset = LoginAttempt.objects.all()
|
||||
serializer_class = LoginAttemptSerializer
|
||||
model_permission_name = "loginattempt"
|
||||
|
||||
|
||||
class MountNameViewSet(viewsets.ModelViewSet):
|
||||
queryset = MountName.objects.all()
|
||||
serializer_class = MountNameSerializer
|
||||
model_permission_name = "mountname"
|
||||
|
||||
|
||||
class MusicDirViewSet(viewsets.ModelViewSet):
|
||||
queryset = MusicDir.objects.all()
|
||||
serializer_class = MusicDirSerializer
|
||||
model_permission_name = "musicdir"
|
||||
|
||||
|
||||
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"
|
||||
|
||||
|
||||
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"
|
||||
|
||||
|
||||
class PreferenceViewSet(viewsets.ModelViewSet):
|
||||
queryset = Preference.objects.all()
|
||||
serializer_class = PreferenceSerializer
|
||||
model_permission_name = "preference"
|
||||
|
||||
|
||||
@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"))
|
||||
|
||||
|
||||
class ServiceRegisterViewSet(viewsets.ModelViewSet):
|
||||
queryset = ServiceRegister.objects.all()
|
||||
serializer_class = ServiceRegisterSerializer
|
||||
model_permission_name = "serviceregister"
|
||||
|
||||
|
||||
class SessionViewSet(viewsets.ModelViewSet):
|
||||
queryset = Session.objects.all()
|
||||
serializer_class = SessionSerializer
|
||||
model_permission_name = "session"
|
||||
|
||||
|
||||
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"
|
||||
|
||||
|
||||
class StreamSettingViewSet(viewsets.ModelViewSet):
|
||||
queryset = StreamSetting.objects.all()
|
||||
serializer_class = StreamSettingSerializer
|
||||
model_permission_name = "streamsetting"
|
||||
|
||||
|
||||
class UserTokenViewSet(viewsets.ModelViewSet):
|
||||
queryset = UserToken.objects.all()
|
||||
serializer_class = UserTokenSerializer
|
||||
model_permission_name = "usertoken"
|
||||
|
||||
|
||||
class TimestampViewSet(viewsets.ModelViewSet):
|
||||
queryset = Timestamp.objects.all()
|
||||
serializer_class = TimestampSerializer
|
||||
model_permission_name = "timestamp"
|
||||
|
||||
|
||||
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"
|
||||
|
||||
|
||||
class CeleryTaskViewSet(viewsets.ModelViewSet):
|
||||
queryset = CeleryTask.objects.all()
|
||||
serializer_class = CeleryTaskSerializer
|
||||
model_permission_name = "celerytask"
|
||||
|
||||
|
||||
class CloudFileViewSet(viewsets.ModelViewSet):
|
||||
queryset = CloudFile.objects.all()
|
||||
serializer_class = CloudFileSerializer
|
||||
model_permission_name = "cloudfile"
|
||||
|
||||
|
||||
class ImportedPodcastViewSet(viewsets.ModelViewSet):
|
||||
queryset = ImportedPodcast.objects.all()
|
||||
serializer_class = ImportedPodcastSerializer
|
||||
model_permission_name = "importedpodcast"
|
||||
|
||||
|
||||
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 ThirdPartyTrackReferenceViewSet(viewsets.ModelViewSet):
|
||||
queryset = ThirdPartyTrackReference.objects.all()
|
||||
serializer_class = ThirdPartyTrackReferenceSerializer
|
||||
model_permission_name = "thirdpartytrackreference"
|
||||
|
||||
|
||||
class TrackTypeViewSet(viewsets.ModelViewSet):
|
||||
queryset = TrackType.objects.all()
|
||||
serializer_class = TrackTypeSerializer
|
||||
model_permission_name = "tracktype"
|
||||
|
||||
|
||||
@api_view(["GET"])
|
||||
@permission_classes((AllowAny,))
|
||||
def version(request, *args, **kwargs):
|
||||
return Response({"api_version": settings.API_VERSION})
|
16
api/libretime_api/wsgi.py
Normal file
16
api/libretime_api/wsgi.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
WSGI config for api project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "libretimeapi.settings")
|
||||
|
||||
application = get_wsgi_application()
|
Loading…
Add table
Add a link
Reference in a new issue