diff --git a/spotifyvis/migrations/0001_initial.py b/spotifyvis/migrations/0001_initial.py new file mode 100644 index 0000000..e22ffd2 --- /dev/null +++ b/spotifyvis/migrations/0001_initial.py @@ -0,0 +1,85 @@ +# Generated by Django 2.0.5 on 2018-06-03 23:01 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Artist', + fields=[ + ('artist_id', models.CharField(max_length=30, primary_key=True, serialize=False)), + ('name', models.CharField(max_length=50, unique=True)), + ('genre', models.CharField(max_length=20)), + ], + options={ + 'verbose_name': 'Artist', + 'verbose_name_plural': 'Artists', + }, + ), + migrations.CreateModel( + name='Track', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('track_id', models.CharField(max_length=30)), + ('year', models.PositiveSmallIntegerField()), + ('popularity', models.DecimalField(decimal_places=2, max_digits=2)), + ('runtime', models.PositiveSmallIntegerField()), + ('name', models.CharField(max_length=75)), + ], + options={ + 'verbose_name': 'Track', + 'verbose_name_plural': 'Tracks', + }, + ), + migrations.CreateModel( + name='User', + fields=[ + ('user_id', models.CharField(max_length=30, primary_key=True, serialize=False)), + ('username', models.CharField(max_length=30)), + ], + options={ + 'verbose_name': 'User', + 'verbose_name_plural': 'Users', + }, + ), + migrations.CreateModel( + name='AudioFeatures', + fields=[ + ('track', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, primary_key=True, serialize=False, to='spotifyvis.Track')), + ('danceability', models.DecimalField(decimal_places=2, max_digits=2)), + ('energy', models.DecimalField(decimal_places=2, max_digits=2)), + ('loudness', models.DecimalField(decimal_places=2, max_digits=2)), + ('speechiness', models.DecimalField(decimal_places=2, max_digits=2)), + ('acousticness', models.DecimalField(decimal_places=2, max_digits=2)), + ('instrumentalness', models.DecimalField(decimal_places=2, max_digits=2)), + ('valence', models.DecimalField(decimal_places=2, max_digits=2)), + ('tempo', models.DecimalField(decimal_places=2, max_digits=2)), + ], + options={ + 'verbose_name': 'AudioFeatures', + 'verbose_name_plural': 'AudioFeatures', + }, + ), + migrations.AddField( + model_name='track', + name='artist', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='spotifyvis.Artist'), + ), + migrations.AddField( + model_name='track', + name='users', + field=models.ManyToManyField(to='spotifyvis.User'), + ), + migrations.AlterUniqueTogether( + name='track', + unique_together={('track_id', 'artist')}, + ), + ] diff --git a/spotifyvis/models.py b/spotifyvis/models.py index d2bdb67..8d28fda 100644 --- a/spotifyvis/models.py +++ b/spotifyvis/models.py @@ -1,17 +1,4 @@ from django.db import models -from django.contrib.postgres.fields import JSONField - -class Genre(models.Model): - - class Meta: - verbose_name = "Genre" - verbose_name_plural = "Genres" - - name = models.CharField() - - def __str__(self): - return self.name - class Artist(models.Model): @@ -19,47 +6,64 @@ class Artist(models.Model): verbose_name = "Artist" verbose_name_plural = "Artists" - name = models.CharField() - genre = models.OneToOneField(Genre, on_delete=models.CASCADE) + artist_id = models.CharField(primary_key=True, max_length=30) + # unique since only storing one genre per artist right now + name = models.CharField(unique=True, max_length=50) + genre = models.CharField(max_length=20) def __str__(self): - return super(Artist, self).__str__() + return self.name -class AudioFeatures(models.Model): - +class User(models.Model): + class Meta: - verbose_name = "AudioFeatures" - verbose_name_plural = "AudioFeatures" + verbose_name = "User" + verbose_name_plural = "Users" - features = JSONField() + user_id = models.CharField(primary_key=True, max_length=30) + username = models.CharField(max_length=30) def __str__(self): - return self.features + return self.username -class Year(models.Model): +class Track(models.Model): class Meta: - verbose_name = "Year" - verbose_name_plural = "Years" - + verbose_name = "Track" + verbose_name_plural = "Tracks" + unique_together = ('track_id', 'artist',) + track_id = models.CharField(max_length=30) + artist = models.ForeignKey(Artist, on_delete=models.CASCADE) year = models.PositiveSmallIntegerField() + popularity = models.DecimalField(decimal_places=2, max_digits=2) + runtime = models.PositiveSmallIntegerField() + name = models.CharField(max_length=75) + users = models.ManyToManyField(User) def __str__(self): - return self.year + return self.name -class Track(models.Model): - +class AudioFeatures(models.Model): + class Meta: - verbose_name = "Track" - verbose_name_plural = "Tracks" + verbose_name = "AudioFeatures" + verbose_name_plural = "AudioFeatures" - popularity = models.DecimalField(decimal_places=2) - runtime = models.PositiveSmallIntegerField() - name = models.CharField() + track = models.OneToOneField(Track, on_delete=models.CASCADE, primary_key=True,) + danceability = models.DecimalField(decimal_places=2, max_digits=2) + energy = models.DecimalField(decimal_places=2, max_digits=2) + loudness = models.DecimalField(decimal_places=2, max_digits=2) + speechiness = models.DecimalField(decimal_places=2, max_digits=2) + acousticness = models.DecimalField(decimal_places=2, max_digits=2) + instrumentalness = models.DecimalField(decimal_places=2, max_digits=2) + valence = models.DecimalField(decimal_places=2, max_digits=2) + tempo = models.DecimalField(decimal_places=2, max_digits=2) def __str__(self): - return self.name + return super(AudioFeatures, self).__str__() + + diff --git a/spotifyvis/static/spotifyvis/scripts/index.js b/spotifyvis/static/spotifyvis/scripts/index.js new file mode 100644 index 0000000..cfac1d1 --- /dev/null +++ b/spotifyvis/static/spotifyvis/scripts/index.js @@ -0,0 +1,42 @@ +document.getElementById("login-btn").addEventListener("click", function() { + let httpRequest = new XMLHttpRequest(); + + /* + * Handler for the response + */ + httpRequest.onreadystatechange = function() { + if (httpRequest.readyState === XMLHttpRequest.DONE) { + if (httpRequest.status === 200) { + // hide the login button + document.getElementById('login').setAttribute("display", "none"); + + let responseData = JSON.parse(httpRequest.responseText); + let dataList = document.getElementById("data-list"); + + + for (let key in responseData) { + let newLi = document.createElement("li"); + let innerList = document.createElement("ul"); + + let dataLabel = document.createElement("li"); + dataLabel.innerText = key; + + let dataValue = document.createElement("li"); + dataValue.innerText = responseData[key]; + + innerList.appendChild(dataLabel); + innerList.appendChild(dataValue); + + newLi.appendChild(innerList); + dataList.appendChild(newLi); + } + } else { + alert("There was a problem with the login request, please try again!"); + } + } + } + + httpRequest.open('GET', '/login', true); + httpRequest.send(); +}); + diff --git a/spotifyvis/templates/spotifyvis/index.html b/spotifyvis/templates/spotifyvis/index.html index e410bf5..32d6db9 100644 --- a/spotifyvis/templates/spotifyvis/index.html +++ b/spotifyvis/templates/spotifyvis/index.html @@ -1,4 +1,5 @@ +{% load static %} User Login @@ -11,14 +12,25 @@ width: 500px; } +

This is an example of the Authorization Code flow

- Log in with Spotify + Log In (Original) + +
+ +
+
+ + + diff --git a/spotifyvis/tests.py b/spotifyvis/tests.py index b88c1e2..ed439b4 100644 --- a/spotifyvis/tests.py +++ b/spotifyvis/tests.py @@ -1,5 +1,5 @@ from django.test import TestCase -from .views import update_std_dev +from .utils import update_std_dev import math # Create your tests here. diff --git a/spotifyvis/utils.py b/spotifyvis/utils.py index 279e4ed..9e537dc 100644 --- a/spotifyvis/utils.py +++ b/spotifyvis/utils.py @@ -27,12 +27,13 @@ def parse_library(headers, tracks, library_stats): for track_dict in saved_tracks_response['items']: # Track the number of samples for calculating # audio feature averages and standard deviations on the fly - num_samples += 1 get_track_info(track_dict['track'], library_stats, num_samples) # get_genre(headers, track_dict['track']['album']['id']) audio_features_dict = get_audio_features(headers, track_dict['track']['id']) - for feature, feature_data in audio_features_dict.items(): - update_audio_feature_stats(feature, feature_data, num_samples, library_stats) + if len(audio_features_dict) != 0: + num_samples += 1 + for feature, feature_data in audio_features_dict.items(): + update_audio_feature_stats(feature, feature_data, num_samples, library_stats) for artist_dict in track_dict['track']['artists']: increase_artist_count(headers, artist_dict['name'], artist_dict['id'], library_stats) # calculates num_songs with offset + songs retrieved @@ -51,10 +52,13 @@ def get_audio_features(headers, track_id): track_id: the id of the soundtrack, needed to query the Spotify API Returns: - A dictionary with the features as its keys + A dictionary with the features as its keys, if audio feature data is missing for the track, + an empty dictionary is returned. """ response = requests.get("https://api.spotify.com/v1/audio-features/{}".format(track_id), headers = headers).json() + if 'error' in response: + return {} features_dict = {} # Data that we don't need diff --git a/spotifyvis/views.py b/spotifyvis/views.py index e728fb3..cfdf114 100644 --- a/spotifyvis/views.py +++ b/spotifyvis/views.py @@ -158,4 +158,4 @@ def user_data(request): pprint.pprint(processed_library_stats) return render(request, 'spotifyvis/user_data.html', context) -# }}} user_data # +# }}} user_data # \ No newline at end of file