diff --git a/api/funkwhale_api/music/models.py b/api/funkwhale_api/music/models.py index 080d159f5..fca044a16 100644 --- a/api/funkwhale_api/music/models.py +++ b/api/funkwhale_api/music/models.py @@ -463,8 +463,6 @@ class TrackQuerySet(common_models.LocalFromFidQuerySet, models.QuerySet): return self.exclude(pk__in=matches) def with_playable_uploads(self, actor): - if not actor: - uploads = Upload.objects.filter(library__privacy_level="public") uploads = Upload.objects.playable_by(actor) return self.prefetch_related( models.Prefetch("uploads", queryset=uploads, to_attr="playable_uploads") diff --git a/api/funkwhale_api/radios/radios.py b/api/funkwhale_api/radios/radios.py index 93825c03a..8834026a6 100644 --- a/api/funkwhale_api/radios/radios.py +++ b/api/funkwhale_api/radios/radios.py @@ -1,6 +1,7 @@ import datetime import json import logging +import pickle import random from typing import List, Optional, Tuple @@ -124,7 +125,10 @@ class SessionRadio(SimpleRadio): def cache_batch_radio_track(self, **kwargs): BATCH_SIZE = 100 # get cached RadioTracks if any - old_evaluated_radio_tracks = cache.get(f"radiosessiontracks{self.session.id}") + try : + cached_evaluated_radio_tracks = pickle.loads(cache.get(f"radiosessiontracks{self.session.id}")) + except TypeError : + cached_evaluated_radio_tracks = None # get the queryset and apply filters kwargs.update(self.get_queryset_kwargs()) @@ -138,24 +142,23 @@ class SessionRadio(SimpleRadio): # select a random batch of the qs sliced_queryset = queryset.order_by("?")[:BATCH_SIZE] - if len(sliced_queryset) == 0 and not old_evaluated_radio_tracks: + if len(sliced_queryset) <= 0 and not cached_evaluated_radio_tracks: raise ValueError("No more radio candidates") - if len(sliced_queryset) > 0: - # create the radio session tracks into db in bulk - radio_tracks = self.session.add(sliced_queryset) + # create the radio session tracks into db in bulk + radio_tracks = self.session.add(sliced_queryset) - # evaluate the queryset to save it in cache - evaluated_radio_tracks = [t for t in radio_tracks] - if old_evaluated_radio_tracks is not None: - evaluated_radio_tracks.append(old_evaluated_radio_tracks) - logger.info( - f"Setting redis cache for radio generation with radio id {self.session.id}" - ) - cache.set( - f"radiosessiontracks{self.session.id}", evaluated_radio_tracks, 3600 - ) - cache.set(f"radioqueryset{self.session.id}", sliced_queryset, 3600) + # evaluate the queryset to save it in cache + if cached_evaluated_radio_tracks is not None: + radio_tracks = [t for t in radio_tracks] + radio_tracks.extend(cached_evaluated_radio_tracks) + logger.info( + f"Setting redis cache for radio generation with radio id {self.session.id}" + ) + cache.set( + f"radiosessiontracks{self.session.id}", pickle.dumps(radio_tracks), 3600 + ) + cache.set(f"radioqueryset{self.session.id}", sliced_queryset, 3600) return sliced_queryset @@ -170,7 +173,8 @@ class SessionRadio(SimpleRadio): return queryset def get_choices_v2(self, quantity, **kwargs): - if cached_radio_tracks := cache.get(f"radiosessiontracks{self.session.id}"): + if cache.get(f"radiosessiontracks{self.session.id}") : + cached_radio_tracks = pickle.loads(cache.get(f"radiosessiontracks{self.session.id}")) logger.info("Using redis cache for radio generation") radio_tracks = cached_radio_tracks if len(radio_tracks) < quantity: diff --git a/api/funkwhale_api/radios/urls.py b/api/funkwhale_api/radios/urls.py index 4890b953f..7f1e3864b 100644 --- a/api/funkwhale_api/radios/urls.py +++ b/api/funkwhale_api/radios/urls.py @@ -5,7 +5,7 @@ from . import views router = routers.OptionalSlashRouter() router.register(r"sessions", views.RadioSessionViewSet, "sessions") router.register(r"radios", views.RadioViewSet, "radios") -router.register(r"tracks", views.RadioSessionTrackViewSet, "tracks") +router.register(r"tracks", views.V1_RadioSessionTrackViewSet, "tracks") urlpatterns = router.urls diff --git a/api/funkwhale_api/radios/views.py b/api/funkwhale_api/radios/views.py index 4dffaec04..80eb1561e 100644 --- a/api/funkwhale_api/radios/views.py +++ b/api/funkwhale_api/radios/views.py @@ -1,3 +1,5 @@ +import pickle + from django.core.cache import cache from django.db.models import Q from drf_spectacular.utils import extend_schema @@ -123,7 +125,7 @@ class RadioSessionViewSet( return context -class RadioSessionTrackViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet): +class V1_RadioSessionTrackViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet): serializer_class = serializers.RadioSessionTrackSerializer queryset = models.RadioSessionTrack.objects.all() permission_classes = [] @@ -206,9 +208,8 @@ class RadioSessionTracksViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet ) # self.perform_create(serializer) # dirty override here, since we use a different serializer for creation and detail - evaluated_radio_tracks = cache.get(f"radiosessiontracks{session.id}") + evaluated_radio_tracks = pickle.loads(cache.get(f"radiosessiontracks{session.id}")) batch = evaluated_radio_tracks[:count] - serializer = self.serializer_class( data=batch, context=self.get_serializer_context(), @@ -222,9 +223,9 @@ class RadioSessionTracksViewSet(mixins.CreateModelMixin, viewsets.GenericViewSet radiotrack.played = True RadioSessionTrack.objects.bulk_update(batch, ["played"]) - # delete the tracks we send from the cache + # delete the tracks we sent from the cache new_cached_radiotracks = evaluated_radio_tracks[count:] - cache.set(f"radiosessiontracks{session.id}", new_cached_radiotracks) + cache.set(f"radiosessiontracks{session.id}", pickle.dumps(new_cached_radiotracks)) return Response( serializer.data, status=status.HTTP_201_CREATED, headers=headers diff --git a/api/tests/radios/test_radios.py b/api/tests/radios/test_radios.py index 6760fb3e0..9a8b04477 100644 --- a/api/tests/radios/test_radios.py +++ b/api/tests/radios/test_radios.py @@ -1,5 +1,6 @@ import json import logging +import pickle import random import pytest @@ -103,8 +104,7 @@ def test_can_get_choices_for_custom_radio(factories): choices = session.radio.get_choices(filter_playable=False) expected = [t.pk for t in tracks] - for t in list(choices.values_list("id", flat=True)): - assert t in expected + assert list(choices.values_list("id", flat=True)) == expected def test_cannot_start_custom_radio_if_not_owner_or_not_public(factories): @@ -423,8 +423,7 @@ def test_get_choices_for_custom_radio_exclude_artist(factories): choices = session.radio.get_choices(filter_playable=False) expected = [u.track.pk for u in included_uploads] - for t in list(choices.values_list("id", flat=True)): - assert t in expected + assert list(choices.values_list("id", flat=True)) == expected def test_get_choices_for_custom_radio_exclude_tag(factories): @@ -442,8 +441,7 @@ def test_get_choices_for_custom_radio_exclude_tag(factories): choices = session.radio.get_choices(filter_playable=False) expected = [u.track.pk for u in included_uploads] - for t in list(choices.values_list("id", flat=True)): - assert t in expected + assert list(choices.values_list("id", flat=True)) == expected def test_can_start_custom_multiple_radio_from_api(api_client, factories): @@ -527,7 +525,7 @@ def test_can_cache_radio_track(factories): session = radio.start_session(user) picked = session.radio.pick_many_v2(quantity=1, filter_playable=False) assert len(picked) == 1 - for t in cache.get(f"radiosessiontracks{session.id}"): + for t in pickle.loads(cache.get(f"radiosessiontracks{session.id}")): assert t.track in uploads