[food] Add calories per day

This commit is contained in:
2025-09-16 15:28:28 -04:00
parent 6306390f82
commit 0b4537b7ed
3 changed files with 48 additions and 9 deletions

View File

@ -1,15 +1,18 @@
import hashlib import hashlib
import logging import logging
import re import re
from datetime import datetime, timedelta from datetime import date, datetime, timedelta
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, Optional
from urllib.parse import urlparse from urllib.parse import urlparse
from zoneinfo import ZoneInfo from zoneinfo import ZoneInfo
import pendulum
import pytz import pytz
from django.apps import apps from django.apps import apps
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.db import models from django.db import models
from django.db.models.fields.json import KeyTextTransform
from django.db.models.functions import Cast, TruncDate
from django.utils import timezone from django.utils import timezone
from profiles.models import UserProfile from profiles.models import UserProfile
from profiles.utils import now_user_timezone from profiles.utils import now_user_timezone
@ -371,3 +374,37 @@ def fix_playback_position_seconds(*scrobbles: "Scrobble", commit=True) -> list["
scrobble.save(update_fields=["playback_position_seconds"]) scrobble.save(update_fields=["playback_position_seconds"])
return updated_scrobbles return updated_scrobbles
def base_scrobble_qs(user_id: int) -> models.QuerySet:
"""Base queryset with calories extracted as integer and day annotated."""
from scrobbles.models import Scrobble
return (
Scrobble.objects
.annotate(day=TruncDate("timestamp"))
.annotate(calories_int=Cast(KeyTextTransform("calories", "log"), models.IntegerField()))
.filter(calories_int__isnull=False, user_id=user_id)
)
def get_daily_calories_for_user_by_day(user_id: int, date: date| str) -> int:
"""Return total calories for a user on a specific day."""
if isinstance(date, str):
date = pendulum.parse(date)
qs = base_scrobble_qs(user_id).filter(day=date)
agg = qs.aggregate(total_calories=models.Sum("calories_int"))
return agg["total_calories"] or 0
def get_daily_calorie_dict_for_user(user_id: int) -> dict[date, int]:
"""Return {day: total_calories} for all days with scrobbles, in one query."""
qs = (
base_scrobble_qs(user_id)
.values("day")
.annotate(total_calories=models.Sum("calories_int"))
.order_by("day")
)
return {entry["day"]: entry["total_calories"] for entry in qs}

View File

@ -2,26 +2,27 @@ import calendar
import json import json
import logging import logging
from datetime import datetime, timedelta from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
from django.shortcuts import redirect
import pendulum import pendulum
import pytz import pytz
from dateutil.relativedelta import relativedelta
from django.apps import apps from django.apps import apps
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Count, Q, Max from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
from django.db.models import Count, Max, Q
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from django.http import FileResponse, HttpResponseRedirect, JsonResponse from django.http import FileResponse, HttpResponseRedirect, JsonResponse
from django.shortcuts import redirect
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.utils import timezone from django.utils import timezone
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.views.generic import DetailView, FormView, TemplateView from django.views.generic import DetailView, FormView, TemplateView
from django.views.generic.edit import CreateView from django.views.generic.edit import CreateView
from django.views.generic.list import ListView from django.views.generic.list import ListView
from moods.models import Mood
from music.aggregators import live_charts, scrobble_counts, week_of_scrobbles from music.aggregators import live_charts, scrobble_counts, week_of_scrobbles
from pendulum.parsing.exceptions import ParserError
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
from rest_framework import status from rest_framework import status
from rest_framework.decorators import ( from rest_framework.decorators import (
api_view, api_view,
@ -54,10 +55,10 @@ from scrobbles.tasks import (
process_tsv_import, process_tsv_import,
) )
from scrobbles.utils import ( from scrobbles.utils import (
get_daily_calories_for_user_by_day,
get_long_plays_completed, get_long_plays_completed,
get_long_plays_in_progress, get_long_plays_in_progress,
) )
from moods.models import Mood
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -146,7 +147,7 @@ class RecentScrobbleList(ListView):
if date_str: if date_str:
try: try:
date = pendulum.parse(date_str) date = pendulum.parse(date_str)
except: except ParserError:
pass pass
if date_str: if date_str:
if date_str == "this_week" or "-W" in date_str: if date_str == "this_week" or "-W" in date_str:
@ -230,6 +231,7 @@ class RecentScrobbleList(ListView):
user=self.request.user, user=self.request.user,
) )
data["counts"] = [] # scrobble_counts(user) data["counts"] = [] # scrobble_counts(user)
data["daily_calories"] = get_daily_calories_for_user_by_day(self.request.user.id, date)
else: else:
data["weekly_data"] = week_of_scrobbles() data["weekly_data"] = week_of_scrobbles()
data["counts"] = scrobble_counts() data["counts"] = scrobble_counts()

View File

@ -4,7 +4,7 @@
<div class="tab-pane fade show" id="latest-beers" role="tabpanel" <div class="tab-pane fade show" id="latest-beers" role="tabpanel"
aria-labelledby="latest-beers-tab"> aria-labelledby="latest-beers-tab">
<div class="table-responsive"> <div class="table-responsive">
{{count}} scrobble {% if time %}| {{time|natural_duration}}{% endif %} {{count}} scrobble {% if time %}| {{time|natural_duration}}{% endif %}{% if daily_calories %}| {{daily_calories}} calories{% endif %}
<table class="table table-striped table-sm"> <table class="table table-striped table-sm">
<thead> <thead>
<tr> <tr>