From 33afb8800b0ba323777bc196221360f58ce96115 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 18 Feb 2017 09:50:09 +0100 Subject: [PATCH 1/5] Initial version of workout choice field --- rowers/forms.py | 4 + rowers/models.py | 22 ++++-- rowers/templates/team_compare_select.html | 95 +++++++++++++++++++++++ rowers/urls.py | 7 ++ rowers/views.py | 92 +++++++++++++++++++++- 5 files changed, 211 insertions(+), 9 deletions(-) create mode 100644 rowers/templates/team_compare_select.html diff --git a/rowers/forms.py b/rowers/forms.py index 23d67fb4..37e6c7aa 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -252,3 +252,7 @@ class StatsOptionsForm(forms.Form): paddle = forms.BooleanField(initial=False,required=False) snow = forms.BooleanField(initial=False,required=False) other = forms.BooleanField(initial=False,required=False) + +class WorkoutMultipleCompareForm(forms.Form): + workouts = forms.ModelMultipleChoiceField(queryset=Workout.objects.all(), + widget=forms.CheckboxSelectMultiple()) diff --git a/rowers/models.py b/rowers/models.py index c1dd3bbb..d43d28c6 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -398,17 +398,23 @@ class Workout(models.Model): privacy = models.CharField(default='visible',max_length=30, choices=privacychoices) - def __str__(self): + def __unicode__(self): date = self.date name = self.name + distance = str(self.distance) + ownerfirst = self.user.user.first_name + ownerlast = self.user.user.last_name + duration = self.duration - try: - stri = date.strftime('%Y-%m-%d')+'_'+name - except AttributeError: - stri = str(date)+'_'+name - - + stri = u'{d}_{n}_{dist}m_{duration:%H:%M:%S}_{ownerfirst}_{ownerlast}'.format( + d = date.strftime('%Y-%m-%d'), + n = name, + dist = distance, + duration = duration, + ownerfirst = ownerfirst, + ownerlast = ownerlast, + ) return stri @@ -890,7 +896,7 @@ class WorkoutComment(models.Model): def __unicode__(self): return u'Comment to: {w} by {u1} {u2}'.format( - w=self.workout.name, + w=self.workout, u1 = self.user.first_name, u2 = self.user.last_name, ) diff --git a/rowers/templates/team_compare_select.html b/rowers/templates/team_compare_select.html new file mode 100644 index 00000000..67988ec9 --- /dev/null +++ b/rowers/templates/team_compare_select.html @@ -0,0 +1,95 @@ +{% extends "base.html" %} +{% load staticfiles %} +{% load rowerfilters %} + +{% block title %}Workouts{% endblock %} + +{% block content %} + + + + +
+ + Select start and end date for a date range: +
+ + {% if team %} +
+ {% else %} + + {% endif %} + + + {{ dateform.as_table }} +
+ {% csrf_token %} +
+
+ +
+
+ {% if team %} +
+ {% else %} + +{% endif %} +
+ +
+
+ +
+
+
+ +
+ +
+ {% if team %} +

{{ team.name }} Team Workouts

+ {% else %} +

Team Workouts

+ {% endif %} + + {% if workouts %} + + {{ form.as_table }} +
+{% else %} +

No workouts found

+{% endif %} +
+ + + +
+ + {% if workouts.has_previous %} + {% if request.GET.q %} + < + {% else %} + < + {% endif %} + {% endif %} + + + Page {{ workouts.number }} of {{ workouts.paginator.num_pages }}. + + + {% if workouts.has_next %} + {% if request.GET.q %} + > + {% else %} + > + {% endif %} + {% endif %} + + +{% endblock %} diff --git a/rowers/urls.py b/rowers/urls.py index e54bbac2..fb71fa02 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -113,6 +113,13 @@ urlpatterns = [ url(r'^list-workouts/team/(?P\d+)/$',views.workouts_view), url(r'^list-workouts/(?P\w+.*)/(?P\w+.*)$',views.workouts_view), url(r'^list-workouts/$',views.workouts_view), + url(r'^team-compare-select/c/(?P\w+.*)/$',views.team_comparison_select), + url(r'^team-compare-select/s/(?P\w+.*)/$',views.team_comparison_select), + url(r'^team-compare-select/c/(?P\w+.*)/s/(?P\w+.*)$',views.team_comparison_select), + url(r'^team-compare-select/team/(?P\d+)/(?P\w+.*)/(?P\w+.*)$',views.team_comparison_select), + url(r'^team-compare-select/team/(?P\d+)/$',views.team_comparison_select), + url(r'^team-compare-select/(?P\w+.*)/(?P\w+.*)$',views.team_comparison_select), + url(r'^team-compare-select/$',views.team_comparison_select), url(r'^list-graphs/$',views.graphs_view), url(r'^(?P\d+)/ote-bests/(?P\w+.*)/(?P\w+.*)$',views.rankings_view), url(r'^(?P\d+)/ote-bests/(?P\d+)$',views.rankings_view), diff --git a/rowers/views.py b/rowers/views.py index 2e50b45e..4e717cbd 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -26,7 +26,7 @@ from rowers.forms import ( StatsOptionsForm,PredictedPieceForm,DateRangeForm,DeltaDaysForm, EmailForm, RegistrationForm, RegistrationFormTermsOfService, RegistrationFormUniqueEmail,CNsummaryForm,UpdateWindForm, - UpdateStreamForm + UpdateStreamForm,WorkoutMultipleCompareForm, ) from rowers.models import Workout, User, Rower, WorkoutForm,FavoriteChart from rowers.models import ( @@ -1955,7 +1955,97 @@ def workout_setprivate_view(request,id, }) return HttpResponseRedirect(url) +# Team comparison +@login_required() +def team_comparison_select(request, + startdatestring="", + enddatestring="", + message='', + successmessage='', + startdate=timezone.now()-datetime.timedelta(days=30), + enddate=timezone.now()+datetime.timedelta(days=1), + teamid=0): + try: + r = Rower.objects.get(user=request.user) + except Rower.DoesNotExist: + raise Http404("Rower doesn't exist") + + if request.method == 'POST': + dateform = DateRangeForm(request.POST) + if dateform.is_valid(): + startdate = dateform.cleaned_data['startdate'] + enddate = dateform.cleaned_data['enddate'] + else: + dateform = DateRangeForm(initial={ + 'startdate':startdate, + 'enddate':enddate, + }) + + startdate = datetime.datetime.combine(startdate,datetime.time()) + enddate = datetime.datetime.combine(enddate,datetime.time(23,59,59)) + enddate = enddate+datetime.timedelta(days=1) + + if startdatestring: + startdate = iso8601.parse_date(startdatestring) + if enddatestring: + enddate = iso8601.parse_date(enddatestring) + + if enddate < startdate: + s = enddate + enddate = startdate + startdate = s + + try: + theteam = Team.objects.get(id=teamid) + except Team.DoesNotExist: + raise Http404("Team doesn't exist") + + if theteam.viewing == 'allmembers' or theteam.manager == request.user: + workouts = Workout.objects.filter(team=theteam, + startdatetime__gte=startdate, + startdatetime__lte=enddate).order_by("-date", "-starttime") + elif theteam.viewing == 'coachonly': + workouts = Workout.objects.filter(team=theteam,user=r, + startdatetime__gte=startdate, + startdatetime__lte=enddate).order_by("-date","-starttime") + + + else: + theteam = None + workouts = Workout.objects.filter(user=r, + startdatetime__gte=startdate, + startdatetime__lte=enddate).order_by("-date", "-starttime") + + query = request.GET.get('q') + if query: + query_list = query.split() + workouts = workouts.filter( + reduce(operator.and_, + (Q(name__icontains=q) for q in query_list)) | + reduce(operator.and_, + (Q(notes__icontains=q) for q in query_list)) + ) + + workouts1 = workouts + + + + form = WorkoutMultipleCompareForm() + form.fields["workouts"].queryset = workouts1 + + return render(request, 'team_compare_select.html', + {'workouts': workouts, + 'message': message, + 'successmessage':successmessage, + 'dateform':dateform, + 'startdate':startdate, + 'enddate':enddate, + 'team':theteam, + 'form':form, + }) + + # List Workouts @login_required() def workouts_view(request,message='',successmessage='', From 46358ba4d4eadca400507f52325eeb6f19856269 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 18 Feb 2017 11:55:14 +0100 Subject: [PATCH 2/5] draft view and chart for multi comparison --- rowers/forms.py | 10 ++++ rowers/interactiveplots.py | 57 ++++++++++++++++++++++- rowers/models.py | 2 +- rowers/templates/team_compare_select.html | 47 +++++++------------ rowers/urls.py | 1 + rowers/views.py | 31 +++++++++--- 6 files changed, 109 insertions(+), 39 deletions(-) diff --git a/rowers/forms.py b/rowers/forms.py index 37e6c7aa..cf29f328 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -256,3 +256,13 @@ class StatsOptionsForm(forms.Form): class WorkoutMultipleCompareForm(forms.Form): workouts = forms.ModelMultipleChoiceField(queryset=Workout.objects.all(), widget=forms.CheckboxSelectMultiple()) + +from rowers.interactiveplots import axlabels + +axlabels.pop('None') +axlabels = list(axlabels.items()) + + +class ChartParamChoiceForm(forms.Form): + xparam = forms.ChoiceField(choices=axlabels,initial='distance') + yparam = forms.ChoiceField(choices=axlabels,initial='hr') diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index c59b4124..8987651c 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -52,7 +52,7 @@ import rowers.dataprep as dataprep axlabels = { 'time': 'Time', 'distance': 'Distance (m)', - 'cumdist': 'Distance (m)', + 'cumdist': 'Cumulative Distance (m)', 'hr': 'Heart Rate (bpm)', 'spm': 'Stroke Rate (spm)', 'pace': 'Pace (/500m)', @@ -1579,6 +1579,61 @@ def interactive_bar_chart(id=0,promember=0): return [script,div] +def interactive_multiple_compare_chart(ids,xparam,yparam,plottype='line'): + columns = [xparam,yparam, + 'ftime','distance','fpace', + 'power','hr','spm', + 'time','pace','workoutstate', + 'workoutid'] + + datadf = dataprep.getsmallrowdata_db(columns,ids=ids) + + yparamname = axlabels[yparam] + + datadf = datadf[datadf[yparam] > 0] + + datadf = datadf[datadf[xparam] > 0] + + # check if dataframe not empty + if datadf.empty: + return ['','

No non-zero data in selection

','',''] + + + + if xparam=='distance': + xaxmax = datadf['distance'].max() + xaxmin = datadf['distance'].min() + else: + xaxmax = yaxmaxima[xparam] + xaxmin = yaxminima[xparam] + + + x_axis_type = 'linear' + y_axis_type = 'linear' + + # Add hover to this comma-separated string and see what changes + if (promember==1): + TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,resize,crosshair' + else: + TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,crosshair' + + plot = Figure(x_axis_type=x_axis_type,y_axis_type=y_axis_type, + tools=TOOLS, + toolbar_location="above", + toolbar_sticky=False) + + + for key,grp in datadf.groupby(['workoutid']): + print key + + script, div = components(plot) + + + + return [script,div] + + + def interactive_comparison_chart(id1=0,id2=0,xparam='distance',yparam='spm', promember=0,plottype='line'): diff --git a/rowers/models.py b/rowers/models.py index d43d28c6..b78740e6 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -407,7 +407,7 @@ class Workout(models.Model): ownerlast = self.user.user.last_name duration = self.duration - stri = u'{d}_{n}_{dist}m_{duration:%H:%M:%S}_{ownerfirst}_{ownerlast}'.format( + stri = u'{d} {n} {dist}m {duration:%H:%M:%S} {ownerfirst} {ownerlast}'.format( d = date.strftime('%Y-%m-%d'), n = name, dist = distance, diff --git a/rowers/templates/team_compare_select.html b/rowers/templates/team_compare_select.html index 67988ec9..3908fb47 100644 --- a/rowers/templates/team_compare_select.html +++ b/rowers/templates/team_compare_select.html @@ -51,14 +51,17 @@
-
- {% if team %} -

{{ team.name }} Team Workouts

- {% else %} -

Team Workouts

- {% endif %} +
+

{{ team.name }} Team Workouts

+
+ +
+
+ {% if workouts %} + + {{ form.as_table }}
@@ -66,30 +69,14 @@

No workouts found

{% endif %}
+
+ {% csrf_token %} + + {{ chartform.as_table }} +
+ +
+
- -
- - {% if workouts.has_previous %} - {% if request.GET.q %} - < - {% else %} - < - {% endif %} - {% endif %} - - - Page {{ workouts.number }} of {{ workouts.paginator.num_pages }}. - - - {% if workouts.has_next %} - {% if request.GET.q %} - > - {% else %} - > - {% endif %} - {% endif %} - - {% endblock %} diff --git a/rowers/urls.py b/rowers/urls.py index fb71fa02..0b1f90c8 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -211,6 +211,7 @@ urlpatterns = [ url(r'^workout/(\d+)/stravauploadw/$',views.workout_strava_upload_view), url(r'^workout/(\d+)/recalcsummary/$',views.workout_recalcsummary_view), url(r'^workout/(\d+)/sporttracksuploadw/$',views.workout_sporttracks_upload_view), + url(r'^multi-compare$',views.multi_compare_view), url(r'^me/teams/c/(?P\w+.*)/s/(?P\w+.*)$',views.rower_teams_view), url(r'^me/teams/s/(?P\w+.*)$',views.rower_teams_view), url(r'^me/teams/c/(?P\w+.*)$',views.rower_teams_view), diff --git a/rowers/views.py b/rowers/views.py index 4e717cbd..0efd134c 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -26,7 +26,7 @@ from rowers.forms import ( StatsOptionsForm,PredictedPieceForm,DateRangeForm,DeltaDaysForm, EmailForm, RegistrationForm, RegistrationFormTermsOfService, RegistrationFormUniqueEmail,CNsummaryForm,UpdateWindForm, - UpdateStreamForm,WorkoutMultipleCompareForm, + UpdateStreamForm,WorkoutMultipleCompareForm,ChartParamChoiceForm ) from rowers.models import Workout, User, Rower, WorkoutForm,FavoriteChart from rowers.models import ( @@ -2027,12 +2027,10 @@ def team_comparison_select(request, (Q(notes__icontains=q) for q in query_list)) ) - workouts1 = workouts - - - form = WorkoutMultipleCompareForm() - form.fields["workouts"].queryset = workouts1 + form.fields["workouts"].queryset = workouts + + chartform = ChartParamChoiceForm() return render(request, 'team_compare_select.html', {'workouts': workouts, @@ -2043,9 +2041,28 @@ def team_comparison_select(request, 'enddate':enddate, 'team':theteam, 'form':form, + 'chartform':chartform, }) - +@login_required() +def multi_compare_view(request): + if request.method == 'POST': + form = WorkoutMultipleCompareForm(request.POST) + chartform = ChartParamChoiceForm(request.POST) + if form.is_valid() and chartform.is_valid(): + cd = form.cleaned_data + workouts = cd['workouts'] + xparam = chartform.cleaned_data['xparam'] + yparam = chartform.cleaned_data['yparam'] + ids = [w.id for w in workouts] + res = interactive_multiple_compare_chart(ids,xparam,yparam) + return HttpResponse("Form is valid") + else: + return HttpResponse("Form is not valid") + else: + url = reverse(workouts_view) + return HttpResponseRedirect(url) + # List Workouts @login_required() def workouts_view(request,message='',successmessage='', From 91d82703f7ec5e5d9c3bb1896b0f55ff9f4ae4bd Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sat, 18 Feb 2017 13:25:21 +0100 Subject: [PATCH 3/5] multi plot sort of functions --- rowers/interactiveplots.py | 63 +++++++++++++++++++++++++++--- rowers/templates/multicompare.html | 49 +++++++++++++++++++++++ rowers/views.py | 18 ++++++++- 3 files changed, 122 insertions(+), 8 deletions(-) create mode 100644 rowers/templates/multicompare.html diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 8987651c..f71d7e45 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -8,6 +8,8 @@ from rowingdata import rowingdata as rrdata from django.utils import timezone +from bokeh.palettes import Dark2_8 as palette +import itertools from bokeh.plotting import figure, ColumnDataSource, Figure,curdoc from bokeh.models import CustomJS,Slider from bokeh.charts import Histogram,HeatMap @@ -1579,7 +1581,8 @@ def interactive_bar_chart(id=0,promember=0): return [script,div] -def interactive_multiple_compare_chart(ids,xparam,yparam,plottype='line'): +def interactive_multiple_compare_chart(ids,xparam,yparam,plottype='line', + promember=0): columns = [xparam,yparam, 'ftime','distance','fpace', 'power','hr','spm', @@ -1590,9 +1593,9 @@ def interactive_multiple_compare_chart(ids,xparam,yparam,plottype='line'): yparamname = axlabels[yparam] - datadf = datadf[datadf[yparam] > 0] + #datadf = datadf[datadf[yparam] > 0] - datadf = datadf[datadf[xparam] > 0] + #datadf = datadf[datadf[xparam] > 0] # check if dataframe not empty if datadf.empty: @@ -1617,15 +1620,63 @@ def interactive_multiple_compare_chart(ids,xparam,yparam,plottype='line'): else: TOOLS = 'pan,box_zoom,wheel_zoom,reset,tap,crosshair' + if yparam == 'pace': + y_axis_type = 'datetime' + yaxmax = 90. + yaxmin = 150. + + if xparam == 'time': + x_axis_type = 'datetime' + + if xparam != 'time': + xvals = xaxmin+np.arange(100)*(xaxmax-xaxmin)/100. + else: + xvals = np.arange(100) + plot = Figure(x_axis_type=x_axis_type,y_axis_type=y_axis_type, tools=TOOLS, toolbar_location="above", toolbar_sticky=False) + colors = itertools.cycle(palette) - for key,grp in datadf.groupby(['workoutid']): - print key - + for id,color in itertools.izip(ids,colors): + group = datadf[datadf['workoutid']==int(id)].copy() + group.sort_values(by='time',ascending=True,inplace=True) + group['x'] = group[xparam] + group['y'] = group[yparam] + source = ColumnDataSource( + group + ) + plot.line('x','y',source=source,color=color,legend=str(id)) + + plot.legend.location='top_left' + plot.xaxis.axis_label = axlabels[xparam] + plot.yaxis.axis_label = axlabels[yparam] + + if (xparam != 'time') and (xparam != 'distance') and (xparam != 'cumdist'): + xrange1 = Range1d(start=yaxminima[xparam],end=yaxmaxima[xparam]) + plot.x_range = xrange1 + + if xparam == 'time': + xrange1 = Range1d(start=xaxmin,end=xaxmax) + plot.x_range = xrange1 + plot.xaxis[0].formatter = DatetimeTickFormatter( + hours = ["%H"], + minutes = ["%M"], + seconds = ["%S"], + days = ["0"], + months = [""], + years = [""] + ) + + + if yparam == 'pace': + plot.yaxis[0].formatter = DatetimeTickFormatter( + seconds = ["%S"], + minutes = ["%M"] + ) + script, div = components(plot) diff --git a/rowers/templates/multicompare.html b/rowers/templates/multicompare.html new file mode 100644 index 00000000..14751e7f --- /dev/null +++ b/rowers/templates/multicompare.html @@ -0,0 +1,49 @@ +{% extends "base.html" %} +{% load staticfiles %} +{% load rowerfilters %} + +{% block title %}View Comparison {% endblock %} + +{% block content %} + + + + + {{ interactiveplot |safe }} + + + + + +
+ + +

Interactive Comparison

+ + +
+ {{ the_div|safe }} +
+ +
+ +{% endblock %} diff --git a/rowers/views.py b/rowers/views.py index 0efd134c..b66d54a6 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -2046,6 +2046,13 @@ def team_comparison_select(request, @login_required() def multi_compare_view(request): + promember=0 + if not request.user.is_anonymous(): + r = Rower.objects.get(user=request.user) + result = request.user.is_authenticated() and ispromember(request.user) + if result: + promember=1 + if request.method == 'POST': form = WorkoutMultipleCompareForm(request.POST) chartform = ChartParamChoiceForm(request.POST) @@ -2055,8 +2062,15 @@ def multi_compare_view(request): xparam = chartform.cleaned_data['xparam'] yparam = chartform.cleaned_data['yparam'] ids = [w.id for w in workouts] - res = interactive_multiple_compare_chart(ids,xparam,yparam) - return HttpResponse("Form is valid") + res = interactive_multiple_compare_chart(ids,xparam,yparam, + promember=promember) + script = res[0] + div = res[1] + return render(request,'multicompare.html', + {'interactiveplot':script, + 'the_div':div, + 'promember':promember, + }) else: return HttpResponse("Form is not valid") else: From e0b719da4e9704ed8ee270405aa72d9563d9fa3f Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sun, 19 Feb 2017 10:45:48 +0100 Subject: [PATCH 4/5] added links to strava, st, c2 under button --- rowers/c2stuff.py | 3 +- rowers/stravastuff.py | 3 +- rowers/templates/export.html | 151 ++++++++++++++++++----------------- rowers/views.py | 16 +++- 4 files changed, 96 insertions(+), 77 deletions(-) diff --git a/rowers/c2stuff.py b/rowers/c2stuff.py index aa29acb3..9cd176d1 100644 --- a/rowers/c2stuff.py +++ b/rowers/c2stuff.py @@ -449,6 +449,7 @@ def get_username(access_token): try: res = me_json['data']['username'] + id = me_json['data']['id'] except KeyError: res = None @@ -482,7 +483,7 @@ def process_callback(request): access_token = get_token(code) - username = get_username(access_token) + username,id = get_username(access_token) return HttpResponse("got a user name: %s" % username) diff --git a/rowers/stravastuff.py b/rowers/stravastuff.py index 144787f2..b0bf84e4 100644 --- a/rowers/stravastuff.py +++ b/rowers/stravastuff.py @@ -234,7 +234,7 @@ def handle_stravaexport(f2,workoutname,stravatoken,description=''): act = client.upload_activity(f2,'tcx',name=workoutname) try: - res = act.wait(poll_interval=5.0) + res = act.wait(poll_interval=5.0,timeout=30) message = 'Workout successfully synchronized to Strava' except: res = 0 @@ -246,6 +246,7 @@ def handle_stravaexport(f2,workoutname,stravatoken,description=''): act = client.update_activity(res.id,activity_type='Rowing',description=description) else: message = 'Strava upload timed out.' + return (0,message) return (res.id,message) diff --git a/rowers/templates/export.html b/rowers/templates/export.html index 910cbd10..b2cf83e9 100644 --- a/rowers/templates/export.html +++ b/rowers/templates/export.html @@ -6,88 +6,91 @@ {% block content %}
+ + +

Export Workout

+
+

+ Edit Workout +

+
+
+

+ Advanced Edit +

+ +
+ +
+

+ Click on the icon to upload this workout to your site of choice. A checkmark indicates that the workout has already been uploaded. If the button is grayed out, click it to authorize the connection to that site. Use TCX or CSV export to email a TCX or CSV file of your workout to yourself. +

+ +
-

Export Workout

- -
-

- Edit Workout -

-
-
-

- Advanced Edit -

- -
- -
-

-Click on the icon to upload this workout to your site of choice. A checkmark indicates that the workout has already been uploaded. If the button is grayed out, click it to authorize the connection to that site. Use TCX or CSV export to email a TCX or CSV file of your workout to yourself. -

- + {% if workout.uploadedtoc2 == 0 %} + {% if user.rower.c2token == None or user.rower.c2token == '' %} +
+ + C2 icon +
+ {% else %} +
+ Concept2 icon +
+ {% endif %} + {% else %} +
+ + Concept2 icon
- -{% if workout.uploadedtoc2 == 0 %} - {% if user.rower.c2token == None or user.rower.c2token == '' %} -
- - C2 icon -
- {% else %} -
- Concept2 icon -
- {% endif %} -{% else %} -
- Concept2 icon -
{% endif %} {% if workout.uploadedtostrava == 0 %} - {% if user.rower.stravatoken == None or user.rower.stravatoken == '' %} -
- - Strava icon -
- {% else %} -
- Strava icon -
- {% endif %} +{% if user.rower.stravatoken == None or user.rower.stravatoken == '' %} +
+ + Strava icon +
{% else %} -
- Concept2 icon -
+
+ Strava icon +
+{% endif %} +{% else %} +
+ + Concept2 icon +
{% endif %} {% if workout.uploadedtosporttracks == 0 %} - {% if user.rower.sporttrackstoken == None or user.rower.sporttrackstoken == '' %} -
- - SportTracks icon -
- {% else %} -
- - SportTracks icon -
- {% endif %} +{% if user.rower.sporttrackstoken == None or user.rower.sporttrackstoken == '' %} +
+ + SportTracks icon +
{% else %} -
- Concept2 icon -
+
+ + SportTracks icon +
{% endif %} -
- - TCX Export -
+{% else %} +
+ + Concept2 icon +
+{% endif %} +
+ + TCX Export +
-
- - CSV Export -
+
+ + CSV Export +
@@ -100,16 +103,16 @@ You only need to do this once. After that, the site will have access until you revoke the authorization for the "rowingdata" app.

-

connect with strava

+

connect with strava

-

connect with Concept2

+

connect with Concept2

-

connect with SportTracks

+

connect with SportTracks

@@ -117,4 +120,4 @@ revoke the authorization for the "rowingdata" app.

-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/rowers/views.py b/rowers/views.py index c264a9f3..42431190 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -810,7 +810,10 @@ def workout_strava_upload_view(request,id=0): message = mes w.uploadedtostrava = -1 w.save() - os.remove(tcxfile) + try: + os.remove(tcxfile) + except WindowsError: + pass url = reverse(workout_export_view, kwargs = { 'id':str(w.id), @@ -3423,6 +3426,16 @@ def workout_export_view(request,id=0, message="", successmessage=""): except Workout.DoesNotExist: raise Http404("Workout doesn't exist") + try: + thetoken = c2_open(request.user) + except C2NoTokenError: + thetoken = 0 + + if (checkworkoutuser(request.user,row)) and thetoken: + c2userid = c2stuff.get_userid(thetoken) + else: + c2userid = 0 + form = WorkoutForm(instance=row) g = GraphImage.objects.filter(workout=row).order_by("-creationdatetime") # check if user is owner of this workout @@ -3439,6 +3452,7 @@ def workout_export_view(request,id=0, message="", successmessage=""): {'workout':row, 'message':message, 'successmessage':successmessage, + 'c2userid':c2userid, }) # list of comments to a workout From 0f494020238379e6a5f30131923501d285d7c036 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Sun, 19 Feb 2017 16:45:53 +0100 Subject: [PATCH 5/5] rough version of comparison --- rowers/forms.py | 6 ++ rowers/interactiveplots.py | 35 ++++++++- rowers/stravastuff.py | 19 +++-- rowers/templates/biginteractive1.html | 92 +++++++++++------------ rowers/templates/list_workouts.html | 7 ++ rowers/templates/multicompare.html | 73 ++++++++++-------- rowers/templates/team_compare_select.html | 20 ++++- rowers/views.py | 14 +++- 8 files changed, 171 insertions(+), 95 deletions(-) diff --git a/rowers/forms.py b/rowers/forms.py index cf29f328..6f251a2e 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -264,5 +264,11 @@ axlabels = list(axlabels.items()) class ChartParamChoiceForm(forms.Form): + plotchoices = ( + ('line','Line Plot'), + ('scatter','Scatter Plot'), + ) xparam = forms.ChoiceField(choices=axlabels,initial='distance') yparam = forms.ChoiceField(choices=axlabels,initial='hr') + plottype = forms.ChoiceField(choices=plotchoices,initial='scatter') + teamid = forms.IntegerField(widget=forms.HiddenInput()) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index f71d7e45..6789d0ce 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -1582,7 +1582,8 @@ def interactive_bar_chart(id=0,promember=0): return [script,div] def interactive_multiple_compare_chart(ids,xparam,yparam,plottype='line', - promember=0): + promember=0, + labeldict=None): columns = [xparam,yparam, 'ftime','distance','fpace', 'power','hr','spm', @@ -1636,21 +1637,49 @@ def interactive_multiple_compare_chart(ids,xparam,yparam,plottype='line', plot = Figure(x_axis_type=x_axis_type,y_axis_type=y_axis_type, tools=TOOLS, toolbar_location="above", + plot_width=920, toolbar_sticky=False) colors = itertools.cycle(palette) + cntr = 0 + for id,color in itertools.izip(ids,colors): group = datadf[datadf['workoutid']==int(id)].copy() group.sort_values(by='time',ascending=True,inplace=True) group['x'] = group[xparam] group['y'] = group[yparam] + + ymean = group['y'].mean() + ylabel = Label(x=100,y=70+20*cntr, + x_units='screen',y_units='screen', + text=yparam+": {ymean:6.2f}".format(ymean=ymean), + background_fill_alpha=.7, + text_color=color, + ) + if yparam != 'time' and yparam != 'pace': + plot.add_layout(ylabel) + + print cntr,id,len(group),ymean + source = ColumnDataSource( group ) - plot.line('x','y',source=source,color=color,legend=str(id)) - plot.legend.location='top_left' + if labeldict: + legend=labeldict[id] + else: + legend=str(id) + + if plottype=='line': + plot.line('x','y',source=source,color=color,legend=legend) + else: + plot.scatter('x','y',source=source,color=color,legend=legend, + fill_alpha=0.4,line_color=None) + + cntr += 1 + + plot.legend.location='bottom_right' plot.xaxis.axis_label = axlabels[xparam] plot.yaxis.axis_label = axlabels[yparam] diff --git a/rowers/stravastuff.py b/rowers/stravastuff.py index b0bf84e4..67780a93 100644 --- a/rowers/stravastuff.py +++ b/rowers/stravastuff.py @@ -39,15 +39,18 @@ from rowsandall_app.settings import C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SEC def ewmovingaverage(interval,window_size): # Experimental code using Exponential Weighted moving average - intervaldf = pd.DataFrame({'v':interval}) - idf_ewma1 = intervaldf.ewm(span=window_size) - idf_ewma2 = intervaldf[::-1].ewm(span=window_size) + try: + intervaldf = pd.DataFrame({'v':interval}) + idf_ewma1 = intervaldf.ewm(span=window_size) + idf_ewma2 = intervaldf[::-1].ewm(span=window_size) + + i_ewma1 = idf_ewma1.mean().ix[:,'v'] + i_ewma2 = idf_ewma2.mean().ix[:,'v'] - i_ewma1 = idf_ewma1.mean().ix[:,'v'] - i_ewma2 = idf_ewma2.mean().ix[:,'v'] - - interval2 = np.vstack((i_ewma1,i_ewma2[::-1])) - interval2 = np.mean( interval2, axis=0) # average + interval2 = np.vstack((i_ewma1,i_ewma2[::-1])) + interval2 = np.mean( interval2, axis=0) # average + except ValueError: + interval2 = interval return interval2 diff --git a/rowers/templates/biginteractive1.html b/rowers/templates/biginteractive1.html index 3613da98..97061e31 100644 --- a/rowers/templates/biginteractive1.html +++ b/rowers/templates/biginteractive1.html @@ -6,57 +6,57 @@ {% block content %} - - + + - {{ interactiveplot |safe }} +{{ interactiveplot |safe }} - - + +
- - -

Interactive Plot

- - {% if user.is_authenticated and mayedit %} -
-

- Edit Workout -

-
-
-

- Advanced Edit -

- -
- {% endif %} - -
+ + +

Interactive Plot

+ + {% if user.is_authenticated and mayedit %} +
+

+ Edit Workout +

+
+
+

+ Advanced Edit +

+ +
+ {% endif %} + +
{{ the_div|safe }} +
+
-
- -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/rowers/templates/list_workouts.html b/rowers/templates/list_workouts.html index 891e596b..33408fe0 100644 --- a/rowers/templates/list_workouts.html +++ b/rowers/templates/list_workouts.html @@ -112,6 +112,13 @@
+ {% if team %} + + {% endif %}
{% if announcements %}

What's New?

diff --git a/rowers/templates/multicompare.html b/rowers/templates/multicompare.html index 14751e7f..c5724579 100644 --- a/rowers/templates/multicompare.html +++ b/rowers/templates/multicompare.html @@ -6,44 +6,53 @@ {% block content %} - - + + - {{ interactiveplot |safe }} +{{ interactiveplot |safe }} - - + +
- - -

Interactive Comparison

- - -
+

Interactive Comparison

+ + +
+ Team Page +
+
+ + + +
{{ the_div|safe }} -
- +
+
{% endblock %} diff --git a/rowers/templates/team_compare_select.html b/rowers/templates/team_compare_select.html index 3908fb47..d855a82b 100644 --- a/rowers/templates/team_compare_select.html +++ b/rowers/templates/team_compare_select.html @@ -13,8 +13,7 @@
- Select start and end date for a date range: -
+
{% if team %}
@@ -69,12 +68,25 @@

No workouts found

{% endif %}
-
+
+

Warning: You are on an experimental part of the site. Use at your own risk.

+

Select two or more workouts on the left, set your plot settings below, + and press submit"

{% csrf_token %} {{ chartform.as_table }}
- +
+

+ +

+
+
+

You can use the date and search forms above to search through all + workouts from this team.

+

TIP: Agree with your team members to put tags (e.g. '8x500m') in the notes section of + your workouts. That makes it easy to search.

+
diff --git a/rowers/views.py b/rowers/views.py index 42431190..cd06ba03 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -2036,7 +2036,7 @@ def team_comparison_select(request, form = WorkoutMultipleCompareForm() form.fields["workouts"].queryset = workouts - chartform = ChartParamChoiceForm() + chartform = ChartParamChoiceForm(initial={'teamid':theteam.id}) return render(request, 'team_compare_select.html', {'workouts': workouts, @@ -2067,15 +2067,25 @@ def multi_compare_view(request): workouts = cd['workouts'] xparam = chartform.cleaned_data['xparam'] yparam = chartform.cleaned_data['yparam'] + plottype = chartform.cleaned_data['plottype'] + teamid = chartform.cleaned_data['teamid'] ids = [w.id for w in workouts] + labeldict = { + w.id: w.__unicode__() for w in workouts + } + res = interactive_multiple_compare_chart(ids,xparam,yparam, - promember=promember) + promember=promember, + plottype=plottype, + labeldict=labeldict) script = res[0] div = res[1] + return render(request,'multicompare.html', {'interactiveplot':script, 'the_div':div, 'promember':promember, + 'teamid':teamid, }) else: return HttpResponse("Form is not valid")