From 070350521939ef77358cd8f6fb15c3443520c144 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 22 Feb 2020 14:37:58 +0100 Subject: [PATCH 1/6] adds SPM to automagical intervals --- rowers/forms.py | 2 ++ rowers/views/workoutviews.py | 36 ++++++++++++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/rowers/forms.py b/rowers/forms.py index 829bbd3a..9555f701 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -731,11 +731,13 @@ class PowerIntervalUpdateForm(forms.Form): ('power','Power'), ('pace','Pace'), ('work','Work per Stroke'), + ('spm','Stroke Rate') ) pace = forms.DurationField(required=False,label='Pace (/500m)') power = forms.IntegerField(required=False,label='Power (W)') work = forms.IntegerField(required=False,label='Work per Stroke (J)') + spm = forms.IntegerField(required=False,label='Stroke Rate') selector = forms.ChoiceField(choices=selectorchoices, required=True, initial='power', diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index ecdd672b..3ebd09e3 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -5614,6 +5614,7 @@ def workout_summary_edit_view(request,id,message="",successmessage="" normp = row.normp normv = row.normv normw = row.normw + normspm = rowdata.df[' Cadence (stokes/min)'].mean() if tss == -1: tss,normp = dataprep.workout_rscore(row) @@ -5635,7 +5636,8 @@ def workout_summary_edit_view(request,id,message="",successmessage="" 'power': int(normp), 'pace': avpace, 'selector': 'power', - 'work': int(normw) + 'work': int(normw), + 'spm': int(normspm), } powerorpace = 'power' @@ -5670,6 +5672,7 @@ def workout_summary_edit_view(request,id,message="",successmessage="" value_pace = request.POST['value_pace'] value_power = request.POST['value_power'] value_work = request.POST['value_work'] + value_spm = request.POST['value_spm'] if powerorpace == 'power': try: power = int(value_power) @@ -5688,6 +5691,11 @@ def workout_summary_edit_view(request,id,message="",successmessage="" work = int(value_work) except ValueError: work = int(normw) + elif powerorpace == 'spm': + try: + spm = int(value_spm) + except ValueError: + spm = int(normspm) if powerorpace == 'power' and power is not None: try: @@ -5711,6 +5719,14 @@ def workout_summary_edit_view(request,id,message="",successmessage="" debug=False,smoothwindow=15.) except: messages.error(request,'Error updating Work per Stroke') + elif powerorpace == 'spm': + try: + print('aap') + rowdata.updateinterval_metric( + ' Cadence (stokes/min)',spm,mode='larger', + debug=False,smoothwindow=2.) + except: + messages.error(request,'Error updating SPM') intervalstats = rowdata.allstats() itime,idist,itype = rowdata.intervalstats_values() @@ -5728,7 +5744,8 @@ def workout_summary_edit_view(request,id,message="",successmessage="" 'power': power, 'pace': datetime.timedelta(seconds=int(pace_secs)), 'work': work, - 'selector': powerorpace + 'selector': powerorpace, + 'spm': int(normspm) } form = SummaryStringForm() powerupdateform = PowerIntervalUpdateForm(initial=data) @@ -5773,7 +5790,8 @@ def workout_summary_edit_view(request,id,message="",successmessage="" 'power': int(normp), 'pace': avpace, 'selector': 'power', - 'work': int(normw) + 'work': int(normw), + 'spm': int(normspm), }) savebutton = 'savestringform' @@ -5786,6 +5804,7 @@ def workout_summary_edit_view(request,id,message="",successmessage="" power = cd['power'] pace = cd['pace'] work = cd['work'] + spm = cd['spm'] try: pace_secs = pace.seconds+pace.microseconds/1.0e6 except AttributeError: @@ -5811,6 +5830,13 @@ def workout_summary_edit_view(request,id,message="",successmessage="" debug=False,smoothwindow=15.) except: messages.error(request,'Error updating Work per Stroke') + elif powerorpace == 'spm': + try: + rowdata.updateinterval_metric(' Cadence (stokes/min)',spm,mode='larger', + debug=False,smoothwindow=2.) + except: + print('mies') + messages.error(request,'Error updating SPM') intervalstats = rowdata.allstats() @@ -5822,6 +5848,7 @@ def workout_summary_edit_view(request,id,message="",successmessage="" 'value_power': power, 'value_pace': pace_secs, 'value_work': work, + 'value_spm': spm, } powerupdateform = PowerIntervalUpdateForm(initial=cd) form = SummaryStringForm() @@ -5888,7 +5915,8 @@ def workout_summary_edit_view(request,id,message="",successmessage="" 'power': int(normp), 'pace': avpace, 'selector': 'power', - 'work': int(normw) + 'work': int(normw), + 'spm': int(normspm), }) From ada370f00725ee89dca8d981d040115190da6ddf Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 22 Feb 2020 15:26:45 +0100 Subject: [PATCH 2/6] adding slider --- rowers/forms.py | 2 ++ rowers/templates/summary_edit.html | 49 +++++++++++++++++++++++------- rowers/views/workoutviews.py | 22 +++++++++++++- 3 files changed, 61 insertions(+), 12 deletions(-) diff --git a/rowers/forms.py b/rowers/forms.py index 9555f701..baf5782d 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -742,6 +742,8 @@ class PowerIntervalUpdateForm(forms.Form): required=True, initial='power', label='Use') + activeminutesmin = forms.IntegerField(required=False,initial=0,widget=forms.HiddenInput()) + activeminutesmax = forms.IntegerField(required=False,initial=0,widget=forms.HiddenInput()) # Form used to update interval stats class IntervalUpdateForm(forms.Form): diff --git a/rowers/templates/summary_edit.html b/rowers/templates/summary_edit.html index 57c9804d..66b2bbad 100644 --- a/rowers/templates/summary_edit.html +++ b/rowers/templates/summary_edit.html @@ -7,6 +7,27 @@ {% block scripts %} {% include "monitorjobs.html" %} + + + + {% endblock %} {% block main %} @@ -47,22 +68,28 @@ {{ form.as_table }} {% csrf_token %} - +

Intervals by Power/Pace

- +

With this form, you can specify a power or pace level. Everything faster/harder than the specified pace/power will become a work interval. Everything slower will become a rest - interval. + interval. Use the slider to limit the active range. Everything outside the active range will + become rest (warming up and cooling down).

- +
+
+

+ + +

{{ powerupdateform.as_table }}
{% csrf_token %} - +
  • @@ -70,7 +97,7 @@ - + {{ interactiveplot |safe }} {{ the_div |safe }} @@ -106,7 +133,7 @@

    Detailed Summary Edit

    This is still experimental and there are known bugs. Use at your own risk. Nothing is stored permanently until you hit Save in the Updated Summary section. You can use the restore original button to restore the original values.

    - +
    @@ -131,18 +158,18 @@
    #TimeDistanceType
    {% csrf_token %} - +
  • Interval Shorthand How-To

    This is a quick way to enter the intervals using a special mini-language.

    You enter something like 8x500m/3min, press "Update" and the site will interpret this for you and update the summary on the right. If you're happy with the result, press the green Save button to update the values. Nothing will be changed permanently until you hit Save.

    - +

    Special characters are x (times), + and / (denotes a rest interval), as well as ( and ). Units are min (minutes), sec (seconds), m (meters) and km (km).

    - +

    A typical interval is described as "10min/5min", with the work part before the "/" and the rest part after it. A zero rest can be omitted, so a single 1000m piece could be described either as "1km" or "1000m". The basic units can be combined with "+" and "Nx". You can use parentheses as in the example below.

    - +

    Here are a few examples.

    diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index 3ebd09e3..a5e49ab5 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -5605,6 +5605,9 @@ def workout_summary_edit_view(request,id,message="",successmessage="" return HttpResponse("Error: CSV Data File Not Found") nrintervals = len(idist) + activeminutesmax = int(rowdata.duration/60.) + activeminutesmin = 0 + maxminutes = activeminutesmax savebutton = 'nosavebutton' formvalues = {} @@ -5638,6 +5641,8 @@ def workout_summary_edit_view(request,id,message="",successmessage="" 'selector': 'power', 'work': int(normw), 'spm': int(normspm), + 'activeminutesmin': 0, + 'activeminutesmax': activeminutesmax, } powerorpace = 'power' @@ -5673,6 +5678,8 @@ def workout_summary_edit_view(request,id,message="",successmessage="" value_power = request.POST['value_power'] value_work = request.POST['value_work'] value_spm = request.POST['value_spm'] + activeminutesmin = request.POST['activeminutesmin'] + activeminutesmax = request.POST['activeminutesmax'] if powerorpace == 'power': try: power = int(value_power) @@ -5745,7 +5752,9 @@ def workout_summary_edit_view(request,id,message="",successmessage="" 'pace': datetime.timedelta(seconds=int(pace_secs)), 'work': work, 'selector': powerorpace, - 'spm': int(normspm) + 'spm': int(normspm), + 'activeminutesmin': activeminutesmin, + 'activeminutesmax': activeminutesmax, } form = SummaryStringForm() powerupdateform = PowerIntervalUpdateForm(initial=data) @@ -5792,6 +5801,8 @@ def workout_summary_edit_view(request,id,message="",successmessage="" 'selector': 'power', 'work': int(normw), 'spm': int(normspm), + 'activeminutesmin': 0, + 'activeminutesmax': activeminutesmax, }) savebutton = 'savestringform' @@ -5805,6 +5816,8 @@ def workout_summary_edit_view(request,id,message="",successmessage="" pace = cd['pace'] work = cd['work'] spm = cd['spm'] + activeminutesmin = cd['activeminutesmin'] + activeminutesmax = cd['activeminutesmax'] try: pace_secs = pace.seconds+pace.microseconds/1.0e6 except AttributeError: @@ -5849,6 +5862,8 @@ def workout_summary_edit_view(request,id,message="",successmessage="" 'value_pace': pace_secs, 'value_work': work, 'value_spm': spm, + 'activeminutesmin': activeminutesmin, + 'activeminutesmax': activeminutesmax, } powerupdateform = PowerIntervalUpdateForm(initial=cd) form = SummaryStringForm() @@ -5917,6 +5932,8 @@ def workout_summary_edit_view(request,id,message="",successmessage="" 'selector': 'power', 'work': int(normw), 'spm': int(normspm), + 'activeminutesmin': 0, + 'activeminutesmax': activeminutesmax, }) @@ -6000,6 +6017,9 @@ def workout_summary_edit_view(request,id,message="",successmessage="" return render(request, 'summary_edit.html', {'form':form, + 'activeminutesmax':activeminutesmax, + 'activeminutesmin':activeminutesmin, + 'maxminutes': maxminutes, 'detailform':detailform, 'powerupdateform':powerupdateform, 'workout':row, From d58e019d5a8ded224ed6d41646173a18eac95126 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sun, 23 Feb 2020 09:44:42 +0100 Subject: [PATCH 3/6] bug fixes --- rowers/views/workoutviews.py | 44 ++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index a5e49ab5..b31f7bac 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -5680,6 +5680,10 @@ def workout_summary_edit_view(request,id,message="",successmessage="" value_spm = request.POST['value_spm'] activeminutesmin = request.POST['activeminutesmin'] activeminutesmax = request.POST['activeminutesmax'] + activesecondsmin = 60.*activeminutesmin + activesecondsmax = 60.*activeminutesmax + if abs(rowdata.duration-activesecondsmax) < 60.: + activesecondsmax = rowdata.duration if powerorpace == 'power': try: power = int(value_power) @@ -5708,7 +5712,9 @@ def workout_summary_edit_view(request,id,message="",successmessage="" try: rowdata.updateinterval_metric( ' Power (watts)',power,mode='larger', - debug=False,smoothwindow=15.) + debug=False,smoothwindow=15., + activewindow=[activesecondsmin,activesecondsmax], + ) except: messages.error(request,'Error updating power') elif powerorpace == 'pace': @@ -5716,14 +5722,18 @@ def workout_summary_edit_view(request,id,message="",successmessage="" velo = 500./pace_secs rowdata.updateinterval_metric( ' AverageBoatSpeed (m/s)',velo,mode='larger', - debug=False,smoothwindow=15.) + debug=False,smoothwindow=15., + activewindow=[activesecondsmin,activesecondsmax], + ) except: messages.error(request,'Error updating pace') elif powerorpace == 'work': try: rowdata.updateinterval_metric( 'driveenergy',work,mode='larger', - debug=False,smoothwindow=15.) + debug=False,smoothwindow=15., + activewindow=[activesecondsmin,activesecondsmax], + ) except: messages.error(request,'Error updating Work per Stroke') elif powerorpace == 'spm': @@ -5731,7 +5741,8 @@ def workout_summary_edit_view(request,id,message="",successmessage="" print('aap') rowdata.updateinterval_metric( ' Cadence (stokes/min)',spm,mode='larger', - debug=False,smoothwindow=2.) + debug=False,smoothwindow=2., + activewindow=[activesecondsmin,activesecondsmax],) except: messages.error(request,'Error updating SPM') @@ -5818,35 +5829,44 @@ def workout_summary_edit_view(request,id,message="",successmessage="" spm = cd['spm'] activeminutesmin = cd['activeminutesmin'] activeminutesmax = cd['activeminutesmax'] + activesecondsmin = 60.*activeminutesmin + activesecondsmax = 60.*activeminutesmax + if abs(rowdata.duration-activesecondsmax) < 60.: + activesecondsmax = rowdata.duration try: pace_secs = pace.seconds+pace.microseconds/1.0e6 except AttributeError: pace_secs = 120. if powerorpace == 'power' and power is not None: - try: - rowdata.updateinterval_metric(' Power (watts)',power,mode='larger', - debug=False,smoothwindow=15) - except: - messages.error(request,'Error updating power') + rowdata.updateinterval_metric(' Power (watts)',power,mode='larger', + debug=False,smoothwindow=15, + activewindow=[activesecondsmin,activesecondsmax], + ) + elif powerorpace == 'pace': try: velo = 500./pace_secs rowdata.updateinterval_metric(' AverageBoatSpeed (m/s)',velo,mode='larger', - debug=False,smoothwindow=15) + debug=False,smoothwindow=15, + activewindow=[activesecondsmin,activesecondsmax], + ) except: messages.error(request,'Error updating pace') elif powerorpace == 'work': try: rowdata.updateinterval_metric( 'driveenergy',work,mode='larger', - debug=False,smoothwindow=15.) + debug=False,smoothwindow=15., + activewindow=[activesecondsmin,activesecondsmax],) except: messages.error(request,'Error updating Work per Stroke') elif powerorpace == 'spm': try: rowdata.updateinterval_metric(' Cadence (stokes/min)',spm,mode='larger', - debug=False,smoothwindow=2.) + debug=False,smoothwindow=2., + activewindow=[activesecondsmin,activesecondsmax], + ) except: print('mies') messages.error(request,'Error updating SPM') From 87d6bd29bc3d6a0b27d464c8ce6c3a5370cf225b Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sun, 23 Feb 2020 10:11:07 +0100 Subject: [PATCH 4/6] color to company color --- rowers/templates/summary_edit.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rowers/templates/summary_edit.html b/rowers/templates/summary_edit.html index 66b2bbad..d4b8edc7 100644 --- a/rowers/templates/summary_edit.html +++ b/rowers/templates/summary_edit.html @@ -82,7 +82,7 @@

    - +

    {{ powerupdateform.as_table }} From ef1f9a88bbb841822367e9d7b4cb7bc6b1b716db Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sun, 23 Feb 2020 10:24:24 +0100 Subject: [PATCH 5/6] bug fixes in summary edit --- rowers/tests/test_aworkouts.py | 5 +++++ rowers/views/workoutviews.py | 11 +++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/rowers/tests/test_aworkouts.py b/rowers/tests/test_aworkouts.py index 55d48c5d..dab07b99 100644 --- a/rowers/tests/test_aworkouts.py +++ b/rowers/tests/test_aworkouts.py @@ -564,6 +564,9 @@ class WorkoutViewTest(TestCase): 'value_pace':'2:23', 'value_power':'200', 'value_work':'400', + 'value_spm':'20', + 'activeminutesmin':'0', + 'activeminutesmax':'60', 'savepowerpaceform':True, } @@ -582,6 +585,8 @@ class WorkoutViewTest(TestCase): 'power': 200, 'pace': '2:30', 'work': 400, + 'activeminutesmin':'0', + 'activeminutesmax': '60', } form = PowerIntervalUpdateForm(form_data) diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index df4e0705..ab9e747c 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -5676,8 +5676,15 @@ def workout_summary_edit_view(request,id,message="",successmessage="" value_spm = request.POST['value_spm'] activeminutesmin = request.POST['activeminutesmin'] activeminutesmax = request.POST['activeminutesmax'] - activesecondsmin = 60.*activeminutesmin - activesecondsmax = 60.*activeminutesmax + try: + activesecondsmin = 60.*float(activeminutesmin) + activesecondsmax = 60.*float(activeminutesmax) + except ValueError: + activesecondsmin = 0 + activeminutesmin = 0 + activeminutesmax = maxminutes + activesecondsmax = rowdata.duration + if abs(rowdata.duration-activesecondsmax) < 60.: activesecondsmax = rowdata.duration if powerorpace == 'power': From bf8f7422f381ccbeacb0e3ee32bc8656923b0904 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sun, 23 Feb 2020 10:31:45 +0100 Subject: [PATCH 6/6] try except to catch workouts without spm --- rowers/views/workoutviews.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rowers/views/workoutviews.py b/rowers/views/workoutviews.py index ab9e747c..da58fdf8 100644 --- a/rowers/views/workoutviews.py +++ b/rowers/views/workoutviews.py @@ -5613,7 +5613,10 @@ def workout_summary_edit_view(request,id,message="",successmessage="" normp = row.normp normv = row.normv normw = row.normw - normspm = rowdata.df[' Cadence (stokes/min)'].mean() + try: + normspm = rowdata.df[' Cadence (stokes/min)'].mean() + except KeyError: + normspm = 0 if tss == -1: tss,normp = dataprep.workout_rscore(row)