[templates] Add HTMX support to Now Playing

This commit is contained in:
2026-06-21 23:00:09 -04:00
parent 7d708ad8a6
commit 948fbc19bf
9 changed files with 100 additions and 22 deletions

View File

@ -88,7 +88,7 @@ fetching and simple saving.
*** Metadata sources
**** Scraper
* Backlog [3/26] :vrobbler:project:personal:
* Backlog [4/26] :vrobbler:project:personal:
** TODO [#C] Create small utility to clean up tracks scrobbled with wonky playback times :bug:music:scrobbles:
:PROPERTIES:
:ID: 702462cf-d54b-48c6-8a7c-78b8de751deb
@ -604,7 +604,7 @@ independent of the email flow it was originally creatdd for
** TODO [#B] Is there way to create unique slugs for media instances :media_types:
** STRT [#B] Use HTMx to update the Now Playing widget :feature:templates:
** DONE [#B] Use HTMx to update the Now Playing widget :feature:templates:
:PROPERTIES:
:ID: 5f5631fc-9ee1-d5a5-d0f8-94fea6fbbfa4
:END:

View File

@ -40,6 +40,7 @@ class UserProfileForm(forms.ModelForm):
"enable_public_widgets",
"widget_custom_css",
"home_scrobble_limit",
"live_now_playing",
"weigh_in_units",
]
widgets = {

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2.29 on 2026-06-22 02:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("profiles", "0038_userprofile_media_type_visibility"),
]
operations = [
migrations.AddField(
model_name="userprofile",
name="live_now_playing",
field=models.BooleanField(default=False),
),
]

View File

@ -98,6 +98,8 @@ class UserProfile(TimeStampedModel):
home_scrobble_limit = models.IntegerField(default=20)
live_now_playing = models.BooleanField(default=False)
weigh_in_units = models.CharField(
max_length=16,
choices=WeighUnit.choices,

View File

@ -5,6 +5,7 @@ from tasks.webhooks import EmacsWebhookView, TodoistWebhookView
app_name = "scrobbles"
urlpatterns = [
path("now-playing/", views.NowPlayingPartialView.as_view(), name="now-playing-partial"),
path("calendar/", views.ScrobbleCalendarView.as_view(), name="calendar"),
path("search/", views.ScrobbleSearchView.as_view(), name="search"),
path("status/", views.ScrobbleStatusView.as_view(), name="status"),

View File

@ -361,6 +361,25 @@ class RecentScrobbleList(ListView):
return Scrobble.objects.all().order_by("-timestamp")
class NowPlayingPartialView(LoginRequiredMixin, TemplateView):
template_name = "scrobbles/_now_playing.html"
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
from scrobbles.constants import EXCLUDE_FROM_NOW_PLAYING
ctx["now_playing_list"] = list(
Scrobble.objects.filter(
in_progress=True,
is_paused=False,
user=self.request.user,
)
.exclude(media_type__in=EXCLUDE_FROM_NOW_PLAYING)
.select_related("track", "video", "podcast_episode")
)
return ctx
class ScrobbleListView(LoginRequiredMixin, ListView):
model = Scrobble
paginate_by = 100

View File

@ -0,0 +1,29 @@
# Generated by Django 4.2.29 on 2026-06-22 02:44
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
(
"trends",
"0002_alter_trendresult_unique_together_trendresult_period_and_more",
),
]
operations = [
migrations.AlterField(
model_name="trendresult",
name="period",
field=models.CharField(
choices=[
("last_30", "Last 30 days"),
("last_90", "Last 90 days"),
("last_year", "Last year"),
],
default="last_30",
max_length=20,
),
),
]

View File

@ -311,26 +311,13 @@
{% endblock %}
{% if now_playing_list and user.is_authenticated %}
<ul>
<b>Now playing</b>
{% for scrobble in now_playing_list %}
<div class="now-playing">
{% if scrobble.media_obj.primary_image_url %}<div style="float:left;padding-right:10px;padding-bottom:10px;"><img src="{{scrobble.media_obj.primary_image_url}}" /></div>{% endif %}
<p><a href="{{scrobble.get_absolute_url}}">{{scrobble.media_obj}}</a></p>
{% if scrobble.logdata %}{% if scrobble.logdata.title%}<p><em>{{scrobble.logdata.title}}</em></p>{% endif %}{% endif %}
<p><small>{{scrobble.local_timestamp|naturaltime}} from {{scrobble.source}}</small></p>
<div class="progress-bar" style="margin-right:5px;">
<span class="progress-bar-fill" style="width: {{scrobble.percent_played}}%;"></span>
</div>
<p class="action-buttons">
<a href="{% url "scrobbles:cancel" scrobble.id %}">Cancel</a>
<a class="right" href="{% url "scrobbles:finish" scrobble.id %}">Finish</a>
</p>
{% if not forloop.last %}<hr/>{% endif %}
</div>
{% endfor %}
</ul>
{% if now_playing_list|length > 1 %}<hr/>{% endif %}
{% if user.profile.live_now_playing %}
<div id="now-playing-container" hx-get="{% url 'scrobbles:now-playing-partial' %}" hx-trigger="every 7s" hx-swap="innerHTML">
{% include "scrobbles/_now_playing.html" %}
</div>
{% else %}
{% include "scrobbles/_now_playing.html" %}
{% endif %}
{% endif %}
{% if active_imports %}

View File

@ -0,0 +1,21 @@
{% load humanize %}
<ul>
<b>Now playing</b>
{% for scrobble in now_playing_list %}
<div class="now-playing">
{% if scrobble.media_obj.primary_image_url %}<div style="float:left;padding-right:10px;padding-bottom:10px;"><img src="{{scrobble.media_obj.primary_image_url}}" /></div>{% endif %}
<p><a href="{{scrobble.get_absolute_url}}">{{scrobble.media_obj}}</a></p>
{% if scrobble.logdata %}{% if scrobble.logdata.title%}<p><em>{{scrobble.logdata.title}}</em></p>{% endif %}{% endif %}
<p><small>{{scrobble.local_timestamp|naturaltime}} from {{scrobble.source}}</small></p>
<div class="progress-bar" style="margin-right:5px;">
<span class="progress-bar-fill" style="width: {{scrobble.percent_played}}%;"></span>
</div>
<p class="action-buttons">
<a href="{% url "scrobbles:cancel" scrobble.id %}">Cancel</a>
<a class="right" href="{% url "scrobbles:finish" scrobble.id %}">Finish</a>
</p>
{% if not forloop.last %}<hr/>{% endif %}
</div>
{% endfor %}
</ul>
{% if now_playing_list|length > 1 %}<hr/>{% endif %}