From 05b5cc404a3e2fa6db840a37e496008627cef7e4 Mon Sep 17 00:00:00 2001 From: Kevin Mok Date: Mon, 11 Jun 2018 21:58:51 -0400 Subject: [PATCH 01/13] Trying to get correct artist breakdown in genres Counts are off since there are multiple artists on a track. --- spotifyvis/templates/spotifyvis/test_db.html | 49 +++++++++----------- spotifyvis/utils.py | 20 +++++++- spotifyvis/views.py | 16 ++++--- 3 files changed, 50 insertions(+), 35 deletions(-) diff --git a/spotifyvis/templates/spotifyvis/test_db.html b/spotifyvis/templates/spotifyvis/test_db.html index 0bf5b7f..0f481bd 100644 --- a/spotifyvis/templates/spotifyvis/test_db.html +++ b/spotifyvis/templates/spotifyvis/test_db.html @@ -1,31 +1,26 @@ - + - - - - - Test DB Page - - - - - - - + + + + + + Test DB Page + + + + + + + + diff --git a/spotifyvis/utils.py b/spotifyvis/utils.py index 2caf39d..8572444 100644 --- a/spotifyvis/utils.py +++ b/spotifyvis/utils.py @@ -4,7 +4,7 @@ import math import pprint from .models import Artist, User, Track, AudioFeatures -from django.db.models import Count +from django.db.models import Count, Q from django.http import JsonResponse from django.core import serializers import json @@ -317,7 +317,7 @@ def update_artist_genre(headers, artist_obj): # }}} # -# {{{ # +# get_top_genre {{{ # def get_top_genre(headers, top_artist_id): """Updates the top genre for a track by querying the Spotify API @@ -393,3 +393,19 @@ def process_library_stats(library_stats): # }}} process_library_stats # +def get_artists_in_genre(user, genre): + """Return count of artists in genre. + + :genre: genre to count artists for. + + :returns: dict of artists in the genre along with the number of songs they + have. + """ + artist_counts = (Artist.objects.filter(track__users=user) + .filter(track__genre=genre) + # .annotate(num_songs=Count('track', filter=Q(track__genre=genre))) + .annotate(num_songs=Count('track')) + ) + processed_artist_counts = [{'name': artist.name, 'num_songs': artist.num_songs} for artist in artist_counts] + # pprint.pprint(processed_artist_counts) + return processed_artist_counts diff --git a/spotifyvis/views.py b/spotifyvis/views.py index c2a2ae2..c14b5ad 100644 --- a/spotifyvis/views.py +++ b/spotifyvis/views.py @@ -13,7 +13,7 @@ from datetime import datetime from django.shortcuts import render, redirect from django.http import HttpResponse, HttpResponseBadRequest, JsonResponse from django.db.models import Count, Q -from .utils import parse_library, process_library_stats +from .utils import parse_library, process_library_stats, get_artists_in_genre from .models import User, Track, AudioFeatures, Artist # }}} imports # @@ -164,7 +164,8 @@ def user_data(request): def test_db(request): """TODO """ - user_id = "polarbier" + # user_id = "polarbier" + user_id = "35kxo00qqo9pd1comj6ylxjq7" context = { 'user_secret': User.objects.get(user_id=user_id).user_secret, } @@ -180,8 +181,9 @@ def get_artist_data(request, user_secret): user = User.objects.get(user_id=user_secret) artist_counts = Artist.objects.annotate(num_songs=Count('track', filter=Q(track__users=user))) - processed_artist_data = [{'name': artist.name, 'num_songs': artist.num_songs} for artist in artist_counts] - return JsonResponse(data=processed_artist_data, safe=False) + processed_artist_counts = [{'name': artist.name, + 'num_songs': artist.num_songs} for artist in artist_counts] + return JsonResponse(data=processed_artist_counts, safe=False) # }}} get_artist_data # @@ -214,12 +216,14 @@ def get_genre_data(request, user_secret): TODO """ user = User.objects.get(user_secret=user_secret) - genre_counts = (Track.objects.filter(users=user) + genre_counts = (Track.objects.filter(users__exact=user) .values('genre') .order_by('genre') .annotate(num_songs=Count('genre')) ) - # pprint.pprint(genre_counts) + for genre_dict in genre_counts: + genre_dict['artists'] = get_artists_in_genre(user, genre_dict['genre']) + pprint.pprint(list(genre_counts)) return JsonResponse(data=list(genre_counts), safe=False) # }}} get_genre_data # From c5185561263330a9154140c46b0a366f951e4ba5 Mon Sep 17 00:00:00 2001 From: Chris Shyi Date: Fri, 15 Jun 2018 17:03:49 -0400 Subject: [PATCH 02/13] Draw audio feature bar charts Started work on drawing the bar charts for audio features. --- spotifyvis/admin.py | 5 ++ spotifyvis/models.py | 8 ++- .../static/spotifyvis/scripts/user_data.js | 8 +-- .../templates/spotifyvis/user_data.html | 63 ++++++++++++++++++- spotifyvis/utils.py | 9 ++- spotifyvis/views.py | 2 +- 6 files changed, 81 insertions(+), 14 deletions(-) diff --git a/spotifyvis/admin.py b/spotifyvis/admin.py index 8c38f3f..bd71265 100644 --- a/spotifyvis/admin.py +++ b/spotifyvis/admin.py @@ -1,3 +1,8 @@ from django.contrib import admin +from .models import Track, Artist, AudioFeatures, User # Register your models here. +admin.site.register(Track) +admin.site.register(Artist) +admin.site.register(AudioFeatures) +admin.site.register(User) diff --git a/spotifyvis/models.py b/spotifyvis/models.py index 0cc8879..6e28a07 100644 --- a/spotifyvis/models.py +++ b/spotifyvis/models.py @@ -51,10 +51,14 @@ class Track(models.Model): runtime = models.PositiveSmallIntegerField() name = models.CharField(max_length=150) users = models.ManyToManyField(User, blank=True) - genre = models.CharField(max_length=30) + genre = models.CharField(max_length=30, default="") def __str__(self): - return self.name + track_str = "{}, genre: {}, artists: [".format(self.name, self.genre) + for artist in self.artists.all(): + track_str += "{}, ".format(artist.name) + track_str += "]" + return track_str # }}} Track # diff --git a/spotifyvis/static/spotifyvis/scripts/user_data.js b/spotifyvis/static/spotifyvis/scripts/user_data.js index 993fe04..84c5141 100644 --- a/spotifyvis/static/spotifyvis/scripts/user_data.js +++ b/spotifyvis/static/spotifyvis/scripts/user_data.js @@ -2,8 +2,9 @@ * Retrieves data for a specific audio feature for a certain user * @param audioFeature: the audio feature for which data will be retrieved * @param clientSecret: the client secret, needed for security + * @param chartElement: the SVG element in which the data will be plotted */ -function getAudioFeatureData(audioFeature, userSecret) { +function plotAudioFeatureData(audioFeature, userSecret, chartElement) { let httpRequest = new XMLHttpRequest(); /* * Handler for the response @@ -12,10 +13,7 @@ function getAudioFeatureData(audioFeature, userSecret) { if (httpRequest.readyState === XMLHttpRequest.DONE) { if (httpRequest.status === 200) { let responseData = JSON.parse(httpRequest.responseText); - // TODO: The data points need to be plotted instead - for (let data of responseData.data_points) { - console.log(data); - } + } else { alert("There was a problem with the login request, please try again!"); } diff --git a/spotifyvis/templates/spotifyvis/user_data.html b/spotifyvis/templates/spotifyvis/user_data.html index ce4799d..8dcfa70 100644 --- a/spotifyvis/templates/spotifyvis/user_data.html +++ b/spotifyvis/templates/spotifyvis/user_data.html @@ -16,10 +16,71 @@

You are using an outdated browser. Please upgrade your browser to improve your experience.

Logged in as {{ id }}

+ diff --git a/spotifyvis/utils.py b/spotifyvis/utils.py index 8572444..94010c1 100644 --- a/spotifyvis/utils.py +++ b/spotifyvis/utils.py @@ -401,11 +401,10 @@ def get_artists_in_genre(user, genre): :returns: dict of artists in the genre along with the number of songs they have. """ - artist_counts = (Artist.objects.filter(track__users=user) - .filter(track__genre=genre) - # .annotate(num_songs=Count('track', filter=Q(track__genre=genre))) - .annotate(num_songs=Count('track')) - ) + artist_counts = (Artist.objects.filter(track__users=user).distinct() + .filter(track__genre=genre).distinct() + .annotate(num_songs=Count('track')) + ) processed_artist_counts = [{'name': artist.name, 'num_songs': artist.num_songs} for artist in artist_counts] # pprint.pprint(processed_artist_counts) return processed_artist_counts diff --git a/spotifyvis/views.py b/spotifyvis/views.py index c14b5ad..e44d998 100644 --- a/spotifyvis/views.py +++ b/spotifyvis/views.py @@ -165,7 +165,7 @@ def test_db(request): """TODO """ # user_id = "polarbier" - user_id = "35kxo00qqo9pd1comj6ylxjq7" + user_id = "chrisshyi13" context = { 'user_secret': User.objects.get(user_id=user_id).user_secret, } From bb9709539844b852d11c2e6502ef2ac1dd37cd9d Mon Sep 17 00:00:00 2001 From: Chris Shyi Date: Fri, 15 Jun 2018 20:11:15 -0400 Subject: [PATCH 03/13] Draw bar chart for instrumentalness Instrumentalness bar chart for 15 songs has been drawn. The chart needs to be beautified with colors and the axis labels could use some styling. --- .../templates/spotifyvis/user_data.html | 42 ++++++++++++------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/spotifyvis/templates/spotifyvis/user_data.html b/spotifyvis/templates/spotifyvis/user_data.html index 8dcfa70..c237c58 100644 --- a/spotifyvis/templates/spotifyvis/user_data.html +++ b/spotifyvis/templates/spotifyvis/user_data.html @@ -18,9 +18,10 @@

Logged in as {{ id }}

- From 840152b99e468f692488f627c151df10bda7c9aa Mon Sep 17 00:00:00 2001 From: Chris Shyi Date: Fri, 15 Jun 2018 20:48:35 -0400 Subject: [PATCH 05/13] Add title to audio feature graphs Audio feature bar graphs now have titles. --- .../templates/spotifyvis/user_data.html | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/spotifyvis/templates/spotifyvis/user_data.html b/spotifyvis/templates/spotifyvis/user_data.html index 34229c2..2bfc6da 100644 --- a/spotifyvis/templates/spotifyvis/user_data.html +++ b/spotifyvis/templates/spotifyvis/user_data.html @@ -20,6 +20,13 @@ - diff --git a/spotifyvis/templates/spotifyvis/logged_in.html b/spotifyvis/templates/spotifyvis/logged_in.html new file mode 100644 index 0000000..a3d9017 --- /dev/null +++ b/spotifyvis/templates/spotifyvis/logged_in.html @@ -0,0 +1,12 @@ + +{% load static %} + + + + Logged In + + + + Audio Features + + \ No newline at end of file diff --git a/spotifyvis/urls.py b/spotifyvis/urls.py index 4612571..f5bc82b 100644 --- a/spotifyvis/urls.py +++ b/spotifyvis/urls.py @@ -11,6 +11,7 @@ urlpatterns = [ path('test_db', test_db, name='test_db'), path('user_artists/', get_artist_data, name='get_artist_data'), path('user_genres/', get_genre_data, name='get_genre_data'), + path('audio_features/', audio_features, name='audio_features'), path('audio_features//', get_audio_feature_data, name='get_audio_feature_data'), ] diff --git a/spotifyvis/views.py b/spotifyvis/views.py index b5992b6..2fcc85e 100644 --- a/spotifyvis/views.py +++ b/spotifyvis/views.py @@ -65,6 +65,7 @@ def index(request): # login {{{ # + def login(request): # use a randomly generated state string to prevent cross-site request forgery attacks @@ -118,6 +119,7 @@ def callback(request): # user_data {{{ # + def user_data(request): token_obtained_at = datetime.strptime(request.session['token_obtained_at'], TIME_FORMAT) valid_for = int(request.session['valid_for']) @@ -130,7 +132,7 @@ def user_data(request): 'client_secret': os.environ['SPOTIFY_CLIENT_SECRET'] } - refresh_token_response = requests.post('https://accounts.spotify.com/api/token', data = req_body).json() + refresh_token_response = requests.post('https://accounts.spotify.com/api/token', data=req_body).json() request.session['access_token'] = refresh_token_response['access_token'] request.session['valid_for'] = refresh_token_response['expires_in'] @@ -139,7 +141,7 @@ def user_data(request): 'Authorization': auth_token_str } - user_data_response = requests.get('https://api.spotify.com/v1/me', headers = headers).json() + user_data_response = requests.get('https://api.spotify.com/v1/me', headers=headers).json() request.session['user_id'] = user_data_response['id'] # store the user_id so it may be used to create model # request.session['user_name'] = user_data_response['display_name'] @@ -148,15 +150,15 @@ def user_data(request): except User.DoesNotExist: # Python docs recommends 32 bytes of randomness against brute force attacks user = User(user_id=user_data_response['id'], user_secret=secrets.token_urlsafe(32)) + request.session['user_secret'] = user.user_secret user.save() context = { - 'id': user_data_response['id'], 'user_secret': user.user_secret, } parse_library(headers, TRACKS_TO_QUERY, user) - return render(request, 'spotifyvis/user_data.html', context) + return render(request, 'spotifyvis/logged_in.html', context) # }}} user_data # @@ -188,6 +190,16 @@ def get_artist_data(request, user_secret): # }}} get_artist_data # + +def audio_features(request, client_secret): + user = User.objects.get(user_secret=client_secret) + context = { + 'user_id': user.user_id, + 'user_secret': client_secret, + } + return render(request, "spotifyvis/audio_features.html", context) + + # get_audio_feature_data {{{ # def get_audio_feature_data(request, audio_feature, client_secret): From 4c55744db87b593a6a6a325f5a7b291d2c21cf71 Mon Sep 17 00:00:00 2001 From: Chris Shyi Date: Mon, 25 Jun 2018 22:09:05 -0400 Subject: [PATCH 13/13] Update docstring for parse_library() The docstring of parse_library still mentions the library_stats dictionary, which had already been removed. --- spotifyvis/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spotifyvis/utils.py b/spotifyvis/utils.py index 3bb47e3..f94f76f 100644 --- a/spotifyvis/utils.py +++ b/spotifyvis/utils.py @@ -15,7 +15,7 @@ import json def parse_library(headers, tracks, user): - """Scans user's library for certain number of tracks to update library_stats with. + """Scans user's library for certain number of tracks and store the information in a database :headers: For API call. :tracks: Number of tracks to get from user's library.