Parse listening history and add Track objects
This commit is contained in:
@@ -47,7 +47,7 @@ class Track(models.Model):
|
|||||||
id = models.CharField(primary_key=True, max_length=MAX_ID)
|
id = models.CharField(primary_key=True, max_length=MAX_ID)
|
||||||
# artist = models.ForeignKey(Artist, on_delete=models.CASCADE)
|
# artist = models.ForeignKey(Artist, on_delete=models.CASCADE)
|
||||||
artists = models.ManyToManyField(Artist, blank=True)
|
artists = models.ManyToManyField(Artist, blank=True)
|
||||||
year = models.PositiveSmallIntegerField()
|
year = models.PositiveSmallIntegerField(null=True)
|
||||||
popularity = models.PositiveSmallIntegerField()
|
popularity = models.PositiveSmallIntegerField()
|
||||||
runtime = models.PositiveSmallIntegerField()
|
runtime = models.PositiveSmallIntegerField()
|
||||||
name = models.CharField(max_length=200)
|
name = models.CharField(max_length=200)
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ from .views import *
|
|||||||
|
|
||||||
app_name = 'api'
|
app_name = 'api'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('scan/<str:user_secret>', parse_library,
|
path('scan/library/<str:user_secret>', parse_library,
|
||||||
name='scan'),
|
name='scan_library'),
|
||||||
|
path('scan/history/<str:user_secret>', parse_history,
|
||||||
|
name='scan_history'),
|
||||||
path('user_artists/<str:user_secret>', get_artist_data,
|
path('user_artists/<str:user_secret>', get_artist_data,
|
||||||
name='get_artist_data'),
|
name='get_artist_data'),
|
||||||
path('user_genres/<str:user_secret>', get_genre_data,
|
path('user_genres/<str:user_secret>', get_genre_data,
|
||||||
|
|||||||
43
api/utils.py
43
api/utils.py
@@ -14,8 +14,8 @@ from login.models import User
|
|||||||
|
|
||||||
# }}} imports #
|
# }}} imports #
|
||||||
|
|
||||||
console_logging = True
|
# console_logging = True
|
||||||
# console_logging = False
|
console_logging = False
|
||||||
artists_genre_processed = 0
|
artists_genre_processed = 0
|
||||||
features_processed = 0
|
features_processed = 0
|
||||||
|
|
||||||
@@ -74,19 +74,32 @@ def save_track_obj(track_dict, artists, user_obj):
|
|||||||
if len(track_query) != 0:
|
if len(track_query) != 0:
|
||||||
return track_query[0], False
|
return track_query[0], False
|
||||||
else:
|
else:
|
||||||
new_track = Track.objects.create(
|
# check if track is simple or full, simple Track object won't have year
|
||||||
id=track_dict['id'],
|
# if 'album' in track_dict:
|
||||||
year=track_dict['album']['release_date'].split('-')[0],
|
try:
|
||||||
popularity=int(track_dict['popularity']),
|
new_track = Track.objects.create(
|
||||||
runtime=int(float(track_dict['duration_ms']) / 1000),
|
id=track_dict['id'],
|
||||||
name=track_dict['name'],
|
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'],
|
||||||
|
)
|
||||||
|
# else:
|
||||||
|
except KeyError:
|
||||||
|
new_track = Track.objects.create(
|
||||||
|
id=track_dict['id'],
|
||||||
|
popularity=int(track_dict['popularity']),
|
||||||
|
runtime=int(float(track_dict['duration_ms']) / 1000),
|
||||||
|
name=track_dict['name'],
|
||||||
|
)
|
||||||
|
|
||||||
# have to add artists and user_obj after saving object since track needs to
|
# have to add artists and user_obj after saving object since track needs to
|
||||||
# have ID before filling in m2m field
|
# have ID before filling in m2m field
|
||||||
for artist in artists:
|
for artist in artists:
|
||||||
new_track.artists.add(artist)
|
new_track.artists.add(artist)
|
||||||
new_track.users.add(user_obj)
|
# print(new_track.name, artist.name)
|
||||||
|
if user_obj != None:
|
||||||
|
new_track.users.add(user_obj)
|
||||||
new_track.save()
|
new_track.save()
|
||||||
return new_track, True
|
return new_track, True
|
||||||
|
|
||||||
@@ -178,6 +191,7 @@ def add_artist_genres(headers, artist_objs):
|
|||||||
else:
|
else:
|
||||||
for genre in artists_response[i]['genres']:
|
for genre in artists_response[i]['genres']:
|
||||||
process_artist_genre(genre, artist_objs[i])
|
process_artist_genre(genre, artist_objs[i])
|
||||||
|
# print(artist_objs[i].name, genre)
|
||||||
|
|
||||||
if console_logging:
|
if console_logging:
|
||||||
global artists_genre_processed
|
global artists_genre_processed
|
||||||
@@ -221,6 +235,15 @@ def get_artists_in_genre(user, genre, max_songs):
|
|||||||
|
|
||||||
# }}} get_artists_in_genre #
|
# }}} get_artists_in_genre #
|
||||||
|
|
||||||
|
def create_artist_for_track(artist_dict):
|
||||||
|
"""TODO: Docstring for create_artist_for_track.
|
||||||
|
|
||||||
|
:artist_dict: TODO
|
||||||
|
:returns: None
|
||||||
|
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
def get_user_header(user_obj):
|
def get_user_header(user_obj):
|
||||||
"""Returns the authorization string needed to make an API call.
|
"""Returns the authorization string needed to make an API call.
|
||||||
|
|
||||||
|
|||||||
68
api/views.py
68
api/views.py
@@ -19,13 +19,16 @@ from login.utils import get_user_context
|
|||||||
# }}} imports #
|
# }}} imports #
|
||||||
|
|
||||||
USER_TRACKS_LIMIT = 50
|
USER_TRACKS_LIMIT = 50
|
||||||
|
HISTORY_LIMIT = 50
|
||||||
ARTIST_LIMIT = 50
|
ARTIST_LIMIT = 50
|
||||||
FEATURES_LIMIT = 100
|
FEATURES_LIMIT = 100
|
||||||
# ARTIST_LIMIT = 25
|
# ARTIST_LIMIT = 25
|
||||||
# FEATURES_LIMIT = 25
|
# FEATURES_LIMIT = 25
|
||||||
TRACKS_TO_QUERY = 100
|
TRACKS_TO_QUERY = 100
|
||||||
|
HISTORY_ENDPOINT = 'https://api.spotify.com/v1/me/player/recently-played'
|
||||||
|
|
||||||
console_logging = True
|
console_logging = True
|
||||||
|
# console_logging = False
|
||||||
|
|
||||||
# parse_library {{{ #
|
# parse_library {{{ #
|
||||||
|
|
||||||
@@ -119,6 +122,71 @@ def parse_library(request, user_secret):
|
|||||||
|
|
||||||
# }}} parse_library #
|
# }}} parse_library #
|
||||||
|
|
||||||
|
# parse_history {{{ #
|
||||||
|
|
||||||
|
def parse_history(request, user_secret):
|
||||||
|
"""Scans user's listening history and stores the information in a
|
||||||
|
database.
|
||||||
|
|
||||||
|
:user_secret: secret for User object who's library is being scanned.
|
||||||
|
:returns: None
|
||||||
|
"""
|
||||||
|
|
||||||
|
payload = {'limit': str(USER_TRACKS_LIMIT)}
|
||||||
|
artist_genre_queue = []
|
||||||
|
user_obj = User.objects.get(secret=user_secret)
|
||||||
|
user_headers = get_user_header(user_obj)
|
||||||
|
|
||||||
|
history_response = requests.get(HISTORY_ENDPOINT,
|
||||||
|
headers=user_headers,
|
||||||
|
params=payload).json()['items']
|
||||||
|
|
||||||
|
if console_logging:
|
||||||
|
tracks_processed = 0
|
||||||
|
|
||||||
|
for track_dict in history_response:
|
||||||
|
# add artists {{{ #
|
||||||
|
|
||||||
|
# update artist info before track so that Track object can reference
|
||||||
|
# Artist object
|
||||||
|
track_artists = []
|
||||||
|
for artist_dict in track_dict['track']['artists']:
|
||||||
|
artist_obj, artist_created = Artist.objects.get_or_create(
|
||||||
|
id=artist_dict['id'],
|
||||||
|
name=artist_dict['name'],)
|
||||||
|
# only add/tally up artist genres if new
|
||||||
|
if artist_created:
|
||||||
|
artist_genre_queue.append(artist_obj)
|
||||||
|
if len(artist_genre_queue) == ARTIST_LIMIT:
|
||||||
|
add_artist_genres(user_headers, artist_genre_queue)
|
||||||
|
artist_genre_queue = []
|
||||||
|
track_artists.append(artist_obj)
|
||||||
|
|
||||||
|
# }}} add artists #
|
||||||
|
|
||||||
|
# don't associate history track with User, not necessarily in their
|
||||||
|
# library
|
||||||
|
track_obj, track_created = save_track_obj(track_dict['track'],
|
||||||
|
track_artists, None)
|
||||||
|
|
||||||
|
if console_logging:
|
||||||
|
tracks_processed += 1
|
||||||
|
print("Added track #{}: {} - {}".format(
|
||||||
|
tracks_processed,
|
||||||
|
track_obj.artists.first(),
|
||||||
|
track_obj.name,
|
||||||
|
))
|
||||||
|
|
||||||
|
if len(artist_genre_queue) > 0:
|
||||||
|
add_artist_genres(user_headers, artist_genre_queue)
|
||||||
|
|
||||||
|
# TODO: update track genres from History relation
|
||||||
|
# update_track_genres(user_obj)
|
||||||
|
|
||||||
|
return render(request, 'graphs/logged_in.html', get_user_context(user_obj))
|
||||||
|
|
||||||
|
# }}} get_history #
|
||||||
|
|
||||||
# get_artist_data {{{ #
|
# get_artist_data {{{ #
|
||||||
|
|
||||||
def get_artist_data(request, user_secret):
|
def get_artist_data(request, user_secret):
|
||||||
|
|||||||
@@ -21,10 +21,18 @@
|
|||||||
<body>
|
<body>
|
||||||
<script src="https://d3js.org/d3.v5.min.js"></script>
|
<script src="https://d3js.org/d3.v5.min.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/randomcolor/0.5.2/randomColor.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/randomcolor/0.5.2/randomColor.min.js"></script>
|
||||||
|
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<script src="{% static "graphs/scripts/genre_graph.js" %}"></script>
|
<script src="{% static "graphs/scripts/genre_graph.js" %}"></script>
|
||||||
|
|
||||||
<svg width="1920" height="740"></svg>
|
<!-- <div class="row">
|
||||||
|
<div class="col-" id="genre-graph"></div>
|
||||||
|
</div> -->
|
||||||
|
|
||||||
|
<svg id="genre-graph" width="600" height="400"
|
||||||
|
viewBox="0 0 600 400"
|
||||||
|
perserveAspectRatio="xMinYMid">
|
||||||
|
</svg>
|
||||||
<script>
|
<script>
|
||||||
var svg = d3.select("svg"),
|
var svg = d3.select("svg"),
|
||||||
margin = {top: 20, right: 20, bottom: 30, left: 40},
|
margin = {top: 20, right: 20, bottom: 30, left: 40},
|
||||||
@@ -38,7 +46,16 @@
|
|||||||
var y = d3.scaleLinear()
|
var y = d3.scaleLinear()
|
||||||
.rangeRound([height, 0]);
|
.rangeRound([height, 0]);
|
||||||
|
|
||||||
d3.json("{% url "api:get_genre_data" user_secret %}").then(create_genre_graph);
|
// PU: trying to store genreData locally
|
||||||
|
var genreData;
|
||||||
|
d3.json("{% url "api:get_genre_data" user_secret %}",
|
||||||
|
function(data) {
|
||||||
|
genreData = data;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
console.log(genreData);
|
||||||
|
create_genre_graph(genreData);
|
||||||
|
// d3.json("{% url "api:get_genre_data" user_secret %}").then(create_genre_graph);
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -18,8 +18,11 @@
|
|||||||
<p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="#">upgrade your browser</a> to improve your experience.</p>
|
<p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="#">upgrade your browser</a> to improve your experience.</p>
|
||||||
<![endif]-->
|
<![endif]-->
|
||||||
<h1>Logged in as {{ user_id }}</h1>
|
<h1>Logged in as {{ user_id }}</h1>
|
||||||
<a href="{% url "api:scan" user_secret %}" class="btn btn-primary">
|
<a href="{% url "api:scan_library" user_secret %}" class="btn btn-primary">
|
||||||
Scan Library
|
Scan Library
|
||||||
</a>
|
</a>
|
||||||
|
<a href="{% url "api:scan_history" user_secret %}" class="btn btn-primary">
|
||||||
|
Scan History
|
||||||
|
</a>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ from .utils import *
|
|||||||
|
|
||||||
TIME_FORMAT = '%Y-%m-%d-%H-%M-%S'
|
TIME_FORMAT = '%Y-%m-%d-%H-%M-%S'
|
||||||
TRACKS_TO_QUERY = 200
|
TRACKS_TO_QUERY = 200
|
||||||
|
AUTH_SCOPE = ['user-library-read', 'user-read-recently-played',]
|
||||||
|
|
||||||
# generate_random_string {{{ #
|
# generate_random_string {{{ #
|
||||||
|
|
||||||
@@ -62,7 +63,7 @@ def spotify_login(request):
|
|||||||
'response_type': 'code',
|
'response_type': 'code',
|
||||||
'redirect_uri': 'http://localhost:8000/login/callback',
|
'redirect_uri': 'http://localhost:8000/login/callback',
|
||||||
'state': state_str,
|
'state': state_str,
|
||||||
'scope': 'user-library-read',
|
'scope': " ".join(AUTH_SCOPE),
|
||||||
'show_dialog': False
|
'show_dialog': False
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user