diff --git a/vrobbler/apps/charts/models.py b/vrobbler/apps/charts/models.py index 6f673af..e1ed073 100644 --- a/vrobbler/apps/charts/models.py +++ b/vrobbler/apps/charts/models.py @@ -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: diff --git a/vrobbler/apps/charts/urls.py b/vrobbler/apps/charts/urls.py index 15eafb2..be147a4 100644 --- a/vrobbler/apps/charts/urls.py +++ b/vrobbler/apps/charts/urls.py @@ -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//", ChartDetailView.as_view(), name="chart-detail"), ] diff --git a/vrobbler/apps/charts/views.py b/vrobbler/apps/charts/views.py index fab2392..f8b3410 100644 --- a/vrobbler/apps/charts/views.py +++ b/vrobbler/apps/charts/views.py @@ -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" diff --git a/vrobbler/apps/music/views.py b/vrobbler/apps/music/views.py index 6dfbbdb..68e4ca3 100644 --- a/vrobbler/apps/music/views.py +++ b/vrobbler/apps/music/views.py @@ -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 diff --git a/vrobbler/apps/scrobbles/views.py b/vrobbler/apps/scrobbles/views.py index 51cc638..10138b1 100644 --- a/vrobbler/apps/scrobbles/views.py +++ b/vrobbler/apps/scrobbles/views.py @@ -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 diff --git a/vrobbler/templates/charts/chart_detail.html b/vrobbler/templates/charts/chart_detail.html new file mode 100644 index 0000000..2e24777 --- /dev/null +++ b/vrobbler/templates/charts/chart_detail.html @@ -0,0 +1,45 @@ +{% extends "base_list.html" %} + +{% block title %}{{ media_label }}{% if year %} - {{ year }}{% endif %}{% endblock %} + +{% block head_extra %} + +{% endblock %} + +{% block lists %} +
+
+

{{ media_emoji }} {{ media_label }}

+
+
+ +
+
+
+ All + {% for cy in chart_years %} + {{ cy }} + {% endfor %} +
+ « All charts +
+
+ +
+
+
    + {% for chart in page_charts %} +
  • + #{{ chart.rank }} + {{ chart.media_obj }} + {{ chart.count }} +
  • + {% empty %} +
  • No chart data for this period.
  • + {% endfor %} +
+
+
+{% endblock %} diff --git a/vrobbler/templates/charts/chart_index.html b/vrobbler/templates/charts/chart_index.html index a451091..f0e5b9d 100644 --- a/vrobbler/templates/charts/chart_index.html +++ b/vrobbler/templates/charts/chart_index.html @@ -62,6 +62,9 @@ {{chart.count}} {% endfor %} +
  • + View all » +
  • {% endif %} @@ -77,6 +80,9 @@ {{chart.count}} {% endfor %} +
  • + View all » +
  • {% endif %} @@ -92,6 +98,9 @@ {{chart.count}} {% endfor %} +
  • + View all » +
  • {% endif %} @@ -107,6 +116,9 @@ {{chart.count}} {% endfor %} +
  • + View all » +
  • {% endif %} @@ -122,6 +134,9 @@ {{chart.count}} {% endfor %} +
  • + View all » +
  • {% endif %} @@ -137,6 +152,9 @@ {{chart.count}} {% endfor %} +
  • + View all » +
  • {% endif %} @@ -152,6 +170,9 @@ {{chart.count}} {% endfor %} +
  • + View all » +
  • {% endif %} @@ -167,6 +188,9 @@ {{chart.count}} {% endfor %} +
  • + View all » +
  • {% endif %} @@ -182,6 +206,9 @@ {{chart.count}} {% endfor %} +
  • + View all » +
  • {% endif %} @@ -197,6 +224,9 @@ {{chart.count}} {% endfor %} +
  • + View all » +
  • {% endif %} @@ -212,6 +242,9 @@ {{chart.count}} {% endfor %} +
  • + View all » +
  • {% endif %} diff --git a/vrobbler/templates/scrobbles/_chart_links.html b/vrobbler/templates/scrobbles/_chart_links.html index fc9c934..3b622ce 100644 --- a/vrobbler/templates/scrobbles/_chart_links.html +++ b/vrobbler/templates/scrobbles/_chart_links.html @@ -1,7 +1,19 @@ {% if charts %}
    -

    {% for chart in charts %}{{chart.rank_emoji}} {{chart.period_str}}{% if forloop.last %}{% else %} | {% endif %}{% endfor %}

    + {% for period_type, chart_list in charts.items %} +
    + + {% if period_type == "year" %}๐Ÿ“… Yearly + {% elif period_type == "month" %}๐Ÿ“† Monthly + {% elif period_type == "week" %}๐Ÿ—“๏ธ Weekly + {% endif %} + + {% for chart in chart_list %} + {{chart.rank_emoji}} {{chart.period_str}}{% if not forloop.last %} ยท {% endif %} + {% endfor %} +
    + {% endfor %}
    {% endif %}