Add history delete button and Truckers FM station
All checks were successful
Build and push Docker image / build (push) Successful in 12s
Test / test (push) Successful in 13s

This commit is contained in:
marwin 2026-03-16 20:16:30 +01:00
parent df601714ec
commit bf2f01c4c6
5 changed files with 43 additions and 3 deletions

View file

@ -10,6 +10,7 @@ urlpatterns = [
path('radio/remove/<int:pk>/', views.remove_station, name='remove_station'), path('radio/remove/<int:pk>/', views.remove_station, name='remove_station'),
path('radio/favorite/<int:pk>/', views.toggle_favorite, name='toggle_favorite'), path('radio/favorite/<int:pk>/', views.toggle_favorite, name='toggle_favorite'),
path('radio/history/', views.history_json, name='history_json'), path('radio/history/', views.history_json, name='history_json'),
path('radio/history/<int:pk>/delete/', views.delete_history_entry, name='delete_history_entry'),
path('radio/play/start/', views.start_play, name='start_play'), path('radio/play/start/', views.start_play, name='start_play'),
path('radio/play/stop/', views.stop_play, name='stop_play'), path('radio/play/stop/', views.stop_play, name='stop_play'),
path('radio/recommendations/', views.recommendations, name='recommendations'), path('radio/recommendations/', views.recommendations, name='recommendations'),

View file

@ -37,7 +37,7 @@ def index(request):
) )
history = list( history = list(
request.user.track_history.values( request.user.track_history.values(
'station_name', 'track', 'played_at', 'scrobbled' 'id', 'station_name', 'track', 'played_at', 'scrobbled'
)[:50] )[:50]
) )
# Convert datetime to ISO string for JSON serialisation in template # Convert datetime to ISO string for JSON serialisation in template
@ -383,6 +383,19 @@ def recommendations(request):
# History JSON # History JSON
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@csrf_exempt
@require_http_methods(['POST'])
def delete_history_entry(request, pk):
if not request.user.is_authenticated:
return JsonResponse({'error': 'authentication required'}, status=401)
try:
entry = TrackHistory.objects.get(pk=pk, user=request.user)
entry.delete()
return JsonResponse({'ok': True})
except TrackHistory.DoesNotExist:
return JsonResponse({'error': 'not found'}, status=404)
def history_json(request): def history_json(request):
if not request.user.is_authenticated: if not request.user.is_authenticated:
return JsonResponse({'error': 'authentication required'}, status=401) return JsonResponse({'error': 'authentication required'}, status=401)

View file

@ -895,3 +895,14 @@ body.dnd-mode .timer-display {
font-size: 0.85rem; font-size: 0.85rem;
color: var(--fg); color: var(--fg);
} }
.btn-delete-history {
background: none;
border: none;
color: var(--muted, #888);
cursor: pointer;
font-size: 0.75rem;
padding: 0 4px;
opacity: 0.5;
}
.btn-delete-history:hover { opacity: 1; color: #e55; }

View file

@ -257,10 +257,23 @@ function addHistoryRow(stationName, track) {
<td>${escapeHtml(stationName)}</td> <td>${escapeHtml(stationName)}</td>
<td>${escapeHtml(track)}</td> <td>${escapeHtml(track)}</td>
<td></td> <td></td>
<td><button class="btn-delete-history" onclick="deleteHistoryEntry(null, this)" title="Remove"></button></td>
`; `;
tbody.insertBefore(tr, tbody.firstChild); tbody.insertBefore(tr, tbody.firstChild);
} }
async function deleteHistoryEntry(id, btn) {
const tr = btn.closest('tr');
if (!id) { tr.remove(); return; }
try {
const res = await fetch(`/radio/history/${id}/delete/`, {
method: 'POST',
headers: {'X-CSRFToken': getCsrfToken()},
});
if (res.ok) tr.remove();
} catch (e) {}
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Affiliate links // Affiliate links
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------

View file

@ -144,18 +144,20 @@
<th>Station</th> <th>Station</th>
<th>Track</th> <th>Track</th>
<th>&#9836;</th> <th>&#9836;</th>
<th></th>
</tr> </tr>
</thead> </thead>
<tbody id="history-tbody"> <tbody id="history-tbody">
{% for entry in history %} {% for entry in history %}
<tr> <tr data-id="{{ entry.id }}">
<td class="history-time">{{ entry.played_at|slice:":16"|cut:"T" }}</td> <td class="history-time">{{ entry.played_at|slice:":16"|cut:"T" }}</td>
<td>{{ entry.station_name }}</td> <td>{{ entry.station_name }}</td>
<td>{{ entry.track }}</td> <td>{{ entry.track }}</td>
<td>{% if entry.scrobbled %}<span title="Scrobbled to Last.fm">&#10003;</span>{% endif %}</td> <td>{% if entry.scrobbled %}<span title="Scrobbled to Last.fm">&#10003;</span>{% endif %}</td>
<td><button class="btn-delete-history" onclick="deleteHistoryEntry({{ entry.id }}, this)" title="Remove"></button></td>
</tr> </tr>
{% empty %} {% empty %}
<tr id="history-empty-row"><td colspan="4" class="empty-msg">No history yet.</td></tr> <tr id="history-empty-row"><td colspan="5" class="empty-msg">No history yet.</td></tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>