[charts] Clean up how they're displayed
All checks were successful
build & deploy / test (push) Successful in 1m51s
build & deploy / build-and-deploy (push) Successful in 30s

This commit is contained in:
2026-05-15 16:03:53 -04:00
parent 2b04a17d77
commit bf7e5677e6
8 changed files with 217 additions and 22 deletions

View File

@ -110,14 +110,13 @@ class ChartRecord(TimeStampedModel):
@property
def period_str(self) -> str:
period = str(self.year)
if self.month:
period = f"{calendar.month_name[self.month]} {period}"
if self.week:
period = f"Week {self.week}, {period}"
if self.day:
period = f"{calendar.month_name[self.month]} {self.day}, {period}"
return period
return f"{calendar.month_name[self.month]} {self.day}, {self.year}"
if self.week:
return f"Week {self.week}, {self.year}"
if self.month:
return f"{calendar.month_name[self.month]} {self.year}"
return str(self.year)
@property
def period_type(self) -> str:

View File

@ -1,4 +1,4 @@
from charts.views import BirdsChartView, ChartRecordView, SpotifyTracksView
from charts.views import BirdsChartView, ChartDetailView, ChartRecordView, SpotifyTracksView
from django.urls import path
app_name = "charts"
@ -7,4 +7,5 @@ urlpatterns = [
path("charts/", ChartRecordView.as_view(), name="charts-home"),
path("charts/spotify/", SpotifyTracksView.as_view(), name="spotify-tracks"),
path("charts/birds/", BirdsChartView.as_view(), name="birds-chart"),
path("charts/<slug:media_type>/", ChartDetailView.as_view(), name="chart-detail"),
]

View File

@ -3,6 +3,7 @@ from datetime import timedelta
from charts.models import ChartRecord
from django.db.models import Count, Q
from django.http import Http404
from django.utils import timezone
from django.views.generic import TemplateView
from profiles.utils import now_user_timezone
@ -617,6 +618,103 @@ class ChartRecordView(TemplateView):
}
MEDIA_TYPE_LABELS = {
"artist": ("🎤", "Top Artists"),
"album": ("💿", "Top Albums"),
"track": ("🎵", "Top Tracks"),
"tv_series": ("📺", "Top TV Series"),
"video": ("🎬", "Top Videos"),
"podcast": ("🎙️", "Top Podcasts"),
"podcast_episode": ("🎙️", "Top Podcast Episodes"),
"board_game": ("🎲", "Top Board Games"),
"book": ("📚", "Top Books"),
"food": ("🍽️", "Top Foods"),
"trail": ("🥾", "Top Trails"),
"geo_location": ("📍", "Top Locations"),
}
class ChartDetailView(TemplateView):
template_name = "charts/chart_detail.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
user = self.request.user
media_type = kwargs.get("media_type")
if media_type not in MEDIA_TYPE_FILTERS:
raise Http404
date_param = self.request.GET.get("date")
now = timezone.now()
if user.is_authenticated:
now = now_user_timezone(user.profile)
today = now.date()
year = today.year
month = None
week = None
day = None
if date_param:
parts = date_param.split("-")
year = int(parts[0])
if len(parts) >= 2 and parts[1].startswith("W"):
week = int(parts[1].lstrip("W"))
month = None
elif len(parts) >= 2:
try:
month = int(parts[1])
except ValueError:
pass
if len(parts) >= 3:
if parts[2].startswith("W"):
week = int(parts[2].lstrip("W"))
else:
day = int(parts[2])
params = {"user": user}
if year:
params["year"] = year
if month:
params["month"] = month
if week:
params["week"] = week
if day:
params["day"] = day
media_filter = MEDIA_TYPE_FILTERS.get(media_type, Q())
qs = ChartRecord.objects.filter(media_filter, **params)
if day is not None:
qs = qs.filter(day__isnull=False)
elif week is not None:
qs = qs.filter(week__isnull=False, day__isnull=True)
elif month is not None:
qs = qs.filter(month__isnull=False, week__isnull=True, day__isnull=True)
else:
qs = qs.filter(month__isnull=True, week__isnull=True, day__isnull=True)
context["page_charts"] = qs.order_by("rank")
context["media_type"] = media_type
emoji, label = MEDIA_TYPE_LABELS.get(media_type, ("", media_type))
context["media_label"] = label
context["media_emoji"] = emoji
context["year"] = year
context["month"] = month
context["week"] = week
context["day"] = day
context["chart_years"] = list(
ChartRecord.objects.filter(Q(user=user) & media_filter)
.values_list("year", flat=True)
.distinct()
.order_by("-year")
)
return context
class SpotifyTracksView(TemplateView):
template_name = "charts/spotify_tracks.html"

View File

@ -14,13 +14,6 @@ class TrackListView(ScrobbleableListView):
class TrackDetailView(ScrobbleableDetailView):
model = Track
def get_context_data(self, **kwargs):
context_data = super().get_context_data(**kwargs)
context_data["charts"] = ChartRecord.objects.filter(
track=self.object, rank__in=[1, 2, 3]
)
return context_data
class ArtistListView(generic.ListView):
model = Artist
@ -57,9 +50,16 @@ class ArtistDetailView(generic.DetailView):
]
context_data["tracks_ranked"] = tracks_ranked
context_data["charts"] = ChartRecord.objects.filter(
charts_qs = ChartRecord.objects.filter(
artist=self.object, rank__in=[1, 2, 3]
)
).exclude(day__isnull=False)
from collections import OrderedDict
grouped = OrderedDict()
for chart in charts_qs:
grouped.setdefault(chart.period_type, []).append(chart)
context_data["charts"] = grouped
from scrobbles.models import Scrobble

View File

@ -135,9 +135,9 @@ class ChartContextMixin:
return context
media_type_map = {
"music.Artist": "artist",
"music.Album": "album",
"music.Track": "track",
"music.artist": "artist",
"music.album": "album",
"music.track": "track",
"videos.video": "video",
"videos.series": "tv_series",
"podcasts.podcast": "podcast",
@ -151,10 +151,17 @@ class ChartContextMixin:
media_type = media_type_map.get(model_label)
if media_type:
context["charts"] = ChartRecord.objects.filter(
charts_qs = ChartRecord.objects.filter(
**{media_type: obj}, rank__in=[1, 2, 3]
).exclude(day__isnull=False)
from collections import OrderedDict
grouped = OrderedDict()
for chart in charts_qs:
grouped.setdefault(chart.period_type, []).append(chart)
context["charts"] = grouped
return context

View File

@ -0,0 +1,45 @@
{% extends "base_list.html" %}
{% block title %}{{ media_label }}{% if year %} - {{ year }}{% endif %}{% endblock %}
{% block head_extra %}
<style>
.container { margin-bottom: 100px; }
</style>
{% endblock %}
{% block lists %}
<div class="row">
<div class="col-12">
<h2>{{ media_emoji }} {{ media_label }}</h2>
</div>
</div>
<div class="row mb-3">
<div class="col-12">
<div class="btn-group" role="group">
<a href="{% url 'charts:chart-detail' media_type %}" class="btn btn-outline-secondary{% if not year or year == today.year and not month and not week and not day %} active{% endif %}">All</a>
{% for cy in chart_years %}
<a href="{% url 'charts:chart-detail' media_type %}?date={{ cy }}" class="btn btn-outline-secondary{% if year == cy and not month and not week %} active{% endif %}">{{ cy }}</a>
{% endfor %}
</div>
<a href="{% url 'charts:charts-home' %}" class="btn btn-outline-primary btn-sm ms-2">&laquo; All charts</a>
</div>
</div>
<div class="row">
<div class="col-md-8 col-lg-6">
<ul class="list-group">
{% for chart in page_charts %}
<li class="list-group-item d-flex justify-content-between align-items-center">
<span class="me-2"><strong>#{{ chart.rank }}</strong></span>
<a href="{{ chart.media_obj.get_absolute_url }}">{{ chart.media_obj }}</a>
<span class="badge bg-primary rounded-pill">{{ chart.count }}</span>
</li>
{% empty %}
<li class="list-group-item">No chart data for this period.</li>
{% endfor %}
</ul>
</div>
</div>
{% endblock %}

View File

@ -62,6 +62,9 @@
<span class="badge bg-primary rounded-pill">{{chart.count}}</span>
</li>
{% endfor %}
<li class="list-group-item">
<a href="{% url 'charts:chart-detail' 'artist' %}">View all &raquo;</a>
</li>
</ul>
</div>
{% endif %}
@ -77,6 +80,9 @@
<span class="badge bg-primary rounded-pill">{{chart.count}}</span>
</li>
{% endfor %}
<li class="list-group-item">
<a href="{% url 'charts:chart-detail' 'album' %}">View all &raquo;</a>
</li>
</ul>
</div>
{% endif %}
@ -92,6 +98,9 @@
<span class="badge bg-primary rounded-pill">{{chart.count}}</span>
</li>
{% endfor %}
<li class="list-group-item">
<a href="{% url 'charts:chart-detail' 'track' %}">View all &raquo;</a>
</li>
</ul>
</div>
{% endif %}
@ -107,6 +116,9 @@
<span class="badge bg-primary rounded-pill">{{chart.count}}</span>
</li>
{% endfor %}
<li class="list-group-item">
<a href="{% url 'charts:chart-detail' 'tv_series' %}">View all &raquo;</a>
</li>
</ul>
</div>
{% endif %}
@ -122,6 +134,9 @@
<span class="badge bg-primary rounded-pill">{{chart.count}}</span>
</li>
{% endfor %}
<li class="list-group-item">
<a href="{% url 'charts:chart-detail' 'video' %}">View all &raquo;</a>
</li>
</ul>
</div>
{% endif %}
@ -137,6 +152,9 @@
<span class="badge bg-primary rounded-pill">{{chart.count}}</span>
</li>
{% endfor %}
<li class="list-group-item">
<a href="{% url 'charts:chart-detail' 'podcast' %}">View all &raquo;</a>
</li>
</ul>
</div>
{% endif %}
@ -152,6 +170,9 @@
<span class="badge bg-primary rounded-pill">{{chart.count}}</span>
</li>
{% endfor %}
<li class="list-group-item">
<a href="{% url 'charts:chart-detail' 'board_game' %}">View all &raquo;</a>
</li>
</ul>
</div>
{% endif %}
@ -167,6 +188,9 @@
<span class="badge bg-primary rounded-pill">{{chart.count}}</span>
</li>
{% endfor %}
<li class="list-group-item">
<a href="{% url 'charts:chart-detail' 'book' %}">View all &raquo;</a>
</li>
</ul>
</div>
{% endif %}
@ -182,6 +206,9 @@
<span class="badge bg-primary rounded-pill">{{chart.count}}</span>
</li>
{% endfor %}
<li class="list-group-item">
<a href="{% url 'charts:chart-detail' 'food' %}">View all &raquo;</a>
</li>
</ul>
</div>
{% endif %}
@ -197,6 +224,9 @@
<span class="badge bg-primary rounded-pill">{{chart.count}}</span>
</li>
{% endfor %}
<li class="list-group-item">
<a href="{% url 'charts:chart-detail' 'trail' %}">View all &raquo;</a>
</li>
</ul>
</div>
{% endif %}
@ -212,6 +242,9 @@
<span class="badge bg-primary rounded-pill">{{chart.count}}</span>
</li>
{% endfor %}
<li class="list-group-item">
<a href="{% url 'charts:chart-detail' 'geo_location' %}">View all &raquo;</a>
</li>
</ul>
</div>
{% endif %}

View File

@ -1,7 +1,19 @@
{% if charts %}
<div class="row">
<div class="col-md">
<p>{% for chart in charts %}<em><a href="{{chart.chart_url}}">{{chart.rank_emoji}} {{chart.period_str}}</a></em>{% if forloop.last %}{% else %} | {% endif %}{% endfor %}</p>
{% for period_type, chart_list in charts.items %}
<div class="mb-1">
<small class="text-muted">
{% if period_type == "year" %}📅 Yearly
{% elif period_type == "month" %}📆 Monthly
{% elif period_type == "week" %}🗓️ Weekly
{% endif %}
</small>
{% for chart in chart_list %}
<a href="{{chart.chart_url}}">{{chart.rank_emoji}} {{chart.period_str}}</a>{% if not forloop.last %} · {% endif %}
{% endfor %}
</div>
{% endfor %}
</div>
</div>
{% endif %}