From caf56289b4ce6daaf47770ac0f81f83904ee6347 Mon Sep 17 00:00:00 2001 From: Colin Powell Date: Mon, 13 Apr 2026 15:59:44 -0400 Subject: [PATCH] [charts] Add Spotify charts! --- vrobbler/apps/charts/urls.py | 3 +- vrobbler/apps/charts/views.py | 40 ++++++++++++++++++- vrobbler/templates/base_list.html | 1 + vrobbler/templates/charts/spotify_tracks.html | 34 ++++++++++++++++ 4 files changed, 76 insertions(+), 2 deletions(-) create mode 100644 vrobbler/templates/charts/spotify_tracks.html diff --git a/vrobbler/apps/charts/urls.py b/vrobbler/apps/charts/urls.py index eb8f21f..7dd919b 100644 --- a/vrobbler/apps/charts/urls.py +++ b/vrobbler/apps/charts/urls.py @@ -1,8 +1,9 @@ -from charts.views import ChartRecordView +from charts.views import ChartRecordView, SpotifyTracksView from django.urls import path app_name = "charts" urlpatterns = [ path("charts/", ChartRecordView.as_view(), name="charts-home"), + path("charts/spotify/", SpotifyTracksView.as_view(), name="spotify-tracks"), ] diff --git a/vrobbler/apps/charts/views.py b/vrobbler/apps/charts/views.py index d2a50b6..f9948e2 100644 --- a/vrobbler/apps/charts/views.py +++ b/vrobbler/apps/charts/views.py @@ -2,10 +2,11 @@ import calendar from datetime import timedelta from charts.models import ChartRecord -from django.db.models import Q +from django.db.models import Count, Q from django.utils import timezone from django.views.generic import TemplateView from profiles.utils import now_user_timezone +from scrobbles.models import Scrobble MEDIA_TYPE_FILTERS = { "artist": Q(artist__isnull=False), @@ -556,3 +557,40 @@ class ChartRecordView(TemplateView): return f"/charts/?date={available_years[idx - 1]}" return f"/charts/?date={year + 1}" return None + + +class SpotifyTracksView(TemplateView): + template_name = "charts/spotify_tracks.html" + + def get_spotify_tracks(self, user, limit=50): + track_ids = ( + Scrobble.objects.filter( + user=user, + track__isnull=False, + ) + .filter(Q(source="Last.fm") | Q(log__mopidy_source="spotify")) + .values("track") + .annotate(count=Count("id")) + .order_by("-count")[:limit] + ) + from music.models import Track + + track_id_list = [item["track"] for item in track_ids] + tracks = Track.objects.filter(id__in=track_id_list) + track_map = {t.id: t for t in tracks} + return [ + { + "track": track_map[tid], + "count": next( + (item["count"] for item in track_ids if item["track"] == tid), 0 + ), + } + for tid in track_id_list + if tid in track_map + ] + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + user = self.request.user + context["spotify_tracks"] = self.get_spotify_tracks(user) + return context diff --git a/vrobbler/templates/base_list.html b/vrobbler/templates/base_list.html index a3af28d..3df1bb6 100644 --- a/vrobbler/templates/base_list.html +++ b/vrobbler/templates/base_list.html @@ -13,6 +13,7 @@
Charts + Spotify Tracks
{% endif %}
diff --git a/vrobbler/templates/charts/spotify_tracks.html b/vrobbler/templates/charts/spotify_tracks.html new file mode 100644 index 0000000..c4e6482 --- /dev/null +++ b/vrobbler/templates/charts/spotify_tracks.html @@ -0,0 +1,34 @@ +{% extends "base_list.html" %} + +{% block title %}Spotify Tracks{% endblock %} + +{% block head_extra %} + +{% endblock %} + +{% block lists %} +
+
+

🎵 Top Spotify Tracks

+

Shows tracks scrobbled from Spotify (either via Last.fm or directly from Spotify)

+
+
+ +
+
+
    + {% for item in spotify_tracks %} +
  • + #{{ forloop.counter }} + {{ item.track.title }} + {{ item.count }} +
  • + {% empty %} +
  • No Spotify tracks found.
  • + {% endfor %} +
+
+
+{% endblock %}