[music] Tracks can have multiple artists
All checks were successful
build & deploy / test (push) Successful in 1m54s
build & deploy / build-and-deploy (push) Successful in 33s

This commit is contained in:
2026-05-28 23:32:09 -04:00
parent 17aed1191d
commit 22956c7c7f
15 changed files with 231 additions and 84 deletions

View File

@ -226,7 +226,7 @@ class RecentScrobbleList(ListView):
# Get user's home scrobble limit (default 20)
home_limit = 20
if hasattr(user, 'profile') and user.profile.home_scrobble_limit:
if hasattr(user, "profile") and user.profile.home_scrobble_limit:
home_limit = user.profile.home_scrobble_limit
today = timezone.localtime(timezone.now())
@ -612,9 +612,7 @@ class KoReaderImportCreateView(LoginRequiredMixin, JsonableResponseMixin, Create
return HttpResponseRedirect(self.request.META.get("HTTP_REFERER"))
class ScaleCSVImportCreateView(
LoginRequiredMixin, JsonableResponseMixin, CreateView
):
class ScaleCSVImportCreateView(LoginRequiredMixin, JsonableResponseMixin, CreateView):
model = ScaleCSVImport
fields = ["csv_file"]
template_name = "scrobbles/upload_form.html"
@ -623,17 +621,13 @@ class ScaleCSVImportCreateView(
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.user = self.request.user
self.object.original_filename = (
form.cleaned_data["csv_file"].name
)
self.object.original_filename = form.cleaned_data["csv_file"].name
self.object.save()
self.object.process()
return HttpResponseRedirect(self.request.META.get("HTTP_REFERER"))
class TrailGPXImportCreateView(
LoginRequiredMixin, JsonableResponseMixin, CreateView
):
class TrailGPXImportCreateView(LoginRequiredMixin, JsonableResponseMixin, CreateView):
model = TrailGPXImport
fields = ["gpx_file"]
template_name = "scrobbles/upload_form.html"
@ -852,7 +846,9 @@ def scrobble_start(request, uuid):
book=media_obj,
user_id=user_id,
)
.filter(Q(long_play_complete=False) | Q(long_play_complete__isnull=True))
.filter(
Q(long_play_complete=False) | Q(long_play_complete__isnull=True)
)
.filter(log__page_end__isnull=False)
.order_by("-timestamp")
.first()
@ -1328,6 +1324,7 @@ class ScrobbleCalendarView(LoginRequiredMixin, TemplateView):
def _day_color(self, month_index, day_num, total_days):
import colorsys
hue = (month_index - 1) * 30 / 360
lightness = 0.80 + (day_num / total_days) * 0.15
r, g, b = colorsys.hls_to_rgb(hue, lightness, 0.5)
@ -1355,7 +1352,9 @@ class ScrobbleCalendarView(LoginRequiredMixin, TemplateView):
if media_type_filter and media_type_filter in self.CALENDAR_MEDIA_TYPES:
active_types = [media_type_filter]
else:
active_types = [t for t in self.CALENDAR_MEDIA_TYPES if t not in self.DEFAULT_EXCLUDE]
active_types = [
t for t in self.CALENDAR_MEDIA_TYPES if t not in self.DEFAULT_EXCLUDE
]
scrobbles = (
Scrobble.objects.filter(
@ -1364,12 +1363,23 @@ class ScrobbleCalendarView(LoginRequiredMixin, TemplateView):
timestamp__date__lte=month_end,
media_type__in=active_types,
)
.select_related("task", "birding_location", "food", "trail", "video_game", "book", "mood", "video", "board_game")
.select_related(
"task",
"birding_location",
"food",
"trail",
"video_game",
"book",
"mood",
"video",
"board_game",
)
.order_by("timestamp")
)
from django.db.models import Count, Q
from django.db.models.functions import TruncDate
total_by_day = dict(
Scrobble.objects.filter(
user=self.request.user,
@ -1377,7 +1387,11 @@ class ScrobbleCalendarView(LoginRequiredMixin, TemplateView):
timestamp__date__lte=month_end,
)
.exclude(Q(media_type="GeoLocation") & Q(geo_location__title__isnull=True))
.annotate(local_date=TruncDate("timestamp", tzinfo=timezone.get_current_timezone()))
.annotate(
local_date=TruncDate(
"timestamp", tzinfo=timezone.get_current_timezone()
)
)
.values("local_date")
.annotate(count=Count("id"))
.values_list("local_date", "count")
@ -1403,17 +1417,27 @@ class ScrobbleCalendarView(LoginRequiredMixin, TemplateView):
{
"uuid": scrobble.uuid,
"emoji": self.MEDIA_EMOJI.get(scrobble.media_type, "📌"),
"title": str(scrobble.media_obj) if scrobble.media_obj else scrobble.media_type,
"title": (
str(scrobble.media_obj)
if scrobble.media_obj
else scrobble.media_type
),
"media_type": scrobble.media_type,
}
)
calendar_days.append({
"day": day_num,
"scrobbles": day_scrobbles,
"total_count": total_by_day.get(datetime(year, month, day_num).date(), 0),
"is_today": year == today.year and month == today.month and day_num == today.day,
"color": self._day_color(month, day_num, total_days),
})
calendar_days.append(
{
"day": day_num,
"scrobbles": day_scrobbles,
"total_count": total_by_day.get(
datetime(year, month, day_num).date(), 0
),
"is_today": year == today.year
and month == today.month
and day_num == today.day,
"color": self._day_color(month, day_num, total_days),
}
)
ctx.update(
{
@ -1430,7 +1454,10 @@ class ScrobbleCalendarView(LoginRequiredMixin, TemplateView):
"day_names": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
"month_color": month_color,
"active_filter": media_type_filter or "",
"media_types": [{"name": mt, "emoji": self.MEDIA_EMOJI.get(mt, "📌")} for mt in self.CALENDAR_MEDIA_TYPES],
"media_types": [
{"name": mt, "emoji": self.MEDIA_EMOJI.get(mt, "📌")}
for mt in self.CALENDAR_MEDIA_TYPES
],
}
)
return ctx
@ -1441,7 +1468,7 @@ class ScrobbleSearchView(LoginRequiredMixin, TemplateView):
MEDIA_FIELDS = {
"Video": ["video__title", "video__overview"],
"Track": ["track__title", "track__artist__name", "track__album__name"],
"Track": ["track__title", "track__artists__name", "track__album__name"],
"PodcastEpisode": ["podcast_episode__title", None],
"Book": ["book__title", "book__summary"],
"Paper": ["paper__title", None],