From b23b6051bd0056eb16e99c648ec1280cf18e579e Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 1 Dec 2020 17:09:00 +0100 Subject: [PATCH 01/15] different pic in PM menu --- rowers/templates/analysis.html | 4 +--- static/img/PM.jpg | Bin 0 -> 18668 bytes 2 files changed, 1 insertion(+), 3 deletions(-) create mode 100644 static/img/PM.jpg diff --git a/rowers/templates/analysis.html b/rowers/templates/analysis.html index 593b55ce..f9bfbb00 100644 --- a/rowers/templates/analysis.html +++ b/rowers/templates/analysis.html @@ -11,12 +11,11 @@ From 7bece0d2d3f3fc497d0dc64c2bded7b268f775f4 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 1 Dec 2020 19:27:22 +0100 Subject: [PATCH 03/15] typo --- rowers/templates/analysis.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rowers/templates/analysis.html b/rowers/templates/analysis.html index c9659aed..e5f11295 100644 --- a/rowers/templates/analysis.html +++ b/rowers/templates/analysis.html @@ -20,7 +20,7 @@

- Manager Fitness, Fatigue and Freshness + Manage Fitness, Fatigue and Freshness

  • From 808bd82ff54ff9319dba60de20ec2da59da58381 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 1 Dec 2020 19:51:09 +0100 Subject: [PATCH 04/15] officializing remove power --- rowers/templates/menu_workout.html | 5 +++ .../workout_remove_power_confirm.html | 41 +++++++++++++++++++ rowers/urls.py | 2 + rowers/views/workoutviews.py | 35 ++++++++++++++++ 4 files changed, 83 insertions(+) create mode 100644 rowers/templates/workout_remove_power_confirm.html diff --git a/rowers/templates/menu_workout.html b/rowers/templates/menu_workout.html index cf9e7c26..28f1292c 100644 --- a/rowers/templates/menu_workout.html +++ b/rowers/templates/menu_workout.html @@ -308,6 +308,11 @@  OTW Power
  • +
  • + +  Remove Power Data + +
  • {% if 'speedcoach2' in workout.workoutsource %}
  • diff --git a/rowers/templates/workout_remove_power_confirm.html b/rowers/templates/workout_remove_power_confirm.html new file mode 100644 index 00000000..a6b3b9e0 --- /dev/null +++ b/rowers/templates/workout_remove_power_confirm.html @@ -0,0 +1,41 @@ +{% extends "newbase.html" %} +{% load staticfiles %} +{% load rowerfilters %} + +{% block title %}Change Workout {% endblock %} + +{% block main %} +

    Delete Power?

    +
      +
    • +

      + This will delete the power data for the following workout: +

      + + + + + + + + + + + + +
      Name:{{ workout.name }}
      Date:{{ workout.date }}
      Time:{{ workout.starttime }}
      Distance:{{ workout.distance }}m
      Duration:{{ workout.duration |durationprint:"%H:%M:%S.%f" }}
      +
    • +
    • +
      + {% csrf_token %} + +
      +
    • +
    + + +{% endblock %} + +{% block sidebar %} +{% include 'menu_workout.html' %} +{% endblock %} diff --git a/rowers/urls.py b/rowers/urls.py index 565dde1c..23f431d9 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -437,6 +437,8 @@ urlpatterns = [ re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/stats/$',views.workout_stats_view,name='workout_stats_view'), re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/data/$',views.workout_data_view, name='workout_data_view'), + re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/zeropower-confirm/$',views.remove_power_confirm_view, + name='remove_power_confirm_view'), re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/zeropower/$',views.remove_power_view, name='remove_power_view'), re_path(r'^workout/(?P\b[0-9A-Fa-f]+\b)/otwsetpower/$',views.workout_otwsetpower_view,name='workout_otwsetpower_view'), diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index 1bd74872..19d0deb6 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -1274,6 +1274,36 @@ def workouts_join_select(request, 'teams':get_my_teams(request.user), }) +@login_required() +def remove_power_confirm_view(request,id=0): + r = getrower(request.user) + workout = get_workout_by_opaqueid(request,id) + + breadcrumbs = [ + { + 'url':'/rowers/list-workouts/', + 'name':'Workouts' + }, + { + 'url':get_workout_default_page(request,encoder.encode_hex(workout.id)), + 'name': encoder.encode_hex(workout.id) + }, + { 'url':reverse('remove_power_confirm_view', + kwargs={'id':encoder.encode_hex(workout.id)}), + 'name': 'Delete' + } + + ] + + return render(request, + 'workout_remove_power_confirm.html', + { + 'workout':workout, + 'rower':r, + 'breadcrumbs':breadcrumbs, + }) + + @login_required() def remove_power_view(request,id=0): r = getrower(request.user) @@ -1299,6 +1329,11 @@ def remove_power_view(request,id=0): res = dataprep.dataprep(row.df, id=workout.id) cpdf,delta,cpvalues = dataprep.setcp(workout) + workout.normp = 0 + workout.rscore = 0 + workout.save() + + dataprep.initiate_cp(r) From 423d6513dec0a8057aee6ef1a0c983bedd395d86 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 1 Dec 2020 20:05:15 +0100 Subject: [PATCH 05/15] left menu --- rowers/templates/menu_analytics.html | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/rowers/templates/menu_analytics.html b/rowers/templates/menu_analytics.html index 28cc9eb4..021950b7 100644 --- a/rowers/templates/menu_analytics.html +++ b/rowers/templates/menu_analytics.html @@ -26,6 +26,16 @@  Fitness
    diff --git a/rowers/templates/rower_preferences.html b/rowers/templates/rower_preferences.html index d15b1e69..434336f4 100644 --- a/rowers/templates/rower_preferences.html +++ b/rowers/templates/rower_preferences.html @@ -99,11 +99,14 @@
  • -

    Range over which Critical Power rolling data (fitness) are calculated

    -

    Use this form to change the number of weeks over which Rowsandall - keeps track of your Critical Power.

    -

    A shorter range will give you notifications of fitness improvements more often, - but will also quickly forget those breakthrough workouts.

    +

    Fitness and Performance Manager Settings

    +

    Use this form to change the parameters affecting your performance management.

    +

    A shorter range for the CP calculations + will give you notifications of fitness improvements more often, + but will also quickly forget those breakthrough workouts. Shorter decay + time constants for the performance manager will model a quicker building up +of fitness and a faster recovery. Recommended values are 42 days (fitness) +and 7 days (fatigue).

    {{ cpform.as_table }}
    diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index 64c86fb3..23ebbf00 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -1555,14 +1555,14 @@ def performancemanager_view(request,userid=0,mode='rower', therower = getrequestrower(request,userid=userid) theuser = therower.user - kfitness = 42 - kfatigue = 7 + kfitness = therower.kfit + kfatigue = therower.kfatigue fitnesstest = 20 metricchoice = 'trimp' modelchoice = 'tsb' usefitscore = False - doform = False - dofatigue = False + doform = therower.showfresh + dofatigue = therower.showfit if request.method == 'POST': form = PerformanceManagerForm(request.POST) @@ -1572,6 +1572,9 @@ def performancemanager_view(request,userid=0,mode='rower', metricchoice = form.cleaned_data['metricchoice'] dofatigue = form.cleaned_data['dofatigue'] doform = form.cleaned_data['doform'] + therower.showfresh = doform + therower.showfatigue = dofatigue + therower.save() else: form = PerformanceManagerForm() @@ -1600,7 +1603,7 @@ def performancemanager_view(request,userid=0,mode='rower', 'script':script, 'div':thediv, }) - + return(HttpResponse(response,content_type='application/json')) diff --git a/rowers/views/userviews.py b/rowers/views/userviews.py index afa15a28..51d2fc4c 100644 --- a/rowers/views/userviews.py +++ b/rowers/views/userviews.py @@ -570,9 +570,13 @@ def rower_prefs_view(request,userid=0,message=""): if cpform.is_valid(): cd = cpform.cleaned_data cprange = cd['cprange'] + kfit = cd['kfit'] + kfatigue = cd['kfatigue'] r.cprange = cprange + r.kfit = kfit + r.kfatigue = kfatigue r.save() - messages.info(request,'Updated CP range value') + messages.info(request,'Updated CP range and time decay constants') success = dataprep.update_rolling_cp(r,mytypes.otwtypes,'water') success = dataprep.update_rolling_cp(r,mytypes.otetypes,'erg') From c909c401dcb8620792eea02a9f468e9327ed9a76 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 2 Dec 2020 15:17:37 +0100 Subject: [PATCH 09/15] adding rpe and goldmedalstandard --- rowers/interactiveplots.py | 4 ++-- rowers/models.py | 16 ++++++++++++++++ rowers/views/workoutviews.py | 4 ++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index ccec5fec..d288ce0f 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -1656,7 +1656,7 @@ def getfatigues( weight = 0 for w in ws: weight += factor*getattr(w,metricchoice) - if getattr(w,metricchoice) == 0: + if getattr(w,metricchoice) <= 0: if metricchoice == 'rscore' and w.hrtss != 0: weight+= factor*w.hrtss else: @@ -1866,7 +1866,7 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, toolbar_location=None, toolbar_sticky=False) - + plot2.x_range = xrange plot2.y_range = Range1d(0,df['impulse'].max()) diff --git a/rowers/models.py b/rowers/models.py index 1e038f9a..0ef6dc5e 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -2904,6 +2904,15 @@ class Workout(models.Model): privacychoices = mytypes.privacychoices adaptivetypes = mytypes.adaptivetypes boatbrands = mytypes.boatbrands + rpechoices = ( + (1,'Very Easy (reading a book, watching television)'), # 20 TSS / hour + (2,'Easy (talk, breathe normally, feels comfortable)'), # 30 TSS / hour + (4,'Moderate (talk in short spurts, breathing more labored, in comfort zone but working)'), # 50 TSS/hour + (6,'Somewhat Hard'), # 70 TSS / hour + (8,'Hard (could barely talk, breathing heavily)'), # 100 TSS / hour + (9,'Very Hard (outside comfort zone)'), # 120 TSS / hour + (10,'Max Effort (could barely remember your name)') # 140 TSS / hour + ) user = models.ForeignKey(Rower,on_delete=models.CASCADE) team = models.ManyToManyField(Team,blank=True) @@ -2931,12 +2940,18 @@ class Workout(models.Model): distance = models.IntegerField(default=0,blank=True) duration = models.TimeField(blank=True) dragfactor = models.IntegerField(default=0,blank=True) + + # scores trimp = models.IntegerField(default=-1,blank=True) rscore = models.IntegerField(default=-1,blank=True) hrtss = models.IntegerField(default=-1,blank=True) normp = models.IntegerField(default=-1,blank=True) normv = models.FloatField(default=-1,blank=True) normw = models.FloatField(default=-1,blank=True) + goldmedalstandard = models.FloatField(default=-1,blank=True) + rpe = models.IntegerField(default=0,blank=True,choices=rpechoices, + verbose_name='Rate of Perceived Exertion') + weightcategory = models.CharField( default="hwt", max_length=10, @@ -3546,6 +3561,7 @@ class WorkoutForm(ModelForm): 'dragfactor', 'weightcategory', 'adaptiveclass', + 'rpe', 'notes', 'rankingpiece', 'duplicate', diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index 19d0deb6..ac67ae44 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -4355,6 +4355,9 @@ def workout_edit_view(request,id=0,message="",successmessage=""): notes = form.cleaned_data['notes'] newdragfactor = form.cleaned_data['dragfactor'] thetimezone = form.cleaned_data['timezone'] + rpe = form.cleaned_data['rpe'] + if not rpe: + rpe = -1 try: ps = form.cleaned_data['plannedsession'] @@ -4421,6 +4424,7 @@ def workout_edit_view(request,id=0,message="",successmessage=""): row.weightcategory = weightcategory row.adaptiveclass = adaptiveclass row.notes = notes + row.rpe = rpe row.duration = duration row.distance = distance row.boattype = boattype From b6785c9376ccf944da74cbaae5161541bbb93ac9 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 2 Dec 2020 15:38:50 +0100 Subject: [PATCH 10/15] using rpe to calculate trimp/tss --- rowers/datautils.py | 14 ++++++++++++++ rowers/interactiveplots.py | 17 ++++++++++++++--- rowers/models.py | 2 +- rowers/views/workoutviews.py | 4 ++++ 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/rowers/datautils.py b/rowers/datautils.py index 7f8c1af4..c19e9b6b 100644 --- a/rowers/datautils.py +++ b/rowers/datautils.py @@ -13,6 +13,20 @@ from rowers.mytypes import otwtypes,otetypes,rowtypes #p0 = [500,350,10,8000] p0 = [190,200,33,16000] +# RPE to TSS +rpetotss = { + 1:20, + 2:30, + 3:40, + 4:50, + 5:60, + 6:70, + 7:80, + 8:100, + 9:120, + 10:140, +} + def updatecp(delta,cpvalues,r,workouttype='water'): if workouttype in otwtypes: p0 = r.p0 diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index d288ce0f..7e14edea 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -97,7 +97,7 @@ import rowers.c2stuff as c2stuff from rowers.metrics import axes,axlabels,yaxminima,yaxmaxima,get_yaxminima,get_yaxmaxima from rowers.utils import lbstoN -from rowers.datautils import p0 +from rowers.datautils import p0,rpetotss import rowers.datautils as datautils from pandas.core.groupby.groupby import DataError @@ -1650,18 +1650,29 @@ def getfatigues( if metricchoice == 'rscore': factor = 2.0 - for i in range(nrdays): + for i in range(nrdays+1): date = startdate+datetime.timedelta(days=i) ws = Workout.objects.filter(user=user.rower,date=date,duplicate=False) weight = 0 for w in ws: weight += factor*getattr(w,metricchoice) if getattr(w,metricchoice) <= 0: - if metricchoice == 'rscore' and w.hrtss != 0: + if metricchoice == 'rscore' and w.hrtss > 0: weight+= factor*w.hrtss else: trimp,hrtss = dataprep.workout_trimp(w) rscore,normp = dataprep.workout_rscore(w) + if w.rpe and w.rpe > 0: + dd = 3600*w.duration.hour+60*w.duration.minute+w.duration.second + dd = dd/3600 + weight += factor*rpetotss[w.rpe]*dd + if metricchoice == 'trimp' and w.trimp <= 0: + trimp,hrtss = dataprep.workout_trimp(w) + rscore,normp = dataprep.workout_rscore(w) + if w.rpe and w.rpe > 0: + dd = 3600*w.duration.hour+60*w.duration.minute+w.duration.second + dd = dd/3600 + weight += factor*rpetotss[w.rpe]*dd impulses.append(weight) diff --git a/rowers/models.py b/rowers/models.py index 0ef6dc5e..b7354d88 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -2948,7 +2948,7 @@ class Workout(models.Model): normp = models.IntegerField(default=-1,blank=True) normv = models.FloatField(default=-1,blank=True) normw = models.FloatField(default=-1,blank=True) - goldmedalstandard = models.FloatField(default=-1,blank=True) + goldmedalstandard = models.FloatField(default=-1,blank=True,verbose_name='Gold Medal Standard') rpe = models.IntegerField(default=0,blank=True,choices=rpechoices, verbose_name='Rate of Perceived Exertion') diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index ac67ae44..093a32b5 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -585,12 +585,16 @@ def addmanual_view(request,raceid=0): weightcategory = form.cleaned_data['weightcategory'] adaptiveclass = form.cleaned_data['adaptiveclass'] distance = form.cleaned_data['distance'] + rpe = form.cleaned_data['rpe'] + if not rpe: + rpe = -1 notes = form.cleaned_data['notes'] thetimezone = form.cleaned_data['timezone'] private = form.cleaned_data['private'] avghr = metricsform.cleaned_data['avghr'] avgpwr = metricsform.cleaned_data['avgpwr'] avgspm = metricsform.cleaned_data['avgspm'] + try: ps = form.cleaned_data['plannedsession'] except KeyError: From 78c6bfb34580519ef77092ec73ee94b32f53c67e Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 2 Dec 2020 15:54:46 +0100 Subject: [PATCH 11/15] Manual Entry working --- rowers/dataprep.py | 5 ++++- rowers/interactiveplots.py | 9 +++++---- rowers/views/workoutviews.py | 2 ++ 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 0ea6635a..73928eaa 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -1242,7 +1242,7 @@ def fetchcp(rower,theworkouts,table='cpdata'): def create_row_df(r,distance,duration,startdatetime,workouttype='rower', avghr=None,avgpwr=None,avgspm=None, rankingpiece = False, - duplicate=False, + duplicate=False,rpe=-1, title='Manual entry',notes='',weightcategory='hwt', adaptiveclass='None'): @@ -1351,6 +1351,7 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower', workoutsource='unknown', notes='', totaldist=0, totaltime=0, rankingpiece=False, + rpe=-1, duplicate=False, summary='', makeprivate=False, @@ -1571,6 +1572,7 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower', if title is not None and len(title)>140: title = title[0:140] + w = Workout(user=r, name=title, date=workoutdate, workouttype=workouttype, boattype=boattype, @@ -1583,6 +1585,7 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower', workoutsource=workoutsource, rankingpiece=rankingpiece, forceunit=forceunit, + rpe=rpe, csvfilename=f2, notes=notes, summary=summary, maxhr=maxhr, averagehr=averagehr, startdatetime=workoutstartdatetime, diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 7e14edea..3c3f7267 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -1655,24 +1655,25 @@ def getfatigues( ws = Workout.objects.filter(user=user.rower,date=date,duplicate=False) weight = 0 for w in ws: - weight += factor*getattr(w,metricchoice) + if getattr(w,metricchoice) > 0: + weight += factor*getattr(w,metricchoice) if getattr(w,metricchoice) <= 0: if metricchoice == 'rscore' and w.hrtss > 0: weight+= factor*w.hrtss - else: + elif metricchoice == 'rscore' and w.hrtss <= 0: trimp,hrtss = dataprep.workout_trimp(w) rscore,normp = dataprep.workout_rscore(w) if w.rpe and w.rpe > 0: dd = 3600*w.duration.hour+60*w.duration.minute+w.duration.second dd = dd/3600 weight += factor*rpetotss[w.rpe]*dd - if metricchoice == 'trimp' and w.trimp <= 0: + elif metricchoice == 'trimp' and w.trimp <= 0: trimp,hrtss = dataprep.workout_trimp(w) rscore,normp = dataprep.workout_rscore(w) if w.rpe and w.rpe > 0: dd = 3600*w.duration.hour+60*w.duration.minute+w.duration.second dd = dd/3600 - weight += factor*rpetotss[w.rpe]*dd + weight += 2*rpetotss[w.rpe]*dd impulses.append(weight) diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index 093a32b5..6373b10c 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -636,6 +636,7 @@ def addmanual_view(request,raceid=0): id,message = dataprep.create_row_df(r, distance, duration,startdatetime, + rpe=rpe, weightcategory=weightcategory, adaptiveclass=adaptiveclass, avghr=avghr, @@ -661,6 +662,7 @@ def addmanual_view(request,raceid=0): w.notes = notes w.plannedsession = ps w.name = name + w.rpe = rpe w.workouttype = workouttype w.boattype = boattype w.save() From edb080f9b2711884954d3f73cf712acbab1714ed Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 2 Dec 2020 18:47:17 +0100 Subject: [PATCH 12/15] should pass tests now --- rowers/dataprep.py | 3 +- rowers/forms.py | 8 +++- rowers/tests/test_cpchart.py | 1 + rowers/tests/test_emails.py | 42 +++++++++++---------- rowers/tests/test_settings.py | 1 + rowers/tests/test_units.py | 3 ++ rowers/tests/test_uploads.py | 69 +++++++++++++++++++++++------------ rowers/tests/test_urls.py | 8 +++- rowers/views/workoutviews.py | 32 ++++++++++++++-- 9 files changed, 116 insertions(+), 51 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 73928eaa..1f6a27ba 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -1572,7 +1572,6 @@ def save_workout_database(f2, r, dosmooth=True, workouttype='rower', if title is not None and len(title)>140: title = title[0:140] - w = Workout(user=r, name=title, date=workoutdate, workouttype=workouttype, boattype=boattype, @@ -1813,6 +1812,7 @@ def new_workout_from_file(r, f2, workoutsource=None, title='Workout', boattype='1x', + rpe=-1, makeprivate=False, notes='', uploadoptions={'boattype':'1x','workouttype':'rower'}): @@ -1947,6 +1947,7 @@ def new_workout_from_file(r, f2, dosummary=dosummary, workoutsource=workoutsource, summary=summary, + rpe=rpe, inboard=inboard, oarlength=oarlength, title=title, forceunit='N', diff --git a/rowers/forms.py b/rowers/forms.py index 26defb73..6d074114 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -245,17 +245,23 @@ class StandardsForm(forms.Form): # The form used for uploading files class DocumentsForm(forms.Form): + rpechoices = Workout.rpechoices + rpechoices = tuple([(-1,'---')]+list(rpechoices)) title = forms.CharField(required=False) file = forms.FileField(required=False, validators=[validate_file_extension]) workouttype = forms.ChoiceField(required=True, - choices=Workout.workouttypes) + choices=Workout.workouttypes, + label='Workout Type') boattype = forms.ChoiceField(required=True, choices=mytypes.boattypes, label = "Boat Type") + rpe = forms.ChoiceField(required=False, + choices=rpechoices, + label='Rate of Perceived Exertion',initial=-1) notes = forms.CharField(required=False, widget=forms.Textarea) diff --git a/rowers/tests/test_cpchart.py b/rowers/tests/test_cpchart.py index 4ab3a2bb..020654c4 100644 --- a/rowers/tests/test_cpchart.py +++ b/rowers/tests/test_cpchart.py @@ -195,6 +195,7 @@ class CPChartTest(TestCase): 'duplicate': False, 'avghr': '160', 'avgpwr': 0, + 'rpe':4, 'avgspm': 40, } diff --git a/rowers/tests/test_emails.py b/rowers/tests/test_emails.py index eca8a37c..7edbd7e1 100644 --- a/rowers/tests/test_emails.py +++ b/rowers/tests/test_emails.py @@ -4,6 +4,7 @@ from __future__ import print_function from __future__ import unicode_literals #from __future__ import print_function from .statements import * +from django.db import transaction @override_settings(TESTING=True) class EmailUpload(TestCase): @@ -62,6 +63,7 @@ workout run 'workouttype':'rower', 'boattype': '1x', 'notes': 'aap noot mies', + 'rpe':1, 'make_plot': False, 'upload_to_C2': False, 'plottype': 'timeplot', @@ -84,27 +86,29 @@ workout run @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) def test_uploadapi2(self,mocked_sqlalchemy,mocked_getsmallrowdata_db): - form_data = { - 'title': 'test', - 'workouttype':'rower', - 'boattype': '1x', - 'notes': 'aap noot mies', - 'make_plot': False, - 'upload_to_C2': False, - 'plottype': 'timeplot', - 'file': 'media/mailbox_attachments/colin3.csv', - 'secret': settings.UPLOAD_SERVICE_SECRET, - 'useremail': 'sander2@ds.nl', - } + with transaction.atomic(): + form_data = { + 'title': 'test', + 'workouttype':'rower', + 'boattype': '1x', + 'notes': 'aap noot mies', + 'make_plot': False, + 'upload_to_C2': False, + 'plottype': 'timeplot', + 'rpe':4, + 'file': 'media/mailbox_attachments/colin3.csv', + 'secret': settings.UPLOAD_SERVICE_SECRET, + 'useremail': 'sander2@ds.nl', + } - url = reverse('workout_upload_api') - response = self.c.post(url,form_data,HTTP_HOST='127.0.0.1:4533') - self.assertEqual(response.status_code,200) + url = reverse('workout_upload_api') + response = self.c.post(url,form_data,HTTP_HOST='127.0.0.1:4533') + self.assertEqual(response.status_code,200) - # should also test if workout is created - w = Workout.objects.get(id=1) - self.assertEqual(w.name,'test') - self.assertEqual(w.notes,'aap noot mies') + # should also test if workout is created + w = Workout.objects.get(id=1) + self.assertEqual(w.name,'test') + self.assertEqual(w.notes,'aap noot mies') @patch('rowers.dataprep.create_engine') @patch('rowers.dataprep.getsmallrowdata_db',side_effect=mocked_getsmallrowdata_db) diff --git a/rowers/tests/test_settings.py b/rowers/tests/test_settings.py index b9a44653..87b440fb 100644 --- a/rowers/tests/test_settings.py +++ b/rowers/tests/test_settings.py @@ -32,6 +32,7 @@ class DataTest(TestCase): 'weightcategory':'lwt', 'adaptiveclass': 'PR1', 'workouttype':'water', + 'rpe':1, 'boattype':'1x', 'private':False, } diff --git a/rowers/tests/test_units.py b/rowers/tests/test_units.py index 77827338..df9658cc 100644 --- a/rowers/tests/test_units.py +++ b/rowers/tests/test_units.py @@ -55,6 +55,7 @@ class ForceUnits(TestCase): 'make_plot':False, 'upload_to_c2':False, 'plottype':'timeplot', + 'rpe': 1, 'file': f, } @@ -100,6 +101,7 @@ class ForceUnits(TestCase): 'boattype':'1x', 'notes':'aap noot mies', 'make_plot':False, + 'rpe': 1, 'upload_to_c2':False, 'plottype':'timeplot', 'file': f, @@ -131,6 +133,7 @@ class ForceUnits(TestCase): file_data = {'file': f} form_data = { 'title':'test', + 'rpe':1, 'workouttype':'rower', 'boattype':'1x', 'notes':'aap noot mies', diff --git a/rowers/tests/test_uploads.py b/rowers/tests/test_uploads.py index 21e03df9..d9fbbac5 100644 --- a/rowers/tests/test_uploads.py +++ b/rowers/tests/test_uploads.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals #from __future__ import print_function from .statements import * nu = datetime.datetime.now() +from django.db import transaction from rowers.views import add_defaultfavorites @@ -50,7 +51,9 @@ class ViewTest(TestCase): 'workouttype':'rower', 'boattype':'1x', 'notes':'aap noot mies', + 'rpe':4, 'make_plot':False, + 'rpe':6, 'upload_to_c2':False, 'plottype':'timeplot', 'file': f, @@ -100,6 +103,7 @@ class ViewTest(TestCase): 'adaptiveclass':'PR1', 'workouttype':'rower', 'boattype':'1x', + 'rpe':4, 'dragfactor':'112', 'private':True, 'notes':'noot mies', @@ -141,6 +145,7 @@ class ViewTest(TestCase): 'workouttype':'rower', 'boattype':'1x', 'notes':'aap noot mies', + 'rpe':6, 'make_plot':False, 'upload_to_C2':False, 'upload_to_Strava':False, @@ -193,6 +198,7 @@ class ViewTest(TestCase): 'upload_to_c2':False, 'plottype':'timeplot', 'file': f, + 'rpe':6, } form = DocumentsForm(form_data,file_data) @@ -229,6 +235,7 @@ class ViewTest(TestCase): 'upload_to_c2':False, 'plottype':'timeplot', 'file': f, + 'rpe':6, } form = DocumentsForm(form_data,file_data) @@ -263,6 +270,7 @@ class ViewTest(TestCase): 'upload_to_c2':False, 'plottype':'timeplot', 'file': f, + 'rpe':6, } form = DocumentsForm(form_data,file_data) @@ -313,6 +321,7 @@ class ViewTest(TestCase): 'upload_to_c2':False, 'plottype':'timeplot', 'file': f, + 'rpe':6, } form = DocumentsForm(form_data,file_data) @@ -350,6 +359,7 @@ class ViewTest(TestCase): 'upload_to_c2':False, 'plottype':'timeplot', 'file': f, + 'rpe':6, } form = DocumentsForm(form_data,file_data) @@ -389,6 +399,7 @@ class ViewTest(TestCase): 'upload_to_c2':False, 'plottype':'timeplot', 'file': f, + 'rpe':6, } form = DocumentsForm(form_data,file_data) @@ -424,6 +435,7 @@ class ViewTest(TestCase): 'make_plot':False, 'upload_to_c2':False, 'plottype':'timeplot', + 'rpe':1, 'file': f, } @@ -459,6 +471,7 @@ class ViewTest(TestCase): 'boattype':'1x', 'notes':'aap noot mies', 'make_plot':False, + 'rpe':1, 'upload_to_c2':False, 'plottype':'timeplot', 'file': f, @@ -531,6 +544,7 @@ class ViewTest(TestCase): file_data = {'file': f} form_data = { 'title':'test', + 'rpe':1, 'workouttype':'water', 'boattype':'1x', 'notes':'aap noot mies', @@ -570,6 +584,7 @@ class ViewTest(TestCase): 'make_plot':False, 'upload_to_c2':False, 'plottype':'timeplot', + 'rpe':4, 'file': f, } @@ -623,35 +638,37 @@ class ViewTest(TestCase): @patch('rowers.dataprep.create_engine') def test_upload_view_RP_interval(self, mocked_sqlalchemy): - self.c.login(username='john',password='koeinsloot') + with transaction.atomic(): + self.c.login(username='john',password='koeinsloot') - filename = 'rowers/tests/testdata/RP_interval.csv' - f = open(filename,'rb') - file_data = {'file': f} - form_data = { - 'title':'test', - 'workouttype':'rower', - 'boattype':'1x', - 'notes':'aap noot mies', - 'make_plot':False, - 'upload_to_c2':False, - 'plottype':'timeplot', - 'file': f, - } + filename = 'rowers/tests/testdata/RP_interval.csv' + f = open(filename,'rb') + file_data = {'file': f} + form_data = { + 'title':'test', + 'workouttype':'rower', + 'boattype':'1x', + 'notes':'aap noot mies', + 'make_plot':False, + 'upload_to_c2':False, + 'plottype':'timeplot', + 'rpe':1, + 'file': f, + } - form = DocumentsForm(form_data,file_data) + form = DocumentsForm(form_data,file_data) - response = self.c.post('/rowers/workout/upload/', form_data, follow=True) - self.assertRedirects(response, expected_url='/rowers/workout/'+encoded1+'/edit/', + response = self.c.post('/rowers/workout/upload/', form_data, follow=True) + self.assertRedirects(response, expected_url='/rowers/workout/'+encoded1+'/edit/', status_code=302,target_status_code=200) - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, 200) - w = Workout.objects.get(id=1) - f_to_be_deleted = w.csvfilename - try: - os.remove(f_to_be_deleted+'.gz') - except (FileNotFoundError,OSError): - pass + w = Workout.objects.get(id=1) + f_to_be_deleted = w.csvfilename + try: + os.remove(f_to_be_deleted+'.gz') + except (FileNotFoundError,OSError): + pass @@ -667,6 +684,7 @@ class ViewTest(TestCase): 'workouttype':'rower', 'boattype':'1x', 'notes':'aap noot mies', + 'rpe':4, 'make_plot':False, 'upload_to_c2':False, 'plottype':'timeplot', @@ -699,6 +717,7 @@ class ViewTest(TestCase): 'workouttype':'rower', 'boattype':'1x', 'notes':'aap noot mies', + 'rpe':4, 'make_plot':False, 'upload_to_c2':False, 'plottype':'timeplot', @@ -731,6 +750,7 @@ class ViewTest(TestCase): 'workouttype':'rower', 'boattype':'1x', 'notes':'aap noot mies', + 'rpe':4, 'make_plot':False, 'upload_to_c2':False, 'plottype':'timeplot', @@ -762,6 +782,7 @@ class ViewTest(TestCase): 'title':'test', 'workouttype':'rower', 'boattype':'1x', + 'rpe':4, 'notes':'aap noot mies', 'make_plot':False, 'upload_to_c2':False, diff --git a/rowers/tests/test_urls.py b/rowers/tests/test_urls.py index e3be6bac..812816ee 100644 --- a/rowers/tests/test_urls.py +++ b/rowers/tests/test_urls.py @@ -10,7 +10,9 @@ nu = datetime.datetime.now() tested = [ - '/rowers/me/delete/' + '/rowers/me/delete/', + '/rowers/performancemanager/' + ] #@pytest.mark.django_db @@ -76,7 +78,7 @@ class URLTests(TestCase): '/rowers/agegroupcp/30/1/', '/rowers/agegrouprecords/male/hwt/', '/rowers/agegrouprecords/male/hwt/2000m/', - '/rowers/agegrouprecords/male/hwt/2000min/', + '/rowers/agegrouprecords/male/hwt/30min/', '/rowers/ajax_agegroup/45/hwt/male/1/', '/rowers/analysis/', '/rowers/analysis/user/1/', @@ -240,6 +242,8 @@ class URLTests(TestCase): # '/rowers/workouts-join-select/2016-01-01/2016-12-31/', ] + + # urlstotest = ['/rowers/createplan/user/1/'] lijst = [] diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index 6373b10c..e90b883d 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -585,8 +585,11 @@ def addmanual_view(request,raceid=0): weightcategory = form.cleaned_data['weightcategory'] adaptiveclass = form.cleaned_data['adaptiveclass'] distance = form.cleaned_data['distance'] - rpe = form.cleaned_data['rpe'] - if not rpe: + try: + rpe = form.cleaned_data['rpe'] + if not rpe: + rpe = -1 + except KeyError: rpe = -1 notes = form.cleaned_data['notes'] thetimezone = form.cleaned_data['timezone'] @@ -4361,8 +4364,11 @@ def workout_edit_view(request,id=0,message="",successmessage=""): notes = form.cleaned_data['notes'] newdragfactor = form.cleaned_data['dragfactor'] thetimezone = form.cleaned_data['timezone'] - rpe = form.cleaned_data['rpe'] - if not rpe: + try: + rpe = form.cleaned_data['rpe'] + if not rpe: + rpe = -1 + except KeyError: rpe = -1 try: @@ -4874,6 +4880,10 @@ def workout_upload_api(request): t = form.cleaned_data['title'] boattype = form.cleaned_data['boattype'] workouttype = form.cleaned_data['workouttype'] + try: + rpe = form.cleaned_data['rpe'] + except KeyError: + rpe = -1 if rowerform.is_valid(): u = rowerform.cleaned_data['user'] r = getrower(u) @@ -4929,6 +4939,7 @@ def workout_upload_api(request): boattype=boattype, makeprivate=makeprivate, title = t, + rpe=rpe, notes=notes, uploadoptions=post_data, ) @@ -5049,6 +5060,13 @@ def workout_upload_view(request, except KeyError: boattype = '1x' + try: + rpe = docformoptions['rpe'] + if not rpe: + rpe = -1 + except KeyError: + rpe = -1 + try: notes = docformoptions['notes'] except KeyError: @@ -5126,6 +5144,10 @@ def workout_upload_view(request, t = form.cleaned_data['title'] workouttype = form.cleaned_data['workouttype'] boattype = form.cleaned_data['boattype'] + try: + rpe = form.cleaned_data['rpe'] + except KeyError: + rpe = -1 request.session['docformoptions'] = { 'workouttype':workouttype, @@ -5166,6 +5188,7 @@ def workout_upload_view(request, 'upload_to_TrainingPeaks':upload_to_tp, 'landingpage':landingpage, 'boattype': boattype, + 'rpe':rpe, 'workouttype': workouttype, } @@ -5181,6 +5204,7 @@ def workout_upload_view(request, workouttype=workouttype, workoutsource=workoutsource, boattype=boattype, + rpe=rpe, makeprivate=makeprivate, title = t, notes=notes, From cbb06e599f44713bc8eeb69f4976b2628dbf660e Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 2 Dec 2020 20:21:09 +0100 Subject: [PATCH 13/15] inner monologues --- rowers/models.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/rowers/models.py b/rowers/models.py index b7354d88..4ebd6636 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -2905,13 +2905,17 @@ class Workout(models.Model): adaptivetypes = mytypes.adaptivetypes boatbrands = mytypes.boatbrands rpechoices = ( - (1,'Very Easy (reading a book, watching television)'), # 20 TSS / hour - (2,'Easy (talk, breathe normally, feels comfortable)'), # 30 TSS / hour - (4,'Moderate (talk in short spurts, breathing more labored, in comfort zone but working)'), # 50 TSS/hour - (6,'Somewhat Hard'), # 70 TSS / hour - (8,'Hard (could barely talk, breathing heavily)'), # 100 TSS / hour - (9,'Very Hard (outside comfort zone)'), # 120 TSS / hour - (10,'Max Effort (could barely remember your name)') # 140 TSS / hour + (0,'Not Specified'), + (1,'1 Very Easy (a walk in the park)'), # 20 TSS / hour + (2,'2 Easy (talk, breathe normally, feels comfortable)'), # 30 TSS / hour + (3,'3 Somewhat easy (you can talk easily but did you notice the beautiful clouds?)'), + (4,'4 Moderate (talk in short spurts, breathing more labored, this feels just right)'), # 50 TSS/hour + (5,"5 (It's not that painful, you just don't want to be here all day.)"), + (6,'6 Somewhat Hard (you can say a few words if you need to)'), # 70 TSS / hour + (7,'7 (This is starting to get painful)'), + (8,"8 Hard (Could barely talk, breathing heavily, hoping you won't have to this that long)"), # 100 TSS / hour + (9,'9 Very Hard (My goodness, please make it stop)'), # 120 TSS / hour + (10,'10 Max Effort (Could barely remember your name, you would rather rip out your toenails than go through this)') # 140 TSS / hour ) user = models.ForeignKey(Rower,on_delete=models.CASCADE) From 6fc4e33e39ffd9e84eff4c6c71a1ce2eaf67609e Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 2 Dec 2020 20:26:39 +0100 Subject: [PATCH 14/15] more descriptions (added vigorous) --- rowers/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rowers/models.py b/rowers/models.py index 4ebd6636..40348401 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -2912,7 +2912,7 @@ class Workout(models.Model): (4,'4 Moderate (talk in short spurts, breathing more labored, this feels just right)'), # 50 TSS/hour (5,"5 (It's not that painful, you just don't want to be here all day.)"), (6,'6 Somewhat Hard (you can say a few words if you need to)'), # 70 TSS / hour - (7,'7 (This is starting to get painful)'), + (7,'7 Vigorous (This is starting to get painful)'), (8,"8 Hard (Could barely talk, breathing heavily, hoping you won't have to this that long)"), # 100 TSS / hour (9,'9 Very Hard (My goodness, please make it stop)'), # 120 TSS / hour (10,'10 Max Effort (Could barely remember your name, you would rather rip out your toenails than go through this)') # 140 TSS / hour From b11e75e456b8e83afc9f45fc3b0c03eafeaac5ea Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Thu, 3 Dec 2020 08:33:50 +0100 Subject: [PATCH 15/15] link to import and end fitness stat --- rowers/interactiveplots.py | 14 ++++++----- rowers/models.py | 12 +++++----- rowers/templates/performancemanager.html | 30 ++++++++++++++++++++++-- rowers/views/analysisviews.py | 8 ++++++- 4 files changed, 49 insertions(+), 15 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 3c3f7267..14711cc9 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -1726,7 +1726,9 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, 'impulse':impulses, }) - + endfitness = fitnesses[-1] + endfatigue = fatigues[-1] + endform = endfitness-endfatigue if modelchoice == 'banister': df['fatigue'] = k2*df['fatigue'] @@ -1865,10 +1867,10 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, hover.tooltips = OrderedDict([ #(legend_label,'@testpower'), ('Date','@fdate'), - (fitlabel,'@fitness'), - (fatiguelabel,'@fatigue'), - (formlabel,'@form'), - ('Impulse','@impulse') + (fitlabel,'@fitness{int}'), + (fatiguelabel,'@fatigue{int}'), + (formlabel,'@form{int}'), + ('Impulse','@impulse{int}') ]) @@ -1908,7 +1910,7 @@ def performance_chart(user,startdate=None,enddate=None,kfitness=42,kfatigue=7, ) ) - return [script,div] + return [script,div,endfitness,endfatigue,endform] def fitnessfit_chart(workouts,user,workoutmode='water',startdate=None, diff --git a/rowers/models.py b/rowers/models.py index 40348401..8a6663a3 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -2907,15 +2907,15 @@ class Workout(models.Model): rpechoices = ( (0,'Not Specified'), (1,'1 Very Easy (a walk in the park)'), # 20 TSS / hour - (2,'2 Easy (talk, breathe normally, feels comfortable)'), # 30 TSS / hour - (3,'3 Somewhat easy (you can talk easily but did you notice the beautiful clouds?)'), - (4,'4 Moderate (talk in short spurts, breathing more labored, this feels just right)'), # 50 TSS/hour + (2,'2 Easy (You breathe normally, it feels comfortable)'), # 30 TSS / hour + (3,'3 Somewhat easy (You can talk easily but did you notice the beautiful clouds?)'), + (4,'4 Moderate (You can talk in short spurts, breathing more labored, this feels just right)'), # 50 TSS/hour (5,"5 (It's not that painful, you just don't want to be here all day.)"), - (6,'6 Somewhat Hard (you can say a few words if you need to)'), # 70 TSS / hour + (6,'6 Somewhat Hard (You can say a few words if you need to)'), # 70 TSS / hour (7,'7 Vigorous (This is starting to get painful)'), - (8,"8 Hard (Could barely talk, breathing heavily, hoping you won't have to this that long)"), # 100 TSS / hour + (8,"8 Hard (You can barely talk, breathing heavily, hoping you won't have to this that long)"), # 100 TSS / hour (9,'9 Very Hard (My goodness, please make it stop)'), # 120 TSS / hour - (10,'10 Max Effort (Could barely remember your name, you would rather rip out your toenails than go through this)') # 140 TSS / hour + (10,'10 Max Effort (You can barely remember your name, you would rather rip out your toenails than go through this)') # 140 TSS / hour ) user = models.ForeignKey(Rower,on_delete=models.CASCADE) diff --git a/rowers/templates/performancemanager.html b/rowers/templates/performancemanager.html index b5a14fab..4136e750 100644 --- a/rowers/templates/performancemanager.html +++ b/rowers/templates/performancemanager.html @@ -29,6 +29,9 @@ // var parsedJSON = $.parseJSON(data); // $("#id_script").replaceWith('
    '+data.script+''); $("#id_chart").replaceWith('
    '+data.div+''); + $("#endfitness").html(data.endfitness) + $("#endfatigue").html(data.endfatigue) + $("#endform").html(data.endform) console.log('done'); } }); @@ -84,7 +87,7 @@ {{ the_div|safe }}
  • -
  • +
  • {{ form.as_table }} @@ -109,6 +112,11 @@ on the left. The model balances out after a few weeks of regular training, so don't make this chart shorter than a few months.

    +

    + For this chart to reflect your fitness and freshness, it is important to have all workouts on + Rowsandall.com. You can automatically import workouts from other fitness platforms. Change + your Import and Export Settings here. +

    The time constants used in generating this performance chart were a fitness decay constant of {{ rower.kfit }} days @@ -116,7 +124,25 @@ You can change these values in your Profile Settings.

    - +
  • +
    +

    +

  • + + + + + + + + + + + +

    Fitness

    {{ endfitness }}

    Fatigue

    {{ endfatigue }}

    Freshness

    {{ endform }}

    +

    + +
  • diff --git a/rowers/views/analysisviews.py b/rowers/views/analysisviews.py index 23ebbf00..757444a5 100644 --- a/rowers/views/analysisviews.py +++ b/rowers/views/analysisviews.py @@ -1578,7 +1578,7 @@ def performancemanager_view(request,userid=0,mode='rower', else: form = PerformanceManagerForm() - script, thediv = performance_chart( + script, thediv, endfitness, endfatigue, endform = performance_chart( theuser,startdate=startdate,enddate=enddate, kfitness = kfitness, kfatigue = kfatigue, @@ -1602,6 +1602,9 @@ def performancemanager_view(request,userid=0,mode='rower', response = json.dumps({ 'script':script, 'div':thediv, + 'endform':int(endform), + 'endfitness': int(endfitness), + 'endfatigue': int(endfatigue), }) return(HttpResponse(response,content_type='application/json')) @@ -1616,6 +1619,9 @@ def performancemanager_view(request,userid=0,mode='rower', 'the_div':thediv, 'mode':mode, 'form':form, + 'endfitness':int(endfitness), + 'endfatigue':int(endfatigue), + 'endform':int(endform), })