[templates] Add calendar view
All checks were successful
build & deploy / test (push) Successful in 1m52s
build & deploy / build-and-deploy (push) Successful in 35s

This commit is contained in:
2026-05-27 13:10:12 -04:00
parent fd95f1e686
commit 99789e5477
6 changed files with 408 additions and 9 deletions

View File

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

View File

@ -2,6 +2,7 @@ import calendar
import json
import logging
from datetime import datetime, timedelta
from uuid import uuid4
import pendulum
import pytz
@ -1281,6 +1282,129 @@ class EmbeddableTopArtistsWidget(TemplateView):
return context
class ScrobbleCalendarView(LoginRequiredMixin, TemplateView):
template_name = "scrobbles/calendar.html"
CALENDAR_MEDIA_TYPES = [
"Task",
"BirdingLocation",
"Food",
"Trail",
"VideoGame",
"Book",
"Mood",
]
MEDIA_EMOJI = {
"Task": "",
"BirdingLocation": "🐦",
"Food": "🍽️",
"Trail": "🥾",
"VideoGame": "🎮",
"Book": "📚",
"Mood": "🥰",
}
MONTH_COLORS = [
"#db7a7a", # Jan
"#db847a", # Feb
"#b0db7a", # Mar
"#7adb82", # Apr
"#7adbb3", # May
"#7ab6db", # Jun
"#7a8edb", # Jul
"#977adb", # Aug
"#c47adb", # Sep
"#db7ac5", # Oct
"#db7a90", # Nov
"#db7a7a", # Dec
]
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)
return f"#{int(r*255):02x}{int(g*255):02x}{int(b*255):02x}"
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
today = timezone.localdate()
date_param = self.request.GET.get("date", today.isoformat()[:7])
try:
year, month = [int(x) for x in date_param.split("-")]
except (ValueError, IndexError):
year, month = today.year, today.month
_, total_days = calendar.monthrange(year, month)
month_start = datetime(year, month, 1).date()
month_end = datetime(year, month, total_days).date()
first_weekday = month_start.weekday()
prev_month = month_start - timedelta(days=1)
next_month = month_end + timedelta(days=1)
scrobbles = (
Scrobble.objects.filter(
user=self.request.user,
timestamp__date__gte=month_start,
timestamp__date__lte=month_end,
media_type__in=self.CALENDAR_MEDIA_TYPES,
)
.select_related("task", "birding_location", "food", "trail", "video_game", "book", "mood")
.order_by("timestamp")
)
day_map = {d: [] for d in range(1, total_days + 1)}
for scrobble in scrobbles:
day_map[scrobble.timestamp.day].append(scrobble)
missing_uuids = [s for s in scrobbles if not s.uuid]
if missing_uuids:
for scrobble in missing_uuids:
scrobble.uuid = uuid4()
Scrobble.objects.bulk_update(missing_uuids, ["uuid"])
calendar_days = []
month_color = self.MONTH_COLORS[(month - 1) % 12]
for day_num in range(1, total_days + 1):
day_scrobbles = []
for scrobble in day_map[day_num]:
day_scrobbles.append(
{
"uuid": scrobble.uuid,
"emoji": self.MEDIA_EMOJI.get(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,
"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(
{
"year": year,
"month": month,
"month_name": month_start.strftime("%B"),
"total_days": total_days,
"first_weekday": first_weekday,
"calendar_days": calendar_days,
"prev_month": prev_month.isoformat()[:7],
"next_month": next_month.isoformat()[:7],
"today": today,
"current_month": today.isoformat()[:7],
"day_names": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
"month_color": month_color,
}
)
return ctx
class ScrobbleSearchView(LoginRequiredMixin, TemplateView):
template_name = "scrobbles/scrobble_search.html"