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)
|
||||
# artist = models.ForeignKey(Artist, on_delete=models.CASCADE)
|
||||
artists = models.ManyToManyField(Artist, blank=True)
|
||||
year = models.PositiveSmallIntegerField()
|
||||
year = models.PositiveSmallIntegerField(null=True)
|
||||
popularity = models.PositiveSmallIntegerField()
|
||||
runtime = models.PositiveSmallIntegerField()
|
||||
name = models.CharField(max_length=200)
|
||||
|
||||
@@ -4,8 +4,10 @@ from .views import *
|
||||
|
||||
app_name = 'api'
|
||||
urlpatterns = [
|
||||
path('scan/<str:user_secret>', parse_library,
|
||||
name='scan'),
|
||||
path('scan/library/<str:user_secret>', parse_library,
|
||||
name='scan_library'),
|
||||
path('scan/history/<str:user_secret>', parse_history,
|
||||
name='scan_history'),
|
||||
path('user_artists/<str:user_secret>', get_artist_data,
|
||||
name='get_artist_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 #
|
||||
|
||||
console_logging = True
|
||||
# console_logging = False
|
||||
# console_logging = True
|
||||
console_logging = False
|
||||
artists_genre_processed = 0
|
||||
features_processed = 0
|
||||
|
||||
@@ -74,19 +74,32 @@ def save_track_obj(track_dict, artists, user_obj):
|
||||
if len(track_query) != 0:
|
||||
return track_query[0], False
|
||||
else:
|
||||
new_track = Track.objects.create(
|
||||
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'],
|
||||
)
|
||||
# check if track is simple or full, simple Track object won't have year
|
||||
# if 'album' in track_dict:
|
||||
try:
|
||||
new_track = Track.objects.create(
|
||||
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'],
|
||||
)
|
||||
# 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 ID before filling in m2m field
|
||||
for artist in artists:
|
||||
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()
|
||||
return new_track, True
|
||||
|
||||
@@ -178,6 +191,7 @@ def add_artist_genres(headers, artist_objs):
|
||||
else:
|
||||
for genre in artists_response[i]['genres']:
|
||||
process_artist_genre(genre, artist_objs[i])
|
||||
# print(artist_objs[i].name, genre)
|
||||
|
||||
if console_logging:
|
||||
global artists_genre_processed
|
||||
@@ -221,6 +235,15 @@ def get_artists_in_genre(user, genre, max_songs):
|
||||
|
||||
# }}} 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):
|
||||
"""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 #
|
||||
|
||||
USER_TRACKS_LIMIT = 50
|
||||
HISTORY_LIMIT = 50
|
||||
ARTIST_LIMIT = 50
|
||||
FEATURES_LIMIT = 100
|
||||
# ARTIST_LIMIT = 25
|
||||
# FEATURES_LIMIT = 25
|
||||
TRACKS_TO_QUERY = 100
|
||||
HISTORY_ENDPOINT = 'https://api.spotify.com/v1/me/player/recently-played'
|
||||
|
||||
console_logging = True
|
||||
# console_logging = False
|
||||
|
||||
# parse_library {{{ #
|
||||
|
||||
@@ -119,6 +122,71 @@ def parse_library(request, user_secret):
|
||||
|
||||
# }}} 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 {{{ #
|
||||
|
||||
def get_artist_data(request, user_secret):
|
||||
|
||||
@@ -21,10 +21,18 @@
|
||||
<body>
|
||||
<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>
|
||||
|
||||
{% load static %}
|
||||
<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>
|
||||
var svg = d3.select("svg"),
|
||||
margin = {top: 20, right: 20, bottom: 30, left: 40},
|
||||
@@ -38,7 +46,16 @@
|
||||
var y = d3.scaleLinear()
|
||||
.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>
|
||||
</body>
|
||||
</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>
|
||||
<![endif]-->
|
||||
<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
|
||||
</a>
|
||||
<a href="{% url "api:scan_history" user_secret %}" class="btn btn-primary">
|
||||
Scan History
|
||||
</a>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -19,6 +19,7 @@ from .utils import *
|
||||
|
||||
TIME_FORMAT = '%Y-%m-%d-%H-%M-%S'
|
||||
TRACKS_TO_QUERY = 200
|
||||
AUTH_SCOPE = ['user-library-read', 'user-read-recently-played',]
|
||||
|
||||
# generate_random_string {{{ #
|
||||
|
||||
@@ -62,7 +63,7 @@ def spotify_login(request):
|
||||
'response_type': 'code',
|
||||
'redirect_uri': 'http://localhost:8000/login/callback',
|
||||
'state': state_str,
|
||||
'scope': 'user-library-read',
|
||||
'scope': " ".join(AUTH_SCOPE),
|
||||
'show_dialog': False
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user