def test_jitter_query_uses_boundary_query(self): songs_mock = mock.MagicMock() energy = .5 valence = .75 danceability = .65 jitter = .2 generate_browse_playlist(energy, valence, danceability, jitter=jitter, songs=songs_mock) energy_lower_limit = energy - jitter energy_upper_limit = energy + jitter valence_lower_limit = valence - jitter valence_upper_limit = valence + jitter danceability_lower_limit = danceability - jitter danceability_upper_limit = danceability + jitter songs_mock.filter.assert_called_once_with( energy__range=(energy_lower_limit, energy_upper_limit), valence__range=(valence_lower_limit, valence_upper_limit), danceability__range=(danceability_lower_limit, danceability_upper_limit), )
def test_playlist_includes_songs_from_other_artists_if_top_artist_playlist_is_less_than_limit( self): top_artists = ['Madlib', 'MF DOOM', 'Surf Curse'] song_params = { 'energy': .5, 'valence': .75, 'danceability': .65, } top_artist_song = MoodyUtil.create_song(artist=top_artists[0], **song_params) # Create songs from other artists for _ in range(10): artist = ''.join( [random.choice(string.ascii_letters) for _ in range(10)]) MoodyUtil.create_song(artist=artist, **song_params) limit = 5 playlist = generate_browse_playlist(song_params['energy'], song_params['valence'], song_params['danceability'], jitter=0, top_artists=top_artists, limit=limit) self.assertEqual(len(playlist), limit) self.assertIn(top_artist_song, playlist)
def test_top_artists_passed_returns_default_playlist_if_no_matches_found( self): top_artists = ['Madlib', 'MF DOOM', 'Surf Curse'] song_params = { 'energy': .5, 'valence': .75, 'danceability': .65, } MoodyUtil.create_song(artist='Bum', **song_params) MoodyUtil.create_song(artist='Wack', **song_params) MoodyUtil.create_song(artist='Geek', **song_params) expected_playlist = list(Song.objects.exclude(artist__in=top_artists)) playlist = generate_browse_playlist(song_params['energy'], song_params['valence'], song_params['danceability'], jitter=0, top_artists=top_artists) # Sort playlists for comparison as the playlist is shuffled playlist = list(playlist) expected_playlist.sort(key=lambda song: song.code) playlist.sort(key=lambda song: song.code) self.assertListEqual(expected_playlist, playlist)
def test_invalid_strategy_passed_raises_exception(self): songs_mock = mock.MagicMock() energy = .5 valence = .75 danceability = .65 jitter = .2 strategy = 'invalid' with self.assertRaises(ValueError): generate_browse_playlist(energy, valence, danceability, strategy=strategy, jitter=jitter, songs=songs_mock)
def test_no_jitter_query_uses_params_passed(self): songs_mock = mock.MagicMock() energy = .5 valence = .75 danceability = .65 generate_browse_playlist(energy, valence, danceability, songs=songs_mock) songs_mock.filter.assert_called_once_with( energy__range=(energy, energy), valence__range=(valence, valence), danceability__range=(danceability, danceability), )
def test_happy_path(self): song = MoodyUtil.create_song(valence=.5, energy=.75) outlier_song = MoodyUtil.create_song(valence=.25, energy=.30) playlist = generate_browse_playlist(song.energy, song.valence, song.danceability) self.assertIn(song, playlist) self.assertNotIn(outlier_song, playlist)
def test_strategy_passed_filters_only_by_strategy_attribute(self): songs_mock = mock.MagicMock() energy = .5 valence = .75 danceability = .65 jitter = .2 strategy = 'energy' generate_browse_playlist(energy, valence, danceability, strategy=strategy, jitter=jitter, songs=songs_mock) energy_lower_limit = energy - jitter energy_upper_limit = energy + jitter songs_mock.filter.assert_called_once_with( energy__range=(energy_lower_limit, energy_upper_limit))
def test_limit_on_playlist(self): energy = .5 valence = .75 danceability = .65 for _ in range(10): MoodyUtil.create_song(energy=energy, valence=valence, danceability=danceability) playlist = generate_browse_playlist(energy, valence, danceability, limit=5) self.assertEqual(len(playlist), 5)
def test_artist_passed_only_returns_songs_from_artist(self): artist = 'TTK' song_params = { 'energy': .5, 'valence': .75, 'danceability': .65, } song_from_artist = MoodyUtil.create_song(artist=artist, **song_params) song_from_other_artist = MoodyUtil.create_song(artist='Bum', **song_params) playlist = generate_browse_playlist(song_params['energy'], song_params['valence'], song_params['danceability'], jitter=0, artist=artist) self.assertIn(song_from_artist, playlist) self.assertNotIn(song_from_other_artist, playlist)
def filter_queryset(self, queryset, **kwargs): cached_playlist_manager = CachedPlaylistManager(self.request.user) jitter = self.cleaned_data.get('jitter') limit = self.cleaned_data.get('limit') or self.default_limit artist = self.cleaned_data.get('artist') energy = None valence = None danceability = None strategy = random.choice(settings.BROWSE_PLAYLIST_STRATEGIES) # Should be able to supply 0 for jitter, so we'll check explicitly for None if jitter is None: jitter = self.default_jitter # Try to use upvotes for this emotion and context to generate attributes for songs to return if self.cleaned_data.get('context'): votes = self.request.user.usersongvote_set.filter( emotion__name=self.cleaned_data['emotion'], context=self.cleaned_data['context'], vote=True ) if votes.exists(): attributes_for_votes = average(votes, 'song__valence', 'song__energy', 'song__danceability') valence = attributes_for_votes['song__valence__avg'] energy = attributes_for_votes['song__energy__avg'] danceability = attributes_for_votes['song__danceability__avg'] # If context not provided or the previous query on upvotes for context did return any votes, # determine attributes from the attributes for the user and emotion if energy is None or valence is None or valence is None: user_emotion = self.request.user.get_user_emotion_record(self.cleaned_data['emotion']) # If the user doesn't have a UserEmotion record for the emotion, fall back to the # default attributes for the emotion if not user_emotion: emotion = Emotion.objects.get(name=self.cleaned_data['emotion']) energy = emotion.energy valence = emotion.valence danceability = emotion.danceability else: energy = user_emotion.energy valence = user_emotion.valence danceability = user_emotion.danceability # Try to fetch top artists for user from Spotify top_artists = None try: spotify_data = SpotifyUserData.objects.get(spotify_auth__user=self.request.user) top_artists = spotify_data.top_artists except SpotifyUserData.DoesNotExist: pass logger.info( 'Generating {} browse playlist for user {}'.format( self.cleaned_data['emotion'], self.request.user.username, ), extra={ 'fingerprint': auto_fingerprint('generate_browse_playlist', **kwargs), 'user_id': self.request.user.pk, 'emotion': Emotion.get_full_name_from_keyword(self.cleaned_data['emotion']), 'genre': self.cleaned_data.get('genre'), 'context': self.cleaned_data.get('context'), 'strategy': strategy, 'energy': energy, 'valence': valence, 'danceability': danceability, 'artist': artist, 'jitter': jitter, 'top_artists': top_artists, 'trace_id': self.request.trace_id, } ) playlist = generate_browse_playlist( energy, valence, danceability, strategy=strategy, limit=limit, jitter=jitter, artist=artist, top_artists=top_artists, songs=queryset ) cached_playlist_manager.cache_browse_playlist( playlist, self.cleaned_data['emotion'], self.cleaned_data.get('context'), self.cleaned_data.get('description') ) return playlist