Graphs and tables for your Spotify account.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

203 lines
6.3 KiB

  1. # imports {{{ #
  2. import math
  3. import random
  4. import requests
  5. import os
  6. import urllib
  7. import json
  8. import pprint
  9. from datetime import datetime
  10. from django.shortcuts import render, redirect
  11. from django.http import HttpResponse, HttpResponseBadRequest, JsonResponse
  12. from django.db.models import Count
  13. from .utils import parse_library, process_library_stats
  14. from .models import User, Track, AudioFeatures, Artist
  15. # }}} imports #
  16. TIME_FORMAT = '%Y-%m-%d-%H-%M-%S'
  17. TRACKS_TO_QUERY = 5
  18. # generate_random_string {{{ #
  19. def generate_random_string(length):
  20. """Generates a random string of a certain length
  21. Args:
  22. length: the desired length of the randomized string
  23. Returns:
  24. A random string
  25. """
  26. rand_str = ""
  27. possible_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
  28. for _ in range(length):
  29. rand_str += possible_chars[random.randint(0, len(possible_chars) - 1)]
  30. return rand_str
  31. # }}} generate_random_string #
  32. # token_expired {{{ #
  33. def token_expired(token_obtained_at, valid_for):
  34. """Returns True if token expired, False if otherwise
  35. Args:
  36. token_obtained_at: datetime object representing the date and time when the token was obtained
  37. valid_for: the time duration for which the token is valid, in seconds
  38. """
  39. time_elapsed = (datetime.today() - token_obtained_at).total_seconds()
  40. return time_elapsed >= valid_for
  41. # }}} token_expired #
  42. # index {{{ #
  43. # Create your views here.
  44. def index(request):
  45. return render(request, 'spotifyvis/index.html')
  46. # }}} index #
  47. # login {{{ #
  48. def login(request):
  49. # use a randomly generated state string to prevent cross-site request forgery attacks
  50. state_str = generate_random_string(16)
  51. request.session['state_string'] = state_str
  52. payload = {
  53. 'client_id': os.environ['SPOTIFY_CLIENT_ID'],
  54. 'response_type': 'code',
  55. 'redirect_uri': 'http://localhost:8000/callback',
  56. 'state': state_str,
  57. 'scope': 'user-library-read',
  58. 'show_dialog': False
  59. }
  60. params = urllib.parse.urlencode(payload) # turn the payload dict into a query string
  61. authorize_url = "https://accounts.spotify.com/authorize/?{}".format(params)
  62. return redirect(authorize_url)
  63. # }}} login #
  64. # callback {{{ #
  65. def callback(request):
  66. # Attempt to retrieve the authorization code from the query string
  67. try:
  68. code = request.GET['code']
  69. except KeyError:
  70. return HttpResponseBadRequest("<h1>Problem with login</h1>")
  71. payload = {
  72. 'grant_type': 'authorization_code',
  73. 'code': code,
  74. 'redirect_uri': 'http://localhost:8000/callback',
  75. 'client_id': os.environ['SPOTIFY_CLIENT_ID'],
  76. 'client_secret': os.environ['SPOTIFY_CLIENT_SECRET'],
  77. }
  78. response = requests.post('https://accounts.spotify.com/api/token', data=payload).json()
  79. # despite its name, datetime.today() returns a datetime object, not a date object
  80. # use datetime.strptime() to get a datetime object from a string
  81. request.session['token_obtained_at'] = datetime.strftime(datetime.today(), TIME_FORMAT)
  82. request.session['access_token'] = response['access_token']
  83. request.session['refresh_token'] = response['refresh_token']
  84. request.session['valid_for'] = response['expires_in']
  85. # print(response)
  86. return redirect('user_data')
  87. # }}} callback #
  88. # user_data {{{ #
  89. def user_data(request):
  90. token_obtained_at = datetime.strptime(request.session['token_obtained_at'], TIME_FORMAT)
  91. valid_for = int(request.session['valid_for'])
  92. if token_expired(token_obtained_at, valid_for):
  93. req_body = {
  94. 'grant_type': 'refresh_token',
  95. 'refresh_token': request.session['refresh_token'],
  96. 'client_id': os.environ['SPOTIFY_CLIENT_ID'],
  97. 'client_secret': os.environ['SPOTIFY_CLIENT_SECRET']
  98. }
  99. refresh_token_response = requests.post('https://accounts.spotify.com/api/token', data = req_body).json()
  100. request.session['access_token'] = refresh_token_response['access_token']
  101. request.session['valid_for'] = refresh_token_response['expires_in']
  102. auth_token_str = "Bearer " + request.session['access_token']
  103. headers = {
  104. 'Authorization': auth_token_str
  105. }
  106. user_data_response = requests.get('https://api.spotify.com/v1/me', headers = headers).json()
  107. request.session['user_id'] = user_data_response['id'] # store the user_id so it may be used to create model
  108. # request.session['user_name'] = user_data_response['display_name']
  109. try:
  110. user = User.objects.get(user_id=user_data_response['id'])
  111. except User.DoesNotExist:
  112. user = User(user_id=user_data_response['id'], user_secret=generate_random_string(30))
  113. user.save()
  114. context = {
  115. 'id': user_data_response['id'],
  116. 'user_secret': user.user_secret,
  117. }
  118. parse_library(headers, TRACKS_TO_QUERY, user)
  119. return render(request, 'spotifyvis/user_data.html', context)
  120. # }}} user_data #
  121. def test_db(request):
  122. user_id = "polarbier"
  123. context = {
  124. 'user_id': user_id,
  125. }
  126. # get_artist_data(user)
  127. return render(request, 'spotifyvis/test_db.html', context)
  128. def get_artist_data(request, user_id):
  129. # TODO: not actual artists for user
  130. # PICK UP: figure out how to pass data to D3/frontend
  131. print(user_id)
  132. # user = User.objects.get(user_id=user_id)
  133. artist_counts = Artist.objects.annotate(num_songs=Count('track'))
  134. processed_artist_data = [{'name': artist.name, 'num_songs': artist.num_songs} for artist in artist_counts]
  135. return JsonResponse(data=processed_artist_data, safe=False)
  136. def get_audio_feature_data(request, audio_feature, client_secret):
  137. """Returns all data points for a given audio feature
  138. Args:
  139. request: the HTTP request
  140. audio_feature: The audio feature to be queried
  141. client_secret: client secret, used to identify the user
  142. """
  143. user = User.objects.get(user_secret=client_secret)
  144. user_tracks = Track.objects.filter(users=user)
  145. response_payload = {
  146. 'data_points': [],
  147. }
  148. for track in user_tracks:
  149. audio_feature_obj = AudioFeatures.objects.get(track=track)
  150. response_payload['data_points'].append(getattr(audio_feature_obj, audio_feature))
  151. return JsonResponse(response_payload)