From cdcc33c0141a8a1b5013119d0ae261c5f6f54375 Mon Sep 17 00:00:00 2001 From: Kevin Mok Date: Wed, 6 Jun 2018 03:46:09 -0400 Subject: [PATCH] Setup Track table, modified models to data Recreated migrations from scratch to fix bug with initializing Track objects. --- musicvis/settings.py | 2 +- spotifyvis/migrations/0001_initial.py | 26 +++++------- spotifyvis/models.py | 22 ++++++---- spotifyvis/utils.py | 59 +++++++++++++++++++++------ spotifyvis/views.py | 9 ++-- 5 files changed, 73 insertions(+), 45 deletions(-) diff --git a/musicvis/settings.py b/musicvis/settings.py index 7eefdbb..0cedb25 100644 --- a/musicvis/settings.py +++ b/musicvis/settings.py @@ -110,7 +110,7 @@ AUTH_PASSWORD_VALIDATORS = [ LANGUAGE_CODE = 'en-us' -TIME_ZONE = 'UTC' +TIME_ZONE = 'America/Toronto' USE_I18N = True diff --git a/spotifyvis/migrations/0001_initial.py b/spotifyvis/migrations/0001_initial.py index e22ffd2..167e531 100644 --- a/spotifyvis/migrations/0001_initial.py +++ b/spotifyvis/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.0.5 on 2018-06-03 23:01 +# Generated by Django 2.0.5 on 2018-06-06 07:26 from django.db import migrations, models import django.db.models.deletion @@ -20,34 +20,32 @@ class Migration(migrations.Migration): ('genre', models.CharField(max_length=20)), ], options={ - 'verbose_name': 'Artist', 'verbose_name_plural': 'Artists', + 'verbose_name': 'Artist', }, ), 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)), + ('track_id', models.CharField(max_length=30, primary_key=True, serialize=False)), ('year', models.PositiveSmallIntegerField()), - ('popularity', models.DecimalField(decimal_places=2, max_digits=2)), + ('popularity', models.PositiveSmallIntegerField()), ('runtime', models.PositiveSmallIntegerField()), ('name', models.CharField(max_length=75)), ], options={ - 'verbose_name': 'Track', 'verbose_name_plural': 'Tracks', + 'verbose_name': 'Track', }, ), 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', + 'verbose_name': 'User', }, ), migrations.CreateModel( @@ -64,22 +62,18 @@ class Migration(migrations.Migration): ('tempo', models.DecimalField(decimal_places=2, max_digits=2)), ], options={ - 'verbose_name': 'AudioFeatures', 'verbose_name_plural': 'AudioFeatures', + 'verbose_name': 'AudioFeatures', }, ), migrations.AddField( model_name='track', - name='artist', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='spotifyvis.Artist'), + name='artists', + field=models.ManyToManyField(blank=True, 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')}, + field=models.ManyToManyField(blank=True, to='spotifyvis.User'), ), ] diff --git a/spotifyvis/models.py b/spotifyvis/models.py index d33da87..15fee46 100644 --- a/spotifyvis/models.py +++ b/spotifyvis/models.py @@ -1,5 +1,8 @@ from django.db import models +# id's are 22 in length in examples but set to 30 for buffer +id_length=30 + # Artist {{{ # class Artist(models.Model): @@ -7,7 +10,7 @@ class Artist(models.Model): verbose_name = "Artist" verbose_name_plural = "Artists" - artist_id = models.CharField(primary_key=True, max_length=30) + artist_id = models.CharField(primary_key=True, max_length=id_length) # unique since only storing one genre per artist right now name = models.CharField(unique=True, max_length=50) genre = models.CharField(max_length=20) @@ -24,11 +27,11 @@ class User(models.Model): verbose_name = "User" verbose_name_plural = "Users" - user_id = models.CharField(primary_key=True, max_length=30) # the user's Spotify ID - username = models.CharField(max_length=30) # User's Spotify user name, if set + user_id = models.CharField(primary_key=True, max_length=id_length) # the user's Spotify ID + # username = models.CharField(max_length=30) # User's Spotify user name, if set def __str__(self): - return self.username + return self.user_id # }}} User # @@ -39,15 +42,16 @@ class Track(models.Model): class Meta: verbose_name = "Track" verbose_name_plural = "Tracks" - unique_together = ('track_id', 'artist',) + # unique_together = ('track_id', 'artist',) - track_id = models.CharField(max_length=30) - artist = models.ForeignKey(Artist, on_delete=models.CASCADE) + track_id = models.CharField(primary_key=True, max_length=id_length) + # artist = models.ForeignKey(Artist, on_delete=models.CASCADE) + artists = models.ManyToManyField(Artist, blank=True) year = models.PositiveSmallIntegerField() - popularity = models.DecimalField(decimal_places=2, max_digits=2) + popularity = models.PositiveSmallIntegerField() runtime = models.PositiveSmallIntegerField() name = models.CharField(max_length=75) - users = models.ManyToManyField(User) + users = models.ManyToManyField(User, blank=True) def __str__(self): return self.name diff --git a/spotifyvis/utils.py b/spotifyvis/utils.py index 9fc0dc7..cc8ff7c 100644 --- a/spotifyvis/utils.py +++ b/spotifyvis/utils.py @@ -30,23 +30,38 @@ def parse_library(headers, tracks, library_stats, user): num_samples = 0 # number of actual track samples feature_data_points = 0 # number of feature data analyses (some tracks do not have analyses available) + # iterate until hit requested num of tracks for _ in range(0, tracks, limit): payload['offset'] = str(offset) + # get current set of tracks saved_tracks_response = requests.get('https://api.spotify.com/v1/me/tracks', headers=headers, params=payload).json() + + # TODO: refactor the for loop body into helper function + # iterate through each track for track_dict in saved_tracks_response['items']: num_samples += 1 + # update artist info before track so that Track object can reference + # Artist object + track_artists = [] + for artist_dict in track_dict['track']['artists']: + increase_artist_count(headers, artist_dict['name'], + artist_dict['id'], library_stats) + track_artists.append(Artist.objects.get_or_create( + artist_id=artist_dict['id'], + name=artist_dict['name'], + )[0]) + + save_track_obj(track_dict['track'], track_artists, user) 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']) if len(audio_features_dict) != 0: # Track the number of audio analyses for calculating # audio feature averages and standard deviations on the fly feature_data_points += 1 - for feature, feature_data in audio_features_dict.items(): - update_audio_feature_stats(feature, feature_data, feature_data_points, library_stats) - for artist_dict in track_dict['track']['artists']: - increase_artist_count(headers, artist_dict['name'], artist_dict['id'], library_stats) + update_audio_feature_stats(feature, feature_data, + feature_data_points, library_stats) + # calculates num_songs with offset + songs retrieved library_stats['num_songs'] = offset + len(saved_tracks_response['items']) offset += limit @@ -55,6 +70,32 @@ def parse_library(headers, tracks, library_stats, user): # }}} parse_library # +def save_track_obj(track_dict, artists, user): + """Make an entry in the database for this track if it doesn't exist already. + + :track_dict: TODO + :artists: artists of the song, passed in as a list of Artist objects. + :user: TODO + :returns: None + + """ + if len(Track.objects.filter(track_id__exact=track_dict['id'])) == 0: + new_track = Track.objects.create( + track_id=track_dict['id'], + year=track_dict['album']['release_date'].split('-')[0], + popularity=int(track_dict['popularity']), + runtime=int(float(track_dict['duration_ms']) / 1000), + name=track_dict['name'], + ) + # print("pop/run: ", new_track.popularity, new_track.runtime) + + # have to add artists and user after saving object since track needs to + # have ID before filling in m2m field + for artist in artists: + new_track.artists.add(artist) + new_track.users.add(user) + new_track.save() + # get_audio_features {{{ # def get_audio_features(headers, track_id): @@ -185,14 +226,6 @@ def increase_artist_count(headers, artist_name, artist_id, library_stats): else: library_stats['artists'][artist_name]['count'] += 1 - # add artist to database if new - if len(Artist.objects.filter(artist_id__contains=artist_id)) == 0: - new_artist = Artist( - artist_id=artist_id, - name=artist_name, - ) - new_artist.save() - # }}} increase_artist_count # # update_popularity_stats {{{ # diff --git a/spotifyvis/views.py b/spotifyvis/views.py index d6800bd..65c9a31 100644 --- a/spotifyvis/views.py +++ b/spotifyvis/views.py @@ -139,12 +139,9 @@ def user_data(request): 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'] - user = None # will be set to the current user object later - # try: - # user = User.objects.get(user_id=request.session['user_id']) - # except User.DoesNotExist: - # user = User.objects.create(user_id=request.session['user_id'], user_name=request.session['user_name']) + # request.session['user_name'] = user_data_response['display_name'] + user = User.objects.get_or_create(user_id=user_data_response['id'])[0] + context = { 'user_name': user_data_response['display_name'], 'id': user_data_response['id'],