Compare commits

..

10 Commits
18.3 ... 18.9

22 changed files with 152 additions and 66 deletions

View File

@ -79,20 +79,7 @@ fetching and simple saving.
:LOGBOOK: :LOGBOOK:
CLOCK: [2025-07-09 Wed 09:55]--[2025-07-09 Wed 10:15] => 0:20 CLOCK: [2025-07-09 Wed 09:55]--[2025-07-09 Wed 10:15] => 0:20
:END: :END:
* Backlog [4/27] * Backlog [1/22]
** STRT Only create a LastFM import if there are files to import :vrobbler:feature:lastfm:importers:project:personal:
:PROPERTIES:
:ID: 7a1456af-c5f1-6385-b34f-0be24a6b65b0
:END:
- Note taken on [2025-07-20 Sun 16:21]
This thing is kicking my butt. As it stands it works, but the scrobbles are not assigned to the tracks properly.
** TODO Add timezone awarness to IMAP importer :personal:project:vrobbler:feature:importer:imap:timezones:
** DONE Track timezone changes for profiles :vrobbler:feature:profiles:personal:project:
:PROPERTIES:
:ID: 89ec867f-29fd-82f1-be17-b49dddc30c78
:END:
[2025-07-11 14:23]
** TODO [#A] Add classmethod for metadata fetching to tracks :vrobbler:feature:music:personal:project: ** TODO [#A] Add classmethod for metadata fetching to tracks :vrobbler:feature:music:personal:project:
:PROPERTIES: :PROPERTIES:
:ID: bc4b45e5-4c65-13c5-ab7b-1937d3fbf5c2 :ID: bc4b45e5-4c65-13c5-ab7b-1937d3fbf5c2
@ -456,6 +443,34 @@ it's annoying.
** TODO [#C] Allow users to see tasks on calendar view :vrobbler:personal:project:templates:feature: ** TODO [#C] Allow users to see tasks on calendar view :vrobbler:personal:project:templates:feature:
https://codepen.io/oliviale/pen/QYqybo https://codepen.io/oliviale/pen/QYqybo
** TODO [#C] Come up with a possible flow using WebDAV and super-productivity for tasks :personal:feature:project:vrobbler:tasks: ** TODO [#C] Come up with a possible flow using WebDAV and super-productivity for tasks :personal:feature:project:vrobbler:tasks:
* Version 18.7
** DONE Use the timezone history log to fix old Scrobbles that fall into those timezone blocks :vrobbler:chore:scrobbles:project:personal:
:PROPERTIES:
:ID: 9d055ac1-584b-20c8-7ad9-9ce36b329dc7
:END:
* Version 18.4
** DONE Track timezone changes for profiles :vrobbler:feature:profiles:personal:project:
:PROPERTIES:
:ID: 89ec867f-29fd-82f1-be17-b49dddc30c78
:END:
[2025-07-11 14:23]
** DONE Only create a LastFM import if there are files to import :vrobbler:feature:lastfm:importers:project:personal:
:PROPERTIES:
:ID: 7a1456af-c5f1-6385-b34f-0be24a6b65b0
:END:
- Note taken on [2025-07-20 Sun 16:21]
This thing is kicking my butt. As it stands it works, but the scrobbles are not assigned to the tracks properly.
* Version 18.3
** DONE Add timezone awarness to IMAP importer :personal:project:vrobbler:feature:importer:imap:timezones:
:PROPERTIES:
:ID: 05837b7c-96aa-6190-3678-e2ae7c7cac75
:END:
* Version 18
** DONE Condense tracks of the same title by the same artist with multiple albums :vrobbler:feature:music:project:personal:
:PROPERTIES:
:ID: b39fcec8-59fd-eab0-5809-b8144c7d2708
:END:
** DONE Import from BG stats a "learning" log field when "Learning to play" is in the comment :vrobbler:feature:boardgames:project:personal: ** DONE Import from BG stats a "learning" log field when "Learning to play" is in the comment :vrobbler:feature:boardgames:project:personal:
:PROPERTIES: :PROPERTIES:
:ID: fda59fab-4349-e99e-54c6-9f1392a1c474 :ID: fda59fab-4349-e99e-54c6-9f1392a1c474

View File

@ -3,16 +3,14 @@ import re
import sqlite3 import sqlite3
from datetime import datetime, timedelta from datetime import datetime, timedelta
from enum import Enum from enum import Enum
import pendulum
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
import requests import requests
from books.constants import BOOKS_TITLES_TO_IGNORE from books.constants import BOOKS_TITLES_TO_IGNORE
from django.apps import apps from django.apps import apps
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from stream_sqlite import stream_sqlite
from scrobbles.notifications import NtfyNotification from scrobbles.notifications import NtfyNotification
from vrobbler.apps.profiles.utils import one_off_fix_colins_profile from stream_sqlite import stream_sqlite
from webdav.client import get_webdav_client from webdav.client import get_webdav_client
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -287,9 +285,6 @@ def build_scrobbles_from_book_map(
datetime.fromtimestamp(int(last_page.get("end_ts"))) datetime.fromtimestamp(int(last_page.get("end_ts")))
) )
if user.id == 1 and not user.profile.timezone_change_log:
one_off_fix_colins_profile(user.profile)
# Adjust for Daylight Saving Time # Adjust for Daylight Saving Time
if timestamp.dst() == timedelta( if timestamp.dst() == timedelta(
0 0

View File

@ -115,6 +115,28 @@ class UserProfile(TimeStampedModel):
return timestamp.replace(tzinfo=timezone) return timestamp.replace(tzinfo=timezone)
def adjust_timezone_of_scrobbles(self, commit=False):
current_dt = None
scrobbles_to_change_qs_list = []
for boundry_dt in self.historic_timezone_changes:
if current_dt and boundry_dt:
logger.info(
f"Checking for scrobbles between {current_dt} and {boundry_dt} to update to {current_dt.tzinfo.name}"
)
scrobbles = self.user.scrobble_set.filter(
timestamp__gte=current_dt,
timestamp__lt=boundry_dt,
).exclude(timezone=current_dt.tzinfo.name)
scrobbles_to_change_qs_list.append(scrobbles)
logger.info(
f"Updating {scrobbles.count()} scrobble timezones to {current_dt.tzinfo.name}"
)
if commit:
scrobbles.update(timezone=current_dt.tzinfo.name)
current_dt = boundry_dt
return scrobbles_to_change_qs_list
@cached_property @cached_property
def task_context_tags(self) -> list[str]: def task_context_tags(self) -> list[str]:
tag_list = [ tag_list = [

View File

@ -59,15 +59,15 @@ def start_of_year(dt, profile) -> datetime:
return start_of_day(dt, profile).replace(month=1, day=1) return start_of_day(dt, profile).replace(month=1, day=1)
def one_off_fix_colins_profile(profile): def fix_profile_historic_timezones(profile):
home_tz = "America/New_York" home_tz = "America/New_York"
europe = "2022-10-15 06:00:00" europe = "2023-10-15 06:00:00"
europe_end = "2023-12-16 12:00:00" europe_end = "2023-12-16 12:00:00"
europe_tz = "Europe/Paris" europe_tz = "Europe/Paris"
washington = "2023-04-28 06:00:00" washington = "2024-04-28 06:00:00"
washington_end = "2023-05-04 12:00:00" washington_end = "2024-05-04 12:00:00"
washington_tz = "America/Los_Angeles" washington_tz = "America/Los_Angeles"
camp = "2024-08-04 17:00:00" camp = "2024-08-04 17:00:00"
@ -78,6 +78,8 @@ def one_off_fix_colins_profile(profile):
summer_end = "2025-07-11 23:30:00" summer_end = "2025-07-11 23:30:00"
summer_tz = "America/Los_Angeles" summer_tz = "America/Los_Angeles"
profile.timezone_change_log = None
profile.timezone_change_log = "" profile.timezone_change_log = ""
profile.timezone_change_log += f"{europe_tz} - {pendulum.parse(europe)}\n" profile.timezone_change_log += f"{europe_tz} - {pendulum.parse(europe)}\n"
profile.timezone_change_log += ( profile.timezone_change_log += (

View File

@ -102,7 +102,7 @@ class ChartRecordAdmin(admin.ModelAdmin):
@admin.register(Scrobble) @admin.register(Scrobble)
class ScrobbleAdmin(admin.ModelAdmin): class ScrobbleAdmin(admin.ModelAdmin):
# date_hierarchy = "timestamp" date_hierarchy = "timestamp"
list_display = ( list_display = (
"timestamp", "timestamp",
"media_name", "media_name",
@ -112,6 +112,7 @@ class ScrobbleAdmin(admin.ModelAdmin):
"in_progress", "in_progress",
"is_paused", "is_paused",
"played_to_completion", "played_to_completion",
"user",
) )
raw_id_fields = ( raw_id_fields = (
"video", "video",
@ -140,6 +141,7 @@ class ScrobbleAdmin(admin.ModelAdmin):
"long_play_complete", "long_play_complete",
"source", "source",
"timezone", "timezone",
"user",
) )
ordering = ("-timestamp",) ordering = ("-timestamp",)
@ -148,3 +150,7 @@ class ScrobbleAdmin(admin.ModelAdmin):
def playback_percent(self, obj): def playback_percent(self, obj):
return obj.percent_played return obj.percent_played
def get_queryset(self, request):
qs = super().get_queryset(request).exclude(timestamp__year=None)
return qs

View File

@ -50,9 +50,7 @@ class LastFM:
enrich=True, enrich=True,
) )
timestamp = self.vrobbler_user.profile.get_timestamp_with_tz( timestamp = lfm_scrobble.get("timestamp")
lfm_scrobble.get("timestamp")
)
stop_timestamp = timestamp + timedelta( stop_timestamp = timestamp + timedelta(
seconds=track.run_time_seconds seconds=track.run_time_seconds
) )
@ -93,7 +91,7 @@ class LastFM:
) )
return created return created
def get_last_scrobbles(self, time_from=None, time_to=None): def get_last_scrobbles(self, time_from=None, time_to=None, check=False):
"""Given a user, Last.fm api key, and secret key, grab a list of scrobbled """Given a user, Last.fm api key, and secret key, grab a list of scrobbled
tracks""" tracks"""
lfm_params = {} lfm_params = {}
@ -109,6 +107,9 @@ class LastFM:
found_scrobbles = self.user.get_recent_tracks(**lfm_params) found_scrobbles = self.user.get_recent_tracks(**lfm_params)
# TOOD spin this out into a celery task over certain threshold of found scrobbles? # TOOD spin this out into a celery task over certain threshold of found scrobbles?
if check and found_scrobbles:
return True
for scrobble in found_scrobbles: for scrobble in found_scrobbles:
logger.info(f"Processing {scrobble}") logger.info(f"Processing {scrobble}")
run_time = None run_time = None

View File

@ -1,9 +1,11 @@
import logging
from books.koreader import fetch_file_from_webdav from books.koreader import fetch_file_from_webdav
from profiles.models import UserProfile from profiles.models import UserProfile
from scrobbles.models import KoReaderImport from scrobbles.models import KoReaderImport
from scrobbles.tasks import process_koreader_import from scrobbles.tasks import process_koreader_import
from scrobbles.utils import get_file_md5_hash
from webdav.client import get_webdav_client from webdav.client import get_webdav_client
import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -11,7 +13,7 @@ logger = logging.getLogger(__name__)
def import_from_webdav_for_all_users(restart=False): def import_from_webdav_for_all_users(restart=False):
"""Grab a list of all users with WebDAV enabled and kickoff imports for them""" """Grab a list of all users with WebDAV enabled and kickoff imports for them"""
# LastFmImport = apps.get_model("scrobbles", "LastFMImport") # WebDavImport = apps.get_model("scrobbles", "WebDavImport")
webdav_enabled_user_ids = UserProfile.objects.filter( webdav_enabled_user_ids = UserProfile.objects.filter(
webdav_url__isnull=False, webdav_url__isnull=False,
webdav_user__isnull=False, webdav_user__isnull=False,
@ -42,7 +44,7 @@ def import_from_webdav_for_all_users(restart=False):
KoReaderImport.objects.filter( KoReaderImport.objects.filter(
user_id=user_id, processed_finished__isnull=False user_id=user_id, processed_finished__isnull=False
) )
.order_by("Processed_finished") .order_by("processed_finished")
.last() .last()
) )

View File

@ -33,6 +33,7 @@ from profiles.utils import (
end_of_day, end_of_day,
end_of_month, end_of_month,
end_of_week, end_of_week,
fix_profile_historic_timezones,
start_of_day, start_of_day,
start_of_month, start_of_month,
start_of_week, start_of_week,
@ -205,6 +206,9 @@ class KoReaderImport(BaseFileImportMixin):
def process(self, force=False): def process(self, force=False):
if self.user.id == 1:
fix_profile_historic_timezones(self.user.profile)
if self.processed_finished and not force: if self.processed_finished and not force:
logger.info( logger.info(
f"{self} already processed on {self.processed_finished}" f"{self} already processed on {self.processed_finished}"
@ -250,6 +254,9 @@ class AudioScrobblerTSVImport(BaseFileImportMixin):
def process(self, force=False): def process(self, force=False):
from scrobbles.importers.tsv import import_audioscrobbler_tsv_file from scrobbles.importers.tsv import import_audioscrobbler_tsv_file
if self.user.id == 1:
fix_profile_historic_timezones(self.user.profile)
if self.processed_finished and not force: if self.processed_finished and not force:
logger.info( logger.info(
f"{self} already processed on {self.processed_finished}" f"{self} already processed on {self.processed_finished}"
@ -280,6 +287,10 @@ class LastFmImport(BaseFileImportMixin):
def process(self, import_all=False): def process(self, import_all=False):
"""Import scrobbles found on LastFM""" """Import scrobbles found on LastFM"""
if self.user.id == 1:
fix_profile_historic_timezones(self.user.profile)
if self.processed_finished: if self.processed_finished:
logger.info( logger.info(
f"{self} already processed on {self.processed_finished}" f"{self} already processed on {self.processed_finished}"
@ -327,6 +338,9 @@ class RetroarchImport(BaseFileImportMixin):
def process(self, import_all=False, force=False): def process(self, import_all=False, force=False):
"""Import scrobbles found on Retroarch""" """Import scrobbles found on Retroarch"""
if self.user.id == 1:
fix_profile_historic_timezones(self.user.profile)
if self.processed_finished and not force: if self.processed_finished and not force:
logger.info( logger.info(
f"{self} already processed on {self.processed_finished}" f"{self} already processed on {self.processed_finished}"

View File

@ -2,7 +2,6 @@ import logging
import re import re
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Any, Optional from typing import Any, Optional
from zoneinfo import ZoneInfo
import pendulum import pendulum
import pytz import pytz
@ -27,6 +26,7 @@ from scrobbles.constants import (
SCROBBLE_CONTENT_URLS, SCROBBLE_CONTENT_URLS,
) )
from scrobbles.models import Scrobble from scrobbles.models import Scrobble
from scrobbles.notifications import NtfyNotification
from scrobbles.utils import convert_to_seconds, extract_domain from scrobbles.utils import convert_to_seconds, extract_domain
from sports.models import SportEvent from sports.models import SportEvent
from sports.thesportsdb import lookup_event_from_thesportsdb from sports.thesportsdb import lookup_event_from_thesportsdb
@ -505,6 +505,7 @@ def email_scrobble_board_game(
scrobble.played_to_completion = True scrobble.played_to_completion = True
scrobble.save() scrobble.save()
scrobbles_created.append(scrobble) scrobbles_created.append(scrobble)
NtfyNotification(scrobble).send()
return scrobbles_created return scrobbles_created

View File

@ -114,6 +114,8 @@ def get_long_plays_completed(user: User) -> list:
def import_lastfm_for_all_users(restart=False): def import_lastfm_for_all_users(restart=False):
"""Grab a list of all users with LastFM enabled and kickoff imports for them""" """Grab a list of all users with LastFM enabled and kickoff imports for them"""
from scrobbles.importers.lastfm import LastFM
LastFmImport = apps.get_model("scrobbles", "LastFMImport") LastFmImport = apps.get_model("scrobbles", "LastFMImport")
lastfm_enabled_user_ids = UserProfile.objects.filter( lastfm_enabled_user_ids = UserProfile.objects.filter(
lastfm_username__isnull=False, lastfm_username__isnull=False,
@ -124,6 +126,31 @@ def import_lastfm_for_all_users(restart=False):
lastfm_import_count = 0 lastfm_import_count = 0
for user_id in lastfm_enabled_user_ids: for user_id in lastfm_enabled_user_ids:
lfm_import = LastFmImport.objects.filter(
user_id=user_id, processed_finished__isnull=False
).last()
if lfm_import:
last_processed = lfm_import.processed_finished
else:
logger.info(
f"Not resuming failed LastFM import {lfm_import.id} for user {user_id}, use restart=True to restart"
"No existing LastFM import, we should start a monthly parsing of lastFm for this user going back to 2002"
)
continue
lfm_client = LastFM(
user=get_user_model().objects.filter(id=user_id).first()
)
has_scrobbles = lfm_client.get_last_scrobbles(
time_from=last_processed, check=True
)
if not has_scrobbles:
logger.info("No new scrobbles to import from LastFM")
continue
lfm_import, created = LastFmImport.objects.get_or_create( lfm_import, created = LastFmImport.objects.get_or_create(
user_id=user_id, processed_finished__isnull=True user_id=user_id, processed_finished__isnull=True
) )

View File

@ -64,15 +64,16 @@ class ScrobbleableListView(ListView):
def get_queryset(self): def get_queryset(self):
queryset = super().get_queryset() queryset = super().get_queryset()
user_filter = Q()
if not self.request.user.is_anonymous: if not self.request.user.is_anonymous:
queryset = queryset.annotate( user_filter = Q(scrobble__user=self.request.user)
queryset = (
queryset.annotate(
scrobble_count=Count("scrobble"), scrobble_count=Count("scrobble"),
filter=Q(scrobble__user=self.request.user), )
).order_by("-scrobble_count") .filter(user_filter, scrobble_count__gt=0)
else: .order_by("-scrobble_count")
queryset = queryset.annotate( )
scrobble_count=Count("scrobble")
).order_by("-scrobble_count")
return queryset return queryset
@ -86,7 +87,7 @@ class ScrobbleableDetailView(DetailView):
if not self.request.user.is_anonymous: if not self.request.user.is_anonymous:
context_data["scrobbles"] = self.object.scrobble_set.filter( context_data["scrobbles"] = self.object.scrobble_set.filter(
user=self.request.user user=self.request.user
) ).order_by("-timestamp")
return context_data return context_data
@ -201,7 +202,7 @@ class RecentScrobbleList(ListView):
processed_finished__isnull=True, processed_finished__isnull=True,
user=self.request.user, user=self.request.user,
) )
data["counts"] = [] #scrobble_counts(user) data["counts"] = [] # scrobble_counts(user)
else: else:
data["weekly_data"] = week_of_scrobbles() data["weekly_data"] = week_of_scrobbles()
data["counts"] = scrobble_counts() data["counts"] = scrobble_counts()

View File

@ -39,7 +39,7 @@
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<p>{{object.scrobble_set.count}} scrobbles</p> <p>{{scrobbles.count}} scrobbles</p>
<p> <p>
<a href="{{object.start_url}}">Drink again</a> <a href="{{object.start_url}}">Drink again</a>
</p> </p>
@ -55,7 +55,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for scrobble in object.scrobble_set.all|dictsortreversed:"timestamp" %} {% for scrobble in scrobbles.all|dictsortreversed:"timestamp" %}
<tr> <tr>
<td>{{scrobble.local_timestamp}}</td> <td>{{scrobble.local_timestamp}}</td>
</tr> </tr>

View File

@ -42,7 +42,7 @@
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<p>{{object.scrobble_set.count}} scrobbles</p> <p>{{scrobbles.count}} scrobbles</p>
<p> <p>
<a href="{{object.start_url}}">Play again</a> <a href="{{object.start_url}}">Play again</a>
</p> </p>
@ -60,7 +60,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for scrobble in object.scrobble_set.all|dictsortreversed:"timestamp" %} {% for scrobble in scrobbles.all|dictsortreversed:"timestamp" %}
<tr> <tr>
<td>{{scrobble.local_timestamp}}</td> <td>{{scrobble.local_timestamp}}</td>
<td>{{scrobble.media_obj.publisher}}</td> <td>{{scrobble.media_obj.publisher}}</td>

View File

@ -26,10 +26,10 @@
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<p>{{object.scrobble_set.count}} scrobbles</p> <p>{{scrobbles.count}} scrobbles</p>
<p>Read {{object.scrobble_set.last.book_pages_read}} pages{% if object.scrobble_set.last.long_play_complete %} and completed{% else %}{% endif %}</p> <p>Read {{scrobbles.last.book_pages_read}} pages{% if scrobbles.last.long_play_complete %} and completed{% else %}{% endif %}</p>
<p> <p>
{% if object.scrobble_set.last.long_play_complete == True %} {% if scrobbles.last.long_play_complete == True %}
<a href="">Read again</a> <a href="">Read again</a>
{% else %} {% else %}
<a href="">Resume reading</a> <a href="">Resume reading</a>
@ -50,7 +50,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for scrobble in object.scrobble_set.all|dictsortreversed:"timestamp" %} {% for scrobble in scrobbles.all|dictsortreversed:"timestamp" %}
<tr> <tr>
<td>{{scrobble.local_timestamp}}</td> <td>{{scrobble.local_timestamp}}</td>
<td>{% if scrobble.long_play_complete == True %}Yes{% endif %}</td> <td>{% if scrobble.long_play_complete == True %}Yes{% endif %}</td>

View File

@ -48,7 +48,7 @@
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<p>{{object.scrobble_set.count}} scrobbles</p> <p>{{scrobbles.count}} scrobbles</p>
<div class="row"> <div class="row">
<div class="col-md"> <div class="col-md">
<h3>Last scrobbles</h3> <h3>Last scrobbles</h3>
@ -60,7 +60,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for scrobble in object.scrobble_set.all|dictsortreversed:"timestamp" %} {% for scrobble in scrobbles.all|dictsortreversed:"timestamp" %}
<tr> <tr>
<td>{{scrobble.local_timestamp|naturaltime}}</td> <td>{{scrobble.local_timestamp|naturaltime}}</td>
</tr> </tr>

View File

@ -26,7 +26,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for scrobble in object.scrobble_set.all %} {% for scrobble in scrobbles.all %}
<tr> <tr>
<td>{{scrobble.local_timestamp}}</td> <td>{{scrobble.local_timestamp}}</td>
</tr> </tr>

View File

@ -9,7 +9,7 @@
{% endif %} {% endif %}
</div> </div>
<div class="row"> <div class="row">
<p>{{object.scrobble_set.count}} scrobbles</p> <p>{{scrobbles.count}} scrobbles</p>
{% if charts %} {% if charts %}
<p>{% for chart in charts %}<em><a href="{{chart.link}}">{{chart}}</a></em>{% if forloop.last %}{% else %} | {% endif %}{% endfor %}</p> <p>{% for chart in charts %}<em><a href="{{chart.link}}">{{chart}}</a></em>{% if forloop.last %}{% else %} | {% endif %}{% endfor %}</p>
{% endif %} {% endif %}
@ -26,7 +26,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for scrobble in object.scrobble_set.all %} {% for scrobble in scrobbles.all %}
<tr> <tr>
<td>{{scrobble.local_timestamp}}</td> <td>{{scrobble.local_timestamp}}</td>
<td><a href="{{scrobble.track.get_absolute_url}}">{{scrobble.track.title}}</a></td> <td><a href="{{scrobble.track.get_absolute_url}}">{{scrobble.track.title}}</a></td>

View File

@ -18,7 +18,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for scrobble in object.scrobble_set.all %} {% for scrobble in scrobbles.all %}
<tr> <tr>
<td>{{scrobble.local_timestamp}}</td> <td>{{scrobble.local_timestamp}}</td>
<td>{{scrobble.media_obj.round.season.name}}</td> <td>{{scrobble.media_obj.round.season.name}}</td>

View File

@ -39,7 +39,7 @@
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<p>{{object.scrobble_set.count}} scrobbles</p> <p>{{scrobbles.count}} scrobbles</p>
<p> <p>
<a href="{{object.start_url}}">Play again</a> <a href="{{object.start_url}}">Play again</a>
</p> </p>
@ -57,7 +57,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for scrobble in object.scrobble_set.all|dictsortreversed:"timestamp" %} {% for scrobble in scrobbles.all|dictsortreversed:"timestamp" %}
<tr> <tr>
<td>{{scrobble.local_timestamp}}</td> <td>{{scrobble.local_timestamp}}</td>
<td><a href="{{scrobble.get_media_source_url}}">{{scrobble.logdata.description}}</a></td> <td><a href="{{scrobble.get_media_source_url}}">{{scrobble.logdata.description}}</a></td>

View File

@ -59,12 +59,12 @@
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<p>{{object.scrobble_set.count}} scrobbles</p> <p>{{scrobbles.count}} scrobbles</p>
{% if object.scrobble_set.last.long_play_seconds %} {% if scrobbles.last.long_play_seconds %}
<p>{{object.scrobble_set.last.long_play_seconds|natural_duration}}{% if object.scrobble_set.last.long_play_complete %} and completed{% else %} spent playing{% endif %}</p> <p>{{scrobbles.last.long_play_seconds|natural_duration}}{% if scrobbles.last.long_play_complete %} and completed{% else %} spent playing{% endif %}</p>
{% endif %} {% endif %}
<p> <p>
{% if object.scrobble_set.last.long_play_complete == True %} {% if scrobbles.last.long_play_complete == True %}
<a href="">Play again</a> <a href="">Play again</a>
{% else %} {% else %}
<a href="{{object.start_url}}">Resume playing</a> <a href="{{object.start_url}}">Resume playing</a>
@ -86,7 +86,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for scrobble in object.scrobble_set.all|dictsortreversed:"timestamp" %} {% for scrobble in scrobbles.all|dictsortreversed:"timestamp" %}
<tr> <tr>
<td>{{scrobble.local-timestamp}}</td> <td>{{scrobble.local-timestamp}}</td>
<td>{% if scrobble.long_play_complete == True %}Yes{% else %}Not yet{% endif %}</td> <td>{% if scrobble.long_play_complete == True %}Yes{% else %}Not yet{% endif %}</td>

View File

@ -85,7 +85,7 @@ dd {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for scrobble in object.scrobble_set.all %} {% for scrobble in scrobbles.all %}
<tr> <tr>
<td>{{scrobble.local_timestamp}}</td> <td>{{scrobble.local_timestamp}}</td>
</tr> </tr>

View File

@ -51,7 +51,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for scrobble in object.scrobble_set.all %} {% for scrobble in scrobbles.all %}
<tr> <tr>
<td>{{scrobble.local_timestamp}}</td> <td>{{scrobble.local_timestamp}}</td>
</tr> </tr>