コード例 #1
0
ファイル: views.py プロジェクト: Moody-Tunes/moodytunes
    def post(self, request, *args, **kwargs):
        # Reject request if user is ratelimited
        if request.limited:
            logger.warning(
                'User {} has been rate limited from suggesting songs'.format(
                    request.user.username),
                extra={
                    'fingerprint':
                    auto_fingerprint('rate_limit_suggest_song', **kwargs),
                    'user_id':
                    request.user.id,
                    'trace_id':
                    request.trace_id,
                })
            messages.error(
                request,
                'You have submitted too many suggestions! Try again in a minute'
            )
            return HttpResponseRedirect(reverse('spotify:suggest'))

        form = self.form_class(request.POST)

        if form.is_valid():
            code = form.cleaned_data['code']
            FetchSongFromSpotifyTask().delay(code,
                                             username=request.user.username,
                                             trace_id=request.trace_id)

            logger.info(
                'Called task to add suggestion for song {} by user {}'.format(
                    code, request.user.username),
                extra={
                    'fingerprint':
                    auto_fingerprint('added_suggested_song', **kwargs),
                    'trace_id':
                    request.trace_id,
                })

            messages.info(
                request,
                'Your song has been slated to be added! Keep an eye out for it in the future'
            )

            return HttpResponseRedirect(reverse('spotify:suggest'))
        else:
            logger.warning('User {} suggested an invalid song code: {}'.format(
                request.user.username,
                request.POST.get('code'),
            ),
                           extra={
                               'fingerprint':
                               auto_fingerprint('invalid_suggested_song',
                                                **kwargs),
                               'trace_id':
                               request.trace_id,
                               'errors':
                               form.errors,
                           })

            return render(request, self.template_name, context={'form': form})
コード例 #2
0
    def create(self, request, *args, **kwargs):
        try:
            song = Song.objects.get(code=self.cleaned_data['song_code'])
        except (Song.DoesNotExist, Song.MultipleObjectsReturned):
            logger.warning(
                'Unable to retrieve song with code {}'.format(self.cleaned_data['song_code']),
                extra={
                    'fingerprint': auto_fingerprint('song_not_found', **kwargs),
                    'trace_id': request.trace_id,
                }
            )

            raise Http404('No song exists with code: {}'.format(self.cleaned_data['song_code']))

        emotion = Emotion.objects.get(name=self.cleaned_data['emotion'])

        vote_data = {
            'user_id': self.request.user.id,
            'emotion_id': emotion.id,
            'song_id': song.id,
            'vote': self.cleaned_data['vote'],
            'context': self.cleaned_data.get('context', ''),
            'description': self.cleaned_data.get('description', '')
        }

        try:
            vote = UserSongVote(**vote_data)
            vote._trace_id = request.trace_id
            vote.save()

            logger.info(
                'Saved vote for user {} voting on song {} for emotion {}'.format(
                    self.request.user.username,
                    song.code,
                    emotion.full_name
                ),
                extra={
                    'vote_data': vote_data,
                    'emotion': emotion.full_name,
                    'vote_id': vote.pk,
                    'fingerprint': auto_fingerprint('created_new_vote', **kwargs),
                    'trace_id': request.trace_id,
                }
            )

            return JsonResponse({'status': 'OK'}, status=status.HTTP_201_CREATED)

        except IntegrityError as exc:
            logger.warning(
                'Bad data supplied to VoteView.create from {}'.format(self.request.user.username),
                extra={
                    'vote_data': vote_data,
                    'fingerprint': auto_fingerprint('bad_vote_data', **kwargs),
                    'exception_info': exc,
                    'trace_id': request.trace_id,
                }
            )

            raise ValidationError('Bad data supplied to {}'.format(self.__class__.__name__))
コード例 #3
0
ファイル: tasks.py プロジェクト: Moody-Tunes/moodytunes
    def run(self, auth_id, playlist_name, songs, cover_image_filename=None, *args, **kwargs):
        self.trace_id = kwargs.get('trace_id', '')
        auth = SpotifyAuth.get_and_refresh_spotify_auth_record(auth_id, trace_id=self.trace_id)

        # Check that user has granted proper scopes to export playlist to Spotify
        if not auth.has_scope(settings.SPOTIFY_PLAYLIST_MODIFY_SCOPE):
            logger.error(
                'User {} has not granted proper scopes to export playlist to Spotify'.format(auth.user.username),
                extra={
                    'fingerprint': auto_fingerprint('missing_scopes_for_playlist_export', **kwargs),
                    'auth_id': auth.pk,
                    'scopes': auth.scopes,
                    'trace_id': self.trace_id,
                }
            )

            raise InsufficientSpotifyScopesError('Insufficient Spotify scopes to export playlist')

        spotify = SpotifyClient(identifier='spotify.tasks.ExportSpotifyPlaylistFromSongsTask-{}'.format(self.trace_id))

        logger.info(
            'Exporting songs to playlist {} for user {} on Spotify'.format(playlist_name, auth.spotify_user_id),
            extra={
                'fingerprint': auto_fingerprint('start_export_playlist', **kwargs),
                'auth_id': auth.pk,
                'trace_id': self.trace_id,
            }
        )

        playlist_id = self.get_or_create_playlist(auth.access_token, auth.spotify_user_id, playlist_name, spotify)

        # Upload cover image for playlist if specified
        if auth.has_scope(settings.SPOTIFY_UPLOAD_PLAYLIST_IMAGE) and cover_image_filename:
            self.upload_cover_image(auth.access_token, playlist_id, cover_image_filename, spotify)

        self.delete_all_songs_from_playlist(auth.access_token, playlist_id, spotify)
        self.add_songs_to_playlist(auth.access_token, playlist_id, songs, spotify)

        # Delete cover image file from disk if present
        #
        # Do this after uploading songs to playlist to keep image file on disk
        # in case of errors with uploading songs to playlist to ensure that if
        # we need to retry because of errors with adding/deleting songs in playlist
        # that we still have the image file on disk for retries.
        if cover_image_filename:
            try:
                os.unlink(cover_image_filename)  # pragma: no cover
            except FileNotFoundError:
                pass

        logger.info(
            'Exported songs to playlist {} for user {} successfully'.format(playlist_name, auth.spotify_user_id),
            extra={
                'fingerprint': auto_fingerprint('success_export_playlist', **kwargs),
                'auth_id': auth.pk,
                'trace_id': self.trace_id,
            }
        )
コード例 #4
0
ファイル: tasks.py プロジェクト: Moody-Tunes/moodytunes
    def run(self, spotify_code, username='******', *args, **kwargs):
        """
        Use Spotify API to fetch song data for a given song and save the song to the database

        :param spotify_code: (str) Spotify URI for the song to be created
        :param username: (str) [Optional] Username for the user that requested this song
        """
        trace_id = kwargs.get('trace_id', '')
        signature = 'spotify.tasks.FetchSongFromSpotifyTask-{}'.format(trace_id)

        # Early exit: if song already exists in our system don't do the work to fetch it
        if Song.objects.filter(code=spotify_code).exists():
            logger.info(
                'Song with code {} already exists in database'.format(spotify_code),
                extra={
                    'fingerprint': auto_fingerprint('song_already_exists', **kwargs),
                    'trace_id': trace_id,
                }
            )

            return

        client = SpotifyClient(identifier=signature)

        track_data = client.get_attributes_for_track(spotify_code)
        song_data = client.get_audio_features_for_tracks([track_data])[0]

        try:
            Song.objects.create(**song_data)

            logger.info(
                'Created song {} in database'.format(spotify_code),
                extra={
                    'fingerprint': auto_fingerprint('created_song', **kwargs),
                    'song_data': song_data,
                    'username': username,
                    'trace_id': trace_id,
                }
            )
        except ValidationError:
            logger.exception(
                'Failed to create song {}, already exists in database'.format(spotify_code),
                extra={
                    'fingerprint': auto_fingerprint('failed_to_create_song', **kwargs),
                    'trace_id': trace_id,
                }
            )

            raise
コード例 #5
0
    def handle(self, *args, **options):
        self.write_to_log_and_output(
            'Starting run to create songs from Spotify',
            extra={
                'fingerprint':
                auto_fingerprint('start_create_songs_from_spotify', **options)
            })

        tracks = self.get_tracks_from_spotify()

        if not tracks:
            # If we didn't get any tracks back from Spotify, raise an exception
            # This will get caught by the periodic task and retry the script again
            self.write_to_log_and_output(
                'Failed to fetch any tracks from Spotify',
                output_stream='stderr',
                log_level=logging.WARNING,
                extra={
                    'fingerprint':
                    auto_fingerprint('failed_to_fetch_tracks_from_spotify',
                                     **options)
                })

            raise CommandError('Failed to fetch any songs from Spotify')

        self.write_to_log_and_output(
            'Got {} tracks from Spotify'.format(len(tracks)),
            extra={
                'fingerprint':
                auto_fingerprint('fetched_tracks_from_spotify', **options),
                'fetched_tracks':
                len(tracks)
            })

        succeeded, failed = self.save_songs_to_database(tracks)

        self.write_to_log_and_output(
            'Finished run to create songs from Spotify',
            extra={
                'fingerprint':
                auto_fingerprint('finish_create_songs_from_spotify',
                                 **options),
                'saved_tracks':
                succeeded,
                'failed_tracks':
                failed
            })

        return 'Created Songs: {}'.format(succeeded)
コード例 #6
0
ファイル: tasks.py プロジェクト: Moody-Tunes/moodytunes
    def run(self, *args, **kwargs):
        logger.info('Starting run to backup mission critical database tables',
                    extra={
                        'fingerprint':
                        auto_fingerprint('start_database_backup', **kwargs)
                    })

        self.delete_old_backups()
        self.backup_models()

        logger.info('Finished run to backup mission critical database tables',
                    extra={
                        'fingerprint':
                        auto_fingerprint('finished_database_backup', **kwargs)
                    })
コード例 #7
0
    def test_happy_path(self):
        test = Test()
        kwargs = test.foo()
        fingerprint = auto_fingerprint('testing', **kwargs)

        self.assertEqual(fingerprint,
                         'libs.tests.test_moody_logging.Test.foo.testing')
コード例 #8
0
ファイル: models.py プロジェクト: Moody-Tunes/moodytunes
    def get_and_refresh_spotify_auth_record(cls, auth_id, **kwargs):
        """
        Fetch the SpotifyAuth record for the given primary key, and refresh if
        the access token is expired

        :param auth_id: (int) Primary key for SpotifyAuth record

        :return: (SpotifyAuth)
        """
        trace_id = kwargs.get('trace_id', '')

        try:
            auth = cls.objects.get(pk=auth_id)
        except (SpotifyAuth.MultipleObjectsReturned, SpotifyAuth.DoesNotExist):
            logger.error(
                'Failed to fetch SpotifyAuth with pk={}'.format(auth_id),
                extra={
                    'fingerprint': auto_fingerprint('failed_to_fetch_spotify_auth', **kwargs),
                    'trace_id': trace_id,
                },
            )

            raise

        if auth.should_refresh_access_token:
            auth.refresh_access_token(trace_id=trace_id)

        return auth
コード例 #9
0
ファイル: tasks.py プロジェクト: Moody-Tunes/moodytunes
    def get_or_create_playlist(self, auth_code, spotify_user_id, playlist_name, spotify, **kwargs):
        """
        Get the Spotify playlist by name for the user, creating it if it does not exist

        :param auth_code: (str) SpotifyAuth access_token for the given user
        :param spotify_user_id: (str) Spotify username for the given user
        :param playlist_name: (str) Name of the playlist to be created
        :param spotify: (spotify_client.SpotifyClient) Spotify Client instance

        :return: (str)
        """
        playlist_id = None

        try:
            resp = spotify.get_user_playlists(auth_code, spotify_user_id)
            playlists = resp['items']

            for playlist in playlists:
                if playlist['name'] == playlist_name:
                    playlist_id = playlist['id']
                    break

        except SpotifyException:
            logger.warning(
                'Error getting playlists for user {}'.format(spotify_user_id),
                exc_info=True,
                extra={
                    'fingerprint': auto_fingerprint('failed_getting_user_playlists', **kwargs),
                    'spotify_user_id': spotify_user_id,
                    'trace_id': self.trace_id,
                }
            )

        if playlist_id is None:
            playlist_id = spotify.create_playlist(auth_code, spotify_user_id, playlist_name)
            logger.info(
                'Created playlist for user {} with name {} successfully'.format(spotify_user_id, playlist_name),
                extra={
                    'fingerprint': auto_fingerprint('created_spotify_playlist', **kwargs),
                    'trace_id': self.trace_id,
                }
            )

        return playlist_id
コード例 #10
0
    def destroy(self, request, *args, **kwargs):
        votes = UserSongVote.objects.filter(
            user_id=self.request.user.id,
            emotion__name=self.cleaned_data['emotion'],
            song__code=self.cleaned_data['song_code'],
            vote=True
        )

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

        if not votes.exists():
            logger.warning(
                'Unable to find UserSongVote to delete',
                extra={
                    'request_data': self.cleaned_data,
                    'fingerprint': auto_fingerprint('unvote_fail_missing_vote', **kwargs),
                    'trace_id': request.trace_id,
                }
            )

            raise Http404()

        for vote in votes:
            vote._trace_id = request.trace_id
            vote.delete()

            logger.info(
                'Deleted vote for user {} with song {} and emotion {} and context {}'.format(
                    self.request.user.username,
                    self.cleaned_data['song_code'],
                    Emotion.get_full_name_from_keyword(self.cleaned_data['emotion']),
                    vote.context or 'None',
                ),
                extra={
                    'fingerprint': auto_fingerprint('unvote_success', **kwargs),
                    'vote_id': vote.pk,
                    'data': self.cleaned_data,
                    'trace_id': request.trace_id,
                }
            )

        return JsonResponse({'status': 'OK'})
コード例 #11
0
ファイル: tasks.py プロジェクト: Moody-Tunes/moodytunes
    def run(self, *args, **kwargs):
        auth_records = SpotifyAuth.objects.all()

        logger.info(
            'Starting run to refresh top artists for {} auth records'.format(auth_records.count()),
            extra={'fingerprint': auto_fingerprint('refresh_top_artists', **kwargs)}
        )

        for auth in auth_records:
            UpdateTopArtistsFromSpotifyTask().delay(auth.pk)
コード例 #12
0
ファイル: tasks.py プロジェクト: Moody-Tunes/moodytunes
    def run(self, user_id, *args, **kwargs):
        """
        Create UserEmotion records for a user for each emotion we have in our system

        :param user_id: (int) Primary key for user record
        """
        trace_id = kwargs.get('trace_id', '')

        try:
            user = MoodyUser.objects.get(pk=user_id)
        except (MoodyUser.DoesNotExist, MoodyUser.MultipleObjectsReturned):
            logger.exception(
                'Unable to fetch MoodyUser with pk={}'.format(user_id),
                extra={
                    'fingerprint':
                    auto_fingerprint('failed_to_fetch_user', **kwargs),
                    'trace_id':
                    trace_id,
                })
            raise

        user_emotions = []
        for emotion in Emotion.objects.all().iterator():
            user_emotions.append(
                UserEmotion(
                    user=user,
                    emotion=emotion,
                    energy=emotion.energy,
                    valence=emotion.valence,
                    danceability=emotion.danceability,
                ))

        UserEmotion.objects.bulk_create(user_emotions)

        logger.info(
            'Created UserEmotion records for user {}'.format(user.username),
            extra={
                'fingerprint':
                auto_fingerprint('created_user_emotion_records', **kwargs),
                'trace_id':
                trace_id,
            })
コード例 #13
0
    def post(self, request, *args, **kwargs):
        form = self.form_class(request.POST)

        if form.is_valid():
            user = MoodyUser(
                username=form.cleaned_data['username'],
                email=form.cleaned_data.get('email'),
            )

            user.set_password(form.cleaned_data['password'])
            user._trace_id = request.trace_id
            user.save()

            UserProfile.objects.create(user=user)

            logger.info(
                'Created new user: {}'.format(user.username),
                extra={
                    'fingerprint': auto_fingerprint('created_new_user', **kwargs),
                    'trace_id': request.trace_id,
                }
            )

            messages.info(request, 'Your account has been created.')

            return HttpResponseRedirect(settings.LOGIN_URL)
        else:
            logger.warning(
                'Failed to create new user because of invalid data',
                extra={
                    'errors': form.errors,
                    'request_data': {
                        'username': request.POST.get('username'),
                        'email': request.POST.get('email')
                    },
                    'fingerprint': auto_fingerprint('failed_to_create_new_user', **kwargs),
                    'trace_id': request.trace_id
                }
            )

            return render(request, self.template_name, {'form': form})
コード例 #14
0
ファイル: tasks.py プロジェクト: Moody-Tunes/moodytunes
    def run(self, auth_id, *args, **kwargs):
        trace_id = kwargs.get('trace_id', '')
        auth = SpotifyAuth.get_and_refresh_spotify_auth_record(auth_id, trace_id=trace_id)

        # Check that user has granted proper scopes to fetch top artists from Spotify
        if not auth.has_scope(settings.SPOTIFY_TOP_ARTIST_READ_SCOPE):
            logger.error(
                'User {} has not granted proper scopes to fetch top artists from Spotify'.format(auth.user.username),
                extra={
                    'fingerprint': auto_fingerprint('missing_scopes_for_update_top_artists', **kwargs),
                    'auth_id': auth.pk,
                    'scopes': auth.scopes,
                    'trace_id': trace_id,
                }
            )

            raise InsufficientSpotifyScopesError('Insufficient Spotify scopes to fetch Spotify top artists')

        spotify_client_identifier = 'update_spotify_top_artists_{}'.format(auth.spotify_user_id)
        spotify = SpotifyClient(identifier=spotify_client_identifier)

        logger.info(
            'Updating top artists for {}'.format(auth.spotify_user_id),
            extra={
                'fingerprint': auto_fingerprint('update_spotify_top_artists', **kwargs),
                'trace_id': trace_id,
            }
        )

        artists = spotify.get_user_top_artists(auth.access_token, settings.SPOTIFY['max_top_artists'])
        spotify_user_data, _ = SpotifyUserData.objects.get_or_create(spotify_auth=auth)
        spotify_user_data.top_artists = artists
        spotify_user_data.save()

        logger.info(
            'Successfully updated top artists for {}'.format(auth.spotify_user_id),
            extra={
                'fingerprint': auto_fingerprint('success_update_spotify_top_artists', **kwargs),
                'trace_id': trace_id,
            }
        )
コード例 #15
0
    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
コード例 #16
0
ファイル: tasks.py プロジェクト: Moody-Tunes/moodytunes
 def delete_old_backups(self, **kwargs):
     for backup_file in os.listdir(settings.DATABASE_BACKUPS_PATH):
         if any([
                 backup_file.startswith(model)
                 for model in settings.DATABASE_BACKUP_TARGETS
         ]):
             backup_filename = os.path.join(settings.DATABASE_BACKUPS_PATH,
                                            backup_file)
             logger.info('Deleting old backup {}'.format(backup_filename),
                         extra={
                             'fingerprint':
                             auto_fingerprint('delete_old_backup', **kwargs)
                         })
             os.unlink(backup_filename)
コード例 #17
0
ファイル: models.py プロジェクト: Moody-Tunes/moodytunes
    def refresh_access_token(self, **kwargs):
        """Make a call to the Spotify API to refresh the access token for the SpotifyAuth record"""
        trace_id = kwargs.get('trace_id', '')
        spotify_client = SpotifyClient(identifier='refresh-access-token:{}'.format(self.spotify_user_id))

        try:
            access_token = spotify_client.refresh_access_token(self.refresh_token)

            self.access_token = access_token
            self.last_refreshed = timezone.now()

            self.save()

            logger.info(
                'Refreshed access token for {}'.format(self.spotify_user_id),
                extra={
                    'fingerprint': auto_fingerprint('success_refresh_access_token', **kwargs),
                    'spotify_username': self.spotify_user_id,
                    'auth_id': self.pk,
                    'user_id': self.user_id,
                    'trace_id': trace_id,
                }
            )

        except SpotifyException:
            logger.exception(
                'Unable to refresh access token for {}'.format(self.spotify_user_id),
                extra={
                    'fingerprint': auto_fingerprint('failed_refresh_access_token', **kwargs),
                    'spotify_username': self.spotify_user_id,
                    'auth_id': self.pk,
                    'user_id': self.user_id,
                    'trace_id': trace_id,
                }
            )

            raise
コード例 #18
0
ファイル: tasks.py プロジェクト: Moody-Tunes/moodytunes
    def backup_models(self, **kwargs):
        for model in settings.DATABASE_BACKUP_TARGETS:
            backup_filename = '{backup_directory}/{model_name}.json'.format(
                backup_directory=settings.DATABASE_BACKUPS_PATH,
                model_name=model)

            logger.info('Writing backup of {} to file {}'.format(
                model, backup_filename),
                        extra={
                            'fingerprint':
                            auto_fingerprint('backup_database_model',
                                             **kwargs),
                            'model':
                            model,
                        })

            call_command('dumpdata', model, output=backup_filename)
コード例 #19
0
    def update(self, request, *args, **kwargs):
        user_profile = get_object_or_404(UserProfile, user=request.user)

        for name, value in self.cleaned_data.items():
            setattr(user_profile, name, value)

        user_profile.save()

        logger.info(
            'Updated UserProfile record for user {}'.format(request.user.username),
            extra={
                'fingerprint': auto_fingerprint('updated_user_profile', **kwargs),
                'request_data': self.cleaned_data,
                'trace_id': request.trace_id
            }
        )

        return JsonResponse({'status': 'OK'})
コード例 #20
0
ファイル: views.py プロジェクト: Moody-Tunes/moodytunes
    def post(self, request, *args, **kwargs):
        auth = request.spotify_auth
        auth_id = auth.pk
        auth.delete()

        logger.info('Deleted SpotifyAuth for user {}'.format(
            request.user.username),
                    extra={
                        'fingerprint':
                        auto_fingerprint('revoked_spotify_auth', **kwargs),
                        'auth_id':
                        auth_id,
                        'trace_id':
                        request.trace_id,
                    })

        messages.info(request,
                      'We have deleted your Spotify data from Moodytunes')
        return HttpResponseRedirect(reverse('accounts:profile'))
コード例 #21
0
    def get_user_emotion_record(self, emotion_name, **kwargs):
        """
        Return the UserEmotion record for a given Emotion name.

        :param emotion_name: (str) `Emotion.name` constant to retrieve

        :return: (UserEmotion)
        """
        try:
            return self.useremotion_set.get(emotion__name=emotion_name)
        except UserEmotion.DoesNotExist:
            logger.warning('User {} has no UserEmotion record for {}'.format(
                self.username, emotion_name),
                           extra={
                               'fingerprint':
                               auto_fingerprint('user_emotion_not_found',
                                                **kwargs)
                           })
            return None
コード例 #22
0
ファイル: tasks.py プロジェクト: Moody-Tunes/moodytunes
    def upload_cover_image(self, auth_code, playlist_id, cover_image_filename, spotify, **kwargs):
        """
        Upload custom cover image for playlist. If any errors were encountered it will just fail
        silently.

        :param auth_code: (str) SpotifyAuth access_token for the given user
        :param playlist_id: (str) Spotify ID of the playlist to be created
        :param cover_image_filename: (str) Filename of cover image as a file on disk
        :param spotify: (spotify_client.SpotifyClient) Spotify Client instance
        """
        try:
            spotify.upload_image_to_playlist(auth_code, playlist_id, cover_image_filename)
        except (SpotifyException, ClientException):
            logger.warning(
                'Unable to upload cover image for playlist {}'.format(playlist_id),
                extra={
                    'fingerprint': auto_fingerprint('failed_upload_cover_image', **kwargs),
                    'trace_id': self.trace_id,
                },
                exc_info=True
            )
コード例 #23
0
    def _log_bad_request(self, request, serializer, **kwargs):
        """
        Helper method to log information about a bad request to our system.

        :param request: (rest_framework.request.Request) Request object that had failed validation
        :param serializer: (rest_framework.serializers.Serializer) Serializer object that rejected the request
        """

        # Filter HTTP headers from request metadata
        request_headers = request.META
        http_headers = dict([(header, value)
                             for header, value in request_headers.items()
                             if header.startswith('HTTP')])
        cleaned_headers = self._clean_headers(copy.deepcopy(http_headers))

        request_data = {
            'params':
            request.GET,
            'data':
            request.data,
            'user_id':
            request.user.id,
            'headers':
            cleaned_headers,
            'method':
            request.method,
            'errors':
            serializer.errors,
            'view':
            '{}.{}'.format(self.__class__.__module__, self.__class__.__name__),
            'fingerprint':
            auto_fingerprint('bad_request', **kwargs),
            'trace_id':
            request.trace_id
        }

        logger.warning('Invalid {} data supplied to {}'.format(
            request.method, self.__class__.__name__),
                       extra=request_data)
コード例 #24
0
    def post(self, request, *args, **kwargs):
        form = self.form_class(request.POST, user=request.user)

        if form.is_valid():
            request.user.update_information(form.cleaned_data)
            messages.info(request, 'Your account information has been updated.')

            return HttpResponseRedirect(reverse('accounts:profile'))
        else:
            logger.warning(
                'Failed to update user info because of invalid data',
                extra={
                    'errors': form.errors,
                    'request_data': {
                        'username': request.POST.get('username'),
                        'email': request.POST.get('email')
                    },
                    'user_id': request.user.id,
                    'fingerprint': auto_fingerprint('failed_to_update_user_info', **kwargs),
                    'trace_id': request.trace_id,
                }
            )
            return render(request, self.template_name, {'form': form})
コード例 #25
0
    def get_object(self, **kwargs):
        cached_playlist_manager = CachedPlaylistManager(self.request.user)
        cached_playlist = cached_playlist_manager.retrieve_cached_browse_playlist()

        if cached_playlist:
            emotion = cached_playlist['emotion']
            playlist = cached_playlist['playlist']
            context = cached_playlist.get('context')
            description = cached_playlist.get('description')

            # Filter out songs user has already voted on from the playlist
            # for the emotion to prevent double votes on songs
            user_voted_songs = self.request.user.usersongvote_set.filter(
                emotion__name=emotion
            ).values_list(
                'song__pk', flat=True
            )

            playlist = [song for song in playlist if song.pk not in user_voted_songs]

            return {
                'emotion': emotion,
                'context': context,
                'description': description,
                'playlist': playlist,
                'trace_id': self.request.trace_id,
            }
        else:
            logger.warning(
                'No cached browse playlist found for user {}'.format(self.request.user.username),
                extra={
                    'fingerprint': auto_fingerprint('no_cached_browse_playlist_found', **kwargs),
                    'trace_id': self.request.trace_id,
                }
            )

            raise Http404('No cached browse playlist found')
コード例 #26
0
ファイル: views.py プロジェクト: Moody-Tunes/moodytunes
    def get(self, request, *args, **kwargs):
        auth = request.spotify_auth

        # Check that user has the proper scopes from Spotify to create playlist
        for scope in settings.SPOTIFY['auth_user_scopes']:
            if not auth.has_scope(scope):
                logger.info(
                    'User {} does not have proper scopes for playlist export. Redirecting to grant scopes'
                    .format(request.user.username),
                    extra={
                        'user_id':
                        request.user.pk,
                        'auth_id':
                        auth.pk,
                        'scopes':
                        auth.scopes,
                        'expected_scopes':
                        settings.SPOTIFY['auth_user_scopes'],
                        'fingerprint':
                        auto_fingerprint('missing_scopes_for_playlist_export',
                                         **kwargs),
                        'trace_id':
                        request.trace_id,
                    })

                auth.delete(
                )  # Delete SpotifyAuth record to ensure that it can be created with proper scopes

                messages.info(
                    request,
                    'Please reauthenticate with Spotify to export your playlist'
                )

                return HttpResponseRedirect(reverse('spotify:spotify-auth'))

        return super().get(request, *args, **kwargs)
コード例 #27
0
    def get_tracks_from_spotify(self, **kwargs):
        """
        Request, format, and return tracks from Spotify's API.

        :return: (list(dict)) Track data for saving as Song records
        """
        spotify = SpotifyClient(
            identifier='create_songs_from_spotify-{}'.format(self._unique_id))

        tracks = []

        for category in settings.SPOTIFY['categories']:
            songs_from_category = 0

            try:
                playlists = spotify.get_playlists_for_category(
                    category, settings.SPOTIFY['max_playlist_from_category'])
                self.logger.info('Got {} playlists for category: {}'.format(
                    len(playlists), category),
                                 extra={
                                     'fingerprint':
                                     auto_fingerprint(
                                         'retrieved_playlists_for_category',
                                         **kwargs),
                                     'command_id':
                                     self._unique_id
                                 })

                for playlist in playlists:
                    if songs_from_category < settings.SPOTIFY[
                            'max_songs_from_category']:
                        num_tracks = settings.SPOTIFY[
                            'max_songs_from_category'] - songs_from_category
                        self.logger.info(
                            'Calling Spotify API to get {} track(s) for playlist {}'
                            .format(num_tracks, playlist['name']),
                            extra={
                                'fingerprint':
                                auto_fingerprint('get_tracks_from_playlist',
                                                 **kwargs),
                                'tracks_to_retrieve':
                                num_tracks,
                                'command_id':
                                self._unique_id
                            })

                        raw_tracks = spotify.get_songs_from_playlist(
                            playlist, num_tracks)

                        self.logger.info(
                            'Calling Spotify API to get feature data for {} tracks'
                            .format(len(raw_tracks)),
                            extra={
                                'fingerprint':
                                auto_fingerprint('get_feature_data_for_tracks',
                                                 **kwargs),
                                'command_id':
                                self._unique_id
                            })

                        complete_tracks = spotify.get_audio_features_for_tracks(
                            raw_tracks)

                        # Add genre information to each track. We can use the category search term as the genre
                        # for songs found for that category
                        for track in complete_tracks:
                            track.update({'genre': category})

                        self.logger.info(
                            'Got {} tracks from {}'.format(
                                len(complete_tracks), playlist['name']),
                            extra={
                                'fingerprint':
                                auto_fingerprint(
                                    'retrieved_tracks_from_playlist',
                                    **kwargs),
                                'command_id':
                                self._unique_id
                            })

                        tracks.extend(complete_tracks)
                        songs_from_category += len(complete_tracks)

                self.write_to_log_and_output(
                    'Finished processing {} tracks for category: {}'.format(
                        songs_from_category, category),
                    extra={
                        'fingerprint':
                        auto_fingerprint('processed_tracks_for_category',
                                         **kwargs)
                    })

            except SpotifyException as exc:
                self.write_to_log_and_output(
                    'Error connecting to Spotify! Exception detail: {}. '
                    'Got {} track(s) successfully.'.format(exc, len(tracks)),
                    output_stream='stderr',
                    log_level=logging.ERROR,
                    extra={
                        'fingerprint':
                        auto_fingerprint('caught_spotify_exception', **kwargs)
                    },
                    exc_info=True,
                )

                break

            except Exception as exc:
                self.write_to_log_and_output(
                    'Unhandled exception when collecting songs from Spotify! Exception detail: {}. '
                    'Got {} track(s) successfully.'.format(exc, len(tracks)),
                    output_stream='stderr',
                    log_level=logging.ERROR,
                    extra={
                        'fingerprint':
                        auto_fingerprint('caught_unhandled_exception',
                                         **kwargs)
                    },
                    exc_info=True,
                )

                break

        return tracks
コード例 #28
0
ファイル: views.py プロジェクト: Moody-Tunes/moodytunes
    def get(self, request, *args, **kwargs):
        # Check to make sure that the user who initiated the request is the one making the request
        # state value for session is set in initial authentication request
        if request.GET.get('state') != request.session['state']:
            logger.error(
                'User {} has an invalid state parameter for OAuth callback'.
                format(request.user.username),
                extra={
                    'session_state':
                    request.session.get('state'),
                    'request_state':
                    request.GET.get('state'),
                    'fingerprint':
                    auto_fingerprint('invalid_oauth_state', **kwargs),
                    'trace_id':
                    request.trace_id,
                })

            raise SuspiciousOperation('Invalid state parameter')

        if 'code' in request.GET:
            code = request.GET['code']
            user = request.user

            # Early exit: if we already have a SpotifyAuth record for the user, exit
            if SpotifyAuth.objects.filter(user=user).exists():
                messages.info(request,
                              'You have already authenticated with Spotify!')

                return HttpResponseRedirect(
                    reverse('spotify:spotify-auth-success'))

            # Get access and refresh tokens for user
            spotify_client = SpotifyClient(
                identifier='spotify.views.SpotifyAuthenticationCallbackView-{}'
                .format(request.trace_id))

            try:
                tokens = spotify_client.get_access_and_refresh_tokens(
                    code, settings.SPOTIFY['auth_redirect_uri'])
            except SpotifyException:
                logger.exception(
                    'Unable to get Spotify tokens for user {}'.format(
                        user.username),
                    extra={
                        'fingerprint':
                        auto_fingerprint('failed_get_spotify_tokens',
                                         **kwargs),
                        'trace_id':
                        request.trace_id,
                    })

                messages.error(
                    request,
                    'We were unable to retrieve your Spotify profile. Please try again.'
                )
                return HttpResponseRedirect(
                    reverse('spotify:spotify-auth-failure'))

            # Get Spotify username from profile data
            try:
                profile_data = spotify_client.get_user_profile(
                    tokens['access_token'])
            except SpotifyException:
                logger.exception(
                    'Unable to get Spotify profile for user {}'.format(
                        user.username),
                    extra={
                        'fingerprint':
                        auto_fingerprint('failed_get_spotify_profile',
                                         **kwargs),
                        'trace_id':
                        request.trace_id,
                    })

                messages.error(
                    request,
                    'We were unable to retrieve your Spotify profile. Please try again.'
                )
                return HttpResponseRedirect(
                    reverse('spotify:spotify-auth-failure'))

            # Create SpotifyAuth record from data
            try:
                with transaction.atomic():
                    auth = SpotifyAuth(
                        user=user,
                        access_token=tokens['access_token'],
                        refresh_token=tokens['refresh_token'],
                        spotify_user_id=profile_data['id'],
                        scopes=settings.SPOTIFY['auth_user_scopes'],
                    )

                    auth._trace_id = request.trace_id
                    auth.save()

                    logger.info(
                        'Created SpotifyAuth record for user {}'.format(
                            user.username),
                        extra={
                            'fingerprint':
                            auto_fingerprint('created_spotify_auth', **kwargs),
                            'auth_id':
                            auth.pk,
                            'user_id':
                            user.pk,
                            'spotify_user_id':
                            profile_data['id'],
                            'scopes':
                            settings.SPOTIFY['auth_user_scopes'],
                            'trace_id':
                            request.trace_id,
                        })

                    messages.info(
                        request,
                        'You have successfully authorized Moodytunes with Spotify!'
                    )
                    redirect_url = request.session.get(
                        'redirect_url') or reverse(
                            'spotify:spotify-auth-success')

                    return HttpResponseRedirect(redirect_url)
            except IntegrityError:
                logger.exception(
                    'Failed to create SpotifyAuth record for MoodyUser {} with Spotify username {}'
                    .format(user.username, profile_data['id']),
                    extra={
                        'fingerprint':
                        auto_fingerprint('failed_to_create_spotify_auth_user',
                                         **kwargs),
                        'trace_id':
                        request.trace_id,
                    })

                messages.error(
                    request,
                    'Spotify user {} has already authorized MoodyTunes.'.
                    format(profile_data['id']))

                return HttpResponseRedirect(
                    reverse('spotify:spotify-auth-failure'))

        else:
            logger.warning('User {} failed Spotify Oauth confirmation'.format(
                request.user.username),
                           extra={
                               'fingerprint':
                               auto_fingerprint(
                                   'user_rejected_oauth_confirmation',
                                   **kwargs),
                               'spotify_oauth_error':
                               request.GET['error'],
                               'trace_id':
                               request.trace_id,
                           })

            # Map error code to human-friendly display
            error_messages = {
                'access_denied':
                'You rejected to link MoodyTunes to Spotify. Please select Accept next time.'
            }

            messages.error(
                request,
                error_messages.get(request.GET['error'], request.GET['error']))

            return HttpResponseRedirect(
                reverse('spotify:spotify-auth-failure'))
コード例 #29
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
コード例 #30
0
ファイル: views.py プロジェクト: Moody-Tunes/moodytunes
    def post(self, request, *args, **kwargs):
        form = self.form_class(request.POST, request.FILES)

        if form.is_valid():
            auth = request.spotify_auth

            playlist_name = form.cleaned_data['playlist_name']
            emotion_name = form.cleaned_data['emotion']
            genre = form.cleaned_data['genre']
            context = form.cleaned_data['context']

            songs = ExportPlaylistHelper.get_export_playlist_for_user(
                request.user, emotion_name, genre, context)

            if not songs:
                msg = 'Your {} playlist is empty! Try adding some songs to export the playlist'.format(
                    Emotion.get_full_name_from_keyword(emotion_name).lower())

                messages.error(request, msg)

                return HttpResponseRedirect(reverse('spotify:export'))

            # Handle cover image upload
            cover_image_filename = None

            if form.cleaned_data.get('cover_image'):
                cover_image_filename = '{}/{}_{}_{}.jpg'.format(
                    settings.IMAGE_FILE_UPLOAD_PATH,
                    request.user.username,
                    form.cleaned_data['emotion'],
                    form.cleaned_data['playlist_name'],
                )

                img = Image.open(form.cleaned_data['cover_image'])
                img = img.convert('RGB')

                with open(cover_image_filename, 'wb+') as img_file:
                    img.save(img_file, format='JPEG')

            logger.info('Exporting {} playlist for user {} to Spotify'.format(
                emotion_name, request.user.username),
                        extra={
                            'emotion':
                            Emotion.get_full_name_from_keyword(emotion_name),
                            'genre':
                            genre,
                            'context':
                            context,
                            'user_id':
                            request.user.pk,
                            'auth_id':
                            auth.pk,
                            'fingerprint':
                            auto_fingerprint('export_playlist_to_spotify',
                                             **kwargs),
                            'trace_id':
                            request.trace_id,
                        })

            ExportSpotifyPlaylistFromSongsTask().delay(
                auth.id,
                playlist_name,
                songs,
                cover_image_filename,
                trace_id=request.trace_id)

            messages.info(
                request,
                'Your playlist has been exported! Check in on Spotify in a little bit to see it'
            )

            return HttpResponseRedirect(reverse('spotify:export'))

        else:
            messages.error(request, 'Please submit a valid request')
            return render(request, self.template_name, {'form': form})