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}
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)
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)
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)
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 )