diff --git a/savepointradio/core/migrations/0003_default_settings.py b/savepointradio/core/migrations/0003_default_settings.py index 7b38559..c0aa8c3 100644 --- a/savepointradio/core/migrations/0003_default_settings.py +++ b/savepointradio/core/migrations/0003_default_settings.py @@ -4,7 +4,7 @@ from django.db import migrations, models 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') db_alias = schema_editor.connection.alias 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.', 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. ' @@ -21,9 +31,10 @@ def default_settings(apps, schema_editor): '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).', + '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', diff --git a/savepointradio/profiles/signals.py b/savepointradio/profiles/signals.py index 55965fa..3f0f9a6 100644 --- a/savepointradio/profiles/signals.py +++ b/savepointradio/profiles/signals.py @@ -18,12 +18,18 @@ def create_profile(sender, instance, created, **kwargs): @receiver(post_save, sender=SongRequest) 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. """ if not created and update_fields: + song = instance.song if 'played_at' in update_fields: - song = instance.song song.last_played = instance.played_at song.num_played = F('num_played') + 1 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() diff --git a/savepointradio/radio/admin.py b/savepointradio/radio/admin.py index 58d3e8f..e5fefa4 100644 --- a/savepointradio/radio/admin.py +++ b/savepointradio/radio/admin.py @@ -113,7 +113,8 @@ class SongAdmin(admin.ModelAdmin): 'album', 'artist_list', '_is_enabled', - '_is_published') + '_is_published', + '_is_requestable') search_fields = ['title'] actions = ['publish_songs', 'add_game', 'remove_game', @@ -126,7 +127,8 @@ class SongAdmin(admin.ModelAdmin): 'last_played', 'num_played', 'created_date', - 'modified_date') + 'modified_date', + 'next_play') fieldsets = ( ('Song Disabling', { 'classes': ('collapse',), @@ -144,6 +146,7 @@ class SongAdmin(admin.ModelAdmin): 'modified_date', 'last_played', 'num_played', + 'next_play', 'length') }), ('Album', { diff --git a/savepointradio/radio/managers.py b/savepointradio/radio/managers.py index c5917fe..5a7c898 100644 --- a/savepointradio/radio/managers.py +++ b/savepointradio/radio/managers.py @@ -85,12 +85,13 @@ class SongManager(RadioManager): length = self.available_songs().aggregate(models.Sum('length')) 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 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) return timedelta(seconds=float(wait)) @@ -107,8 +108,8 @@ class SongManager(RadioManager): (or at all). """ return self.available_songs().filter( - models.Q(last_played__lt=self.datetime_from_wait()) | - models.Q(last_played__isnull=True) + models.Q(next_play__lt=timezone.now()) | + models.Q(next_play__isnull=True) ) def requestable(self): diff --git a/savepointradio/radio/migrations/0003_song_next_play.py b/savepointradio/radio/migrations/0003_song_next_play.py new file mode 100644 index 0000000..d8cad19 --- /dev/null +++ b/savepointradio/radio/migrations/0003_song_next_play.py @@ -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'), + ), + ] diff --git a/savepointradio/radio/models.py b/savepointradio/radio/models.py index e7a0f2c..7b0cd0d 100644 --- a/savepointradio/radio/models.py +++ b/savepointradio/radio/models.py @@ -7,6 +7,7 @@ from django.utils import timezone from django.utils.translation import ugettext_lazy as _ from core.behaviors import Disableable, Publishable, Timestampable +from core.utils import get_setting from .managers import RadioManager, SongManager @@ -123,6 +124,10 @@ class Song(Disableable, Publishable, Timestampable, models.Model): null=True, blank=True, editable=False) + next_play = models.DateTimeField(_('can be played again'), + null=True, + blank=True, + editable=False) length = models.DecimalField(_('song length (in seconds)'), max_digits=8, decimal_places=2, @@ -203,13 +208,26 @@ class Song(Disableable, Publishable, Timestampable, models.Model): return timedelta(seconds=0) 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. """ + last = self.last_played if last_play is None else last_play + if self._is_song() and self._is_available(): - if self.last_played: - return self.last_played + Song.music.wait_total() + if last: + # 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 None