[charts] Big revamp of the charts app
All checks were successful
build & deploy / test (push) Successful in 1m53s
build & deploy / deploy (push) Successful in 24s

This commit is contained in:
2026-03-21 18:13:34 -04:00
parent 565adfe58e
commit d5d27256f8
31 changed files with 2006 additions and 1100 deletions

View File

@ -58,7 +58,6 @@ from scrobbles.export import export_scrobbles
from scrobbles.forms import ExportScrobbleForm, ScrobbleForm
from scrobbles.models import (
AudioScrobblerTSVImport,
ChartRecord,
KoReaderImport,
LastFmImport,
RetroarchImport,
@ -728,231 +727,6 @@ def export(request):
return response
class ChartRecordView(TemplateView):
template_name = "scrobbles/chart_index.html"
@staticmethod
def get_media_filter(media_type: str = "") -> Q:
filters = {
"Track": Q(track__isnull=False),
"Artist": Q(artist__isnull=False),
"Series": Q(series__isnull=False),
"Video": Q(video__isnull=False),
"": Q(),
}
return filters[media_type]
def get_chart_records(self, media_type: str = "", **kwargs):
media_filter = self.get_media_filter(media_type)
charts = ChartRecord.objects.filter(
media_filter, user=self.request.user, **kwargs
).order_by("rank")
if charts.count() == 0:
ChartRecord.build(user=self.request.user, model_str=media_type, **kwargs)
charts = ChartRecord.objects.filter(
media_filter, user=self.request.user, **kwargs
).order_by("rank")
return charts
def get_chart(
self, period: str = "all_time", limit=15, media: str = ""
) -> QuerySet:
now = timezone.now()
params = {}
params["media_type"] = media
if period == "today":
params["day"] = now.day
params["month"] = now.month
params["year"] = now.year
if period == "week":
params["week"] = now.ioscalendar()[1]
params["year"] = now.year
if period == "month":
params["month"] = now.month
params["year"] = now.year
if period == "year":
params["year"] = now.year
return self.get_chart_records(**params)[:limit]
def get_context_data(self, **kwargs):
context_data = super().get_context_data(**kwargs)
date = self.request.GET.get("date")
media_type = self.request.GET.get("media", "Track")
user = self.request.user
params = {}
context_data["chart_type"] = self.request.GET.get("chart_type", "maloja")
context_data["artist_charts"] = {}
if not date:
limit = 20
artist = {"user": user, "media_type": "Artist", "limit": limit}
# This is weird. They don't display properly as QuerySets, so we cast to lists
context_data["chart_keys"] = {
"today": "Today",
"last7": "Last 7 days",
"last30": "Last 30 days",
"year": "This year",
"all": "All time",
}
context_data["current_artist_charts"] = batch_live_charts(
user=user, media_type="Artist", limit=limit
)
track = {"user": user, "media_type": "Track", "limit": limit}
context_data["current_track_charts"] = batch_live_charts(
user=user, media_type="Track", limit=limit
)
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 - fetch all and filter in Python
series_all = list(
Scrobble.objects.with_related()
.filter(
user=user,
video__video_type=Video.VideoType.TV_EPISODE,
played_to_completion=True,
timestamp__gte=start_day_of_month,
)
.order_by("-timestamp")
)
context_data["series_this_week"] = [
s for s in series_all if s.timestamp >= start_day_of_week
]
context_data["series_this_month"] = series_all
# Movie Scrobbles - fetch all and filter in Python
movies_all = list(
Scrobble.objects.with_related()
.filter(
user=user,
video__video_type=Video.VideoType.MOVIE,
played_to_completion=True,
timestamp__gte=start_day_of_month,
)
.order_by("-timestamp")
)
context_data["videos_this_week"] = [
v for v in movies_all if v.timestamp >= start_day_of_week
]
context_data["videos_this_month"] = movies_all
# YouTube Scrobbles - fetch all and filter in Python
youtube_all = list(
Scrobble.objects.with_related()
.filter(
user=user,
video__video_type=Video.VideoType.YOUTUBE,
played_to_completion=True,
timestamp__gte=start_day_of_month,
)
.order_by("-timestamp")
)
context_data["youtube_this_week"] = [
y for y in youtube_all if y.timestamp >= start_day_of_week
]
context_data["youtube_this_month"] = youtube_all
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")),
}
context_data["youtube_channel_charts"] = {
"today": list(
live_youtube_channel_charts(user=user, chart_period="today")
),
"last7": list(
live_youtube_channel_charts(user=user, chart_period="last7")
),
"last30": list(
live_youtube_channel_charts(user=user, chart_period="last30")
),
"year": list(
live_youtube_channel_charts(user=user, chart_period="year")
),
"all": list(live_youtube_channel_charts(user=user, chart_period="all")),
}
return context_data
# Date provided, lookup past charts, returning nothing if it's now or in the future.
now = timezone.now()
year = now.year
params = {"year": year}
name = f"Chart for {year}"
date_params = date.split("-")
year = int(date_params[0])
in_progress = False
if len(date_params) == 2:
if "W" in date_params[1]:
week = int(date_params[1].strip('W"'))
params["week"] = week
start = datetime.strptime(date + "-1", "%Y-W%W-%w").replace(
tzinfo=pytz.utc
)
end = start + timedelta(days=6)
in_progress = start <= now <= end
as_str = start.strftime("Week of %B %d, %Y")
name = f"Chart for {as_str}"
else:
month = int(date_params[1])
params["month"] = month
month_str = calendar.month_name[month]
name = f"Chart for {month_str} {year}"
in_progress = now.month == month and now.year == year
if len(date_params) == 3:
month = int(date_params[1])
day = int(date_params[2])
params["month"] = month
params["day"] = day
month_str = calendar.month_name[month]
name = f"Chart for {month_str} {day}, {year}"
in_progress = now.month == month and now.year == year and now.day == day
media_filter = self.get_media_filter("Track")
track_charts = ChartRecord.objects.filter(
media_filter, user=self.request.user, **params
).order_by("rank")
media_filter = self.get_media_filter("Artist")
artist_charts = ChartRecord.objects.filter(
media_filter, user=self.request.user, **params
).order_by("rank")
if track_charts.count() == 0 and not in_progress:
ChartRecord.build(user=self.request.user, model_str="Track", **params)
media_filter = self.get_media_filter("Track")
track_charts = ChartRecord.objects.filter(
media_filter, user=self.request.user, **params
).order_by("rank")
if artist_charts.count() == 0 and not in_progress:
ChartRecord.build(user=self.request.user, model_str="Artist", **params)
media_filter = self.get_media_filter("Artist")
artist_charts = ChartRecord.objects.filter(
media_filter, user=self.request.user, **params
).order_by("rank")
context_data["media_type"] = media_type
context_data["track_charts"] = track_charts
context_data["artist_charts"] = artist_charts
context_data["name"] = " ".join(["Top", media_type, "for", name])
context_data["in_progress"] = in_progress
return context_data
class ScrobbleStatusView(LoginRequiredMixin, TemplateView):
model = Scrobble
template_name = "scrobbles/status.html"