Files
vrobbler/vrobbler/apps/trends/views.py
Colin Powell 41a68291a4
All checks were successful
build / test (push) Successful in 2m22s
[trends] Allow disabling one or many or all trends
2026-06-25 18:58:23 -04:00

182 lines
6.1 KiB
Python

from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.views.generic import TemplateView, View
from trends.models import TrendResult
from trends.trends import TREND_REGISTRY
from trends.utils import get_disabled_trends, get_period_nav, get_supported_periods
TREND_METADATA = {
"activity-distribution": {
"title": "Activity Distribution",
"description": "How your scrobbles are divided across media types.",
"icon": "📊",
},
"concurrent-listening": {
"title": "Concurrent Listening",
"description": "What music were you listening to while on trails or at locations?",
"icon": "🎧",
},
"concurrent-reading": {
"title": "Concurrent Reading",
"description": "What music did you listen to while reading books?",
"icon": "📖",
},
"mood-trajectory": {
"title": "Mood Trajectory",
"description": "How your mood quality has changed over time.",
"icon": "📈",
},
"mood-by-time": {
"title": "Mood by Time",
"description": "How your mood varies by hour of day and day of week.",
"icon": "🕐",
},
"mood-distribution": {
"title": "Mood Distribution",
"description": "Which moods you feel most often.",
"icon": "🎭",
},
"mood-streaks": {
"title": "Mood Streaks",
"description": "Your longest runs of positive and negative moods.",
"icon": "🔥",
},
"mood-weather": {
"title": "Mood & Weather",
"description": "How weather conditions correlate with your mood.",
"icon": "🌤",
},
"peak-hours": {
"title": "Peak Activity Hours",
"description": "What time of day are you most active?",
"icon": "🕐",
},
"reading-pace-vs-activity": {
"title": "Reading Pace vs Music",
"description": "Compare how long you read per session with and without concurrent music.",
"icon": "📊",
},
"time-of-day-categories": {
"title": "Time of Day Categories",
"description": "Are you an early bird, day jay, or night owl? Categorized by Books, Trails, Birding Locations, and Board Games.",
"icon": "🦉",
},
"trending-up": {
"title": "Trending Media Types",
"description": "Which media types have you been consuming more or less of recently?",
"icon": "📈",
},
"weekly-rhythm": {
"title": "Weekly Rhythm",
"description": "Which days of the week see the most scrobble activity?",
"icon": "📅",
},
}
class TrendListView(LoginRequiredMixin, TemplateView):
template_name = "trends/trend_list.html"
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
disabled = get_disabled_trends(self.request.user)
results = TrendResult.objects.filter(
user=self.request.user,
).order_by("trend_slug", "-computed_at")
latest_by_slug = {}
for r in results:
if r.trend_slug not in latest_by_slug:
latest_by_slug[r.trend_slug] = r
trends = []
for slug in TREND_REGISTRY:
if slug in disabled:
continue
meta = TREND_METADATA.get(slug, {})
result = latest_by_slug.get(slug)
is_disabled = slug in (self.request.user.profile.disabled_trends or [])
trends.append(
{
"slug": slug,
"title": meta.get("title", slug),
"description": meta.get("description", ""),
"icon": meta.get("icon", ""),
"computed_at": result.computed_at if result else None,
"has_data": result is not None,
"disabled": is_disabled,
}
)
ctx["trends"] = trends
return ctx
class TrendDetailView(LoginRequiredMixin, TemplateView):
template_name = "trends/trend_detail.html"
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
slug = kwargs["trend_slug"]
if slug not in TREND_REGISTRY:
ctx["trend_not_found"] = True
return ctx
period = self.request.GET.get("period", "last_30")
meta = TREND_METADATA.get(slug, {})
ctx["trend"] = {
"slug": slug,
"title": meta.get("title", slug),
"description": meta.get("description", ""),
"icon": meta.get("icon", ""),
}
supported = get_supported_periods(slug)
ctx["supported_periods"] = supported
ctx["current_period"] = period
ctx["current_period_label"] = supported.get(period, "")
prev_period, next_period = get_period_nav(period, slug)
ctx["prev_period"] = prev_period
ctx["next_period"] = next_period
result = TrendResult.objects.filter(
user=self.request.user,
trend_slug=slug,
period=period,
).first()
if result:
ctx["computed_at"] = result.computed_at
ctx["data"] = result.data
else:
ctx["computed_at"] = None
ctx["data"] = None
disabled = get_disabled_trends(self.request.user)
ctx["trend_disabled"] = slug in disabled
ctx["globally_disabled"] = self.request.user.profile.trends_disabled
return ctx
class ToggleTrendDisabledView(LoginRequiredMixin, View):
def post(self, request, trend_slug):
profile = request.user.profile
disabled = set(profile.disabled_trends or [])
if trend_slug in disabled:
disabled.discard(trend_slug)
messages.success(request, f"Trend re-enabled.")
else:
disabled.add(trend_slug)
messages.success(request, f"Trend disabled.")
profile.disabled_trends = list(disabled)
profile.save(update_fields=["disabled_trends"])
return HttpResponseRedirect(
request.META.get("HTTP_REFERER", reverse("trends:trends-home"))
)