Added dynamic settings and removed my own attempt.

This commit is contained in:
Josh W 2020-02-15 21:12:30 -05:00
parent 6e4cccb5f6
commit ccddf6c8bf
11 changed files with 122 additions and 145 deletions

View file

@ -3,8 +3,10 @@ asgiref>=3.2.3
cffi>=1.13.2 cffi>=1.13.2
dj-database-url>=0.5.0 dj-database-url>=0.5.0
Django>=3.0.2 Django>=3.0.2
django-dynamic-preferences>=1.8.1
django-inline-actions>=2.3.0 django-inline-actions>=2.3.0
djangorestframework>=3.11.0 djangorestframework>=3.11.0
persisting-theory>=0.2.1
psycopg2-binary>=2.8.4 psycopg2-binary>=2.8.4
pycparser>=2.19 pycparser>=2.19
python-decouple>=3.3 python-decouple>=3.3

View file

@ -1,6 +1,7 @@
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.utils import timezone from django.utils import timezone
from dynamic_preferences.registries import global_preferences_registry
from rest_framework import status from rest_framework import status
from rest_framework.authentication import (SessionAuthentication, from rest_framework.authentication import (SessionAuthentication,
TokenAuthentication) TokenAuthentication)
@ -10,7 +11,6 @@ from rest_framework.renderers import BrowsableAPIRenderer, JSONRenderer
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from core.utils import get_setting
from profiles.exceptions import MakeRequestError from profiles.exceptions import MakeRequestError
from profiles.models import RadioProfile, SongRequest from profiles.models import RadioProfile, SongRequest
from radio.models import Song from radio.models import Song
@ -22,6 +22,7 @@ from ..serializers.controls import (JustPlayedSerializer,
User = get_user_model() User = get_user_model()
radio_settings = global_preferences_registry.manager()
class JustPlayed(APIView): class JustPlayed(APIView):
@ -63,7 +64,7 @@ class NextRequest(RetrieveAPIView):
def retrieve(self, request): def retrieve(self, request):
dj_profile = RadioProfile.objects.get(user__is_dj=True) dj_profile = RadioProfile.objects.get(user__is_dj=True)
limit = get_setting('songs_per_jingle') limit = radio_settings['general__songs_per_jingle']
queued_songs = SongRequest.music.get_queued_requests(limit) queued_songs = SongRequest.music.get_queued_requests(limit)
if [j for j in queued_songs if j.song.is_jingle]: if [j for j in queued_songs if j.song.is_jingle]:
if not SongRequest.music.new_requests().exists(): if not SongRequest.music.new_requests().exists():

View file

@ -0,0 +1,92 @@
from django.forms import ValidationError
from dynamic_preferences.types import FloatPreference, IntegerPreference
from dynamic_preferences.preferences import Section
from dynamic_preferences.registries import global_preferences_registry
general = Section('general')
replay = Section('replay')
@global_preferences_registry.register
class MaxSongRequests(IntegerPreference):
section = general
name = 'max_song_requests'
help_text = (
'The maximum amount of requests a user can have queued at any given '
'time. This restriction does not apply to users who are designated '
'as staff.'
)
default = 5
required = True
def validate(self, value):
if value < 0:
raise ValidationError('Must be greater than 0.')
@global_preferences_registry.register
class SongsPerJingle(IntegerPreference):
section = general
name = 'songs_per_jingle'
help_text = 'The amount of songs that will be played between jingles.'
default = 30
required = True
def validate(self, value):
if value < 0:
raise ValidationError('Must be greater than 0.')
@global_preferences_registry.register
class ReplayRatio(FloatPreference):
section = replay
name = 'replay_ratio'
help_text = (
'This defines how long before a song can be played/requested again '
'once it\'s been played. The ratio is based on the total song length '
'of all the enabled, requestable songs in the radio playlist.\n\n'
'Example: If the total song length of the radio playlist is 432000 '
'seconds (5 days), then a ratio of 0.75 will mean that a song cannot '
'be played again for 324000 seconds '
'(0.75 * 432000 = 324000 seconds = 3 days, 18 hours).'
)
default = float(0.75)
required = True
def validate(self, value):
if value < 0.0 or value > 1.0:
raise ValidationError('Must be between 0.0 and 1.0, inclusive.')
@global_preferences_registry.register
class MinRatingsForVariance(IntegerPreference):
section = replay
name = 'min_ratings_for_variance'
help_text = (
'The minimum amount of ratings for the rating variance to take effect '
'on the replay ratios.'
)
default = 5
required = True
def validate(self, value):
if value < 0:
raise ValidationError('Must be greater than 0.')
@global_preferences_registry.register
class RatingVarianceRatio(FloatPreference):
section = replay
name = 'rating_variance_ratio'
help_text = (
'The range in which the replay ratio can be adjusted due to profile '
'ratings.'
)
default = float(0.20)
required = True
def validate(self, value):
if value < 0.0 or value > 1.0:
raise ValidationError('Must be between 0.0 and 1.0, inclusive.')

View file

@ -1,4 +1,4 @@
# Generated by Django 3.0.2 on 2020-01-21 16:49 # Generated by Django 3.0.3 on 2020-02-16 01:40
from django.db import migrations, models from django.db import migrations, models
import django.utils.timezone import django.utils.timezone
@ -13,16 +13,6 @@ class Migration(migrations.Migration):
] ]
operations = [ operations = [
migrations.CreateModel(
name='Setting',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=64, unique=True, verbose_name='name')),
('description', models.TextField(blank=True, verbose_name='description')),
('setting_type', models.PositiveIntegerField(choices=[(0, 'Integer'), (1, 'Float'), (2, 'String'), (3, 'Bool')], default=0, verbose_name='variable type')),
('data', models.TextField(verbose_name='data')),
],
),
migrations.CreateModel( migrations.CreateModel(
name='RadioUser', name='RadioUser',
fields=[ fields=[

View file

@ -1,56 +0,0 @@
# Generated by Django 2.0 on 2017-12-28 15:16
from django.db import migrations, models
def default_settings(apps, schema_editor):
SETTING_TYPES = {'Integer': 0, 'Float': 1, 'String': 2, 'Bool': 3}
Setting = apps.get_model('core', 'Setting')
db_alias = schema_editor.connection.alias
Setting.objects.using(db_alias).bulk_create([
Setting(name='max_song_requests',
description='The maximum amount of requests a user can have '
'queued at any given time. This restriction does '
'not apply to users who are designated as staff.',
setting_type=SETTING_TYPES['Integer'],
data='5'),
Setting(name='min_ratings_for_variance',
description='The minimum amount of ratings for the rating '
'variance to take effect on the replay ratios.',
setting_type=SETTING_TYPES['Integer'],
data='5'),
Setting(name='rating_variance_ratio',
description='The range in which the replay ratio can be '
'adjusted due to profile ratings.',
setting_type=SETTING_TYPES['Float'],
data='0.20'),
Setting(name='replay_ratio',
description='This defines how long before a song can be '
'played/requested again once it\'s been played. '
'The ratio is based on the total song length of '
'all the enabled, requestable songs in the radio '
'playlist. Example: If the total song length of '
'the radio playlist is 432000 seconds (5 days), '
'then a ratio of 0.75 will mean that a song '
'cannot be played again for 324000 seconds '
'(0.75 * 432000 = 324000 seconds = 3 days, 18 '
'hours).',
setting_type=SETTING_TYPES['Float'],
data='0.75'),
Setting(name='songs_per_jingle',
description='The amount of songs that will be played between '
'jingles.',
setting_type=SETTING_TYPES['Integer'],
data='30'),
])
class Migration(migrations.Migration):
dependencies = [
('core', '0002_create_dj_user'),
]
operations = [
migrations.RunPython(default_settings),
]

View file

@ -56,39 +56,3 @@ class RadioUser(AbstractBaseUser, PermissionsMixin):
name=self.name, name=self.name,
email=self.email, email=self.email,
) )
class Setting(models.Model):
"""
A model for keeping track of dynamic settings while the site is online and
the radio is running.
"""
INTEGER = 0
FLOAT = 1
STRING = 2
BOOL = 3
TYPE_CHOICES = (
(INTEGER, 'Integer'),
(FLOAT, 'Float'),
(STRING, 'String'),
(BOOL, 'Bool'),
)
name = models.CharField(_('name'), max_length=64, unique=True)
description = models.TextField(_('description'), blank=True)
setting_type = models.PositiveIntegerField(_('variable type'),
choices=TYPE_CHOICES,
default=INTEGER)
data = models.TextField(_('data'))
def get(self):
if self.setting_type == self.INTEGER:
return int(self.data)
elif self.setting_type == self.FLOAT:
return float(self.data)
elif self.setting_type == self.BOOL:
return self.data == 'True'
else:
return self.data
def __str__(self):
return '{}: {}'.format(self.name, self.data)

View file

@ -14,8 +14,6 @@ from urllib.request import pathname2url, url2pathname
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.utils.encoding import iri_to_uri, uri_to_iri from django.utils.encoding import iri_to_uri, uri_to_iri
from .models import Setting
GROUP_NT_UNC = r'file://[A-Za-z0-9!@#$%^&\'\)\(\.\-_{}~]+/' GROUP_NT_UNC = r'file://[A-Za-z0-9!@#$%^&\'\)\(\.\-_{}~]+/'
@ -34,32 +32,6 @@ FILE_IRI_PATTERN = (
) )
def get_setting(name):
'''Helper function to get dynamic settings from the database.'''
setting = Setting.objects.get(name=name)
return setting.get()
def set_setting(name, value, setting_type=None):
'''Helper function to set dynamic settings from the database.'''
setting_types = {'Integer': 0, 'Float': 1, 'String': 2, 'Bool': 3}
try:
setting = Setting.objects.get(name=name)
setting.data = str(value)
if setting_type in setting_types:
setting.setting_type = setting_types[setting_type]
setting.save()
except ObjectDoesNotExist:
if setting_type in setting_types:
Setting.objects.create(name=name,
setting_type=setting_types[setting_type],
data=str(value))
else:
error_msg = 'New settings need type (Integer, Float, String, Bool)'
raise TypeError(error_msg)
return
def naturalize(text): def naturalize(text):
''' '''
Return a normalized unicode string, with removed starting articles, for use Return a normalized unicode string, with removed starting articles, for use

View file

@ -4,13 +4,17 @@ from django.core.validators import (MaxLengthValidator, MinValueValidator,
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from dynamic_preferences.registries import global_preferences_registry
from core.behaviors import Disableable, Timestampable from core.behaviors import Disableable, Timestampable
from core.utils import get_setting
from radio.models import Song from radio.models import Song
from .exceptions import MakeRequestError from .exceptions import MakeRequestError
from .managers import RequestManager from .managers import RequestManager
radio_settings = global_preferences_registry.manager()
class RadioProfile(Disableable, Timestampable, models.Model): class RadioProfile(Disableable, Timestampable, models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, user = models.OneToOneField(settings.AUTH_USER_MODEL,
on_delete=models.CASCADE, on_delete=models.CASCADE,
@ -38,7 +42,7 @@ class RadioProfile(Disableable, Timestampable, models.Model):
def has_reached_request_max(self): def has_reached_request_max(self):
self_requests = SongRequest.music.unplayed().filter(profile=self) self_requests = SongRequest.music.unplayed().filter(profile=self)
max_requests = get_setting('max_song_requests') max_requests = radio_settings['general__max_song_requests']
return self_requests.count() >= max_requests return self_requests.count() >= max_requests
def can_request(self): def can_request(self):
@ -59,7 +63,7 @@ class RadioProfile(Disableable, Timestampable, models.Model):
raise MakeRequestError('User is currently disabled.') raise MakeRequestError('User is currently disabled.')
if self.has_reached_request_max() and not self.user.is_staff: if self.has_reached_request_max() and not self.user.is_staff:
max_requests = get_setting('max_song_requests') max_requests = radio_settings['general__max_song_requests']
message = 'User has reached the maximum request limit ({}).' message = 'User has reached the maximum request limit ({}).'
raise MakeRequestError(message.format(max_requests)) raise MakeRequestError(message.format(max_requests))

View file

@ -10,13 +10,16 @@ from django.apps import apps
from django.db import models from django.db import models
from django.utils import timezone from django.utils import timezone
from core.utils import get_setting from dynamic_preferences.registries import global_preferences_registry
from .querysets import RadioQuerySet, SongQuerySet from .querysets import RadioQuerySet, SongQuerySet
# Set decimal precision # Set decimal precision
getcontext().prec = 16 getcontext().prec = 16
radio_settings = global_preferences_registry.manager()
class RadioManager(models.Manager): class RadioManager(models.Manager):
''' '''
@ -97,7 +100,8 @@ class SongManager(RadioManager):
Default length in seconds before a song can be played again. This is Default length in seconds before a song can be played again. This is
based on the replay ratio set in the application settings. based on the replay ratio set in the application settings.
''' '''
total_ratio = get_setting('replay_ratio') + adjusted_ratio replay_ratio = radio_settings['replay__replay_ratio']
total_ratio = replay_ratio + adjusted_ratio
wait = self.playlist_length() * Decimal(total_ratio) wait = self.playlist_length() * Decimal(total_ratio)
wait = wait.quantize(Decimal('.01'), rounding=ROUND_UP) wait = wait.quantize(Decimal('.01'), rounding=ROUND_UP)
return timedelta(seconds=float(wait)) return timedelta(seconds=float(wait))

View file

@ -11,8 +11,9 @@ from django.db import models
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from dynamic_preferences.registries import global_preferences_registry
from core.behaviors import Disableable, Publishable, Timestampable from core.behaviors import Disableable, Publishable, Timestampable
from core.utils import get_setting
from .fields import RadioIRIField from .fields import RadioIRIField
from .managers import RadioManager, SongManager from .managers import RadioManager, SongManager
@ -20,6 +21,8 @@ from .managers import RadioManager, SongManager
# Set decimal precision # Set decimal precision
getcontext().prec = 16 getcontext().prec = 16
radio_settings = global_preferences_registry.manager()
class Album(Disableable, Publishable, Timestampable, models.Model): class Album(Disableable, Publishable, Timestampable, models.Model):
''' '''
@ -265,13 +268,13 @@ class Song(Disableable, Publishable, Timestampable, models.Model):
if self._is_song() and self._is_available(): if self._is_song() and self._is_available():
if last: if last:
# Check if we have enough ratings to change ratio # Check if we have enough ratings to change ratio
min_ratings = get_setting('min_ratings_for_variance') min_rate = radio_settings['replay__min_ratings_for_variance']
if self.rating_set.count() >= min_ratings: if self.rating_set.count() >= min_rate:
rate_ratio = get_setting('rating_variance_ratio') ratio = radio_settings['replay__rating_variance_ratio']
# -((average - 1)/(highest_rating - 1)) * rating_ratio # -((average - 1)/(highest_rating - 1)) * ratio
base = -((self._average_rating() - 1) / 4) * rate_ratio base = -((self._average_rating() - 1) / 4) * ratio
adjusted_ratio = float(base + (rate_ratio * 0.5)) adjusted_ratio = float(base + (ratio * 0.5))
else: else:
adjusted_ratio = float(0.0) adjusted_ratio = float(0.0)

View file

@ -57,6 +57,7 @@ INSTALLED_APPS = [
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'dynamic_preferences',
'rest_framework', 'rest_framework',
'rest_framework.authtoken', 'rest_framework.authtoken',