Exemplo n.º 1
0
class VoteInfoView(GetRequestValidatorMixin, generics.RetrieveAPIView):
    """
    Returns a JSON response of info on votes for a given user, emotion, and song. Currently used to
    find the different contexts for a song that a user has voted on.
    """

    serializer_class = VoteInfoSerializer

    get_request_serializer = VoteInfoRequestSerializer

    if settings.DEBUG:  # pragma: no cover
        from base.documentation_utils import build_documentation_for_request_serializer
        schema = build_documentation_for_request_serializer(VoteInfoRequestSerializer, 'query')

    def get_object(self):
        contexts = UserSongVote.objects.filter(
            user=self.request.user,
            emotion__name=self.cleaned_data['emotion'],
            song__code=self.cleaned_data['song_code'],
        ).values_list(
            'context',
            flat=True,
        )

        return {'contexts': contexts}
Exemplo n.º 2
0
    def test_build_docs(self):
        location = 'query'

        expected_foo_schema = coreapi.Field(
            'foo',
            required=True,
            location=location,
            type='string',
            description='Test Char Field'
        )

        expected_bar_schema = coreapi.Field(
            'bar',
            required=False,
            location=location,
            type='integer',
            description='Test Int Field'
        )

        schema = build_documentation_for_request_serializer(DummyRequestSerializer, location)
        fields = schema._manual_fields

        self.assertEqual(fields[0], expected_foo_schema)
        self.assertEqual(fields[1], expected_bar_schema)
Exemplo n.º 3
0
    def test_get_manual_fields_for_patch_request_returns_patch_serializer_fields(self):
        schema = build_documentation_for_request_serializer(self.patch_request_serializer, 'form')
        expected_fields = schema._manual_fields
        fields = self.schema_instance.get_manual_fields('/', 'PATCH')

        self.assertEqual(fields, expected_fields)
Exemplo n.º 4
0
class BrowseView(GetRequestValidatorMixin, generics.ListAPIView):
    """
    Return a JSON response of Song records that match a given input query params.
    The main thing that should be passed is `emotion`, which is the mood in our
    system the user desires to feel.
    """
    serializer_class = SongSerializer
    queryset = Song.objects.all()

    default_jitter = settings.BROWSE_DEFAULT_JITTER
    default_limit = settings.BROWSE_DEFAULT_LIMIT

    get_request_serializer = BrowseSongsRequestSerializer

    if settings.DEBUG:  # pragma: no cover
        from base.documentation_utils import build_documentation_for_request_serializer
        schema = build_documentation_for_request_serializer(BrowseSongsRequestSerializer, 'query')

    def list(self, request, *args, **kwargs):
        resp = super().list(request, *args, **kwargs)
        resp.data.update({'trace_id': request.trace_id})

        return resp

    @update_logging_data
    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

    def get_queryset(self):
        queryset = super().get_queryset()

        if self.cleaned_data.get('genre'):
            queryset = queryset.filter(genre=self.cleaned_data['genre'])

        user_votes = self.request.user.usersongvote_set.filter(emotion__name=self.cleaned_data['emotion'])

        # If a context is provided, only exclude songs a user has voted on for that context
        # This allows a song to be a candidate for multiple context playlists for a particular emotion
        # Songs in WORK context could also be in PARTY context, maybe?
        if self.cleaned_data.get('context'):
            user_votes = user_votes.filter(context=self.cleaned_data['context'])

        previously_voted_song_ids = user_votes.values_list('song__id', flat=True)

        return queryset.exclude(id__in=previously_voted_song_ids)
Exemplo n.º 5
0
class PlaylistView(GetRequestValidatorMixin, generics.ListAPIView):
    """
    Returns a JSON response of songs that the user has voted as making them feel a desired emotion.
    """
    serializer_class = PlaylistSerializer
    queryset = UserSongVote.objects.all()

    get_request_serializer = PlaylistSongsRequestSerializer

    if settings.DEBUG:  # pragma: no cover
        from base.documentation_utils import build_documentation_for_request_serializer
        schema = build_documentation_for_request_serializer(PlaylistSongsRequestSerializer, 'query')

    @update_logging_data
    def list(self, request, *args, **kwargs):
        logger.info(
            'Generating {} emotion playlist for user {}'.format(
                self.cleaned_data['emotion'],
                self.request.user.username,
            ),
            extra={
                'fingerprint': auto_fingerprint('generate_emotion_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'),
                'artist': self.cleaned_data.get('artist'),
                'page': self.request.GET.get('page'),
                'trace_id': request.trace_id,
            }
        )

        resp = super().list(request, *args, **kwargs)

        first_page = last_page = None

        if resp.data['previous']:
            first_page = re.sub(r'&page=[0-9]*', '', resp.data['previous'])

        if resp.data['next']:
            last_page = re.sub(r'page=[0-9]*', 'page=last', resp.data['next'])

        queryset = self.filter_queryset(self.get_queryset())

        # Update response data with analytics for emotion
        votes_for_emotion_data = average(queryset, 'song__valence', 'song__energy', 'song__danceability')
        valence = votes_for_emotion_data['song__valence__avg'] or 0
        energy = votes_for_emotion_data['song__energy__avg'] or 0
        danceability = votes_for_emotion_data['song__danceability__avg'] or 0

        # Normalize emotion data as whole numbers
        normalized_valence = valence * 100
        normalized_energy = energy * 100
        normalized_danceability = danceability * 100

        resp.data.update({
            'valence': normalized_valence,
            'energy': normalized_energy,
            'danceability': normalized_danceability,
            'emotion_name': Emotion.get_full_name_from_keyword(self.cleaned_data['emotion']),
            'first_page': first_page,
            'last_page': last_page,
        })

        return resp

    def filter_queryset(self, queryset):
        if self.cleaned_data.get('genre'):
            queryset = queryset.filter(song__genre=self.cleaned_data['genre'])

        if self.cleaned_data.get('context'):
            queryset = queryset.filter(context=self.cleaned_data['context'])

        if self.cleaned_data.get('artist'):
            queryset = queryset.filter(song__artist__icontains=self.cleaned_data['artist'])

        return filter_duplicate_votes_on_song_from_playlist(queryset)

    def get_queryset(self):
        queryset = super().get_queryset()

        return queryset.filter(
            user=self.request.user,
            emotion__name=self.cleaned_data['emotion'],
            vote=True
        )