Compare commits
No commits in common. "b73f3b5bb80f1c7b4792f7ebcdee2687a8655868" and "704a792e49f56d3ed1852071bed09a18e3d9800d" have entirely different histories.
b73f3b5bb8
...
704a792e49
11 changed files with 101 additions and 240 deletions
Binary file not shown.
|
@ -64,17 +64,19 @@ 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)
|
||||||
|
|
||||||
if SongRequest.music.should_play_jingle():
|
limit = radio_settings['general__songs_per_jingle']
|
||||||
random_jingle = Song.music.get_random_jingle()
|
queued_songs = SongRequest.music.get_queued_requests(limit)
|
||||||
next_request = SongRequest.objects.create(profile=dj_profile,
|
if [j for j in queued_songs if j.song.is_jingle]:
|
||||||
song=random_jingle)
|
|
||||||
else:
|
|
||||||
if not SongRequest.music.new_requests().exists():
|
if not SongRequest.music.new_requests().exists():
|
||||||
random_song = Song.music.get_random_requestable_song()
|
random_song = Song.music.get_random_requestable_song()
|
||||||
next_request = SongRequest.objects.create(profile=dj_profile,
|
next_request = SongRequest.objects.create(profile=dj_profile,
|
||||||
song=random_song)
|
song=random_song)
|
||||||
else:
|
else:
|
||||||
next_request = SongRequest.music.next_request()
|
next_request = SongRequest.music.next_request()
|
||||||
|
else:
|
||||||
|
random_jingle = Song.music.get_random_jingle()
|
||||||
|
next_request = SongRequest.objects.create(profile=dj_profile,
|
||||||
|
song=random_jingle)
|
||||||
|
|
||||||
next_request.queued_at = timezone.now()
|
next_request.queued_at = timezone.now()
|
||||||
next_request.save(update_fields=['queued_at'])
|
next_request.save(update_fields=['queued_at'])
|
||||||
|
@ -94,8 +96,9 @@ class MakeRequest(APIView):
|
||||||
def post(self, request, format=None):
|
def post(self, request, format=None):
|
||||||
serializer = MakeRequestSerializer(data=request.data)
|
serializer = MakeRequestSerializer(data=request.data)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
|
profile = RadioProfile.objects.get(user=request.user)
|
||||||
try:
|
try:
|
||||||
request.user.profile.make_request(serializer.data['song'])
|
profile.make_request(serializer.data['song'])
|
||||||
except MakeRequestError as e:
|
except MakeRequestError as e:
|
||||||
return Response({'detail': str(e)},
|
return Response({'detail': str(e)},
|
||||||
status=status.HTTP_400_BAD_REQUEST)
|
status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
|
@ -201,9 +201,10 @@ class SongViewSet(viewsets.ModelViewSet):
|
||||||
def favorite(self, request, pk=None):
|
def favorite(self, request, pk=None):
|
||||||
'''Add a song to the user's favorites list.'''
|
'''Add a song to the user's favorites list.'''
|
||||||
song = self.get_object()
|
song = self.get_object()
|
||||||
if song not in request.user.profile.favorites.all():
|
profile = RadioProfile.objects.get(user=request.user)
|
||||||
request.user.profile.favorites.add(song)
|
if song not in profile.favorites.all():
|
||||||
request.user.profile.save()
|
profile.favorites.add(song)
|
||||||
|
profile.save()
|
||||||
return Response({'detail': 'Song has been added to favorites.'})
|
return Response({'detail': 'Song has been added to favorites.'})
|
||||||
message = 'Song is already a favorite.'
|
message = 'Song is already a favorite.'
|
||||||
return Response({'detail': message},
|
return Response({'detail': message},
|
||||||
|
@ -215,9 +216,10 @@ class SongViewSet(viewsets.ModelViewSet):
|
||||||
def unfavorite(self, request, pk=None):
|
def unfavorite(self, request, pk=None):
|
||||||
'''Remove a song from the user's favorites list.'''
|
'''Remove a song from the user's favorites list.'''
|
||||||
song = self.get_object()
|
song = self.get_object()
|
||||||
if song in request.user.profile.favorites.all():
|
profile = RadioProfile.objects.get(user=request.user)
|
||||||
request.user.profile.favorites.remove(song)
|
if song in profile.favorites.all():
|
||||||
request.user.profile.save()
|
profile.favorites.remove(song)
|
||||||
|
profile.save()
|
||||||
message = 'Song has been removed from favorites.'
|
message = 'Song has been removed from favorites.'
|
||||||
return Response({'detail': message})
|
return Response({'detail': message})
|
||||||
message = 'Song is already not a favorite.'
|
message = 'Song is already not a favorite.'
|
||||||
|
@ -246,9 +248,10 @@ class SongViewSet(viewsets.ModelViewSet):
|
||||||
serializer = RateSongSerializer(data=request.data)
|
serializer = RateSongSerializer(data=request.data)
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
song = self.get_object()
|
song = self.get_object()
|
||||||
|
profile = RadioProfile.objects.get(user=request.user)
|
||||||
if 'value' in serializer.data:
|
if 'value' in serializer.data:
|
||||||
rating, created = Rating.objects.update_or_create(
|
rating, created = Rating.objects.update_or_create(
|
||||||
profile=request.user.profile,
|
profile=profile,
|
||||||
song=song,
|
song=song,
|
||||||
defaults={'value': serializer.data['value']}
|
defaults={'value': serializer.data['value']}
|
||||||
)
|
)
|
||||||
|
@ -267,7 +270,8 @@ class SongViewSet(viewsets.ModelViewSet):
|
||||||
def unrate(self, request, pk=None):
|
def unrate(self, request, pk=None):
|
||||||
'''Remove a user's rating from a song.'''
|
'''Remove a user's rating from a song.'''
|
||||||
song = self.get_object()
|
song = self.get_object()
|
||||||
rating = song.rating_set.filter(profile=request.user.profile)
|
profile = RadioProfile.objects.get(user=request.user)
|
||||||
|
rating = song.rating_set.filter(profile=profile)
|
||||||
if rating:
|
if rating:
|
||||||
rating.delete()
|
rating.delete()
|
||||||
return Response({'detail': 'Rating deleted from song.'})
|
return Response({'detail': 'Rating deleted from song.'})
|
||||||
|
|
|
@ -1,16 +1,11 @@
|
||||||
from django.forms import ValidationError
|
from django.forms import ValidationError
|
||||||
from django.utils import timezone
|
|
||||||
|
|
||||||
from dynamic_preferences.types import (
|
from dynamic_preferences.types import FloatPreference, IntegerPreference
|
||||||
BooleanPreference, ChoicePreference, DurationPreference,
|
|
||||||
FloatPreference, IntegerPreference
|
|
||||||
)
|
|
||||||
from dynamic_preferences.preferences import Section
|
from dynamic_preferences.preferences import Section
|
||||||
from dynamic_preferences.registries import global_preferences_registry
|
from dynamic_preferences.registries import global_preferences_registry
|
||||||
|
|
||||||
|
|
||||||
general = Section('general')
|
general = Section('general')
|
||||||
jingles = Section('jingles')
|
|
||||||
replay = Section('replay')
|
replay = Section('replay')
|
||||||
|
|
||||||
|
|
||||||
|
@ -31,31 +26,9 @@ class MaxSongRequests(IntegerPreference):
|
||||||
raise ValidationError('Must be greater than 0.')
|
raise ValidationError('Must be greater than 0.')
|
||||||
|
|
||||||
|
|
||||||
@global_preferences_registry.register
|
|
||||||
class JinglesEnabled(BooleanPreference):
|
|
||||||
section = jingles
|
|
||||||
name = 'jingles_enabled'
|
|
||||||
help_text = 'Enable jingle functionality for the radio.'
|
|
||||||
default = False
|
|
||||||
required = False
|
|
||||||
|
|
||||||
|
|
||||||
@global_preferences_registry.register
|
|
||||||
class JinglesFunction(ChoicePreference):
|
|
||||||
section = jingles
|
|
||||||
name = 'jingles_function'
|
|
||||||
help_text = 'The function to determine when jingles are played.'
|
|
||||||
choices = [
|
|
||||||
('after_songs', 'After so many songs are played'),
|
|
||||||
('after_time', 'After so much time has passed'),
|
|
||||||
]
|
|
||||||
default = 'after_songs'
|
|
||||||
required = True
|
|
||||||
|
|
||||||
|
|
||||||
@global_preferences_registry.register
|
@global_preferences_registry.register
|
||||||
class SongsPerJingle(IntegerPreference):
|
class SongsPerJingle(IntegerPreference):
|
||||||
section = jingles
|
section = general
|
||||||
name = 'songs_per_jingle'
|
name = 'songs_per_jingle'
|
||||||
help_text = 'The amount of songs that will be played between jingles.'
|
help_text = 'The amount of songs that will be played between jingles.'
|
||||||
default = 30
|
default = 30
|
||||||
|
@ -66,15 +39,6 @@ class SongsPerJingle(IntegerPreference):
|
||||||
raise ValidationError('Must be greater than 0.')
|
raise ValidationError('Must be greater than 0.')
|
||||||
|
|
||||||
|
|
||||||
@global_preferences_registry.register
|
|
||||||
class TimeBeforeJingle(DurationPreference):
|
|
||||||
section = jingles
|
|
||||||
name = 'time_before_jingle'
|
|
||||||
help_text = 'The amount of time before a jingle is played.'
|
|
||||||
default = timezone.timedelta(hours=1)
|
|
||||||
required = True
|
|
||||||
|
|
||||||
|
|
||||||
@global_preferences_registry.register
|
@global_preferences_registry.register
|
||||||
class ReplayRatio(FloatPreference):
|
class ReplayRatio(FloatPreference):
|
||||||
section = replay
|
section = replay
|
||||||
|
@ -96,15 +60,6 @@ class ReplayRatio(FloatPreference):
|
||||||
raise ValidationError('Must be between 0.0 and 1.0, inclusive.')
|
raise ValidationError('Must be between 0.0 and 1.0, inclusive.')
|
||||||
|
|
||||||
|
|
||||||
@global_preferences_registry.register
|
|
||||||
class VarianceEnabled(BooleanPreference):
|
|
||||||
section = replay
|
|
||||||
name = 'variance_enabled'
|
|
||||||
help_text = 'Enable variance to replay time based on user ratings.'
|
|
||||||
default = False
|
|
||||||
required = False
|
|
||||||
|
|
||||||
|
|
||||||
@global_preferences_registry.register
|
@global_preferences_registry.register
|
||||||
class MinRatingsForVariance(IntegerPreference):
|
class MinRatingsForVariance(IntegerPreference):
|
||||||
section = replay
|
section = replay
|
||||||
|
|
|
@ -13,11 +13,15 @@ class RequestSongActionMixin(object):
|
||||||
return actions
|
return actions
|
||||||
|
|
||||||
def request_song(self, request, obj, parent_obj=None):
|
def request_song(self, request, obj, parent_obj=None):
|
||||||
|
profile = RadioProfile.objects.get(user=request.user)
|
||||||
|
|
||||||
# This is to get around the M2M 'through' table on Artists
|
# This is to get around the M2M 'through' table on Artists
|
||||||
song = obj.song if not isinstance(obj, Song) else obj
|
song = obj
|
||||||
|
if not isinstance(song, Song):
|
||||||
|
song = obj.song
|
||||||
|
|
||||||
try:
|
try:
|
||||||
request.user.profile.make_request(song)
|
profile.make_request(song)
|
||||||
except MakeRequestError as e:
|
except MakeRequestError as e:
|
||||||
return messages.error(
|
return messages.error(
|
||||||
request,
|
request,
|
||||||
|
@ -26,7 +30,7 @@ class RequestSongActionMixin(object):
|
||||||
return messages.success(request, 'Successfully queued song.')
|
return messages.success(request, 'Successfully queued song.')
|
||||||
|
|
||||||
def get_request_song_label(self, obj):
|
def get_request_song_label(self, obj):
|
||||||
return 'Queue'
|
return 'Request Song'
|
||||||
|
|
||||||
|
|
||||||
class ToggleFavoriteActionsMixin(object):
|
class ToggleFavoriteActionsMixin(object):
|
||||||
|
@ -37,22 +41,21 @@ class ToggleFavoriteActionsMixin(object):
|
||||||
return actions
|
return actions
|
||||||
|
|
||||||
def toggle_favorite(self, request, obj, parent_obj=None):
|
def toggle_favorite(self, request, obj, parent_obj=None):
|
||||||
# This is to get around the M2M 'through' table
|
profile = RadioProfile.objects.get(user=request.user)
|
||||||
song = obj.song if not isinstance(obj, Song) else obj
|
|
||||||
|
|
||||||
found = request.user.profile.favorites.filter(pk=song.pk).exists()
|
# This is to get around the M2M 'through' table
|
||||||
|
song = obj
|
||||||
|
if not isinstance(song, Song):
|
||||||
|
song = obj.song
|
||||||
|
|
||||||
|
found = profile.favorites.filter(pk=song.pk).exists()
|
||||||
if found:
|
if found:
|
||||||
request.user.profile.favorites.remove(song)
|
profile.favorites.remove(song)
|
||||||
else:
|
else:
|
||||||
request.user.profile.favorites.add(song)
|
profile.favorites.add(song)
|
||||||
|
|
||||||
status = 'removed from favorites' if found else 'added to favorites'
|
status = 'removed from favorites' if found else 'added to favorites'
|
||||||
messages.success(request, 'Song successfully {}.'.format(status))
|
messages.success(request, 'Song successfully {}.'.format(status))
|
||||||
|
|
||||||
def get_toggle_favorite_label(self, obj):
|
def get_toggle_favorite_label(self, obj):
|
||||||
# This is to get around the M2M 'through' table
|
return 'Toggle Favorite'
|
||||||
song = obj.song if not isinstance(obj, Song) else obj
|
|
||||||
|
|
||||||
if self._request.user.profile.favorites.filter(pk=song.pk).exists():
|
|
||||||
return 'Unfavorite'
|
|
||||||
return 'Favorite'
|
|
||||||
|
|
|
@ -1,10 +1,4 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils import timezone
|
|
||||||
|
|
||||||
from dynamic_preferences.registries import global_preferences_registry
|
|
||||||
|
|
||||||
|
|
||||||
radio_settings = global_preferences_registry.manager()
|
|
||||||
|
|
||||||
|
|
||||||
class RequestManager(models.Manager):
|
class RequestManager(models.Manager):
|
||||||
|
@ -40,22 +34,3 @@ class RequestManager(models.Manager):
|
||||||
|
|
||||||
def next_request(self):
|
def next_request(self):
|
||||||
return self.new_requests().earliest('created_date')
|
return self.new_requests().earliest('created_date')
|
||||||
|
|
||||||
def should_play_jingle(self):
|
|
||||||
if radio_settings['jingles__jingles_enabled']:
|
|
||||||
function = radio_settings['jingles__jingles_function']
|
|
||||||
if function == 'after_songs':
|
|
||||||
limit = radio_settings['jingles__songs_per_jingle']
|
|
||||||
queued_songs = self.get_queued_requests(limit)
|
|
||||||
if not [j for j in queued_songs if j.song.is_jingle]:
|
|
||||||
return True
|
|
||||||
elif function == 'after_time':
|
|
||||||
now = timezone.now()
|
|
||||||
delta = radio_settings['jingles__time_before_jingle']
|
|
||||||
earlier = now - delta
|
|
||||||
queued_songs = self.get_queued_requests().filter(
|
|
||||||
queued_at__range=(earlier, now)
|
|
||||||
)
|
|
||||||
if not [j for j in queued_songs if j.song.is_jingle]:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Generated by Django 3.0.3 on 2020-03-10 14:19
|
# Generated by Django 2.0.3 on 2018-03-30 15:24
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
import django.core.validators
|
import django.core.validators
|
||||||
|
@ -11,8 +11,8 @@ class Migration(migrations.Migration):
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('radio', '0005_replaygain_data'),
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('radio', '0002_naming_and_sorting'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -25,7 +25,21 @@ class Migration(migrations.Migration):
|
||||||
('disabled_reason', models.TextField(blank=True, verbose_name='reason for disabling')),
|
('disabled_reason', models.TextField(blank=True, verbose_name='reason for disabling')),
|
||||||
('created_date', models.DateTimeField(auto_now_add=True, verbose_name='added on')),
|
('created_date', models.DateTimeField(auto_now_add=True, verbose_name='added on')),
|
||||||
('modified_date', models.DateTimeField(auto_now=True, verbose_name='last modified')),
|
('modified_date', models.DateTimeField(auto_now=True, verbose_name='last modified')),
|
||||||
('favorites', models.ManyToManyField(related_name='favorited_by', to='radio.Song')),
|
('favorites', models.ManyToManyField(related_name='song_favorites', to='radio.Song')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Rating',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created_date', models.DateTimeField(auto_now_add=True, verbose_name='added on')),
|
||||||
|
('modified_date', models.DateTimeField(auto_now=True, verbose_name='last modified')),
|
||||||
|
('value', models.PositiveIntegerField(validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(5)], verbose_name='song rating')),
|
||||||
|
('profile', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rating_profile', to='profiles.RadioProfile')),
|
||||||
|
('song', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='radio.Song')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'abstract': False,
|
'abstract': False,
|
||||||
|
@ -43,36 +57,22 @@ class Migration(migrations.Migration):
|
||||||
('song', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='radio.Song')),
|
('song', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='radio.Song')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'ordering': ['-queued_at', '-created_date'],
|
'ordering': ['-created_date'],
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Rating',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('created_date', models.DateTimeField(auto_now_add=True, verbose_name='added on')),
|
|
||||||
('modified_date', models.DateTimeField(auto_now=True, verbose_name='last modified')),
|
|
||||||
('value', models.PositiveIntegerField(validators=[django.core.validators.MinValueValidator(1), django.core.validators.MaxValueValidator(5)], verbose_name='song rating')),
|
|
||||||
('profile', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='rating_profile', to='profiles.RadioProfile')),
|
|
||||||
('song', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='radio.Song')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='radioprofile',
|
model_name='radioprofile',
|
||||||
name='ratings',
|
name='ratings',
|
||||||
field=models.ManyToManyField(related_name='ratings', through='profiles.Rating', to='radio.Song'),
|
field=models.ManyToManyField(related_name='song_ratings', through='profiles.Rating', to='radio.Song'),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='radioprofile',
|
model_name='radioprofile',
|
||||||
name='song_requests',
|
name='song_requests',
|
||||||
field=models.ManyToManyField(related_name='request_history', through='profiles.SongRequest', to='radio.Song'),
|
field=models.ManyToManyField(related_name='song_requests', through='profiles.SongRequest', to='radio.Song'),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='radioprofile',
|
model_name='radioprofile',
|
||||||
name='user',
|
name='user',
|
||||||
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='profile', to=settings.AUTH_USER_MODEL),
|
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -18,15 +18,14 @@ 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,
|
||||||
related_name='profile',
|
|
||||||
null=True,
|
null=True,
|
||||||
blank=True)
|
blank=True)
|
||||||
favorites = models.ManyToManyField(Song, related_name='favorited_by')
|
favorites = models.ManyToManyField(Song, related_name='song_favorites')
|
||||||
ratings = models.ManyToManyField(Song,
|
ratings = models.ManyToManyField(Song,
|
||||||
related_name='ratings',
|
related_name='song_ratings',
|
||||||
through='Rating')
|
through='Rating')
|
||||||
song_requests = models.ManyToManyField(Song,
|
song_requests = models.ManyToManyField(Song,
|
||||||
related_name='request_history',
|
related_name='song_requests',
|
||||||
through='SongRequest')
|
through='SongRequest')
|
||||||
|
|
||||||
def disable(self, reason=''):
|
def disable(self, reason=''):
|
||||||
|
|
|
@ -6,10 +6,9 @@ from inline_actions.admin import (
|
||||||
InlineActionsMixin, InlineActionsModelAdminMixin
|
InlineActionsMixin, InlineActionsModelAdminMixin
|
||||||
)
|
)
|
||||||
|
|
||||||
from core.utils import quantify
|
|
||||||
from profiles.actions import RequestSongActionMixin, ToggleFavoriteActionsMixin
|
|
||||||
from .actions import change_items, publish_items, remove_items
|
from .actions import change_items, publish_items, remove_items
|
||||||
from .models import Album, Artist, Game, Song, Store
|
from .models import Album, Artist, Game, Song, Store
|
||||||
|
from profiles.actions import RequestSongActionMixin, ToggleFavoriteActionsMixin
|
||||||
|
|
||||||
|
|
||||||
class BaseSongInline(RequestSongActionMixin,
|
class BaseSongInline(RequestSongActionMixin,
|
||||||
|
@ -244,6 +243,7 @@ class StoreAdmin(admin.ModelAdmin):
|
||||||
|
|
||||||
@admin.register(Song)
|
@admin.register(Song)
|
||||||
class SongAdmin(RequestSongActionMixin,
|
class SongAdmin(RequestSongActionMixin,
|
||||||
|
ToggleFavoriteActionsMixin,
|
||||||
InlineActionsModelAdminMixin,
|
InlineActionsModelAdminMixin,
|
||||||
admin.ModelAdmin):
|
admin.ModelAdmin):
|
||||||
formfield_overrides = {
|
formfield_overrides = {
|
||||||
|
@ -258,10 +258,9 @@ class SongAdmin(RequestSongActionMixin,
|
||||||
'_is_enabled',
|
'_is_enabled',
|
||||||
'_is_published',
|
'_is_published',
|
||||||
'_is_requestable')
|
'_is_requestable')
|
||||||
list_select_related = ('album', 'game')
|
list_select_related = True
|
||||||
search_fields = ['title']
|
search_fields = ['title']
|
||||||
actions = ['publish_songs',
|
actions = ['publish_songs',
|
||||||
'add_favorites', 'remove_favorites',
|
|
||||||
'add_game', 'remove_game',
|
'add_game', 'remove_game',
|
||||||
'add_album', 'remove_album',
|
'add_album', 'remove_album',
|
||||||
'add_artists', 'remove_artists']
|
'add_artists', 'remove_artists']
|
||||||
|
@ -301,28 +300,17 @@ class SongAdmin(RequestSongActionMixin,
|
||||||
)
|
)
|
||||||
inlines = [ArtistInline, StoreInline]
|
inlines = [ArtistInline, StoreInline]
|
||||||
|
|
||||||
def get_queryset(self, request):
|
|
||||||
qs = super().get_queryset(request)
|
|
||||||
return qs.prefetch_related('artists', 'stores')
|
|
||||||
|
|
||||||
def formfield_for_foreignkey(self, db_field, request, **kwargs):
|
def formfield_for_foreignkey(self, db_field, request, **kwargs):
|
||||||
if db_field.name == 'active_store':
|
if db_field.name == 'active_store':
|
||||||
# New songs won't have an 'object_id' until they are saved for the
|
kwargs['queryset'] = Store.objects.filter(
|
||||||
# first time. So on new songs, force an empty QuerySet.
|
song__pk=request.resolver_match.kwargs['object_id']
|
||||||
try:
|
)
|
||||||
kwargs['queryset'] = Store.objects.filter(
|
|
||||||
song__pk=request.resolver_match.kwargs['object_id']
|
|
||||||
)
|
|
||||||
except KeyError:
|
|
||||||
kwargs['queryset'] = Store.objects.none()
|
|
||||||
return super().formfield_for_foreignkey(db_field, request, **kwargs)
|
return super().formfield_for_foreignkey(db_field, request, **kwargs)
|
||||||
|
|
||||||
def artist_list(self, obj):
|
|
||||||
return ', '.join([a.full_name for a in obj.artists.all()])
|
|
||||||
|
|
||||||
# Admin Actions
|
# Admin Actions
|
||||||
|
|
||||||
## Metadata CRUD
|
def artist_list(self, obj):
|
||||||
|
return ', '.join([a.full_name for a in obj.artists.all()])
|
||||||
|
|
||||||
def add_album(self, request, queryset):
|
def add_album(self, request, queryset):
|
||||||
return change_items(request, queryset, 'album', 'add_album')
|
return change_items(request, queryset, 'album', 'add_album')
|
||||||
|
@ -350,28 +338,6 @@ class SongAdmin(RequestSongActionMixin,
|
||||||
return remove_items(request, queryset, 'game', 'remove_game')
|
return remove_items(request, queryset, 'game', 'remove_game')
|
||||||
remove_game.short_description = "Remove game from selected items"
|
remove_game.short_description = "Remove game from selected items"
|
||||||
|
|
||||||
## Allow multiple songs to be favorited by the admin user.
|
|
||||||
|
|
||||||
def add_favorites(self, request, queryset):
|
|
||||||
request.user.profile.favorites.add(*queryset)
|
|
||||||
message = quantify(len(queryset), queryset.model)
|
|
||||||
messages.success(
|
|
||||||
request,
|
|
||||||
'{} successfully added to favorites.'.format(message)
|
|
||||||
)
|
|
||||||
add_favorites.short_description = "Add selected songs to favorites"
|
|
||||||
|
|
||||||
def remove_favorites(self, request, queryset):
|
|
||||||
request.user.profile.favorites.remove(*queryset)
|
|
||||||
message = quantify(len(queryset), queryset.model)
|
|
||||||
messages.success(
|
|
||||||
request,
|
|
||||||
'{} successfully removed from favorites.'.format(message)
|
|
||||||
)
|
|
||||||
remove_favorites.short_description = "Remove selected songs from favorites"
|
|
||||||
|
|
||||||
## Publish songs if they are sitting unpublished.
|
|
||||||
|
|
||||||
def publish_songs(self, request, queryset):
|
def publish_songs(self, request, queryset):
|
||||||
publish_items(request, queryset)
|
publish_items(request, queryset)
|
||||||
publish_songs.short_description = "Publish selected songs"
|
publish_songs.short_description = "Publish selected songs"
|
||||||
|
|
|
@ -9,16 +9,12 @@ import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
from django.utils import timezone
|
|
||||||
|
|
||||||
from core.utils import path_to_iri
|
from core.utils import path_to_iri
|
||||||
from radio.models import Album, Artist, Game, Store, Song
|
from radio.models import Album, Artist, Game, Store, Song
|
||||||
|
|
||||||
decimal.getcontext().prec = 8
|
decimal.getcontext().prec = 8
|
||||||
|
|
||||||
PROGRESS = '\rAdding {}: [{:>{width}} / {}]'
|
|
||||||
COMPLETED = 'Imported {} {}'
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
'''Main "importoldreadio" command class'''
|
'''Main "importoldreadio" command class'''
|
||||||
|
@ -44,62 +40,32 @@ class Command(BaseCommand):
|
||||||
}
|
}
|
||||||
|
|
||||||
# Fetching albums first
|
# Fetching albums first
|
||||||
number = len(playlist['albums']) if 'albums' in playlist else 0
|
|
||||||
self.stdout.write('\n')
|
|
||||||
for album in playlist['albums']:
|
for album in playlist['albums']:
|
||||||
Album.objects.create(title=album['title'],
|
Album.objects.create(title=album['title'],
|
||||||
disabled=album['disabled'])
|
disabled=album['disabled'])
|
||||||
totals['albums'] += 1
|
totals['albums'] += 1
|
||||||
self.stdout.write(PROGRESS.format(
|
|
||||||
'albums',
|
self.stdout.write('Imported {} albums'.format(str(totals['albums'])))
|
||||||
totals['albums'],
|
|
||||||
number,
|
|
||||||
width=len(str(number))
|
|
||||||
), ending='')
|
|
||||||
self.stdout.flush()
|
|
||||||
self.stdout.write('\n')
|
|
||||||
self.stdout.write(COMPLETED.format(str(totals['albums']), 'albums'))
|
|
||||||
|
|
||||||
# Next up, artists
|
# Next up, artists
|
||||||
number = len(playlist['artists']) if 'artists' in playlist else 0
|
|
||||||
self.stdout.write('\n')
|
|
||||||
for artist in playlist['artists']:
|
for artist in playlist['artists']:
|
||||||
Artist.objects.create(alias=artist['alias'] or '',
|
Artist.objects.create(alias=artist['alias'] or '',
|
||||||
first_name=artist['first_name'] or '',
|
first_name=artist['first_name'] or '',
|
||||||
last_name=artist['last_name'] or '',
|
last_name=artist['last_name'] or '',
|
||||||
disabled=artist['disabled'])
|
disabled=artist['disabled'])
|
||||||
totals['artists'] += 1
|
totals['artists'] += 1
|
||||||
self.stdout.write(PROGRESS.format(
|
|
||||||
'artists',
|
self.stdout.write('Imported {} artists'.format(str(totals['artists'])))
|
||||||
totals['artists'],
|
|
||||||
number,
|
|
||||||
width=len(str(number))
|
|
||||||
), ending='')
|
|
||||||
self.stdout.flush()
|
|
||||||
self.stdout.write('\n')
|
|
||||||
self.stdout.write(COMPLETED.format(str(totals['artists']), 'artists'))
|
|
||||||
|
|
||||||
# On to games
|
# On to games
|
||||||
number = len(playlist['games']) if 'games' in playlist else 0
|
|
||||||
self.stdout.write('\n')
|
|
||||||
for game in playlist['games']:
|
for game in playlist['games']:
|
||||||
Game.objects.create(title=game['title'],
|
Game.objects.create(title=game['title'],
|
||||||
disabled=game['disabled'])
|
disabled=game['disabled'])
|
||||||
totals['games'] += 1
|
totals['games'] += 1
|
||||||
self.stdout.write(PROGRESS.format(
|
|
||||||
'games',
|
self.stdout.write('Imported {} games'.format(str(totals['games'])))
|
||||||
totals['games'],
|
|
||||||
number,
|
|
||||||
width=len(str(number))
|
|
||||||
), ending='')
|
|
||||||
self.stdout.flush()
|
|
||||||
self.stdout.write('\n')
|
|
||||||
self.stdout.write(COMPLETED.format(str(totals['games']), 'games'))
|
|
||||||
|
|
||||||
# Followed by the songs
|
# Followed by the songs
|
||||||
number = len(playlist['songs']) if 'songs' in playlist else 0
|
|
||||||
progress = '\rAdding requestables: [{:>{width}} / {}]'
|
|
||||||
self.stdout.write('\n')
|
|
||||||
for song in playlist['songs']:
|
for song in playlist['songs']:
|
||||||
try:
|
try:
|
||||||
album = Album.objects.get(title__exact=song['album'])
|
album = Album.objects.get(title__exact=song['album'])
|
||||||
|
@ -162,32 +128,26 @@ class Command(BaseCommand):
|
||||||
else:
|
else:
|
||||||
totals['jingles'] += 1
|
totals['jingles'] += 1
|
||||||
|
|
||||||
self.stdout.write(PROGRESS.format(
|
self.stdout.write(
|
||||||
'requestables',
|
'Imported {} requestables ({} songs, {} jingles)'.format(
|
||||||
totals['songs'] + totals['jingles'],
|
str(totals['songs'] + totals['jingles']),
|
||||||
number,
|
|
||||||
width=len(str(number))
|
|
||||||
), ending='')
|
|
||||||
self.stdout.flush()
|
|
||||||
self.stdout.write('\n')
|
|
||||||
self.stdout.write(COMPLETED.format(
|
|
||||||
str(totals['songs'] + totals['jingles']),
|
|
||||||
'requestables ({} songs, {} jingles)'.format(
|
|
||||||
str(totals['songs']),
|
str(totals['songs']),
|
||||||
str(totals['jingles'])
|
str(totals['jingles'])
|
||||||
)
|
)
|
||||||
))
|
)
|
||||||
|
|
||||||
pub = input('Do you want to publish all imported objects as well? '
|
pub = input('Do you want to publish all imported objects as well? '
|
||||||
'[Y/N] ')
|
'[Y/N] ')
|
||||||
|
|
||||||
if pub in ('Y', 'y'):
|
if pub in ('Y', 'y'):
|
||||||
date = timezone.now()
|
for album in Album.objects.all():
|
||||||
Album.objects.all().update(published_date=date)
|
album.publish()
|
||||||
Artist.objects.all().update(published_date=date)
|
for artist in Artist.objects.all():
|
||||||
Game.objects.all().update(published_date=date)
|
artist.publish()
|
||||||
Song.objects.all().update(published_date=date)
|
for game in Game.objects.all():
|
||||||
|
game.publish()
|
||||||
|
for song in Song.objects.all():
|
||||||
|
song.publish()
|
||||||
self.stdout.write('Published imported objects successfully')
|
self.stdout.write('Published imported objects successfully')
|
||||||
else:
|
else:
|
||||||
self.stdout.write('Skipped publishing songs')
|
self.stdout.write('Skipped publishing songs')
|
||||||
|
|
|
@ -246,20 +246,6 @@ class Song(Disableable, Publishable, Timestampable, models.Model):
|
||||||
return None
|
return None
|
||||||
average_rating = property(_average_rating)
|
average_rating = property(_average_rating)
|
||||||
|
|
||||||
def get_adjusted_ratio(self):
|
|
||||||
'''
|
|
||||||
Return a change to the replay ratio based on various factors.
|
|
||||||
'''
|
|
||||||
if radio_settings['replay__variance_enabled']:
|
|
||||||
min_ratings = radio_settings['replay__min_ratings_for_variance']
|
|
||||||
if self.rating_set.count() >= min_ratings:
|
|
||||||
ratio = radio_settings['replay__rating_variance_ratio']
|
|
||||||
|
|
||||||
# -((average - 1)/(highest_rating - 1)) * ratio
|
|
||||||
base = -((self._average_rating() - 1) / 4) * ratio
|
|
||||||
return float(base + (ratio * 0.5))
|
|
||||||
return float(0.0)
|
|
||||||
|
|
||||||
def get_time_until_requestable(self):
|
def get_time_until_requestable(self):
|
||||||
'''
|
'''
|
||||||
Length of time before a song can be requested again.
|
Length of time before a song can be requested again.
|
||||||
|
@ -281,7 +267,17 @@ 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:
|
||||||
adjusted_ratio = self.get_adjusted_ratio()
|
# Check if we have enough ratings to change ratio
|
||||||
|
min_rate = radio_settings['replay__min_ratings_for_variance']
|
||||||
|
if self.rating_set.count() >= min_rate:
|
||||||
|
ratio = radio_settings['replay__rating_variance_ratio']
|
||||||
|
|
||||||
|
# -((average - 1)/(highest_rating - 1)) * ratio
|
||||||
|
base = -((self._average_rating() - 1) / 4) * ratio
|
||||||
|
adjusted_ratio = float(base + (ratio * 0.5))
|
||||||
|
else:
|
||||||
|
adjusted_ratio = float(0.0)
|
||||||
|
|
||||||
return last + Song.music.wait_total(adjusted_ratio)
|
return last + Song.music.wait_total(adjusted_ratio)
|
||||||
return timezone.now()
|
return timezone.now()
|
||||||
return None
|
return None
|
||||||
|
|
Loading…
Reference in a new issue