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
| Date |
+ With |
+ Rated |
{% for scrobble in scrobbles.all %}
| {{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 %} |
{% endfor %}