[scrobbles] Fix display of task notes
This commit is contained in:
16
PROJECT.org
16
PROJECT.org
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
|
||||
@ -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")
|
||||
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user