Split spotifyvis code into different apps (#47)

Server is able to start, but none of the apps are linked together yet.
This commit is contained in:
2018-06-29 04:15:08 -04:00
parent 8851c5ce25
commit 8b1344d453
34 changed files with 319 additions and 538 deletions

4
api/apps.py Normal file
View File

@@ -0,0 +1,4 @@
from django.apps import AppConfig
class ApiConfig(AppConfig):
name = 'api'

12
api/urls.py Normal file
View File

@@ -0,0 +1,12 @@
from django.urls import path, include
from .views import *
urlpatterns = [
path('user_artists/<str:user_secret>', get_artist_data,
name='get_artist_data'),
path('user_genres/<str:user_secret>', get_genre_data,
name='get_genre_data'),
path('audio_features/<str:audio_feature>/<str:user_secret>',
get_audio_feature_data, name='get_audio_feature_data'),
]

86
api/views.py Normal file
View File

@@ -0,0 +1,86 @@
# imports {{{ #
import math
import random
import requests
import os
import urllib
import secrets
import pprint
import string
from datetime import datetime
from django.http import JsonResponse
from django.db.models import Count, Q
from .utils import parse_library, get_artists_in_genre, update_track_genres
from .models import User, Track, AudioFeatures, Artist
# }}} imports #
TRACKS_TO_QUERY = 200
# get_artist_data {{{ #
def get_artist_data(request, user_secret):
"""Returns artist data as a JSON serialized list of dictionaries
The (key, value) pairs are (artist name, song count for said artist)
:param request: the HTTP request
:param user_secret: the user secret used for identification
:return: a JsonResponse
"""
user = User.objects.get(user_secret=user_secret)
artist_counts = Artist.objects.annotate(num_songs=Count('track',
filter=Q(track__users=user)))
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 #
# get_audio_feature_data {{{ #
def get_audio_feature_data(request, audio_feature, user_secret):
"""Returns all data points for a given audio feature
Args:
request: the HTTP request
audio_feature: The audio feature to be queried
user_secret: client secret, used to identify the user
"""
user = User.objects.get(user_secret=user_secret)
user_tracks = Track.objects.filter(users=user)
response_payload = {
'data_points': [],
}
for track in user_tracks:
try:
audio_feature_obj = AudioFeatures.objects.get(track=track)
response_payload['data_points'].append(getattr(audio_feature_obj, audio_feature))
except AudioFeatures.DoesNotExist:
continue
return JsonResponse(response_payload)
# }}} get_audio_feature_data #
# get_genre_data {{{ #
def get_genre_data(request, user_secret):
"""Return genre data needed to create the graph user.
TODO
"""
user = User.objects.get(user_secret=user_secret)
genre_counts = (Track.objects.filter(users__exact=user)
.values('genre')
.order_by('genre')
.annotate(num_songs=Count('genre'))
)
for genre_dict in genre_counts:
genre_dict['artists'] = get_artists_in_genre(user, genre_dict['genre'],
genre_dict['num_songs'])
print("*** Genre Breakdown ***")
pprint.pprint(list(genre_counts))
return JsonResponse(data=list(genre_counts), safe=False)
# }}} get_genre_data #

4
graphs/apps.py Normal file
View File

@@ -0,0 +1,4 @@
from django.apps import AppConfig
class GraphsConfig(AppConfig):
name = 'graphs'

104
graphs/models.py Normal file
View File

@@ -0,0 +1,104 @@
from django.db import models
# id's are 22 in length in examples but set to 30 for buffer
MAX_ID = 30
# Genre {{{ #
class Genre(models.Model):
class Meta:
verbose_name = "Genre"
verbose_name_plural = "Genres"
name = models.CharField(primary_key=True, max_length=50)
num_songs = models.PositiveIntegerField()
def __str__(self):
return self.name
# }}} Genre #
# Artist {{{ #
class Artist(models.Model):
class Meta:
verbose_name = "Artist"
verbose_name_plural = "Artists"
artist_id = models.CharField(primary_key=True, max_length=MAX_ID)
# unique since only storing one genre per artist right now
name = models.CharField(unique=True, max_length=50)
genres = models.ManyToManyField(Genre, blank=True)
def __str__(self):
return self.name
# }}} Artist #
# User {{{ #
class User(models.Model):
class Meta:
verbose_name = "User"
verbose_name_plural = "Users"
user_id = models.CharField(primary_key=True, max_length=MAX_ID) # the user's Spotify ID
user_secret = models.CharField(max_length=50, default='')
def __str__(self):
return self.user_id
# }}} User #
# Track {{{ #
class Track(models.Model):
class Meta:
verbose_name = "Track"
verbose_name_plural = "Tracks"
track_id = models.CharField(primary_key=True, max_length=MAX_ID)
# artist = models.ForeignKey(Artist, on_delete=models.CASCADE)
artists = models.ManyToManyField(Artist, blank=True)
year = models.PositiveSmallIntegerField()
popularity = models.PositiveSmallIntegerField()
runtime = models.PositiveSmallIntegerField()
name = models.CharField(max_length=200)
users = models.ManyToManyField(User, blank=True)
genre = models.ForeignKey(Genre, on_delete=models.CASCADE, blank=True,
null=True)
def __str__(self):
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 #
# AudioFeatures {{{ #
class AudioFeatures(models.Model):
class Meta:
verbose_name = "AudioFeatures"
verbose_name_plural = "AudioFeatures"
track = models.OneToOneField(Track, on_delete=models.CASCADE, primary_key=True,)
acousticness = models.DecimalField(decimal_places=3, max_digits=3)
danceability = models.DecimalField(decimal_places=3, max_digits=3)
energy = models.DecimalField(decimal_places=3, max_digits=3)
instrumentalness = models.DecimalField(decimal_places=3, max_digits=3)
loudness = models.DecimalField(decimal_places=3, max_digits=6)
speechiness = models.DecimalField(decimal_places=3, max_digits=3)
tempo = models.DecimalField(decimal_places=3, max_digits=6)
valence = models.DecimalField(decimal_places=3, max_digits=3)
def __str__(self):
return super(AudioFeatures, self).__str__()
# }}} AudioFeatures #

12
graphs/urls.py Normal file
View File

@@ -0,0 +1,12 @@
from django.urls import path, include
from .views import *
urlpatterns = [
path('artists/<str:user_secret>', artist_data,
name='display_artist_graph'),
path('genre/<str:user_secret>', display_genre_graph,
name='display_genre_graph'),
path('audio_features/<str:user_secret>', audio_features,
name='display_audio_features'),
]

51
graphs/views.py Normal file
View File

@@ -0,0 +1,51 @@
# imports {{{ #
import math
import random
import requests
import os
import urllib
import secrets
import pprint
import string
from datetime import datetime
from django.shortcuts import render, redirect
# }}} imports #
def artist_data(request, user_secret):
"""Renders the artist data graph display page
:param request: the HTTP request
:param user_secret: the user secret used for identification
:return: render the artist data graph display page
"""
user = User.objects.get(user_secret=user_secret)
context = {
'user_id': user.user_id,
'user_secret': user_secret,
}
return render(request, "spotifyvis/artist_graph.html", context)
def display_genre_graph(request, user_secret):
user = User.objects.get(user_secret=user_secret)
context = {
'user_secret': user_secret,
}
return render(request, "spotifyvis/genre_graph.html", context)
def audio_features(request, user_secret):
"""Renders the audio features page
:param request: the HTTP request
:param user_secret: user secret used for identification
:return: renders the audio features page
"""
user = User.objects.get(user_secret=user_secret)
context = {
'user_id': user.user_id,
'user_secret': user_secret,
}
return render(request, "spotifyvis/audio_features.html", context)

0
login/__init__.py Normal file
View File

4
login/apps.py Normal file
View File

@@ -0,0 +1,4 @@
from django.apps import AppConfig
class LoginConfig(AppConfig):
name = 'login'

11
login/urls.py Normal file
View File

@@ -0,0 +1,11 @@
from django.urls import path, include
from .views import *
urlpatterns = [
path('', index, name='index'),
path('spotify_login', spotify_login, name='spotify_login'),
path('callback', callback, name='callback'),
path('user_data', user_data, name='user_data'),
path('admin_graphs', admin_graphs, name='admin_graphs'),
]

View File

@@ -11,10 +11,7 @@ import string
from datetime import datetime from datetime import datetime
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.http import HttpResponse, HttpResponseBadRequest, JsonResponse from django.http import HttpResponseBadRequest
from django.db.models import Count, Q
from .utils import parse_library, get_artists_in_genre, update_track_genres
from .models import User, Track, AudioFeatures, Artist
# }}} imports # # }}} imports #
@@ -65,7 +62,7 @@ def index(request):
# login {{{ # # login {{{ #
# uses Authorization Code flow # uses Authorization Code flow
def login(request): def spotify_login(request):
# use a randomly generated state string to prevent cross-site request forgery attacks # use a randomly generated state string to prevent cross-site request forgery attacks
state_str = generate_random_string(16) state_str = generate_random_string(16)
request.session['state_string'] = state_str request.session['state_string'] = state_str
@@ -117,7 +114,6 @@ def callback(request):
# user_data {{{ # # user_data {{{ #
def user_data(request): def user_data(request):
# get user token {{{ # # get user token {{{ #
@@ -165,7 +161,8 @@ def user_data(request):
'user_secret': user.user_secret, 'user_secret': user.user_secret,
} }
parse_library(headers, TRACKS_TO_QUERY, user) # TODO: redirect to API app to parse library or loading page
# parse_library(headers, TRACKS_TO_QUERY, user)
return render(request, 'spotifyvis/logged_in.html', context) return render(request, 'spotifyvis/logged_in.html', context)
# }}} user_data # # }}} user_data #
@@ -182,107 +179,3 @@ def admin_graphs(request):
} }
update_track_genres(user_obj) update_track_genres(user_obj)
return render(request, 'spotifyvis/logged_in.html', context) return render(request, 'spotifyvis/logged_in.html', context)
def artist_data(request, user_secret):
"""Renders the artist data graph display page
:param request: the HTTP request
:param user_secret: the user secret used for identification
:return: render the artist data graph display page
"""
user = User.objects.get(user_secret=user_secret)
context = {
'user_id': user.user_id,
'user_secret': user_secret,
}
return render(request, "spotifyvis/artist_graph.html", context)
# get_artist_data {{{ #
def get_artist_data(request, user_secret):
"""Returns artist data as a JSON serialized list of dictionaries
The (key, value) pairs are (artist name, song count for said artist)
:param request: the HTTP request
:param user_secret: the user secret used for identification
:return: a JsonResponse
"""
user = User.objects.get(user_secret=user_secret)
artist_counts = Artist.objects.annotate(num_songs=Count('track',
filter=Q(track__users=user)))
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 #
def display_genre_graph(request, user_secret):
user = User.objects.get(user_secret=user_secret)
context = {
'user_secret': user_secret,
}
return render(request, "spotifyvis/genre_graph.html", context)
def audio_features(request, user_secret):
"""Renders the audio features page
:param request: the HTTP request
:param user_secret: user secret used for identification
:return: renders the audio features page
"""
user = User.objects.get(user_secret=user_secret)
context = {
'user_id': user.user_id,
'user_secret': user_secret,
}
return render(request, "spotifyvis/audio_features.html", context)
# get_audio_feature_data {{{ #
def get_audio_feature_data(request, audio_feature, user_secret):
"""Returns all data points for a given audio feature
Args:
request: the HTTP request
audio_feature: The audio feature to be queried
user_secret: client secret, used to identify the user
"""
user = User.objects.get(user_secret=user_secret)
user_tracks = Track.objects.filter(users=user)
response_payload = {
'data_points': [],
}
for track in user_tracks:
try:
audio_feature_obj = AudioFeatures.objects.get(track=track)
response_payload['data_points'].append(getattr(audio_feature_obj, audio_feature))
except AudioFeatures.DoesNotExist:
continue
return JsonResponse(response_payload)
# }}} get_audio_feature_data #
# get_genre_data {{{ #
def get_genre_data(request, user_secret):
"""Return genre data needed to create the graph user.
TODO
"""
user = User.objects.get(user_secret=user_secret)
genre_counts = (Track.objects.filter(users__exact=user)
.values('genre')
.order_by('genre')
.annotate(num_songs=Count('genre'))
)
for genre_dict in genre_counts:
genre_dict['artists'] = get_artists_in_genre(user, genre_dict['genre'],
genre_dict['num_songs'])
print("*** Genre Breakdown ***")
pprint.pprint(list(genre_counts))
return JsonResponse(data=list(genre_counts), safe=False)
# }}} get_genre_data #

View File

@@ -3,7 +3,7 @@ import os
import sys import sys
if __name__ == "__main__": if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "musicvis.settings") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "spotifyvis.settings")
try: try:
from django.core.management import execute_from_command_line from django.core.management import execute_from_command_line
except ImportError as exc: except ImportError as exc:

View File

@@ -1,22 +0,0 @@
"""musicdata URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/2.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('', include('spotifyvis.urls')),
path('admin/', admin.site.urls),
]

View File

@@ -1,250 +0,0 @@
{
'added_at':'2018-05-18T19:16:36Z',
'track':{
'album':{
'album_type':'single',
'artists':[
{
'external_urls':{
'spotify':'https://open.spotify.com/artist/64KEffDW9EtZ1y2vBYgq8T'
},
'href':'https://api.spotify.com/v1/artists/64KEffDW9EtZ1y2vBYgq8T',
'id':'64KEffDW9EtZ1y2vBYgq8T',
'name':'Marshmello',
'type':'artist',
'uri':'spotify:artist:64KEffDW9EtZ1y2vBYgq8T'
},
{
'external_urls':{
'spotify':'https://open.spotify.com/artist/5gCRApTajqwbnHHPbr2Fpi'
},
'href':'https://api.spotify.com/v1/artists/5gCRApTajqwbnHHPbr2Fpi',
'id':'5gCRApTajqwbnHHPbr2Fpi',
'name':'Juicy J',
'type':'artist',
'uri':'spotify:artist:5gCRApTajqwbnHHPbr2Fpi'
},
{
'external_urls':{
'spotify':'https://open.spotify.com/artist/4IWBUUAFIplrNtaOHcJPRM'
},
'href':'https://api.spotify.com/v1/artists/4IWBUUAFIplrNtaOHcJPRM',
'id':'4IWBUUAFIplrNtaOHcJPRM',
'name':'James Arthur',
'type':'artist',
'uri':'spotify:artist:4IWBUUAFIplrNtaOHcJPRM'
}
],
'available_markets':[
'AD',
'AR',
'AT',
'AU',
'BE',
'BG',
'BO',
'BR',
'CA',
'CH',
'CL',
'CO',
'CR',
'CY',
'CZ',
'DE',
'DK',
'DO',
'EC',
'EE',
'ES',
'FI',
'FR',
'GB',
'GR',
'GT',
'HK',
'HN',
'HU',
'ID',
'IE',
'IL',
'IS',
'IT',
'JP',
'LI',
'LT',
'LU',
'LV',
'MC',
'MT',
'MX',
'MY',
'NI',
'NL',
'NO',
'NZ',
'PA',
'PE',
'PH',
'PL',
'PT',
'PY',
'RO',
'SE',
'SG',
'SK',
'SV',
'TH',
'TR',
'TW',
'US',
'UY',
'VN',
'ZA'
],
'external_urls':{
'spotify':'https://open.spotify.com/album/6TvqOieExu0IJb9Q1gOoCz'
},
'href':'https://api.spotify.com/v1/albums/6TvqOieExu0IJb9Q1gOoCz',
'id':'6TvqOieExu0IJb9Q1gOoCz',
'images':[
{
'height':640,
'url':'https://i.scdn.co/image/b3556956b8e4881c85228ada91aa953e5c0458ef',
'width':640
},
{
'height':300,
'url':'https://i.scdn.co/image/d76072f5ca739466bd27f42f3356fa1a38c6a92d',
'width':300
},
{
'height':64,
'url':'https://i.scdn.co/image/bfd092dfa503566d9c9a3042f213fe02bed8a5cc',
'width':64
}
],
'name':'You Can Cry',
'release_date':'2018-05-04',
'release_date_precision':'day',
'type':'album',
'uri':'spotify:album:6TvqOieExu0IJb9Q1gOoCz'
},
'artists':[
{
'external_urls':{
'spotify':'https://open.spotify.com/artist/64KEffDW9EtZ1y2vBYgq8T'
},
'href':'https://api.spotify.com/v1/artists/64KEffDW9EtZ1y2vBYgq8T',
'id':'64KEffDW9EtZ1y2vBYgq8T',
'name':'Marshmello',
'type':'artist',
'uri':'spotify:artist:64KEffDW9EtZ1y2vBYgq8T'
},
{
'external_urls':{
'spotify':'https://open.spotify.com/artist/5gCRApTajqwbnHHPbr2Fpi'
},
'href':'https://api.spotify.com/v1/artists/5gCRApTajqwbnHHPbr2Fpi',
'id':'5gCRApTajqwbnHHPbr2Fpi',
'name':'Juicy J',
'type':'artist',
'uri':'spotify:artist:5gCRApTajqwbnHHPbr2Fpi'
},
{
'external_urls':{
'spotify':'https://open.spotify.com/artist/4IWBUUAFIplrNtaOHcJPRM'
},
'href':'https://api.spotify.com/v1/artists/4IWBUUAFIplrNtaOHcJPRM',
'id':'4IWBUUAFIplrNtaOHcJPRM',
'name':'James Arthur',
'type':'artist',
'uri':'spotify:artist:4IWBUUAFIplrNtaOHcJPRM'
}
],
'available_markets':[
'AD',
'AR',
'AT',
'AU',
'BE',
'BG',
'BO',
'BR',
'CA',
'CH',
'CL',
'CO',
'CR',
'CY',
'CZ',
'DE',
'DK',
'DO',
'EC',
'EE',
'ES',
'FI',
'FR',
'GB',
'GR',
'GT',
'HK',
'HN',
'HU',
'ID',
'IE',
'IL',
'IS',
'IT',
'JP',
'LI',
'LT',
'LU',
'LV',
'MC',
'MT',
'MX',
'MY',
'NI',
'NL',
'NO',
'NZ',
'PA',
'PE',
'PH',
'PL',
'PT',
'PY',
'RO',
'SE',
'SG',
'SK',
'SV',
'TH',
'TR',
'TW',
'US',
'UY',
'VN',
'ZA'
],
'disc_number':1,
'duration_ms':194533,
'explicit':False,
'external_ids':{
'isrc':'USQX91800946'
},
'external_urls':{
'spotify':'https://open.spotify.com/track/3ZbJMlEL4Kcme0ONRO7Slx'
},
'href':'https://api.spotify.com/v1/tracks/3ZbJMlEL4Kcme0ONRO7Slx',
'id':'3ZbJMlEL4Kcme0ONRO7Slx',
'name':'You Can Cry',
'popularity':81,
'preview_url':'https://p.scdn.co/mp3-preview/6c31f3dee18a1e7c452ce9b6948a6e04aa7629d6?cid=aefd4e45060d4f9ba5bea0f6e6d36359',
'track_number':1,
'type':'track',
'uri':'spotify:track:3ZbJMlEL4Kcme0ONRO7Slx'
}
}

View File

@@ -1,5 +0,0 @@
from django.apps import AppConfig
class SpotifyvisConfig(AppConfig):
name = 'spotifyvis'

View File

@@ -37,7 +37,9 @@ INSTALLED_APPS = [
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'spotifyvis.apps.SpotifyvisConfig', 'login.apps.LoginConfig',
'api.apps.ApiConfig',
'graphs.apps.GraphsConfig',
] ]
MIDDLEWARE = [ MIDDLEWARE = [
@@ -50,7 +52,7 @@ MIDDLEWARE = [
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
] ]
ROOT_URLCONF = 'musicvis.urls' ROOT_URLCONF = 'spotifyvis.urls'
TEMPLATES = [ TEMPLATES = [
{ {
@@ -68,7 +70,7 @@ TEMPLATES = [
}, },
] ]
WSGI_APPLICATION = 'musicvis.wsgi.application' WSGI_APPLICATION = 'spotifyvis.wsgi.application'
# Database # Database

View File

@@ -1,42 +0,0 @@
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();
});

View File

@@ -1,21 +0,0 @@
{% load static %}
<!DOCTYPE html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>User Spotify Data</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="{% static 'spotifyvis/css/dark_bg.css' %}">
</head>
<body>
<!--[if lt IE 7]>
<p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="#">upgrade your browser</a> to improve your experience.</p>
<![endif]-->
<p>Logged in as {{ id }}</p>
</body>
</html>

View File

@@ -1,67 +0,0 @@
from django.test import TestCase
from .utils import update_std_dev
import math
# Create your tests here.
class UpdateStdDevTest(TestCase):
def test_two_data_points(self):
"""
tests if update_std_dev behaves correctly for two data points
"""
cur_mean = 5
cur_std_dev = 0
new_mean, new_std_dev = update_std_dev(cur_mean, cur_std_dev, 10, 2)
self.assertTrue(math.isclose(new_mean, 7.5, rel_tol=0.01))
self.assertTrue(math.isclose(new_std_dev, 3.5355, rel_tol=0.01))
def test_three_data_points(self):
"""
tests if update_std_dev behaves correctly for three data points
"""
cur_mean = 7.5
cur_std_dev = 3.5355
new_mean, new_std_dev = update_std_dev(cur_mean, cur_std_dev, 15, 3)
self.assertTrue(math.isclose(new_mean, 10, rel_tol=0.01))
self.assertTrue(math.isclose(new_std_dev, 5, rel_tol=0.01))
def test_four_data_points(self):
"""
tests if update_std_dev behaves correctly for four data points
"""
cur_mean = 10
cur_std_dev = 5
new_mean, new_std_dev = update_std_dev(cur_mean, cur_std_dev, 20, 4)
self.assertTrue(math.isclose(new_mean, 12.5, rel_tol=0.01))
self.assertTrue(math.isclose(new_std_dev, 6.455, rel_tol=0.01))
def test_five_data_points(self):
"""
tests if update_std_dev behaves correctly for five data points
"""
cur_mean = 12.5
cur_std_dev = 6.455
new_mean, new_std_dev = update_std_dev(cur_mean, cur_std_dev, 63, 5)
self.assertTrue(math.isclose(new_mean, 22.6, rel_tol=0.01))
self.assertTrue(math.isclose(new_std_dev, 23.2658, rel_tol=0.01))
def test_sixteen_data_points(self):
"""
tests if update_std_dev behaves correctly for sixteen data points
"""
cur_mean = 0.4441
cur_std_dev = 0.2855
new_mean, new_std_dev = update_std_dev(cur_mean, cur_std_dev, 0.7361, 16)
self.assertTrue(math.isclose(new_mean, 0.4624, rel_tol=0.01))
self.assertTrue(math.isclose(new_std_dev, 0.2853, rel_tol=0.01))

View File

@@ -1,19 +1,24 @@
"""musicdata URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/2.0/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include from django.urls import path, include
from .views import *
urlpatterns = [ urlpatterns = [
path('', index, name='index'), path('admin/', admin.site.urls),
path('login', login, name='login'), path('login/', include('login.urls')),
path('callback', callback, name='callback'), path('api/', include('api.urls')),
path('user_data', user_data, name='user_data'), path('graphs/', include('graphs.urls')),
path('admin_graphs', admin_graphs, name='admin_graphs'),
path('api/user_artists/<str:user_secret>', get_artist_data, name='get_artist_data'),
path('artists/<str:user_secret>', artist_data, name='display_artist_graph'),
path('api/user_genres/<str:user_secret>', get_genre_data, name='get_genre_data'),
path('graphs/genre/<str:user_secret>', display_genre_graph,
name='display_genre_graph'),
path('audio_features/<str:user_secret>', audio_features, name='display_audio_features'),
path('api/audio_features/<str:audio_feature>/<str:user_secret>',
get_audio_feature_data, name='get_audio_feature_data'),
] ]

View File

@@ -11,6 +11,6 @@ import os
from django.core.wsgi import get_wsgi_application from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "musicvis.settings") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "spotifyvis.settings")
application = get_wsgi_application() application = get_wsgi_application()