From f44fee0f12ecfd6294924a440627727f65981080 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 22 Aug 2017 20:15:06 +0200 Subject: [PATCH 1/4] split workout (bugs) --- rowers/dataprep.py | 38 ++++++++++ rowers/forms.py | 15 ++++ rowers/templates/splitworkout.html | 113 +++++++++++++++++++++++++++++ rowers/urls.py | 1 + rowers/views.py | 54 +++++++++++++- 5 files changed, 219 insertions(+), 2 deletions(-) create mode 100644 rowers/templates/splitworkout.html diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 8d5f9473..b454942a 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -820,6 +820,43 @@ def new_workout_from_file(r,f2, return (id,message,f2) +def split_workout(r,parent,splitsecond,splitmode): + data,row = getrowdata_db(id=parent.id) + + data['time'] = data['time']/1000. + + data1 = data[data['time']<=splitsecond].copy() + data2 = data[data['time']>splitsecond].copy() + + + messages = [] + ids = [] + + if 'keep first' in splitmode: + id,message = new_workout_from_df(r,data1, + title=parent.name+' First Part', + parent=parent) + messages.append(message) + ids.append(id) + if 'keep second' in splitmode: + data2['cumdist'] = data2['cumdist'] - data2['cumdist'].min() + data2['time'] = data2['time'] - data2['time'].min() + id,message = new_workout_from_df(r,data2, + title=parent.name+' Second Part', + parent=parent) + messages.append(message) + ids.append(id) + + if not 'keep original' in splitmode: + if 'keep second' in splitmode or 'keep first' in splitmode: + parent.delete() + messages.append('Deleted Workout: '+parent.name) + else: + messages.append('That would delete your workout') + ids.append(parent.id) + + return ids,messages + # Create new workout from data frame and store it in the database # This routine should be used everywhere in views.py and mailprocessing.py # Currently there is code duplication @@ -853,6 +890,7 @@ def new_workout_from_df(r,df, df.rename(columns = columndict,inplace=True) + starttimeunix = mktime(startdatetime.utctimetuple()) df[' ElapsedTime (sec)'] = df['TimeStamp (sec)'] df['TimeStamp (sec)'] = df['TimeStamp (sec)']+starttimeunix diff --git a/rowers/forms.py b/rowers/forms.py index 732097e4..59d85ebf 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -110,6 +110,21 @@ class TeamUploadOptionsForm(forms.Form): class Meta: fields = ['make_plot','plottype'] +# This form is used on the Workout Split page +class WorkoutSplitForm(forms.Form): + splitchoices = ( + ('keep original','Keep Original'), + ('keep first','Keep First Part'), + ('keep second','Keep Second Part'), + ) + splittime = forms.TimeField(input_formats=['%H:%M:%S.%f','%H:%M:%S','%M:%S.%f']) + splitmode = forms.MultipleChoiceField( + initial=['keep original', + 'keep first', + 'keep second'], + label='Split Mode', + choices=splitchoices) + # This form is used on the Analysis page to add a custom distance/time # trial and predict the pace class PredictedPieceForm(forms.Form): diff --git a/rowers/templates/splitworkout.html b/rowers/templates/splitworkout.html new file mode 100644 index 00000000..3d9b1fda --- /dev/null +++ b/rowers/templates/splitworkout.html @@ -0,0 +1,113 @@ +{% extends "base.html" %} +{% load staticfiles %} +{% load rowerfilters %} +{% load tz %} + +{% block title %}Change Workout {% endblock %} + +{% block content %} +
+ + {% if form.errors %} +

+ Please correct the error{{ form.errors|pluralize }} below. +

+ {% endif %} + +

Edit Workout Interval Data

+
+
+

+ Edit +

+
+
+

+ Export + +

+
+
+

+ Advanced +

+ Advanced Functionality (More interactive Charts) + +
+
+ + + {% localtime on %} + + + + + + + + + + + + +
Date/Time:{{ workout.startdatetime }}
Distance:{{ workout.distance }}m
Duration:{{ workout.duration |durationprint:"%H:%M:%S.%f" }}
Public link to this workout + https://rowsandall.com/rowers/workout/{{ workout.id }} + +
Public link to interactive chart + https://rowsandall.com/rowers/workout/{{ workout.id }}/interactiveplot + +
+ {% endlocaltime %} +

Workout Split

+

Split your workout in half

+ +
+ + {{ form.as_table }} +
+ {% csrf_token %} +
+ +
+
+
+ +
+
+ + + + + {{ thescript |safe }} + + + + + +
+ {{ thediv |safe }} +
+
+
+ + +{% endblock %} diff --git a/rowers/urls.py b/rowers/urls.py index b94aafbb..80abf3e3 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -202,6 +202,7 @@ urlpatterns = [ url(r'^workout/(?P\d+)/crewnerdsummary$',views.workout_crewnerd_summary_view), url(r'^workout/(?P\d+)/editintervals$',views.workout_summary_edit_view), url(r'^workout/(?P\d+)/restore$',views.workout_summary_restore_view), + url(r'^workout/(?P\d+)/split$',views.workout_split_view), url(r'^workout/(?P\d+)/interactiveplot$',views.workout_biginteractive_view), url(r'^workout/(?P\d+)/view$',views.workout_view), url(r'^workout/(?P\d+)$',views.workout_view), diff --git a/rowers/views.py b/rowers/views.py index 16ede869..f1be7269 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -36,7 +36,7 @@ from rowers.forms import ( RegistrationFormUniqueEmail,CNsummaryForm,UpdateWindForm, UpdateStreamForm,WorkoutMultipleCompareForm,ChartParamChoiceForm, FusionMetricChoiceForm,BoxPlotChoiceForm,MultiFlexChoiceForm, - TrendFlexModalForm, + TrendFlexModalForm,WorkoutSplitForm, ) from rowers.models import Workout, User, Rower, WorkoutForm,FavoriteChart from rowers.models import ( @@ -7773,6 +7773,8 @@ def workout_delete_view(request,id=0): messages.info(request,'Workout deleted') try: url = request.session['referer'] + if 'rowers/workout/' in url: + url = reverse(workouts_view) except KeyError: url = reverse(workouts_view) @@ -7877,7 +7879,7 @@ def graph_show_view(request,id): raise Http404("This graph doesn't exist") except Workout.DoesNotExist: raise Http404("This workout doesn't exist") - + # Restore original stroke data and summary @login_required() def workout_summary_restore_view(request,id,message="",successmessage=""): @@ -7935,6 +7937,54 @@ def workout_summary_restore_view(request,id,message="",successmessage=""): ) return HttpResponseRedirect(url) +# Split a workout +@user_passes_test(ispromember,login_url="/",redirect_field_name=None) +def workout_split_view(request,id=id): + try: + row = Workout.objects.get(id=id) + if (checkworkoutuser(request.user,row)==False): + raise PermissionDenied("You are not allowed to edit this workout") + except Workout.DoesNotExist: + raise Http404("Workout doesn't exist") + + r = getrower(request.user) + if not checkworkoutuser(request.user,row): + raise PermissionDenied("You are not allowed to edit this workout") + + if request.method == 'POST': + form = WorkoutSplitForm(request.POST) + if form.is_valid(): + splittime = form.cleaned_data['splittime'] + splitsecond = splittime.hour*3600 + splitsecond += splittime.minute*60 + splitsecond += splittime.second + splitsecond += splittime.microsecond/1.e6 + splitmode = form.cleaned_data['splitmode'] + ids,mesgs = dataprep.split_workout(r,row,splitsecond,splitmode) + for message in mesgs: + messages.info(request,message) + + url = reverse(workouts_view) + return HttpResponseRedirect(url) + + form = WorkoutSplitForm() + + # create interactive plot + try: + res = interactive_chart(id,promember=1) + script = res[0] + div = res[1] + except ValueError: + pass + + return render(request, 'splitworkout.html', + {'form':form, + 'workout':row, + 'thediv':script, + 'thescript':div, + }) + + # Fuse two workouts @user_passes_test(ispromember,login_url="/",redirect_field_name=None) def workout_fusion_view(request,id1=0,id2=1): From 7e78abc1ef4bef2874dc1dc8adcdb6ae14aeb872 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Tue, 22 Aug 2017 22:28:09 +0200 Subject: [PATCH 2/4] split seems to be working now --- rowers/dataprep.py | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index b454942a..9dfb3e68 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -828,7 +828,34 @@ def split_workout(r,parent,splitsecond,splitmode): data1 = data[data['time']<=splitsecond].copy() data2 = data[data['time']>splitsecond].copy() + data1 = data1.sort_values(['time']) + data1 = data1.interpolate(method='linear',axis=0,limit_direction='both', + limit=10) + data1.fillna(method='bfill',inplace=True) + + # Some new stuff to try out + data1 = data1.groupby('time',axis=0).mean() + data1['time'] = data1.index + data1.reset_index(drop=True,inplace=True) + + data2 = data2.sort_values(['time']) + data2 = data2.interpolate(method='linear',axis=0,limit_direction='both', + limit=10) + data2.fillna(method='bfill',inplace=True) + + # Some new stuff to try out + data2 = data2.groupby('time',axis=0).mean() + data2['time'] = data2.index + data2.reset_index(drop=True,inplace=True) + + data1['pace'] = data1['pace']/1000. + data2['pace'] = data2['pace']/1000. + + + data1.drop_duplicates(subset='time',inplace=True) + data2.drop_duplicates(subset='time',inplace=True) + messages = [] ids = [] @@ -839,8 +866,9 @@ def split_workout(r,parent,splitsecond,splitmode): messages.append(message) ids.append(id) if 'keep second' in splitmode: - data2['cumdist'] = data2['cumdist'] - data2['cumdist'].min() - data2['time'] = data2['time'] - data2['time'].min() + data2['cumdist'] = data2['cumdist'] - data2.ix[0,'cumdist'] + data2['distance'] = data2['distance'] - data2.ix[0,'distance'] + data2['time'] = data2['time'] - data2.ix[0,'time'] id,message = new_workout_from_df(r,data2, title=parent.name+' Second Part', parent=parent) @@ -893,6 +921,7 @@ def new_workout_from_df(r,df, starttimeunix = mktime(startdatetime.utctimetuple()) df[' ElapsedTime (sec)'] = df['TimeStamp (sec)'] + print df[' ElapsedTime (sec)'].values df['TimeStamp (sec)'] = df['TimeStamp (sec)']+starttimeunix row = rrdata(df=df) From 45fe8a61810b27514cf15bde9c37b86f5cac2320 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 23 Aug 2017 09:55:26 +0200 Subject: [PATCH 3/4] workout split works --- rowers/dataprep.py | 34 +++++++++++++--- rowers/forms.py | 18 +++++++-- rowers/templates/advancedotw.html | 16 +++++++- rowers/templates/splitworkout.html | 64 +++++++++++++++++++----------- rowers/views.py | 5 ++- 5 files changed, 102 insertions(+), 35 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index 9dfb3e68..d495185a 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -860,20 +860,36 @@ def split_workout(r,parent,splitsecond,splitmode): ids = [] if 'keep first' in splitmode: + if 'secondprivate' in splitmode: + setprivate = True + else: + setprivate = False + id,message = new_workout_from_df(r,data1, title=parent.name+' First Part', - parent=parent) + parent=parent, + setprivate=setprivate) messages.append(message) ids.append(id) if 'keep second' in splitmode: data2['cumdist'] = data2['cumdist'] - data2.ix[0,'cumdist'] data2['distance'] = data2['distance'] - data2.ix[0,'distance'] data2['time'] = data2['time'] - data2.ix[0,'time'] + if 'secondprivate' in splitmode: + setprivate = True + else: + setprivate = False + + dt = datetime.timedelta(seconds=splitsecond) + id,message = new_workout_from_df(r,data2, title=parent.name+' Second Part', - parent=parent) + parent=parent, + setprivate=setprivate, + dt=dt) messages.append(message) ids.append(id) + if not 'keep original' in splitmode: if 'keep second' in splitmode or 'keep first' in splitmode: @@ -882,6 +898,9 @@ def split_workout(r,parent,splitsecond,splitmode): else: messages.append('That would delete your workout') ids.append(parent.id) + elif 'originalprivate' in splitmode: + parent.privacy = 'hidden' + parent.save() return ids,messages @@ -890,7 +909,9 @@ def split_workout(r,parent,splitsecond,splitmode): # Currently there is code duplication def new_workout_from_df(r,df, title='New Workout', - parent=None): + parent=None, + setprivate=False, + dt=datetime.timedelta()): message = None @@ -902,7 +923,7 @@ def new_workout_from_df(r,df, notes=parent.notes summary=parent.summary makeprivate=parent.privacy - startdatetime=parent.startdatetime + startdatetime=parent.startdatetime+dt else: oarlength = 2.89 inboard = 0.88 @@ -912,6 +933,9 @@ def new_workout_from_df(r,df, makeprivate=False startdatetime = timezone.now() + if setprivate: + makeprivate=True + timestr = strftime("%Y%m%d-%H%M%S") csvfilename ='media/df_'+timestr+'.csv' @@ -921,7 +945,7 @@ def new_workout_from_df(r,df, starttimeunix = mktime(startdatetime.utctimetuple()) df[' ElapsedTime (sec)'] = df['TimeStamp (sec)'] - print df[' ElapsedTime (sec)'].values + df['TimeStamp (sec)'] = df['TimeStamp (sec)']+starttimeunix row = rrdata(df=df) diff --git a/rowers/forms.py b/rowers/forms.py index 59d85ebf..eecde602 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -116,14 +116,26 @@ class WorkoutSplitForm(forms.Form): ('keep original','Keep Original'), ('keep first','Keep First Part'), ('keep second','Keep Second Part'), + ('firstprivate','Set First Part Private'), + ('secondprivate','Set Second Part Private'), + ('originalprivate','Set Original Private'), ) - splittime = forms.TimeField(input_formats=['%H:%M:%S.%f','%H:%M:%S','%M:%S.%f']) + splittime = forms.TimeField(input_formats=['%H:%M:%S.%f', + '%H:%M:%S', + '%H:%M:%S', + '%M:%S.%f', + '%M:%S', + '%M'], + label = 'Split Time') splitmode = forms.MultipleChoiceField( initial=['keep original', 'keep first', - 'keep second'], + 'keep second', + 'firstprivate', + 'secondprivate'], label='Split Mode', - choices=splitchoices) + choices=splitchoices, + widget = forms.CheckboxSelectMultiple()) # This form is used on the Analysis page to add a custom distance/time # trial and predict the pace diff --git a/rowers/templates/advancedotw.html b/rowers/templates/advancedotw.html index 3b8ac298..abbccd67 100644 --- a/rowers/templates/advancedotw.html +++ b/rowers/templates/advancedotw.html @@ -190,18 +190,30 @@ See (and save) the big interactive plot

-
+

{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %} Sensor Fusion {% else %} - Dist Metrics Plot + Split Workout {% endif %}

Merge data from another source into this workout

+
+

+ {% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %} + Split Workout + {% else %} + Split Workout + {% endif %} +

+

+ Split workout into two seperate workouts +

+
diff --git a/rowers/templates/splitworkout.html b/rowers/templates/splitworkout.html index 3d9b1fda..a264f626 100644 --- a/rowers/templates/splitworkout.html +++ b/rowers/templates/splitworkout.html @@ -38,29 +38,8 @@ {% localtime on %} - - - - - - - - - - - - -
Date/Time:{{ workout.startdatetime }}
Distance:{{ workout.distance }}m
Duration:{{ workout.duration |durationprint:"%H:%M:%S.%f" }}
Public link to this workout - https://rowsandall.com/rowers/workout/{{ workout.id }} - -
Public link to interactive chart - https://rowsandall.com/rowers/workout/{{ workout.id }}/interactiveplot - -
- {% endlocaltime %} -

Workout Split

-

Split your workout in half

- + +

{{ form.as_table }} @@ -70,6 +49,22 @@ +

+

Workout Split

+

Split your workout in half at the given time using the form above. + Use the chart on + the right to find the point where you want to split.

+

Use any of the following formats: +

    +
  • H:MM:SS.d, e.g. 1:45:00.0 for one hour and 45 minutes
  • +
  • H:MM:SS, e.g. 1:45:00 for one hour and 45 minutes
  • +
  • MM:SS.d, e.g. 30:00.0 for thirty minutes
  • +
  • MM, e.g. 30 for thirty minutes
  • +
+

+

Warning: If you deselect, "Keep Original", the original workout + cannot be restored afterwards.

+
@@ -106,6 +101,29 @@
{{ thediv |safe }}
+ +
+ + + + + + + + + + + +
Date/Time:{{ workout.startdatetime }}
Distance:{{ workout.distance }}m
Duration:{{ workout.duration |durationprint:"%H:%M:%S.%f" }}
Public link to this workout + https://rowsandall.com/rowers/workout/{{ workout.id }} + +
Public link to interactive chart + https://rowsandall.com/rowers/workout/{{ workout.id }}/interactiveplot + +
+ {% endlocaltime %} + + diff --git a/rowers/views.py b/rowers/views.py index f1be7269..b4bab4f6 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -7948,8 +7948,6 @@ def workout_split_view(request,id=id): raise Http404("Workout doesn't exist") r = getrower(request.user) - if not checkworkoutuser(request.user,row): - raise PermissionDenied("You are not allowed to edit this workout") if request.method == 'POST': form = WorkoutSplitForm(request.POST) @@ -7965,6 +7963,9 @@ def workout_split_view(request,id=id): messages.info(request,message) url = reverse(workouts_view) + qdict = {'q':row.name} + url+='?'+urllib.urlencode(qdict) + return HttpResponseRedirect(url) form = WorkoutSplitForm() From 0af56b6e2f6a45ee6a95504cc456dd98804ad4d5 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 23 Aug 2017 10:57:27 +0200 Subject: [PATCH 4/4] added OTE split --- rowers/dataprep.py | 24 ++++++++++++++++-------- rowers/templates/advancededit.html | 16 ++++++++++++++-- rowers/templates/advancedotw.html | 2 +- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/rowers/dataprep.py b/rowers/dataprep.py index d495185a..e2e040d2 100644 --- a/rowers/dataprep.py +++ b/rowers/dataprep.py @@ -91,7 +91,7 @@ columndict = { 'finish':'finish', 'peakforceangle':'peakforceangle', 'wash':'wash', - 'slip':'wash', + 'slip':'slip', 'workoutstate':' WorkoutState', } @@ -107,13 +107,17 @@ def get_latlon(id): rowdata = rdata(w.csvfilename) try: - latitude = rowdata.df.ix[:,' latitude'] - longitude = rowdata.df.ix[:,' longitude'] - except KeyError: - latitude = 0*rowdata.df.ix[:,'TimeStamp (sec)'] - latitude = 0*rowdata.df.ix[:,'TimeStamp (sec)'] - - return [latitude,longitude] + try: + latitude = rowdata.df.ix[:,' latitude'] + longitude = rowdata.df.ix[:,' longitude'] + except KeyError: + latitude = 0*rowdata.df.ix[:,'TimeStamp (sec)'] + longitude = 0*rowdata.df.ix[:,'TimeStamp (sec)'] + return [latitude,longitude] + except AttributeError: + return [pd.Series([]),pd.Series([])] + + return [pd.Series([]),pd.Series([])] def get_workouts(ids,userid): goodids = [] @@ -822,6 +826,10 @@ def new_workout_from_file(r,f2, def split_workout(r,parent,splitsecond,splitmode): data,row = getrowdata_db(id=parent.id) + latitude,longitude = get_latlon(parent.id) + if not latitude.empty and not longitude.empty: + data[' latitude'] = latitude + data[' longitude'] = longitude data['time'] = data['time']/1000. diff --git a/rowers/templates/advancededit.html b/rowers/templates/advancededit.html index 6db5b03c..55de324b 100644 --- a/rowers/templates/advancededit.html +++ b/rowers/templates/advancededit.html @@ -133,18 +133,30 @@ Plot the Power Histogram of this workout

-
+

{% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %} Sensor Fusion {% else %} - Dist Metrics Plot + Sensor Fusion {% endif %}

Merge data from another source into this workout

+
+

+ {% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %} + Split Workout + {% else %} + Split Workout + {% endif %} +

+

+ Split workout into two seperate workouts +

+
diff --git a/rowers/templates/advancedotw.html b/rowers/templates/advancedotw.html index abbccd67..3e3a2e39 100644 --- a/rowers/templates/advancedotw.html +++ b/rowers/templates/advancedotw.html @@ -195,7 +195,7 @@ {% if user.rower.rowerplan == 'pro' or user.rower.rowerplan == 'coach' %} Sensor Fusion {% else %} - Split Workout + Sensor Fusion {% endif %}