~cypheon/trakka

a14c69be685a87d332f954572143907fe63e2acb — Johann Rudloff 1 year, 2 months ago e30fe51 main
Implement saving health measurements
4 files changed, 50 insertions(+), 3 deletions(-)

M activities/jinja2/health.html
M activities/urls.py
M activities/views.py
M trakka/jinja2.py
M activities/jinja2/health.html => activities/jinja2/health.html +9 -1
@@ 1,7 1,7 @@
{% extends 'profile_main.html' %}

{% block profile %}
<form action="TODO:store_measurement" method="post" enctype="multipart/form-data">
<form action="{{ url('activities:health-measurement') }}" method="post" enctype="application/x-www-form-urlencoded">
{{ csrf_input }}
  <p>
    You can leave fields empty, only set fields will be stored.


@@ 27,6 27,14 @@
    <input class="w3" type="number" name="max_hr" min="1" max="1000" step="1">
    BPM
  </p>
  <p>
    <label for="measurement_date" class="db pv2">Date of measurement</label>
    <input class="" type="date" name="measurement_date" required
           value="{{ now() | isodate}}">
    <label for="measurement_time" class="dn">Time of measurement</label>
    <input class="" type="time" name="measurement_time" required
           value="{{ now() | isotime }}">
  </p>
  <input type=submit class="{{styles['action-button']}}" value="Add measurements">
</form>
{% endblock %}

M activities/urls.py => activities/urls.py +1 -0
@@ 8,6 8,7 @@ urlpatterns = [
    path('', views.index, name='index'),
    path('profile', views.profile, name='profile'),
    path('health', views.health, name='health'),
    path('health/measurement', views.health_measurement, name='health-measurement'),
    path('trigger/schedule', views.schedule, name='trigger-schedule'),
    path('upload', views.upload, name='upload'),
    path('activities', views.overview, name='overview'),

M activities/views.py => activities/views.py +27 -2
@@ 1,5 1,5 @@
from django.http import HttpResponse
from django.shortcuts import render
from django.shortcuts import redirect, render
from django.core.exceptions import RequestDataTooBig
from django.views.decorators.csrf import csrf_exempt



@@ 8,7 8,7 @@ from django.contrib.auth.models import User

from django import forms

from .models import Activity
from .models import Activity, HealthData
from .services import UPLOAD_MAX_SIZE, create_activity
from .tasks import process_update_activity
from . import graph


@@ 23,6 23,14 @@ class UploadFileForm(forms.Form):
    title = forms.CharField(max_length=1024, required=False)
    content = forms.FileField()

class HealthMeasurementForm(forms.Form):
    body_weight = forms.FloatField(required=False)
    body_height = forms.FloatField(required=False)
    min_hr = forms.IntegerField(required=False)
    max_hr = forms.IntegerField(required=False)
    measurement_date = forms.DateField()
    measurement_time = forms.TimeField()

@login_required
def index(request):
    return render(request, 'upload.html')


@@ 35,6 43,23 @@ def profile(request):
def health(request):
    return render(request, 'health.html')

def save_healthdata(user, time, typ, value):
    if value is not None:
        m = HealthData(user=user, measurement_ts=time, measurement_type=typ, measurement_value=value)
        m.save()

@login_required
def health_measurement(request):
    if request.method == 'POST':
        form = HealthMeasurementForm(request.POST)
        if form.is_valid():
            t = datetime.datetime.combine(form.cleaned_data['measurement_date'], form.cleaned_data['measurement_time'])
            save_healthdata(request.user, t, 'WEIGHT', form.cleaned_data.get('body_weight'))
            save_healthdata(request.user, t, 'HEIGHT', form.cleaned_data.get('body_height'))
            save_healthdata(request.user, t, 'RESTING_HR', form.cleaned_data.get('min_hr'))
            save_healthdata(request.user, t, 'MAX_HR', form.cleaned_data.get('max_hr'))
    return redirect('activities:health')

def schedule(request):
    unprocessed = Activity.objects.filter(status='PENDING').all()[:1000]
    for act in unprocessed:

M trakka/jinja2.py => trakka/jinja2.py +13 -0
@@ 29,6 29,16 @@ def distance(meters: Optional[float]):
        return '-'
    return f'{meters/1000:0.1f} km'

def isodate(d: Optional[dt.datetime]) -> str:
    if d is None:
        return '-'
    return d.strftime('%Y-%m-%d')

def isotime(d: Optional[dt.datetime]) -> str:
    if d is None:
        return '-'
    return d.strftime('%H:%M')

def date(d: Optional[dt.datetime]) -> str:
    if d is None:
        return '-'


@@ 47,6 57,7 @@ def optional_int(x: Optional[Any]) -> str:
def environment(**options):
    env = Environment(**options)
    env.globals.update({
        'now': dt.datetime.now,
        'static': static,
        'url': reverse,
        'styles': GLOBAL_STYLES,


@@ 56,6 67,8 @@ def environment(**options):
        'datetime': datetime,
        'distance': distance,
        'duration': duration,
        'isodate': isodate,
        'isotime': isotime,
        'optional_int': optional_int,
    })
    return env