[scrobbles] Fix display of task notes
All checks were successful
build & deploy / test (push) Successful in 1m40s
build & deploy / deploy (push) Successful in 21s

This commit is contained in:
2026-03-24 15:10:33 -04:00
parent cce2db0ea1
commit 4121915aa3
4 changed files with 149 additions and 33 deletions

View File

@ -92,7 +92,7 @@ fetching and simple saving.
:LOGBOOK:
CLOCK: [2025-07-09 Wed 09:55]--[2025-07-09 Wed 10:15] => 0:20
:END:
* Backlog [16/35]
* Backlog [17/37]
** TODO [#C] Create small utility to clean up tracks scrobbled with wonky playback times :vrobbler:personal:bug:music:scrobbles:
** TODO [#C] Move to using more robust mopidy-webhooks pacakge form pypi :utility:improvement:
:PROPERTIES:
@ -431,11 +431,14 @@ https://app.todoist.com/app/task/add-a-csv-endpoint-for-users-book-reads-that-li
** TODO [#A] When creating org-mode tasks, don't copy comments :vrobbler:bug:scrobbles:tasks:
** TODO Add sentiment parsing for Scrobbles with notes :vrobbler:project:scrobbles:sentiment:
** DONE Fix genearting chart records :vrobbler:bug:personal:project:chartrecords:
** STRT Fix display of notes so they look like stickies :vrobbler:project:personal:notes:scrobbles:
:PROPERTIES:
:ID: e749d762-44ac-dd7e-017e-2423016737ff
:ID: 0ace8814-e96e-fa54-28d1-c57dcb508f1e
:END:
** DONE Add searching to scrobbles :vrobbler:project:personal:search:scrobbles:
:PROPERTIES:
:ID: b4690c76-d741-4320-20e8-c003251fe035
:END:
** DONE Fix uniqueness of imdb_id messing up youtube videos :vrobbler:project:bug:videos:
:PROPERTIES:
:ID: 610b8c4c-abf1-8630-a4fd-08d9939da9f5
@ -445,6 +448,11 @@ Turns out you can't make imdb_id unique by itself or you never get to create
youtube videos. Rather, we should make imdb_id and youtube_id unique together so
imdb_id can be empty for every youtube_id and vice versa.
** DONE Fix genearting chart records :vrobbler:bug:personal:project:chartrecords:
:PROPERTIES:
:ID: e749d762-44ac-dd7e-017e-2423016737ff
:END:
** DONE [#A] Save raw scrobble request data to every scrobble log :vrobbler:personal:feature:scrobbles:
:PROPERTIES:
:ID: b4686db4-1319-399c-9b84-f0b1036c6815

View File

@ -792,6 +792,7 @@ class ScrobbleDetailView(DetailView):
model = Scrobble
slug_field = "uuid"
slug_url_kwarg = "uuid"
paginate_by = 100
def get_form_class(self):
return self.object.media_obj.logdata_cls().form()
@ -830,10 +831,55 @@ class ScrobbleDetailView(DetailView):
context = self.get_context_data(log_form=form)
return self.render_to_response(context)
MEDIA_FK_MAP = {
"Video": "video",
"Track": "track",
"PodcastEpisode": "podcast_episode",
"Book": "book",
"Paper": "paper",
"VideoGame": "video_game",
"BoardGame": "board_game",
"Beer": "beer",
"Food": "food",
"Puzzle": "puzzle",
"Trail": "trail",
"GeoLocation": "geo_location",
"Task": "task",
"WebPage": "web_page",
"LifeEvent": "life_event",
"Mood": "mood",
"BrickSet": "brick_set",
}
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if "log_form" not in context:
context["log_form"] = self.get_form()
media_obj = self.object.media_obj
user = self.object.user
media_type = self.object.media_type
fk_field = self.MEDIA_FK_MAP.get(media_type)
if fk_field:
related_scrobbles = (
Scrobble.objects.filter(user=user)
.filter(**{fk_field: media_obj})
.exclude(id=self.object.id)
.order_by("-timestamp")
)
else:
related_scrobbles = Scrobble.objects.none()
page = self.request.GET.get("page")
paginator = Paginator(related_scrobbles, self.paginate_by)
try:
context["related_scrobbles"] = paginator.page(page)
except PageNotAnInteger:
context["related_scrobbles"] = paginator.page(1)
except EmptyPage:
context["related_scrobbles"] = paginator.page(paginator.num_pages)
return context

View File

@ -1,3 +1,4 @@
import json
from dataclasses import dataclass
from typing import Optional
@ -45,15 +46,20 @@ class TaskLogData(BaseLogData):
lines = []
if self.notes:
for note in self.notes:
notes = self.notes
if isinstance(notes, str):
notes = [notes]
for note in notes:
if isinstance(note, dict):
timestamp, note_text = next(iter(note.items()))
# Flatten newlines and clean whitespace
note_text = " ".join(note_text.strip().split())
lines.append(f"{timestamp}: {note_text} [{labels_str}]")
if isinstance(note, list):
lines += note
if isinstance(note, str):
lines.append(note)
return "\n".join(lines).encode("utf-8").decode("unicode_escape")

View File

@ -1,6 +1,7 @@
{% extends "base_list.html" %}
{% load form_tags %}
{% load mathfilters %}
{% load naturalduration %}
{% load static %}
{% block title %}{{object.name}}{% endblock %}
@ -11,40 +12,95 @@
<h1>{{ object.media_obj }} - {{object.media_type}}</h1>
{% if object.notes %}
<details class="mb-3">
<summary>Notes ({{ object.notes|length }})</summary>
<ul class="mt-2">
{% for note in object.notes %}
<li>{{ note }}</li>
{% endfor %}
</ul>
</details>
{% with notes_string=object.logdata.notes_as_str %}
{% if notes_string %}
<div class="mb-3">
<h5>Notes</h5>
<pre>{{ notes_string }}</pre>
</div>
{% endif %}
{% endwith %}
<!-- Your existing detail page content -->
{% if object.logdata.avg_seconds_per_page %}
<p>Rate: {{object.logdata.avg_seconds_per_page}}s per page</p>
{% endif %}
<h2>Edit Log</h2>
<form method="post" class="needs-validation" novalidate>
{% csrf_token %}
{% for field in log_form %}
<div class="mb-3">
<label for="{{ field.id_for_label }}" class="form-label">{{ field.label }}</label>
{{ field|add_class:"form-control" }}
{% if field.help_text %}
<div class="form-text">{{ field.help_text }}</div>
{% endif %}
{% for error in field.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
</div>
{% endfor %}
<details class="mb-3">
<summary>Edit Log</summary>
<form method="post" class="needs-validation mt-3" novalidate>
{% csrf_token %}
{% for field in log_form %}
<div class="mb-3">
<label for="{{ field.id_for_label }}" class="form-label">{{ field.label }}</label>
{{ field|add_class:"form-control" }}
{% if field.help_text %}
<div class="form-text">{{ field.help_text }}</div>
{% endif %}
{% for error in field.errors %}
<div class="invalid-feedback d-block">{{ error }}</div>
{% endfor %}
</div>
{% endfor %}
<button type="submit" class="btn btn-primary">Save</button>
</form>
<button type="submit" class="btn btn-primary">Save</button>
</form>
</details>
<h2 class="mt-4">Other Scrobbles of This Media</h2>
{% if related_scrobbles %}
<p class="text-muted">{{ related_scrobbles.paginator.count }} scrobble{{ related_scrobbles.paginator.count|pluralize }}</p>
<table class="table table-striped table-sm">
<thead>
<tr>
<th scope="col">Date</th>
<th scope="col">Title</th>
<th scope="col">Time</th>
</tr>
</thead>
<tbody>
{% for scrobble in related_scrobbles %}
<tr>
<td><a href="{% url 'scrobbles:detail' scrobble.uuid %}">{{ scrobble.timestamp|date:"M d, Y" }}</a></td>
<td>
{% 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 %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if related_scrobbles.has_other_pages %}
<nav>
<ul class="pagination">
{% if related_scrobbles.has_previous %}
<li class="page-item"><a class="page-link" href="?page={{ related_scrobbles.previous_page_number }}">Previous</a></li>
{% else %}
<li class="page-item disabled"><span class="page-link">Previous</span></li>
{% endif %}
{% for num in related_scrobbles.paginator.page_range %}
{% if related_scrobbles.number == num %}
<li class="page-item active"><span class="page-link">{{ num }}</span></li>
{% elif num > related_scrobbles.number|add:'-3' and num < related_scrobbles.number|add:'3' %}
<li class="page-item"><a class="page-link" href="?page={{ num }}">{{ num }}</a></li>
{% endif %}
{% endfor %}
{% if related_scrobbles.has_next %}
<li class="page-item"><a class="page-link" href="?page={{ related_scrobbles.next_page_number }}">Next</a></li>
{% else %}
<li class="page-item disabled"><span class="page-link">Next</span></li>
{% endif %}
</ul>
</nav>
{% endif %}
{% else %}
<p class="text-muted">No other scrobbles of this media.</p>
{% endif %}
</div>