Compare commits

..

2 Commits
52.0 ... 52.1

Author SHA1 Message Date
c1744fab37 [release] Bump to version 52.1
All checks were successful
build / test (push) Successful in 1m56s
deploy / test (push) Successful in 2m1s
deploy / build-and-deploy (push) Successful in 29s
- Show time per scrobble in long play lists and total time playing
2026-06-15 15:27:10 -04:00
042a3eb737 [templates] Add aggregate data
Some checks failed
build / test (push) Has been cancelled
2026-06-15 15:26:46 -04:00
11 changed files with 85 additions and 11 deletions

View File

@ -594,7 +594,20 @@ named constants for maintainability.
- ~vrobbler/apps/webpages/models.py~ (line 290) -- ="url"=
- ~vrobbler/apps/scrobbles/importers/tsv.py~ (line 55) -- ="S"= completion status
** TODO [#C] Show time per scrobble in long play lists and total time playing :templates:longplay:scrobbles:
* Version 52.1 [1/1]
** DONE [#C] Show time per scrobble in long play lists and total time playing :templates:longplay:scrobbles:
:PROPERTIES:
:ID: b3d16230-8ec5-46db-b166-59e98d0ee06c
:END:
*** Description
Long play time should be show in the table of scrobbles on a media detail page.
The total time spent in a long play that's either no completed yet or completed
should be displayed as well. If completed, the date finished should be shown as
well.
* Version 52.0 [5/5]
** DONE [#B] Allow marking media as long play complete from detail page :templates:scrobbles:longplay:
:PROPERTIES:

View File

@ -1,6 +1,6 @@
[tool.poetry]
name = "vrobbler"
version = "52.0"
version = "52.1"
description = ""
authors = ["Colin Powell <colin@unbl.ink>"]

View File

@ -206,6 +206,19 @@ class ScrobbleableDetailView(ChartContextMixin, DetailView):
context_data["scrobbles"] = page_obj.object_list
context_data["is_paginated"] = paginator.num_pages > 1
media = self.object
if hasattr(media, "is_long_play_media") and media.is_long_play_media():
qs = media.scrobble_set.filter(user=self.request.user)
completed = qs.filter(long_play_complete=True).order_by("-timestamp").first()
if completed and completed.long_play_seconds:
context_data["long_play_total_seconds"] = completed.long_play_seconds
context_data["long_play_finished_date"] = completed.timestamp
else:
latest_finished = qs.filter(played_to_completion=True).order_by("-timestamp").first()
if latest_finished and latest_finished.long_play_seconds:
context_data["long_play_total_seconds"] = latest_finished.long_play_seconds
context_data["long_play_finished_date"] = None
return context_data
@ -1297,6 +1310,24 @@ class ScrobbleDetailView(DetailView):
else:
context["has_mopidy_uri"] = False
if self.object.is_long_play and fk_field:
all_scrobbles = Scrobble.objects.filter(
user=user, **{fk_field: media_obj}
)
completed = all_scrobbles.filter(
long_play_complete=True
).order_by("-timestamp").first()
if completed and completed.long_play_seconds:
context["long_play_total_seconds"] = completed.long_play_seconds
context["long_play_finished_date"] = completed.timestamp
else:
latest_finished = all_scrobbles.filter(
played_to_completion=True
).order_by("-timestamp").first()
if latest_finished and latest_finished.long_play_seconds:
context["long_play_total_seconds"] = latest_finished.long_play_seconds
context["long_play_finished_date"] = None
return context

View File

@ -6,6 +6,7 @@
<tr>
<th scope="col">Latest</th>
<th scope="col">Title</th>
<th scope="col">Time</th>
<th scope="col">Scrobbles</th>
<th scope="col">Complete</th>
<th scope="col">Start</th>
@ -15,16 +16,19 @@
<tbody>
{% for obj in object_list %}
{% if obj.title %}
{% with last=obj.scrobble_set.last %}
<tr>
<td><a href="{{obj.scrobble_set.last.get_absolute_url}}">{{obj.scrobble_set.last.local_timestamp}}
<td><a href="{{last.get_absolute_url}}">{{last.local_timestamp}}
<td><a href="{{obj.get_absolute_url}}">{{obj}}</a></td>
{% if request.user.is_authenticated %}
<td>{% if last.long_play_seconds %}{{ last.long_play_seconds|natural_duration }}{% elif last.elapsed_time %}{{ last.elapsed_time|natural_duration }}{% endif %}</td>
<td>{{obj.scrobble_count}}</td>
<td>{% if obj.scrobble_set.last.long_play_complete == True %}Yes{% else %}No{% endif %}</td>
<td><a type="button" class="btn btn-sm btn-primary" href="{{obj.start_url}}">Scrobble</a></td>
<td><a type="button" class="btn btn-sm btn-warning" href="{{obj.get_longplay_finish_url}}">Finish</a></td>
{% endif %}
</tr>
{% endwith %}
{% endif %}
{% endfor %}
</tbody>

View File

@ -33,7 +33,11 @@
<p><a href="{{object.next_readcomics_url}}">Read next issue</a></p>
{% endif %}
<p>{{scrobbles.count}} scrobbles</p>
<p>
{{scrobbles.count}} scrobbles
{% if long_play_total_seconds %} | Total time: {{ long_play_total_seconds|natural_duration }}{% endif %}
{% if long_play_finished_date %} | Finished: {{ long_play_finished_date|date:"M d, Y" }}{% endif %}
</p>
<p><a href="{{object.resume_start_url}}">Resume reading</a></p>
</div>

View File

@ -26,7 +26,11 @@
</div>
</div>
<div class="row">
<p>{{scrobbles.count}} scrobbles</p>
<p>
{{scrobbles.count}} scrobbles
{% if long_play_total_seconds %} | Total time: {{ long_play_total_seconds|natural_duration }}{% endif %}
{% if long_play_finished_date %} | Finished: {{ long_play_finished_date|date:"M d, Y" }}{% endif %}
</p>
</div>
<div class="row">
<div class="col-md">

View File

@ -264,7 +264,13 @@
<h2 class="mt-4">All scrobbles of this {{ object.media_type|lower }}</h2>
{% if related_scrobbles %}
<p class="text-muted">{{ related_scrobbles.paginator.count }} scrobble{{ related_scrobbles.paginator.count|pluralize }}</p>
<p class="text-muted">
{{ related_scrobbles.paginator.count }} scrobble{{ related_scrobbles.paginator.count|pluralize }}
{% if object.is_long_play and long_play_total_seconds %}
| Total time: {{ long_play_total_seconds|natural_duration }}
{% if long_play_finished_date %} | Finished: {{ long_play_finished_date|date:"M d, Y" }}{% endif %}
{% endif %}
</p>
<table class="table table-striped table-sm">
<thead>
<tr>
@ -281,7 +287,11 @@
{% if scrobble.media_type == "Task" and scrobble.logdata.title %}{{ scrobble.media_obj.title }}: {{ scrobble.logdata.title }}{% else %}{{ scrobble.media_obj.title|default:scrobble.media_obj }}{% endif %}
</td>
<td>
{% if scrobble.playback_position_seconds %}{{ scrobble.playback_position_seconds|natural_duration }}{% endif %}
{% if scrobble.is_long_play and scrobble.long_play_seconds %}
{{ scrobble.playback_position_seconds|natural_duration }} ({{ scrobble.long_play_seconds|natural_duration }} total)
{% elif scrobble.playback_position_seconds %}
{{ scrobble.playback_position_seconds|natural_duration }}
{% endif %}
</td>
</tr>
{% endfor %}

View File

@ -50,7 +50,11 @@
{% endif %}
<div class="row">
<p>{{scrobbles.count}} scrobbles</p>
<p>
{{scrobbles.count}} scrobbles
{% if long_play_total_seconds %} | Total time: {{ long_play_total_seconds|natural_duration }}{% endif %}
{% if long_play_finished_date %} | Finished: {{ long_play_finished_date|date:"M d, Y" }}{% endif %}
</p>
<p>
<a href="{{object.start_url}}">Play again</a>
</p>

View File

@ -26,7 +26,7 @@
{% block lists %}
<div class="row">
<div class="col-md">
<div class="table-responsive">{% include "_scrobblable_list.html" %}</div>
<div class="table-responsive">{% include "_longplay_scrobblable_list.html" %}</div>
</div>
</div>
{% endblock %}

View File

@ -59,7 +59,11 @@
</div>
</div>
<div class="row">
<p>{{scrobbles.count}} scrobbles</p>
<p>
{{scrobbles.count}} scrobbles
{% if long_play_total_seconds %} | Total time: {{ long_play_total_seconds|natural_duration }}{% endif %}
{% if long_play_finished_date %} | Finished: {{ long_play_finished_date|date:"M d, Y" }}{% endif %}
</p>
<p>
<a href="">Play again</a>
</p>

View File

@ -16,7 +16,7 @@
<div class="col-md">
<div class="table-responsive">
{% include "_scrobblable_list.html" %}
{% include "_longplay_scrobblable_list.html" %}
</div>
</div>
</div>