示例#1
0
    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),
        )
示例#2
0
    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)
示例#3
0
    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)
示例#4
0
    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)
示例#5
0
    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),
        )
示例#6
0
    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)
示例#7
0
    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))
示例#8
0
    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)
示例#9
0
    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)
示例#10
0
    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