Browse Source

Parse listening history and add Track objects

master
Kevin Mok 6 years ago
parent
commit
2644a3aeb2
  1. 2
      api/models.py
  2. 6
      api/urls.py
  3. 27
      api/utils.py
  4. 68
      api/views.py
  5. 21
      graphs/templates/graphs/genre_graph.html
  6. 5
      login/templates/login/scan.html
  7. 3
      login/views.py

2
api/models.py

@ -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)

6
api/urls.py

@ -4,8 +4,10 @@ from .views import *
app_name = 'api' app_name = 'api'
urlpatterns = [ 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, 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,

27
api/utils.py

@ -14,8 +14,8 @@ from login.models import User
# }}} imports # # }}} imports #
console_logging = True
# console_logging = False
# console_logging = True
console_logging = False
artists_genre_processed = 0 artists_genre_processed = 0
features_processed = 0 features_processed = 0
@ -74,6 +74,9 @@ 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:
# 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( new_track = Track.objects.create(
id=track_dict['id'], id=track_dict['id'],
year=track_dict['album']['release_date'].split('-')[0], year=track_dict['album']['release_date'].split('-')[0],
@ -81,11 +84,21 @@ def save_track_obj(track_dict, artists, user_obj):
runtime=int(float(track_dict['duration_ms']) / 1000), runtime=int(float(track_dict['duration_ms']) / 1000),
name=track_dict['name'], 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)
# print(new_track.name, artist.name)
if user_obj != None:
new_track.users.add(user_obj) 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

@ -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
graphs/templates/graphs/genre_graph.html

@ -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>

5
login/templates/login/scan.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>

3
login/views.py

@ -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
} }

Loading…
Cancel
Save