From 69d3d1e46d2178caaa3ff95787c9ccadde557500 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 12 Feb 2021 16:46:44 +0100 Subject: [PATCH] understands interval string when defining workout --- requirements.txt | 2 +- rowers/c2stuff.py | 1 - rowers/models.py | 8 ++ rowers/plannedsessions.py | 176 +++++++++++++++++++++++++++++++++++++- rowers/views/planviews.py | 11 +++ 5 files changed, 195 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index ae7cf418..c09261fd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -180,7 +180,7 @@ ratelim==0.1.6 redis==3.5.3 requests==2.23.0 requests-oauthlib==1.2.0 -rowingdata==3.1.1 +rowingdata==3.1.3 rowingphysics==0.5.0 rq==0.13.0 rules==2.1 diff --git a/rowers/c2stuff.py b/rowers/c2stuff.py index 6b193151..88be1fe6 100644 --- a/rowers/c2stuff.py +++ b/rowers/c2stuff.py @@ -196,7 +196,6 @@ def get_c2_workouts(rower,do_async=True): knownc2ids = uniqify(knownc2ids+tombstones+parkedids) newids = [c2id for c2id in c2ids if not c2id in knownc2ids] - print(newids,'aap') newparkedids = uniqify(newids+parkedids) diff --git a/rowers/models.py b/rowers/models.py index d240abe3..8e5c2e28 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -2275,6 +2275,11 @@ class PlannedSession(models.Model): is_template = models.BooleanField(default=False) + fitfile = models.CharField(default=None,max_length=150,blank=True,null=True) + json_string = models.TextField(max_length=1000,default=None,blank=True,null=True) + interval_string = models.TextField(max_length=1000,default=None,blank=True,null=True, + verbose_name='Interval String (optional)') + def __str__(self): name = self.name @@ -2493,6 +2498,7 @@ class PlannedSessionForm(ModelForm): 'sessionunit', 'course', 'comment', + 'interval_string' ] dateTimeOptions = { @@ -2505,6 +2511,7 @@ class PlannedSessionForm(ModelForm): 'startdate': AdminDateWidget(), 'enddate': AdminDateWidget(), 'preferreddate': AdminDateWidget(), + 'interval_string': forms.Textarea(attrs={'rows':2,'cols':50}) } def __init__(self,*args,**kwargs): @@ -4330,6 +4337,7 @@ class PlannedSessionComment(models.Model): user = models.ForeignKey(User,on_delete=models.PROTECT) plannedsession = models.ForeignKey(PlannedSession,on_delete=models.CASCADE) + def __str__(self): return u'Comment to: {w} by {u1} {u2}'.format( w=self.workout, diff --git a/rowers/plannedsessions.py b/rowers/plannedsessions.py index 3ea7819a..ae06c6ed 100644 --- a/rowers/plannedsessions.py +++ b/rowers/plannedsessions.py @@ -16,11 +16,15 @@ from django.conf import settings import pytz from dateutil import parser from rowers.utils import myqueue,calculate_age,totaltime_sec_to_string + +from fitparse import FitFile + import re import django_rq queue = django_rq.get_queue('default') queuelow = django_rq.get_queue('low') queuehigh = django_rq.get_queue('low') +import json import pandas as pd @@ -77,6 +81,176 @@ from rowers.tasks import ( ) from rowers.utils import totaltime_sec_to_string +def step_to_string(step): + + target_dict = { + 'heart_rate': { + 'name':'Heart Rate between', + 'low':'custom_target_heart_rate_low', + 'high': 'custom_target_heart_rate_high' + }, + 'speed':{ + 'name': 'Speed between', + 'low': 'custom_target_speed_low', + 'high': 'custom_target_speed_high', + }, + 'cadence':{ + 'name': 'SPM between', + 'low': 'custom_target_cadence_low', + 'high': 'custom_target_cadence_high', + }, + 'open':{ + 'name': 'between', + 'low': 'custom_target_value_low', + 'high': 'custom_target_value_high', + }, + 'power':{ + 'name': 'Power between', + 'low': 'custom_target_power_low', + 'high': 'custom_target_power_high', + }, + 'stroke_type':{ + 'name': 'Stroke type', + 'low': 'target_swim_stroke', + 'high': '', + }, + } + + duration = '' + repeat = '' + target = '' + intensity = '' + nr = '' + name = '' + + for key, value in step.items(): + + if key == 'duration_distance': + duration = '{v} m'.format(v=value) + if key == 'duration_time': + dd = timedelta(seconds=value) + duration = '{v} min'.format(v=str(dd)) + if key in ['duration_hr']: + duration = 'until HR reaches {v}'.format(v=value) + if key == 'target_type' and value is not None: + dd = target_dict[value] + t = dd['name'] + l = '' + h = '' + if dd['low']: + v = step[dd['low']] + if value == 'heart_rate': + if v<100: + v = '{v} \%'.format(v=v) + else: + v = v-100 + v = '{v}'.format(v=v) + l = '{v}'.format(v=v) + if dd['high']: + v = step[dd['high']] + if value == 'heart_rate': + if v<100: + v = '{v} \%'.format(v=v) + else: + v = v-100 + v = '{v}'.format(v=v) + h = 'and {v}'.format(v=v) + + target = '{t} {l} {h}'.format( + t = t, + l = l, + h = h, + ) + if key == 'intensity': + intensity = '{v}'.format(v=value) + if key == 'repeat': + pass + if key == 'message_index': + nr = '{v}'.format(v=value) + if key == 'wkt_step_name': + name = '{v}'.format(v=value) + + s = '{nr}: {name} {intensity} {duration} {target} {repeat}'.format( + nr = nr, + name = name, + intensity = intensity, + duration = duration, + target=target, + repeat = repeat, + ) + + return s + +def ps_dict_get_description(d): + s = '' + steps = d['steps'] + for step in steps: + s += step_to_string(step) + s += '\n' + + return s + +class FitWorkoutSteps(object): + def __init__(self,readfile): + self.readfile = readfile + self.fitfile = FitFile(readfile, check_crc=False) + self.records = self.fitfile.messages + self.name = '' + self.sport = 'Custom' + # message 0 is file id + # message 1 must be workout description + m0 = self.records[0] + typ = m0.fields[0].value + if typ != 'workout': + raise TypeError("Wrong FIT file type") + + steps = [] + stepmessages = self.fitfile.get_messages('workout_step') + + for msg in stepmessages: + if msg.name == 'workout_step': + step = {} + for field in msg.fields: + ff = field.as_dict() + step[ff['name']] = ff['value'] + steps.append(step) + + for msg in self.fitfile.get_messages('workout'): + if msg.name == 'workout': + for field in msg.fields: + ff = field.as_dict() + if ff['name'] == 'wkt_name': + self.name = ff['value'] + if ff['name'] == 'sport': + self.sport = ff['value'] + + + self.steps = steps + + def to_json(self): + d = self.get_dict() + + return json.dumps(d) + + def get_dict(self): + d = {} + d['filename'] = self.readfile + d['name'] = self.name + d['steps'] = self.steps + d['sport'] = self.sport + + return d + + + def get_description(self): + s = '' + for step in self.steps: + s += step_to_string(step) + s += '\n' + + return s + + def checkscores(r,macrocycles): for m in macrocycles: createmesofillers(m) @@ -1474,7 +1648,7 @@ def default_class(r,w,race): boatclass = 'water' else: boatclass = 'rower' - + boattype = '1x' adaptiveclass = 'None' weightclass = 'hwt' diff --git a/rowers/views/planviews.py b/rowers/views/planviews.py index 85cf88ac..40197a4a 100644 --- a/rowers/views/planviews.py +++ b/rowers/views/planviews.py @@ -4,6 +4,7 @@ from __future__ import print_function from __future__ import unicode_literals from rowers.views.statements import * +from rowingdata import trainingparser @login_required @permission_required('plannedsession.view_session',fn=get_session_by_pk,raise_exception=True) @@ -438,6 +439,9 @@ def plannedsession_create_view(request, comment = cd['comment'] course = cd['course'] name = cd['name'] + interval_string = cd['interval_string'] + + if sessionunit == 'min': sessionmode = 'time' @@ -460,6 +464,13 @@ def plannedsession_create_view(request, ps.save() + if interval_string: + dct = trainingparser.parsetodict(interval_string) + dct = trainingparser.tofitdict(dct) + txt = ps_dict_get_description(dct) + ps.comment += '\n'+txt + ps.save() + add_rower_session(r,ps)