From 83d89001a77b71feb077fc069caa73278eff57b1 Mon Sep 17 00:00:00 2001 From: Colin Powell Date: Sun, 22 Mar 2026 12:59:54 -0400 Subject: [PATCH] [charts] Fix chart building --- vrobbler/apps/boardgames/views.py | 8 +- vrobbler/apps/charts/admin.py | 6 - .../management/commands/build_charts.py | 64 ++++++++- vrobbler/apps/charts/models.py | 10 ++ vrobbler/apps/charts/utils.py | 121 ++++++++++++------ vrobbler/apps/foods/views.py | 8 +- vrobbler/apps/locations/views.py | 3 +- vrobbler/apps/music/views.py | 25 ++-- vrobbler/apps/podcasts/views.py | 8 +- vrobbler/apps/scrobbles/tasks.py | 22 +--- vrobbler/apps/scrobbles/views.py | 36 +++++- vrobbler/apps/videos/views.py | 8 +- .../boardgames/boardgame_detail.html | 7 + vrobbler/templates/books/book_detail.html | 7 + vrobbler/templates/foods/food_detail.html | 8 ++ vrobbler/templates/music/album_detail.html | 2 +- vrobbler/templates/music/artist_detail.html | 4 +- vrobbler/templates/music/track_detail.html | 2 +- .../templates/podcasts/podcast_detail.html | 7 + vrobbler/templates/trails/trail_detail.html | 7 + vrobbler/templates/videos/series_detail.html | 7 + vrobbler/templates/videos/video_detail.html | 7 + 22 files changed, 291 insertions(+), 86 deletions(-) diff --git a/vrobbler/apps/boardgames/views.py b/vrobbler/apps/boardgames/views.py index adff3ed..a1b5076 100644 --- a/vrobbler/apps/boardgames/views.py +++ b/vrobbler/apps/boardgames/views.py @@ -3,7 +3,11 @@ from django.utils import timezone from django.views import generic from boardgames.models import BoardGame, BoardGameDesigner, BoardGamePublisher from scrobbles.models import Scrobble -from scrobbles.views import ScrobbleableListView, ScrobbleableDetailView +from scrobbles.views import ( + ChartContextMixin, + ScrobbleableListView, + ScrobbleableDetailView, +) class BoardGameListView(ScrobbleableListView): @@ -64,7 +68,7 @@ class BoardGameListView(ScrobbleableListView): return context_data -class BoardGameDetailView(ScrobbleableDetailView): +class BoardGameDetailView(ScrobbleableDetailView, ChartContextMixin): model = BoardGame diff --git a/vrobbler/apps/charts/admin.py b/vrobbler/apps/charts/admin.py index 37f1055..8c8bca2 100644 --- a/vrobbler/apps/charts/admin.py +++ b/vrobbler/apps/charts/admin.py @@ -22,12 +22,6 @@ class ChartRecordAdmin(admin.ModelAdmin): "month", "week", "day", - "tv_series", - "podcast", - "board_game", - "book", - "food", - "trail", ) ordering = ("-created",) search_fields = ( diff --git a/vrobbler/apps/charts/management/commands/build_charts.py b/vrobbler/apps/charts/management/commands/build_charts.py index 4ff27fe..34fcc96 100644 --- a/vrobbler/apps/charts/management/commands/build_charts.py +++ b/vrobbler/apps/charts/management/commands/build_charts.py @@ -1,11 +1,58 @@ import logging -from charts.utils import build_all_historical_charts, rebuild_all_charts +from charts.utils import ( + build_all_historical_charts, + build_charts_since, + rebuild_all_charts, +) from django.core.management.base import BaseCommand logger = logging.getLogger(__name__) +def get_chart_counts(): + from charts.models import ChartRecord + + daily = ChartRecord.objects.filter(day__isnull=False).count() + weekly = ChartRecord.objects.filter(week__isnull=False, day__isnull=True).count() + monthly = ChartRecord.objects.filter( + month__isnull=False, week__isnull=True, day__isnull=True + ).count() + yearly = ChartRecord.objects.filter( + month__isnull=True, week__isnull=True, day__isnull=True + ).count() + total = ChartRecord.objects.count() + + return { + "daily": daily, + "weekly": weekly, + "monthly": monthly, + "yearly": yearly, + "total": total, + } + + +def print_chart_summary(before=None): + counts = get_chart_counts() + if before: + diff = {k: counts[k] - before[k] for k in counts} + print( + f" Daily: {counts['daily']} ({diff['daily']:+d}), " + f"Weekly: {counts['weekly']} ({diff['weekly']:+d}), " + f"Monthly: {counts['monthly']} ({diff['monthly']:+d}), " + f"Yearly: {counts['yearly']} ({diff['yearly']:+d}), " + f"Total: {counts['total']} ({diff['total']:+d})" + ) + else: + print( + f" Daily: {counts['daily']}, " + f"Weekly: {counts['weekly']}, " + f"Monthly: {counts['monthly']}, " + f"Yearly: {counts['yearly']}, " + f"Total: {counts['total']}" + ) + + class Command(BaseCommand): help = "Build missing charts for all users" @@ -38,22 +85,29 @@ class Command(BaseCommand): ) return users = [user] - action = "Rebuilding" if full_rebuild else "Building charts for" + action = "Full rebuild for" if full_rebuild else "Progressive build for" self.stdout.write(f"{action} user: {user}") else: users = User.objects.all() - action = "Rebuilding" if full_rebuild else "Building charts for" + action = "Full rebuild for" if full_rebuild else "Progressive build for" self.stdout.write(f"{action} charts for {users.count()} users...") + before_counts = get_chart_counts() + self.stdout.write("Chart counts before:") + print_chart_summary() + for user in users: try: - logger.info(f"Building all historical charts for {user}") + logger.info(f"Building charts for {user}") if full_rebuild: rebuild_all_charts(user) else: - build_all_historical_charts(user) + build_charts_since(user) self.stdout.write(self.style.SUCCESS(f" OK {user}")) except Exception as e: self.stderr.write(f" FAILED {user}: {e}") + self.stdout.write("Chart counts after (diff):") + print_chart_summary(before_counts) + self.stdout.write(self.style.SUCCESS("Done!")) diff --git a/vrobbler/apps/charts/models.py b/vrobbler/apps/charts/models.py index 2841e3d..acbdaaa 100644 --- a/vrobbler/apps/charts/models.py +++ b/vrobbler/apps/charts/models.py @@ -136,3 +136,13 @@ class ChartRecord(TimeStampedModel): def get_absolute_url(self): return reverse("charts:charts-home") + + @property + def chart_url(self) -> str: + url = reverse("charts:charts-home") + date_str = str(self.year) + if self.week: + date_str = f"{self.year}-W{self.week}" + elif self.month: + date_str = f"{self.year}-{self.month:02d}" + return f"{url}?date={date_str}" diff --git a/vrobbler/apps/charts/utils.py b/vrobbler/apps/charts/utils.py index 99856c2..25f3171 100644 --- a/vrobbler/apps/charts/utils.py +++ b/vrobbler/apps/charts/utils.py @@ -29,12 +29,8 @@ def get_time_filter( tz = pytz.timezone(settings.TIME_ZONE) if day and month: - start = timezone.make_aware( - datetime.datetime(year, month, day, 0, 0, 0) - ) - end = timezone.make_aware( - datetime.datetime(year, month, day, 23, 59, 59) - ) + start = timezone.make_aware(datetime.datetime(year, month, day, 0, 0, 0)) + end = timezone.make_aware(datetime.datetime(year, month, day, 23, 59, 59)) elif week: period = datetime.date.fromisocalendar(year, week, 1) start = timezone.make_aware( @@ -44,9 +40,7 @@ def get_time_filter( elif month: last_day = calendar.monthrange(year, month)[1] start = timezone.make_aware(datetime.datetime(year, month, 1)) - end = timezone.make_aware( - datetime.datetime(year, month, last_day, 23, 59, 59) - ) + end = timezone.make_aware(datetime.datetime(year, month, last_day, 23, 59, 59)) else: start = timezone.make_aware(datetime.datetime(year, 1, 1)) end = timezone.make_aware(datetime.datetime(year, 12, 31, 23, 59, 59)) @@ -77,18 +71,23 @@ def build_charts( "podcast_episode", "board_game", "trail", - "geo_location", "food", "book", ] period_filter = Q(year=year) - if month: - period_filter &= Q(month=month) - if week: - period_filter &= Q(week=week) if day: - period_filter &= Q(day=day) + period_filter = ( + period_filter & Q(day=day) & Q(week__isnull=True) & Q(month__isnull=True) + ) + elif week: + period_filter = ( + period_filter & Q(week=week) & Q(day__isnull=True) & Q(month__isnull=True) + ) + elif month: + period_filter = ( + period_filter & Q(month=month) & Q(week__isnull=True) & Q(day__isnull=True) + ) ChartRecord.objects.filter(period_filter, user=user).delete() @@ -115,8 +114,7 @@ def build_charts( "annotate": Count("id", distinct=True), }, "tv_series": { - "filter": Q(video__isnull=False) - & Q(video__tv_series__isnull=False), + "filter": Q(video__isnull=False) & Q(video__tv_series__isnull=False), "values": "video__tv_series", "annotate": Count("id", distinct=True), }, @@ -181,12 +179,8 @@ def build_charts( if not results: continue - unique_counts = sorted( - set(r["scrobble_count"] for r in results), reverse=True - ) - ranks = { - count: rank for rank, count in enumerate(unique_counts, start=1) - } + unique_counts = sorted(set(r["scrobble_count"] for r in results), reverse=True) + ranks = {count: rank for rank, count in enumerate(unique_counts, start=1)} chart_records = [] total_created = 0 @@ -204,16 +198,12 @@ def build_charts( chart_records.append(ChartRecord(**chart_record_data)) if len(chart_records) >= BATCH_SIZE: - ChartRecord.objects.bulk_create( - chart_records, batch_size=BATCH_SIZE - ) + ChartRecord.objects.bulk_create(chart_records, batch_size=BATCH_SIZE) total_created += len(chart_records) chart_records = [] if chart_records: - ChartRecord.objects.bulk_create( - chart_records, batch_size=BATCH_SIZE - ) + ChartRecord.objects.bulk_create(chart_records, batch_size=BATCH_SIZE) total_created += len(chart_records) logger.info( @@ -266,9 +256,7 @@ def build_monthly_charts( ) -def build_yearly_charts( - user, year: int, media_types: Optional[list] = None -) -> None: +def build_yearly_charts(user, year: int, media_types: Optional[list] = None) -> None: """Build yearly charts for a specific year.""" build_charts( user=user, @@ -277,9 +265,7 @@ def build_yearly_charts( ) -def build_all_historical_charts( - user, media_types: Optional[list] = None -) -> None: +def build_all_historical_charts(user, media_types: Optional[list] = None) -> None: """Build all historical charts for a user.""" Scrobble = apps.get_model("scrobbles", "Scrobble") @@ -325,12 +311,75 @@ def build_daily_charts( build_charts( user=user, year=year, - month=month, day=day, media_types=media_types, ) +def build_charts_since(user, media_types: Optional[list] = None) -> None: + """Build/update charts starting from the last known ChartRecord date.""" + ChartRecord = apps.get_model("charts", "ChartRecord") + Scrobble = apps.get_model("scrobbles", "Scrobble") + + latest_daily = ( + ChartRecord.objects.filter(user=user, day__isnull=False) + .order_by("-year", "-month", "-day") + .first() + ) + + if latest_daily: + start_date = datetime.date( + latest_daily.year, latest_daily.month, latest_daily.day + ) + logger.info( + f"Building charts since {start_date} for {user} (last known record)" + ) + else: + first_scrobble = ( + Scrobble.objects.filter(user=user, played_to_completion=True) + .order_by("timestamp") + .first() + ) + if not first_scrobble: + logger.info(f"No scrobbles found for {user}") + return + start_date = first_scrobble.timestamp.date() + logger.info( + f"No existing charts found for {user}, building from " + f"first scrobble: {start_date}" + ) + + start_date -= datetime.timedelta(days=1) + last_week = start_date.isocalendar()[1] + last_month = start_date.month + last_year = start_date.year + + now = timezone.now() + current = start_date + datetime.timedelta(days=1) + + while current <= now.date(): + year = current.year + month = current.month + day = current.day + week = current.isocalendar()[1] + + if last_month != month: + build_monthly_charts(user, year, month, media_types) + last_month = month + + build_daily_charts(user, year, month, day, media_types) + + if last_week != week: + build_weekly_charts(user, year, week, media_types) + last_week = week + + if last_year != year: + build_yearly_charts(user, year, media_types) + last_year = year + + current += datetime.timedelta(days=1) + + def rebuild_all_charts(user, media_types: Optional[list] = None) -> None: """Delete all existing chart records for a user and rebuild them.""" ChartRecord = apps.get_model("charts", "ChartRecord") diff --git a/vrobbler/apps/foods/views.py b/vrobbler/apps/foods/views.py index 1b32fa5..e45642f 100644 --- a/vrobbler/apps/foods/views.py +++ b/vrobbler/apps/foods/views.py @@ -1,11 +1,15 @@ from foods.models import Food -from scrobbles.views import ScrobbleableListView, ScrobbleableDetailView +from scrobbles.views import ( + ScrobbleableListView, + ScrobbleableDetailView, + ChartContextMixin, +) class FoodListView(ScrobbleableListView): model = Food -class FoodDetailView(ScrobbleableDetailView): +class FoodDetailView(ScrobbleableDetailView, ChartContextMixin): model = Food diff --git a/vrobbler/apps/locations/views.py b/vrobbler/apps/locations/views.py index cbdbb0b..b348cfb 100644 --- a/vrobbler/apps/locations/views.py +++ b/vrobbler/apps/locations/views.py @@ -2,6 +2,7 @@ from django.db.models import Count from django.views import generic from locations.models import GeoLocation from scrobbles.stats import get_scrobble_count_qs +from scrobbles.views import ChartContextMixin class GeoLocationListView(generic.ListView): @@ -22,6 +23,6 @@ class GeoLocationListView(generic.ListView): return context_data -class GeoLocationDetailView(generic.DetailView): +class GeoLocationDetailView(ChartContextMixin, generic.DetailView): model = GeoLocation slug_field = "uuid" diff --git a/vrobbler/apps/music/views.py b/vrobbler/apps/music/views.py index 1be2e1a..6dfbbdb 100644 --- a/vrobbler/apps/music/views.py +++ b/vrobbler/apps/music/views.py @@ -47,19 +47,28 @@ class ArtistDetailView(generic.DetailView): def get_context_data(self, **kwargs): context_data = super().get_context_data(**kwargs) artist = context_data["object"] - rank = 1 - tracks_ranked = [] - scrobbles = artist.tracks.first().scrobble_count - for track in artist.tracks: - if scrobbles > track.scrobble_count: - rank += 1 - tracks_ranked.append((rank, track)) - scrobbles = track.scrobble_count + + ranked_tracks = sorted( + artist.tracks.all(), key=lambda t: t.scrobble_count, reverse=True + )[:10] + + tracks_ranked = [ + (rank, track) for rank, track in enumerate(ranked_tracks, start=1) + ] context_data["tracks_ranked"] = tracks_ranked context_data["charts"] = ChartRecord.objects.filter( artist=self.object, rank__in=[1, 2, 3] ) + + from scrobbles.models import Scrobble + + context_data["recent_scrobbles"] = ( + Scrobble.objects.filter(track__artist=artist) + .select_related("track", "track__album") + .order_by("-timestamp")[:100] + ) + return context_data diff --git a/vrobbler/apps/podcasts/views.py b/vrobbler/apps/podcasts/views.py index eb954ab..9433780 100644 --- a/vrobbler/apps/podcasts/views.py +++ b/vrobbler/apps/podcasts/views.py @@ -4,7 +4,11 @@ from django.views import generic from podcasts.models import Podcast, PodcastEpisode from scrobbles.models import Scrobble -from scrobbles.views import ScrobbleableListView, ScrobbleableDetailView +from scrobbles.views import ( + ScrobbleableListView, + ScrobbleableDetailView, + ChartContextMixin, +) class PodcastListView(ScrobbleableListView): @@ -61,7 +65,7 @@ class PodcastListView(ScrobbleableListView): return context_data -class PodcastDetailView(generic.DetailView): +class PodcastDetailView(ChartContextMixin, generic.DetailView): model = Podcast slug_field = "uuid" diff --git a/vrobbler/apps/scrobbles/tasks.py b/vrobbler/apps/scrobbles/tasks.py index e9a762f..5d3139d 100644 --- a/vrobbler/apps/scrobbles/tasks.py +++ b/vrobbler/apps/scrobbles/tasks.py @@ -1,7 +1,7 @@ import logging from celery import shared_task -from charts.utils import build_all_historical_charts, build_yesterdays_charts +from charts.utils import build_charts_since from django.apps import apps from django.contrib.auth import get_user_model @@ -53,27 +53,17 @@ def process_koreader_import(import_id): @shared_task def create_yesterdays_charts(): + """Build/update charts for all users starting from last known record.""" for user in User.objects.all(): - build_yesterdays_charts(user) - - -@shared_task -def build_missing_charts_for_all_users(): - """Build missing charts for all users.""" - for user in User.objects.all(): - logger.info(f"Building all historical charts for {user}") - try: - build_all_historical_charts(user) - except Exception as e: - logger.error(f"Failed to build charts for {user}: {e}") + build_charts_since(user) @shared_task def build_charts_for_user(user_id): - """Build all historical charts for a specific user.""" + """Build charts for a specific user starting from last known record.""" user = User.objects.filter(id=user_id).first() if not user: logger.error(f"User with id {user_id} not found") return - logger.info(f"Building all historical charts for {user}") - build_all_historical_charts(user) + logger.info(f"Building charts for {user}") + build_charts_since(user) diff --git a/vrobbler/apps/scrobbles/views.py b/vrobbler/apps/scrobbles/views.py index ada99bb..255da76 100644 --- a/vrobbler/apps/scrobbles/views.py +++ b/vrobbler/apps/scrobbles/views.py @@ -105,7 +105,40 @@ class ScrobbleableListView(ListView): return queryset -class ScrobbleableDetailView(DetailView): +class ChartContextMixin: + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + from charts.models import ChartRecord + + obj = context.get("object") + if not obj: + return context + + media_type_map = { + "music.Artist": "artist", + "music.Album": "album", + "music.Track": "track", + "videos.video": "video", + "videos.series": "tv_series", + "podcasts.podcast": "podcast", + "boardgames.boardgame": "board_game", + "trails.trail": "trail", + "foods.food": "food", + "books.book": "book", + } + + model_label = f"{obj._meta.app_label}.{obj._meta.model_name}" + media_type = media_type_map.get(model_label) + + if media_type: + context["charts"] = ChartRecord.objects.filter( + **{media_type: obj}, rank__in=[1, 2, 3] + ) + + return context + + +class ScrobbleableDetailView(ChartContextMixin, DetailView): model = None slug_field = "uuid" @@ -734,7 +767,6 @@ class ScrobbleStatusView(LoginRequiredMixin, TemplateView): def get_context_data(self, **kwargs): data = super().get_context_data(**kwargs) user_scrobble_qs = Scrobble.objects.filter().order_by("-timestamp") - progress_plays = user_scrobble_qs.filter(in_progress=True, is_paused=False) data["listening"] = progress_plays.filter( Q(track__isnull=False) | Q(podcast_episode__isnull=False) diff --git a/vrobbler/apps/videos/views.py b/vrobbler/apps/videos/views.py index bba1505..dc86c20 100644 --- a/vrobbler/apps/videos/views.py +++ b/vrobbler/apps/videos/views.py @@ -4,7 +4,11 @@ from django.utils import timezone from django.views import generic from scrobbles.models import Scrobble from videos.models import Channel, Series, Video -from scrobbles.views import ScrobbleableListView, ScrobbleableDetailView +from scrobbles.views import ( + ScrobbleableListView, + ScrobbleableDetailView, + ChartContextMixin, +) class MovieListView(LoginRequiredMixin, generic.ListView): @@ -19,7 +23,7 @@ class SeriesListView(LoginRequiredMixin, generic.ListView): model = Series -class SeriesDetailView(LoginRequiredMixin, generic.DetailView): +class SeriesDetailView(LoginRequiredMixin, ChartContextMixin, generic.DetailView): model = Series slug_field = "uuid" diff --git a/vrobbler/templates/boardgames/boardgame_detail.html b/vrobbler/templates/boardgames/boardgame_detail.html index a768cc1..f7364e4 100644 --- a/vrobbler/templates/boardgames/boardgame_detail.html +++ b/vrobbler/templates/boardgames/boardgame_detail.html @@ -47,6 +47,13 @@ Play again

+{% if charts %} +
+
+

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

+
+
+{% endif %}

Last scrobbles

diff --git a/vrobbler/templates/books/book_detail.html b/vrobbler/templates/books/book_detail.html index 37a8531..f9c138d 100644 --- a/vrobbler/templates/books/book_detail.html +++ b/vrobbler/templates/books/book_detail.html @@ -41,6 +41,13 @@ {% endif %} {% endfor %}
+{% if charts %} +
+
+

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

+
+
+{% endif %}

Last scrobbles

diff --git a/vrobbler/templates/foods/food_detail.html b/vrobbler/templates/foods/food_detail.html index c2343be..03e1a1d 100644 --- a/vrobbler/templates/foods/food_detail.html +++ b/vrobbler/templates/foods/food_detail.html @@ -9,6 +9,14 @@

{{object.description}}

+{{charts}} +{% if charts %} +
+
+

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

+
+
+{% endif %}

Last scrobbles

diff --git a/vrobbler/templates/music/album_detail.html b/vrobbler/templates/music/album_detail.html index c88a24c..4cd1ec6 100644 --- a/vrobbler/templates/music/album_detail.html +++ b/vrobbler/templates/music/album_detail.html @@ -33,7 +33,7 @@

{{object.scrobbles.count}} scrobbles

{% if charts %} -

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

+

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

{% endif %}

Top tracks

diff --git a/vrobbler/templates/music/artist_detail.html b/vrobbler/templates/music/artist_detail.html index e4766b0..89bcd85 100644 --- a/vrobbler/templates/music/artist_detail.html +++ b/vrobbler/templates/music/artist_detail.html @@ -34,7 +34,7 @@

{{artist.scrobbles.count}} scrobbles

{% if charts %} -

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

+

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

{% endif %}

Top tracks

@@ -81,7 +81,7 @@ - {% for scrobble in object.scrobbles %} + {% for scrobble in recent_scrobbles %} {{scrobble.local_timestamp}} {{scrobble.track.title}} diff --git a/vrobbler/templates/music/track_detail.html b/vrobbler/templates/music/track_detail.html index 733ddb7..22edcbc 100644 --- a/vrobbler/templates/music/track_detail.html +++ b/vrobbler/templates/music/track_detail.html @@ -11,7 +11,7 @@

{{scrobbles.count}} scrobbles

{% if charts %} -

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

+

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

{% endif %}

Last scrobbles

diff --git a/vrobbler/templates/podcasts/podcast_detail.html b/vrobbler/templates/podcasts/podcast_detail.html index 954f2a3..8cb07d2 100644 --- a/vrobbler/templates/podcasts/podcast_detail.html +++ b/vrobbler/templates/podcasts/podcast_detail.html @@ -31,6 +31,13 @@
+{% if charts %} +
+
+

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

+
+
+{% endif %}

Last scrobbles

diff --git a/vrobbler/templates/trails/trail_detail.html b/vrobbler/templates/trails/trail_detail.html index f68ab6f..150d881 100644 --- a/vrobbler/templates/trails/trail_detail.html +++ b/vrobbler/templates/trails/trail_detail.html @@ -44,6 +44,13 @@ Play again

+{% if charts %} +
+
+

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

+
+
+{% endif %}

Last scrobbles

diff --git a/vrobbler/templates/videos/series_detail.html b/vrobbler/templates/videos/series_detail.html index c5ca460..dca82d7 100644 --- a/vrobbler/templates/videos/series_detail.html +++ b/vrobbler/templates/videos/series_detail.html @@ -44,6 +44,13 @@

+{% if charts %} +
+
+

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

+
+
+{% endif %}

Last scrobbles

diff --git a/vrobbler/templates/videos/video_detail.html b/vrobbler/templates/videos/video_detail.html index 5614838..51e9791 100644 --- a/vrobbler/templates/videos/video_detail.html +++ b/vrobbler/templates/videos/video_detail.html @@ -83,18 +83,25 @@ dd {
+ {% if charts %} +

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

+ {% endif %}

Last scrobbles

+ + {% for scrobble in scrobbles.all %} + + {% endfor %}
DateWithRated
{{scrobble.local_timestamp}}{% if scrobble.logdata.with_people %}{{scrobble.logdata.with_people|join:", " }}{% else %}Solo{% endif %}{% if scrobble.logdata.rating %}{{scrobble.logdata.rating }}{% else %}Unrated{% endif %}