Implemented rating variance for song requests.

This commit is contained in:
Josh Washburne 2019-01-15 15:29:14 -05:00
parent bf88b8cfdc
commit b93b8751ec
6 changed files with 72 additions and 15 deletions

View file

@ -4,7 +4,7 @@ from django.db import migrations, models
def default_settings(apps, schema_editor): def default_settings(apps, schema_editor):
SETTING_TYPES = { 'Integer': 0, 'Float': 1, 'String': 2, 'Bool': 3 } SETTING_TYPES = {'Integer': 0, 'Float': 1, 'String': 2, 'Bool': 3}
Setting = apps.get_model('core', 'Setting') Setting = apps.get_model('core', 'Setting')
db_alias = schema_editor.connection.alias db_alias = schema_editor.connection.alias
Setting.objects.using(db_alias).bulk_create([ Setting.objects.using(db_alias).bulk_create([
@ -14,6 +14,16 @@ def default_settings(apps, schema_editor):
'not apply to users who are designated as staff.', 'not apply to users who are designated as staff.',
setting_type=SETTING_TYPES['Integer'], setting_type=SETTING_TYPES['Integer'],
data='5'), 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', Setting(name='replay_ratio',
description='This defines how long before a song can be ' description='This defines how long before a song can be '
'played/requested again once it\'s been played. ' 'played/requested again once it\'s been played. '
@ -21,9 +31,10 @@ def default_settings(apps, schema_editor):
'all the enabled, requestable songs in the radio ' 'all the enabled, requestable songs in the radio '
'playlist. Example: If the total song length of ' 'playlist. Example: If the total song length of '
'the radio playlist is 432000 seconds (5 days), ' 'the radio playlist is 432000 seconds (5 days), '
'then a ratio of 0.75 will mean that a song cannot ' 'then a ratio of 0.75 will mean that a song '
'be played again for 324000 seconds (0.75 * 432000 ' 'cannot be played again for 324000 seconds '
'= 324000 seconds = 3 days, 18 hours).', '(0.75 * 432000 = 324000 seconds = 3 days, 18 '
'hours).',
setting_type=SETTING_TYPES['Float'], setting_type=SETTING_TYPES['Float'],
data='0.75'), data='0.75'),
Setting(name='songs_per_jingle', Setting(name='songs_per_jingle',

View file

@ -18,12 +18,18 @@ def create_profile(sender, instance, created, **kwargs):
@receiver(post_save, sender=SongRequest) @receiver(post_save, sender=SongRequest)
def update_song_plays(sender, instance, created, update_fields, **kwargs): def update_song_plays(sender, instance, created, update_fields, **kwargs):
""" """
Update the song data with the latest play time and the number of times Set when a song can be requested again once it's queued. Once played,
update the song data with the latest play time and the number of times
played. played.
""" """
if not created and update_fields: if not created and update_fields:
song = instance.song
if 'played_at' in update_fields: if 'played_at' in update_fields:
song = instance.song
song.last_played = instance.played_at song.last_played = instance.played_at
song.num_played = F('num_played') + 1 song.num_played = F('num_played') + 1
song.save() song.save()
if 'queued_at' in update_fields:
if song.is_song:
queued = instance.queued_at
song.next_play = song.get_date_when_requestable(queued)
song.save()

View file

@ -113,7 +113,8 @@ class SongAdmin(admin.ModelAdmin):
'album', 'album',
'artist_list', 'artist_list',
'_is_enabled', '_is_enabled',
'_is_published') '_is_published',
'_is_requestable')
search_fields = ['title'] search_fields = ['title']
actions = ['publish_songs', actions = ['publish_songs',
'add_game', 'remove_game', 'add_game', 'remove_game',
@ -126,7 +127,8 @@ class SongAdmin(admin.ModelAdmin):
'last_played', 'last_played',
'num_played', 'num_played',
'created_date', 'created_date',
'modified_date') 'modified_date',
'next_play')
fieldsets = ( fieldsets = (
('Song Disabling', { ('Song Disabling', {
'classes': ('collapse',), 'classes': ('collapse',),
@ -144,6 +146,7 @@ class SongAdmin(admin.ModelAdmin):
'modified_date', 'modified_date',
'last_played', 'last_played',
'num_played', 'num_played',
'next_play',
'length') 'length')
}), }),
('Album', { ('Album', {

View file

@ -85,12 +85,13 @@ class SongManager(RadioManager):
length = self.available_songs().aggregate(models.Sum('length')) length = self.available_songs().aggregate(models.Sum('length'))
return length['length__sum'] return length['length__sum']
def wait_total(self): def wait_total(self, adjusted_ratio=0.0):
""" """
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.
""" """
wait = self.playlist_length() * Decimal(get_setting('replay_ratio')) total_ratio = get_setting('replay_ratio') + adjusted_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))
@ -107,8 +108,8 @@ class SongManager(RadioManager):
(or at all). (or at all).
""" """
return self.available_songs().filter( return self.available_songs().filter(
models.Q(last_played__lt=self.datetime_from_wait()) | models.Q(next_play__lt=timezone.now()) |
models.Q(last_played__isnull=True) models.Q(next_play__isnull=True)
) )
def requestable(self): def requestable(self):

View file

@ -0,0 +1,18 @@
# Generated by Django 2.1.1 on 2018-09-24 18:02
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('radio', '0002_naming_and_sorting'),
]
operations = [
migrations.AddField(
model_name='song',
name='next_play',
field=models.DateTimeField(blank=True, editable=False, null=True, verbose_name='can be played again'),
),
]

View file

@ -7,6 +7,7 @@ from django.utils import timezone
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from core.behaviors import Disableable, Publishable, Timestampable from core.behaviors import Disableable, Publishable, Timestampable
from core.utils import get_setting
from .managers import RadioManager, SongManager from .managers import RadioManager, SongManager
@ -123,6 +124,10 @@ class Song(Disableable, Publishable, Timestampable, models.Model):
null=True, null=True,
blank=True, blank=True,
editable=False) editable=False)
next_play = models.DateTimeField(_('can be played again'),
null=True,
blank=True,
editable=False)
length = models.DecimalField(_('song length (in seconds)'), length = models.DecimalField(_('song length (in seconds)'),
max_digits=8, max_digits=8,
decimal_places=2, decimal_places=2,
@ -203,13 +208,26 @@ class Song(Disableable, Publishable, Timestampable, models.Model):
return timedelta(seconds=0) return timedelta(seconds=0)
return None return None
def get_date_when_requestable(self): def get_date_when_requestable(self, last_play=None):
""" """
Datetime when a song can be requested again. Datetime when a song can be requested again.
""" """
last = self.last_played if last_play is None else last_play
if self._is_song() and self._is_available(): if self._is_song() and self._is_available():
if self.last_played: if last:
return self.last_played + Song.music.wait_total() # Check if we have enough ratings to change ratio
min_ratings = get_setting('min_ratings_for_variance')
if self.rating_set.count() >= min_ratings:
rate_ratio = get_setting('rating_variance_ratio')
# -((average - 1)/(highest_rating - 1)) * rating_ratio
base = -((self._average_rating() - 1) / 4) * rate_ratio
adjusted_ratio = float(base + (rate_ratio * 0.5))
else:
adjusted_ratio = float(0.0)
return last + Song.music.wait_total(adjusted_ratio)
return timezone.now() return timezone.now()
return None return None