From 0e5d8e6b2f03d6bf63711f29b60fa9a6fb0f0710 Mon Sep 17 00:00:00 2001 From: Colin Powell Date: Wed, 11 Mar 2026 23:53:08 -0400 Subject: [PATCH] [charts] Add TV charts --- vrobbler/apps/music/aggregators.py | 60 +++++- vrobbler/apps/scrobbles/views.py | 70 ++++++- vrobbler/templates/scrobbles/_top_charts.html | 184 ++++++++++++++++++ .../templates/scrobbles/scrobble_list.html | 1 + 4 files changed, 313 insertions(+), 2 deletions(-) diff --git a/vrobbler/apps/music/aggregators.py b/vrobbler/apps/music/aggregators.py index 248b012..1172a19 100644 --- a/vrobbler/apps/music/aggregators.py +++ b/vrobbler/apps/music/aggregators.py @@ -89,6 +89,7 @@ def live_charts( media_type: str = "Track", chart_period: str = "all", limit: int = 15, + app_label: str = "music", ) -> QuerySet: now = timezone.now() tzinfo = now.tzinfo @@ -106,7 +107,7 @@ def live_charts( start_day_of_month = now.replace(day=1) start_day_of_year = now.replace(month=1, day=1) - media_model = apps.get_model(app_label="music", model_name=media_type) + media_model = apps.get_model(app_label=app_label, model_name=media_type) period_queries = { "today": {"scrobble__timestamp__gte": start_of_today}, @@ -154,3 +155,60 @@ def live_charts( def artist_scrobble_count(artist_id: int, filter: str = "today") -> int: return Scrobble.objects.filter(track__artist=artist_id).count() + + +def live_tv_charts( + user: "User", + chart_period: str = "all", + limit: int = 15, +) -> QuerySet: + from django.db.models import OuterRef, Subquery + from videos.models import Video, Series + + now = timezone.now() + tzinfo = now.tzinfo + now = now.date() + if user.is_authenticated: + now = now_user_timezone(user.profile) + tzinfo = now.tzinfo + + seven_days_ago = now - timedelta(days=7) + thirty_days_ago = now - timedelta(days=30) + start_of_today = datetime.combine(now, datetime.min.time(), tzinfo) + start_day_of_week = start_of_today - timedelta(days=now.today().isoweekday() % 7) + start_day_of_month = now.replace(day=1) + start_day_of_year = now.replace(month=1, day=1) + + period_queries = { + "today": Q(timestamp__gte=start_of_today), + "week": Q(timestamp__gte=start_day_of_week), + "last7": Q(timestamp__gte=seven_days_ago), + "last30": Q(timestamp__gte=thirty_days_ago), + "month": Q(timestamp__gte=start_day_of_month), + "year": Q(timestamp__gte=start_day_of_year), + "all": Q(), + } + + time_filter = period_queries[chart_period] + completion_filter = Q( + user=user, + played_to_completion=True, + video__video_type=Video.VideoType.TV_EPISODE, + ) + + scrobble_counts = ( + Scrobble.objects.filter( + completion_filter, + time_filter, + video__tv_series=OuterRef("pk"), + ) + .values("video__tv_series") + .annotate(c=Count("id")) + .values("c") + ) + + return ( + Series.objects.annotate(num_scrobbles=Subquery(scrobble_counts)) + .filter(num_scrobbles__gt=0) + .order_by("-num_scrobbles")[:limit] + ) diff --git a/vrobbler/apps/scrobbles/views.py b/vrobbler/apps/scrobbles/views.py index 6563edf..ac86b53 100644 --- a/vrobbler/apps/scrobbles/views.py +++ b/vrobbler/apps/scrobbles/views.py @@ -21,7 +21,12 @@ from django.views.generic import DetailView, FormView, TemplateView from django.views.generic.edit import CreateView from django.views.generic.list import ListView from moods.models import Mood -from music.aggregators import live_charts, scrobble_counts, week_of_scrobbles +from music.aggregators import ( + live_charts, + live_tv_charts, + scrobble_counts, + week_of_scrobbles, +) from pendulum.parsing.exceptions import ParserError from rest_framework import status from rest_framework.decorators import ( @@ -59,6 +64,7 @@ from scrobbles.utils import ( get_long_plays_completed, get_long_plays_in_progress, ) +from profiles.utils import now_user_timezone logger = logging.getLogger(__name__) @@ -822,6 +828,68 @@ class ChartRecordView(TemplateView): "year": list(live_charts(**track, chart_period="year")), "all": list(live_charts(**track)), } + + now = timezone.now() + tzinfo = now.tzinfo + if user.is_authenticated: + now = now_user_timezone(user.profile) + tzinfo = now.tzinfo + now = now.date() + start_of_today = datetime.combine(now, datetime.min.time(), tzinfo) + start_day_of_week = start_of_today - timedelta( + days=now.isoweekday() % 7 + ) + start_day_of_month = now.replace(day=1) + + # TV Series Scrobbles + series_scrobbles = Scrobble.objects.filter( + user=user, + video__video_type=Video.VideoType.TV_EPISODE, + played_to_completion=True, + ) + context_data["series_this_week"] = series_scrobbles.filter( + timestamp__gte=start_day_of_week + ).order_by("-timestamp") + context_data["series_this_month"] = series_scrobbles.filter( + timestamp__gte=start_day_of_month + ).order_by("-timestamp") + + # Movie Scrobbles + movie_scrobbles = Scrobble.objects.filter( + user=user, + video__video_type=Video.VideoType.MOVIE, + played_to_completion=True, + ) + context_data["videos_this_week"] = movie_scrobbles.filter( + timestamp__gte=start_day_of_week + ).order_by("-timestamp") + context_data["videos_this_month"] = movie_scrobbles.filter( + timestamp__gte=start_day_of_month + ).order_by("-timestamp") + + # YouTube Scrobbles + youtube_scrobbles = Scrobble.objects.filter( + user=user, + video__video_type=Video.VideoType.YOUTUBE, + played_to_completion=True, + ) + context_data["youtube_this_week"] = youtube_scrobbles.filter( + timestamp__gte=start_day_of_week + ).order_by("-timestamp") + context_data["youtube_this_month"] = youtube_scrobbles.filter( + timestamp__gte=start_day_of_month + ).order_by("-timestamp") + + context_data["tv_show_charts"] = { + "today": list(live_tv_charts(user=user, chart_period="today")), + "last7": list(live_tv_charts(user=user, chart_period="last7")), + "last30": list( + live_tv_charts(user=user, chart_period="last30") + ), + "year": list(live_tv_charts(user=user, chart_period="year")), + "all": list(live_tv_charts(user=user, chart_period="all")), + } + return context_data # Date provided, lookup past charts, returning nothing if it's now or in the future. diff --git a/vrobbler/templates/scrobbles/_top_charts.html b/vrobbler/templates/scrobbles/_top_charts.html index ed99efe..67898f0 100644 --- a/vrobbler/templates/scrobbles/_top_charts.html +++ b/vrobbler/templates/scrobbles/_top_charts.html @@ -342,3 +342,187 @@ {% endfor %} + +
+

Top TV Shows

+ + +
+ {% for key, shows in tv_show_charts.items %} +
+
+ {% if shows.0 %} +
+
+
#1 {{shows.0.name}} ({{shows.0.num_scrobbles}} episodes)
+ {% if shows.0.cover_image %} + + {% else %} + + {% endif %} +
+
+ {% endif %} + {% if shows.1 %} +
+
+
+
#2 {{shows.1.name}} ({{shows.1.num_scrobbles}})
+ {% if shows.1.cover_image %} + + {% else %} + + {% endif %} +
+
+
#3 {{shows.2.name}} ({{shows.2.num_scrobbles}})
+ {% if shows.2.cover_image %} + + {% else %} + + {% endif %} +
+
+
+ {% endif %} +
+
+ {% endfor %} +
+
+ +
+
+

TV Shows Watched

+ + {% if series_this_week %} +

This Week

+
+ + + {% for scrobble in series_this_week %} + + + + + {% empty %} + + {% endfor %} + +
{{scrobble.video}}{{scrobble.timestamp|date:"N d"}}
No episodes watched this week
+
+ {% endif %} + + {% if series_this_month %} +

This Month

+
+ + + {% for scrobble in series_this_month %} + + + + + {% empty %} + + {% endfor %} + +
{{scrobble.video}}{{scrobble.timestamp|date:"N d"}}
No episodes watched this month
+
+ {% endif %} +
+ + {% if videos_this_week or videos_this_month %} +
+

Movies Watched

+ + {% if videos_this_week %} +

This Week

+
+ + + {% for scrobble in videos_this_week %} + + + + + {% empty %} + + {% endfor %} + +
{{scrobble.video}}{{scrobble.timestamp|date:"N d"}}
No movies watched this week
+
+ {% endif %} + + {% if videos_this_month %} +

This Month

+
+ + + {% for scrobble in videos_this_month %} + + + + + {% empty %} + + {% endfor %} + +
{{scrobble.video}}{{scrobble.timestamp|date:"N d"}}
No movies watched this month
+
+ {% endif %} + +
+ {% endif %} + + {% if youtue_this_week or youtube_this_month %} +
+

YouTube Watched

+ + {% if youtube_this_week %} +

This Week

+
+ + + {% for scrobble in youtube_this_week %} + + + + + {% empty %} + + {% endfor %} + +
{{scrobble.video}}{{scrobble.timestamp|date:"N d"}}
No youtube watched this week
+
+ {% endif %} + + {% if youtube_this_month %} +

This Month

+
+ + + {% for scrobble in youtube_this_month %} + + + + + {% empty %} + + {% endfor %} + +
{{scrobble.video}}{{scrobble.timestamp|date:"N d"}}
No youtube watched this month
+
+ {% endif %} + +
+ {% endif %} +
diff --git a/vrobbler/templates/scrobbles/scrobble_list.html b/vrobbler/templates/scrobbles/scrobble_list.html index 4065894..f733178 100644 --- a/vrobbler/templates/scrobbles/scrobble_list.html +++ b/vrobbler/templates/scrobbles/scrobble_list.html @@ -95,6 +95,7 @@ + Charts {% endif %}