[food] Add calories per day
This commit is contained in:
@ -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}
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user