[moods] Add a nice form for checking in
All checks were successful
build & deploy / test (push) Successful in 2m2s
build & deploy / deploy (push) Successful in 38s

This commit is contained in:
2026-04-28 15:44:48 -04:00
parent e912eda6e4
commit ef4e510814
8 changed files with 173 additions and 1 deletions

View File

@ -1,5 +1,6 @@
import logging import logging
from dataclasses import dataclass from dataclasses import dataclass
from enum import IntEnum
from typing import Optional from typing import Optional
from uuid import uuid4 from uuid import uuid4
@ -21,10 +22,21 @@ class MoodType(models.TextChoices):
NEGATIVE = "negative", "Negative" NEGATIVE = "negative", "Negative"
class MoodQuality(IntEnum):
VERY_UNPLEASANT = 1
UNPLEASANT = 2
SLIGHTLY_UNPLEASANT = 3
NEUTRAL = 4
SLIGHTLY_PLEASANT = 5
PLEASANT = 6
VERY_PLEASANT = 7
@dataclass @dataclass
class MoodLogData(BaseLogData): class MoodLogData(BaseLogData):
mood_type: Optional[str] = None mood_type: Optional[str] = None
mood_reason_ids: Optional[list[int]] = None mood_reason_ids: Optional[list[int]] = None
mood_quality: Optional[int] = None
@classmethod @classmethod
def override_fields(cls) -> dict: def override_fields(cls) -> dict:
@ -42,6 +54,14 @@ class MoodLogData(BaseLogData):
required=False, required=False,
widget=forms.SelectMultiple(attrs={"size": 10}), widget=forms.SelectMultiple(attrs={"size": 10}),
), ),
"mood_quality": forms.ChoiceField(
choices=[
(mq.value, mq.name.replace("_", " ").title())
for mq in MoodQuality
],
required=False,
widget=forms.Select(attrs={"class": "form-control"}),
),
} }

View File

@ -0,0 +1,28 @@
{% extends "base.html" %}
{% block page_title %}Check-in - Select your mood{% endblock %}
{% block content %}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-8">
<h2 class="mb-4">Select your mood</h2>
<form method="post">
{% csrf_token %}
<div class="d-grid gap-3">
{% for mood in moods %}
<button type="submit" name="mood" value="{{ mood.id }}"
class="btn btn-outline-secondary btn-lg">
{% if mood.title %}{{ mood.title }}{% else %}{{ mood.uuid }}{% endif %}
{% if mood.description %}<br><small class="text-muted">{{ mood.description }}</small>{% endif %}
</button>
{% endfor %}
</div>
</form>
<div class="mt-3">
<a href="{% url 'moods:checkin' %}" class="btn btn-link">Back to quality selection</a>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,24 @@
{% extends "base.html" %}
{% block page_title %}Check-in - How are you feeling?{% endblock %}
{% block content %}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-8">
<h2 class="mb-4">How are you feeling?</h2>
<form method="post">
{% csrf_token %}
<div class="d-grid gap-3">
{% for quality in qualities %}
<button type="submit" name="mood_quality" value="{{ quality.value }}"
class="btn btn-outline-primary btn-lg">
{{ quality.name }}
</button>
{% endfor %}
</div>
</form>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,15 @@
{% extends "base.html" %}
{% block page_title %}Check-in Complete{% endblock %}
{% block content %}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-8 text-center">
<h2 class="mb-4">Check-in Complete!</h2>
<p class="lead">Your mood has been recorded.</p>
<a href="{% url 'vrobbler-home' %}" class="btn btn-primary">Back to Home</a>
</div>
</div>
</div>
{% endblock %}

View File

@ -11,4 +11,6 @@ urlpatterns = [
views.MoodDetailView.as_view(), views.MoodDetailView.as_view(),
name="mood_detail", name="mood_detail",
), ),
path("checkin/", views.checkin, name="checkin"),
path("checkin/success/", views.checkin_success, name="checkin_success"),
] ]

View File

@ -1,4 +1,83 @@
from moods.models import Mood import logging
from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect, render
from django.urls import reverse
from django.utils import timezone
from moods.models import Mood, MoodLogData, MoodQuality
from scrobbles.models import Scrobble
logger = logging.getLogger(__name__)
@login_required
def checkin(request):
"""Two-step mood check-in wizard"""
step = request.session.get("checkin_step", 1)
if request.method == "POST":
if step == 1:
quality = request.POST.get("mood_quality")
if quality:
request.session["checkin_quality"] = int(quality)
request.session["checkin_step"] = 2
return redirect("moods:checkin")
qualities = [
{"value": q.value, "name": q.name.replace("_", " ").title()}
for q in MoodQuality
]
return render(
request, "moods/checkin_quality.html", {"qualities": qualities}
)
elif step == 2:
mood_id = request.POST.get("mood")
quality = request.session.get("checkin_quality")
if mood_id and quality:
try:
mood = Mood.objects.get(id=mood_id)
log_data = MoodLogData(
mood_quality=quality,
mood_type=mood.mood_type,
)
scrobble = Scrobble.objects.create(
user=request.user,
mood=mood,
media_type=Scrobble.MediaType.MOOD,
timestamp=timezone.now(),
source="web_checkin",
log=log_data.asdict,
)
# Clear session data
request.session.pop("checkin_step", None)
request.session.pop("checkin_quality", None)
return redirect("moods:checkin_success")
except Mood.DoesNotExist:
logger.error(f"Mood with id {mood_id} not found")
return redirect("moods:checkin")
if step == 1:
qualities = [
{"value": q.value, "name": q.name.replace("_", " ").title()}
for q in MoodQuality
]
return render(
request, "moods/checkin_quality.html", {"qualities": qualities}
)
else:
moods = Mood.objects.all()
return render(request, "moods/checkin_mood.html", {"moods": moods})
@login_required
def checkin_success(request):
return render(request, "moods/checkin_success.html")
from scrobbles.views import ScrobbleableListView, ScrobbleableDetailView from scrobbles.views import ScrobbleableListView, ScrobbleableDetailView

View File

@ -15,6 +15,7 @@ AUTO_FINISH_MEDIA = {
"webpages": "WebPage", "webpages": "WebPage",
"tracks": "Track", "tracks": "Track",
"videos": "Video", "videos": "Video",
"moods": "Mood",
} }
PLAY_AGAIN_MEDIA = { PLAY_AGAIN_MEDIA = {

View File

@ -218,6 +218,9 @@
{% endif %} {% endif %}
{% if user.is_authenticated %} {% if user.is_authenticated %}
<div class="navbar-nav"> <div class="navbar-nav">
<div class="nav-item text-nowrap">
<a class="nav-link px-3" href="{% url "moods:checkin" %}">Check-in</a>
</div>
<div class="nav-item text-nowrap"> <div class="nav-item text-nowrap">
<a class="nav-link px-3" href="{% url "profiles:profile_settings" %}">Settings</a> <a class="nav-link px-3" href="{% url "profiles:profile_settings" %}">Settings</a>
</div> </div>