add API v2
This commit is contained in:
parent
f809c3a8ff
commit
2df0189a90
71 changed files with 2740 additions and 315 deletions
0
api/libretimeapi/__init__.py
Normal file
0
api/libretimeapi/__init__.py
Normal file
7
api/libretimeapi/apps.py
Normal file
7
api/libretimeapi/apps.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
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'
|
20
api/libretimeapi/managers.py
Normal file
20
api/libretimeapi/managers.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
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/libretimeapi/models/__init__.py
Normal file
14
api/libretimeapi/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 *
|
141
api/libretimeapi/models/authentication.py
Normal file
141
api/libretimeapi/models/authentication.py
Normal file
|
@ -0,0 +1,141 @@
|
|||
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 USER_TYPES, ADMIN
|
||||
|
||||
|
||||
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):
|
||||
print('is_staff')
|
||||
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'
|
13
api/libretimeapi/models/celery.py
Normal file
13
api/libretimeapi/models/celery.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
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'
|
11
api/libretimeapi/models/countries.py
Normal file
11
api/libretimeapi/models/countries.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
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'
|
||||
|
105
api/libretimeapi/models/files.py
Normal file
105
api/libretimeapi/models/files.py
Normal file
|
@ -0,0 +1,105 @@
|
|||
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'
|
41
api/libretimeapi/models/playlists.py
Normal file
41
api/libretimeapi/models/playlists.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
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'
|
73
api/libretimeapi/models/playout.py
Normal file
73
api/libretimeapi/models/playout.py
Normal file
|
@ -0,0 +1,73 @@
|
|||
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'
|
77
api/libretimeapi/models/podcasts.py
Normal file
77
api/libretimeapi/models/podcasts.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
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'
|
30
api/libretimeapi/models/preferences.py
Normal file
30
api/libretimeapi/models/preferences.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
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'
|
30
api/libretimeapi/models/schedule.py
Normal file
30
api/libretimeapi/models/schedule.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
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()
|
||||
|
||||
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'),
|
||||
]
|
11
api/libretimeapi/models/services.py
Normal file
11
api/libretimeapi/models/services.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
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'
|
||||
|
94
api/libretimeapi/models/shows.py
Normal file
94
api/libretimeapi/models/shows.py
Normal file
|
@ -0,0 +1,94 @@
|
|||
from django.db import models
|
||||
from .playlists import Playlist
|
||||
from .files import File
|
||||
|
||||
|
||||
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'
|
66
api/libretimeapi/models/smart_blocks.py
Normal file
66
api/libretimeapi/models/smart_blocks.py
Normal file
|
@ -0,0 +1,66 @@
|
|||
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'),
|
||||
]
|
||||
|
25
api/libretimeapi/models/tracks.py
Normal file
25
api/libretimeapi/models/tracks.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
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/libretimeapi/models/user_constants.py
Normal file
11
api/libretimeapi/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',
|
||||
}
|
40
api/libretimeapi/models/webstreams.py
Normal file
40
api/libretimeapi/models/webstreams.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
from django.db import models
|
||||
from django.contrib.auth import get_user_model
|
||||
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'
|
104
api/libretimeapi/permission_constants.py
Normal file
104
api/libretimeapi/permission_constants.py
Normal file
|
@ -0,0 +1,104 @@
|
|||
import logging
|
||||
from django.contrib.auth.models import Group, Permission
|
||||
from .models.user_constants import GUEST, DJ, 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,
|
||||
}
|
102
api/libretimeapi/permissions.py
Normal file
102
api/libretimeapi/permissions.py
Normal file
|
@ -0,0 +1,102 @@
|
|||
from rest_framework.permissions import BasePermission
|
||||
from django.conf import settings
|
||||
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)
|
265
api/libretimeapi/serializers.py
Normal file
265
api/libretimeapi/serializers.py
Normal file
|
@ -0,0 +1,265 @@
|
|||
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)
|
||||
class Meta:
|
||||
model = Schedule
|
||||
fields = [
|
||||
'item_url',
|
||||
'id',
|
||||
'starts',
|
||||
'ends',
|
||||
'clip_length',
|
||||
'fade_in',
|
||||
'fade_out',
|
||||
'cue_in',
|
||||
'cue_out',
|
||||
'media_item_played',
|
||||
'file',
|
||||
'file_id',
|
||||
'stream',
|
||||
'stream_id',
|
||||
'instance',
|
||||
'instance_id',
|
||||
]
|
||||
|
||||
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__'
|
184
api/libretimeapi/settings.py
Normal file
184
api/libretimeapi/settings.py
Normal file
|
@ -0,0 +1,184 @@
|
|||
import configparser
|
||||
import os
|
||||
from .utils import read_config_file, get_random_string
|
||||
|
||||
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:
|
||||
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',
|
||||
'url_filter',
|
||||
]
|
||||
|
||||
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': [
|
||||
'url_filter.integrations.drf.DjangoFilterBackend',
|
||||
],
|
||||
'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',
|
||||
'propogate': True,
|
||||
},
|
||||
'libretimeapi': {
|
||||
'handlers': ['file', 'console'],
|
||||
'level': 'INFO',
|
||||
'propogate': True,
|
||||
},
|
||||
},
|
||||
}
|
0
api/libretimeapi/tests/__init__.py
Normal file
0
api/libretimeapi/tests/__init__.py
Normal file
BIN
api/libretimeapi/tests/resources/song.mp3
Normal file
BIN
api/libretimeapi/tests/resources/song.mp3
Normal file
Binary file not shown.
24
api/libretimeapi/tests/runners.py
Normal file
24
api/libretimeapi/tests/runners.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
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
|
40
api/libretimeapi/tests/test_models.py
Normal file
40
api/libretimeapi/tests/test_models.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
from rest_framework.test import APITestCase
|
||||
from django.contrib.auth.models import Group
|
||||
from django.apps import apps
|
||||
from libretimeapi.models import User
|
||||
from libretimeapi.models.user_constants import GUEST, DJ
|
||||
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])
|
119
api/libretimeapi/tests/test_permissions.py
Normal file
119
api/libretimeapi/tests/test_permissions.py
Normal file
|
@ -0,0 +1,119 @@
|
|||
import os
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.conf import settings
|
||||
from rest_framework.test import APITestCase, APIRequestFactory
|
||||
from model_bakery import baker
|
||||
from libretimeapi.permissions import IsSystemTokenOrUser
|
||||
from libretimeapi.permission_constants import GUEST_PERMISSIONS, DJ_PERMISSIONS, PROGRAM_MANAGER_PERMISSIONS
|
||||
from libretimeapi.models.user_constants import GUEST, DJ, PROGRAM_MANAGER, ADMIN
|
||||
|
||||
|
||||
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))
|
||||
response = self.logged_in_test_model('users', 'guest', GUEST, self.client.get)
|
||||
self.assertEqual(response.status_code, 403, msg='Invalid for model users')
|
||||
|
||||
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)
|
38
api/libretimeapi/tests/test_views.py
Normal file
38
api/libretimeapi/tests/test_views.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
import os
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.conf import settings
|
||||
from rest_framework.test import APITestCase, APIRequestFactory
|
||||
from model_bakery import baker
|
||||
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)
|
51
api/libretimeapi/urls.py
Normal file
51
api/libretimeapi/urls.py
Normal file
|
@ -0,0 +1,51 @@
|
|||
from django.urls import include, path
|
||||
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/version/', version),
|
||||
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
|
||||
]
|
25
api/libretimeapi/utils.py
Normal file
25
api/libretimeapi/utils.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
import configparser
|
||||
import sys
|
||||
import string
|
||||
import random
|
||||
|
||||
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)]
|
228
api/libretimeapi/views.py
Normal file
228
api/libretimeapi/views.py
Normal file
|
@ -0,0 +1,228 @@
|
|||
import os
|
||||
from django.conf import settings
|
||||
from django.http import FileResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework import status, viewsets
|
||||
from rest_framework.decorators import api_view, action, permission_classes
|
||||
from rest_framework.permissions import AllowAny
|
||||
from rest_framework.response import Response
|
||||
from .serializers import *
|
||||
from .permissions import IsAdminOrOwnUser
|
||||
|
||||
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 = 'perference'
|
||||
|
||||
class ScheduleViewSet(viewsets.ModelViewSet):
|
||||
queryset = Schedule.objects.all()
|
||||
serializer_class = ScheduleSerializer
|
||||
filter_fields = ('starts', 'ends', 'playout_status', 'broadcasted')
|
||||
model_permission_name = 'schedule'
|
||||
|
||||
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/libretimeapi/wsgi.py
Normal file
16
api/libretimeapi/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