From 4b80e30b74623ad22e232fa1804ab11131462c4f Mon Sep 17 00:00:00 2001 From: Sander Roosendaal Date: Fri, 5 May 2017 11:54:37 +0200 Subject: [PATCH] Strava, ST, RK done --- rowers/#runkeeperstuff.py# | 282 ---------------------------- rowers/c2stuff.py | 112 +++++++---- rowers/forms.py | 16 +- rowers/models.py | 13 +- rowers/runkeeperstuff.py | 94 +++++++++- rowers/sporttracksstuff.py | 65 ++++++- rowers/stravastuff.py | 64 +++++++ rowers/templates/document_form.html | 5 +- rowers/templates/export.html | 2 +- rowers/utils.py | 2 + rowers/views.py | 272 ++++++++++----------------- 11 files changed, 420 insertions(+), 507 deletions(-) delete mode 100644 rowers/#runkeeperstuff.py# diff --git a/rowers/#runkeeperstuff.py# b/rowers/#runkeeperstuff.py# deleted file mode 100644 index 25866bc8..00000000 --- a/rowers/#runkeeperstuff.py# +++ /dev/null @@ -1,282 +0,0 @@ -# All the functionality needed to connect to Runkeeper - -# Python -import oauth2 as oauth -import cgi -import requests -import requests.auth -import json -from django.utils import timezone -from datetime import datetime -import numpy as np -from dateutil import parser -import time -import math -from math import sin,cos,atan2,sqrt -import os,sys - -# Django -from django.shortcuts import render_to_response -from django.http import HttpResponseRedirect, HttpResponse,JsonResponse -from django.conf import settings -from django.contrib.auth import authenticate, login, logout -from django.contrib.auth.models import User -from django.contrib.auth.decorators import login_required - -# Project -# from .models import Profile -from rowingdata import rowingdata -import pandas as pd -from rowers.models import Rower,Workout - -from rowsandall_app.settings import ( - C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET, - STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET, - RUNKEEPER_CLIENT_ID, RUNKEEPER_CLIENT_SECRET,RUNKEEPER_REDIRECT_URI, - ) - -# Custom error class - to raise a NoTokenError -class RunKeeperNoTokenError(Exception): - def __init__(self,value): - self.value=value - - def __str__(self): - return repr(self.value) - -# Exponentially weighted moving average -# Used for data smoothing of the jagged data obtained by Strava -# See bitbucket issue 72 -def ewmovingaverage(interval,window_size): - # Experimental code using Exponential Weighted moving average - - 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'] - - interval2 = np.vstack((i_ewma1,i_ewma2[::-1])) - interval2 = np.mean( interval2, axis=0) # average - except ValueError: - interval2 = interval - - return interval2 - -from utils import geo_distance - - -# Custom exception handler, returns a 401 HTTP message -# with exception details in the json data -def custom_exception_handler(exc,message): - - response = { - "errors": [ - { - "code": str(exc), - "detail": message, - } - ] - } - - res = HttpResponse(message) - res.status_code = 401 - res.json = json.dumps(response) - - return res - -# Exchange access code for long-lived access token -def get_token(code): - client_auth = requests.auth.HTTPBasicAuth(RUNKEEPER_CLIENT_ID, RUNKEEPER_CLIENT_SECRET) - post_data = {"grant_type": "authorization_code", - "code": code, - "redirect_uri": RUNKEEPER_REDIRECT_URI, - "client_secret": RUNKEEPER_CLIENT_SECRET, - "client_id":RUNKEEPER_CLIENT_ID, - } - headers = {'user-agent': 'sanderroosendaal'} - response = requests.post("https://runkeeper.com/apps/token", - data=post_data, - headers=headers) - try: - token_json = response.json() - thetoken = token_json['access_token'] - except KeyError: - thetoken = 0 - - return thetoken - -# Make authorization URL including random string -def make_authorization_url(request): - # Generate a random string for the state parameter - # Save it for use later to prevent xsrf attacks - from uuid import uuid4 - state = str(uuid4()) - - params = {"client_id": RUNKEEPER_CLIENT_ID, - "response_type": "code", - "redirect_uri": RUNKEEPER_REDIRECT_URI, - } - import urllib - url = "https://www.runkeeper.com/opps/authorize" +urllib.urlencode(params) - - return HttpResponseRedirect(url) - -# Get list of workouts available on Runkeeper -def get_runkeeper_workout_list(user): - r = Rower.objects.get(user=user) - if (r.runkeepertoken == '') or (r.runkeepertoken is None): - s = "Token doesn't exist. Need to authorize" - return custom_exception_handler(401,s) - else: - # ready to fetch. Hurray - authorizationstring = str('Bearer ' + r.runkeepertoken) - headers = {'Authorization': authorizationstring, - 'user-agent': 'sanderroosendaal', - 'Content-Type': 'application/json'} - url = "https://api.runkeeper.com/fitnessActivities" - s = requests.get(url,headers=headers) - - return s - -# Get workout summary data by Runkeeper ID -def get_runkeeper_workout(user,runkeeperid): - r = Rower.objects.get(user=user) - if (r.runkeepertoken == '') or (r.runkeepertoken is None): - return custom_exception_handler(401,s) - s = "Token doesn't exist. Need to authorize" - else: - # ready to fetch. Hurray - authorizationstring = str('Bearer ' + r.runkeepertoken) - headers = {'Authorization': authorizationstring, - 'user-agent': 'sanderroosendaal', - 'Content-Type': 'application/json'} - url = "https://api.runkeeper.com/fitnessActivities/"+str(runkeeperid) - s = requests.get(url,headers=headers) - - return s - -# Create Workout Data for upload to SportTracks -def createrunkeeperworkoutdata(w): - filename = w.csvfilename - try: - row = rowingdata(filename) - except: - return 0 - - averagehr = int(row.df[' HRCur (bpm)'].mean()) - maxhr = int(row.df[' HRCur (bpm)'].max()) - duration = w.duration.hour*3600 - duration += w.duration.minute*60 - duration += w.duration.second - duration += +1.0e-6*w.duration.microsecond - - # 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-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) - d = d.astype(int) - spm = row.df[' Cadence (stokes/min)'].astype(int) - spm[0] = spm[1] - hr = row.df[' HRCur (bpm)'].astype(int) - - haslatlon=1 - - 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 - - # path data - if haslatlon: - locdata = [] - for e in zip(t,lat,lon): - point = {'timestamp':e[0], - 'latitude':e[1], - 'longitude':e[2],} - locdata.append(point) - - hrdata = [] - for e in zip(t,hr): - point = {'timestamp':e[0], - 'heart_rate':e[1] - } - hrdata.append(point) - - distancedata = [] - for e in zip(t,d): - point = {'timestamp':e[0], - 'distance':e[1] - } - distancedata.append(point) - - start_time = w.startdatetime.strftime("%a, %d %b %Y %H:%M:%S") - - if haslatlon: - data = { - "type": "Rowing", - "start_time": start_time, - "total_distance": int(w.distance), - "duration": duration, - "notes": w.notes, - "average_heart_rate": averagehr, - "path": locdata, - "distance": distancedata, - "heartrate": hrdata, - "post_to_twitter":"false", - "post_to_facebook":"false", - } - else: - data = { - "type": "Rowing", - "start_time": start_time, - "total_distance": int(w.distance), - "duration": duration, - "notes": w.notes, - "avg_heartrate": averagehr, - "distance": distancedata, - "heartrate": hrdata, - "post_to_twitter":"false", - "post_to_facebook":"false", - } - - - return data - -# Obtain Runkeeper Workout ID from the response returned on successful -# upload -def getidfromresponse(response): - uri = response.headers["Location"] - id = uri[len(uri)-9:] - - return int(id) - - -# Get user id, having access token -# Handy for checking if the API access is working -def get_userid(access_token): - authorizationstring = str('Bearer ' + access_token) - headers = {'Authorization': authorizationstring, - 'user-agent': 'sanderroosendaal', - 'Content-Type': 'application/json'} - import urllib - url = "https://api.runkeeper.com/user" - response = requests.get(url,headers=headers) - - - me_json = response.json() - - try: - res = me_json['userID'] - except KeyError: - res = 0 - - return res diff --git a/rowers/c2stuff.py b/rowers/c2stuff.py index b73ca700..745ca19f 100644 --- a/rowers/c2stuff.py +++ b/rowers/c2stuff.py @@ -27,6 +27,7 @@ from rowingdata import rowingdata import pandas as pd import numpy as np from rowers.models import Rower,Workout +from rowers.models import checkworkoutuser import sys import urllib from requests import Request, Session @@ -60,13 +61,26 @@ def custom_exception_handler(exc,message): return res -# Check if workout is owned by this user -def checkworkoutuser(user,workout): - try: - r = Rower.objects.get(user=user) - return (workout.user == r) - except Rower.DoesNotExist: - return(False) +# Checks if user has Concept2 tokens, resets tokens if they are +# expired. +def c2_open(user): + r = Rower.objects.get(user=user) + if (r.c2token == '') or (r.c2token is None): + s = "Token doesn't exist. Need to authorize" + raise C2NoTokenError("User has no token") + else: + if (timezone.now()>r.tokenexpirydate): + res = rower_c2_token_refresh(user) + if res[0] != None: + thetoken = res[0] + else: + raise C2NoTokenError("User has no token") + else: + thetoken = r.c2token + + return thetoken + + # convert datetime object to seconds def makeseconds(t): @@ -249,9 +263,13 @@ def createc2workoutdata(w): row = rowingdata(filename) except IOError: return 0 - - averagehr = int(row.df[' HRCur (bpm)'].mean()) - maxhr = int(row.df[' HRCur (bpm)'].max()) + + try: + averagehr = int(row.df[' HRCur (bpm)'].mean()) + maxhr = int(row.df[' HRCur (bpm)'].max()) + except ValueError: + averagehr = 0 + maxhr = 0 # adding diff, trying to see if this is valid t = 10*row.df.ix[:,'TimeStamp (sec)'].values-10*row.df.ix[0,'TimeStamp (sec)'] @@ -265,7 +283,10 @@ def createc2workoutdata(w): p = p.astype(int) spm = row.df[' Cadence (stokes/min)'].astype(int) spm[0] = spm[1] - hr = row.df[' HRCur (bpm)'].astype(int) + try: + hr = row.df[' HRCur (bpm)'].astype(int) + except ValueError: + hr = 0*d stroke_data = [] for i in range(len(t)): thisrecord = {"t":t[i],"d":d[i],"p":p[i],"spm":spm[i],"hr":hr[i]} @@ -521,34 +542,49 @@ def process_callback(request): # Uploading workout def workout_c2_upload(user,w): response = 'trying C2 upload' - r = Rower.objects.get(user=user) - if (r.c2token == '') or (r.c2token is None): - s = "Token doesn't exist. Need to authorize" - return custom_exception_handler(401,s) - elif (timezone.now()>r.tokenexpirydate): - s = "Token expired. Needs to refresh." - return custom_exception_handler(401,s) - else: - # ready to upload. Hurray - if (checkworkoutuser(user,w)): - c2userid = get_userid(r.c2token) - data = createc2workoutdata(w) - authorizationstring = str('Bearer ' + r.c2token) - headers = {'Authorization': authorizationstring, - 'user-agent': 'sanderroosendaal', - 'Content-Type': 'application/json'} - import urllib - url = "https://log.concept2.com/api/users/%s/results" % (c2userid) - response = requests.post(url,headers=headers,data=json.dumps(data)) - if (response.status_code == 201): - s= json.loads(response.text) - c2id = s['data']['id'] - w.uploadedtoc2 = c2id - w.save() - else: - response = "You are not authorized to upload this workout" + thetoken = c2_open(user) - return response + r = Rower.objects.get(user=user) + + # ready to upload. Hurray + if (checkworkoutuser(user,w)): + c2userid = get_userid(r.c2token) + if not c2userid: + raise C2NoTokenError + + data = createc2workoutdata(w) + if data == 0: + return "Error: No data file. Contact info@rowsandall.com if the problem persists",0 + + authorizationstring = str('Bearer ' + r.c2token) + headers = {'Authorization': authorizationstring, + 'user-agent': 'sanderroosendaal', + 'Content-Type': 'application/json'} + import urllib + url = "https://log.concept2.com/api/users/%s/results" % (c2userid) + response = requests.post(url,headers=headers,data=json.dumps(data)) + + if (response.status_code == 409 ): + message = "Duplicate error" + w.uploadedtoc2 = -1 + c2id = -1 + w.save() + elif (response.status_code == 201 or response.status_code == 200): + try: + s= json.loads(response.text) + c2id = s['data']['id'] + w.uploadedtoc2 = c2id + w.save() + message = "" + except: + message = "Something went wrong in workout_c2_upload_view. Response code 200/201 but C2 sync failed: "+response.text + c2id = 0 + + else: + message = "You are not authorized to upload this workout" + c2id = 0 + + return message,c2id # This is token refresh. Looks for tokens in our database, then refreshes def rower_c2_token_refresh(user): diff --git a/rowers/forms.py b/rowers/forms.py index da1c5ed0..d5a1715d 100644 --- a/rowers/forms.py +++ b/rowers/forms.py @@ -83,7 +83,21 @@ class UploadOptionsForm(forms.Form): choices=plotchoices, initial='timeplot', label='Plot Type') - upload_to_C2 = forms.BooleanField(initial=False,required=False) + upload_to_C2 = forms.BooleanField(initial=False,required=False, + label='Upload to Concept2 logbook') + upload_to_Strava = forms.BooleanField(initial=False,required=False, + label='Upload to Strava') + upload_to_SportTracks = forms.BooleanField(initial=False,required=False, + label='Upload to SportTracks') + upload_to_RunKeeper = forms.BooleanField(initial=False,required=False, + label='Upload to RunKeeper') + upload_to_MapMyFitness = forms.BooleanField(initial=False, + required=False, + label='Upload to MapMyFitness') + upload_to_TrainingPeaks = forms.BooleanField(initial=False, + required=False, + label='Upload to TrainingPeaks') + # do_physics = forms.BooleanField(initial=False,required=False,label='Power Estimate (OTW)') makeprivate = forms.BooleanField(initial=False,required=False, label='Make Workout Private') diff --git a/rowers/models.py b/rowers/models.py index 4ff61e2a..9102a17a 100644 --- a/rowers/models.py +++ b/rowers/models.py @@ -209,7 +209,6 @@ class Rower(models.Model): runkeepertoken = models.CharField(default='',max_length=200, blank=True,null=True) - # runkeepertokenexpirydate = models.DateTimeField(blank=True,null=True) # runkeeperrefreshtoken = models.CharField(default='',max_length=200, # blank=True,null=True) @@ -324,6 +323,15 @@ class BaseFavoriteFormSet(BaseFormSet): if not yparam2: yparam2 = 'None' +# Check if workout is owned by this user +def checkworkoutuser(user,workout): + try: + r = Rower.objects.get(user=user) + return (workout.user == r) + except Rower.DoesNotExist: + return(False) + + # Workout class Workout(models.Model): workouttypes = ( @@ -398,7 +406,8 @@ class Workout(models.Model): uploadedtounderarmour = models.IntegerField(default=0) uploadedtotp = models.IntegerField(default=0) uploadedtorunkeeper = models.IntegerField(default=0) - + runkeeperuri = models.CharField(default='',max_length=200) + # empower stuff inboard = models.FloatField(default=0.88) oarlength = models.FloatField(default=2.89) diff --git a/rowers/runkeeperstuff.py b/rowers/runkeeperstuff.py index 466709a8..dc2b3969 100644 --- a/rowers/runkeeperstuff.py +++ b/rowers/runkeeperstuff.py @@ -27,7 +27,7 @@ from django.contrib.auth.decorators import login_required # from .models import Profile from rowingdata import rowingdata import pandas as pd -from rowers.models import Rower,Workout +from rowers.models import Rower,Workout,checkworkoutuser from rowsandall_app.settings import ( C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET, @@ -86,6 +86,17 @@ def custom_exception_handler(exc,message): return res +# Checks if user has SportTracks token, renews them if they are expired +def runkeeper_open(user): + r = Rower.objects.get(user=user) + if (r.runkeepertoken == '') or (r.runkeepertoken is None): + s = "Token doesn't exist. Need to authorize" + raise RunKeeperNoTokenError("User has no token") + else: + thetoken = r.runkeepertoken + + return thetoken + # Exchange access code for long-lived access token def get_token(code): client_auth = requests.auth.HTTPBasicAuth(RUNKEEPER_CLIENT_ID, RUNKEEPER_CLIENT_SECRET) @@ -261,6 +272,26 @@ def getidfromresponse(response): return int(id) +def geturifromid(access_token,id): + authorizationstring = str('Bearer ' + access_token) + headers = {'Authorization': authorizationstring, + 'user-agent': 'sanderroosendaal', + 'Content-Type': 'application/json'} + import urllib + url = "https://api.runkeeper.com/fitnessActivities/"+str(id) + response = requests.get(url,headers=headers) + try: + me_json = response.json() + except: + return '' + + try: + res = me_json['uri'] + except KeyError: + res = '' + + return res + # Get user id, having access token # Handy for checking if the API access is working @@ -277,11 +308,66 @@ def get_userid(access_token): try: me_json = response.json() except: - return 0 + return '' try: res = me_json['userID'] except KeyError: - res = 0 + res = '' + + print res,'userID' + return str(res) + +def workout_runkeeper_upload(user,w): + message = "" + rkid = 0 + + r = w.user + + + thetoken = runkeeper_open(r.user) + + # ready to upload. Hurray + + if (checkworkoutuser(user,w)): + data = createrunkeeperworkoutdata(w) + if not data: + message = "Data error" + rkid = 0 + return message, rkid - return res + authorizationstring = str('Bearer ' + thetoken) + headers = {'Authorization': authorizationstring, + 'user-agent': 'sanderroosendaal', + 'Content-Type': 'application/vnd.com.runkeeper.NewFitnessActivity+json', + 'Content-Length':'nnn'} + + url = "https://api.runkeeper.com/fitnessActivities" + response = requests.post(url,headers=headers,data=json.dumps(data)) + + # check for duplicate error first + if (response.status_code == 409 ): + message = "Duplicate error" + w.uploadedtorunkeeper = -1 + rkid = -1 + w.save() + return message, rkid + elif (response.status_code == 201 or response.status_code==200): + rkid = getidfromresponse(response) + rkuri = geturifromid(thetoken,rkid) + w.uploadedtorunkeeper = rkid + w.runkeeperuri = rkuri + w.save() + return '',rkid + else: + s = response + message = "Something went wrong in workout_runkeeper_upload_view: %s - %s" % (s.reason,s.text) + rkid = 0 + return message, rkid + + else: + message = "You are not authorized to upload this workout" + rkid = 0 + return message, rkid + + return message,rkid diff --git a/rowers/sporttracksstuff.py b/rowers/sporttracksstuff.py index c6a33100..053f6915 100644 --- a/rowers/sporttracksstuff.py +++ b/rowers/sporttracksstuff.py @@ -29,7 +29,7 @@ from django.contrib.auth.decorators import login_required # from .models import Profile from rowingdata import rowingdata import pandas as pd -from rowers.models import Rower,Workout +from rowers.models import Rower,Workout,checkworkoutuser from rowsandall_app.settings import C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET, STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET, SPORTTRACKS_CLIENT_SECRET, SPORTTRACKS_CLIENT_ID, SPORTTRACKS_REDIRECT_URI @@ -61,6 +61,21 @@ def custom_exception_handler(exc,message): return res +# Checks if user has SportTracks token, renews them if they are expired +def sporttracks_open(user): + r = Rower.objects.get(user=user) + if (r.sporttrackstoken == '') or (r.sporttrackstoken is None): + s = "Token doesn't exist. Need to authorize" + raise SportTracksNoTokenError("User has no token") + else: + if (timezone.now()>r.sporttrackstokenexpirydate): + thetoken = sporttracksstuff.rower_sporttracks_token_refresh(user) + else: + thetoken = r.sporttrackstoken + + return thetoken + + # Refresh ST token using refresh token def do_refresh_token(refreshtoken): client_auth = requests.auth.HTTPBasicAuth(SPORTTRACKS_CLIENT_ID, SPORTTRACKS_CLIENT_SECRET) @@ -303,3 +318,51 @@ def getidfromresponse(response): return int(id) +def workout_sporttracks_upload(user,w): + message = "" + stid = 0 + # ready to upload. Hurray + r = w.user + + thetoken = sporttracks_open(user) + + if (checkworkoutuser(user,w)): + data = createsporttracksworkoutdata(w) + if not data: + message = "Data error" + stid = 0 + return message,stid + + authorizationstring = str('Bearer ' + thetoken) + headers = {'Authorization': authorizationstring, + 'user-agent': 'sanderroosendaal', + 'Content-Type': 'application/json'} + + 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" + w.uploadedtosporttracks = -1 + stid = -1 + w.save() + return message, stid + elif (response.status_code == 201 or response.status_code==200): + s= json.loads(response.text) + stid = getidfromresponse(response) + w.uploadedtosporttracks = stid + w.save() + return '',stid + else: + s = response + message = "Something went wrong in workout_sporttracks_upload_view: %s" % s.reason + stid = 0 + return message,stid + + else: + message = "You are not authorized to upload this workout" + stid = 0 + return message,stid + + return message,stid diff --git a/rowers/stravastuff.py b/rowers/stravastuff.py index e4669000..270ff2cd 100644 --- a/rowers/stravastuff.py +++ b/rowers/stravastuff.py @@ -29,8 +29,10 @@ from django.contrib.auth.decorators import login_required from rowingdata import rowingdata import pandas as pd from rowers.models import Rower,Workout +from rowers.models import checkworkoutuser import stravalib +from stravalib.exc import ActivityUploadFailed,TimeoutExceeded from rowsandall_app.settings import C2_CLIENT_ID, C2_REDIRECT_URI, C2_CLIENT_SECRET, STRAVA_CLIENT_ID, STRAVA_REDIRECT_URI, STRAVA_CLIENT_SECRET @@ -77,6 +79,15 @@ def custom_exception_handler(exc,message): return res +# Custom error class - to raise a NoTokenError +class StravaNoTokenError(Exception): + def __init__(self,value): + self.value=value + + def __str__(self): + return repr(self.value) + + # Exchange access code for long-lived access token def get_token(code): client_auth = requests.auth.HTTPBasicAuth(STRAVA_CLIENT_ID, STRAVA_CLIENT_SECRET) @@ -277,3 +288,56 @@ def handle_stravaexport(f2,workoutname,stravatoken,description=''): return (res.id,message) +def workout_strava_upload(user,w): + message = "" + stravaid=-1 + r = Rower.objects.get(user=user) + res = -1 + if (r.stravatoken == '') or (r.stravatoken is None): + s = "Token doesn't exist. Need to authorize" + raise StravaNoTokenError + else: + if (checkworkoutuser(user,w)): + try: + tcxfile = createstravaworkoutdata(w) + if tcxfile: + with open(tcxfile,'rb') as f: + res,mes = handle_stravaexport(f,w.name, + r.stravatoken, + description=w.notes+'\n from '+w.workoutsource+' via rowsandall.com') + if res==0: + message = mes + w.uploadedtostrava = -1 + stravaid = -1 + w.save() + try: + os.remove(tcxfile) + except WindowsError: + pass + return message,stravaid + + w.uploadedtostrava = res + w.save() + try: + os.remove(tcxfile) + except WindowsError: + pass + message = '' + stravaid = res + return message,stravaid + else: + message = "Strava Upload error" + w.uploadedtostrava = -1 + stravaid = -1 + w.save() + return message, stravaid + + except ActivityUploadFailed as e: + message = "Strava Upload error: %s" % e + w.uploadedtostrava = -1 + stravaid = -1 + w.save() + os.remove(tcxfile) + return message,stravaid + return message,stravaid + return message,stravaid diff --git a/rowers/templates/document_form.html b/rowers/templates/document_form.html index 26d6d14f..6c316df4 100644 --- a/rowers/templates/document_form.html +++ b/rowers/templates/document_form.html @@ -35,7 +35,10 @@

- You can select one static plot to be generated immediately for this workout. You can select to upload to Concept2 automatically. If you check "make private", this workout will not be visible to your followers and will not show up in your teams' workouts list. + You can select one static plot to be generated immediately for + this workout. You can select to upload to major fitness + platforms automatically. + If you check "make private", this workout will not be visible to your followers and will not show up in your teams' workouts list.

diff --git a/rowers/templates/export.html b/rowers/templates/export.html index 696b9709..006f9f1b 100644 --- a/rowers/templates/export.html +++ b/rowers/templates/export.html @@ -106,7 +106,7 @@ {% endif %} {% else %}

- + Runkeeper icon
{% endif %} diff --git a/rowers/utils.py b/rowers/utils.py index 93594ba0..9d345c46 100644 --- a/rowers/utils.py +++ b/rowers/utils.py @@ -3,6 +3,8 @@ import numpy as np lbstoN = 4.44822 + + def serialize_list(value,token=','): assert(isinstance(value, list) or isinstance(value, tuple) or isinstance(value,np.ndarray)) return token.join([unicode(s) for s in value]) diff --git a/rowers/views.py b/rowers/views.py index 99d52253..c12c58ac 100644 --- a/rowers/views.py +++ b/rowers/views.py @@ -51,12 +51,13 @@ import os,sys import datetime import iso8601 import c2stuff -from c2stuff import C2NoTokenError -from runkeeperstuff import RunKeeperNoTokenError -from sporttracksstuff import SportTracksNoTokenError +from c2stuff import C2NoTokenError,c2_open +from runkeeperstuff import RunKeeperNoTokenError,runkeeper_open +from sporttracksstuff import SportTracksNoTokenError,sporttracks_open from tpstuff import TPNoTokenError from iso8601 import ParseError import stravastuff +from stravastuff import StravaNoTokenError import sporttracksstuff import underarmourstuff import tpstuff @@ -261,6 +262,7 @@ def splitstdata(lijst): return [np.array(t),np.array(latlong)] from utils import geo_distance,serialize_list,deserialize_list +from rowers.models import checkworkoutuser # Check if a user is a Coach member def iscoachmember(user): @@ -369,14 +371,6 @@ def sendmail(request): else: return HttpResponseRedirect('/rowers/email/') -# Check if workout belongs to this user -def checkworkoutuser(user,workout): - try: - r = Rower.objects.get(user=user) - managers = [team.manager for team in workout.team.all()] - return (workout.user == r or user in managers) - except Rower.DoesNotExist: - return(False) # Create workout data from Strava or Concept2 # data and create the associated Workout object and save it @@ -1039,38 +1033,7 @@ def add_workout_from_underarmourdata(user,importid,data): return (id,message) -# Checks if user has Concept2 tokens, resets tokens if they are -# expired. -def c2_open(user): - r = Rower.objects.get(user=user) - if (r.c2token == '') or (r.c2token is None): - s = "Token doesn't exist. Need to authorize" - raise C2NoTokenError("User has no token") - else: - if (timezone.now()>r.tokenexpirydate): - res = c2stuff.rower_c2_token_refresh(user) - if res[0] != None: - thetoken = res[0] - else: - raise C2NoTokenError("User has no token") - else: - thetoken = r.c2token - - return thetoken -# Checks if user has SportTracks token, renews them if they are expired -def sporttracks_open(user): - r = Rower.objects.get(user=user) - if (r.sporttrackstoken == '') or (r.sporttrackstoken is None): - s = "Token doesn't exist. Need to authorize" - raise SportTracksNoTokenError("User has no token") - else: - if (timezone.now()>r.sporttrackstokenexpirydate): - thetoken = sporttracksstuff.rower_sporttracks_token_refresh(user) - else: - thetoken = r.sporttrackstoken - - return thetoken # Checks if user has UnderArmour token, renews them if they are expired def underarmour_open(user): @@ -1109,16 +1072,6 @@ def tp_open(user): return thetoken -# Checks if user has SportTracks token, renews them if they are expired -def runkeeper_open(user): - r = Rower.objects.get(user=user) - if (r.runkeepertoken == '') or (r.runkeepertoken is None): - s = "Token doesn't exist. Need to authorize" - raise RunKeeperNoTokenError("User has no token") - else: - thetoken = r.runkeepertoken - - return thetoken # Export workout to TCX and send to user's email address @login_required() @@ -1395,76 +1348,20 @@ def workout_c2_upload_view(request,id=0): raise Http404("Workout doesn't exist") try: - thetoken = c2_open(r.user) + message,c2id = c2stuff.workout_c2_upload(request.user,w) except C2NoTokenError: - return HttpResponseRedirect("/rowers/me/c2authorize/") + return HttpResponseRedirect("/rowers/me/c2authorize/") - if (checkworkoutuser(request.user,w)): - c2userid = c2stuff.get_userid(thetoken) - if not c2userid: - return HttpResponseRedirect("/rowers/me/c2authorize") - - data = c2stuff.createc2workoutdata(w) - if data == 0: - message = "Error: No data file. Contact info@rowsandall.com if this problem persists" - url = reverse(workout_export_view, - kwargs = { - 'message':str(message), - 'id':str(w.id), - }) - - return HttpResponseRedirect(url) - - authorizationstring = str('Bearer ' + thetoken) - headers = {'Authorization': authorizationstring, - 'user-agent': 'sanderroosendaal', - 'Content-Type': 'application/json'} - try: - url = "https://log.concept2.com/api/users/%s/results" % (c2userid) - response = requests.post(url,headers=headers,data=json.dumps(data)) - except: - message = "Unexpected Error: "+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") - errorlog.write(timestr+errorstring+"\r\n") - - # check for duplicate error first - if (response.status_code == 409 ): - message = "Duplicate error" - w.uploadedtoc2 = -1 - w.save() - elif (response.status_code == 201 or response.status_code == 200): - try: - s= json.loads(response.text) - c2id = s['data']['id'] - w.uploadedtoc2 = c2id - w.save() - url = "/rowers/workout/"+str(w.id)+"/export" - return HttpResponseRedirect(url) - except: - message = "Something went wrong in workout_c2_upload_view. Response code 200/201 but C2 sync failed: "+response.text - with open("media/c2errors.log","a") as errorlog: - errorstring = str(sys.exc_info()[0]) - timestr = strftime("%Y%m%d-%H%M%S") - errorlog.write(timestr+errorstring+"\r\n") - - - else: - s = response - message = "Something went wrong in workout_c2_upload_view. C2 sync failed." - with open("media/c2errors.log","a") as errorlog: - errorstring = str(sys.exc_info()[0]) - timestr = strftime("%Y%m%d-%H%M%S") - errorlog.write(timestr+errorstring+"\r\n") - + if message: + url = reverse(workout_export_view, + kwargs = { + 'message':str(message), + 'id':str(w.id), + }) else: - message = "You are not authorized to upload this workout" - - url = reverse(workout_export_view, - kwargs = { - 'message':str(message), - 'id':str(w.id), + url = reverse(workout_export_view, + kwargs = { + 'id':str(w.id), }) return HttpResponseRedirect(url) @@ -4604,7 +4501,7 @@ def workout_otwpowerplot_view(request,id=0,message="",successmessage=""): 'the_div':div, 'mayedit':mayedit}) -# the page where you can chose where to export this workout +# the page where you can choose where to export this workout @login_required() def workout_export_view(request,id=0, message="", successmessage=""): request.session[translation.LANGUAGE_SESSION_KEY] = USER_LANGUAGE @@ -5610,6 +5507,8 @@ def workout_getrunkeeperworkout_view(request,runkeeperid): id,message = add_workout_from_runkeeperdata(request.user,runkeeperid,data) w = Workout.objects.get(id=id) w.uploadedtorunkeeper=runkeeperid + thetoken = runkeeper_open(request.user) + w.runkeeperuri = runkeeperstuff.geturifromid(thetoken,runkeeperid) w.save() if message: url = reverse(workout_edit_view, @@ -5820,6 +5719,31 @@ def workout_upload_view(request,message="", except KeyError: upload_toc2 = False + try: + upload_tostrava = uploadoptions['upload_to_Strava'] + except KeyError: + upload_tostrava = False + + try: + upload_tost = uploadoptions['upload_to_SportTracks'] + except KeyError: + upload_tost = False + + try: + upload_tork = uploadoptions['upload_to_RunKeeper'] + except KeyError: + upload_tork = False + + try: + upload_toua = uploadoptions['upload_to_MapMyFitness'] + except KeyError: + upload_toua = False + + try: + upload_totp = uploadoptions['upload_to_TrainingPeaks'] + except KeyError: + upload_totp = False + r = Rower.objects.get(user=request.user) if request.method == 'POST': form = DocumentsForm(request.POST,request.FILES) @@ -5836,6 +5760,11 @@ def workout_upload_view(request,message="", make_plot = optionsform.cleaned_data['make_plot'] plottype = optionsform.cleaned_data['plottype'] upload_to_c2 = optionsform.cleaned_data['upload_to_C2'] + upload_to_strava = optionsform.cleaned_data['upload_to_Strava'] + upload_to_st = optionsform.cleaned_data['upload_to_SportTracks'] + upload_to_rk = optionsform.cleaned_data['upload_to_RunKeeper'] + upload_to_ua = optionsform.cleaned_data['upload_to_MapMyFitness'] + upload_to_tp = optionsform.cleaned_data['upload_to_TrainingPeaks'] makeprivate = optionsform.cleaned_data['makeprivate'] uploadoptions = { @@ -5843,6 +5772,11 @@ def workout_upload_view(request,message="", 'make_plot':make_plot, 'plottype':plottype, 'upload_to_C2':upload_to_c2, + 'upload_to_Strava':upload_to_strava, + 'upload_to_SportTracks':upload_to_st, + 'upload_to_RunKeeper':upload_to_rk, + 'upload_to_MapMyFitness':upload_to_ua, + 'upload_to_TrainingPeaks':upload_to_tp, } @@ -5933,65 +5867,49 @@ def workout_upload_view(request,message="", filename=fullpathimagename) i.save() - # upload to C2 - if (upload_to_c2): - try: - thetoken = c2_open(request.user) - except C2NoTokenError: - return HttpResponseRedirect("/rowers/me/c2authorize/") - try: - c2userid = c2stuff.get_userid(thetoken) - if not c2userid: - return HttpResponseRedirect("/rowers/me/c2authorize") - data = c2stuff.createc2workoutdata(w) - authorizationstring = str('Bearer ' + thetoken) - headers = {'Authorization': authorizationstring, - 'user-agent': 'sanderroosendaal', - 'Content-Type': 'application/json'} + # upload to C2 + if (upload_to_c2): + try: + c2message,c2id = c2stuff.workout_c2_upload(request.user,w) + except C2NoTokenError: + pass + if (upload_to_strava): + try: + stravamessage,stravaid = stravastuff.workout_strava_upload( + request.user,w + ) + except StravaNoTokenError: + pass - url = "https://log.concept2.com/api/users/%s/results" % (c2userid) - response = requests.post(url,headers=headers,data=json.dumps(data)) + if (upload_to_st): + try: + stmessage,stid = sporttracksstuff.workout_sporttracks_upload( + request.user,w + ) + except SportTracksNoTokenError: + pass + + if (upload_to_rk): + try: + rkmessage,rkid = runkeeperstuff.workout_runkeeper_upload( + request.user,w + ) + except RunKeeperNoTokenError: + pass + + if message: + url = reverse(workout_edit_view, + kwargs={ + 'message':message, + 'id':w.id, + }) - # response = c2stuff.workout_c2_upload(request.user,w) - if (response.status_code != 201 and response.status_code != 200): - if settings.DEBUG: - return HttpResponse(response) - else: - message = "C2 upload failed" - url = reverse(workout_edit_view, - kwargs={ - 'message':message, - 'id':str(w.id), - }) - return HttpResponseRedirect(url) - else: - s= json.loads(response.text) - c2id = s['data']['id'] - w.uploadedtoc2 = c2id - w.save() - except: - message = "C2 upload failed" - url = reverse(workout_edit_view, - kwargs={ - 'message':message, - 'id':str(w.id), - }) - return HttpResponseRedirect(url) - - - if message: - url = reverse(workout_edit_view, - kwargs={ - 'message':message, - 'id':w.id, - }) - - else: - url = reverse(workout_edit_view, - kwargs = { - 'id':w.id, - }) - return HttpResponseRedirect(url) + else: + url = reverse(workout_edit_view, + kwargs = { + 'id':w.id, + }) + return HttpResponseRedirect(url) else: response = render(request, 'document_form.html',