From 822b0045ec37883e17f18aedfacc77cd15193264 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 23 Dec 2016 08:47:49 +0100 Subject: [PATCH 1/7] added fav charts and user --- rowers/serializers.py | 16 +++++++++++++++- rowers/urls.py | 23 ++++++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/rowers/serializers.py b/rowers/serializers.py index e0c8b560..8842d18d 100644 --- a/rowers/serializers.py +++ b/rowers/serializers.py @@ -1,5 +1,5 @@ from rest_framework import serializers -from rowers.models import Workout,Rower,StrokeData +from rowers.models import Workout,Rower,StrokeData,FavoriteChart import datetime @@ -8,6 +8,7 @@ class RowerSerializer(serializers.HyperlinkedModelSerializer): class Meta: model = Rower fields = ( + 'id', 'weightcategory', 'max', 'rest', @@ -19,6 +20,19 @@ class RowerSerializer(serializers.HyperlinkedModelSerializer): 'ftp', ) +class FavoriteChartSerializer(serializers.ModelSerializer): + class Meta: + model = FavoriteChart + fields = ( + 'id', + 'xparam', + 'yparam1', + 'yparam2', + 'plottype', + 'workouttype', + 'reststrokes', + 'user' + ) class WorkoutSerializer(serializers.ModelSerializer): class Meta: diff --git a/rowers/urls.py b/rowers/urls.py index 8ca46c3e..d21b2692 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -2,7 +2,7 @@ from django.conf import settings from django.conf.urls import url, include from django.contrib.auth.models import User -from models import Workout,Rower,StrokeData +from models import Workout,Rower,StrokeData,FavoriteChart from rest_framework import routers, serializers, viewsets,permissions from rest_framework.urlpatterns import format_suffix_patterns @@ -19,6 +19,7 @@ from rowers.serializers import ( WorkoutSerializer, RowerSerializer, StrokeDataSerializer, + FavoriteChartSerializer, ) class WorkoutViewSet(viewsets.ModelViewSet): @@ -36,12 +37,32 @@ class WorkoutViewSet(viewsets.ModelViewSet): IsOwnerOrNot, ) +class RowerViewSet(viewsets.ModelViewSet): + model = Rower + serializer_class = RowerSerializer + #queryset = Rower.objects.all() + + def get_queryset(self): + r = Rower.objects.filter(user=self.request.user) + return r + + permission_classes = ( + IsOwnerOrNot, + ) + +class FavoriteChartViewSet(viewsets.ModelViewSet): + model = FavoriteChart + serializer_class = FavoriteChartSerializer + queryset = FavoriteChart.objects.all() + class StrokeDataViewSet(viewsets.ModelViewSet): serializer_class = StrokeDataSerializer # Routers provide an easy way of automatically determining the URL conf. router = routers.DefaultRouter() router.register(r'api/workouts',WorkoutViewSet, 'workout') +router.register(r'api/me',RowerViewSet, 'rower') +router.register(r'api/charts',FavoriteChartViewSet, 'charts') handler500 = 'views.error500_view' From 145435e5739ea7e81134bea9681ce99a82160bfd Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Mon, 2 Jan 2017 10:21:39 +0100 Subject: [PATCH 2/7] Rowpro log warning message --- rowers/views.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rowers/views.py b/rowers/views.py index 01c3116c..167ecb92 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -3759,6 +3759,13 @@ def workout_upload_view(request,message=""): args=[str(message)]) response = HttpResponseRedirect(url) return response + + if fileformat == 'rowprolog': + message = "This RowPro logbook summary does not contain stroke data. Please use the Stroke Data CSV file for the individual workout in your log." + url = reverse(workout_upload_view, + args=[str(message)]) + response = HttpResponseRedirect(url) + return response if fileformat == 'unknown': message = "We couldn't recognize the file type" From 3e833fd2179d5282db7951bafafb28d2f009c120 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 4 Jan 2017 10:33:56 +0100 Subject: [PATCH 3/7] Revoke button --- rowers/templates/rower_form.html | 29 +++++++++++++++++++++++++++-- rowers/urls.py | 12 +++++++++++- rowers/views.py | 26 ++++++++++++++++++++++++++ 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/rowers/templates/rower_form.html b/rowers/templates/rower_form.html index e103f631..90674a5c 100644 --- a/rowers/templates/rower_form.html +++ b/rowers/templates/rower_form.html @@ -9,7 +9,7 @@

{% endif %}
-

Heart Rate Bands

+

Heart Rate Bands

@@ -22,8 +22,8 @@
-

Functional Threshold Power

+

Functional Threshold Power

{{ powerform.as_table }} @@ -34,6 +34,31 @@

+ {% if grants %} +

+

Applications

+
+ + + + + + + + + {% for grant in grants %} + + + + + + {% endfor %} + +
ApplicationScopeRevoke
{{ grant.application }}{{ grant.scope }} + Revoke +
+

+ {% endif %}
diff --git a/rowers/urls.py b/rowers/urls.py index d21b2692..94febda1 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -21,7 +21,16 @@ from rowers.serializers import ( StrokeDataSerializer, FavoriteChartSerializer, ) - + +from oauth2_provider.views import ( + AuthorizedTokensListView, + AuthorizedTokenDeleteView, + ) + +from oauth2_provider.views.base import ( + RevokeTokenView + ) + class WorkoutViewSet(viewsets.ModelViewSet): model = Workout #queryset = Workout.objects.all().order_by("-date", "-starttime") @@ -180,6 +189,7 @@ urlpatterns = [ url(r'^me/edit/$',views.rower_edit_view), url(r'^me/edit/(.+.*)/$',views.rower_edit_view), url(r'^me/c2authorize/$',views.rower_c2_authorize), + url(r'^me/revokeapp/(\d+)$',views.rower_revokeapp_view), url(r'^me/stravaauthorize/$',views.rower_strava_authorize), url(r'^me/sporttracksauthorize/$',views.rower_sporttracks_authorize), url(r'^me/sporttracksrefresh/$',views.rower_sporttracks_token_refresh), diff --git a/rowers/views.py b/rowers/views.py index 167ecb92..d6c8792b 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -75,6 +75,8 @@ import stravalib from stravalib.exc import ActivityUploadFailed,TimeoutExceeded from weather import get_wind_data +from oauth2_provider.models import Application,Grant,AccessToken + import django_rq queue = django_rq.get_queue('default') queuelow = django_rq.get_queue('low') @@ -4780,14 +4782,38 @@ def rower_edit_view(request,message=""): r = Rower.objects.get(user=request.user) form = RowerForm(instance=r) powerform = RowerPowerForm(instance=r) + grants = AccessToken.objects.filter(user=request.user) return render(request, 'rower_form.html', { 'form':form, 'powerform':powerform, + 'grants':grants, }) except Rower.DoesNotExist: return HttpResponse("This user doesn't exist") +@login_required() +def rower_revokeapp_view(request,id=0): + tokens = AccessToken.objects.filter(user=request.user,application=id) + refreshtokens = AccessToken.objects.filter(user=request.user,application=id) + for token in tokens: + token.revoke() + for token in refreshtokens: + token.revoke() + + r = Rower.objects.get(user=request.user) + form = RowerForm(instance=r) + powerform = RowerPowerForm(instance=r) + grants = AccessToken.objects.filter(user=request.user) + return render(request, 'rower_form.html', + { + 'form':form, + 'powerform':powerform, + 'grants':grants, + }) + + + class JSONResponse(HttpResponse): def __init__(self, data, **kwargs): content = JSONRenderer().render(data) From 467471437edd40772678b7e0d93183702e564996 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 4 Jan 2017 11:05:32 +0100 Subject: [PATCH 4/7] toggle rest stroke buttons --- rowers/interactiveplots.py | 15 +- rowers/templates/flexchart3.html | 265 ++++++++++++------------ rowers/templates/flexchart3otw.html | 3 +- rowers/templates/forcecurve_single.html | 41 ++-- rowers/views.py | 13 +- 5 files changed, 187 insertions(+), 150 deletions(-) diff --git a/rowers/interactiveplots.py b/rowers/interactiveplots.py index 248545b1..be61191e 100644 --- a/rowers/interactiveplots.py +++ b/rowers/interactiveplots.py @@ -125,7 +125,7 @@ def tailwind(bearing,vwind,winddir): from rowers.dataprep import nicepaceformat,niceformat from rowers.dataprep import timedeltaconv -def interactive_forcecurve(theworkouts): +def interactive_forcecurve(theworkouts,workstrokesonly=False): TOOLS = 'save,pan,box_zoom,wheel_zoom,reset,tap,hover,resize,crosshair' ids = [int(w.id) for w in theworkouts] @@ -133,10 +133,21 @@ def interactive_forcecurve(theworkouts): boattype = theworkouts[0].boattype columns = ['catch','slip','wash','finish','averageforce', - 'peakforceangle','peakforce','spm','distance'] + 'peakforceangle','peakforce','spm','distance', + 'workoutstate'] rowdata = dataprep.getsmallrowdata_db(columns,ids=ids) + workoutstateswork = [1,4,5,8,9,6,7] + workoutstatesrest = [3] + workoutstatetransition = [0,2,10,11,12,13] + + if workstrokesonly: + try: + rowdata = rowdata[~rowdata['workoutstate'].isin(workoutstatesrest)] + except KeyError: + pass + catchav = rowdata['catch'].mean() finishav = rowdata['finish'].mean() washav = rowdata['wash'].mean() diff --git a/rowers/templates/flexchart3.html b/rowers/templates/flexchart3.html index ecb92d10..6a8b6d35 100644 --- a/rowers/templates/flexchart3.html +++ b/rowers/templates/flexchart3.html @@ -8,159 +8,160 @@ {% localtime on %} {% block content %} - {{ js_res | safe }} - {{ css_res| safe }} +{{ js_res | safe }} +{{ css_res| safe }} - - - + + + - {{ the_script |safe }} +{{ the_script |safe }} - +

 

+ - - - -
-
-
+ + +
+
+ {% csrf_token %} {% if workstrokesonly %} - + + {% else %} - + + {% endif %} - - - If your data source allows, this will show or hide strokes taken during rest intervals. -
-
- Line Plot -
- -
- + + If your data source allows, this will show or hide strokes taken during rest intervals. +
+
+ Line Plot +
+ +
+
- - - {{ the_div|safe }} - + + + {{ the_div|safe }} +
{% if user.rower.rowerplan == 'pro' %} diff --git a/rowers/templates/flexchart3otw.html b/rowers/templates/flexchart3otw.html index 62938673..67ab99b7 100644 --- a/rowers/templates/flexchart3otw.html +++ b/rowers/templates/flexchart3otw.html @@ -174,10 +174,11 @@ {% csrf_token %} {% if workstrokesonly %} + {% else %} + {% endif %} - If your data source allows, this will show or hide strokes taken during rest intervals. diff --git a/rowers/templates/forcecurve_single.html b/rowers/templates/forcecurve_single.html index 2f7e83b1..c45300c4 100644 --- a/rowers/templates/forcecurve_single.html +++ b/rowers/templates/forcecurve_single.html @@ -8,22 +8,22 @@ {% localtime on %} {% block content %} - {{ js_res | safe }} - {{ css_res| safe }} +{{ js_res | safe }} +{{ css_res| safe }} - - - + + + - {{ the_script |safe }} +{{ the_script |safe }} - + -
+ {% endif %} + +
+
+ {% csrf_token %} + {% if workstrokesonly %} + + + {% else %} + + + {% endif %} +
+ If your data source allows, this will show or hide strokes taken during rest intervals. +
+
diff --git a/rowers/views.py b/rowers/views.py index d6c8792b..f80ff03a 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -1463,7 +1463,7 @@ def cum_flex(request,theuser=0, }) @user_passes_test(promember,login_url="/",redirect_field_name=None) -def workout_forcecurve_view(request,id=0): +def workout_forcecurve_view(request,id=0,workstrokesonly=False): row = Workout.objects.get(id=id) promember=0 mayedit=0 @@ -1478,7 +1478,15 @@ def workout_forcecurve_view(request,id=0): if not promember: return HttpResponseRedirect("/rowers/about/") - script,div,js_resources,css_resources = interactive_forcecurve([row]) + if request.method == 'POST' and 'workstrokesonly' in request.POST: + workstrokesonly = request.POST['workstrokesonly'] + if workstrokesonly == 'True': + workstrokesonly = True + else: + workstrokesonly = False + + script,div,js_resources,css_resources = interactive_forcecurve([row], + workstrokesonly=workstrokesonly) return render(request, 'forcecurve_single.html', @@ -1489,6 +1497,7 @@ def workout_forcecurve_view(request,id=0): 'css_res':css_resources, 'id':id, 'mayedit':mayedit, + 'workstrokesonly': not workstrokesonly, }) @login_required() From 41f7ab2ff5bf28b4a4aa22fcfbc9545c2dfb6ae4 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Wed, 4 Jan 2017 12:51:14 +0100 Subject: [PATCH 5/7] Chart permissions --- rowers/urls.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/rowers/urls.py b/rowers/urls.py index 94febda1..5d98324d 100644 --- a/rowers/urls.py +++ b/rowers/urls.py @@ -59,10 +59,23 @@ class RowerViewSet(viewsets.ModelViewSet): IsOwnerOrNot, ) + http_method_names = ['get','patch'] + + class FavoriteChartViewSet(viewsets.ModelViewSet): model = FavoriteChart serializer_class = FavoriteChartSerializer - queryset = FavoriteChart.objects.all() + #queryset = FavoriteChart.objects.all() + + def get_queryset(self): + r = Rower.objects.filter(user=self.request.user) + return FavoriteChart.objects.filter(user=r) + + permission_classes = ( + IsOwnerOrNot, + ) + + http_method_names = ['get','put','patch','delete'] class StrokeDataViewSet(viewsets.ModelViewSet): serializer_class = StrokeDataSerializer From 5f45d385f36967f58a852ec5959ac433e34ffd0b Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Thu, 5 Jan 2017 09:03:57 +0100 Subject: [PATCH 6/7] some more stuff related to strava bug --- rowers/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rowers/views.py b/rowers/views.py index f17bee1c..53abf953 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -859,7 +859,7 @@ def workout_tcxemail_view(request,id=0): }) except: successmessage = "" - message = "Something went wrong (strava export) "+str(sys.exc_info()[0]) + message = "Something went wrong (TCX export) "+str(sys.exc_info()[0]) with open("media/c2errors.log","a") as errorlog: errorstring = str(sys.exc_info()[0]) timestr = strftime("%Y%m%d-%H%M%S") From 1f056096d778d979983971356557669fa3351d79 Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Thu, 5 Jan 2017 17:10:33 +0100 Subject: [PATCH 7/7] sporttracksexport bug fixing --- rowers/sporttracksstuff.py | 7 ++++++- rowers/views.py | 3 +-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/rowers/sporttracksstuff.py b/rowers/sporttracksstuff.py index 7783a8ca..b0270b03 100644 --- a/rowers/sporttracksstuff.py +++ b/rowers/sporttracksstuff.py @@ -181,8 +181,10 @@ def createsporttracksworkoutdata(w): maxhr = int(row.df[' HRCur (bpm)'].max()) # adding diff, trying to see if this is valid - t = row.df.ix[:,'TimeStamp (sec)'].values-10*row.df.ix[0,'TimeStamp (sec)'] + #t = row.df.ix[:,'TimeStamp (sec)'].values-10*row.df.ix[0,'TimeStamp (sec)'] + t = row.df.ix[:,'TimeStamp (sec)'].values-row.df.ix[0,'TimeStamp (sec)'] t[0] = t[1] + d = row.df.ix[:,'cum_dist'].values d[0] = d[1] t = t.astype(int) @@ -196,9 +198,12 @@ def createsporttracksworkoutdata(w): try: lat = row.df[' latitude'].values lon = row.df[' longitude'].values + if not lat.std() and not lon.std(): + haslatlon = 0 except KeyError: haslatlon = 0 + haspower = 1 try: power = row.df[' Power (watts)'].values diff --git a/rowers/views.py b/rowers/views.py index 53abf953..698643ba 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -1078,7 +1078,6 @@ def workout_sporttracks_upload_view(request,id=0): url = "https://api.sporttracks.mobi/api/v2/fitnessActivities.json" response = requests.post(url,headers=headers,data=json.dumps(data)) - # check for duplicate error first if (response.status_code == 409 ): message = "Duplicate error" @@ -1093,7 +1092,7 @@ def workout_sporttracks_upload_view(request,id=0): return HttpResponseRedirect(url) else: s = response - message = "Something went wrong in workout_sporttracks_upload_view %s" % s + message = "Something went wrong in workout_sporttracks_upload_view: %s" % s.reason else: message = "You are not authorized to upload this workout"